From aa3d76452ee75ecab845f734eeab299494042065 Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 21:24:51 -0800 Subject: [PATCH 1/9] protoss --- truffle/app/app_build_pb2.py | 6 +- truffle/app/app_build_pb2_grpc.py | 4 +- truffle/app/app_install_pb2.py | 25 +- truffle/app/app_install_pb2.pyi | 14 +- truffle/app/app_install_pb2_grpc.py | 4 +- truffle/app/app_pb2.py | 48 +++ truffle/app/app_pb2.pyi | 69 +++++ .../{system_pb2_grpc.py => app_pb2_grpc.py} | 4 +- truffle/app/app_runtime_pb2.py | 41 +++ truffle/app/app_runtime_pb2.pyi | 21 ++ truffle/app/app_runtime_pb2_grpc.py | 97 +++++++ truffle/app/app_type_pb2.py | 36 --- truffle/app/app_type_pb2.pyi | 16 - truffle/app/background_feed_pb2.py | 45 --- truffle/app/background_pb2.py | 102 +++---- truffle/app/background_pb2.pyi | 115 +++----- truffle/app/background_pb2_grpc.py | 75 +---- truffle/app/curator_pb2.py | 57 ---- truffle/app/curator_pb2.pyi | 71 ----- truffle/app/curator_pb2_grpc.py | 183 ------------ truffle/app/default_app_manifest_pb2.py | 18 +- truffle/app/default_app_manifest_pb2.pyi | 26 +- truffle/app/default_app_manifest_pb2_grpc.py | 4 +- truffle/app/foreground_pb2.py | 18 +- truffle/app/foreground_pb2.pyi | 41 ++- truffle/app/foreground_pb2_grpc.py | 4 +- truffle/app/system_pb2.py | 38 --- truffle/app/system_pb2.pyi | 22 -- truffle/app/task_runtime_pb2.py | 128 -------- truffle/app/task_runtime_pb2.pyi | 124 -------- truffle/app/task_runtime_pb2_grpc.py | 274 ------------------ truffle/common/content_pb2.py | 6 +- truffle/common/content_pb2_grpc.py | 4 +- truffle/common/file_pb2.py | 6 +- truffle/common/file_pb2_grpc.py | 4 +- truffle/common/icon_pb2.py | 6 +- truffle/common/icon_pb2_grpc.py | 4 +- truffle/common/led_states_pb2.py | 40 --- truffle/common/led_states_pb2.pyi | 47 --- truffle/common/led_states_pb2_grpc.py | 24 -- truffle/common/tool_provider_pb2.py | 6 +- truffle/common/tool_provider_pb2_grpc.py | 4 +- truffle/infer/convo/conversation_pb2.py | 6 +- truffle/infer/convo/conversation_pb2_grpc.py | 4 +- truffle/infer/convo/msg_pb2.py | 6 +- truffle/infer/convo/msg_pb2_grpc.py | 4 +- truffle/infer/embedding_pb2.py | 6 +- truffle/infer/embedding_pb2_grpc.py | 4 +- truffle/infer/finishreason_pb2.py | 6 +- truffle/infer/finishreason_pb2_grpc.py | 4 +- truffle/infer/gencfg_pb2.py | 6 +- truffle/infer/gencfg_pb2_grpc.py | 4 +- truffle/infer/infer_pb2.py | 6 +- truffle/infer/infer_pb2_grpc.py | 71 ++--- truffle/infer/irequest_pb2.py | 6 +- truffle/infer/irequest_pb2_grpc.py | 4 +- truffle/infer/iresponse_pb2.py | 6 +- truffle/infer/iresponse_pb2_grpc.py | 4 +- truffle/infer/model_pb2.py | 6 +- truffle/infer/model_pb2_grpc.py | 4 +- truffle/infer/tokenize_pb2.py | 6 +- truffle/infer/tokenize_pb2_grpc.py | 4 +- truffle/infer/usage_pb2.py | 6 +- truffle/infer/usage_pb2_grpc.py | 4 +- truffle/os/app_queries_pb2.py | 35 +-- truffle/os/app_queries_pb2.pyi | 37 +-- truffle/os/app_queries_pb2_grpc.py | 4 +- truffle/os/background_feed_pb2.py | 50 ++++ truffle/{app => os}/background_feed_pb2.pyi | 46 ++- .../{app => os}/background_feed_pb2_grpc.py | 4 +- truffle/os/background_feed_queries_pb2.py | 32 +- truffle/os/background_feed_queries_pb2.pyi | 38 +-- .../os/background_feed_queries_pb2_grpc.py | 4 +- truffle/os/builder_pb2.py | 30 +- truffle/os/builder_pb2.pyi | 16 +- truffle/os/builder_pb2_grpc.py | 4 +- truffle/os/classification_pb2.py | 40 --- truffle/os/classification_pb2.pyi | 28 -- truffle/os/classification_pb2_grpc.py | 24 -- truffle/os/client_metadata_pb2.py | 6 +- truffle/os/client_metadata_pb2_grpc.py | 4 +- truffle/os/client_session_pb2.py | 6 +- truffle/os/client_session_pb2.pyi | 4 +- truffle/os/client_session_pb2_grpc.py | 4 +- truffle/os/client_state_pb2.py | 6 +- truffle/os/client_state_pb2_grpc.py | 4 +- truffle/os/client_user_pb2.py | 6 +- truffle/os/client_user_pb2_grpc.py | 4 +- truffle/os/hardware_control_pb2.py | 6 +- truffle/os/hardware_control_pb2_grpc.py | 4 +- truffle/os/hardware_info_pb2.py | 13 +- truffle/os/hardware_info_pb2.pyi | 9 +- truffle/os/hardware_info_pb2_grpc.py | 4 +- truffle/os/hardware_network_pb2.py | 6 +- truffle/os/hardware_network_pb2_grpc.py | 4 +- truffle/os/hardware_settings_pb2.py | 6 +- truffle/os/hardware_settings_pb2_grpc.py | 4 +- truffle/os/hardware_stats_pb2.py | 6 +- truffle/os/hardware_stats_pb2_grpc.py | 4 +- truffle/os/installer_pb2.py | 105 +++---- truffle/os/installer_pb2.pyi | 74 +++-- truffle/os/installer_pb2_grpc.py | 4 +- truffle/os/notification_pb2.py | 22 +- truffle/os/notification_pb2.pyi | 20 +- truffle/os/notification_pb2_grpc.py | 4 +- truffle/os/proactivity_pb2.py | 51 ++++ truffle/os/proactivity_pb2.pyi | 79 +++++ .../proactivity_pb2_grpc.py} | 4 +- truffle/os/system_info_pb2.py | 6 +- truffle/os/system_info_pb2_grpc.py | 4 +- truffle/os/system_settings_pb2.py | 10 +- truffle/os/system_settings_pb2.pyi | 6 +- truffle/os/system_settings_pb2_grpc.py | 4 +- truffle/os/task_actions_pb2.py | 6 +- truffle/os/task_actions_pb2.pyi | 1 + truffle/os/task_actions_pb2_grpc.py | 4 +- truffle/os/task_error_pb2.py | 6 +- truffle/os/task_error_pb2_grpc.py | 4 +- truffle/os/task_info_pb2.py | 6 +- truffle/os/task_info_pb2.pyi | 4 +- truffle/os/task_info_pb2_grpc.py | 4 +- truffle/os/task_options_pb2.py | 6 +- truffle/os/task_options_pb2_grpc.py | 4 +- truffle/os/task_pb2.py | 16 +- truffle/os/task_pb2.pyi | 22 +- truffle/os/task_pb2_grpc.py | 4 +- truffle/os/task_queries_pb2.py | 6 +- truffle/os/task_queries_pb2_grpc.py | 4 +- truffle/os/task_search_pb2.py | 6 +- truffle/os/task_search_pb2.pyi | 4 +- truffle/os/task_search_pb2_grpc.py | 4 +- truffle/os/task_step_pb2.py | 36 ++- truffle/os/task_step_pb2.pyi | 41 +-- truffle/os/task_step_pb2_grpc.py | 4 +- truffle/os/task_target_pb2.py | 6 +- truffle/os/task_target_pb2_grpc.py | 4 +- truffle/os/task_user_response_pb2.py | 6 +- truffle/os/task_user_response_pb2_grpc.py | 4 +- truffle/os/truffleos_pb2.py | 32 +- truffle/os/truffleos_pb2.pyi | 16 +- truffle/os/truffleos_pb2_grpc.py | 190 +++++------- 141 files changed, 1252 insertions(+), 2151 deletions(-) create mode 100644 truffle/app/app_pb2.py create mode 100644 truffle/app/app_pb2.pyi rename truffle/app/{system_pb2_grpc.py => app_pb2_grpc.py} (87%) create mode 100644 truffle/app/app_runtime_pb2.py create mode 100644 truffle/app/app_runtime_pb2.pyi create mode 100644 truffle/app/app_runtime_pb2_grpc.py delete mode 100644 truffle/app/app_type_pb2.py delete mode 100644 truffle/app/app_type_pb2.pyi delete mode 100644 truffle/app/background_feed_pb2.py delete mode 100644 truffle/app/curator_pb2.py delete mode 100644 truffle/app/curator_pb2.pyi delete mode 100644 truffle/app/curator_pb2_grpc.py delete mode 100644 truffle/app/system_pb2.py delete mode 100644 truffle/app/system_pb2.pyi delete mode 100644 truffle/app/task_runtime_pb2.py delete mode 100644 truffle/app/task_runtime_pb2.pyi delete mode 100644 truffle/app/task_runtime_pb2_grpc.py delete mode 100644 truffle/common/led_states_pb2.py delete mode 100644 truffle/common/led_states_pb2.pyi delete mode 100644 truffle/common/led_states_pb2_grpc.py create mode 100644 truffle/os/background_feed_pb2.py rename truffle/{app => os}/background_feed_pb2.pyi (50%) rename truffle/{app => os}/background_feed_pb2_grpc.py (86%) delete mode 100644 truffle/os/classification_pb2.py delete mode 100644 truffle/os/classification_pb2.pyi delete mode 100644 truffle/os/classification_pb2_grpc.py create mode 100644 truffle/os/proactivity_pb2.py create mode 100644 truffle/os/proactivity_pb2.pyi rename truffle/{app/app_type_pb2_grpc.py => os/proactivity_pb2_grpc.py} (86%) diff --git a/truffle/app/app_build_pb2.py b/truffle/app/app_build_pb2.py index 28406bb..f371f65 100644 --- a/truffle/app/app_build_pb2.py +++ b/truffle/app/app_build_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/app/app_build.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/app/app_build.proto' ) diff --git a/truffle/app/app_build_pb2_grpc.py b/truffle/app/app_build_pb2_grpc.py index 8a82ac6..a7be4f2 100644 --- a/truffle/app/app_build_pb2_grpc.py +++ b/truffle/app/app_build_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/app_build_pb2_grpc.py depends on' + + ' but the generated code in truffle/app/app_build_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/app/app_install_pb2.py b/truffle/app/app_install_pb2.py index 1f90b58..84253bc 100644 --- a/truffle/app/app_install_pb2.py +++ b/truffle/app/app_install_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/app/app_install.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/app/app_install.proto' ) @@ -23,21 +23,26 @@ from truffle.os import installer_pb2 as truffle_dot_os_dot_installer__pb2 +try: + truffle_dot_app_dot_app__pb2 = truffle_dot_os_dot_installer__pb2.truffle_dot_app_dot_app__pb2 +except AttributeError: + truffle_dot_app_dot_app__pb2 = truffle_dot_os_dot_installer__pb2.truffle.app.app_pb2 from truffle.app import app_build_pb2 as truffle_dot_app_dot_app__build__pb2 from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 +from truffle.app import foreground_pb2 as truffle_dot_app_dot_foreground__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dtruffle/app/app_install.proto\x12\x0btruffle.app\x1a\x1atruffle/os/installer.proto\x1a\x1btruffle/app/app_build.proto\x1a\x1ctruffle/app/background.proto\".\n\x1aGetFinalInstallInfoRequest\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\"\xa6\x01\n\x1bGetFinalInstallInfoResponse\x12\x32\n\x0eprocess_config\x18\x01 \x01(\x0b\x32\x1a.truffle.app.ProcessConfig\x12\x42\n\x0c\x62g_rt_policy\x18\x02 \x01(\x0b\x32\'.truffle.app.BackgroundAppRuntimePolicyH\x00\x88\x01\x01\x42\x0f\n\r_bg_rt_policy2\xce\x01\n\x11\x41ppInstallService\x12O\n\nInstallApp\x12\x1d.truffle.os.AppInstallRequest\x1a\x1e.truffle.os.AppInstallResponse(\x01\x30\x01\x12h\n\x13GetFinalInstallInfo\x12\'.truffle.app.GetFinalInstallInfoRequest\x1a(.truffle.app.GetFinalInstallInfoResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dtruffle/app/app_install.proto\x12\x0btruffle.app\x1a\x1atruffle/os/installer.proto\x1a\x1btruffle/app/app_build.proto\x1a\x1ctruffle/app/background.proto\x1a\x1ctruffle/app/foreground.proto\".\n\x1aGetFinalInstallInfoRequest\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\"\xc3\x01\n\x1bGetFinalInstallInfoResponse\x12?\n\rbg_build_info\x18\x01 \x01(\x0b\x32#.truffle.app.BackgroundAppBuildInfoH\x00\x88\x01\x01\x12?\n\rfg_build_info\x18\x02 \x01(\x0b\x32#.truffle.app.ForegroundAppBuildInfoH\x01\x88\x01\x01\x42\x10\n\x0e_bg_build_infoB\x10\n\x0e_fg_build_info2\xce\x01\n\x11\x41ppInstallService\x12O\n\nInstallApp\x12\x1d.truffle.os.AppInstallRequest\x1a\x1e.truffle.os.AppInstallResponse(\x01\x30\x01\x12h\n\x13GetFinalInstallInfo\x12\'.truffle.app.GetFinalInstallInfoRequest\x1a(.truffle.app.GetFinalInstallInfoResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.app_install_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_GETFINALINSTALLINFOREQUEST']._serialized_start=133 - _globals['_GETFINALINSTALLINFOREQUEST']._serialized_end=179 - _globals['_GETFINALINSTALLINFORESPONSE']._serialized_start=182 - _globals['_GETFINALINSTALLINFORESPONSE']._serialized_end=348 - _globals['_APPINSTALLSERVICE']._serialized_start=351 - _globals['_APPINSTALLSERVICE']._serialized_end=557 + _globals['_GETFINALINSTALLINFOREQUEST']._serialized_start=163 + _globals['_GETFINALINSTALLINFOREQUEST']._serialized_end=209 + _globals['_GETFINALINSTALLINFORESPONSE']._serialized_start=212 + _globals['_GETFINALINSTALLINFORESPONSE']._serialized_end=407 + _globals['_APPINSTALLSERVICE']._serialized_start=410 + _globals['_APPINSTALLSERVICE']._serialized_end=616 # @@protoc_insertion_point(module_scope) diff --git a/truffle/app/app_install_pb2.pyi b/truffle/app/app_install_pb2.pyi index 1d428e6..989aa1b 100644 --- a/truffle/app/app_install_pb2.pyi +++ b/truffle/app/app_install_pb2.pyi @@ -1,6 +1,8 @@ from truffle.os import installer_pb2 as _installer_pb2 +from truffle.app import app_pb2 as _app_pb2 from truffle.app import app_build_pb2 as _app_build_pb2 from truffle.app import background_pb2 as _background_pb2 +from truffle.app import foreground_pb2 as _foreground_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from collections.abc import Mapping as _Mapping @@ -15,9 +17,9 @@ class GetFinalInstallInfoRequest(_message.Message): def __init__(self, app_uuid: _Optional[str] = ...) -> None: ... class GetFinalInstallInfoResponse(_message.Message): - __slots__ = ("process_config", "bg_rt_policy") - PROCESS_CONFIG_FIELD_NUMBER: _ClassVar[int] - BG_RT_POLICY_FIELD_NUMBER: _ClassVar[int] - process_config: _app_build_pb2.ProcessConfig - bg_rt_policy: _background_pb2.BackgroundAppRuntimePolicy - def __init__(self, process_config: _Optional[_Union[_app_build_pb2.ProcessConfig, _Mapping]] = ..., bg_rt_policy: _Optional[_Union[_background_pb2.BackgroundAppRuntimePolicy, _Mapping]] = ...) -> None: ... + __slots__ = ("bg_build_info", "fg_build_info") + BG_BUILD_INFO_FIELD_NUMBER: _ClassVar[int] + FG_BUILD_INFO_FIELD_NUMBER: _ClassVar[int] + bg_build_info: _background_pb2.BackgroundAppBuildInfo + fg_build_info: _foreground_pb2.ForegroundAppBuildInfo + def __init__(self, bg_build_info: _Optional[_Union[_background_pb2.BackgroundAppBuildInfo, _Mapping]] = ..., fg_build_info: _Optional[_Union[_foreground_pb2.ForegroundAppBuildInfo, _Mapping]] = ...) -> None: ... diff --git a/truffle/app/app_install_pb2_grpc.py b/truffle/app/app_install_pb2_grpc.py index 9170a0f..e888508 100644 --- a/truffle/app/app_install_pb2_grpc.py +++ b/truffle/app/app_install_pb2_grpc.py @@ -6,7 +6,7 @@ from truffle.app import app_install_pb2 as truffle_dot_app_dot_app__install__pb2 from truffle.os import installer_pb2 as truffle_dot_os_dot_installer__pb2 -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -19,7 +19,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/app_install_pb2_grpc.py depends on' + + ' but the generated code in truffle/app/app_install_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/app/app_pb2.py b/truffle/app/app_pb2.py new file mode 100644 index 0000000..bc62753 --- /dev/null +++ b/truffle/app/app_pb2.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: truffle/app/app.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'truffle/app/app.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from truffle.common import icon_pb2 as truffle_dot_common_dot_icon__pb2 +from truffle.app import foreground_pb2 as truffle_dot_app_dot_foreground__pb2 +from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15truffle/app/app.proto\x12\x0btruffle.app\x1a\x19truffle/common/icon.proto\x1a\x1ctruffle/app/foreground.proto\x1a\x1ctruffle/app/background.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"g\n\x0b\x41ppMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\"\n\x04icon\x18\x02 \x01(\x0b\x32\x14.truffle.common.Icon\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x11\n\tbundle_id\x18\x04 \x01(\t\"$\n\tAppConfig\x12\x17\n\x0f\x63\x61n_reconfigure\x18\x01 \x01(\x08\"\x8b\x03\n\x03\x41pp\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12*\n\x08metadata\x18\x02 \x01(\x0b\x32\x18.truffle.app.AppMetadata\x12\x33\n\nforeground\x18\x03 \x01(\x0b\x32\x1a.truffle.app.ForegroundAppH\x00\x88\x01\x01\x12\x33\n\nbackground\x18\x04 \x01(\x0b\x32\x1a.truffle.app.BackgroundAppH\x01\x88\x01\x01\x12)\n\x05\x65rror\x18\x05 \x01(\x0b\x32\x15.truffle.app.AppErrorH\x02\x88\x01\x01\x12&\n\x06\x63onfig\x18\x06 \x01(\x0b\x32\x16.truffle.app.AppConfig\x12\x30\n\x0cinstalled_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x33\n\x0flast_updated_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\r\n\x0b_foregroundB\r\n\x0b_backgroundB\x08\n\x06_error\"\xc1\x01\n\x08\x41ppError\x12\x33\n\nerror_type\x18\x01 \x01(\x0e\x32\x1f.truffle.app.AppError.ErrorType\x12\x15\n\rerror_message\x18\x02 \x01(\t\"i\n\tErrorType\x12\x1a\n\x16\x41PP_ERROR_TYPE_INVALID\x10\x00\x12\x15\n\x11\x41PP_ERROR_RUNTIME\x10\x01\x12\x12\n\x0e\x41PP_ERROR_AUTH\x10\x02\x12\x15\n\x11\x41PP_ERROR_UNKNOWN\x10\x03\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.app_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_APPMETADATA']._serialized_start=158 + _globals['_APPMETADATA']._serialized_end=261 + _globals['_APPCONFIG']._serialized_start=263 + _globals['_APPCONFIG']._serialized_end=299 + _globals['_APP']._serialized_start=302 + _globals['_APP']._serialized_end=697 + _globals['_APPERROR']._serialized_start=700 + _globals['_APPERROR']._serialized_end=893 + _globals['_APPERROR_ERRORTYPE']._serialized_start=788 + _globals['_APPERROR_ERRORTYPE']._serialized_end=893 +# @@protoc_insertion_point(module_scope) diff --git a/truffle/app/app_pb2.pyi b/truffle/app/app_pb2.pyi new file mode 100644 index 0000000..a9d03e7 --- /dev/null +++ b/truffle/app/app_pb2.pyi @@ -0,0 +1,69 @@ +import datetime + +from truffle.common import icon_pb2 as _icon_pb2 +from truffle.app import foreground_pb2 as _foreground_pb2 +from truffle.app import background_pb2 as _background_pb2 +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class AppMetadata(_message.Message): + __slots__ = ("name", "icon", "description", "bundle_id") + NAME_FIELD_NUMBER: _ClassVar[int] + ICON_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + BUNDLE_ID_FIELD_NUMBER: _ClassVar[int] + name: str + icon: _icon_pb2.Icon + description: str + bundle_id: str + def __init__(self, name: _Optional[str] = ..., icon: _Optional[_Union[_icon_pb2.Icon, _Mapping]] = ..., description: _Optional[str] = ..., bundle_id: _Optional[str] = ...) -> None: ... + +class AppConfig(_message.Message): + __slots__ = ("can_reconfigure",) + CAN_RECONFIGURE_FIELD_NUMBER: _ClassVar[int] + can_reconfigure: bool + def __init__(self, can_reconfigure: bool = ...) -> None: ... + +class App(_message.Message): + __slots__ = ("uuid", "metadata", "foreground", "background", "error", "config", "installed_at", "last_updated_at") + UUID_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] + FOREGROUND_FIELD_NUMBER: _ClassVar[int] + BACKGROUND_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + INSTALLED_AT_FIELD_NUMBER: _ClassVar[int] + LAST_UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + uuid: str + metadata: AppMetadata + foreground: _foreground_pb2.ForegroundApp + background: _background_pb2.BackgroundApp + error: AppError + config: AppConfig + installed_at: _timestamp_pb2.Timestamp + last_updated_at: _timestamp_pb2.Timestamp + def __init__(self, uuid: _Optional[str] = ..., metadata: _Optional[_Union[AppMetadata, _Mapping]] = ..., foreground: _Optional[_Union[_foreground_pb2.ForegroundApp, _Mapping]] = ..., background: _Optional[_Union[_background_pb2.BackgroundApp, _Mapping]] = ..., error: _Optional[_Union[AppError, _Mapping]] = ..., config: _Optional[_Union[AppConfig, _Mapping]] = ..., installed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., last_updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class AppError(_message.Message): + __slots__ = ("error_type", "error_message") + class ErrorType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + APP_ERROR_TYPE_INVALID: _ClassVar[AppError.ErrorType] + APP_ERROR_RUNTIME: _ClassVar[AppError.ErrorType] + APP_ERROR_AUTH: _ClassVar[AppError.ErrorType] + APP_ERROR_UNKNOWN: _ClassVar[AppError.ErrorType] + APP_ERROR_TYPE_INVALID: AppError.ErrorType + APP_ERROR_RUNTIME: AppError.ErrorType + APP_ERROR_AUTH: AppError.ErrorType + APP_ERROR_UNKNOWN: AppError.ErrorType + ERROR_TYPE_FIELD_NUMBER: _ClassVar[int] + ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] + error_type: AppError.ErrorType + error_message: str + def __init__(self, error_type: _Optional[_Union[AppError.ErrorType, str]] = ..., error_message: _Optional[str] = ...) -> None: ... diff --git a/truffle/app/system_pb2_grpc.py b/truffle/app/app_pb2_grpc.py similarity index 87% rename from truffle/app/system_pb2_grpc.py rename to truffle/app/app_pb2_grpc.py index ea78c67..fa08a1a 100644 --- a/truffle/app/system_pb2_grpc.py +++ b/truffle/app/app_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/system_pb2_grpc.py depends on' + + ' but the generated code in truffle/app/app_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/app/app_runtime_pb2.py b/truffle/app/app_runtime_pb2.py new file mode 100644 index 0000000..127582a --- /dev/null +++ b/truffle/app/app_runtime_pb2.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: truffle/app/app_runtime.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'truffle/app/app_runtime.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from truffle.app import app_pb2 as truffle_dot_app_dot_app__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dtruffle/app/app_runtime.proto\x12\x0btruffle.app\x1a\x15truffle/app/app.proto\"r\n\x1c\x41ppRuntimeReportErrorRequest\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\x12$\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x15.truffle.app.AppError\x12\x1a\n\x12needs_intervention\x18\x03 \x01(\x08\"\x1f\n\x1d\x41ppRuntimeReportErrorResponse2y\n\x11\x41ppRuntimeService\x12\x64\n\x0bReportError\x12).truffle.app.AppRuntimeReportErrorRequest\x1a*.truffle.app.AppRuntimeReportErrorResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.app_runtime_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_APPRUNTIMEREPORTERRORREQUEST']._serialized_start=69 + _globals['_APPRUNTIMEREPORTERRORREQUEST']._serialized_end=183 + _globals['_APPRUNTIMEREPORTERRORRESPONSE']._serialized_start=185 + _globals['_APPRUNTIMEREPORTERRORRESPONSE']._serialized_end=216 + _globals['_APPRUNTIMESERVICE']._serialized_start=218 + _globals['_APPRUNTIMESERVICE']._serialized_end=339 +# @@protoc_insertion_point(module_scope) diff --git a/truffle/app/app_runtime_pb2.pyi b/truffle/app/app_runtime_pb2.pyi new file mode 100644 index 0000000..764c6bf --- /dev/null +++ b/truffle/app/app_runtime_pb2.pyi @@ -0,0 +1,21 @@ +from truffle.app import app_pb2 as _app_pb2 +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class AppRuntimeReportErrorRequest(_message.Message): + __slots__ = ("app_uuid", "error", "needs_intervention") + APP_UUID_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + NEEDS_INTERVENTION_FIELD_NUMBER: _ClassVar[int] + app_uuid: str + error: _app_pb2.AppError + needs_intervention: bool + def __init__(self, app_uuid: _Optional[str] = ..., error: _Optional[_Union[_app_pb2.AppError, _Mapping]] = ..., needs_intervention: bool = ...) -> None: ... + +class AppRuntimeReportErrorResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... diff --git a/truffle/app/app_runtime_pb2_grpc.py b/truffle/app/app_runtime_pb2_grpc.py new file mode 100644 index 0000000..b95d456 --- /dev/null +++ b/truffle/app/app_runtime_pb2_grpc.py @@ -0,0 +1,97 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from truffle.app import app_runtime_pb2 as truffle_dot_app_dot_app__runtime__pb2 + +GRPC_GENERATED_VERSION = '1.76.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in truffle/app/app_runtime_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class AppRuntimeServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ReportError = channel.unary_unary( + '/truffle.app.AppRuntimeService/ReportError', + request_serializer=truffle_dot_app_dot_app__runtime__pb2.AppRuntimeReportErrorRequest.SerializeToString, + response_deserializer=truffle_dot_app_dot_app__runtime__pb2.AppRuntimeReportErrorResponse.FromString, + _registered_method=True) + + +class AppRuntimeServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def ReportError(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AppRuntimeServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ReportError': grpc.unary_unary_rpc_method_handler( + servicer.ReportError, + request_deserializer=truffle_dot_app_dot_app__runtime__pb2.AppRuntimeReportErrorRequest.FromString, + response_serializer=truffle_dot_app_dot_app__runtime__pb2.AppRuntimeReportErrorResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'truffle.app.AppRuntimeService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('truffle.app.AppRuntimeService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class AppRuntimeService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ReportError(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/truffle.app.AppRuntimeService/ReportError', + truffle_dot_app_dot_app__runtime__pb2.AppRuntimeReportErrorRequest.SerializeToString, + truffle_dot_app_dot_app__runtime__pb2.AppRuntimeReportErrorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/truffle/app/app_type_pb2.py b/truffle/app/app_type_pb2.py deleted file mode 100644 index 2c65e37..0000000 --- a/truffle/app/app_type_pb2.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/app/app_type.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'truffle/app/app_type.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1atruffle/app/app_type.proto\x12\x0btruffle.app*f\n\x07\x41ppType\x12\x14\n\x10\x41PP_TYPE_INVALID\x10\x00\x12\x17\n\x13\x41PP_TYPE_FOREGROUND\x10\x01\x12\x17\n\x13\x41PP_TYPE_BACKGROUND\x10\x02\x12\x13\n\x0f\x41PP_TYPE_SYSTEM\x10\x03\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.app_type_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_APPTYPE']._serialized_start=43 - _globals['_APPTYPE']._serialized_end=145 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/app/app_type_pb2.pyi b/truffle/app/app_type_pb2.pyi deleted file mode 100644 index 9f2aeb0..0000000 --- a/truffle/app/app_type_pb2.pyi +++ /dev/null @@ -1,16 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from typing import ClassVar as _ClassVar - -DESCRIPTOR: _descriptor.FileDescriptor - -class AppType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - APP_TYPE_INVALID: _ClassVar[AppType] - APP_TYPE_FOREGROUND: _ClassVar[AppType] - APP_TYPE_BACKGROUND: _ClassVar[AppType] - APP_TYPE_SYSTEM: _ClassVar[AppType] -APP_TYPE_INVALID: AppType -APP_TYPE_FOREGROUND: AppType -APP_TYPE_BACKGROUND: AppType -APP_TYPE_SYSTEM: AppType diff --git a/truffle/app/background_feed_pb2.py b/truffle/app/background_feed_pb2.py deleted file mode 100644 index abc9691..0000000 --- a/truffle/app/background_feed_pb2.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/app/background_feed.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'truffle/app/background_feed.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from truffle.common import content_pb2 as truffle_dot_common_dot_content__pb2 -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!truffle/app/background_feed.proto\x12\x0btruffle.app\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1ctruffle/common/content.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xe3\x01\n\x08\x46\x65\x65\x64\x43\x61rd\x12\r\n\x05title\x18\x01 \x01(\t\x12\x0c\n\x04\x62ody\x18\x02 \x01(\t\x12\x32\n\rmedia_sources\x18\x03 \x03(\x0b\x32\x1b.truffle.common.MediaSource\x12\x12\n\nsource_uri\x18\x04 \x01(\t\x12\x35\n\x11\x63ontent_timestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x08metadata\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x88\x01\x01\x42\x0b\n\t_metadata\"9\n\x0e\x42\x61\x63kgroundFeed\x12\'\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x16.truffle.app.FeedEntry\"\x8c\x01\n\tFeedEntry\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x10\n\x08\x61pp_uuid\x18\x02 \x01(\t\x12-\n\ttimestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12#\n\x04\x63\x61rd\x18\x05 \x01(\x0b\x32\x15.truffle.app.FeedCard\x12\r\n\x05likes\x18\x07 \x01(\x05\"?\n\x14\x46\x65\x65\x64\x45ntryTaskContext\x12\'\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x16.truffle.app.FeedEntryb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.background_feed_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_FEEDCARD']._serialized_start=144 - _globals['_FEEDCARD']._serialized_end=371 - _globals['_BACKGROUNDFEED']._serialized_start=373 - _globals['_BACKGROUNDFEED']._serialized_end=430 - _globals['_FEEDENTRY']._serialized_start=433 - _globals['_FEEDENTRY']._serialized_end=573 - _globals['_FEEDENTRYTASKCONTEXT']._serialized_start=575 - _globals['_FEEDENTRYTASKCONTEXT']._serialized_end=638 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/app/background_pb2.py b/truffle/app/background_pb2.py index 82b1a94..f481f34 100644 --- a/truffle/app/background_pb2.py +++ b/truffle/app/background_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/app/background.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/app/background.proto' ) @@ -25,11 +25,11 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 -from truffle.app import background_feed_pb2 as truffle_dot_app_dot_background__feed__pb2 from truffle.common import icon_pb2 as truffle_dot_common_dot_icon__pb2 +from truffle.app import app_build_pb2 as truffle_dot_app_dot_app__build__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/app/background.proto\x12\x0btruffle.app\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\x1a google/protobuf/descriptor.proto\x1a!truffle/app/background_feed.proto\x1a\x19truffle/common/icon.proto\"\x9b\x0b\n\x1a\x42\x61\x63kgroundAppRuntimePolicy\x12\x44\n\x08interval\x18\x01 \x01(\x0b\x32\x30.truffle.app.BackgroundAppRuntimePolicy.IntervalH\x00\x12\x46\n\x05times\x18\x02 \x01(\x0b\x32\x35.truffle.app.BackgroundAppRuntimePolicy.SpecificTimesH\x00\x12@\n\x06\x61lways\x18\x03 \x01(\x0b\x32..truffle.app.BackgroundAppRuntimePolicy.AlwaysH\x00\x12\x37\n\x14\x66\x65\x65\x64_entry_retention\x18\n \x01(\x0b\x32\x19.google.protobuf.Duration\x1a\x39\n\tTimeOfDay\x12\x0c\n\x04hour\x18\x01 \x01(\r\x12\x0e\n\x06minute\x18\x02 \x01(\r\x12\x0e\n\x06second\x18\x03 \x01(\r\x1a\xa5\x01\n\x0b\x44\x61ilyWindow\x12K\n\x10\x64\x61ily_start_time\x18\x01 \x01(\x0b\x32\x31.truffle.app.BackgroundAppRuntimePolicy.TimeOfDay\x12I\n\x0e\x64\x61ily_end_time\x18\x02 \x01(\x0b\x32\x31.truffle.app.BackgroundAppRuntimePolicy.TimeOfDay\x1a\x91\x03\n\x0cWeeklyWindow\x12\x10\n\x08\x64\x61y_mask\x18\x01 \x01(\r\"\xee\x02\n\x05Masks\x12\x19\n\x15WEEKLY_WINDOW_DEFAULT\x10\x00\x12\x1a\n\x16WEEKLY_WINDOW_ALL_DAYS\x10\x00\x12\x1a\n\x16WEEKLY_WINDOW_SATURDAY\x10\x01\x12\x18\n\x14WEEKLY_WINDOW_FRIDAY\x10\x02\x12\x1a\n\x16WEEKLY_WINDOW_THURSDAY\x10\x04\x12\x1b\n\x17WEEKLY_WINDOW_WEDNESDAY\x10\x08\x12\x19\n\x15WEEKLY_WINDOW_TUESDAY\x10\x10\x12\x18\n\x14WEEKLY_WINDOW_MONDAY\x10 \x12\x18\n\x14WEEKLY_WINDOW_SUNDAY\x10@\x12\x1a\n\x16WEEKLY_WINDOW_WEEKENDS\x10\x41\x12\x1a\n\x16WEEKLY_WINDOW_WEEKDAYS\x10>\x12\x19\n\x15WEEKLY_WINDOW_NO_DAYS\x10\x7f\x12\x19\n\x15WEEKLY_WINDOW_INVALID\x10\x7f\x1a\x02\x10\x01\x1a\xbf\x02\n\x08Interval\x12+\n\x08\x64uration\x18\x01 \x01(\x0b\x32\x19.google.protobuf.Duration\x12K\n\x08schedule\x18\x02 \x01(\x0b\x32\x39.truffle.app.BackgroundAppRuntimePolicy.Interval.Schedule\x1a\xb8\x01\n\x08Schedule\x12N\n\x0c\x64\x61ily_window\x18\x01 \x01(\x0b\x32\x33.truffle.app.BackgroundAppRuntimePolicy.DailyWindowH\x00\x88\x01\x01\x12K\n\rweekly_window\x18\x02 \x01(\x0b\x32\x34.truffle.app.BackgroundAppRuntimePolicy.WeeklyWindowB\x0f\n\r_daily_window\x1a\xa2\x01\n\rSpecificTimes\x12\x44\n\trun_times\x18\x01 \x03(\x0b\x32\x31.truffle.app.BackgroundAppRuntimePolicy.TimeOfDay\x12K\n\rweekly_window\x18\x02 \x01(\x0b\x32\x34.truffle.app.BackgroundAppRuntimePolicy.WeeklyWindow\x1a\x08\n\x06\x41lwaysB\x06\n\x04whenJ\x04\x08\x04\x10\n\"\xe8\x01\n\rBackgroundApp\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x35\n\x08metadata\x18\x02 \x01(\x0b\x32#.truffle.app.BackgroundApp.Metadata\x12?\n\x0eruntime_policy\x18\x03 \x01(\x0b\x32\'.truffle.app.BackgroundAppRuntimePolicy\x1aQ\n\x08Metadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\"\n\x04icon\x18\x02 \x01(\x0b\x32\x14.truffle.common.Icon\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\"\xdc\x01\n\x19\x42\x61\x63kgroundAppNotification\x12\x16\n\x0e\x66\x65\x65\x64_entry_ids\x18\x02 \x03(\x04\x12\x43\n\toperation\x18\x03 \x01(\x0e\x32\x30.truffle.app.BackgroundAppNotification.Operation\"b\n\tOperation\x12\x15\n\x11OPERATION_INVALID\x10\x00\x12\x11\n\rOPERATION_ADD\x10\x01\x12\x14\n\x10OPERATION_DELETE\x10\x02\x12\x15\n\x11OPERATION_REFRESH\x10\x03\"\x90\x01\n\x16\x42\x61\x63kgroundAppBuildInfo\x12\x35\n\x08metadata\x18\x01 \x01(\x0b\x32#.truffle.app.BackgroundApp.Metadata\x12?\n\x0eruntime_policy\x18\x02 \x01(\x0b\x32\'.truffle.app.BackgroundAppRuntimePolicy\"L\n%BackgroundAppSubmitFeedContentRequest\x12#\n\x04\x63\x61rd\x18\x02 \x01(\x0b\x32\x15.truffle.app.FeedCard\"?\n&BackgroundAppSubmitFeedContentResponse\x12\x15\n\rfeed_entry_id\x18\x01 \x01(\x04\"\x1b\n\x19\x42\x61\x63kgroundAppOnRunRequest\"\x1c\n\x1a\x42\x61\x63kgroundAppOnRunResponse\"\x1b\n\x19\x42\x61\x63kgroundAppYieldRequest\"Y\n\x1a\x42\x61\x63kgroundAppYieldResponse\x12;\n\x17next_scheduled_run_time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xeb\x01\n\x12\x42\x61\x63kgroundAppError\x12=\n\nerror_type\x18\x01 \x01(\x0e\x32).truffle.app.BackgroundAppError.ErrorType\x12\x15\n\rerror_message\x18\x02 \x01(\t\"\x7f\n\tErrorType\x12\x1d\n\x19\x42G_APP_ERROR_TYPE_INVALID\x10\x00\x12\x1d\n\x19\x42G_APP_ERROR_TYPE_RUNTIME\x10\x01\x12\x15\n\x11\x42G_APP_ERROR_AUTH\x10\x02\x12\x1d\n\x19\x42G_APP_ERROR_TYPE_UNKNOWN\x10\x03\"m\n\x1f\x42\x61\x63kgroundAppReportErrorRequest\x12.\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x1f.truffle.app.BackgroundAppError\x12\x1a\n\x12needs_intervention\x18\x02 \x01(\x08\"\"\n BackgroundAppReportErrorResponse2\xb4\x03\n\x14\x42\x61\x63kgroundAppService\x12|\n\x11SubmitFeedContent\x12\x32.truffle.app.BackgroundAppSubmitFeedContentRequest\x1a\x33.truffle.app.BackgroundAppSubmitFeedContentResponse\x12X\n\x05OnRun\x12&.truffle.app.BackgroundAppOnRunRequest\x1a\'.truffle.app.BackgroundAppOnRunResponse\x12X\n\x05Yield\x12&.truffle.app.BackgroundAppYieldRequest\x1a\'.truffle.app.BackgroundAppYieldResponse\x12j\n\x0bReportError\x12,.truffle.app.BackgroundAppReportErrorRequest\x1a-.truffle.app.BackgroundAppReportErrorResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/app/background.proto\x12\x0btruffle.app\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\x1a google/protobuf/descriptor.proto\x1a\x19truffle/common/icon.proto\x1a\x1btruffle/app/app_build.proto\"\x9b\x0b\n\x1a\x42\x61\x63kgroundAppRuntimePolicy\x12\x44\n\x08interval\x18\x01 \x01(\x0b\x32\x30.truffle.app.BackgroundAppRuntimePolicy.IntervalH\x00\x12\x46\n\x05times\x18\x02 \x01(\x0b\x32\x35.truffle.app.BackgroundAppRuntimePolicy.SpecificTimesH\x00\x12@\n\x06\x61lways\x18\x03 \x01(\x0b\x32..truffle.app.BackgroundAppRuntimePolicy.AlwaysH\x00\x12\x37\n\x14\x66\x65\x65\x64_entry_retention\x18\n \x01(\x0b\x32\x19.google.protobuf.Duration\x1a\x39\n\tTimeOfDay\x12\x0c\n\x04hour\x18\x01 \x01(\r\x12\x0e\n\x06minute\x18\x02 \x01(\r\x12\x0e\n\x06second\x18\x03 \x01(\r\x1a\xa5\x01\n\x0b\x44\x61ilyWindow\x12K\n\x10\x64\x61ily_start_time\x18\x01 \x01(\x0b\x32\x31.truffle.app.BackgroundAppRuntimePolicy.TimeOfDay\x12I\n\x0e\x64\x61ily_end_time\x18\x02 \x01(\x0b\x32\x31.truffle.app.BackgroundAppRuntimePolicy.TimeOfDay\x1a\x91\x03\n\x0cWeeklyWindow\x12\x10\n\x08\x64\x61y_mask\x18\x01 \x01(\r\"\xee\x02\n\x05Masks\x12\x19\n\x15WEEKLY_WINDOW_DEFAULT\x10\x00\x12\x1a\n\x16WEEKLY_WINDOW_ALL_DAYS\x10\x00\x12\x1a\n\x16WEEKLY_WINDOW_SATURDAY\x10\x01\x12\x18\n\x14WEEKLY_WINDOW_FRIDAY\x10\x02\x12\x1a\n\x16WEEKLY_WINDOW_THURSDAY\x10\x04\x12\x1b\n\x17WEEKLY_WINDOW_WEDNESDAY\x10\x08\x12\x19\n\x15WEEKLY_WINDOW_TUESDAY\x10\x10\x12\x18\n\x14WEEKLY_WINDOW_MONDAY\x10 \x12\x18\n\x14WEEKLY_WINDOW_SUNDAY\x10@\x12\x1a\n\x16WEEKLY_WINDOW_WEEKENDS\x10\x41\x12\x1a\n\x16WEEKLY_WINDOW_WEEKDAYS\x10>\x12\x19\n\x15WEEKLY_WINDOW_NO_DAYS\x10\x7f\x12\x19\n\x15WEEKLY_WINDOW_INVALID\x10\x7f\x1a\x02\x10\x01\x1a\xbf\x02\n\x08Interval\x12+\n\x08\x64uration\x18\x01 \x01(\x0b\x32\x19.google.protobuf.Duration\x12K\n\x08schedule\x18\x02 \x01(\x0b\x32\x39.truffle.app.BackgroundAppRuntimePolicy.Interval.Schedule\x1a\xb8\x01\n\x08Schedule\x12N\n\x0c\x64\x61ily_window\x18\x01 \x01(\x0b\x32\x33.truffle.app.BackgroundAppRuntimePolicy.DailyWindowH\x00\x88\x01\x01\x12K\n\rweekly_window\x18\x02 \x01(\x0b\x32\x34.truffle.app.BackgroundAppRuntimePolicy.WeeklyWindowB\x0f\n\r_daily_window\x1a\xa2\x01\n\rSpecificTimes\x12\x44\n\trun_times\x18\x01 \x03(\x0b\x32\x31.truffle.app.BackgroundAppRuntimePolicy.TimeOfDay\x12K\n\rweekly_window\x18\x02 \x01(\x0b\x32\x34.truffle.app.BackgroundAppRuntimePolicy.WeeklyWindow\x1a\x08\n\x06\x41lwaysB\x06\n\x04whenJ\x04\x08\x04\x10\n\"P\n\rBackgroundApp\x12?\n\x0eruntime_policy\x18\x01 \x01(\x0b\x32\'.truffle.app.BackgroundAppRuntimePolicy\"\x86\x01\n\x16\x42\x61\x63kgroundAppBuildInfo\x12+\n\x07process\x18\x01 \x01(\x0b\x32\x1a.truffle.app.ProcessConfig\x12?\n\x0eruntime_policy\x18\x02 \x01(\x0b\x32\'.truffle.app.BackgroundAppRuntimePolicy\"\xb8\x01\n\x11\x42\x61\x63kgroundContext\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x0c\n\x04uris\x18\x02 \x03(\t\x12\x39\n\x08priority\x18\x03 \x01(\x0e\x32\'.truffle.app.BackgroundContext.Priority\"I\n\x08Priority\x12\x18\n\x14PRIORITY_UNSPECIFIED\x10\x00\x12\x10\n\x0cPRIORITY_LOW\x10\x01\x12\x11\n\rPRIORITY_HIGH\x10\x03\"T\n!BackgroundAppSubmitContextRequest\x12/\n\x07\x63ontent\x18\x01 \x01(\x0b\x32\x1e.truffle.app.BackgroundContext\"$\n\"BackgroundAppSubmitContextResponse\"\x1b\n\x19\x42\x61\x63kgroundAppOnRunRequest\"\x1c\n\x1a\x42\x61\x63kgroundAppOnRunResponse\"\x1b\n\x19\x42\x61\x63kgroundAppYieldRequest\"Y\n\x1a\x42\x61\x63kgroundAppYieldResponse\x12;\n\x17next_scheduled_run_time\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\"\n BackgroundAppReportErrorResponse2\xb5\x02\n\x14\x42\x61\x63kgroundAppService\x12i\n\x06Submit\x12..truffle.app.BackgroundAppSubmitContextRequest\x1a/.truffle.app.BackgroundAppSubmitContextResponse\x12X\n\x05OnRun\x12&.truffle.app.BackgroundAppOnRunRequest\x1a\'.truffle.app.BackgroundAppOnRunResponse\x12X\n\x05Yield\x12&.truffle.app.BackgroundAppYieldRequest\x1a\'.truffle.app.BackgroundAppYieldResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -38,54 +38,46 @@ DESCRIPTOR._loaded_options = None _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW_MASKS']._loaded_options = None _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW_MASKS']._serialized_options = b'\020\001' - _globals['_BACKGROUNDAPPRUNTIMEPOLICY']._serialized_start=207 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY']._serialized_end=1642 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_TIMEOFDAY']._serialized_start=502 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_TIMEOFDAY']._serialized_end=559 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_DAILYWINDOW']._serialized_start=562 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_DAILYWINDOW']._serialized_end=727 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW']._serialized_start=730 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW']._serialized_end=1131 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW_MASKS']._serialized_start=765 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW_MASKS']._serialized_end=1131 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_INTERVAL']._serialized_start=1134 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_INTERVAL']._serialized_end=1453 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_INTERVAL_SCHEDULE']._serialized_start=1269 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_INTERVAL_SCHEDULE']._serialized_end=1453 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_SPECIFICTIMES']._serialized_start=1456 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_SPECIFICTIMES']._serialized_end=1618 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_ALWAYS']._serialized_start=1620 - _globals['_BACKGROUNDAPPRUNTIMEPOLICY_ALWAYS']._serialized_end=1628 - _globals['_BACKGROUNDAPP']._serialized_start=1645 - _globals['_BACKGROUNDAPP']._serialized_end=1877 - _globals['_BACKGROUNDAPP_METADATA']._serialized_start=1796 - _globals['_BACKGROUNDAPP_METADATA']._serialized_end=1877 - _globals['_BACKGROUNDAPPNOTIFICATION']._serialized_start=1880 - _globals['_BACKGROUNDAPPNOTIFICATION']._serialized_end=2100 - _globals['_BACKGROUNDAPPNOTIFICATION_OPERATION']._serialized_start=2002 - _globals['_BACKGROUNDAPPNOTIFICATION_OPERATION']._serialized_end=2100 - _globals['_BACKGROUNDAPPBUILDINFO']._serialized_start=2103 - _globals['_BACKGROUNDAPPBUILDINFO']._serialized_end=2247 - _globals['_BACKGROUNDAPPSUBMITFEEDCONTENTREQUEST']._serialized_start=2249 - _globals['_BACKGROUNDAPPSUBMITFEEDCONTENTREQUEST']._serialized_end=2325 - _globals['_BACKGROUNDAPPSUBMITFEEDCONTENTRESPONSE']._serialized_start=2327 - _globals['_BACKGROUNDAPPSUBMITFEEDCONTENTRESPONSE']._serialized_end=2390 - _globals['_BACKGROUNDAPPONRUNREQUEST']._serialized_start=2392 - _globals['_BACKGROUNDAPPONRUNREQUEST']._serialized_end=2419 - _globals['_BACKGROUNDAPPONRUNRESPONSE']._serialized_start=2421 - _globals['_BACKGROUNDAPPONRUNRESPONSE']._serialized_end=2449 - _globals['_BACKGROUNDAPPYIELDREQUEST']._serialized_start=2451 - _globals['_BACKGROUNDAPPYIELDREQUEST']._serialized_end=2478 - _globals['_BACKGROUNDAPPYIELDRESPONSE']._serialized_start=2480 - _globals['_BACKGROUNDAPPYIELDRESPONSE']._serialized_end=2569 - _globals['_BACKGROUNDAPPERROR']._serialized_start=2572 - _globals['_BACKGROUNDAPPERROR']._serialized_end=2807 - _globals['_BACKGROUNDAPPERROR_ERRORTYPE']._serialized_start=2680 - _globals['_BACKGROUNDAPPERROR_ERRORTYPE']._serialized_end=2807 - _globals['_BACKGROUNDAPPREPORTERRORREQUEST']._serialized_start=2809 - _globals['_BACKGROUNDAPPREPORTERRORREQUEST']._serialized_end=2918 - _globals['_BACKGROUNDAPPREPORTERRORRESPONSE']._serialized_start=2920 - _globals['_BACKGROUNDAPPREPORTERRORRESPONSE']._serialized_end=2954 - _globals['_BACKGROUNDAPPSERVICE']._serialized_start=2957 - _globals['_BACKGROUNDAPPSERVICE']._serialized_end=3393 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY']._serialized_start=201 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY']._serialized_end=1636 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_TIMEOFDAY']._serialized_start=496 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_TIMEOFDAY']._serialized_end=553 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_DAILYWINDOW']._serialized_start=556 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_DAILYWINDOW']._serialized_end=721 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW']._serialized_start=724 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW']._serialized_end=1125 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW_MASKS']._serialized_start=759 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_WEEKLYWINDOW_MASKS']._serialized_end=1125 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_INTERVAL']._serialized_start=1128 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_INTERVAL']._serialized_end=1447 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_INTERVAL_SCHEDULE']._serialized_start=1263 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_INTERVAL_SCHEDULE']._serialized_end=1447 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_SPECIFICTIMES']._serialized_start=1450 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_SPECIFICTIMES']._serialized_end=1612 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_ALWAYS']._serialized_start=1614 + _globals['_BACKGROUNDAPPRUNTIMEPOLICY_ALWAYS']._serialized_end=1622 + _globals['_BACKGROUNDAPP']._serialized_start=1638 + _globals['_BACKGROUNDAPP']._serialized_end=1718 + _globals['_BACKGROUNDAPPBUILDINFO']._serialized_start=1721 + _globals['_BACKGROUNDAPPBUILDINFO']._serialized_end=1855 + _globals['_BACKGROUNDCONTEXT']._serialized_start=1858 + _globals['_BACKGROUNDCONTEXT']._serialized_end=2042 + _globals['_BACKGROUNDCONTEXT_PRIORITY']._serialized_start=1969 + _globals['_BACKGROUNDCONTEXT_PRIORITY']._serialized_end=2042 + _globals['_BACKGROUNDAPPSUBMITCONTEXTREQUEST']._serialized_start=2044 + _globals['_BACKGROUNDAPPSUBMITCONTEXTREQUEST']._serialized_end=2128 + _globals['_BACKGROUNDAPPSUBMITCONTEXTRESPONSE']._serialized_start=2130 + _globals['_BACKGROUNDAPPSUBMITCONTEXTRESPONSE']._serialized_end=2166 + _globals['_BACKGROUNDAPPONRUNREQUEST']._serialized_start=2168 + _globals['_BACKGROUNDAPPONRUNREQUEST']._serialized_end=2195 + _globals['_BACKGROUNDAPPONRUNRESPONSE']._serialized_start=2197 + _globals['_BACKGROUNDAPPONRUNRESPONSE']._serialized_end=2225 + _globals['_BACKGROUNDAPPYIELDREQUEST']._serialized_start=2227 + _globals['_BACKGROUNDAPPYIELDREQUEST']._serialized_end=2254 + _globals['_BACKGROUNDAPPYIELDRESPONSE']._serialized_start=2256 + _globals['_BACKGROUNDAPPYIELDRESPONSE']._serialized_end=2345 + _globals['_BACKGROUNDAPPREPORTERRORRESPONSE']._serialized_start=2347 + _globals['_BACKGROUNDAPPREPORTERRORRESPONSE']._serialized_end=2381 + _globals['_BACKGROUNDAPPSERVICE']._serialized_start=2384 + _globals['_BACKGROUNDAPPSERVICE']._serialized_end=2693 # @@protoc_insertion_point(module_scope) diff --git a/truffle/app/background_pb2.pyi b/truffle/app/background_pb2.pyi index 7847843..a22e289 100644 --- a/truffle/app/background_pb2.pyi +++ b/truffle/app/background_pb2.pyi @@ -1,8 +1,10 @@ +import datetime + from google.protobuf import timestamp_pb2 as _timestamp_pb2 from google.protobuf import duration_pb2 as _duration_pb2 from google.protobuf import descriptor_pb2 as _descriptor_pb2 -from truffle.app import background_feed_pb2 as _background_feed_pb2 from truffle.common import icon_pb2 as _icon_pb2 +from truffle.app import app_build_pb2 as _app_build_pb2 from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor @@ -76,7 +78,7 @@ class BackgroundAppRuntimePolicy(_message.Message): SCHEDULE_FIELD_NUMBER: _ClassVar[int] duration: _duration_pb2.Duration schedule: BackgroundAppRuntimePolicy.Interval.Schedule - def __init__(self, duration: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., schedule: _Optional[_Union[BackgroundAppRuntimePolicy.Interval.Schedule, _Mapping]] = ...) -> None: ... + def __init__(self, duration: _Optional[_Union[datetime.timedelta, _duration_pb2.Duration, _Mapping]] = ..., schedule: _Optional[_Union[BackgroundAppRuntimePolicy.Interval.Schedule, _Mapping]] = ...) -> None: ... class SpecificTimes(_message.Message): __slots__ = ("run_times", "weekly_window") RUN_TIMES_FIELD_NUMBER: _ClassVar[int] @@ -95,64 +97,49 @@ class BackgroundAppRuntimePolicy(_message.Message): times: BackgroundAppRuntimePolicy.SpecificTimes always: BackgroundAppRuntimePolicy.Always feed_entry_retention: _duration_pb2.Duration - def __init__(self, interval: _Optional[_Union[BackgroundAppRuntimePolicy.Interval, _Mapping]] = ..., times: _Optional[_Union[BackgroundAppRuntimePolicy.SpecificTimes, _Mapping]] = ..., always: _Optional[_Union[BackgroundAppRuntimePolicy.Always, _Mapping]] = ..., feed_entry_retention: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ...) -> None: ... + def __init__(self, interval: _Optional[_Union[BackgroundAppRuntimePolicy.Interval, _Mapping]] = ..., times: _Optional[_Union[BackgroundAppRuntimePolicy.SpecificTimes, _Mapping]] = ..., always: _Optional[_Union[BackgroundAppRuntimePolicy.Always, _Mapping]] = ..., feed_entry_retention: _Optional[_Union[datetime.timedelta, _duration_pb2.Duration, _Mapping]] = ...) -> None: ... class BackgroundApp(_message.Message): - __slots__ = ("uuid", "metadata", "runtime_policy") - class Metadata(_message.Message): - __slots__ = ("name", "icon", "description") - NAME_FIELD_NUMBER: _ClassVar[int] - ICON_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - name: str - icon: _icon_pb2.Icon - description: str - def __init__(self, name: _Optional[str] = ..., icon: _Optional[_Union[_icon_pb2.Icon, _Mapping]] = ..., description: _Optional[str] = ...) -> None: ... - UUID_FIELD_NUMBER: _ClassVar[int] - METADATA_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("runtime_policy",) RUNTIME_POLICY_FIELD_NUMBER: _ClassVar[int] - uuid: str - metadata: BackgroundApp.Metadata runtime_policy: BackgroundAppRuntimePolicy - def __init__(self, uuid: _Optional[str] = ..., metadata: _Optional[_Union[BackgroundApp.Metadata, _Mapping]] = ..., runtime_policy: _Optional[_Union[BackgroundAppRuntimePolicy, _Mapping]] = ...) -> None: ... - -class BackgroundAppNotification(_message.Message): - __slots__ = ("feed_entry_ids", "operation") - class Operation(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - OPERATION_INVALID: _ClassVar[BackgroundAppNotification.Operation] - OPERATION_ADD: _ClassVar[BackgroundAppNotification.Operation] - OPERATION_DELETE: _ClassVar[BackgroundAppNotification.Operation] - OPERATION_REFRESH: _ClassVar[BackgroundAppNotification.Operation] - OPERATION_INVALID: BackgroundAppNotification.Operation - OPERATION_ADD: BackgroundAppNotification.Operation - OPERATION_DELETE: BackgroundAppNotification.Operation - OPERATION_REFRESH: BackgroundAppNotification.Operation - FEED_ENTRY_IDS_FIELD_NUMBER: _ClassVar[int] - OPERATION_FIELD_NUMBER: _ClassVar[int] - feed_entry_ids: _containers.RepeatedScalarFieldContainer[int] - operation: BackgroundAppNotification.Operation - def __init__(self, feed_entry_ids: _Optional[_Iterable[int]] = ..., operation: _Optional[_Union[BackgroundAppNotification.Operation, str]] = ...) -> None: ... + def __init__(self, runtime_policy: _Optional[_Union[BackgroundAppRuntimePolicy, _Mapping]] = ...) -> None: ... class BackgroundAppBuildInfo(_message.Message): - __slots__ = ("metadata", "runtime_policy") - METADATA_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("process", "runtime_policy") + PROCESS_FIELD_NUMBER: _ClassVar[int] RUNTIME_POLICY_FIELD_NUMBER: _ClassVar[int] - metadata: BackgroundApp.Metadata + process: _app_build_pb2.ProcessConfig runtime_policy: BackgroundAppRuntimePolicy - def __init__(self, metadata: _Optional[_Union[BackgroundApp.Metadata, _Mapping]] = ..., runtime_policy: _Optional[_Union[BackgroundAppRuntimePolicy, _Mapping]] = ...) -> None: ... + def __init__(self, process: _Optional[_Union[_app_build_pb2.ProcessConfig, _Mapping]] = ..., runtime_policy: _Optional[_Union[BackgroundAppRuntimePolicy, _Mapping]] = ...) -> None: ... + +class BackgroundContext(_message.Message): + __slots__ = ("content", "uris", "priority") + class Priority(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PRIORITY_UNSPECIFIED: _ClassVar[BackgroundContext.Priority] + PRIORITY_LOW: _ClassVar[BackgroundContext.Priority] + PRIORITY_HIGH: _ClassVar[BackgroundContext.Priority] + PRIORITY_UNSPECIFIED: BackgroundContext.Priority + PRIORITY_LOW: BackgroundContext.Priority + PRIORITY_HIGH: BackgroundContext.Priority + CONTENT_FIELD_NUMBER: _ClassVar[int] + URIS_FIELD_NUMBER: _ClassVar[int] + PRIORITY_FIELD_NUMBER: _ClassVar[int] + content: str + uris: _containers.RepeatedScalarFieldContainer[str] + priority: BackgroundContext.Priority + def __init__(self, content: _Optional[str] = ..., uris: _Optional[_Iterable[str]] = ..., priority: _Optional[_Union[BackgroundContext.Priority, str]] = ...) -> None: ... -class BackgroundAppSubmitFeedContentRequest(_message.Message): - __slots__ = ("card",) - CARD_FIELD_NUMBER: _ClassVar[int] - card: _background_feed_pb2.FeedCard - def __init__(self, card: _Optional[_Union[_background_feed_pb2.FeedCard, _Mapping]] = ...) -> None: ... +class BackgroundAppSubmitContextRequest(_message.Message): + __slots__ = ("content",) + CONTENT_FIELD_NUMBER: _ClassVar[int] + content: BackgroundContext + def __init__(self, content: _Optional[_Union[BackgroundContext, _Mapping]] = ...) -> None: ... -class BackgroundAppSubmitFeedContentResponse(_message.Message): - __slots__ = ("feed_entry_id",) - FEED_ENTRY_ID_FIELD_NUMBER: _ClassVar[int] - feed_entry_id: int - def __init__(self, feed_entry_id: _Optional[int] = ...) -> None: ... +class BackgroundAppSubmitContextResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... class BackgroundAppOnRunRequest(_message.Message): __slots__ = () @@ -170,33 +157,7 @@ class BackgroundAppYieldResponse(_message.Message): __slots__ = ("next_scheduled_run_time",) NEXT_SCHEDULED_RUN_TIME_FIELD_NUMBER: _ClassVar[int] next_scheduled_run_time: _timestamp_pb2.Timestamp - def __init__(self, next_scheduled_run_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... - -class BackgroundAppError(_message.Message): - __slots__ = ("error_type", "error_message") - class ErrorType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - BG_APP_ERROR_TYPE_INVALID: _ClassVar[BackgroundAppError.ErrorType] - BG_APP_ERROR_TYPE_RUNTIME: _ClassVar[BackgroundAppError.ErrorType] - BG_APP_ERROR_AUTH: _ClassVar[BackgroundAppError.ErrorType] - BG_APP_ERROR_TYPE_UNKNOWN: _ClassVar[BackgroundAppError.ErrorType] - BG_APP_ERROR_TYPE_INVALID: BackgroundAppError.ErrorType - BG_APP_ERROR_TYPE_RUNTIME: BackgroundAppError.ErrorType - BG_APP_ERROR_AUTH: BackgroundAppError.ErrorType - BG_APP_ERROR_TYPE_UNKNOWN: BackgroundAppError.ErrorType - ERROR_TYPE_FIELD_NUMBER: _ClassVar[int] - ERROR_MESSAGE_FIELD_NUMBER: _ClassVar[int] - error_type: BackgroundAppError.ErrorType - error_message: str - def __init__(self, error_type: _Optional[_Union[BackgroundAppError.ErrorType, str]] = ..., error_message: _Optional[str] = ...) -> None: ... - -class BackgroundAppReportErrorRequest(_message.Message): - __slots__ = ("error", "needs_intervention") - ERROR_FIELD_NUMBER: _ClassVar[int] - NEEDS_INTERVENTION_FIELD_NUMBER: _ClassVar[int] - error: BackgroundAppError - needs_intervention: bool - def __init__(self, error: _Optional[_Union[BackgroundAppError, _Mapping]] = ..., needs_intervention: bool = ...) -> None: ... + def __init__(self, next_scheduled_run_time: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... class BackgroundAppReportErrorResponse(_message.Message): __slots__ = () diff --git a/truffle/app/background_pb2_grpc.py b/truffle/app/background_pb2_grpc.py index a731773..5f1f658 100644 --- a/truffle/app/background_pb2_grpc.py +++ b/truffle/app/background_pb2_grpc.py @@ -5,7 +5,7 @@ from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -18,7 +18,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/background_pb2_grpc.py depends on' + + ' but the generated code in truffle/app/background_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' @@ -37,10 +37,10 @@ def __init__(self, channel): Args: channel: A grpc.Channel. """ - self.SubmitFeedContent = channel.unary_unary( - '/truffle.app.BackgroundAppService/SubmitFeedContent', - request_serializer=truffle_dot_app_dot_background__pb2.BackgroundAppSubmitFeedContentRequest.SerializeToString, - response_deserializer=truffle_dot_app_dot_background__pb2.BackgroundAppSubmitFeedContentResponse.FromString, + self.Submit = channel.unary_unary( + '/truffle.app.BackgroundAppService/Submit', + request_serializer=truffle_dot_app_dot_background__pb2.BackgroundAppSubmitContextRequest.SerializeToString, + response_deserializer=truffle_dot_app_dot_background__pb2.BackgroundAppSubmitContextResponse.FromString, _registered_method=True) self.OnRun = channel.unary_unary( '/truffle.app.BackgroundAppService/OnRun', @@ -52,11 +52,6 @@ def __init__(self, channel): request_serializer=truffle_dot_app_dot_background__pb2.BackgroundAppYieldRequest.SerializeToString, response_deserializer=truffle_dot_app_dot_background__pb2.BackgroundAppYieldResponse.FromString, _registered_method=True) - self.ReportError = channel.unary_unary( - '/truffle.app.BackgroundAppService/ReportError', - request_serializer=truffle_dot_app_dot_background__pb2.BackgroundAppReportErrorRequest.SerializeToString, - response_deserializer=truffle_dot_app_dot_background__pb2.BackgroundAppReportErrorResponse.FromString, - _registered_method=True) class BackgroundAppServiceServicer(object): @@ -65,8 +60,8 @@ class BackgroundAppServiceServicer(object): gated by per app api key, available in the environment when the app is run. """ - def SubmitFeedContent(self, request, context): - """post to curator/feed + def Submit(self, request, context): + """post context """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -85,19 +80,13 @@ def Yield(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def ReportError(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - def add_BackgroundAppServiceServicer_to_server(servicer, server): rpc_method_handlers = { - 'SubmitFeedContent': grpc.unary_unary_rpc_method_handler( - servicer.SubmitFeedContent, - request_deserializer=truffle_dot_app_dot_background__pb2.BackgroundAppSubmitFeedContentRequest.FromString, - response_serializer=truffle_dot_app_dot_background__pb2.BackgroundAppSubmitFeedContentResponse.SerializeToString, + 'Submit': grpc.unary_unary_rpc_method_handler( + servicer.Submit, + request_deserializer=truffle_dot_app_dot_background__pb2.BackgroundAppSubmitContextRequest.FromString, + response_serializer=truffle_dot_app_dot_background__pb2.BackgroundAppSubmitContextResponse.SerializeToString, ), 'OnRun': grpc.unary_unary_rpc_method_handler( servicer.OnRun, @@ -109,11 +98,6 @@ def add_BackgroundAppServiceServicer_to_server(servicer, server): request_deserializer=truffle_dot_app_dot_background__pb2.BackgroundAppYieldRequest.FromString, response_serializer=truffle_dot_app_dot_background__pb2.BackgroundAppYieldResponse.SerializeToString, ), - 'ReportError': grpc.unary_unary_rpc_method_handler( - servicer.ReportError, - request_deserializer=truffle_dot_app_dot_background__pb2.BackgroundAppReportErrorRequest.FromString, - response_serializer=truffle_dot_app_dot_background__pb2.BackgroundAppReportErrorResponse.SerializeToString, - ), } generic_handler = grpc.method_handlers_generic_handler( 'truffle.app.BackgroundAppService', rpc_method_handlers) @@ -129,7 +113,7 @@ class BackgroundAppService(object): """ @staticmethod - def SubmitFeedContent(request, + def Submit(request, target, options=(), channel_credentials=None, @@ -142,9 +126,9 @@ def SubmitFeedContent(request, return grpc.experimental.unary_unary( request, target, - '/truffle.app.BackgroundAppService/SubmitFeedContent', - truffle_dot_app_dot_background__pb2.BackgroundAppSubmitFeedContentRequest.SerializeToString, - truffle_dot_app_dot_background__pb2.BackgroundAppSubmitFeedContentResponse.FromString, + '/truffle.app.BackgroundAppService/Submit', + truffle_dot_app_dot_background__pb2.BackgroundAppSubmitContextRequest.SerializeToString, + truffle_dot_app_dot_background__pb2.BackgroundAppSubmitContextResponse.FromString, options, channel_credentials, insecure, @@ -208,30 +192,3 @@ def Yield(request, timeout, metadata, _registered_method=True) - - @staticmethod - def ReportError(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.app.BackgroundAppService/ReportError', - truffle_dot_app_dot_background__pb2.BackgroundAppReportErrorRequest.SerializeToString, - truffle_dot_app_dot_background__pb2.BackgroundAppReportErrorResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/truffle/app/curator_pb2.py b/truffle/app/curator_pb2.py deleted file mode 100644 index 3893185..0000000 --- a/truffle/app/curator_pb2.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/app/curator.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'truffle/app/curator.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from truffle.app import background_feed_pb2 as truffle_dot_app_dot_background__feed__pb2 -from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 -from truffle.os import background_feed_queries_pb2 as truffle_dot_os_dot_background__feed__queries__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19truffle/app/curator.proto\x12\x0btruffle.app\x1a!truffle/app/background_feed.proto\x1a\x1ctruffle/app/background.proto\x1a(truffle/os/background_feed_queries.proto\"\x16\n\x14TakeFeedbackResponse\"q\n\x13TakeFeedbackRequest\x12;\n\x08\x66\x65\x65\x64\x62\x61\x63k\x18\x01 \x01(\x0b\x32).truffle.os.BackgroundFeedFeedbackRequest\x12\x1d\n\x15\x66\x65\x65\x64\x62\x61\x63k_request_uuid\x18\x02 \x01(\t\"\xae\x01\n\rFeedOperation\x12\x15\n\rfeed_entry_id\x18\x01 \x01(\x04\x12\x43\n\toperation\x18\x02 \x01(\x0e\x32\x30.truffle.app.BackgroundAppNotification.Operation\x12\x30\n\x0cupdated_card\x18\x03 \x01(\x0b\x32\x15.truffle.app.FeedCardH\x00\x88\x01\x01\x42\x0f\n\r_updated_card\"@\n\x14HandleNewPostRequest\x12(\n\x08new_post\x18\x01 \x01(\x0b\x32\x16.truffle.app.FeedEntry\"\x17\n\x15HandleNewPostResponse\"\"\n\x0c\x43uratorState\x12\x12\n\nstate_json\x18\x01 \x01(\t\"\x87\x01\n\x12\x46\x65\x65\x64\x43ontrolRequest\x12\x38\n\x10last_known_state\x18\x01 \x01(\x0b\x32\x19.truffle.app.CuratorStateH\x00\x88\x01\x01\x12\"\n\x1a\x63urator_user_session_token\x18\x02 \x01(\tB\x13\n\x11_last_known_state\"2\n\x11\x46\x65\x65\x64\x62\x61\x63kProcessed\x12\x1d\n\x15\x66\x65\x65\x64\x62\x61\x63k_request_uuid\x18\x01 \x01(\t\"\xbe\x01\n\x13\x46\x65\x65\x64\x43ontrolResponse\x12/\n\toperation\x18\x01 \x01(\x0b\x32\x1a.truffle.app.FeedOperationH\x00\x12\x32\n\rstate_to_save\x18\x02 \x01(\x0b\x32\x19.truffle.app.CuratorStateH\x00\x12\x37\n\rfeedback_done\x18\x03 \x01(\x0b\x32\x1e.truffle.app.FeedbackProcessedH\x00\x42\t\n\x07\x63ontrol2\x91\x02\n\x0e\x43uratorService\x12S\n\x0cTakeFeedback\x12 .truffle.app.TakeFeedbackRequest\x1a!.truffle.app.TakeFeedbackResponse\x12V\n\rHandleNewPost\x12!.truffle.app.HandleNewPostRequest\x1a\".truffle.app.HandleNewPostResponse\x12R\n\x0b\x46\x65\x65\x64\x43ontrol\x12\x1f.truffle.app.FeedControlRequest\x1a .truffle.app.FeedControlResponse0\x01\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.curator_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_TAKEFEEDBACKRESPONSE']._serialized_start=149 - _globals['_TAKEFEEDBACKRESPONSE']._serialized_end=171 - _globals['_TAKEFEEDBACKREQUEST']._serialized_start=173 - _globals['_TAKEFEEDBACKREQUEST']._serialized_end=286 - _globals['_FEEDOPERATION']._serialized_start=289 - _globals['_FEEDOPERATION']._serialized_end=463 - _globals['_HANDLENEWPOSTREQUEST']._serialized_start=465 - _globals['_HANDLENEWPOSTREQUEST']._serialized_end=529 - _globals['_HANDLENEWPOSTRESPONSE']._serialized_start=531 - _globals['_HANDLENEWPOSTRESPONSE']._serialized_end=554 - _globals['_CURATORSTATE']._serialized_start=556 - _globals['_CURATORSTATE']._serialized_end=590 - _globals['_FEEDCONTROLREQUEST']._serialized_start=593 - _globals['_FEEDCONTROLREQUEST']._serialized_end=728 - _globals['_FEEDBACKPROCESSED']._serialized_start=730 - _globals['_FEEDBACKPROCESSED']._serialized_end=780 - _globals['_FEEDCONTROLRESPONSE']._serialized_start=783 - _globals['_FEEDCONTROLRESPONSE']._serialized_end=973 - _globals['_CURATORSERVICE']._serialized_start=976 - _globals['_CURATORSERVICE']._serialized_end=1249 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/app/curator_pb2.pyi b/truffle/app/curator_pb2.pyi deleted file mode 100644 index 7317d86..0000000 --- a/truffle/app/curator_pb2.pyi +++ /dev/null @@ -1,71 +0,0 @@ -from truffle.app import background_feed_pb2 as _background_feed_pb2 -from truffle.app import background_pb2 as _background_pb2 -from truffle.os import background_feed_queries_pb2 as _background_feed_queries_pb2 -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class TakeFeedbackResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class TakeFeedbackRequest(_message.Message): - __slots__ = ("feedback", "feedback_request_uuid") - FEEDBACK_FIELD_NUMBER: _ClassVar[int] - FEEDBACK_REQUEST_UUID_FIELD_NUMBER: _ClassVar[int] - feedback: _background_feed_queries_pb2.BackgroundFeedFeedbackRequest - feedback_request_uuid: str - def __init__(self, feedback: _Optional[_Union[_background_feed_queries_pb2.BackgroundFeedFeedbackRequest, _Mapping]] = ..., feedback_request_uuid: _Optional[str] = ...) -> None: ... - -class FeedOperation(_message.Message): - __slots__ = ("feed_entry_id", "operation", "updated_card") - FEED_ENTRY_ID_FIELD_NUMBER: _ClassVar[int] - OPERATION_FIELD_NUMBER: _ClassVar[int] - UPDATED_CARD_FIELD_NUMBER: _ClassVar[int] - feed_entry_id: int - operation: _background_pb2.BackgroundAppNotification.Operation - updated_card: _background_feed_pb2.FeedCard - def __init__(self, feed_entry_id: _Optional[int] = ..., operation: _Optional[_Union[_background_pb2.BackgroundAppNotification.Operation, str]] = ..., updated_card: _Optional[_Union[_background_feed_pb2.FeedCard, _Mapping]] = ...) -> None: ... - -class HandleNewPostRequest(_message.Message): - __slots__ = ("new_post",) - NEW_POST_FIELD_NUMBER: _ClassVar[int] - new_post: _background_feed_pb2.FeedEntry - def __init__(self, new_post: _Optional[_Union[_background_feed_pb2.FeedEntry, _Mapping]] = ...) -> None: ... - -class HandleNewPostResponse(_message.Message): - __slots__ = () - def __init__(self) -> None: ... - -class CuratorState(_message.Message): - __slots__ = ("state_json",) - STATE_JSON_FIELD_NUMBER: _ClassVar[int] - state_json: str - def __init__(self, state_json: _Optional[str] = ...) -> None: ... - -class FeedControlRequest(_message.Message): - __slots__ = ("last_known_state", "curator_user_session_token") - LAST_KNOWN_STATE_FIELD_NUMBER: _ClassVar[int] - CURATOR_USER_SESSION_TOKEN_FIELD_NUMBER: _ClassVar[int] - last_known_state: CuratorState - curator_user_session_token: str - def __init__(self, last_known_state: _Optional[_Union[CuratorState, _Mapping]] = ..., curator_user_session_token: _Optional[str] = ...) -> None: ... - -class FeedbackProcessed(_message.Message): - __slots__ = ("feedback_request_uuid",) - FEEDBACK_REQUEST_UUID_FIELD_NUMBER: _ClassVar[int] - feedback_request_uuid: str - def __init__(self, feedback_request_uuid: _Optional[str] = ...) -> None: ... - -class FeedControlResponse(_message.Message): - __slots__ = ("operation", "state_to_save", "feedback_done") - OPERATION_FIELD_NUMBER: _ClassVar[int] - STATE_TO_SAVE_FIELD_NUMBER: _ClassVar[int] - FEEDBACK_DONE_FIELD_NUMBER: _ClassVar[int] - operation: FeedOperation - state_to_save: CuratorState - feedback_done: FeedbackProcessed - def __init__(self, operation: _Optional[_Union[FeedOperation, _Mapping]] = ..., state_to_save: _Optional[_Union[CuratorState, _Mapping]] = ..., feedback_done: _Optional[_Union[FeedbackProcessed, _Mapping]] = ...) -> None: ... diff --git a/truffle/app/curator_pb2_grpc.py b/truffle/app/curator_pb2_grpc.py deleted file mode 100644 index 000ab71..0000000 --- a/truffle/app/curator_pb2_grpc.py +++ /dev/null @@ -1,183 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - -from truffle.app import curator_pb2 as truffle_dot_app_dot_curator__pb2 - -GRPC_GENERATED_VERSION = '1.72.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/curator_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) - - -class CuratorServiceStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.TakeFeedback = channel.unary_unary( - '/truffle.app.CuratorService/TakeFeedback', - request_serializer=truffle_dot_app_dot_curator__pb2.TakeFeedbackRequest.SerializeToString, - response_deserializer=truffle_dot_app_dot_curator__pb2.TakeFeedbackResponse.FromString, - _registered_method=True) - self.HandleNewPost = channel.unary_unary( - '/truffle.app.CuratorService/HandleNewPost', - request_serializer=truffle_dot_app_dot_curator__pb2.HandleNewPostRequest.SerializeToString, - response_deserializer=truffle_dot_app_dot_curator__pb2.HandleNewPostResponse.FromString, - _registered_method=True) - self.FeedControl = channel.unary_stream( - '/truffle.app.CuratorService/FeedControl', - request_serializer=truffle_dot_app_dot_curator__pb2.FeedControlRequest.SerializeToString, - response_deserializer=truffle_dot_app_dot_curator__pb2.FeedControlResponse.FromString, - _registered_method=True) - - -class CuratorServiceServicer(object): - """Missing associated documentation comment in .proto file.""" - - def TakeFeedback(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def HandleNewPost(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def FeedControl(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_CuratorServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'TakeFeedback': grpc.unary_unary_rpc_method_handler( - servicer.TakeFeedback, - request_deserializer=truffle_dot_app_dot_curator__pb2.TakeFeedbackRequest.FromString, - response_serializer=truffle_dot_app_dot_curator__pb2.TakeFeedbackResponse.SerializeToString, - ), - 'HandleNewPost': grpc.unary_unary_rpc_method_handler( - servicer.HandleNewPost, - request_deserializer=truffle_dot_app_dot_curator__pb2.HandleNewPostRequest.FromString, - response_serializer=truffle_dot_app_dot_curator__pb2.HandleNewPostResponse.SerializeToString, - ), - 'FeedControl': grpc.unary_stream_rpc_method_handler( - servicer.FeedControl, - request_deserializer=truffle_dot_app_dot_curator__pb2.FeedControlRequest.FromString, - response_serializer=truffle_dot_app_dot_curator__pb2.FeedControlResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'truffle.app.CuratorService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('truffle.app.CuratorService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class CuratorService(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def TakeFeedback(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.app.CuratorService/TakeFeedback', - truffle_dot_app_dot_curator__pb2.TakeFeedbackRequest.SerializeToString, - truffle_dot_app_dot_curator__pb2.TakeFeedbackResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def HandleNewPost(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.app.CuratorService/HandleNewPost', - truffle_dot_app_dot_curator__pb2.HandleNewPostRequest.SerializeToString, - truffle_dot_app_dot_curator__pb2.HandleNewPostResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def FeedControl(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/truffle.app.CuratorService/FeedControl', - truffle_dot_app_dot_curator__pb2.FeedControlRequest.SerializeToString, - truffle_dot_app_dot_curator__pb2.FeedControlResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/truffle/app/default_app_manifest_pb2.py b/truffle/app/default_app_manifest_pb2.py index 1ae3978..a996440 100644 --- a/truffle/app/default_app_manifest_pb2.py +++ b/truffle/app/default_app_manifest_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/app/default_app_manifest.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/app/default_app_manifest.proto' ) @@ -22,20 +22,20 @@ _sym_db = _symbol_database.Default() -from truffle.app import app_type_pb2 as truffle_dot_app_dot_app__type__pb2 from truffle.common import icon_pb2 as truffle_dot_common_dot_icon__pb2 from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from truffle.app import app_pb2 as truffle_dot_app_dot_app__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&truffle/app/default_app_manifest.proto\x12\x0btruffle.app\x1a\x1atruffle/app/app_type.proto\x1a\x19truffle/common/icon.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb7\x02\n\x12\x44\x65\x66\x61ultAppManifest\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x30\n\x0cgenerated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x38\n\x04\x61pps\x18\x03 \x03(\x0b\x32*.truffle.app.DefaultAppManifest.DefaultApp\x1a\xa3\x01\n\nDefaultApp\x12&\n\x08\x61pp_type\x18\x01 \x01(\x0e\x32\x14.truffle.app.AppType\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x12\n\nbundle_url\x18\x03 \x01(\t\x12\"\n\x04icon\x18\x04 \x01(\x0b\x32\x14.truffle.common.Icon\x12\x12\n\nbundle_md5\x18\x05 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x06 \x01(\tb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&truffle/app/default_app_manifest.proto\x12\x0btruffle.app\x1a\x19truffle/common/icon.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x15truffle/app/app.proto\"\xbd\x02\n\x12\x44\x65\x66\x61ultAppManifest\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x30\n\x0cgenerated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x38\n\x04\x61pps\x18\x03 \x03(\x0b\x32*.truffle.app.DefaultAppManifest.DefaultApp\x1a\xa9\x01\n\nDefaultApp\x12\r\n\x05index\x18\x01 \x01(\r\x12\x12\n\nbundle_url\x18\x02 \x01(\t\x12*\n\x08metadata\x18\x03 \x01(\x0b\x32\x18.truffle.app.AppMetadata\x12\x12\n\nbundle_md5\x18\x04 \x01(\t\x12\x1b\n\x13provides_foreground\x18\x05 \x01(\x08\x12\x1b\n\x13provides_background\x18\x06 \x01(\x08\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.default_app_manifest_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_DEFAULTAPPMANIFEST']._serialized_start=144 - _globals['_DEFAULTAPPMANIFEST']._serialized_end=455 - _globals['_DEFAULTAPPMANIFEST_DEFAULTAPP']._serialized_start=292 - _globals['_DEFAULTAPPMANIFEST_DEFAULTAPP']._serialized_end=455 + _globals['_DEFAULTAPPMANIFEST']._serialized_start=139 + _globals['_DEFAULTAPPMANIFEST']._serialized_end=456 + _globals['_DEFAULTAPPMANIFEST_DEFAULTAPP']._serialized_start=287 + _globals['_DEFAULTAPPMANIFEST_DEFAULTAPP']._serialized_end=456 # @@protoc_insertion_point(module_scope) diff --git a/truffle/app/default_app_manifest_pb2.pyi b/truffle/app/default_app_manifest_pb2.pyi index a06eb47..aa96a8d 100644 --- a/truffle/app/default_app_manifest_pb2.pyi +++ b/truffle/app/default_app_manifest_pb2.pyi @@ -1,6 +1,8 @@ -from truffle.app import app_type_pb2 as _app_type_pb2 +import datetime + from truffle.common import icon_pb2 as _icon_pb2 from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from truffle.app import app_pb2 as _app_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message @@ -12,24 +14,24 @@ DESCRIPTOR: _descriptor.FileDescriptor class DefaultAppManifest(_message.Message): __slots__ = ("version", "generated_at", "apps") class DefaultApp(_message.Message): - __slots__ = ("app_type", "name", "bundle_url", "icon", "bundle_md5", "description") - APP_TYPE_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("index", "bundle_url", "metadata", "bundle_md5", "provides_foreground", "provides_background") + INDEX_FIELD_NUMBER: _ClassVar[int] BUNDLE_URL_FIELD_NUMBER: _ClassVar[int] - ICON_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] BUNDLE_MD5_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - app_type: _app_type_pb2.AppType - name: str + PROVIDES_FOREGROUND_FIELD_NUMBER: _ClassVar[int] + PROVIDES_BACKGROUND_FIELD_NUMBER: _ClassVar[int] + index: int bundle_url: str - icon: _icon_pb2.Icon + metadata: _app_pb2.AppMetadata bundle_md5: str - description: str - def __init__(self, app_type: _Optional[_Union[_app_type_pb2.AppType, str]] = ..., name: _Optional[str] = ..., bundle_url: _Optional[str] = ..., icon: _Optional[_Union[_icon_pb2.Icon, _Mapping]] = ..., bundle_md5: _Optional[str] = ..., description: _Optional[str] = ...) -> None: ... + provides_foreground: bool + provides_background: bool + def __init__(self, index: _Optional[int] = ..., bundle_url: _Optional[str] = ..., metadata: _Optional[_Union[_app_pb2.AppMetadata, _Mapping]] = ..., bundle_md5: _Optional[str] = ..., provides_foreground: bool = ..., provides_background: bool = ...) -> None: ... VERSION_FIELD_NUMBER: _ClassVar[int] GENERATED_AT_FIELD_NUMBER: _ClassVar[int] APPS_FIELD_NUMBER: _ClassVar[int] version: str generated_at: _timestamp_pb2.Timestamp apps: _containers.RepeatedCompositeFieldContainer[DefaultAppManifest.DefaultApp] - def __init__(self, version: _Optional[str] = ..., generated_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., apps: _Optional[_Iterable[_Union[DefaultAppManifest.DefaultApp, _Mapping]]] = ...) -> None: ... + def __init__(self, version: _Optional[str] = ..., generated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., apps: _Optional[_Iterable[_Union[DefaultAppManifest.DefaultApp, _Mapping]]] = ...) -> None: ... diff --git a/truffle/app/default_app_manifest_pb2_grpc.py b/truffle/app/default_app_manifest_pb2_grpc.py index 8507aef..f33f561 100644 --- a/truffle/app/default_app_manifest_pb2_grpc.py +++ b/truffle/app/default_app_manifest_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/default_app_manifest_pb2_grpc.py depends on' + + ' but the generated code in truffle/app/default_app_manifest_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/app/foreground_pb2.py b/truffle/app/foreground_pb2.py index 730276d..d358d53 100644 --- a/truffle/app/foreground_pb2.py +++ b/truffle/app/foreground_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/app/foreground.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/app/foreground.proto' ) @@ -22,20 +22,20 @@ _sym_db = _symbol_database.Default() -from truffle.common import icon_pb2 as truffle_dot_common_dot_icon__pb2 +from truffle.app import app_build_pb2 as truffle_dot_app_dot_app__build__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/app/foreground.proto\x12\x0btruffle.app\x1a\x19truffle/common/icon.proto\"\xa7\x01\n\rForegroundApp\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x35\n\x08metadata\x18\x02 \x01(\x0b\x32#.truffle.app.ForegroundApp.Metadata\x1aQ\n\x08Metadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\"\n\x04icon\x18\x02 \x01(\x0b\x32\x14.truffle.common.Icon\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\"O\n\x16\x46oregroundAppBuildInfo\x12\x35\n\x08metadata\x18\x01 \x01(\x0b\x32#.truffle.app.ForegroundApp.Metadatab\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/app/foreground.proto\x12\x0btruffle.app\x1a\x1btruffle/app/app_build.proto\"\xa5\x01\n\rForegroundApp\x12\x41\n\x0f\x61vailable_tools\x18\x01 \x03(\x0b\x32(.truffle.app.ForegroundApp.AvailableTool\x1aQ\n\rAvailableTool\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x18\n\x10tool_description\x18\x02 \x01(\t\x12\x13\n\x0b\x61rgs_schema\x18\x03 \x01(\t\"E\n\x16\x46oregroundAppBuildInfo\x12+\n\x07process\x18\x01 \x01(\x0b\x32\x1a.truffle.app.ProcessConfigb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.foreground_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_FOREGROUNDAPP']._serialized_start=73 + _globals['_FOREGROUNDAPP']._serialized_start=75 _globals['_FOREGROUNDAPP']._serialized_end=240 - _globals['_FOREGROUNDAPP_METADATA']._serialized_start=159 - _globals['_FOREGROUNDAPP_METADATA']._serialized_end=240 + _globals['_FOREGROUNDAPP_AVAILABLETOOL']._serialized_start=159 + _globals['_FOREGROUNDAPP_AVAILABLETOOL']._serialized_end=240 _globals['_FOREGROUNDAPPBUILDINFO']._serialized_start=242 - _globals['_FOREGROUNDAPPBUILDINFO']._serialized_end=321 + _globals['_FOREGROUNDAPPBUILDINFO']._serialized_end=311 # @@protoc_insertion_point(module_scope) diff --git a/truffle/app/foreground_pb2.pyi b/truffle/app/foreground_pb2.pyi index e946641..ffe121b 100644 --- a/truffle/app/foreground_pb2.pyi +++ b/truffle/app/foreground_pb2.pyi @@ -1,30 +1,29 @@ -from truffle.common import icon_pb2 as _icon_pb2 +from truffle.app import app_build_pb2 as _app_build_pb2 +from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping +from collections.abc import Iterable as _Iterable, Mapping as _Mapping from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor class ForegroundApp(_message.Message): - __slots__ = ("uuid", "metadata") - class Metadata(_message.Message): - __slots__ = ("name", "icon", "description") - NAME_FIELD_NUMBER: _ClassVar[int] - ICON_FIELD_NUMBER: _ClassVar[int] - DESCRIPTION_FIELD_NUMBER: _ClassVar[int] - name: str - icon: _icon_pb2.Icon - description: str - def __init__(self, name: _Optional[str] = ..., icon: _Optional[_Union[_icon_pb2.Icon, _Mapping]] = ..., description: _Optional[str] = ...) -> None: ... - UUID_FIELD_NUMBER: _ClassVar[int] - METADATA_FIELD_NUMBER: _ClassVar[int] - uuid: str - metadata: ForegroundApp.Metadata - def __init__(self, uuid: _Optional[str] = ..., metadata: _Optional[_Union[ForegroundApp.Metadata, _Mapping]] = ...) -> None: ... + __slots__ = ("available_tools",) + class AvailableTool(_message.Message): + __slots__ = ("tool_name", "tool_description", "args_schema") + TOOL_NAME_FIELD_NUMBER: _ClassVar[int] + TOOL_DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + ARGS_SCHEMA_FIELD_NUMBER: _ClassVar[int] + tool_name: str + tool_description: str + args_schema: str + def __init__(self, tool_name: _Optional[str] = ..., tool_description: _Optional[str] = ..., args_schema: _Optional[str] = ...) -> None: ... + AVAILABLE_TOOLS_FIELD_NUMBER: _ClassVar[int] + available_tools: _containers.RepeatedCompositeFieldContainer[ForegroundApp.AvailableTool] + def __init__(self, available_tools: _Optional[_Iterable[_Union[ForegroundApp.AvailableTool, _Mapping]]] = ...) -> None: ... class ForegroundAppBuildInfo(_message.Message): - __slots__ = ("metadata",) - METADATA_FIELD_NUMBER: _ClassVar[int] - metadata: ForegroundApp.Metadata - def __init__(self, metadata: _Optional[_Union[ForegroundApp.Metadata, _Mapping]] = ...) -> None: ... + __slots__ = ("process",) + PROCESS_FIELD_NUMBER: _ClassVar[int] + process: _app_build_pb2.ProcessConfig + def __init__(self, process: _Optional[_Union[_app_build_pb2.ProcessConfig, _Mapping]] = ...) -> None: ... diff --git a/truffle/app/foreground_pb2_grpc.py b/truffle/app/foreground_pb2_grpc.py index b0258f3..31cdf1f 100644 --- a/truffle/app/foreground_pb2_grpc.py +++ b/truffle/app/foreground_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/foreground_pb2_grpc.py depends on' + + ' but the generated code in truffle/app/foreground_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/app/system_pb2.py b/truffle/app/system_pb2.py deleted file mode 100644 index fa84ae9..0000000 --- a/truffle/app/system_pb2.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/app/system.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'truffle/app/system.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 -from truffle.app import app_build_pb2 as truffle_dot_app_dot_app__build__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18truffle/app/system.proto\x12\x0btruffle.app\x1a\x1ctruffle/app/background.proto\x1a\x1btruffle/app/app_build.proto\"\xc5\x01\n\tSystemApp\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x12\n\nsource_dir\x18\x02 \x01(\t\x12+\n\x07process\x18\x03 \x01(\x0b\x32\x1a.truffle.app.ProcessConfig\x12\x0f\n\x07no_ckpt\x18\x04 \x01(\x08\x12\x45\n\x0fschedule_policy\x18\x05 \x01(\x0b\x32\'.truffle.app.BackgroundAppRuntimePolicyH\x00\x88\x01\x01\x42\x12\n\x10_schedule_policyb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.system_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_SYSTEMAPP']._serialized_start=101 - _globals['_SYSTEMAPP']._serialized_end=298 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/app/system_pb2.pyi b/truffle/app/system_pb2.pyi deleted file mode 100644 index 1529d8c..0000000 --- a/truffle/app/system_pb2.pyi +++ /dev/null @@ -1,22 +0,0 @@ -from truffle.app import background_pb2 as _background_pb2 -from truffle.app import app_build_pb2 as _app_build_pb2 -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class SystemApp(_message.Message): - __slots__ = ("key", "source_dir", "process", "no_ckpt", "schedule_policy") - KEY_FIELD_NUMBER: _ClassVar[int] - SOURCE_DIR_FIELD_NUMBER: _ClassVar[int] - PROCESS_FIELD_NUMBER: _ClassVar[int] - NO_CKPT_FIELD_NUMBER: _ClassVar[int] - SCHEDULE_POLICY_FIELD_NUMBER: _ClassVar[int] - key: str - source_dir: str - process: _app_build_pb2.ProcessConfig - no_ckpt: bool - schedule_policy: _background_pb2.BackgroundAppRuntimePolicy - def __init__(self, key: _Optional[str] = ..., source_dir: _Optional[str] = ..., process: _Optional[_Union[_app_build_pb2.ProcessConfig, _Mapping]] = ..., no_ckpt: bool = ..., schedule_policy: _Optional[_Union[_background_pb2.BackgroundAppRuntimePolicy, _Mapping]] = ...) -> None: ... diff --git a/truffle/app/task_runtime_pb2.py b/truffle/app/task_runtime_pb2.py deleted file mode 100644 index 9a46ed1..0000000 --- a/truffle/app/task_runtime_pb2.py +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/app/task_runtime.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'truffle/app/task_runtime.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from truffle.os import task_pb2 as truffle_dot_os_dot_task__pb2 -try: - truffle_dot_os_dot_task__info__pb2 = truffle_dot_os_dot_task__pb2.truffle_dot_os_dot_task__info__pb2 -except AttributeError: - truffle_dot_os_dot_task__info__pb2 = truffle_dot_os_dot_task__pb2.truffle.os.task_info_pb2 -try: - truffle_dot_os_dot_task__user__response__pb2 = truffle_dot_os_dot_task__pb2.truffle_dot_os_dot_task__user__response__pb2 -except AttributeError: - truffle_dot_os_dot_task__user__response__pb2 = truffle_dot_os_dot_task__pb2.truffle.os.task_user_response_pb2 -try: - truffle_dot_os_dot_task__step__pb2 = truffle_dot_os_dot_task__pb2.truffle_dot_os_dot_task__step__pb2 -except AttributeError: - truffle_dot_os_dot_task__step__pb2 = truffle_dot_os_dot_task__pb2.truffle.os.task_step_pb2 -try: - truffle_dot_common_dot_content__pb2 = truffle_dot_os_dot_task__pb2.truffle_dot_common_dot_content__pb2 -except AttributeError: - truffle_dot_common_dot_content__pb2 = truffle_dot_os_dot_task__pb2.truffle.common.content_pb2 -from truffle.os import task_user_response_pb2 as truffle_dot_os_dot_task__user__response__pb2 -from truffle.os import task_actions_pb2 as truffle_dot_os_dot_task__actions__pb2 -try: - truffle_dot_os_dot_task__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_os_dot_task__pb2 -except AttributeError: - truffle_dot_os_dot_task__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.os.task_pb2 -try: - truffle_dot_os_dot_task__info__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_os_dot_task__info__pb2 -except AttributeError: - truffle_dot_os_dot_task__info__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.os.task_info_pb2 -try: - truffle_dot_os_dot_task__user__response__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_os_dot_task__user__response__pb2 -except AttributeError: - truffle_dot_os_dot_task__user__response__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.os.task_user_response_pb2 -try: - truffle_dot_os_dot_task__step__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_os_dot_task__step__pb2 -except AttributeError: - truffle_dot_os_dot_task__step__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.os.task_step_pb2 -try: - truffle_dot_common_dot_content__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_common_dot_content__pb2 -except AttributeError: - truffle_dot_common_dot_content__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.common.content_pb2 -try: - truffle_dot_os_dot_task__target__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_os_dot_task__target__pb2 -except AttributeError: - truffle_dot_os_dot_task__target__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.os.task_target_pb2 -try: - truffle_dot_os_dot_task__options__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_os_dot_task__options__pb2 -except AttributeError: - truffle_dot_os_dot_task__options__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.os.task_options_pb2 -try: - truffle_dot_os_dot_task__user__response__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_os_dot_task__user__response__pb2 -except AttributeError: - truffle_dot_os_dot_task__user__response__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.os.task_user_response_pb2 -try: - truffle_dot_common_dot_tool__provider__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle_dot_common_dot_tool__provider__pb2 -except AttributeError: - truffle_dot_common_dot_tool__provider__pb2 = truffle_dot_os_dot_task__actions__pb2.truffle.common.tool_provider_pb2 -from truffle.os import task_step_pb2 as truffle_dot_os_dot_task__step__pb2 -try: - truffle_dot_common_dot_content__pb2 = truffle_dot_os_dot_task__step__pb2.truffle_dot_common_dot_content__pb2 -except AttributeError: - truffle_dot_common_dot_content__pb2 = truffle_dot_os_dot_task__step__pb2.truffle.common.content_pb2 -from truffle.infer.convo import conversation_pb2 as truffle_dot_infer_dot_convo_dot_conversation__pb2 -try: - truffle_dot_infer_dot_convo_dot_msg__pb2 = truffle_dot_infer_dot_convo_dot_conversation__pb2.truffle_dot_infer_dot_convo_dot_msg__pb2 -except AttributeError: - truffle_dot_infer_dot_convo_dot_msg__pb2 = truffle_dot_infer_dot_convo_dot_conversation__pb2.truffle.infer.convo.msg_pb2 -from truffle.app import background_feed_pb2 as truffle_dot_app_dot_background__feed__pb2 -from truffle.common import file_pb2 as truffle_dot_common_dot_file__pb2 -from truffle.os import task_options_pb2 as truffle_dot_os_dot_task__options__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1etruffle/app/task_runtime.proto\x12\x0btruffle.app\x1a\x15truffle/os/task.proto\x1a#truffle/os/task_user_response.proto\x1a\x1dtruffle/os/task_actions.proto\x1a\x1atruffle/os/task_step.proto\x1a&truffle/infer/convo/conversation.proto\x1a!truffle/app/background_feed.proto\x1a\x19truffle/common/file.proto\x1a\x1dtruffle/os/task_options.proto\"\xad\x01\n\rToolsProvider\x12:\n\nmcp_server\x18\x01 \x01(\x0b\x32$.truffle.app.ToolsProvider.MCPServerH\x00\x1aT\n\tMCPServer\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\r\x12\x11\n\x04path\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x07\n\x05_pathB\n\n\x08provider\"K\n\x16\x41\x64\x64ToolProviderRequest\x12\x31\n\rtool_provider\x18\x01 \x01(\x0b\x32\x1a.truffle.app.ToolsProvider\"U\n\x17\x41\x64\x64ToolProviderResponse\x12:\n\x16\x63urrent_tool_providers\x18\x01 \x03(\x0b\x32\x1a.truffle.app.ToolsProvider\"2\n\x19RemoveToolProviderRequest\x12\x15\n\rprovider_uuid\x18\x01 \x01(\t\"X\n\x1aRemoveToolProviderResponse\x12:\n\x16\x63urrent_tool_providers\x18\x01 \x03(\x0b\x32\x1a.truffle.app.ToolsProvider\"h\n\x11TaskContextUpdate\x12\x37\n\x0clatest_convo\x18\x01 \x01(\x0b\x32!.truffle.infer.convo.Conversation\x12\x1a\n\x12\x61ssociated_node_id\x18\x02 \x01(\x05\"\xd5\x01\n\x07NewTask\x12-\n\x0cuser_message\x18\x01 \x01(\x0b\x32\x17.truffle.os.UserMessage\x12:\n\x0e\x61ttached_files\x18\x02 \x03(\x0b\x32\".truffle.common.AttachedFileIntent\x12\x45\n\x15\x61ttached_feed_entries\x18\x03 \x01(\x0b\x32!.truffle.app.FeedEntryTaskContextH\x00\x88\x01\x01\x42\x18\n\x16_attached_feed_entries\"b\n\x08PrevTask\x12\x1e\n\x04task\x18\x01 \x01(\x0b\x32\x10.truffle.os.Task\x12\x36\n\x0elatest_context\x18\x02 \x01(\x0b\x32\x1e.truffle.app.TaskContextUpdate\"\xd5\x01\n\x10StartTaskRequest\x12(\n\x08new_task\x18\x01 \x01(\x0b\x32\x14.truffle.app.NewTaskH\x00\x12*\n\tprev_task\x18\x02 \x01(\x0b\x32\x15.truffle.app.PrevTaskH\x00\x12(\n\x07options\x18\x03 \x01(\x0b\x32\x17.truffle.os.TaskOptions\x12\x32\n\x0etool_providers\x18\x04 \x03(\x0b\x32\x1a.truffle.app.ToolsProviderB\r\n\x0btask_source\"\x87\x01\n\x10TaskRuntimeError\x12\r\n\x05\x65rror\x18\x01 \x01(\t\x12\x14\n\x07\x64\x65tails\x18\x02 \x01(\tH\x00\x88\x01\x01\x12%\n\x18\x61ssociated_provider_uuid\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\n\n\x08_detailsB\x1b\n\x19_associated_provider_uuid\"\xc4\x01\n\x11TaskRuntimeUpdate\x12\x33\n\x0btask_update\x18\x01 \x01(\x0b\x32\x1c.truffle.os.TaskStreamUpdateH\x00\x12\x36\n\rruntime_error\x18\x02 \x01(\x0b\x32\x1d.truffle.app.TaskRuntimeErrorH\x00\x12\x38\n\x0e\x63ontext_update\x18\x03 \x01(\x0b\x32\x1e.truffle.app.TaskContextUpdateH\x00\x42\x08\n\x06update2\xd3\x03\n\x12TaskRuntimeService\x12K\n\x08OpenTask\x12\x1d.truffle.app.StartTaskRequest\x1a\x1e.truffle.app.TaskRuntimeUpdate0\x01\x12S\n\x0fHandleInterrupt\x12 .truffle.os.InterruptTaskRequest\x1a\x1e.truffle.os.TaskActionResponse\x12V\n\x12HandleUserResponse\x12 .truffle.os.RespondToTaskRequest\x1a\x1e.truffle.os.TaskActionResponse\x12\\\n\x0f\x41\x64\x64ToolProvider\x12#.truffle.app.AddToolProviderRequest\x1a$.truffle.app.AddToolProviderResponse\x12\x65\n\x12RemoveToolProvider\x12&.truffle.app.RemoveToolProviderRequest\x1a\'.truffle.app.RemoveToolProviderResponseb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.app.task_runtime_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_TOOLSPROVIDER']._serialized_start=300 - _globals['_TOOLSPROVIDER']._serialized_end=473 - _globals['_TOOLSPROVIDER_MCPSERVER']._serialized_start=377 - _globals['_TOOLSPROVIDER_MCPSERVER']._serialized_end=461 - _globals['_ADDTOOLPROVIDERREQUEST']._serialized_start=475 - _globals['_ADDTOOLPROVIDERREQUEST']._serialized_end=550 - _globals['_ADDTOOLPROVIDERRESPONSE']._serialized_start=552 - _globals['_ADDTOOLPROVIDERRESPONSE']._serialized_end=637 - _globals['_REMOVETOOLPROVIDERREQUEST']._serialized_start=639 - _globals['_REMOVETOOLPROVIDERREQUEST']._serialized_end=689 - _globals['_REMOVETOOLPROVIDERRESPONSE']._serialized_start=691 - _globals['_REMOVETOOLPROVIDERRESPONSE']._serialized_end=779 - _globals['_TASKCONTEXTUPDATE']._serialized_start=781 - _globals['_TASKCONTEXTUPDATE']._serialized_end=885 - _globals['_NEWTASK']._serialized_start=888 - _globals['_NEWTASK']._serialized_end=1101 - _globals['_PREVTASK']._serialized_start=1103 - _globals['_PREVTASK']._serialized_end=1201 - _globals['_STARTTASKREQUEST']._serialized_start=1204 - _globals['_STARTTASKREQUEST']._serialized_end=1417 - _globals['_TASKRUNTIMEERROR']._serialized_start=1420 - _globals['_TASKRUNTIMEERROR']._serialized_end=1555 - _globals['_TASKRUNTIMEUPDATE']._serialized_start=1558 - _globals['_TASKRUNTIMEUPDATE']._serialized_end=1754 - _globals['_TASKRUNTIMESERVICE']._serialized_start=1757 - _globals['_TASKRUNTIMESERVICE']._serialized_end=2224 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/app/task_runtime_pb2.pyi b/truffle/app/task_runtime_pb2.pyi deleted file mode 100644 index dce11c1..0000000 --- a/truffle/app/task_runtime_pb2.pyi +++ /dev/null @@ -1,124 +0,0 @@ -from truffle.os import task_pb2 as _task_pb2 -from truffle.os import task_info_pb2 as _task_info_pb2 -from truffle.os import task_user_response_pb2 as _task_user_response_pb2 -from truffle.os import task_step_pb2 as _task_step_pb2 -from truffle.os import task_user_response_pb2 as _task_user_response_pb2_1 -from truffle.os import task_actions_pb2 as _task_actions_pb2 -from truffle.os import task_pb2 as _task_pb2_1 -from truffle.os import task_target_pb2 as _task_target_pb2 -from truffle.os import task_options_pb2 as _task_options_pb2 -from truffle.os import task_user_response_pb2 as _task_user_response_pb2_1_1 -from truffle.common import tool_provider_pb2 as _tool_provider_pb2 -from truffle.os import task_step_pb2 as _task_step_pb2_1 -from truffle.common import content_pb2 as _content_pb2 -from truffle.infer.convo import conversation_pb2 as _conversation_pb2 -from truffle.infer.convo import msg_pb2 as _msg_pb2 -from truffle.app import background_feed_pb2 as _background_feed_pb2 -from truffle.common import file_pb2 as _file_pb2 -from truffle.os import task_options_pb2 as _task_options_pb2_1 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class ToolsProvider(_message.Message): - __slots__ = ("mcp_server",) - class MCPServer(_message.Message): - __slots__ = ("uuid", "address", "port", "path") - UUID_FIELD_NUMBER: _ClassVar[int] - ADDRESS_FIELD_NUMBER: _ClassVar[int] - PORT_FIELD_NUMBER: _ClassVar[int] - PATH_FIELD_NUMBER: _ClassVar[int] - uuid: str - address: str - port: int - path: str - def __init__(self, uuid: _Optional[str] = ..., address: _Optional[str] = ..., port: _Optional[int] = ..., path: _Optional[str] = ...) -> None: ... - MCP_SERVER_FIELD_NUMBER: _ClassVar[int] - mcp_server: ToolsProvider.MCPServer - def __init__(self, mcp_server: _Optional[_Union[ToolsProvider.MCPServer, _Mapping]] = ...) -> None: ... - -class AddToolProviderRequest(_message.Message): - __slots__ = ("tool_provider",) - TOOL_PROVIDER_FIELD_NUMBER: _ClassVar[int] - tool_provider: ToolsProvider - def __init__(self, tool_provider: _Optional[_Union[ToolsProvider, _Mapping]] = ...) -> None: ... - -class AddToolProviderResponse(_message.Message): - __slots__ = ("current_tool_providers",) - CURRENT_TOOL_PROVIDERS_FIELD_NUMBER: _ClassVar[int] - current_tool_providers: _containers.RepeatedCompositeFieldContainer[ToolsProvider] - def __init__(self, current_tool_providers: _Optional[_Iterable[_Union[ToolsProvider, _Mapping]]] = ...) -> None: ... - -class RemoveToolProviderRequest(_message.Message): - __slots__ = ("provider_uuid",) - PROVIDER_UUID_FIELD_NUMBER: _ClassVar[int] - provider_uuid: str - def __init__(self, provider_uuid: _Optional[str] = ...) -> None: ... - -class RemoveToolProviderResponse(_message.Message): - __slots__ = ("current_tool_providers",) - CURRENT_TOOL_PROVIDERS_FIELD_NUMBER: _ClassVar[int] - current_tool_providers: _containers.RepeatedCompositeFieldContainer[ToolsProvider] - def __init__(self, current_tool_providers: _Optional[_Iterable[_Union[ToolsProvider, _Mapping]]] = ...) -> None: ... - -class TaskContextUpdate(_message.Message): - __slots__ = ("latest_convo", "associated_node_id") - LATEST_CONVO_FIELD_NUMBER: _ClassVar[int] - ASSOCIATED_NODE_ID_FIELD_NUMBER: _ClassVar[int] - latest_convo: _conversation_pb2.Conversation - associated_node_id: int - def __init__(self, latest_convo: _Optional[_Union[_conversation_pb2.Conversation, _Mapping]] = ..., associated_node_id: _Optional[int] = ...) -> None: ... - -class NewTask(_message.Message): - __slots__ = ("user_message", "attached_files", "attached_feed_entries") - USER_MESSAGE_FIELD_NUMBER: _ClassVar[int] - ATTACHED_FILES_FIELD_NUMBER: _ClassVar[int] - ATTACHED_FEED_ENTRIES_FIELD_NUMBER: _ClassVar[int] - user_message: _task_user_response_pb2_1_1.UserMessage - attached_files: _containers.RepeatedCompositeFieldContainer[_file_pb2.AttachedFileIntent] - attached_feed_entries: _background_feed_pb2.FeedEntryTaskContext - def __init__(self, user_message: _Optional[_Union[_task_user_response_pb2_1_1.UserMessage, _Mapping]] = ..., attached_files: _Optional[_Iterable[_Union[_file_pb2.AttachedFileIntent, _Mapping]]] = ..., attached_feed_entries: _Optional[_Union[_background_feed_pb2.FeedEntryTaskContext, _Mapping]] = ...) -> None: ... - -class PrevTask(_message.Message): - __slots__ = ("task", "latest_context") - TASK_FIELD_NUMBER: _ClassVar[int] - LATEST_CONTEXT_FIELD_NUMBER: _ClassVar[int] - task: _task_pb2_1.Task - latest_context: TaskContextUpdate - def __init__(self, task: _Optional[_Union[_task_pb2_1.Task, _Mapping]] = ..., latest_context: _Optional[_Union[TaskContextUpdate, _Mapping]] = ...) -> None: ... - -class StartTaskRequest(_message.Message): - __slots__ = ("new_task", "prev_task", "options", "tool_providers") - NEW_TASK_FIELD_NUMBER: _ClassVar[int] - PREV_TASK_FIELD_NUMBER: _ClassVar[int] - OPTIONS_FIELD_NUMBER: _ClassVar[int] - TOOL_PROVIDERS_FIELD_NUMBER: _ClassVar[int] - new_task: NewTask - prev_task: PrevTask - options: _task_options_pb2_1.TaskOptions - tool_providers: _containers.RepeatedCompositeFieldContainer[ToolsProvider] - def __init__(self, new_task: _Optional[_Union[NewTask, _Mapping]] = ..., prev_task: _Optional[_Union[PrevTask, _Mapping]] = ..., options: _Optional[_Union[_task_options_pb2_1.TaskOptions, _Mapping]] = ..., tool_providers: _Optional[_Iterable[_Union[ToolsProvider, _Mapping]]] = ...) -> None: ... - -class TaskRuntimeError(_message.Message): - __slots__ = ("error", "details", "associated_provider_uuid") - ERROR_FIELD_NUMBER: _ClassVar[int] - DETAILS_FIELD_NUMBER: _ClassVar[int] - ASSOCIATED_PROVIDER_UUID_FIELD_NUMBER: _ClassVar[int] - error: str - details: str - associated_provider_uuid: str - def __init__(self, error: _Optional[str] = ..., details: _Optional[str] = ..., associated_provider_uuid: _Optional[str] = ...) -> None: ... - -class TaskRuntimeUpdate(_message.Message): - __slots__ = ("task_update", "runtime_error", "context_update") - TASK_UPDATE_FIELD_NUMBER: _ClassVar[int] - RUNTIME_ERROR_FIELD_NUMBER: _ClassVar[int] - CONTEXT_UPDATE_FIELD_NUMBER: _ClassVar[int] - task_update: _task_pb2_1.TaskStreamUpdate - runtime_error: TaskRuntimeError - context_update: TaskContextUpdate - def __init__(self, task_update: _Optional[_Union[_task_pb2_1.TaskStreamUpdate, _Mapping]] = ..., runtime_error: _Optional[_Union[TaskRuntimeError, _Mapping]] = ..., context_update: _Optional[_Union[TaskContextUpdate, _Mapping]] = ...) -> None: ... diff --git a/truffle/app/task_runtime_pb2_grpc.py b/truffle/app/task_runtime_pb2_grpc.py deleted file mode 100644 index 0fe0e2b..0000000 --- a/truffle/app/task_runtime_pb2_grpc.py +++ /dev/null @@ -1,274 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - -from truffle.app import task_runtime_pb2 as truffle_dot_app_dot_task__runtime__pb2 -from truffle.os import task_actions_pb2 as truffle_dot_os_dot_task__actions__pb2 -from truffle.os import task_user_response_pb2 as truffle_dot_os_dot_task__user__response__pb2 - -GRPC_GENERATED_VERSION = '1.72.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/task_runtime_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) - - -class TaskRuntimeServiceStub(object): - """task == foreground app, bear with me - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.OpenTask = channel.unary_stream( - '/truffle.app.TaskRuntimeService/OpenTask', - request_serializer=truffle_dot_app_dot_task__runtime__pb2.StartTaskRequest.SerializeToString, - response_deserializer=truffle_dot_app_dot_task__runtime__pb2.TaskRuntimeUpdate.FromString, - _registered_method=True) - self.HandleInterrupt = channel.unary_unary( - '/truffle.app.TaskRuntimeService/HandleInterrupt', - request_serializer=truffle_dot_os_dot_task__actions__pb2.InterruptTaskRequest.SerializeToString, - response_deserializer=truffle_dot_os_dot_task__actions__pb2.TaskActionResponse.FromString, - _registered_method=True) - self.HandleUserResponse = channel.unary_unary( - '/truffle.app.TaskRuntimeService/HandleUserResponse', - request_serializer=truffle_dot_os_dot_task__user__response__pb2.RespondToTaskRequest.SerializeToString, - response_deserializer=truffle_dot_os_dot_task__actions__pb2.TaskActionResponse.FromString, - _registered_method=True) - self.AddToolProvider = channel.unary_unary( - '/truffle.app.TaskRuntimeService/AddToolProvider', - request_serializer=truffle_dot_app_dot_task__runtime__pb2.AddToolProviderRequest.SerializeToString, - response_deserializer=truffle_dot_app_dot_task__runtime__pb2.AddToolProviderResponse.FromString, - _registered_method=True) - self.RemoveToolProvider = channel.unary_unary( - '/truffle.app.TaskRuntimeService/RemoveToolProvider', - request_serializer=truffle_dot_app_dot_task__runtime__pb2.RemoveToolProviderRequest.SerializeToString, - response_deserializer=truffle_dot_app_dot_task__runtime__pb2.RemoveToolProviderResponse.FromString, - _registered_method=True) - - -class TaskRuntimeServiceServicer(object): - """task == foreground app, bear with me - """ - - def OpenTask(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def HandleInterrupt(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def HandleUserResponse(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def AddToolProvider(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def RemoveToolProvider(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_TaskRuntimeServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'OpenTask': grpc.unary_stream_rpc_method_handler( - servicer.OpenTask, - request_deserializer=truffle_dot_app_dot_task__runtime__pb2.StartTaskRequest.FromString, - response_serializer=truffle_dot_app_dot_task__runtime__pb2.TaskRuntimeUpdate.SerializeToString, - ), - 'HandleInterrupt': grpc.unary_unary_rpc_method_handler( - servicer.HandleInterrupt, - request_deserializer=truffle_dot_os_dot_task__actions__pb2.InterruptTaskRequest.FromString, - response_serializer=truffle_dot_os_dot_task__actions__pb2.TaskActionResponse.SerializeToString, - ), - 'HandleUserResponse': grpc.unary_unary_rpc_method_handler( - servicer.HandleUserResponse, - request_deserializer=truffle_dot_os_dot_task__user__response__pb2.RespondToTaskRequest.FromString, - response_serializer=truffle_dot_os_dot_task__actions__pb2.TaskActionResponse.SerializeToString, - ), - 'AddToolProvider': grpc.unary_unary_rpc_method_handler( - servicer.AddToolProvider, - request_deserializer=truffle_dot_app_dot_task__runtime__pb2.AddToolProviderRequest.FromString, - response_serializer=truffle_dot_app_dot_task__runtime__pb2.AddToolProviderResponse.SerializeToString, - ), - 'RemoveToolProvider': grpc.unary_unary_rpc_method_handler( - servicer.RemoveToolProvider, - request_deserializer=truffle_dot_app_dot_task__runtime__pb2.RemoveToolProviderRequest.FromString, - response_serializer=truffle_dot_app_dot_task__runtime__pb2.RemoveToolProviderResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'truffle.app.TaskRuntimeService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('truffle.app.TaskRuntimeService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class TaskRuntimeService(object): - """task == foreground app, bear with me - """ - - @staticmethod - def OpenTask(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/truffle.app.TaskRuntimeService/OpenTask', - truffle_dot_app_dot_task__runtime__pb2.StartTaskRequest.SerializeToString, - truffle_dot_app_dot_task__runtime__pb2.TaskRuntimeUpdate.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def HandleInterrupt(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.app.TaskRuntimeService/HandleInterrupt', - truffle_dot_os_dot_task__actions__pb2.InterruptTaskRequest.SerializeToString, - truffle_dot_os_dot_task__actions__pb2.TaskActionResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def HandleUserResponse(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.app.TaskRuntimeService/HandleUserResponse', - truffle_dot_os_dot_task__user__response__pb2.RespondToTaskRequest.SerializeToString, - truffle_dot_os_dot_task__actions__pb2.TaskActionResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def AddToolProvider(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.app.TaskRuntimeService/AddToolProvider', - truffle_dot_app_dot_task__runtime__pb2.AddToolProviderRequest.SerializeToString, - truffle_dot_app_dot_task__runtime__pb2.AddToolProviderResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def RemoveToolProvider(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.app.TaskRuntimeService/RemoveToolProvider', - truffle_dot_app_dot_task__runtime__pb2.RemoveToolProviderRequest.SerializeToString, - truffle_dot_app_dot_task__runtime__pb2.RemoveToolProviderResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/truffle/common/content_pb2.py b/truffle/common/content_pb2.py index 2eeffb8..075e214 100644 --- a/truffle/common/content_pb2.py +++ b/truffle/common/content_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/common/content.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/common/content.proto' ) diff --git a/truffle/common/content_pb2_grpc.py b/truffle/common/content_pb2_grpc.py index 308017e..9f80a64 100644 --- a/truffle/common/content_pb2_grpc.py +++ b/truffle/common/content_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/common/content_pb2_grpc.py depends on' + + ' but the generated code in truffle/common/content_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/common/file_pb2.py b/truffle/common/file_pb2.py index 604fc59..6d6ee4f 100644 --- a/truffle/common/file_pb2.py +++ b/truffle/common/file_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/common/file.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/common/file.proto' ) diff --git a/truffle/common/file_pb2_grpc.py b/truffle/common/file_pb2_grpc.py index d037a40..bfc7eb8 100644 --- a/truffle/common/file_pb2_grpc.py +++ b/truffle/common/file_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/common/file_pb2_grpc.py depends on' + + ' but the generated code in truffle/common/file_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/common/icon_pb2.py b/truffle/common/icon_pb2.py index 25b47f3..2318742 100644 --- a/truffle/common/icon_pb2.py +++ b/truffle/common/icon_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/common/icon.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/common/icon.proto' ) diff --git a/truffle/common/icon_pb2_grpc.py b/truffle/common/icon_pb2_grpc.py index 7c5c6b0..2d156fd 100644 --- a/truffle/common/icon_pb2_grpc.py +++ b/truffle/common/icon_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/common/icon_pb2_grpc.py depends on' + + ' but the generated code in truffle/common/icon_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/common/led_states_pb2.py b/truffle/common/led_states_pb2.py deleted file mode 100644 index 1ebad80..0000000 --- a/truffle/common/led_states_pb2.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/common/led_states.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'truffle/common/led_states.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ftruffle/common/led_states.proto\x12\x0etruffle.common\"C\n\tLedStatus\x12\'\n\x05state\x18\x01 \x01(\x0e\x32\x18.truffle.common.LedState\x12\r\n\x05\x63olor\x18\x02 \x01(\r*\xfd\x02\n\x08LedState\x12\x15\n\x11LED_STATE_INVALID\x10\x00\x12\x16\n\x12LED_STATE_DISABLED\x10\x00\x12\x11\n\rLED_STATE_OFF\x10\x01\x12\x15\n\x11LED_STATE_STARTUP\x10\x02\x12\x1e\n\x1aLED_STATE_READY_TO_CONNECT\x10\x03\x12\x18\n\x14LED_STATE_CONNECTING\x10\x04\x12\x17\n\x13LED_STATE_CONNECTED\x10\x05\x12\x13\n\x0fLED_STATE_ERROR\x10\x06\x12\x17\n\x13LED_STATE_REASONING\x10\x07\x12\x12\n\x0eLED_STATE_IDLE\x10\x08\x12\x14\n\x10LED_STATE_TYPING\x10\t\x12\x1d\n\x19LED_STATE_RESPOND_TO_USER\x10\n\x12\x15\n\x11LED_STATE_ONBOARD\x10\x0b\x12\x19\n\x15LED_STATE_ONCE_ACTION\x10\x0c\x12\x18\n\x14LED_STATE_ONCE_FLAIR\x10\r\x1a\x02\x10\x01\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.common.led_states_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_LEDSTATE']._loaded_options = None - _globals['_LEDSTATE']._serialized_options = b'\020\001' - _globals['_LEDSTATE']._serialized_start=121 - _globals['_LEDSTATE']._serialized_end=502 - _globals['_LEDSTATUS']._serialized_start=51 - _globals['_LEDSTATUS']._serialized_end=118 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/common/led_states_pb2.pyi b/truffle/common/led_states_pb2.pyi deleted file mode 100644 index 3e2f122..0000000 --- a/truffle/common/led_states_pb2.pyi +++ /dev/null @@ -1,47 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class LedState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - LED_STATE_INVALID: _ClassVar[LedState] - LED_STATE_DISABLED: _ClassVar[LedState] - LED_STATE_OFF: _ClassVar[LedState] - LED_STATE_STARTUP: _ClassVar[LedState] - LED_STATE_READY_TO_CONNECT: _ClassVar[LedState] - LED_STATE_CONNECTING: _ClassVar[LedState] - LED_STATE_CONNECTED: _ClassVar[LedState] - LED_STATE_ERROR: _ClassVar[LedState] - LED_STATE_REASONING: _ClassVar[LedState] - LED_STATE_IDLE: _ClassVar[LedState] - LED_STATE_TYPING: _ClassVar[LedState] - LED_STATE_RESPOND_TO_USER: _ClassVar[LedState] - LED_STATE_ONBOARD: _ClassVar[LedState] - LED_STATE_ONCE_ACTION: _ClassVar[LedState] - LED_STATE_ONCE_FLAIR: _ClassVar[LedState] -LED_STATE_INVALID: LedState -LED_STATE_DISABLED: LedState -LED_STATE_OFF: LedState -LED_STATE_STARTUP: LedState -LED_STATE_READY_TO_CONNECT: LedState -LED_STATE_CONNECTING: LedState -LED_STATE_CONNECTED: LedState -LED_STATE_ERROR: LedState -LED_STATE_REASONING: LedState -LED_STATE_IDLE: LedState -LED_STATE_TYPING: LedState -LED_STATE_RESPOND_TO_USER: LedState -LED_STATE_ONBOARD: LedState -LED_STATE_ONCE_ACTION: LedState -LED_STATE_ONCE_FLAIR: LedState - -class LedStatus(_message.Message): - __slots__ = ("state", "color") - STATE_FIELD_NUMBER: _ClassVar[int] - COLOR_FIELD_NUMBER: _ClassVar[int] - state: LedState - color: int - def __init__(self, state: _Optional[_Union[LedState, str]] = ..., color: _Optional[int] = ...) -> None: ... diff --git a/truffle/common/led_states_pb2_grpc.py b/truffle/common/led_states_pb2_grpc.py deleted file mode 100644 index 276c1ac..0000000 --- a/truffle/common/led_states_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.72.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/common/led_states_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/common/tool_provider_pb2.py b/truffle/common/tool_provider_pb2.py index 024bb84..a96461b 100644 --- a/truffle/common/tool_provider_pb2.py +++ b/truffle/common/tool_provider_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/common/tool_provider.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/common/tool_provider.proto' ) diff --git a/truffle/common/tool_provider_pb2_grpc.py b/truffle/common/tool_provider_pb2_grpc.py index 1d2335c..a578d03 100644 --- a/truffle/common/tool_provider_pb2_grpc.py +++ b/truffle/common/tool_provider_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/common/tool_provider_pb2_grpc.py depends on' + + ' but the generated code in truffle/common/tool_provider_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/convo/conversation_pb2.py b/truffle/infer/convo/conversation_pb2.py index 5ba3cc2..bb3ec89 100644 --- a/truffle/infer/convo/conversation_pb2.py +++ b/truffle/infer/convo/conversation_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/convo/conversation.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/convo/conversation.proto' ) diff --git a/truffle/infer/convo/conversation_pb2_grpc.py b/truffle/infer/convo/conversation_pb2_grpc.py index e3a3b17..4195d2c 100644 --- a/truffle/infer/convo/conversation_pb2_grpc.py +++ b/truffle/infer/convo/conversation_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/convo/conversation_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/convo/conversation_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/convo/msg_pb2.py b/truffle/infer/convo/msg_pb2.py index 2ec02cd..9726e77 100644 --- a/truffle/infer/convo/msg_pb2.py +++ b/truffle/infer/convo/msg_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/convo/msg.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/convo/msg.proto' ) diff --git a/truffle/infer/convo/msg_pb2_grpc.py b/truffle/infer/convo/msg_pb2_grpc.py index db9c591..d719b1c 100644 --- a/truffle/infer/convo/msg_pb2_grpc.py +++ b/truffle/infer/convo/msg_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/convo/msg_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/convo/msg_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/embedding_pb2.py b/truffle/infer/embedding_pb2.py index 42cda6d..20c805c 100644 --- a/truffle/infer/embedding_pb2.py +++ b/truffle/infer/embedding_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/embedding.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/embedding.proto' ) diff --git a/truffle/infer/embedding_pb2_grpc.py b/truffle/infer/embedding_pb2_grpc.py index b32d5d9..36a3d32 100644 --- a/truffle/infer/embedding_pb2_grpc.py +++ b/truffle/infer/embedding_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/embedding_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/embedding_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/finishreason_pb2.py b/truffle/infer/finishreason_pb2.py index ea0eba0..e6dfe75 100644 --- a/truffle/infer/finishreason_pb2.py +++ b/truffle/infer/finishreason_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/finishreason.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/finishreason.proto' ) diff --git a/truffle/infer/finishreason_pb2_grpc.py b/truffle/infer/finishreason_pb2_grpc.py index 853a3ef..6733980 100644 --- a/truffle/infer/finishreason_pb2_grpc.py +++ b/truffle/infer/finishreason_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/finishreason_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/finishreason_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/gencfg_pb2.py b/truffle/infer/gencfg_pb2.py index 1218626..83c3156 100644 --- a/truffle/infer/gencfg_pb2.py +++ b/truffle/infer/gencfg_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/gencfg.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/gencfg.proto' ) diff --git a/truffle/infer/gencfg_pb2_grpc.py b/truffle/infer/gencfg_pb2_grpc.py index 15da2a6..f4b8609 100644 --- a/truffle/infer/gencfg_pb2_grpc.py +++ b/truffle/infer/gencfg_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/gencfg_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/gencfg_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/infer_pb2.py b/truffle/infer/infer_pb2.py index dec4d15..bcef3df 100644 --- a/truffle/infer/infer_pb2.py +++ b/truffle/infer/infer_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/infer.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/infer.proto' ) diff --git a/truffle/infer/infer_pb2_grpc.py b/truffle/infer/infer_pb2_grpc.py index 6c6b6a4..290012e 100644 --- a/truffle/infer/infer_pb2_grpc.py +++ b/truffle/infer/infer_pb2_grpc.py @@ -11,7 +11,7 @@ from truffle.infer import iresponse_pb2 as truffle_dot_infer_dot_iresponse__pb2 from truffle.infer import model_pb2 as truffle_dot_infer_dot_model__pb2 -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -24,7 +24,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/infer_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/infer_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' @@ -33,11 +33,7 @@ class InferenceServiceStub(object): """ - Defines the main gRPC service for all AI inference operations. - This service is the primary entry point for clients to interact with generative - models, create embeddings, manage model configurations, and use other related - utility functions. It consolidates all necessary data structures from other - .proto files into a single, cohesive API. + legacy inference service apis (v1) """ def __init__(self, channel): @@ -120,114 +116,89 @@ def __init__(self, channel): class InferenceServiceServicer(object): """ - Defines the main gRPC service for all AI inference operations. - This service is the primary entry point for clients to interact with generative - models, create embeddings, manage model configurations, and use other related - utility functions. It consolidates all necessary data structures from other - .proto files into a single, cohesive API. + legacy inference service apis (v1) """ def Generate(self, request, context): - """Starts a generation task that streams responses back to the client. - This is suitable for interactive applications where responses are displayed - as they are generated. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GenerateSync(self, request, context): - """Performs a generation task and returns the full response in a single message. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GenerateBatch(self, request, context): - """Processes a batch of inference requests in parallel. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def Embed(self, request, context): - """Generates embeddings for a given set of inputs. Embeddings are numerical - representations of text that can be used for semantic search, clustering, - and other machine learning tasks. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def EmbedQueries(self, request, context): - """A specialized version of Embed for generating embeddings for - search queries. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetModelList(self, request, context): - """Retrieves a list of all available models. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetModel(self, request, context): - """Fetches detailed information about a specific model. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def SetModels(self, request, context): - """Configures model parameters such as context length, batch size etc. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetModelState(self, request, context): - """Gets the current state of a model, such as loading, loaded, unloaded etc. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def OnModelStateChange(self, request, context): - """Subscribes to updates on the state of a model to avoid polling. - pass an empty ID to get updates for all models. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetEmbeddingModelList(self, request, context): - """Retrieves a list of all available embedding models. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetEmbeddingModelInfo(self, request, context): - """Gets detailed information about a specific embedding model (input length, dimension size etc). - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def BuildConvo(self, request, context): - """Builds a context from a conversation. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def ValidateGenerationConfig(self, request, context): - """Validates a generation configuration to ensure that it is compatible with - the models. - """ + """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') @@ -315,11 +286,7 @@ def add_InferenceServiceServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. class InferenceService(object): """ - Defines the main gRPC service for all AI inference operations. - This service is the primary entry point for clients to interact with generative - models, create embeddings, manage model configurations, and use other related - utility functions. It consolidates all necessary data structures from other - .proto files into a single, cohesive API. + legacy inference service apis (v1) """ @staticmethod diff --git a/truffle/infer/irequest_pb2.py b/truffle/infer/irequest_pb2.py index df2bafa..3d073a7 100644 --- a/truffle/infer/irequest_pb2.py +++ b/truffle/infer/irequest_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/irequest.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/irequest.proto' ) diff --git a/truffle/infer/irequest_pb2_grpc.py b/truffle/infer/irequest_pb2_grpc.py index de17794..eeb5ec9 100644 --- a/truffle/infer/irequest_pb2_grpc.py +++ b/truffle/infer/irequest_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/irequest_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/irequest_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/iresponse_pb2.py b/truffle/infer/iresponse_pb2.py index 5c6ad2a..47071c3 100644 --- a/truffle/infer/iresponse_pb2.py +++ b/truffle/infer/iresponse_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/iresponse.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/iresponse.proto' ) diff --git a/truffle/infer/iresponse_pb2_grpc.py b/truffle/infer/iresponse_pb2_grpc.py index 1be00b7..734b976 100644 --- a/truffle/infer/iresponse_pb2_grpc.py +++ b/truffle/infer/iresponse_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/iresponse_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/iresponse_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/model_pb2.py b/truffle/infer/model_pb2.py index 7253b61..e6e3dc5 100644 --- a/truffle/infer/model_pb2.py +++ b/truffle/infer/model_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/model.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/model.proto' ) diff --git a/truffle/infer/model_pb2_grpc.py b/truffle/infer/model_pb2_grpc.py index 05e4dd8..a6fb854 100644 --- a/truffle/infer/model_pb2_grpc.py +++ b/truffle/infer/model_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/model_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/model_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/tokenize_pb2.py b/truffle/infer/tokenize_pb2.py index 9d13153..4957b72 100644 --- a/truffle/infer/tokenize_pb2.py +++ b/truffle/infer/tokenize_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/tokenize.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/tokenize.proto' ) diff --git a/truffle/infer/tokenize_pb2_grpc.py b/truffle/infer/tokenize_pb2_grpc.py index 25b9a25..c9231c3 100644 --- a/truffle/infer/tokenize_pb2_grpc.py +++ b/truffle/infer/tokenize_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/tokenize_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/tokenize_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/infer/usage_pb2.py b/truffle/infer/usage_pb2.py index 60361b0..2d01766 100644 --- a/truffle/infer/usage_pb2.py +++ b/truffle/infer/usage_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/infer/usage.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/infer/usage.proto' ) diff --git a/truffle/infer/usage_pb2_grpc.py b/truffle/infer/usage_pb2_grpc.py index 0e4108f..b686b44 100644 --- a/truffle/infer/usage_pb2_grpc.py +++ b/truffle/infer/usage_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/infer/usage_pb2_grpc.py depends on' + + ' but the generated code in truffle/infer/usage_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/app_queries_pb2.py b/truffle/os/app_queries_pb2.py index 1b0f73c..4ea7a94 100644 --- a/truffle/os/app_queries_pb2.py +++ b/truffle/os/app_queries_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/app_queries.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/app_queries.proto' ) @@ -22,31 +22,22 @@ _sym_db = _symbol_database.Default() -from truffle.app import foreground_pb2 as truffle_dot_app_dot_foreground__pb2 -from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 +from truffle.app import app_pb2 as truffle_dot_app_dot_app__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/os/app_queries.proto\x12\ntruffle.os\x1a\x1ctruffle/app/foreground.proto\x1a\x1ctruffle/app/background.proto\")\n\x18GetForegroundAppsRequest\x12\r\n\x05uuids\x18\x01 \x03(\t\"E\n\x19GetForegroundAppsResponse\x12(\n\x04\x61pps\x18\x01 \x03(\x0b\x32\x1a.truffle.app.ForegroundApp\")\n\x18GetBackgroundAppsRequest\x12\r\n\x05uuids\x18\x01 \x03(\t\"E\n\x19GetBackgroundAppsResponse\x12(\n\x04\x61pps\x18\x01 \x03(\x0b\x32\x1a.truffle.app.BackgroundApp\"\x13\n\x11GetAllAppsRequest\"~\n\x12GetAllAppsResponse\x12\x33\n\x0f\x66oreground_apps\x18\x01 \x03(\x0b\x32\x1a.truffle.app.ForegroundApp\x12\x33\n\x0f\x62\x61\x63kground_apps\x18\x02 \x03(\x0b\x32\x1a.truffle.app.BackgroundApp\"$\n\x10\x44\x65leteAppRequest\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\"\x13\n\x11\x44\x65leteAppResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/os/app_queries.proto\x12\ntruffle.os\x1a\x15truffle/app/app.proto\"\x13\n\x11GetAllAppsRequest\"4\n\x12GetAllAppsResponse\x12\x1e\n\x04\x61pps\x18\x01 \x03(\x0b\x32\x10.truffle.app.App\"$\n\x10\x44\x65leteAppRequest\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\"\x13\n\x11\x44\x65leteAppResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.os.app_queries_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_GETFOREGROUNDAPPSREQUEST']._serialized_start=104 - _globals['_GETFOREGROUNDAPPSREQUEST']._serialized_end=145 - _globals['_GETFOREGROUNDAPPSRESPONSE']._serialized_start=147 - _globals['_GETFOREGROUNDAPPSRESPONSE']._serialized_end=216 - _globals['_GETBACKGROUNDAPPSREQUEST']._serialized_start=218 - _globals['_GETBACKGROUNDAPPSREQUEST']._serialized_end=259 - _globals['_GETBACKGROUNDAPPSRESPONSE']._serialized_start=261 - _globals['_GETBACKGROUNDAPPSRESPONSE']._serialized_end=330 - _globals['_GETALLAPPSREQUEST']._serialized_start=332 - _globals['_GETALLAPPSREQUEST']._serialized_end=351 - _globals['_GETALLAPPSRESPONSE']._serialized_start=353 - _globals['_GETALLAPPSRESPONSE']._serialized_end=479 - _globals['_DELETEAPPREQUEST']._serialized_start=481 - _globals['_DELETEAPPREQUEST']._serialized_end=517 - _globals['_DELETEAPPRESPONSE']._serialized_start=519 - _globals['_DELETEAPPRESPONSE']._serialized_end=538 + _globals['_GETALLAPPSREQUEST']._serialized_start=67 + _globals['_GETALLAPPSREQUEST']._serialized_end=86 + _globals['_GETALLAPPSRESPONSE']._serialized_start=88 + _globals['_GETALLAPPSRESPONSE']._serialized_end=140 + _globals['_DELETEAPPREQUEST']._serialized_start=142 + _globals['_DELETEAPPREQUEST']._serialized_end=178 + _globals['_DELETEAPPRESPONSE']._serialized_start=180 + _globals['_DELETEAPPRESPONSE']._serialized_end=199 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/app_queries_pb2.pyi b/truffle/os/app_queries_pb2.pyi index d18e1f9..e1f2157 100644 --- a/truffle/os/app_queries_pb2.pyi +++ b/truffle/os/app_queries_pb2.pyi @@ -1,5 +1,4 @@ -from truffle.app import foreground_pb2 as _foreground_pb2 -from truffle.app import background_pb2 as _background_pb2 +from truffle.app import app_pb2 as _app_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message @@ -8,41 +7,15 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor -class GetForegroundAppsRequest(_message.Message): - __slots__ = ("uuids",) - UUIDS_FIELD_NUMBER: _ClassVar[int] - uuids: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, uuids: _Optional[_Iterable[str]] = ...) -> None: ... - -class GetForegroundAppsResponse(_message.Message): - __slots__ = ("apps",) - APPS_FIELD_NUMBER: _ClassVar[int] - apps: _containers.RepeatedCompositeFieldContainer[_foreground_pb2.ForegroundApp] - def __init__(self, apps: _Optional[_Iterable[_Union[_foreground_pb2.ForegroundApp, _Mapping]]] = ...) -> None: ... - -class GetBackgroundAppsRequest(_message.Message): - __slots__ = ("uuids",) - UUIDS_FIELD_NUMBER: _ClassVar[int] - uuids: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, uuids: _Optional[_Iterable[str]] = ...) -> None: ... - -class GetBackgroundAppsResponse(_message.Message): - __slots__ = ("apps",) - APPS_FIELD_NUMBER: _ClassVar[int] - apps: _containers.RepeatedCompositeFieldContainer[_background_pb2.BackgroundApp] - def __init__(self, apps: _Optional[_Iterable[_Union[_background_pb2.BackgroundApp, _Mapping]]] = ...) -> None: ... - class GetAllAppsRequest(_message.Message): __slots__ = () def __init__(self) -> None: ... class GetAllAppsResponse(_message.Message): - __slots__ = ("foreground_apps", "background_apps") - FOREGROUND_APPS_FIELD_NUMBER: _ClassVar[int] - BACKGROUND_APPS_FIELD_NUMBER: _ClassVar[int] - foreground_apps: _containers.RepeatedCompositeFieldContainer[_foreground_pb2.ForegroundApp] - background_apps: _containers.RepeatedCompositeFieldContainer[_background_pb2.BackgroundApp] - def __init__(self, foreground_apps: _Optional[_Iterable[_Union[_foreground_pb2.ForegroundApp, _Mapping]]] = ..., background_apps: _Optional[_Iterable[_Union[_background_pb2.BackgroundApp, _Mapping]]] = ...) -> None: ... + __slots__ = ("apps",) + APPS_FIELD_NUMBER: _ClassVar[int] + apps: _containers.RepeatedCompositeFieldContainer[_app_pb2.App] + def __init__(self, apps: _Optional[_Iterable[_Union[_app_pb2.App, _Mapping]]] = ...) -> None: ... class DeleteAppRequest(_message.Message): __slots__ = ("app_uuid",) diff --git a/truffle/os/app_queries_pb2_grpc.py b/truffle/os/app_queries_pb2_grpc.py index 18983d9..e6864a7 100644 --- a/truffle/os/app_queries_pb2_grpc.py +++ b/truffle/os/app_queries_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/app_queries_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/app_queries_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/background_feed_pb2.py b/truffle/os/background_feed_pb2.py new file mode 100644 index 0000000..d609505 --- /dev/null +++ b/truffle/os/background_feed_pb2.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: truffle/os/background_feed.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'truffle/os/background_feed.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 +from truffle.common import content_pb2 as truffle_dot_common_dot_content__pb2 +from truffle.os import proactivity_pb2 as truffle_dot_os_dot_proactivity__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n truffle/os/background_feed.proto\x12\ntruffle.os\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1ctruffle/common/content.proto\x1a\x1ctruffle/os/proactivity.proto\"\xdc\x01\n\x08\x46\x65\x65\x64\x43\x61rd\x12\r\n\x05title\x18\x01 \x01(\t\x12\x0c\n\x04\x62ody\x18\x02 \x01(\t\x12\x32\n\rmedia_sources\x18\x03 \x03(\x0b\x32\x1b.truffle.common.MediaSource\x12\x12\n\nsource_uri\x18\x04 \x01(\t\x12.\n\ncreated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\x08metadata\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x88\x01\x01\x42\x0b\n\t_metadata\"8\n\x0e\x42\x61\x63kgroundFeed\x12&\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x15.truffle.os.FeedEntry\"\xce\x01\n\x15\x46\x65\x65\x64\x45ntryNotification\x12\x11\n\tentry_ids\x18\x01 \x03(\x04\x12>\n\toperation\x18\x02 \x01(\x0e\x32+.truffle.os.FeedEntryNotification.Operation\"b\n\tOperation\x12\x15\n\x11OPERATION_INVALID\x10\x00\x12\x11\n\rOPERATION_ADD\x10\x01\x12\x14\n\x10OPERATION_DELETE\x10\x02\x12\x15\n\x11OPERATION_REFRESH\x10\x03\"\xb0\x01\n\tFeedEntry\x12\n\n\x02id\x18\x01 \x01(\x04\x12$\n\x04\x63\x61rd\x18\x02 \x01(\x0b\x32\x14.truffle.os.FeedCardH\x00\x12\x37\n\x10proactive_action\x18\x03 \x01(\x0b\x32\x1b.truffle.os.ProactiveActionH\x00\x12-\n\ttimestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\t\n\x07\x63ontent\">\n\x14\x46\x65\x65\x64\x45ntryTaskContext\x12&\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x15.truffle.os.FeedEntryb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.os.background_feed_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_FEEDCARD']._serialized_start=172 + _globals['_FEEDCARD']._serialized_end=392 + _globals['_BACKGROUNDFEED']._serialized_start=394 + _globals['_BACKGROUNDFEED']._serialized_end=450 + _globals['_FEEDENTRYNOTIFICATION']._serialized_start=453 + _globals['_FEEDENTRYNOTIFICATION']._serialized_end=659 + _globals['_FEEDENTRYNOTIFICATION_OPERATION']._serialized_start=561 + _globals['_FEEDENTRYNOTIFICATION_OPERATION']._serialized_end=659 + _globals['_FEEDENTRY']._serialized_start=662 + _globals['_FEEDENTRY']._serialized_end=838 + _globals['_FEEDENTRYTASKCONTEXT']._serialized_start=840 + _globals['_FEEDENTRYTASKCONTEXT']._serialized_end=902 +# @@protoc_insertion_point(module_scope) diff --git a/truffle/app/background_feed_pb2.pyi b/truffle/os/background_feed_pb2.pyi similarity index 50% rename from truffle/app/background_feed_pb2.pyi rename to truffle/os/background_feed_pb2.pyi index f9bbf1d..3513f11 100644 --- a/truffle/app/background_feed_pb2.pyi +++ b/truffle/os/background_feed_pb2.pyi @@ -1,7 +1,11 @@ +import datetime + from google.protobuf import timestamp_pb2 as _timestamp_pb2 -from truffle.common import content_pb2 as _content_pb2 from google.protobuf import struct_pb2 as _struct_pb2 +from truffle.common import content_pb2 as _content_pb2 +from truffle.os import proactivity_pb2 as _proactivity_pb2 from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from collections.abc import Iterable as _Iterable, Mapping as _Mapping @@ -10,20 +14,20 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor class FeedCard(_message.Message): - __slots__ = ("title", "body", "media_sources", "source_uri", "content_timestamp", "metadata") + __slots__ = ("title", "body", "media_sources", "source_uri", "created_at", "metadata") TITLE_FIELD_NUMBER: _ClassVar[int] BODY_FIELD_NUMBER: _ClassVar[int] MEDIA_SOURCES_FIELD_NUMBER: _ClassVar[int] SOURCE_URI_FIELD_NUMBER: _ClassVar[int] - CONTENT_TIMESTAMP_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] METADATA_FIELD_NUMBER: _ClassVar[int] title: str body: str media_sources: _containers.RepeatedCompositeFieldContainer[_content_pb2.MediaSource] source_uri: str - content_timestamp: _timestamp_pb2.Timestamp + created_at: _timestamp_pb2.Timestamp metadata: _struct_pb2.Struct - def __init__(self, title: _Optional[str] = ..., body: _Optional[str] = ..., media_sources: _Optional[_Iterable[_Union[_content_pb2.MediaSource, _Mapping]]] = ..., source_uri: _Optional[str] = ..., content_timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... + def __init__(self, title: _Optional[str] = ..., body: _Optional[str] = ..., media_sources: _Optional[_Iterable[_Union[_content_pb2.MediaSource, _Mapping]]] = ..., source_uri: _Optional[str] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... class BackgroundFeed(_message.Message): __slots__ = ("entries",) @@ -31,19 +35,35 @@ class BackgroundFeed(_message.Message): entries: _containers.RepeatedCompositeFieldContainer[FeedEntry] def __init__(self, entries: _Optional[_Iterable[_Union[FeedEntry, _Mapping]]] = ...) -> None: ... +class FeedEntryNotification(_message.Message): + __slots__ = ("entry_ids", "operation") + class Operation(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + OPERATION_INVALID: _ClassVar[FeedEntryNotification.Operation] + OPERATION_ADD: _ClassVar[FeedEntryNotification.Operation] + OPERATION_DELETE: _ClassVar[FeedEntryNotification.Operation] + OPERATION_REFRESH: _ClassVar[FeedEntryNotification.Operation] + OPERATION_INVALID: FeedEntryNotification.Operation + OPERATION_ADD: FeedEntryNotification.Operation + OPERATION_DELETE: FeedEntryNotification.Operation + OPERATION_REFRESH: FeedEntryNotification.Operation + ENTRY_IDS_FIELD_NUMBER: _ClassVar[int] + OPERATION_FIELD_NUMBER: _ClassVar[int] + entry_ids: _containers.RepeatedScalarFieldContainer[int] + operation: FeedEntryNotification.Operation + def __init__(self, entry_ids: _Optional[_Iterable[int]] = ..., operation: _Optional[_Union[FeedEntryNotification.Operation, str]] = ...) -> None: ... + class FeedEntry(_message.Message): - __slots__ = ("id", "app_uuid", "timestamp", "card", "likes") + __slots__ = ("id", "card", "proactive_action", "timestamp") ID_FIELD_NUMBER: _ClassVar[int] - APP_UUID_FIELD_NUMBER: _ClassVar[int] - TIMESTAMP_FIELD_NUMBER: _ClassVar[int] CARD_FIELD_NUMBER: _ClassVar[int] - LIKES_FIELD_NUMBER: _ClassVar[int] + PROACTIVE_ACTION_FIELD_NUMBER: _ClassVar[int] + TIMESTAMP_FIELD_NUMBER: _ClassVar[int] id: int - app_uuid: str - timestamp: _timestamp_pb2.Timestamp card: FeedCard - likes: int - def __init__(self, id: _Optional[int] = ..., app_uuid: _Optional[str] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., card: _Optional[_Union[FeedCard, _Mapping]] = ..., likes: _Optional[int] = ...) -> None: ... + proactive_action: _proactivity_pb2.ProactiveAction + timestamp: _timestamp_pb2.Timestamp + def __init__(self, id: _Optional[int] = ..., card: _Optional[_Union[FeedCard, _Mapping]] = ..., proactive_action: _Optional[_Union[_proactivity_pb2.ProactiveAction, _Mapping]] = ..., timestamp: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... class FeedEntryTaskContext(_message.Message): __slots__ = ("entries",) diff --git a/truffle/app/background_feed_pb2_grpc.py b/truffle/os/background_feed_pb2_grpc.py similarity index 86% rename from truffle/app/background_feed_pb2_grpc.py rename to truffle/os/background_feed_pb2_grpc.py index eb25b60..cc8f5ef 100644 --- a/truffle/app/background_feed_pb2_grpc.py +++ b/truffle/os/background_feed_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/background_feed_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/background_feed_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/background_feed_queries_pb2.py b/truffle/os/background_feed_queries_pb2.py index a3d91d3..5fa8b9f 100644 --- a/truffle/os/background_feed_queries_pb2.py +++ b/truffle/os/background_feed_queries_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/background_feed_queries.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/background_feed_queries.proto' ) @@ -23,10 +23,10 @@ from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 -from truffle.app import background_feed_pb2 as truffle_dot_app_dot_background__feed__pb2 +from truffle.os import background_feed_pb2 as truffle_dot_os_dot_background__feed__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(truffle/os/background_feed_queries.proto\x12\ntruffle.os\x1a\x1ctruffle/app/background.proto\x1a!truffle/app/background_feed.proto\"Z\n\x18GetBackgroundFeedRequest\x12\x17\n\x0ftarget_entry_id\x18\x01 \x01(\x04\x12\x12\n\nmax_before\x18\x02 \x01(\x05\x12\x11\n\tmax_after\x18\x03 \x01(\x05\"D\n\x19GetBackgroundFeedResponse\x12\'\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x16.truffle.app.FeedEntry\"J\n\x1eLikeBackgroundFeedEntryRequest\x12\x15\n\rfeed_entry_id\x18\x01 \x01(\x04\x12\x11\n\tincrement\x18\x02 \x01(\x05\"9\n\x1fLikeBackgroundFeedEntryResponse\x12\x16\n\x0enew_like_count\x18\x01 \x01(\x05\"\x1d\n\x1bGetLatestFeedEntryIDRequest\"<\n\x1cGetLatestFeedEntryIDResponse\x12\x1c\n\x14latest_feed_entry_id\x18\x01 \x01(\x04\"R\n\x1d\x42\x61\x63kgroundFeedFeedbackRequest\x12\x1f\n\x17\x61ssociated_feed_entries\x18\x01 \x03(\x04\x12\x10\n\x08\x66\x65\x65\x64\x62\x61\x63k\x18\x02 \x01(\t\"7\n\x1e\x42\x61\x63kgroundFeedFeedbackResponse\x12\x15\n\rfeedback_uuid\x18\x01 \x01(\tb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(truffle/os/background_feed_queries.proto\x12\ntruffle.os\x1a\x1ctruffle/app/background.proto\x1a truffle/os/background_feed.proto\"\x8a\x01\n\x18GetBackgroundFeedRequest\x12\x17\n\x0ftarget_entry_id\x18\x01 \x01(\x04\x12\x12\n\nmax_before\x18\x02 \x01(\x05\x12\x11\n\tmax_after\x18\x03 \x01(\x05\x12\x17\n\x0finclude_actions\x18\x04 \x01(\x08\x12\x15\n\rinclude_cards\x18\x05 \x01(\x08\"C\n\x19GetBackgroundFeedResponse\x12&\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x15.truffle.os.FeedEntry\"\x1d\n\x1bGetLatestFeedEntryIDRequest\"<\n\x1cGetLatestFeedEntryIDResponse\x12\x1c\n\x14latest_feed_entry_id\x18\x01 \x01(\x04\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -34,19 +34,11 @@ if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals['_GETBACKGROUNDFEEDREQUEST']._serialized_start=121 - _globals['_GETBACKGROUNDFEEDREQUEST']._serialized_end=211 - _globals['_GETBACKGROUNDFEEDRESPONSE']._serialized_start=213 - _globals['_GETBACKGROUNDFEEDRESPONSE']._serialized_end=281 - _globals['_LIKEBACKGROUNDFEEDENTRYREQUEST']._serialized_start=283 - _globals['_LIKEBACKGROUNDFEEDENTRYREQUEST']._serialized_end=357 - _globals['_LIKEBACKGROUNDFEEDENTRYRESPONSE']._serialized_start=359 - _globals['_LIKEBACKGROUNDFEEDENTRYRESPONSE']._serialized_end=416 - _globals['_GETLATESTFEEDENTRYIDREQUEST']._serialized_start=418 - _globals['_GETLATESTFEEDENTRYIDREQUEST']._serialized_end=447 - _globals['_GETLATESTFEEDENTRYIDRESPONSE']._serialized_start=449 - _globals['_GETLATESTFEEDENTRYIDRESPONSE']._serialized_end=509 - _globals['_BACKGROUNDFEEDFEEDBACKREQUEST']._serialized_start=511 - _globals['_BACKGROUNDFEEDFEEDBACKREQUEST']._serialized_end=593 - _globals['_BACKGROUNDFEEDFEEDBACKRESPONSE']._serialized_start=595 - _globals['_BACKGROUNDFEEDFEEDBACKRESPONSE']._serialized_end=650 + _globals['_GETBACKGROUNDFEEDREQUEST']._serialized_end=259 + _globals['_GETBACKGROUNDFEEDRESPONSE']._serialized_start=261 + _globals['_GETBACKGROUNDFEEDRESPONSE']._serialized_end=328 + _globals['_GETLATESTFEEDENTRYIDREQUEST']._serialized_start=330 + _globals['_GETLATESTFEEDENTRYIDREQUEST']._serialized_end=359 + _globals['_GETLATESTFEEDENTRYIDRESPONSE']._serialized_start=361 + _globals['_GETLATESTFEEDENTRYIDRESPONSE']._serialized_end=421 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/background_feed_queries_pb2.pyi b/truffle/os/background_feed_queries_pb2.pyi index 9ee2d57..7313d33 100644 --- a/truffle/os/background_feed_queries_pb2.pyi +++ b/truffle/os/background_feed_queries_pb2.pyi @@ -1,5 +1,5 @@ from truffle.app import background_pb2 as _background_pb2 -from truffle.app import background_feed_pb2 as _background_feed_pb2 +from truffle.os import background_feed_pb2 as _background_feed_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message @@ -9,14 +9,18 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor class GetBackgroundFeedRequest(_message.Message): - __slots__ = ("target_entry_id", "max_before", "max_after") + __slots__ = ("target_entry_id", "max_before", "max_after", "include_actions", "include_cards") TARGET_ENTRY_ID_FIELD_NUMBER: _ClassVar[int] MAX_BEFORE_FIELD_NUMBER: _ClassVar[int] MAX_AFTER_FIELD_NUMBER: _ClassVar[int] + INCLUDE_ACTIONS_FIELD_NUMBER: _ClassVar[int] + INCLUDE_CARDS_FIELD_NUMBER: _ClassVar[int] target_entry_id: int max_before: int max_after: int - def __init__(self, target_entry_id: _Optional[int] = ..., max_before: _Optional[int] = ..., max_after: _Optional[int] = ...) -> None: ... + include_actions: bool + include_cards: bool + def __init__(self, target_entry_id: _Optional[int] = ..., max_before: _Optional[int] = ..., max_after: _Optional[int] = ..., include_actions: bool = ..., include_cards: bool = ...) -> None: ... class GetBackgroundFeedResponse(_message.Message): __slots__ = ("entries",) @@ -24,20 +28,6 @@ class GetBackgroundFeedResponse(_message.Message): entries: _containers.RepeatedCompositeFieldContainer[_background_feed_pb2.FeedEntry] def __init__(self, entries: _Optional[_Iterable[_Union[_background_feed_pb2.FeedEntry, _Mapping]]] = ...) -> None: ... -class LikeBackgroundFeedEntryRequest(_message.Message): - __slots__ = ("feed_entry_id", "increment") - FEED_ENTRY_ID_FIELD_NUMBER: _ClassVar[int] - INCREMENT_FIELD_NUMBER: _ClassVar[int] - feed_entry_id: int - increment: int - def __init__(self, feed_entry_id: _Optional[int] = ..., increment: _Optional[int] = ...) -> None: ... - -class LikeBackgroundFeedEntryResponse(_message.Message): - __slots__ = ("new_like_count",) - NEW_LIKE_COUNT_FIELD_NUMBER: _ClassVar[int] - new_like_count: int - def __init__(self, new_like_count: _Optional[int] = ...) -> None: ... - class GetLatestFeedEntryIDRequest(_message.Message): __slots__ = () def __init__(self) -> None: ... @@ -47,17 +37,3 @@ class GetLatestFeedEntryIDResponse(_message.Message): LATEST_FEED_ENTRY_ID_FIELD_NUMBER: _ClassVar[int] latest_feed_entry_id: int def __init__(self, latest_feed_entry_id: _Optional[int] = ...) -> None: ... - -class BackgroundFeedFeedbackRequest(_message.Message): - __slots__ = ("associated_feed_entries", "feedback") - ASSOCIATED_FEED_ENTRIES_FIELD_NUMBER: _ClassVar[int] - FEEDBACK_FIELD_NUMBER: _ClassVar[int] - associated_feed_entries: _containers.RepeatedScalarFieldContainer[int] - feedback: str - def __init__(self, associated_feed_entries: _Optional[_Iterable[int]] = ..., feedback: _Optional[str] = ...) -> None: ... - -class BackgroundFeedFeedbackResponse(_message.Message): - __slots__ = ("feedback_uuid",) - FEEDBACK_UUID_FIELD_NUMBER: _ClassVar[int] - feedback_uuid: str - def __init__(self, feedback_uuid: _Optional[str] = ...) -> None: ... diff --git a/truffle/os/background_feed_queries_pb2_grpc.py b/truffle/os/background_feed_queries_pb2_grpc.py index d152381..e6fa230 100644 --- a/truffle/os/background_feed_queries_pb2_grpc.py +++ b/truffle/os/background_feed_queries_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/background_feed_queries_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/background_feed_queries_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/builder_pb2.py b/truffle/os/builder_pb2.py index 866f33f..dde6b87 100644 --- a/truffle/os/builder_pb2.py +++ b/truffle/os/builder_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/builder.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/builder.proto' ) @@ -22,27 +22,27 @@ _sym_db = _symbol_database.Default() -from truffle.app import app_type_pb2 as truffle_dot_app_dot_app__type__pb2 from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 from truffle.app import foreground_pb2 as truffle_dot_app_dot_foreground__pb2 from truffle.app import app_build_pb2 as truffle_dot_app_dot_app__build__pb2 +from truffle.app import app_pb2 as truffle_dot_app_dot_app__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18truffle/os/builder.proto\x12\ntruffle.os\x1a\x1atruffle/app/app_type.proto\x1a\x1ctruffle/app/background.proto\x1a\x1ctruffle/app/foreground.proto\x1a\x1btruffle/app/app_build.proto\"f\n\x18StartBuildSessionRequest\x12\x15\n\x08\x61pp_uuid\x18\x01 \x01(\tH\x00\x88\x01\x01\x12&\n\x08\x61pp_type\x18\x02 \x01(\x0e\x32\x14.truffle.app.AppTypeB\x0b\n\t_app_uuid\"B\n\x19StartBuildSessionResponse\x12\x13\n\x0b\x61\x63\x63\x65ss_path\x18\x01 \x01(\t\x12\x10\n\x08\x61pp_uuid\x18\x02 \x01(\t\"\xef\x01\n\x19\x46inishBuildSessionRequest\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\x12\x0f\n\x07\x64iscard\x18\x02 \x01(\x08\x12\x39\n\nforeground\x18\x03 \x01(\x0b\x32#.truffle.app.ForegroundAppBuildInfoH\x00\x12\x39\n\nbackground\x18\x04 \x01(\x0b\x32#.truffle.app.BackgroundAppBuildInfoH\x00\x12+\n\x07process\x18\x05 \x01(\x0b\x32\x1a.truffle.app.ProcessConfigB\x0c\n\nbuild_info\"D\n\x11\x42uildSessionError\x12\r\n\x05\x65rror\x18\x01 \x01(\t\x12\x14\n\x07\x64\x65tails\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\n\n\x08_details\"Y\n\x1a\x46inishBuildSessionResponse\x12\x31\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x1d.truffle.os.BuildSessionErrorH\x00\x88\x01\x01\x42\x08\n\x06_errorb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18truffle/os/builder.proto\x12\ntruffle.os\x1a\x1ctruffle/app/background.proto\x1a\x1ctruffle/app/foreground.proto\x1a\x1btruffle/app/app_build.proto\x1a\x15truffle/app/app.proto\">\n\x18StartBuildSessionRequest\x12\x15\n\x08\x61pp_uuid\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_app_uuid\"B\n\x19StartBuildSessionResponse\x12\x13\n\x0b\x61\x63\x63\x65ss_path\x18\x01 \x01(\t\x12\x10\n\x08\x61pp_uuid\x18\x02 \x01(\t\"\x84\x02\n\x19\x46inishBuildSessionRequest\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\x12\x0f\n\x07\x64iscard\x18\x02 \x01(\x08\x12*\n\x08metadata\x18\x03 \x01(\x0b\x32\x18.truffle.app.AppMetadata\x12<\n\nforeground\x18\x04 \x01(\x0b\x32#.truffle.app.ForegroundAppBuildInfoH\x00\x88\x01\x01\x12<\n\nbackground\x18\x05 \x01(\x0b\x32#.truffle.app.BackgroundAppBuildInfoH\x01\x88\x01\x01\x42\r\n\x0b_foregroundB\r\n\x0b_background\"D\n\x11\x42uildSessionError\x12\r\n\x05\x65rror\x18\x01 \x01(\t\x12\x14\n\x07\x64\x65tails\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\n\n\x08_details\"Y\n\x1a\x46inishBuildSessionResponse\x12\x31\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x1d.truffle.os.BuildSessionErrorH\x00\x88\x01\x01\x42\x08\n\x06_errorb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.os.builder_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_STARTBUILDSESSIONREQUEST']._serialized_start=157 - _globals['_STARTBUILDSESSIONREQUEST']._serialized_end=259 - _globals['_STARTBUILDSESSIONRESPONSE']._serialized_start=261 - _globals['_STARTBUILDSESSIONRESPONSE']._serialized_end=327 - _globals['_FINISHBUILDSESSIONREQUEST']._serialized_start=330 - _globals['_FINISHBUILDSESSIONREQUEST']._serialized_end=569 - _globals['_BUILDSESSIONERROR']._serialized_start=571 - _globals['_BUILDSESSIONERROR']._serialized_end=639 - _globals['_FINISHBUILDSESSIONRESPONSE']._serialized_start=641 - _globals['_FINISHBUILDSESSIONRESPONSE']._serialized_end=730 + _globals['_STARTBUILDSESSIONREQUEST']._serialized_start=152 + _globals['_STARTBUILDSESSIONREQUEST']._serialized_end=214 + _globals['_STARTBUILDSESSIONRESPONSE']._serialized_start=216 + _globals['_STARTBUILDSESSIONRESPONSE']._serialized_end=282 + _globals['_FINISHBUILDSESSIONREQUEST']._serialized_start=285 + _globals['_FINISHBUILDSESSIONREQUEST']._serialized_end=545 + _globals['_BUILDSESSIONERROR']._serialized_start=547 + _globals['_BUILDSESSIONERROR']._serialized_end=615 + _globals['_FINISHBUILDSESSIONRESPONSE']._serialized_start=617 + _globals['_FINISHBUILDSESSIONRESPONSE']._serialized_end=706 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/builder_pb2.pyi b/truffle/os/builder_pb2.pyi index f36a285..e62fd5a 100644 --- a/truffle/os/builder_pb2.pyi +++ b/truffle/os/builder_pb2.pyi @@ -1,7 +1,7 @@ -from truffle.app import app_type_pb2 as _app_type_pb2 from truffle.app import background_pb2 as _background_pb2 from truffle.app import foreground_pb2 as _foreground_pb2 from truffle.app import app_build_pb2 as _app_build_pb2 +from truffle.app import app_pb2 as _app_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from collections.abc import Mapping as _Mapping @@ -10,12 +10,10 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor class StartBuildSessionRequest(_message.Message): - __slots__ = ("app_uuid", "app_type") + __slots__ = ("app_uuid",) APP_UUID_FIELD_NUMBER: _ClassVar[int] - APP_TYPE_FIELD_NUMBER: _ClassVar[int] app_uuid: str - app_type: _app_type_pb2.AppType - def __init__(self, app_uuid: _Optional[str] = ..., app_type: _Optional[_Union[_app_type_pb2.AppType, str]] = ...) -> None: ... + def __init__(self, app_uuid: _Optional[str] = ...) -> None: ... class StartBuildSessionResponse(_message.Message): __slots__ = ("access_path", "app_uuid") @@ -26,18 +24,18 @@ class StartBuildSessionResponse(_message.Message): def __init__(self, access_path: _Optional[str] = ..., app_uuid: _Optional[str] = ...) -> None: ... class FinishBuildSessionRequest(_message.Message): - __slots__ = ("app_uuid", "discard", "foreground", "background", "process") + __slots__ = ("app_uuid", "discard", "metadata", "foreground", "background") APP_UUID_FIELD_NUMBER: _ClassVar[int] DISCARD_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] FOREGROUND_FIELD_NUMBER: _ClassVar[int] BACKGROUND_FIELD_NUMBER: _ClassVar[int] - PROCESS_FIELD_NUMBER: _ClassVar[int] app_uuid: str discard: bool + metadata: _app_pb2.AppMetadata foreground: _foreground_pb2.ForegroundAppBuildInfo background: _background_pb2.BackgroundAppBuildInfo - process: _app_build_pb2.ProcessConfig - def __init__(self, app_uuid: _Optional[str] = ..., discard: bool = ..., foreground: _Optional[_Union[_foreground_pb2.ForegroundAppBuildInfo, _Mapping]] = ..., background: _Optional[_Union[_background_pb2.BackgroundAppBuildInfo, _Mapping]] = ..., process: _Optional[_Union[_app_build_pb2.ProcessConfig, _Mapping]] = ...) -> None: ... + def __init__(self, app_uuid: _Optional[str] = ..., discard: bool = ..., metadata: _Optional[_Union[_app_pb2.AppMetadata, _Mapping]] = ..., foreground: _Optional[_Union[_foreground_pb2.ForegroundAppBuildInfo, _Mapping]] = ..., background: _Optional[_Union[_background_pb2.BackgroundAppBuildInfo, _Mapping]] = ...) -> None: ... class BuildSessionError(_message.Message): __slots__ = ("error", "details") diff --git a/truffle/os/builder_pb2_grpc.py b/truffle/os/builder_pb2_grpc.py index d5be29b..9a3c71e 100644 --- a/truffle/os/builder_pb2_grpc.py +++ b/truffle/os/builder_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/builder_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/builder_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/classification_pb2.py b/truffle/os/classification_pb2.py deleted file mode 100644 index 0489117..0000000 --- a/truffle/os/classification_pb2.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/os/classification.proto -# Protobuf Python Version: 6.30.0 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 30, - 0, - '', - 'truffle/os/classification.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ftruffle/os/classification.proto\x12\ntruffle.os\"K\n\x0f\x43lassifyRequest\x12\x0e\n\x06prompt\x18\x01 \x01(\t\x12\x18\n\x0bmax_results\x18\x02 \x01(\x05H\x00\x88\x01\x01\x42\x0e\n\x0c_max_results\"\x81\x01\n\x10\x43lassifyResponse\x12<\n\x07results\x18\x01 \x03(\x0b\x32+.truffle.os.ClassifyResponse.ClassifyResult\x1a/\n\x0e\x43lassifyResult\x12\x0e\n\x06\x61pp_id\x18\x01 \x01(\t\x12\r\n\x05score\x18\x02 \x01(\x02\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.os.classification_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_CLASSIFYREQUEST']._serialized_start=47 - _globals['_CLASSIFYREQUEST']._serialized_end=122 - _globals['_CLASSIFYRESPONSE']._serialized_start=125 - _globals['_CLASSIFYRESPONSE']._serialized_end=254 - _globals['_CLASSIFYRESPONSE_CLASSIFYRESULT']._serialized_start=207 - _globals['_CLASSIFYRESPONSE_CLASSIFYRESULT']._serialized_end=254 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/os/classification_pb2.pyi b/truffle/os/classification_pb2.pyi deleted file mode 100644 index 4bca042..0000000 --- a/truffle/os/classification_pb2.pyi +++ /dev/null @@ -1,28 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class ClassifyRequest(_message.Message): - __slots__ = ("prompt", "max_results") - PROMPT_FIELD_NUMBER: _ClassVar[int] - MAX_RESULTS_FIELD_NUMBER: _ClassVar[int] - prompt: str - max_results: int - def __init__(self, prompt: _Optional[str] = ..., max_results: _Optional[int] = ...) -> None: ... - -class ClassifyResponse(_message.Message): - __slots__ = ("results",) - class ClassifyResult(_message.Message): - __slots__ = ("app_id", "score") - APP_ID_FIELD_NUMBER: _ClassVar[int] - SCORE_FIELD_NUMBER: _ClassVar[int] - app_id: str - score: float - def __init__(self, app_id: _Optional[str] = ..., score: _Optional[float] = ...) -> None: ... - RESULTS_FIELD_NUMBER: _ClassVar[int] - results: _containers.RepeatedCompositeFieldContainer[ClassifyResponse.ClassifyResult] - def __init__(self, results: _Optional[_Iterable[_Union[ClassifyResponse.ClassifyResult, _Mapping]]] = ...) -> None: ... diff --git a/truffle/os/classification_pb2_grpc.py b/truffle/os/classification_pb2_grpc.py deleted file mode 100644 index 62c9799..0000000 --- a/truffle/os/classification_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.72.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/classification_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/os/client_metadata_pb2.py b/truffle/os/client_metadata_pb2.py index 9ba5066..d04c359 100644 --- a/truffle/os/client_metadata_pb2.py +++ b/truffle/os/client_metadata_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/client_metadata.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/client_metadata.proto' ) diff --git a/truffle/os/client_metadata_pb2_grpc.py b/truffle/os/client_metadata_pb2_grpc.py index 66437f4..3adeae5 100644 --- a/truffle/os/client_metadata_pb2_grpc.py +++ b/truffle/os/client_metadata_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/client_metadata_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/client_metadata_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/client_session_pb2.py b/truffle/os/client_session_pb2.py index 0eaf851..b8d9c42 100644 --- a/truffle/os/client_session_pb2.py +++ b/truffle/os/client_session_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/client_session.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/client_session.proto' ) diff --git a/truffle/os/client_session_pb2.pyi b/truffle/os/client_session_pb2.pyi index a1a2178..23dc24b 100644 --- a/truffle/os/client_session_pb2.pyi +++ b/truffle/os/client_session_pb2.pyi @@ -1,3 +1,5 @@ +import datetime + from truffle.os import client_metadata_pb2 as _client_metadata_pb2 from google.protobuf import timestamp_pb2 as _timestamp_pb2 from google.protobuf.internal import containers as _containers @@ -43,7 +45,7 @@ class NewSessionVerification(_message.Message): verification_token: str expires_at: _timestamp_pb2.Timestamp requesting_client: _client_metadata_pb2.ClientMetadata - def __init__(self, verification_token: _Optional[str] = ..., expires_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., requesting_client: _Optional[_Union[_client_metadata_pb2.ClientMetadata, _Mapping]] = ...) -> None: ... + def __init__(self, verification_token: _Optional[str] = ..., expires_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., requesting_client: _Optional[_Union[_client_metadata_pb2.ClientMetadata, _Mapping]] = ...) -> None: ... class VerifyNewSessionRequest(_message.Message): __slots__ = ("verification_token", "allow") diff --git a/truffle/os/client_session_pb2_grpc.py b/truffle/os/client_session_pb2_grpc.py index d244186..d94ebaf 100644 --- a/truffle/os/client_session_pb2_grpc.py +++ b/truffle/os/client_session_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/client_session_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/client_session_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/client_state_pb2.py b/truffle/os/client_state_pb2.py index f1fe300..59da1b3 100644 --- a/truffle/os/client_state_pb2.py +++ b/truffle/os/client_state_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/client_state.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/client_state.proto' ) diff --git a/truffle/os/client_state_pb2_grpc.py b/truffle/os/client_state_pb2_grpc.py index 0fd1040..92d9bc3 100644 --- a/truffle/os/client_state_pb2_grpc.py +++ b/truffle/os/client_state_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/client_state_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/client_state_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/client_user_pb2.py b/truffle/os/client_user_pb2.py index 1ccf5e6..d2cb9a3 100644 --- a/truffle/os/client_user_pb2.py +++ b/truffle/os/client_user_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/client_user.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/client_user.proto' ) diff --git a/truffle/os/client_user_pb2_grpc.py b/truffle/os/client_user_pb2_grpc.py index 0a6955b..732739c 100644 --- a/truffle/os/client_user_pb2_grpc.py +++ b/truffle/os/client_user_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/client_user_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/client_user_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/hardware_control_pb2.py b/truffle/os/hardware_control_pb2.py index 9f28a54..25f2ab5 100644 --- a/truffle/os/hardware_control_pb2.py +++ b/truffle/os/hardware_control_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/hardware_control.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/hardware_control.proto' ) diff --git a/truffle/os/hardware_control_pb2_grpc.py b/truffle/os/hardware_control_pb2_grpc.py index e98792b..0cabd14 100644 --- a/truffle/os/hardware_control_pb2_grpc.py +++ b/truffle/os/hardware_control_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/hardware_control_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/hardware_control_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/hardware_info_pb2.py b/truffle/os/hardware_info_pb2.py index bcf246c..f98e4da 100644 --- a/truffle/os/hardware_info_pb2.py +++ b/truffle/os/hardware_info_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/hardware_info.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/hardware_info.proto' ) @@ -23,17 +23,16 @@ from truffle.os import hardware_network_pb2 as truffle_dot_os_dot_hardware__network__pb2 -from truffle.common import led_states_pb2 as truffle_dot_common_dot_led__states__pb2 from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1etruffle/os/hardware_info.proto\x12\ntruffle.os\x1a!truffle/os/hardware_network.proto\x1a\x1ftruffle/common/led_states.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xa8\x02\n\x0cHardwareInfo\x12\x10\n\x08hostname\x18\x01 \x01(\t\x12\x12\n\nip_address\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x39\n\x0enetwork_status\x18\x04 \x01(\x0e\x32!.truffle.os.HardwareNetworkStatus\x12\x42\n\x14\x63urrent_wifi_network\x18\x05 \x01(\x0b\x32\x1f.truffle.os.HardwareWifiNetworkH\x00\x88\x01\x01\x12\x15\n\rserial_number\x18\n \x01(\t\x12.\n\nstart_time\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x17\n\x15_current_wifi_networkb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1etruffle/os/hardware_info.proto\x12\ntruffle.os\x1a!truffle/os/hardware_network.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xba\x02\n\x0cHardwareInfo\x12\x10\n\x08hostname\x18\x01 \x01(\t\x12\x12\n\nip_address\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x39\n\x0enetwork_status\x18\x04 \x01(\x0e\x32!.truffle.os.HardwareNetworkStatus\x12\x42\n\x14\x63urrent_wifi_network\x18\x05 \x01(\x0b\x32\x1f.truffle.os.HardwareWifiNetworkH\x00\x88\x01\x01\x12\x15\n\rserial_number\x18\n \x01(\t\x12.\n\nstart_time\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08timezone\x18\x0c \x01(\tB\x17\n\x15_current_wifi_networkb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.os.hardware_info_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_HARDWAREINFO']._serialized_start=148 - _globals['_HARDWAREINFO']._serialized_end=444 + _globals['_HARDWAREINFO']._serialized_start=115 + _globals['_HARDWAREINFO']._serialized_end=429 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/hardware_info_pb2.pyi b/truffle/os/hardware_info_pb2.pyi index 5042bff..5381f44 100644 --- a/truffle/os/hardware_info_pb2.pyi +++ b/truffle/os/hardware_info_pb2.pyi @@ -1,5 +1,6 @@ +import datetime + from truffle.os import hardware_network_pb2 as _hardware_network_pb2 -from truffle.common import led_states_pb2 as _led_states_pb2 from google.protobuf import timestamp_pb2 as _timestamp_pb2 from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message @@ -9,7 +10,7 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor class HardwareInfo(_message.Message): - __slots__ = ("hostname", "ip_address", "mac_address", "network_status", "current_wifi_network", "serial_number", "start_time") + __slots__ = ("hostname", "ip_address", "mac_address", "network_status", "current_wifi_network", "serial_number", "start_time", "timezone") HOSTNAME_FIELD_NUMBER: _ClassVar[int] IP_ADDRESS_FIELD_NUMBER: _ClassVar[int] MAC_ADDRESS_FIELD_NUMBER: _ClassVar[int] @@ -17,6 +18,7 @@ class HardwareInfo(_message.Message): CURRENT_WIFI_NETWORK_FIELD_NUMBER: _ClassVar[int] SERIAL_NUMBER_FIELD_NUMBER: _ClassVar[int] START_TIME_FIELD_NUMBER: _ClassVar[int] + TIMEZONE_FIELD_NUMBER: _ClassVar[int] hostname: str ip_address: str mac_address: str @@ -24,4 +26,5 @@ class HardwareInfo(_message.Message): current_wifi_network: _hardware_network_pb2.HardwareWifiNetwork serial_number: str start_time: _timestamp_pb2.Timestamp - def __init__(self, hostname: _Optional[str] = ..., ip_address: _Optional[str] = ..., mac_address: _Optional[str] = ..., network_status: _Optional[_Union[_hardware_network_pb2.HardwareNetworkStatus, str]] = ..., current_wifi_network: _Optional[_Union[_hardware_network_pb2.HardwareWifiNetwork, _Mapping]] = ..., serial_number: _Optional[str] = ..., start_time: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + timezone: str + def __init__(self, hostname: _Optional[str] = ..., ip_address: _Optional[str] = ..., mac_address: _Optional[str] = ..., network_status: _Optional[_Union[_hardware_network_pb2.HardwareNetworkStatus, str]] = ..., current_wifi_network: _Optional[_Union[_hardware_network_pb2.HardwareWifiNetwork, _Mapping]] = ..., serial_number: _Optional[str] = ..., start_time: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., timezone: _Optional[str] = ...) -> None: ... diff --git a/truffle/os/hardware_info_pb2_grpc.py b/truffle/os/hardware_info_pb2_grpc.py index 2b1cec8..0920be0 100644 --- a/truffle/os/hardware_info_pb2_grpc.py +++ b/truffle/os/hardware_info_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/hardware_info_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/hardware_info_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/hardware_network_pb2.py b/truffle/os/hardware_network_pb2.py index b961935..a93c1d4 100644 --- a/truffle/os/hardware_network_pb2.py +++ b/truffle/os/hardware_network_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/hardware_network.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/hardware_network.proto' ) diff --git a/truffle/os/hardware_network_pb2_grpc.py b/truffle/os/hardware_network_pb2_grpc.py index 83be82a..122a5b5 100644 --- a/truffle/os/hardware_network_pb2_grpc.py +++ b/truffle/os/hardware_network_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/hardware_network_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/hardware_network_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/hardware_settings_pb2.py b/truffle/os/hardware_settings_pb2.py index 73b02f9..2dc5c8b 100644 --- a/truffle/os/hardware_settings_pb2.py +++ b/truffle/os/hardware_settings_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/hardware_settings.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/hardware_settings.proto' ) diff --git a/truffle/os/hardware_settings_pb2_grpc.py b/truffle/os/hardware_settings_pb2_grpc.py index c3ea15a..ed803c6 100644 --- a/truffle/os/hardware_settings_pb2_grpc.py +++ b/truffle/os/hardware_settings_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/hardware_settings_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/hardware_settings_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/hardware_stats_pb2.py b/truffle/os/hardware_stats_pb2.py index f2deb13..6cfab76 100644 --- a/truffle/os/hardware_stats_pb2.py +++ b/truffle/os/hardware_stats_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/hardware_stats.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/hardware_stats.proto' ) diff --git a/truffle/os/hardware_stats_pb2_grpc.py b/truffle/os/hardware_stats_pb2_grpc.py index e5ba7dd..122c216 100644 --- a/truffle/os/hardware_stats_pb2_grpc.py +++ b/truffle/os/hardware_stats_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/hardware_stats_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/hardware_stats_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/installer_pb2.py b/truffle/os/installer_pb2.py index 82cf1b8..4ac2a76 100644 --- a/truffle/os/installer_pb2.py +++ b/truffle/os/installer_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/installer.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/installer.proto' ) @@ -22,12 +22,11 @@ _sym_db = _symbol_database.Default() -from truffle.app import foreground_pb2 as truffle_dot_app_dot_foreground__pb2 -from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 -from truffle.app import app_type_pb2 as truffle_dot_app_dot_app__type__pb2 +from truffle.app import app_pb2 as truffle_dot_app_dot_app__pb2 +from truffle.app.app_pb2 import * -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1atruffle/os/installer.proto\x12\ntruffle.os\x1a\x1ctruffle/app/foreground.proto\x1a\x1ctruffle/app/background.proto\x1a\x1atruffle/app/app_type.proto\"\x87\x01\n\x10\x41ppInstallSource\x12\x35\n\x0bsource_type\x18\x01 \x01(\x0e\x32 .truffle.os.AppInstallSourceType\x12\x10\n\x03url\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08git_hash\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x06\n\x04_urlB\x0b\n\t_git_hash\"\xc3\x07\n\x0f\x41ppInstallModal\x12\x12\n\nstep_index\x18\x01 \x01(\x05\x12\x11\n\tstep_name\x18\x02 \x01(\t\x12\x41\n\rwelcome_modal\x18\n \x01(\x0b\x32(.truffle.os.AppInstallModal.WelcomeModalH\x00\x12H\n\x11text_fields_modal\x18\x0b \x01(\x0b\x32+.truffle.os.AppInstallModal.TextFieldsModalH\x00\x12\x39\n\tvnc_modal\x18\x0c \x01(\x0b\x32$.truffle.os.AppInstallModal.VNCModalH\x00\x12?\n\x0c\x66inish_modal\x18\x0e \x01(\x0b\x32\'.truffle.os.AppInstallModal.FinishModalH\x00\x12H\n\x11upload_file_modal\x18\x0f \x01(\x0b\x32+.truffle.os.AppInstallModal.UploadFileModalH\x00\x1a\'\n\x0cWelcomeModal\x12\x17\n\x0fwelcome_message\x18\x01 \x01(\t\x1a\xca\x02\n\x0fTextFieldsModal\x12\x14\n\x0cinstructions\x18\x01 \x01(\t\x12G\n\x06\x66ields\x18\x02 \x03(\x0b\x32\x37.truffle.os.AppInstallModal.TextFieldsModal.FieldsEntry\x1ar\n\tTextField\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x0bplaceholder\x18\x02 \x01(\t\x12\x13\n\x0bis_password\x18\x03 \x01(\x08\x12\x1a\n\rdefault_value\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_default_value\x1a\x64\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x44\n\x05value\x18\x02 \x01(\x0b\x32\x35.truffle.os.AppInstallModal.TextFieldsModal.TextField:\x02\x38\x01\x1aR\n\x08VNCModal\x12\x14\n\x0cinstructions\x18\x01 \x01(\t\x12\x14\n\x0cvnc_uri_path\x18\x02 \x01(\t\x12\x1a\n\x12\x63loses_on_complete\x18\x03 \x01(\x08\x1a*\n\x0fUploadFileModal\x12\x17\n\x0fupload_uri_path\x18\x01 \x01(\t\x1a\x37\n\x0b\x46inishModal\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\x12\x16\n\x0e\x66inish_message\x18\x02 \x01(\tB\x07\n\x05modal\"(\n\x0f\x41ppInstallError\x12\x15\n\rerror_message\x18\x01 \x01(\t\",\n\x11\x41ppInstallLoading\x12\x17\n\x0floading_message\x18\x01 \x01(\t\"\xde\x01\n\x12\x41ppInstallMetadata\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12&\n\x08\x61pp_type\x18\x02 \x01(\x0e\x32\x14.truffle.app.AppType\x12\x42\n\x13\x66oreground_metadata\x18\x03 \x01(\x0b\x32#.truffle.app.ForegroundApp.MetadataH\x00\x12\x42\n\x13\x62\x61\x63kground_metadata\x18\x04 \x01(\x0b\x32#.truffle.app.BackgroundApp.MetadataH\x00\x42\n\n\x08metadata\"\xc1\x03\n\x14\x41ppInstallUserAction\x12;\n\x04next\x18\x01 \x01(\x0b\x32+.truffle.os.AppInstallUserAction.NextActionH\x00\x12N\n\x0btext_fields\x18\x02 \x01(\x0b\x32\x37.truffle.os.AppInstallUserAction.SubmitTextFieldsActionH\x00\x12=\n\x05\x61\x62ort\x18\x03 \x01(\x0b\x32,.truffle.os.AppInstallUserAction.AbortActionH\x00\x1a\x0c\n\nNextAction\x1a\xb5\x01\n\x16SubmitTextFieldsAction\x12\x64\n\x0f\x66ield_responses\x18\x01 \x03(\x0b\x32K.truffle.os.AppInstallUserAction.SubmitTextFieldsAction.FieldResponsesEntry\x1a\x35\n\x13\x46ieldResponsesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\r\n\x0b\x41\x62ortActionB\x08\n\x06\x61\x63tion\"\xde\x01\n\x11\x41ppInstallRequest\x12\x42\n\tstart_new\x18\x01 \x01(\x0b\x32-.truffle.os.AppInstallRequest.StartNewInstallH\x00\x12\x37\n\x0buser_action\x18\x03 \x01(\x0b\x32 .truffle.os.AppInstallUserActionH\x00\x1a?\n\x0fStartNewInstall\x12,\n\x06source\x18\x01 \x01(\x0b\x32\x1c.truffle.os.AppInstallSourceB\x0b\n\toperation\"\x83\x02\n\x12\x41ppInstallResponse\x12\x34\n\rinstall_modal\x18\x01 \x01(\x0b\x32\x1b.truffle.os.AppInstallModalH\x00\x12\x34\n\rinstall_error\x18\x02 \x01(\x0b\x32\x1b.truffle.os.AppInstallErrorH\x00\x12\x38\n\x0finstall_loading\x18\x03 \x01(\x0b\x32\x1d.truffle.os.AppInstallLoadingH\x00\x12:\n\x10install_metadata\x18\x04 \x01(\x0b\x32\x1e.truffle.os.AppInstallMetadataH\x00\x42\x0b\n\toperation*\xa3\x01\n\x14\x41ppInstallSourceType\x12\'\n#APP_INSTALL_SOURCE_TYPE_UNSPECIFIED\x10\x00\x12\x1f\n\x1b\x41PP_INSTALL_SOURCE_TYPE_URL\x10\x01\x12 \n\x1c\x41PP_INSTALL_SOURCE_TYPE_FILE\x10\x02\x12\x1f\n\x1b\x41PP_INSTALL_SOURCE_TYPE_GIT\x10\x03\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1atruffle/os/installer.proto\x12\ntruffle.os\x1a\x15truffle/app/app.proto\"\x87\x01\n\x10\x41ppInstallSource\x12\x35\n\x0bsource_type\x18\x01 \x01(\x0e\x32 .truffle.os.AppInstallSourceType\x12\x10\n\x03url\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08git_hash\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x06\n\x04_urlB\x0b\n\t_git_hash\"\xd9\x08\n\x0f\x41ppInstallModal\x12\x12\n\nstep_index\x18\x01 \x01(\x05\x12\x11\n\tstep_name\x18\x02 \x01(\t\x12\x41\n\rwelcome_modal\x18\n \x01(\x0b\x32(.truffle.os.AppInstallModal.WelcomeModalH\x00\x12H\n\x11text_fields_modal\x18\x0b \x01(\x0b\x32+.truffle.os.AppInstallModal.TextFieldsModalH\x00\x12\x39\n\tvnc_modal\x18\x0c \x01(\x0b\x32$.truffle.os.AppInstallModal.VNCModalH\x00\x12?\n\x0c\x66inish_modal\x18\x0e \x01(\x0b\x32\'.truffle.os.AppInstallModal.FinishModalH\x00\x12H\n\x11upload_file_modal\x18\x0f \x01(\x0b\x32+.truffle.os.AppInstallModal.UploadFileModalH\x00\x12=\n\x0boauth_modal\x18\x10 \x01(\x0b\x32&.truffle.os.AppInstallModal.OAuthModalH\x00\x1a\'\n\x0cWelcomeModal\x12\x17\n\x0fwelcome_message\x18\x01 \x01(\t\x1a\xca\x02\n\x0fTextFieldsModal\x12\x14\n\x0cinstructions\x18\x01 \x01(\t\x12G\n\x06\x66ields\x18\x02 \x03(\x0b\x32\x37.truffle.os.AppInstallModal.TextFieldsModal.FieldsEntry\x1ar\n\tTextField\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x0bplaceholder\x18\x02 \x01(\t\x12\x13\n\x0bis_password\x18\x03 \x01(\x08\x12\x1a\n\rdefault_value\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_default_value\x1a\x64\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x44\n\x05value\x18\x02 \x01(\x0b\x32\x35.truffle.os.AppInstallModal.TextFieldsModal.TextField:\x02\x38\x01\x1aR\n\x08VNCModal\x12\x14\n\x0cinstructions\x18\x01 \x01(\t\x12\x14\n\x0cvnc_uri_path\x18\x02 \x01(\t\x12\x1a\n\x12\x63loses_on_complete\x18\x03 \x01(\x08\x1aU\n\nOAuthModal\x12\x14\n\x0cinstructions\x18\x01 \x01(\t\x12\x10\n\x08provider\x18\x02 \x01(\t\x12\x10\n\x08\x61uth_url\x18\x03 \x01(\t\x12\r\n\x05state\x18\x04 \x01(\t\x1a*\n\x0fUploadFileModal\x12\x17\n\x0fupload_uri_path\x18\x01 \x01(\t\x1a\x37\n\x0b\x46inishModal\x12\x10\n\x08\x61pp_uuid\x18\x01 \x01(\t\x12\x16\n\x0e\x66inish_message\x18\x02 \x01(\tB\x07\n\x05modal\"(\n\x0f\x41ppInstallError\x12\x15\n\rerror_message\x18\x01 \x01(\t\",\n\x11\x41ppInstallLoading\x12\x17\n\x0floading_message\x18\x01 \x01(\t\"\xb1\x01\n\x0e\x41ppInstallHint\x12\x34\n\x08ui_state\x18\x01 \x01(\x0e\x32\".truffle.os.AppInstallHint.UiState\"i\n\x07UiState\x12\x18\n\x14UI_STATE_UNSPECIFIED\x10\x00\x12#\n\x1fUI_STATE_USER_INTERACTION_READY\x10\x01\x12\x1f\n\x1bUI_STATE_MOVE_TO_BACKGROUND\x10\x02\"~\n\x12\x41ppInstallMetadata\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12*\n\x08metadata\x18\x02 \x01(\x0b\x32\x18.truffle.app.AppMetadata\x12\x16\n\x0ehas_foreground\x18\x03 \x01(\x08\x12\x16\n\x0ehas_background\x18\x04 \x01(\x08\"\xb8\x04\n\x14\x41ppInstallUserAction\x12;\n\x04next\x18\x01 \x01(\x0b\x32+.truffle.os.AppInstallUserAction.NextActionH\x00\x12N\n\x0btext_fields\x18\x02 \x01(\x0b\x32\x37.truffle.os.AppInstallUserAction.SubmitTextFieldsActionH\x00\x12=\n\x05\x61\x62ort\x18\x03 \x01(\x0b\x32,.truffle.os.AppInstallUserAction.AbortActionH\x00\x12\x43\n\x05oauth\x18\x04 \x01(\x0b\x32\x32.truffle.os.AppInstallUserAction.SubmitOAuthActionH\x00\x1a\x0c\n\nNextAction\x1a\xb5\x01\n\x16SubmitTextFieldsAction\x12\x64\n\x0f\x66ield_responses\x18\x01 \x03(\x0b\x32K.truffle.os.AppInstallUserAction.SubmitTextFieldsAction.FieldResponsesEntry\x1a\x35\n\x13\x46ieldResponsesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x11SubmitOAuthAction\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\x1a\r\n\x0b\x41\x62ortActionB\x08\n\x06\x61\x63tion\"\xde\x01\n\x11\x41ppInstallRequest\x12\x42\n\tstart_new\x18\x01 \x01(\x0b\x32-.truffle.os.AppInstallRequest.StartNewInstallH\x00\x12\x37\n\x0buser_action\x18\x03 \x01(\x0b\x32 .truffle.os.AppInstallUserActionH\x00\x1a?\n\x0fStartNewInstall\x12,\n\x06source\x18\x01 \x01(\x0b\x32\x1c.truffle.os.AppInstallSourceB\x0b\n\toperation\"\xb7\x02\n\x12\x41ppInstallResponse\x12\x34\n\rinstall_modal\x18\x01 \x01(\x0b\x32\x1b.truffle.os.AppInstallModalH\x00\x12\x34\n\rinstall_error\x18\x02 \x01(\x0b\x32\x1b.truffle.os.AppInstallErrorH\x00\x12\x38\n\x0finstall_loading\x18\x03 \x01(\x0b\x32\x1d.truffle.os.AppInstallLoadingH\x00\x12:\n\x10install_metadata\x18\x04 \x01(\x0b\x32\x1e.truffle.os.AppInstallMetadataH\x00\x12\x32\n\x0cinstall_hint\x18\x05 \x01(\x0b\x32\x1a.truffle.os.AppInstallHintH\x00\x42\x0b\n\toperation*\xa3\x01\n\x14\x41ppInstallSourceType\x12\'\n#APP_INSTALL_SOURCE_TYPE_UNSPECIFIED\x10\x00\x12\x1f\n\x1b\x41PP_INSTALL_SOURCE_TYPE_URL\x10\x01\x12 \n\x1c\x41PP_INSTALL_SOURCE_TYPE_FILE\x10\x02\x12\x1f\n\x1b\x41PP_INSTALL_SOURCE_TYPE_GIT\x10\x03P\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -38,46 +37,54 @@ _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_FIELDSENTRY']._serialized_options = b'8\001' _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION_FIELDRESPONSESENTRY']._loaded_options = None _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION_FIELDRESPONSESENTRY']._serialized_options = b'8\001' - _globals['_APPINSTALLSOURCETYPE']._serialized_start=2487 - _globals['_APPINSTALLSOURCETYPE']._serialized_end=2650 - _globals['_APPINSTALLSOURCE']._serialized_start=131 - _globals['_APPINSTALLSOURCE']._serialized_end=266 - _globals['_APPINSTALLMODAL']._serialized_start=269 - _globals['_APPINSTALLMODAL']._serialized_end=1232 - _globals['_APPINSTALLMODAL_WELCOMEMODAL']._serialized_start=666 - _globals['_APPINSTALLMODAL_WELCOMEMODAL']._serialized_end=705 - _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL']._serialized_start=708 - _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL']._serialized_end=1038 - _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_TEXTFIELD']._serialized_start=822 - _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_TEXTFIELD']._serialized_end=936 - _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_FIELDSENTRY']._serialized_start=938 - _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_FIELDSENTRY']._serialized_end=1038 - _globals['_APPINSTALLMODAL_VNCMODAL']._serialized_start=1040 - _globals['_APPINSTALLMODAL_VNCMODAL']._serialized_end=1122 - _globals['_APPINSTALLMODAL_UPLOADFILEMODAL']._serialized_start=1124 - _globals['_APPINSTALLMODAL_UPLOADFILEMODAL']._serialized_end=1166 - _globals['_APPINSTALLMODAL_FINISHMODAL']._serialized_start=1168 - _globals['_APPINSTALLMODAL_FINISHMODAL']._serialized_end=1223 - _globals['_APPINSTALLERROR']._serialized_start=1234 - _globals['_APPINSTALLERROR']._serialized_end=1274 - _globals['_APPINSTALLLOADING']._serialized_start=1276 - _globals['_APPINSTALLLOADING']._serialized_end=1320 - _globals['_APPINSTALLMETADATA']._serialized_start=1323 - _globals['_APPINSTALLMETADATA']._serialized_end=1545 - _globals['_APPINSTALLUSERACTION']._serialized_start=1548 - _globals['_APPINSTALLUSERACTION']._serialized_end=1997 - _globals['_APPINSTALLUSERACTION_NEXTACTION']._serialized_start=1776 - _globals['_APPINSTALLUSERACTION_NEXTACTION']._serialized_end=1788 - _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION']._serialized_start=1791 - _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION']._serialized_end=1972 - _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION_FIELDRESPONSESENTRY']._serialized_start=1919 - _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION_FIELDRESPONSESENTRY']._serialized_end=1972 - _globals['_APPINSTALLUSERACTION_ABORTACTION']._serialized_start=1974 - _globals['_APPINSTALLUSERACTION_ABORTACTION']._serialized_end=1987 - _globals['_APPINSTALLREQUEST']._serialized_start=2000 - _globals['_APPINSTALLREQUEST']._serialized_end=2222 - _globals['_APPINSTALLREQUEST_STARTNEWINSTALL']._serialized_start=2146 - _globals['_APPINSTALLREQUEST_STARTNEWINSTALL']._serialized_end=2209 - _globals['_APPINSTALLRESPONSE']._serialized_start=2225 - _globals['_APPINSTALLRESPONSE']._serialized_end=2484 + _globals['_APPINSTALLSOURCETYPE']._serialized_start=2826 + _globals['_APPINSTALLSOURCETYPE']._serialized_end=2989 + _globals['_APPINSTALLSOURCE']._serialized_start=66 + _globals['_APPINSTALLSOURCE']._serialized_end=201 + _globals['_APPINSTALLMODAL']._serialized_start=204 + _globals['_APPINSTALLMODAL']._serialized_end=1317 + _globals['_APPINSTALLMODAL_WELCOMEMODAL']._serialized_start=664 + _globals['_APPINSTALLMODAL_WELCOMEMODAL']._serialized_end=703 + _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL']._serialized_start=706 + _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL']._serialized_end=1036 + _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_TEXTFIELD']._serialized_start=820 + _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_TEXTFIELD']._serialized_end=934 + _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_FIELDSENTRY']._serialized_start=936 + _globals['_APPINSTALLMODAL_TEXTFIELDSMODAL_FIELDSENTRY']._serialized_end=1036 + _globals['_APPINSTALLMODAL_VNCMODAL']._serialized_start=1038 + _globals['_APPINSTALLMODAL_VNCMODAL']._serialized_end=1120 + _globals['_APPINSTALLMODAL_OAUTHMODAL']._serialized_start=1122 + _globals['_APPINSTALLMODAL_OAUTHMODAL']._serialized_end=1207 + _globals['_APPINSTALLMODAL_UPLOADFILEMODAL']._serialized_start=1209 + _globals['_APPINSTALLMODAL_UPLOADFILEMODAL']._serialized_end=1251 + _globals['_APPINSTALLMODAL_FINISHMODAL']._serialized_start=1253 + _globals['_APPINSTALLMODAL_FINISHMODAL']._serialized_end=1308 + _globals['_APPINSTALLERROR']._serialized_start=1319 + _globals['_APPINSTALLERROR']._serialized_end=1359 + _globals['_APPINSTALLLOADING']._serialized_start=1361 + _globals['_APPINSTALLLOADING']._serialized_end=1405 + _globals['_APPINSTALLHINT']._serialized_start=1408 + _globals['_APPINSTALLHINT']._serialized_end=1585 + _globals['_APPINSTALLHINT_UISTATE']._serialized_start=1480 + _globals['_APPINSTALLHINT_UISTATE']._serialized_end=1585 + _globals['_APPINSTALLMETADATA']._serialized_start=1587 + _globals['_APPINSTALLMETADATA']._serialized_end=1713 + _globals['_APPINSTALLUSERACTION']._serialized_start=1716 + _globals['_APPINSTALLUSERACTION']._serialized_end=2284 + _globals['_APPINSTALLUSERACTION_NEXTACTION']._serialized_start=2013 + _globals['_APPINSTALLUSERACTION_NEXTACTION']._serialized_end=2025 + _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION']._serialized_start=2028 + _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION']._serialized_end=2209 + _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION_FIELDRESPONSESENTRY']._serialized_start=2156 + _globals['_APPINSTALLUSERACTION_SUBMITTEXTFIELDSACTION_FIELDRESPONSESENTRY']._serialized_end=2209 + _globals['_APPINSTALLUSERACTION_SUBMITOAUTHACTION']._serialized_start=2211 + _globals['_APPINSTALLUSERACTION_SUBMITOAUTHACTION']._serialized_end=2259 + _globals['_APPINSTALLUSERACTION_ABORTACTION']._serialized_start=2261 + _globals['_APPINSTALLUSERACTION_ABORTACTION']._serialized_end=2274 + _globals['_APPINSTALLREQUEST']._serialized_start=2287 + _globals['_APPINSTALLREQUEST']._serialized_end=2509 + _globals['_APPINSTALLREQUEST_STARTNEWINSTALL']._serialized_start=2433 + _globals['_APPINSTALLREQUEST_STARTNEWINSTALL']._serialized_end=2496 + _globals['_APPINSTALLRESPONSE']._serialized_start=2512 + _globals['_APPINSTALLRESPONSE']._serialized_end=2823 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/installer_pb2.pyi b/truffle/os/installer_pb2.pyi index 78b5080..21ac0b4 100644 --- a/truffle/os/installer_pb2.pyi +++ b/truffle/os/installer_pb2.pyi @@ -1,12 +1,14 @@ -from truffle.app import foreground_pb2 as _foreground_pb2 -from truffle.app import background_pb2 as _background_pb2 -from truffle.app import app_type_pb2 as _app_type_pb2 +from truffle.app import app_pb2 as _app_pb2 from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from collections.abc import Mapping as _Mapping from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union +from truffle.app.app_pb2 import AppMetadata as AppMetadata +from truffle.app.app_pb2 import AppConfig as AppConfig +from truffle.app.app_pb2 import App as App +from truffle.app.app_pb2 import AppError as AppError DESCRIPTOR: _descriptor.FileDescriptor @@ -32,7 +34,7 @@ class AppInstallSource(_message.Message): def __init__(self, source_type: _Optional[_Union[AppInstallSourceType, str]] = ..., url: _Optional[str] = ..., git_hash: _Optional[str] = ...) -> None: ... class AppInstallModal(_message.Message): - __slots__ = ("step_index", "step_name", "welcome_modal", "text_fields_modal", "vnc_modal", "finish_modal", "upload_file_modal") + __slots__ = ("step_index", "step_name", "welcome_modal", "text_fields_modal", "vnc_modal", "finish_modal", "upload_file_modal", "oauth_modal") class WelcomeModal(_message.Message): __slots__ = ("welcome_message",) WELCOME_MESSAGE_FIELD_NUMBER: _ClassVar[int] @@ -72,6 +74,17 @@ class AppInstallModal(_message.Message): vnc_uri_path: str closes_on_complete: bool def __init__(self, instructions: _Optional[str] = ..., vnc_uri_path: _Optional[str] = ..., closes_on_complete: bool = ...) -> None: ... + class OAuthModal(_message.Message): + __slots__ = ("instructions", "provider", "auth_url", "state") + INSTRUCTIONS_FIELD_NUMBER: _ClassVar[int] + PROVIDER_FIELD_NUMBER: _ClassVar[int] + AUTH_URL_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + instructions: str + provider: str + auth_url: str + state: str + def __init__(self, instructions: _Optional[str] = ..., provider: _Optional[str] = ..., auth_url: _Optional[str] = ..., state: _Optional[str] = ...) -> None: ... class UploadFileModal(_message.Message): __slots__ = ("upload_uri_path",) UPLOAD_URI_PATH_FIELD_NUMBER: _ClassVar[int] @@ -91,6 +104,7 @@ class AppInstallModal(_message.Message): VNC_MODAL_FIELD_NUMBER: _ClassVar[int] FINISH_MODAL_FIELD_NUMBER: _ClassVar[int] UPLOAD_FILE_MODAL_FIELD_NUMBER: _ClassVar[int] + OAUTH_MODAL_FIELD_NUMBER: _ClassVar[int] step_index: int step_name: str welcome_modal: AppInstallModal.WelcomeModal @@ -98,7 +112,8 @@ class AppInstallModal(_message.Message): vnc_modal: AppInstallModal.VNCModal finish_modal: AppInstallModal.FinishModal upload_file_modal: AppInstallModal.UploadFileModal - def __init__(self, step_index: _Optional[int] = ..., step_name: _Optional[str] = ..., welcome_modal: _Optional[_Union[AppInstallModal.WelcomeModal, _Mapping]] = ..., text_fields_modal: _Optional[_Union[AppInstallModal.TextFieldsModal, _Mapping]] = ..., vnc_modal: _Optional[_Union[AppInstallModal.VNCModal, _Mapping]] = ..., finish_modal: _Optional[_Union[AppInstallModal.FinishModal, _Mapping]] = ..., upload_file_modal: _Optional[_Union[AppInstallModal.UploadFileModal, _Mapping]] = ...) -> None: ... + oauth_modal: AppInstallModal.OAuthModal + def __init__(self, step_index: _Optional[int] = ..., step_name: _Optional[str] = ..., welcome_modal: _Optional[_Union[AppInstallModal.WelcomeModal, _Mapping]] = ..., text_fields_modal: _Optional[_Union[AppInstallModal.TextFieldsModal, _Mapping]] = ..., vnc_modal: _Optional[_Union[AppInstallModal.VNCModal, _Mapping]] = ..., finish_modal: _Optional[_Union[AppInstallModal.FinishModal, _Mapping]] = ..., upload_file_modal: _Optional[_Union[AppInstallModal.UploadFileModal, _Mapping]] = ..., oauth_modal: _Optional[_Union[AppInstallModal.OAuthModal, _Mapping]] = ...) -> None: ... class AppInstallError(_message.Message): __slots__ = ("error_message",) @@ -112,20 +127,34 @@ class AppInstallLoading(_message.Message): loading_message: str def __init__(self, loading_message: _Optional[str] = ...) -> None: ... +class AppInstallHint(_message.Message): + __slots__ = ("ui_state",) + class UiState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + UI_STATE_UNSPECIFIED: _ClassVar[AppInstallHint.UiState] + UI_STATE_USER_INTERACTION_READY: _ClassVar[AppInstallHint.UiState] + UI_STATE_MOVE_TO_BACKGROUND: _ClassVar[AppInstallHint.UiState] + UI_STATE_UNSPECIFIED: AppInstallHint.UiState + UI_STATE_USER_INTERACTION_READY: AppInstallHint.UiState + UI_STATE_MOVE_TO_BACKGROUND: AppInstallHint.UiState + UI_STATE_FIELD_NUMBER: _ClassVar[int] + ui_state: AppInstallHint.UiState + def __init__(self, ui_state: _Optional[_Union[AppInstallHint.UiState, str]] = ...) -> None: ... + class AppInstallMetadata(_message.Message): - __slots__ = ("uuid", "app_type", "foreground_metadata", "background_metadata") + __slots__ = ("uuid", "metadata", "has_foreground", "has_background") UUID_FIELD_NUMBER: _ClassVar[int] - APP_TYPE_FIELD_NUMBER: _ClassVar[int] - FOREGROUND_METADATA_FIELD_NUMBER: _ClassVar[int] - BACKGROUND_METADATA_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] + HAS_FOREGROUND_FIELD_NUMBER: _ClassVar[int] + HAS_BACKGROUND_FIELD_NUMBER: _ClassVar[int] uuid: str - app_type: _app_type_pb2.AppType - foreground_metadata: _foreground_pb2.ForegroundApp.Metadata - background_metadata: _background_pb2.BackgroundApp.Metadata - def __init__(self, uuid: _Optional[str] = ..., app_type: _Optional[_Union[_app_type_pb2.AppType, str]] = ..., foreground_metadata: _Optional[_Union[_foreground_pb2.ForegroundApp.Metadata, _Mapping]] = ..., background_metadata: _Optional[_Union[_background_pb2.BackgroundApp.Metadata, _Mapping]] = ...) -> None: ... + metadata: _app_pb2.AppMetadata + has_foreground: bool + has_background: bool + def __init__(self, uuid: _Optional[str] = ..., metadata: _Optional[_Union[_app_pb2.AppMetadata, _Mapping]] = ..., has_foreground: bool = ..., has_background: bool = ...) -> None: ... class AppInstallUserAction(_message.Message): - __slots__ = ("next", "text_fields", "abort") + __slots__ = ("next", "text_fields", "abort", "oauth") class NextAction(_message.Message): __slots__ = () def __init__(self) -> None: ... @@ -141,16 +170,25 @@ class AppInstallUserAction(_message.Message): FIELD_RESPONSES_FIELD_NUMBER: _ClassVar[int] field_responses: _containers.ScalarMap[str, str] def __init__(self, field_responses: _Optional[_Mapping[str, str]] = ...) -> None: ... + class SubmitOAuthAction(_message.Message): + __slots__ = ("code", "state") + CODE_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + code: str + state: str + def __init__(self, code: _Optional[str] = ..., state: _Optional[str] = ...) -> None: ... class AbortAction(_message.Message): __slots__ = () def __init__(self) -> None: ... NEXT_FIELD_NUMBER: _ClassVar[int] TEXT_FIELDS_FIELD_NUMBER: _ClassVar[int] ABORT_FIELD_NUMBER: _ClassVar[int] + OAUTH_FIELD_NUMBER: _ClassVar[int] next: AppInstallUserAction.NextAction text_fields: AppInstallUserAction.SubmitTextFieldsAction abort: AppInstallUserAction.AbortAction - def __init__(self, next: _Optional[_Union[AppInstallUserAction.NextAction, _Mapping]] = ..., text_fields: _Optional[_Union[AppInstallUserAction.SubmitTextFieldsAction, _Mapping]] = ..., abort: _Optional[_Union[AppInstallUserAction.AbortAction, _Mapping]] = ...) -> None: ... + oauth: AppInstallUserAction.SubmitOAuthAction + def __init__(self, next: _Optional[_Union[AppInstallUserAction.NextAction, _Mapping]] = ..., text_fields: _Optional[_Union[AppInstallUserAction.SubmitTextFieldsAction, _Mapping]] = ..., abort: _Optional[_Union[AppInstallUserAction.AbortAction, _Mapping]] = ..., oauth: _Optional[_Union[AppInstallUserAction.SubmitOAuthAction, _Mapping]] = ...) -> None: ... class AppInstallRequest(_message.Message): __slots__ = ("start_new", "user_action") @@ -166,13 +204,15 @@ class AppInstallRequest(_message.Message): def __init__(self, start_new: _Optional[_Union[AppInstallRequest.StartNewInstall, _Mapping]] = ..., user_action: _Optional[_Union[AppInstallUserAction, _Mapping]] = ...) -> None: ... class AppInstallResponse(_message.Message): - __slots__ = ("install_modal", "install_error", "install_loading", "install_metadata") + __slots__ = ("install_modal", "install_error", "install_loading", "install_metadata", "install_hint") INSTALL_MODAL_FIELD_NUMBER: _ClassVar[int] INSTALL_ERROR_FIELD_NUMBER: _ClassVar[int] INSTALL_LOADING_FIELD_NUMBER: _ClassVar[int] INSTALL_METADATA_FIELD_NUMBER: _ClassVar[int] + INSTALL_HINT_FIELD_NUMBER: _ClassVar[int] install_modal: AppInstallModal install_error: AppInstallError install_loading: AppInstallLoading install_metadata: AppInstallMetadata - def __init__(self, install_modal: _Optional[_Union[AppInstallModal, _Mapping]] = ..., install_error: _Optional[_Union[AppInstallError, _Mapping]] = ..., install_loading: _Optional[_Union[AppInstallLoading, _Mapping]] = ..., install_metadata: _Optional[_Union[AppInstallMetadata, _Mapping]] = ...) -> None: ... + install_hint: AppInstallHint + def __init__(self, install_modal: _Optional[_Union[AppInstallModal, _Mapping]] = ..., install_error: _Optional[_Union[AppInstallError, _Mapping]] = ..., install_loading: _Optional[_Union[AppInstallLoading, _Mapping]] = ..., install_metadata: _Optional[_Union[AppInstallMetadata, _Mapping]] = ..., install_hint: _Optional[_Union[AppInstallHint, _Mapping]] = ...) -> None: ... diff --git a/truffle/os/installer_pb2_grpc.py b/truffle/os/installer_pb2_grpc.py index c1eb7ce..9e4d9a2 100644 --- a/truffle/os/installer_pb2_grpc.py +++ b/truffle/os/installer_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/installer_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/installer_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/notification_pb2.py b/truffle/os/notification_pb2.py index c66af35..198341b 100644 --- a/truffle/os/notification_pb2.py +++ b/truffle/os/notification_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/notification.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/notification.proto' ) @@ -26,20 +26,20 @@ from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 from truffle.os import hardware_stats_pb2 as truffle_dot_os_dot_hardware__stats__pb2 from truffle.os import client_session_pb2 as truffle_dot_os_dot_client__session__pb2 -from truffle.app import background_pb2 as truffle_dot_app_dot_background__pb2 +from truffle.os import background_feed_pb2 as truffle_dot_os_dot_background__feed__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dtruffle/os/notification.proto\x12\ntruffle.os\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1ftruffle/os/hardware_stats.proto\x1a\x1ftruffle/os/client_session.proto\x1a\x1ctruffle/app/background.proto\"!\n\x1fSubscribeToNotificationsRequest\"\xa6\x05\n\x0cNotification\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).truffle.os.Notification.NotificationType\x12\x15\n\rassociated_id\x18\x02 \x01(\t\x12&\n\x04none\x18\x03 \x01(\x0b\x32\x16.google.protobuf.EmptyH\x00\x12*\n\x07payload\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x12\x46\n\x18new_session_verification\x18\x07 \x01(\x0b\x32\".truffle.os.NewSessionVerificationH\x00\x12M\n\x1b\x62\x61\x63kground_app_notification\x18\n \x01(\x0b\x32&.truffle.app.BackgroundAppNotificationH\x00\x12\x10\n\x08is_error\x18\x08 \x01(\x08\"\xc0\x02\n\x10NotificationType\x12\x1d\n\x19NOTIFICATION_TYPE_INVALID\x10\x00\x12\x12\n\x0e\x42G_FEED_UPDATE\x10\x01\x12\x13\n\x0fTASK_HAS_RESULT\x10\x02\x12\x1e\n\x1a\x42G_FEED_FEEDBACK_PROCESSED\x10\x03\x12\x15\n\x11\x42G_APP_LIST_DIRTY\x10\x0e\x12\x15\n\x11\x46G_APP_LIST_DIRTY\x10\x0f\x12\x13\n\x0fTASK_LIST_DIRTY\x10\x10\x12\x11\n\rSESSION_READY\x10\x14\x12 \n\x1cSESSION_VERIFICATION_REQUEST\x10\x15\x12\x11\n\rSESSION_ADDED\x10\x16\x12\x12\n\x0eSESSION_DENIED\x10\x17\x12\x12\n\x0eSERVER_CLOSING\x10\x1f\x12\x11\n\rDISPLAY_TOAST\x10 B\x06\n\x04\x64\x61tab\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dtruffle/os/notification.proto\x12\ntruffle.os\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1ftruffle/os/hardware_stats.proto\x1a\x1ftruffle/os/client_session.proto\x1a truffle/os/background_feed.proto\"!\n\x1fSubscribeToNotificationsRequest\"\xb7\x04\n\x0cNotification\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).truffle.os.Notification.NotificationType\x12\x15\n\rassociated_id\x18\x02 \x01(\t\x12&\n\x04none\x18\x03 \x01(\x0b\x32\x16.google.protobuf.EmptyH\x00\x12\x46\n\x18new_session_verification\x18\x07 \x01(\x0b\x32\".truffle.os.NewSessionVerificationH\x00\x12\x44\n\x17\x66\x65\x65\x64_entry_notification\x18\n \x01(\x0b\x32!.truffle.os.FeedEntryNotificationH\x00\x12\x10\n\x08is_error\x18\x08 \x01(\x08\"\x86\x02\n\x10NotificationType\x12\x1d\n\x19NOTIFICATION_TYPE_INVALID\x10\x00\x12\x12\n\x0e\x42G_FEED_UPDATE\x10\x01\x12\x13\n\x0fTASK_HAS_RESULT\x10\x02\x12\x12\n\x0e\x41PP_LIST_DIRTY\x10\x0e\x12\x13\n\x0fTASK_LIST_DIRTY\x10\x10\x12\x11\n\rSESSION_READY\x10\x14\x12 \n\x1cSESSION_VERIFICATION_REQUEST\x10\x15\x12\x11\n\rSESSION_ADDED\x10\x16\x12\x12\n\x0eSESSION_DENIED\x10\x17\x12\x12\n\x0eSERVER_CLOSING\x10\x1f\x12\x11\n\rDISPLAY_TOAST\x10 B\x06\n\x04\x64\x61tab\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.os.notification_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_SUBSCRIBETONOTIFICATIONSREQUEST']._serialized_start=200 - _globals['_SUBSCRIBETONOTIFICATIONSREQUEST']._serialized_end=233 - _globals['_NOTIFICATION']._serialized_start=236 - _globals['_NOTIFICATION']._serialized_end=914 - _globals['_NOTIFICATION_NOTIFICATIONTYPE']._serialized_start=586 - _globals['_NOTIFICATION_NOTIFICATIONTYPE']._serialized_end=906 + _globals['_SUBSCRIBETONOTIFICATIONSREQUEST']._serialized_start=204 + _globals['_SUBSCRIBETONOTIFICATIONSREQUEST']._serialized_end=237 + _globals['_NOTIFICATION']._serialized_start=240 + _globals['_NOTIFICATION']._serialized_end=807 + _globals['_NOTIFICATION_NOTIFICATIONTYPE']._serialized_start=537 + _globals['_NOTIFICATION_NOTIFICATIONTYPE']._serialized_end=799 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/notification_pb2.pyi b/truffle/os/notification_pb2.pyi index 1a521b0..dee6108 100644 --- a/truffle/os/notification_pb2.pyi +++ b/truffle/os/notification_pb2.pyi @@ -2,7 +2,7 @@ from google.protobuf import struct_pb2 as _struct_pb2 from google.protobuf import empty_pb2 as _empty_pb2 from truffle.os import hardware_stats_pb2 as _hardware_stats_pb2 from truffle.os import client_session_pb2 as _client_session_pb2 -from truffle.app import background_pb2 as _background_pb2 +from truffle.os import background_feed_pb2 as _background_feed_pb2 from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message @@ -16,15 +16,13 @@ class SubscribeToNotificationsRequest(_message.Message): def __init__(self) -> None: ... class Notification(_message.Message): - __slots__ = ("type", "associated_id", "none", "payload", "new_session_verification", "background_app_notification", "is_error") + __slots__ = ("type", "associated_id", "none", "new_session_verification", "feed_entry_notification", "is_error") class NotificationType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): __slots__ = () NOTIFICATION_TYPE_INVALID: _ClassVar[Notification.NotificationType] BG_FEED_UPDATE: _ClassVar[Notification.NotificationType] TASK_HAS_RESULT: _ClassVar[Notification.NotificationType] - BG_FEED_FEEDBACK_PROCESSED: _ClassVar[Notification.NotificationType] - BG_APP_LIST_DIRTY: _ClassVar[Notification.NotificationType] - FG_APP_LIST_DIRTY: _ClassVar[Notification.NotificationType] + APP_LIST_DIRTY: _ClassVar[Notification.NotificationType] TASK_LIST_DIRTY: _ClassVar[Notification.NotificationType] SESSION_READY: _ClassVar[Notification.NotificationType] SESSION_VERIFICATION_REQUEST: _ClassVar[Notification.NotificationType] @@ -35,9 +33,7 @@ class Notification(_message.Message): NOTIFICATION_TYPE_INVALID: Notification.NotificationType BG_FEED_UPDATE: Notification.NotificationType TASK_HAS_RESULT: Notification.NotificationType - BG_FEED_FEEDBACK_PROCESSED: Notification.NotificationType - BG_APP_LIST_DIRTY: Notification.NotificationType - FG_APP_LIST_DIRTY: Notification.NotificationType + APP_LIST_DIRTY: Notification.NotificationType TASK_LIST_DIRTY: Notification.NotificationType SESSION_READY: Notification.NotificationType SESSION_VERIFICATION_REQUEST: Notification.NotificationType @@ -48,15 +44,13 @@ class Notification(_message.Message): TYPE_FIELD_NUMBER: _ClassVar[int] ASSOCIATED_ID_FIELD_NUMBER: _ClassVar[int] NONE_FIELD_NUMBER: _ClassVar[int] - PAYLOAD_FIELD_NUMBER: _ClassVar[int] NEW_SESSION_VERIFICATION_FIELD_NUMBER: _ClassVar[int] - BACKGROUND_APP_NOTIFICATION_FIELD_NUMBER: _ClassVar[int] + FEED_ENTRY_NOTIFICATION_FIELD_NUMBER: _ClassVar[int] IS_ERROR_FIELD_NUMBER: _ClassVar[int] type: Notification.NotificationType associated_id: str none: _empty_pb2.Empty - payload: _struct_pb2.Struct new_session_verification: _client_session_pb2.NewSessionVerification - background_app_notification: _background_pb2.BackgroundAppNotification + feed_entry_notification: _background_feed_pb2.FeedEntryNotification is_error: bool - def __init__(self, type: _Optional[_Union[Notification.NotificationType, str]] = ..., associated_id: _Optional[str] = ..., none: _Optional[_Union[_empty_pb2.Empty, _Mapping]] = ..., payload: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., new_session_verification: _Optional[_Union[_client_session_pb2.NewSessionVerification, _Mapping]] = ..., background_app_notification: _Optional[_Union[_background_pb2.BackgroundAppNotification, _Mapping]] = ..., is_error: bool = ...) -> None: ... + def __init__(self, type: _Optional[_Union[Notification.NotificationType, str]] = ..., associated_id: _Optional[str] = ..., none: _Optional[_Union[_empty_pb2.Empty, _Mapping]] = ..., new_session_verification: _Optional[_Union[_client_session_pb2.NewSessionVerification, _Mapping]] = ..., feed_entry_notification: _Optional[_Union[_background_feed_pb2.FeedEntryNotification, _Mapping]] = ..., is_error: bool = ...) -> None: ... diff --git a/truffle/os/notification_pb2_grpc.py b/truffle/os/notification_pb2_grpc.py index 1b8bc2c..504a3bc 100644 --- a/truffle/os/notification_pb2_grpc.py +++ b/truffle/os/notification_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/notification_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/notification_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/proactivity_pb2.py b/truffle/os/proactivity_pb2.py new file mode 100644 index 0000000..b1c9d3b --- /dev/null +++ b/truffle/os/proactivity_pb2.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: truffle/os/proactivity.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'truffle/os/proactivity.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/os/proactivity.proto\x12\ntruffle.os\x1a\x1fgoogle/protobuf/timestamp.proto\"\xe9\x04\n\x0fProactiveAction\x12\r\n\x05title\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12:\n\nactionable\x18\x04 \x01(\x0b\x32&.truffle.os.ProactiveAction.Actionable\x12\x32\n\x06status\x18\x05 \x01(\x0e\x32\".truffle.os.ProactiveAction.Status\x12.\n\ncreated_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x11\n\tapp_uuids\x18\x08 \x03(\t\x12\x1b\n\x13prompt_for_subagent\x18\t \x01(\t\x1a\x9c\x01\n\nActionable\x12J\n\x0c\x62oolean_text\x18\x01 \x01(\x0b\x32\x32.truffle.os.ProactiveAction.Actionable.BooleanTextH\x00\x1a:\n\x0b\x42ooleanText\x12\x0f\n\x07\x61pprove\x18\x01 \x01(\x08\x12\x11\n\x04text\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x07\n\x05_textB\x06\n\x04type\"\x92\x01\n\x06Status\x12\x18\n\x14\x41\x43TION_STATE_INVALID\x10\x00\x12\x18\n\x14\x41\x43TION_STATE_PENDING\x10\x01\x12\x1c\n\x18\x41\x43TION_STATE_IN_PROGRESS\x10\x02\x12\x1a\n\x16\x41\x43TION_STATE_CANCELLED\x10\x03\x12\x1a\n\x16\x41\x43TION_STATE_COMPLETED\x10\x04\"n\n\x1d\x41pproveProactiveActionRequest\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\x04\x12;\n\x0buser_action\x18\x02 \x01(\x0b\x32&.truffle.os.ProactiveAction.Actionable\"U\n\x1e\x41pproveProactiveActionResponse\x12\x33\n\x0eupdated_action\x18\x01 \x01(\x0b\x32\x1b.truffle.os.ProactiveAction\"0\n\x1c\x43\x61ncelProactiveActionRequest\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\x04\"\x1f\n\x1d\x43\x61ncelProactiveActionResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.os.proactivity_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_PROACTIVEACTION']._serialized_start=78 + _globals['_PROACTIVEACTION']._serialized_end=695 + _globals['_PROACTIVEACTION_ACTIONABLE']._serialized_start=390 + _globals['_PROACTIVEACTION_ACTIONABLE']._serialized_end=546 + _globals['_PROACTIVEACTION_ACTIONABLE_BOOLEANTEXT']._serialized_start=480 + _globals['_PROACTIVEACTION_ACTIONABLE_BOOLEANTEXT']._serialized_end=538 + _globals['_PROACTIVEACTION_STATUS']._serialized_start=549 + _globals['_PROACTIVEACTION_STATUS']._serialized_end=695 + _globals['_APPROVEPROACTIVEACTIONREQUEST']._serialized_start=697 + _globals['_APPROVEPROACTIVEACTIONREQUEST']._serialized_end=807 + _globals['_APPROVEPROACTIVEACTIONRESPONSE']._serialized_start=809 + _globals['_APPROVEPROACTIVEACTIONRESPONSE']._serialized_end=894 + _globals['_CANCELPROACTIVEACTIONREQUEST']._serialized_start=896 + _globals['_CANCELPROACTIVEACTIONREQUEST']._serialized_end=944 + _globals['_CANCELPROACTIVEACTIONRESPONSE']._serialized_start=946 + _globals['_CANCELPROACTIVEACTIONRESPONSE']._serialized_end=977 +# @@protoc_insertion_point(module_scope) diff --git a/truffle/os/proactivity_pb2.pyi b/truffle/os/proactivity_pb2.pyi new file mode 100644 index 0000000..c2e952e --- /dev/null +++ b/truffle/os/proactivity_pb2.pyi @@ -0,0 +1,79 @@ +import datetime + +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class ProactiveAction(_message.Message): + __slots__ = ("title", "description", "actionable", "status", "created_at", "updated_at", "app_uuids", "prompt_for_subagent") + class Status(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ACTION_STATE_INVALID: _ClassVar[ProactiveAction.Status] + ACTION_STATE_PENDING: _ClassVar[ProactiveAction.Status] + ACTION_STATE_IN_PROGRESS: _ClassVar[ProactiveAction.Status] + ACTION_STATE_CANCELLED: _ClassVar[ProactiveAction.Status] + ACTION_STATE_COMPLETED: _ClassVar[ProactiveAction.Status] + ACTION_STATE_INVALID: ProactiveAction.Status + ACTION_STATE_PENDING: ProactiveAction.Status + ACTION_STATE_IN_PROGRESS: ProactiveAction.Status + ACTION_STATE_CANCELLED: ProactiveAction.Status + ACTION_STATE_COMPLETED: ProactiveAction.Status + class Actionable(_message.Message): + __slots__ = ("boolean_text",) + class BooleanText(_message.Message): + __slots__ = ("approve", "text") + APPROVE_FIELD_NUMBER: _ClassVar[int] + TEXT_FIELD_NUMBER: _ClassVar[int] + approve: bool + text: str + def __init__(self, approve: bool = ..., text: _Optional[str] = ...) -> None: ... + BOOLEAN_TEXT_FIELD_NUMBER: _ClassVar[int] + boolean_text: ProactiveAction.Actionable.BooleanText + def __init__(self, boolean_text: _Optional[_Union[ProactiveAction.Actionable.BooleanText, _Mapping]] = ...) -> None: ... + TITLE_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + ACTIONABLE_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + APP_UUIDS_FIELD_NUMBER: _ClassVar[int] + PROMPT_FOR_SUBAGENT_FIELD_NUMBER: _ClassVar[int] + title: str + description: str + actionable: ProactiveAction.Actionable + status: ProactiveAction.Status + created_at: _timestamp_pb2.Timestamp + updated_at: _timestamp_pb2.Timestamp + app_uuids: _containers.RepeatedScalarFieldContainer[str] + prompt_for_subagent: str + def __init__(self, title: _Optional[str] = ..., description: _Optional[str] = ..., actionable: _Optional[_Union[ProactiveAction.Actionable, _Mapping]] = ..., status: _Optional[_Union[ProactiveAction.Status, str]] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., app_uuids: _Optional[_Iterable[str]] = ..., prompt_for_subagent: _Optional[str] = ...) -> None: ... + +class ApproveProactiveActionRequest(_message.Message): + __slots__ = ("entry_id", "user_action") + ENTRY_ID_FIELD_NUMBER: _ClassVar[int] + USER_ACTION_FIELD_NUMBER: _ClassVar[int] + entry_id: int + user_action: ProactiveAction.Actionable + def __init__(self, entry_id: _Optional[int] = ..., user_action: _Optional[_Union[ProactiveAction.Actionable, _Mapping]] = ...) -> None: ... + +class ApproveProactiveActionResponse(_message.Message): + __slots__ = ("updated_action",) + UPDATED_ACTION_FIELD_NUMBER: _ClassVar[int] + updated_action: ProactiveAction + def __init__(self, updated_action: _Optional[_Union[ProactiveAction, _Mapping]] = ...) -> None: ... + +class CancelProactiveActionRequest(_message.Message): + __slots__ = ("entry_id",) + ENTRY_ID_FIELD_NUMBER: _ClassVar[int] + entry_id: int + def __init__(self, entry_id: _Optional[int] = ...) -> None: ... + +class CancelProactiveActionResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... diff --git a/truffle/app/app_type_pb2_grpc.py b/truffle/os/proactivity_pb2_grpc.py similarity index 86% rename from truffle/app/app_type_pb2_grpc.py rename to truffle/os/proactivity_pb2_grpc.py index 7886f3d..ebb049d 100644 --- a/truffle/app/app_type_pb2_grpc.py +++ b/truffle/os/proactivity_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/app/app_type_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/proactivity_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/system_info_pb2.py b/truffle/os/system_info_pb2.py index 2968f0c..7a91170 100644 --- a/truffle/os/system_info_pb2.py +++ b/truffle/os/system_info_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/system_info.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/system_info.proto' ) diff --git a/truffle/os/system_info_pb2_grpc.py b/truffle/os/system_info_pb2_grpc.py index b4a31ff..1ff7692 100644 --- a/truffle/os/system_info_pb2_grpc.py +++ b/truffle/os/system_info_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/system_info_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/system_info_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/system_settings_pb2.py b/truffle/os/system_settings_pb2.py index 93de2d3..2f372f5 100644 --- a/truffle/os/system_settings_pb2.py +++ b/truffle/os/system_settings_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/system_settings.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/system_settings.proto' ) @@ -26,7 +26,7 @@ from truffle.os.hardware_settings_pb2 import * -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n truffle/os/system_settings.proto\x12\ntruffle.os\x1a\"truffle/os/hardware_settings.proto\"\xac\x01\n\x0eSystemSettings\x12<\n\x11hardware_settings\x18\x01 \x01(\x0b\x32\x1c.truffle.os.HardwareSettingsH\x00\x88\x01\x01\x12\x34\n\rtask_settings\x18\x02 \x01(\x0b\x32\x18.truffle.os.TaskSettingsH\x01\x88\x01\x01\x42\x14\n\x12_hardware_settingsB\x10\n\x0e_task_settings\"0\n\x0cTaskSettings\x12\x1a\n\x12\x64\x65\x66\x61ult_model_uuid\x18\x02 \x01(\tJ\x04\x08\x01\x10\x02P\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n truffle/os/system_settings.proto\x12\ntruffle.os\x1a\"truffle/os/hardware_settings.proto\"\xac\x01\n\x0eSystemSettings\x12<\n\x11hardware_settings\x18\x01 \x01(\x0b\x32\x1c.truffle.os.HardwareSettingsH\x00\x88\x01\x01\x12\x34\n\rtask_settings\x18\x02 \x01(\x0b\x32\x18.truffle.os.TaskSettingsH\x01\x88\x01\x01\x42\x14\n\x12_hardware_settingsB\x10\n\x0e_task_settings\"\x0e\n\x0cTaskSettingsP\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -36,5 +36,5 @@ _globals['_SYSTEMSETTINGS']._serialized_start=85 _globals['_SYSTEMSETTINGS']._serialized_end=257 _globals['_TASKSETTINGS']._serialized_start=259 - _globals['_TASKSETTINGS']._serialized_end=307 + _globals['_TASKSETTINGS']._serialized_end=273 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/system_settings_pb2.pyi b/truffle/os/system_settings_pb2.pyi index 16864f1..870865a 100644 --- a/truffle/os/system_settings_pb2.pyi +++ b/truffle/os/system_settings_pb2.pyi @@ -16,7 +16,5 @@ class SystemSettings(_message.Message): def __init__(self, hardware_settings: _Optional[_Union[_hardware_settings_pb2.HardwareSettings, _Mapping]] = ..., task_settings: _Optional[_Union[TaskSettings, _Mapping]] = ...) -> None: ... class TaskSettings(_message.Message): - __slots__ = ("default_model_uuid",) - DEFAULT_MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - default_model_uuid: str - def __init__(self, default_model_uuid: _Optional[str] = ...) -> None: ... + __slots__ = () + def __init__(self) -> None: ... diff --git a/truffle/os/system_settings_pb2_grpc.py b/truffle/os/system_settings_pb2_grpc.py index b192150..45fd07e 100644 --- a/truffle/os/system_settings_pb2_grpc.py +++ b/truffle/os/system_settings_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/system_settings_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/system_settings_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_actions_pb2.py b/truffle/os/task_actions_pb2.py index 6e57ed2..a78e2db 100644 --- a/truffle/os/task_actions_pb2.py +++ b/truffle/os/task_actions_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_actions.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_actions.proto' ) diff --git a/truffle/os/task_actions_pb2.pyi b/truffle/os/task_actions_pb2.pyi index 11ff9e6..775b279 100644 --- a/truffle/os/task_actions_pb2.pyi +++ b/truffle/os/task_actions_pb2.pyi @@ -15,6 +15,7 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union from truffle.os.task_pb2 import Task as Task from truffle.os.task_pb2 import TasksList as TasksList from truffle.os.task_pb2 import TaskNode as TaskNode +from truffle.os.task_pb2 import StreamingTaskStepResult as StreamingTaskStepResult from truffle.os.task_pb2 import TaskStreamUpdate as TaskStreamUpdate from truffle.os.task_target_pb2 import TargetTask as TargetTask from truffle.os.task_options_pb2 import TaskOptions as TaskOptions diff --git a/truffle/os/task_actions_pb2_grpc.py b/truffle/os/task_actions_pb2_grpc.py index 1b4d731..1f22a7b 100644 --- a/truffle/os/task_actions_pb2_grpc.py +++ b/truffle/os/task_actions_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_actions_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_actions_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_error_pb2.py b/truffle/os/task_error_pb2.py index a78d4c0..3a4e0d8 100644 --- a/truffle/os/task_error_pb2.py +++ b/truffle/os/task_error_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_error.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_error.proto' ) diff --git a/truffle/os/task_error_pb2_grpc.py b/truffle/os/task_error_pb2_grpc.py index 7876bc4..2443ad2 100644 --- a/truffle/os/task_error_pb2_grpc.py +++ b/truffle/os/task_error_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_error_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_error_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_info_pb2.py b/truffle/os/task_info_pb2.py index 0d6aa9b..4de7973 100644 --- a/truffle/os/task_info_pb2.py +++ b/truffle/os/task_info_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_info.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_info.proto' ) diff --git a/truffle/os/task_info_pb2.pyi b/truffle/os/task_info_pb2.pyi index 00f2f90..b457df9 100644 --- a/truffle/os/task_info_pb2.pyi +++ b/truffle/os/task_info_pb2.pyi @@ -1,3 +1,5 @@ +import datetime + from google.protobuf import timestamp_pb2 as _timestamp_pb2 from truffle.os import task_options_pb2 as _task_options_pb2 from google.protobuf.internal import containers as _containers @@ -48,4 +50,4 @@ class TaskInfo(_message.Message): created: _timestamp_pb2.Timestamp last_updated: _timestamp_pb2.Timestamp access_uri: str - def __init__(self, run_state: _Optional[_Union[TaskInfo.TaskRunState, str]] = ..., app_uuids: _Optional[_Iterable[str]] = ..., task_title: _Optional[str] = ..., options: _Optional[_Union[_task_options_pb2.TaskOptions, _Mapping]] = ..., created: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., last_updated: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., access_uri: _Optional[str] = ...) -> None: ... + def __init__(self, run_state: _Optional[_Union[TaskInfo.TaskRunState, str]] = ..., app_uuids: _Optional[_Iterable[str]] = ..., task_title: _Optional[str] = ..., options: _Optional[_Union[_task_options_pb2.TaskOptions, _Mapping]] = ..., created: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., last_updated: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., access_uri: _Optional[str] = ...) -> None: ... diff --git a/truffle/os/task_info_pb2_grpc.py b/truffle/os/task_info_pb2_grpc.py index b2a4954..58c324e 100644 --- a/truffle/os/task_info_pb2_grpc.py +++ b/truffle/os/task_info_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_info_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_info_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_options_pb2.py b/truffle/os/task_options_pb2.py index f809068..099f5c7 100644 --- a/truffle/os/task_options_pb2.py +++ b/truffle/os/task_options_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_options.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_options.proto' ) diff --git a/truffle/os/task_options_pb2_grpc.py b/truffle/os/task_options_pb2_grpc.py index 11e3f5b..8930d65 100644 --- a/truffle/os/task_options_pb2_grpc.py +++ b/truffle/os/task_options_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_options_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_options_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_pb2.py b/truffle/os/task_pb2.py index b50c38c..c612b37 100644 --- a/truffle/os/task_pb2.py +++ b/truffle/os/task_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task.proto' ) @@ -37,7 +37,7 @@ from truffle.os.task_user_response_pb2 import * from truffle.os.task_step_pb2 import * -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15truffle/os/task.proto\x12\ntruffle.os\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19truffle/common/file.proto\x1a\x1atruffle/os/task_info.proto\x1a#truffle/os/task_user_response.proto\x1a\x1atruffle/os/task_step.proto\x1a\x1btruffle/os/task_error.proto\"t\n\x04Task\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\"\n\x04info\x18\x02 \x01(\x0b\x32\x14.truffle.os.TaskInfo\x12\x12\n\ntask_flags\x18\x03 \x01(\r\x12#\n\x05nodes\x18\x05 \x03(\x0b\x32\x14.truffle.os.TaskNode\",\n\tTasksList\x12\x1f\n\x05tasks\x18\x01 \x03(\x0b\x32\x10.truffle.os.Task\"\xc0\x01\n\x08TaskNode\x12\n\n\x02id\x18\x01 \x01(\r\x12\x11\n\tparent_id\x18\x02 \x01(\r\x12\x11\n\tchild_ids\x18\x03 \x03(\r\x12+\n\x05\x66iles\x18\x08 \x03(\x0b\x32\x1c.truffle.common.AttachedFile\x12 \n\x04step\x18\t \x01(\x0b\x32\x10.truffle.os.StepH\x00\x12+\n\x08user_msg\x18\n \x01(\x0b\x32\x17.truffle.os.UserMessageH\x00\x42\x06\n\x04item\"\xaf\x01\n\x10TaskStreamUpdate\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\'\n\x04info\x18\x03 \x01(\x0b\x32\x14.truffle.os.TaskInfoH\x00\x88\x01\x01\x12#\n\x05nodes\x18\x02 \x03(\x0b\x32\x14.truffle.os.TaskNode\x12)\n\x05\x65rror\x18\x05 \x01(\x0b\x32\x15.truffle.os.TaskErrorH\x01\x88\x01\x01\x42\x07\n\x05_infoB\x08\n\x06_errorP\x02P\x03P\x04\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15truffle/os/task.proto\x12\ntruffle.os\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19truffle/common/file.proto\x1a\x1atruffle/os/task_info.proto\x1a#truffle/os/task_user_response.proto\x1a\x1atruffle/os/task_step.proto\x1a\x1btruffle/os/task_error.proto\"t\n\x04Task\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\"\n\x04info\x18\x02 \x01(\x0b\x32\x14.truffle.os.TaskInfo\x12\x12\n\ntask_flags\x18\x03 \x01(\r\x12#\n\x05nodes\x18\x05 \x03(\x0b\x32\x14.truffle.os.TaskNode\",\n\tTasksList\x12\x1f\n\x05tasks\x18\x01 \x03(\x0b\x32\x10.truffle.os.Task\"\xf0\x01\n\x08TaskNode\x12\n\n\x02id\x18\x01 \x01(\r\x12\x11\n\tparent_id\x18\x02 \x01(\r\x12\x11\n\tchild_ids\x18\x03 \x03(\r\x12.\n\ncreated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12+\n\x05\x66iles\x18\x08 \x03(\x0b\x32\x1c.truffle.common.AttachedFile\x12 \n\x04step\x18\t \x01(\x0b\x32\x10.truffle.os.StepH\x00\x12+\n\x08user_msg\x18\n \x01(\x0b\x32\x17.truffle.os.UserMessageH\x00\x42\x06\n\x04item\"C\n\x17StreamingTaskStepResult\x12\x0f\n\x07node_id\x18\x01 \x01(\r\x12\x17\n\x0fpartial_content\x18\x02 \x01(\t\"\x92\x02\n\x10TaskStreamUpdate\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\'\n\x04info\x18\x03 \x01(\x0b\x32\x14.truffle.os.TaskInfoH\x00\x88\x01\x01\x12#\n\x05nodes\x18\x02 \x03(\x0b\x32\x14.truffle.os.TaskNode\x12)\n\x05\x65rror\x18\x05 \x01(\x0b\x32\x15.truffle.os.TaskErrorH\x01\x88\x01\x01\x12G\n\x15streaming_step_result\x18\x06 \x01(\x0b\x32#.truffle.os.StreamingTaskStepResultH\x02\x88\x01\x01\x42\x07\n\x05_infoB\x08\n\x06_errorB\x18\n\x16_streaming_step_resultP\x02P\x03P\x04\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -49,7 +49,9 @@ _globals['_TASKSLIST']._serialized_start=337 _globals['_TASKSLIST']._serialized_end=381 _globals['_TASKNODE']._serialized_start=384 - _globals['_TASKNODE']._serialized_end=576 - _globals['_TASKSTREAMUPDATE']._serialized_start=579 - _globals['_TASKSTREAMUPDATE']._serialized_end=754 + _globals['_TASKNODE']._serialized_end=624 + _globals['_STREAMINGTASKSTEPRESULT']._serialized_start=626 + _globals['_STREAMINGTASKSTEPRESULT']._serialized_end=693 + _globals['_TASKSTREAMUPDATE']._serialized_start=696 + _globals['_TASKSTREAMUPDATE']._serialized_end=970 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/task_pb2.pyi b/truffle/os/task_pb2.pyi index 2778deb..322c076 100644 --- a/truffle/os/task_pb2.pyi +++ b/truffle/os/task_pb2.pyi @@ -1,3 +1,5 @@ +import datetime + from google.protobuf import timestamp_pb2 as _timestamp_pb2 from truffle.common import file_pb2 as _file_pb2 from truffle.os import task_info_pb2 as _task_info_pb2 @@ -42,29 +44,41 @@ class TasksList(_message.Message): def __init__(self, tasks: _Optional[_Iterable[_Union[Task, _Mapping]]] = ...) -> None: ... class TaskNode(_message.Message): - __slots__ = ("id", "parent_id", "child_ids", "files", "step", "user_msg") + __slots__ = ("id", "parent_id", "child_ids", "created_at", "files", "step", "user_msg") ID_FIELD_NUMBER: _ClassVar[int] PARENT_ID_FIELD_NUMBER: _ClassVar[int] CHILD_IDS_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] FILES_FIELD_NUMBER: _ClassVar[int] STEP_FIELD_NUMBER: _ClassVar[int] USER_MSG_FIELD_NUMBER: _ClassVar[int] id: int parent_id: int child_ids: _containers.RepeatedScalarFieldContainer[int] + created_at: _timestamp_pb2.Timestamp files: _containers.RepeatedCompositeFieldContainer[_file_pb2.AttachedFile] step: _task_step_pb2.Step user_msg: _task_user_response_pb2.UserMessage - def __init__(self, id: _Optional[int] = ..., parent_id: _Optional[int] = ..., child_ids: _Optional[_Iterable[int]] = ..., files: _Optional[_Iterable[_Union[_file_pb2.AttachedFile, _Mapping]]] = ..., step: _Optional[_Union[_task_step_pb2.Step, _Mapping]] = ..., user_msg: _Optional[_Union[_task_user_response_pb2.UserMessage, _Mapping]] = ...) -> None: ... + def __init__(self, id: _Optional[int] = ..., parent_id: _Optional[int] = ..., child_ids: _Optional[_Iterable[int]] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., files: _Optional[_Iterable[_Union[_file_pb2.AttachedFile, _Mapping]]] = ..., step: _Optional[_Union[_task_step_pb2.Step, _Mapping]] = ..., user_msg: _Optional[_Union[_task_user_response_pb2.UserMessage, _Mapping]] = ...) -> None: ... + +class StreamingTaskStepResult(_message.Message): + __slots__ = ("node_id", "partial_content") + NODE_ID_FIELD_NUMBER: _ClassVar[int] + PARTIAL_CONTENT_FIELD_NUMBER: _ClassVar[int] + node_id: int + partial_content: str + def __init__(self, node_id: _Optional[int] = ..., partial_content: _Optional[str] = ...) -> None: ... class TaskStreamUpdate(_message.Message): - __slots__ = ("task_id", "info", "nodes", "error") + __slots__ = ("task_id", "info", "nodes", "error", "streaming_step_result") TASK_ID_FIELD_NUMBER: _ClassVar[int] INFO_FIELD_NUMBER: _ClassVar[int] NODES_FIELD_NUMBER: _ClassVar[int] ERROR_FIELD_NUMBER: _ClassVar[int] + STREAMING_STEP_RESULT_FIELD_NUMBER: _ClassVar[int] task_id: str info: _task_info_pb2.TaskInfo nodes: _containers.RepeatedCompositeFieldContainer[TaskNode] error: _task_error_pb2.TaskError - def __init__(self, task_id: _Optional[str] = ..., info: _Optional[_Union[_task_info_pb2.TaskInfo, _Mapping]] = ..., nodes: _Optional[_Iterable[_Union[TaskNode, _Mapping]]] = ..., error: _Optional[_Union[_task_error_pb2.TaskError, _Mapping]] = ...) -> None: ... + streaming_step_result: StreamingTaskStepResult + def __init__(self, task_id: _Optional[str] = ..., info: _Optional[_Union[_task_info_pb2.TaskInfo, _Mapping]] = ..., nodes: _Optional[_Iterable[_Union[TaskNode, _Mapping]]] = ..., error: _Optional[_Union[_task_error_pb2.TaskError, _Mapping]] = ..., streaming_step_result: _Optional[_Union[StreamingTaskStepResult, _Mapping]] = ...) -> None: ... diff --git a/truffle/os/task_pb2_grpc.py b/truffle/os/task_pb2_grpc.py index 563c6fb..61b5ff6 100644 --- a/truffle/os/task_pb2_grpc.py +++ b/truffle/os/task_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_queries_pb2.py b/truffle/os/task_queries_pb2.py index 8c23762..8a6eb9e 100644 --- a/truffle/os/task_queries_pb2.py +++ b/truffle/os/task_queries_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_queries.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_queries.proto' ) diff --git a/truffle/os/task_queries_pb2_grpc.py b/truffle/os/task_queries_pb2_grpc.py index bd31e7f..2d4e661 100644 --- a/truffle/os/task_queries_pb2_grpc.py +++ b/truffle/os/task_queries_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_queries_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_queries_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_search_pb2.py b/truffle/os/task_search_pb2.py index 2fcf123..5af5440 100644 --- a/truffle/os/task_search_pb2.py +++ b/truffle/os/task_search_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_search.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_search.proto' ) diff --git a/truffle/os/task_search_pb2.pyi b/truffle/os/task_search_pb2.pyi index 789a436..4c64b2f 100644 --- a/truffle/os/task_search_pb2.pyi +++ b/truffle/os/task_search_pb2.pyi @@ -1,3 +1,5 @@ +import datetime + from google.protobuf import timestamp_pb2 as _timestamp_pb2 from truffle.os import task_info_pb2 as _task_info_pb2 from google.protobuf.internal import containers as _containers @@ -33,7 +35,7 @@ class TaskSearchResult(_message.Message): task_info: _task_info_pb2.TaskInfo timestamp: _timestamp_pb2.Timestamp content: TaskSearchResult.TaskSearchContent - def __init__(self, task_id: _Optional[str] = ..., task_info: _Optional[_Union[_task_info_pb2.TaskInfo, _Mapping]] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., content: _Optional[_Union[TaskSearchResult.TaskSearchContent, _Mapping]] = ...) -> None: ... + def __init__(self, task_id: _Optional[str] = ..., task_info: _Optional[_Union[_task_info_pb2.TaskInfo, _Mapping]] = ..., timestamp: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., content: _Optional[_Union[TaskSearchResult.TaskSearchContent, _Mapping]] = ...) -> None: ... class SearchTasksResponse(_message.Message): __slots__ = ("total_results", "current_offset", "results") diff --git a/truffle/os/task_search_pb2_grpc.py b/truffle/os/task_search_pb2_grpc.py index 022f753..15dc29f 100644 --- a/truffle/os/task_search_pb2_grpc.py +++ b/truffle/os/task_search_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_search_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_search_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_step_pb2.py b/truffle/os/task_step_pb2.py index 3f55d2e..b1cf1da 100644 --- a/truffle/os/task_step_pb2.py +++ b/truffle/os/task_step_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_step.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_step.proto' ) @@ -24,29 +24,27 @@ from truffle.os import task_user_response_pb2 as truffle_dot_os_dot_task__user__response__pb2 from truffle.common import content_pb2 as truffle_dot_common_dot_content__pb2 -from truffle.infer import usage_pb2 as truffle_dot_infer_dot_usage__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 from truffle.common.content_pb2 import * -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1atruffle/os/task_step.proto\x12\ntruffle.os\x1a#truffle/os/task_user_response.proto\x1a\x1ctruffle/common/content.proto\x1a\x19truffle/infer/usage.proto\"\x8d\x08\n\x04Step\x12.\n\x05state\x18\x01 \x01(\x0e\x32\x1a.truffle.os.Step.StepStateH\x00\x88\x01\x01\x12;\n\ruser_response\x18\n \x01(\x0b\x32\x1f.truffle.os.PendingUserResponseH\x01\x88\x01\x01\x12+\n\x08thinking\x18\x02 \x01(\x0b\x32\x19.truffle.os.Step.Thinking\x12-\n\ntool_calls\x18\x03 \x03(\x0b\x32\x19.truffle.os.Step.ToolCall\x12+\n\texecution\x18\x04 \x01(\x0b\x32\x18.truffle.os.Step.Execute\x12)\n\x07results\x18\x05 \x01(\x0b\x32\x18.truffle.os.Step.Results\x12)\n\x07metrics\x18\x06 \x01(\x0b\x32\x18.truffle.os.Step.Metrics\x12\x17\n\nmodel_uuid\x18\x07 \x01(\tH\x02\x88\x01\x01\x1a\x93\x01\n\x08Thinking\x12\x12\n\ncot_chunks\x18\x01 \x03(\t\x12\x15\n\rcot_summaries\x18\x02 \x03(\t\x12\x17\n\nraw_output\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x1e\n\x11thinking_finished\x18\x04 \x01(\x08H\x01\x88\x01\x01\x42\r\n\x0b_raw_outputB\x14\n\x12_thinking_finished\x1aR\n\x08ToolCall\x12\x16\n\ttool_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07summary\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x0c\n\n_tool_nameB\n\n\x08_summary\x1a\x1f\n\x07\x45xecute\x12\x14\n\x0ctool_updates\x18\x01 \x03(\t\x1a\xbd\x01\n\x07Results\x12\x14\n\x07summary\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x63ontent\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x1f\n\x12\x63ontent_incomplete\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12.\n\x03web\x18\x04 \x01(\x0b\x32\x1c.truffle.common.WebComponentH\x03\x88\x01\x01\x42\n\n\x08_summaryB\n\n\x08_contentB\x15\n\x13_content_incompleteB\x06\n\x04_web\x1aQ\n\x07Metrics\x12\x32\n\x0finference_usage\x18\x01 \x01(\x0b\x32\x14.truffle.infer.UsageH\x00\x88\x01\x01\x42\x12\n\x10_inference_usage\"W\n\tStepState\x12\x10\n\x0cSTEP_INVALID\x10\x00\x12\x13\n\x0fSTEP_GENERATING\x10\x01\x12\x12\n\x0eSTEP_EXECUTING\x10\x02\x12\x0f\n\x0bSTEP_RESULT\x10\x03\x42\x08\n\x06_stateB\x10\n\x0e_user_responseB\r\n\x0b_model_uuidP\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1atruffle/os/task_step.proto\x12\ntruffle.os\x1a#truffle/os/task_user_response.proto\x1a\x1ctruffle/common/content.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xc5\x05\n\x04Step\x12.\n\x05state\x18\x01 \x01(\x0e\x32\x1a.truffle.os.Step.StepStateH\x00\x88\x01\x01\x12;\n\ruser_response\x18\n \x01(\x0b\x32\x1f.truffle.os.PendingUserResponseH\x01\x88\x01\x01\x12+\n\x08thinking\x18\x02 \x01(\x0b\x32\x19.truffle.os.Step.Thinking\x12-\n\ntool_calls\x18\x03 \x03(\x0b\x32\x19.truffle.os.Step.ToolCall\x12+\n\texecution\x18\x04 \x01(\x0b\x32\x18.truffle.os.Step.Execute\x12)\n\x07results\x18\x05 \x01(\x0b\x32\x18.truffle.os.Step.Results\x12\x17\n\nmodel_uuid\x18\x07 \x01(\tH\x02\x88\x01\x01\x1a\x35\n\x08Thinking\x12\x12\n\ncot_chunks\x18\x01 \x03(\t\x12\x15\n\rcot_summaries\x18\x02 \x03(\t\x1an\n\x08ToolCall\x12\x16\n\ttool_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07summary\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x11\n\x04\x61rgs\x18\x03 \x01(\tH\x02\x88\x01\x01\x42\x0c\n\n_tool_nameB\n\n\x08_summaryB\x07\n\x05_args\x1a\t\n\x07\x45xecute\x1aM\n\x07Results\x12\x14\n\x07summary\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x63ontent\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\n\n\x08_summaryB\n\n\x08_content\"W\n\tStepState\x12\x10\n\x0cSTEP_INVALID\x10\x00\x12\x13\n\x0fSTEP_GENERATING\x10\x01\x12\x12\n\x0eSTEP_EXECUTING\x10\x02\x12\x0f\n\x0bSTEP_RESULT\x10\x03\x42\x08\n\x06_stateB\x10\n\x0e_user_responseB\r\n\x0b_model_uuidP\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.os.task_step_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_STEP']._serialized_start=137 - _globals['_STEP']._serialized_end=1174 - _globals['_STEP_THINKING']._serialized_start=503 - _globals['_STEP_THINKING']._serialized_end=650 - _globals['_STEP_TOOLCALL']._serialized_start=652 - _globals['_STEP_TOOLCALL']._serialized_end=734 - _globals['_STEP_EXECUTE']._serialized_start=736 - _globals['_STEP_EXECUTE']._serialized_end=767 - _globals['_STEP_RESULTS']._serialized_start=770 - _globals['_STEP_RESULTS']._serialized_end=959 - _globals['_STEP_METRICS']._serialized_start=961 - _globals['_STEP_METRICS']._serialized_end=1042 - _globals['_STEP_STEPSTATE']._serialized_start=1044 - _globals['_STEP_STEPSTATE']._serialized_end=1131 + _globals['_STEP']._serialized_start=143 + _globals['_STEP']._serialized_end=852 + _globals['_STEP_THINKING']._serialized_start=465 + _globals['_STEP_THINKING']._serialized_end=518 + _globals['_STEP_TOOLCALL']._serialized_start=520 + _globals['_STEP_TOOLCALL']._serialized_end=630 + _globals['_STEP_EXECUTE']._serialized_start=632 + _globals['_STEP_EXECUTE']._serialized_end=641 + _globals['_STEP_RESULTS']._serialized_start=643 + _globals['_STEP_RESULTS']._serialized_end=720 + _globals['_STEP_STEPSTATE']._serialized_start=722 + _globals['_STEP_STEPSTATE']._serialized_end=809 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/task_step_pb2.pyi b/truffle/os/task_step_pb2.pyi index 5b4164a..9f4c8da 100644 --- a/truffle/os/task_step_pb2.pyi +++ b/truffle/os/task_step_pb2.pyi @@ -1,6 +1,6 @@ from truffle.os import task_user_response_pb2 as _task_user_response_pb2 from truffle.common import content_pb2 as _content_pb2 -from truffle.infer import usage_pb2 as _usage_pb2 +from google.protobuf import timestamp_pb2 as _timestamp_pb2 from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor @@ -13,7 +13,7 @@ from truffle.common.content_pb2 import WebComponent as WebComponent DESCRIPTOR: _descriptor.FileDescriptor class Step(_message.Message): - __slots__ = ("state", "user_response", "thinking", "tool_calls", "execution", "results", "metrics", "model_uuid") + __slots__ = ("state", "user_response", "thinking", "tool_calls", "execution", "results", "model_uuid") class StepState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): __slots__ = () STEP_INVALID: _ClassVar[Step.StepState] @@ -25,51 +25,37 @@ class Step(_message.Message): STEP_EXECUTING: Step.StepState STEP_RESULT: Step.StepState class Thinking(_message.Message): - __slots__ = ("cot_chunks", "cot_summaries", "raw_output", "thinking_finished") + __slots__ = ("cot_chunks", "cot_summaries") COT_CHUNKS_FIELD_NUMBER: _ClassVar[int] COT_SUMMARIES_FIELD_NUMBER: _ClassVar[int] - RAW_OUTPUT_FIELD_NUMBER: _ClassVar[int] - THINKING_FINISHED_FIELD_NUMBER: _ClassVar[int] cot_chunks: _containers.RepeatedScalarFieldContainer[str] cot_summaries: _containers.RepeatedScalarFieldContainer[str] - raw_output: str - thinking_finished: bool - def __init__(self, cot_chunks: _Optional[_Iterable[str]] = ..., cot_summaries: _Optional[_Iterable[str]] = ..., raw_output: _Optional[str] = ..., thinking_finished: bool = ...) -> None: ... + def __init__(self, cot_chunks: _Optional[_Iterable[str]] = ..., cot_summaries: _Optional[_Iterable[str]] = ...) -> None: ... class ToolCall(_message.Message): - __slots__ = ("tool_name", "summary") + __slots__ = ("tool_name", "summary", "args") TOOL_NAME_FIELD_NUMBER: _ClassVar[int] SUMMARY_FIELD_NUMBER: _ClassVar[int] + ARGS_FIELD_NUMBER: _ClassVar[int] tool_name: str summary: str - def __init__(self, tool_name: _Optional[str] = ..., summary: _Optional[str] = ...) -> None: ... + args: str + def __init__(self, tool_name: _Optional[str] = ..., summary: _Optional[str] = ..., args: _Optional[str] = ...) -> None: ... class Execute(_message.Message): - __slots__ = ("tool_updates",) - TOOL_UPDATES_FIELD_NUMBER: _ClassVar[int] - tool_updates: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, tool_updates: _Optional[_Iterable[str]] = ...) -> None: ... + __slots__ = () + def __init__(self) -> None: ... class Results(_message.Message): - __slots__ = ("summary", "content", "content_incomplete", "web") + __slots__ = ("summary", "content") SUMMARY_FIELD_NUMBER: _ClassVar[int] CONTENT_FIELD_NUMBER: _ClassVar[int] - CONTENT_INCOMPLETE_FIELD_NUMBER: _ClassVar[int] - WEB_FIELD_NUMBER: _ClassVar[int] summary: str content: str - content_incomplete: bool - web: _content_pb2.WebComponent - def __init__(self, summary: _Optional[str] = ..., content: _Optional[str] = ..., content_incomplete: bool = ..., web: _Optional[_Union[_content_pb2.WebComponent, _Mapping]] = ...) -> None: ... - class Metrics(_message.Message): - __slots__ = ("inference_usage",) - INFERENCE_USAGE_FIELD_NUMBER: _ClassVar[int] - inference_usage: _usage_pb2.Usage - def __init__(self, inference_usage: _Optional[_Union[_usage_pb2.Usage, _Mapping]] = ...) -> None: ... + def __init__(self, summary: _Optional[str] = ..., content: _Optional[str] = ...) -> None: ... STATE_FIELD_NUMBER: _ClassVar[int] USER_RESPONSE_FIELD_NUMBER: _ClassVar[int] THINKING_FIELD_NUMBER: _ClassVar[int] TOOL_CALLS_FIELD_NUMBER: _ClassVar[int] EXECUTION_FIELD_NUMBER: _ClassVar[int] RESULTS_FIELD_NUMBER: _ClassVar[int] - METRICS_FIELD_NUMBER: _ClassVar[int] MODEL_UUID_FIELD_NUMBER: _ClassVar[int] state: Step.StepState user_response: _task_user_response_pb2.PendingUserResponse @@ -77,6 +63,5 @@ class Step(_message.Message): tool_calls: _containers.RepeatedCompositeFieldContainer[Step.ToolCall] execution: Step.Execute results: Step.Results - metrics: Step.Metrics model_uuid: str - def __init__(self, state: _Optional[_Union[Step.StepState, str]] = ..., user_response: _Optional[_Union[_task_user_response_pb2.PendingUserResponse, _Mapping]] = ..., thinking: _Optional[_Union[Step.Thinking, _Mapping]] = ..., tool_calls: _Optional[_Iterable[_Union[Step.ToolCall, _Mapping]]] = ..., execution: _Optional[_Union[Step.Execute, _Mapping]] = ..., results: _Optional[_Union[Step.Results, _Mapping]] = ..., metrics: _Optional[_Union[Step.Metrics, _Mapping]] = ..., model_uuid: _Optional[str] = ...) -> None: ... + def __init__(self, state: _Optional[_Union[Step.StepState, str]] = ..., user_response: _Optional[_Union[_task_user_response_pb2.PendingUserResponse, _Mapping]] = ..., thinking: _Optional[_Union[Step.Thinking, _Mapping]] = ..., tool_calls: _Optional[_Iterable[_Union[Step.ToolCall, _Mapping]]] = ..., execution: _Optional[_Union[Step.Execute, _Mapping]] = ..., results: _Optional[_Union[Step.Results, _Mapping]] = ..., model_uuid: _Optional[str] = ...) -> None: ... diff --git a/truffle/os/task_step_pb2_grpc.py b/truffle/os/task_step_pb2_grpc.py index 1937b34..65fe0d4 100644 --- a/truffle/os/task_step_pb2_grpc.py +++ b/truffle/os/task_step_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_step_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_step_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_target_pb2.py b/truffle/os/task_target_pb2.py index 6596839..2d85791 100644 --- a/truffle/os/task_target_pb2.py +++ b/truffle/os/task_target_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_target.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_target.proto' ) diff --git a/truffle/os/task_target_pb2_grpc.py b/truffle/os/task_target_pb2_grpc.py index 400a908..daa929f 100644 --- a/truffle/os/task_target_pb2_grpc.py +++ b/truffle/os/task_target_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_target_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_target_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/task_user_response_pb2.py b/truffle/os/task_user_response_pb2.py index 181a0e5..238fc45 100644 --- a/truffle/os/task_user_response_pb2.py +++ b/truffle/os/task_user_response_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/task_user_response.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/task_user_response.proto' ) diff --git a/truffle/os/task_user_response_pb2_grpc.py b/truffle/os/task_user_response_pb2_grpc.py index f23ce5a..5df8dd7 100644 --- a/truffle/os/task_user_response_pb2_grpc.py +++ b/truffle/os/task_user_response_pb2_grpc.py @@ -4,7 +4,7 @@ import warnings -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -17,7 +17,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/task_user_response_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/task_user_response_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' diff --git a/truffle/os/truffleos_pb2.py b/truffle/os/truffleos_pb2.py index 936eea4..5d300c4 100644 --- a/truffle/os/truffleos_pb2.py +++ b/truffle/os/truffleos_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: truffle/os/truffleos.proto -# Protobuf Python Version: 6.30.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 6, - 30, - 0, + 31, + 1, '', 'truffle/os/truffleos.proto' ) @@ -98,8 +98,13 @@ from truffle.os import task_search_pb2 as truffle_dot_os_dot_task__search__pb2 from truffle.os import builder_pb2 as truffle_dot_os_dot_builder__pb2 from truffle.os import background_feed_queries_pb2 as truffle_dot_os_dot_background__feed__queries__pb2 +from truffle.os import proactivity_pb2 as truffle_dot_os_dot_proactivity__pb2 from truffle.os import app_queries_pb2 as truffle_dot_os_dot_app__queries__pb2 from truffle.os import installer_pb2 as truffle_dot_os_dot_installer__pb2 +try: + truffle_dot_app_dot_app__pb2 = truffle_dot_os_dot_installer__pb2.truffle_dot_app_dot_app__pb2 +except AttributeError: + truffle_dot_app_dot_app__pb2 = truffle_dot_os_dot_installer__pb2.truffle.app.app_pb2 from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 from truffle.os.hardware_stats_pb2 import * @@ -117,9 +122,10 @@ from truffle.os.task_search_pb2 import * from truffle.os.builder_pb2 import * from truffle.os.background_feed_queries_pb2 import * +from truffle.os.proactivity_pb2 import * from truffle.os.app_queries_pb2 import * -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1atruffle/os/truffleos.proto\x12\ntruffle.os\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1ftruffle/os/hardware_stats.proto\x1a!truffle/os/hardware_control.proto\x1a truffle/os/system_settings.proto\x1a\x1ctruffle/os/system_info.proto\x1a\x1dtruffle/os/notification.proto\x1a\x1ftruffle/os/client_session.proto\x1a\x1ctruffle/os/client_user.proto\x1a\x1dtruffle/os/client_state.proto\x1a\x15truffle/os/task.proto\x1a\x1dtruffle/os/task_queries.proto\x1a\x1dtruffle/os/task_actions.proto\x1a#truffle/os/task_user_response.proto\x1a\x1ctruffle/os/task_search.proto\x1a\x18truffle/os/builder.proto\x1a(truffle/os/background_feed_queries.proto\x1a\x1ctruffle/os/app_queries.proto\x1a\x1atruffle/os/installer.proto\x1a\x1cgoogle/api/annotations.proto2\xc9$\n\tTruffleOS\x12m\n\x0e\x41pps_DeleteApp\x12\x1c.truffle.os.DeleteAppRequest\x1a\x1d.truffle.os.DeleteAppResponse\"\x1e\x82\xd3\xe4\x93\x02\x18*\x16/v1/os/apps/{app_uuid}\x12\x81\x01\n\x12\x41pps_GetBackground\x12$.truffle.os.GetBackgroundAppsRequest\x1a%.truffle.os.GetBackgroundAppsResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/v1/os/apps/background\x12\x81\x01\n\x12\x41pps_GetForeground\x12$.truffle.os.GetForegroundAppsRequest\x1a%.truffle.os.GetForegroundAppsResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/v1/os/apps/foreground\x12\x65\n\x0b\x41pps_GetAll\x12\x1d.truffle.os.GetAllAppsRequest\x1a\x1e.truffle.os.GetAllAppsResponse\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/os/apps/all\x12T\n\x0f\x41pps_InstallApp\x12\x1d.truffle.os.AppInstallRequest\x1a\x1e.truffle.os.AppInstallResponse(\x01\x30\x01\x12\x8d\x01\n\x12\x42\x61\x63kground_GetFeed\x12$.truffle.os.GetBackgroundFeedRequest\x1a%.truffle.os.GetBackgroundFeedResponse\"*\x82\xd3\xe4\x93\x02$\"\x1f/v1/os/apps/background/feed:get:\x01*\x12\xa8\x01\n\x1f\x42\x61\x63kground_GetLatestFeedEntryID\x12\'.truffle.os.GetLatestFeedEntryIDRequest\x1a(.truffle.os.GetLatestFeedEntryIDResponse\"2\x82\xd3\xe4\x93\x02,\"\'/v1/os/apps/background/latestfeedid:get:\x01*\x12\xa6\x01\n\x18\x42\x61\x63kground_LikeFeedEntry\x12*.truffle.os.LikeBackgroundFeedEntryRequest\x1a+.truffle.os.LikeBackgroundFeedEntryResponse\"1\x82\xd3\xe4\x93\x02+\"&/v1/os/apps/background/feed/entry:like:\x01*\x12\xa7\x01\n\x1d\x42\x61\x63kground_SubmitFeedFeedback\x12).truffle.os.BackgroundFeedFeedbackRequest\x1a*.truffle.os.BackgroundFeedFeedbackResponse\"/\x82\xd3\xe4\x93\x02)\"$/v1/os/apps/background/feed:feedback:\x01*\x12\x90\x01\n\x19\x42uilder_StartBuildSession\x12$.truffle.os.StartBuildSessionRequest\x1a%.truffle.os.StartBuildSessionResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/os/builder/builds:start:\x01*\x12\x94\x01\n\x1a\x42uilder_FinishBuildSession\x12%.truffle.os.FinishBuildSessionRequest\x1a&.truffle.os.FinishBuildSessionResponse\"\'\x82\xd3\xe4\x93\x02!\"\x1c/v1/os/builder/builds:finish:\x01*\x12\x83\x01\n\x16\x43lient_RegisterNewUser\x12\".truffle.os.RegisterNewUserRequest\x1a#.truffle.os.RegisterNewUserResponse\" \x82\xd3\xe4\x93\x02\x1a\"\x15/v1/os/users:register:\x01*\x12\x8f\x01\n\x19\x43lient_RegisterNewSession\x12%.truffle.os.RegisterNewSessionRequest\x1a&.truffle.os.RegisterNewSessionResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x18/v1/os/sessions:register:\x01*\x12\x82\x01\n\x15\x43lient_UserIDForToken\x12!.truffle.os.UserIDForTokenRequest\x1a\".truffle.os.UserIDForTokenResponse\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x1a/v1/os/tokens/{token}/user\x12\x8b\x01\n#Client_VerifyNewSessionRegistration\x12#.truffle.os.VerifyNewSessionRequest\x1a\x1c.truffle.os.NewSessionStatus\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/os/sessions:verify:\x01*\x12x\n\x1b\x43lient_GetUserRecoveryCodes\x12\x16.google.protobuf.Empty\x1a\x1d.truffle.os.UserRecoveryCodes\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x1a/v1/os/users/recoveryCodes\x12\x87\x01\n\x18\x43lient_UpdateClientState\x12$.truffle.os.UpdateClientStateRequest\x1a%.truffle.os.UpdateClientStateResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/os/client/state:\x01*\x12\x81\x01\n\x15\x43lient_GetClientState\x12!.truffle.os.GetClientStateRequest\x1a\".truffle.os.GetClientStateResponse\"!\x82\xd3\xe4\x93\x02\x1b\x12\x19/v1/os/client/state/{key}\x12\x88\x01\n\x19\x43lient_GetAllClientStates\x12%.truffle.os.GetAllClientStatesRequest\x1a&.truffle.os.GetAllClientStatesResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/v1/os/client/states\x12\x8b\x01\n\x18SubscribeToNotifications\x12+.truffle.os.SubscribeToNotificationsRequest\x1a\x18.truffle.os.Notification\"&\x82\xd3\xe4\x93\x02 \x12\x1e/v1/os/notifications:subscribe0\x01\x12o\n\x11Hardware_GetStats\x12 .truffle.os.HardwareStatsRequest\x1a\x19.truffle.os.HardwareStats\"\x1d\x82\xd3\xe4\x93\x02\x17\x12\x15/v1/os/hardware/stats\x12|\n\x15Hardware_StatsUpdates\x12 .truffle.os.HardwareStatsRequest\x1a\x19.truffle.os.HardwareStats\"$\x82\xd3\xe4\x93\x02\x1e\x12\x1c/v1/os/hardware/stats:stream0\x01\x12\x93\x01\n\x15Hardware_PowerControl\x12\'.truffle.os.HardwarePowerControlRequest\x1a(.truffle.os.HardwarePowerControlResponse\"\'\x82\xd3\xe4\x93\x02!\"\x1c/v1/os/hardware/powerControl:\x01*\x12i\n\x0cSystem_GetID\x12\x1e.truffle.os.SystemGetIDRequest\x1a\x1f.truffle.os.SystemGetIDResponse\"\x18\x82\xd3\xe4\x93\x02\x12\x12\x10/v1/os/system/id\x12h\n\x12System_GetSettings\x12\x16.google.protobuf.Empty\x1a\x1a.truffle.os.SystemSettings\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/v1/os/system/settings\x12o\n\x12System_SetSettings\x12\x1a.truffle.os.SystemSettings\x1a\x1a.truffle.os.SystemSettings\"!\x82\xd3\xe4\x93\x02\x1b\x32\x16/v1/os/system/settings:\x01*\x12\\\n\x0eSystem_GetInfo\x12\x16.google.protobuf.Empty\x1a\x16.truffle.os.SystemInfo\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/os/system/info\x12\x8e\x01\n\x15System_CheckForUpdate\x12\'.truffle.os.SystemCheckForUpdateRequest\x1a(.truffle.os.SystemCheckForUpdateResponse\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x1a/v1/os/system/check-update\x12j\n\rTask_OpenTask\x12\x1b.truffle.os.OpenTaskRequest\x1a\x1c.truffle.os.TaskStreamUpdate\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/os/tasks:open:\x01*0\x01\x12\x83\x01\n\x12Task_InterruptTask\x12 .truffle.os.InterruptTaskRequest\x1a\x1e.truffle.os.TaskActionResponse\"+\x82\xd3\xe4\x93\x02%\" /v1/os/tasks/{task_id}:interrupt:\x01*\x12\x81\x01\n\x12Task_RespondToTask\x12 .truffle.os.RespondToTaskRequest\x1a\x1e.truffle.os.TaskActionResponse\")\x82\xd3\xe4\x93\x02#\"\x1e/v1/os/tasks/{task_id}:respond:\x01*\x12\x9e\x01\n\x15Task_SetAvailableApps\x12\'.truffle.os.TaskSetAvailableAppsRequest\x1a(.truffle.os.TaskSetAvailableAppsResponse\"2\x82\xd3\xe4\x93\x02,\"\'/v1/os/tasks/{task_id}:setAvailableApps:\x01*\x12s\n\x10Task_SearchTasks\x12\x1e.truffle.os.SearchTasksRequest\x1a\x1f.truffle.os.SearchTasksResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/os/tasks:search:\x01*\x12\x63\n\rTask_GetTasks\x12\x1b.truffle.os.GetTasksRequest\x1a\x10.truffle.os.Task\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/os/tasks:streamGet:\x01*0\x01\x12\x62\n\x0fTask_GetOneTask\x12\x1d.truffle.os.GetOneTaskRequest\x1a\x10.truffle.os.Task\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/v1/os/tasks/{task_id}\x12u\n\x11Task_GetTaskInfos\x12\x1f.truffle.os.GetTaskInfosRequest\x1a .truffle.os.GetTaskInfosResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x12/v1/os/tasks/infos:\x01*P\x01P\x02P\x03P\x04P\x05P\x06P\x07P\x08P\tP\nP\x0bP\x0cP\rP\x0eP\x0fP\x10\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1atruffle/os/truffleos.proto\x12\ntruffle.os\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1ftruffle/os/hardware_stats.proto\x1a!truffle/os/hardware_control.proto\x1a truffle/os/system_settings.proto\x1a\x1ctruffle/os/system_info.proto\x1a\x1dtruffle/os/notification.proto\x1a\x1ftruffle/os/client_session.proto\x1a\x1ctruffle/os/client_user.proto\x1a\x1dtruffle/os/client_state.proto\x1a\x15truffle/os/task.proto\x1a\x1dtruffle/os/task_queries.proto\x1a\x1dtruffle/os/task_actions.proto\x1a#truffle/os/task_user_response.proto\x1a\x1ctruffle/os/task_search.proto\x1a\x18truffle/os/builder.proto\x1a(truffle/os/background_feed_queries.proto\x1a\x1ctruffle/os/proactivity.proto\x1a\x1ctruffle/os/app_queries.proto\x1a\x1atruffle/os/installer.proto\x1a\x1cgoogle/api/annotations.proto2\xfd#\n\tTruffleOS\x12m\n\x0e\x41pps_DeleteApp\x12\x1c.truffle.os.DeleteAppRequest\x1a\x1d.truffle.os.DeleteAppResponse\"\x1e\x82\xd3\xe4\x93\x02\x18*\x16/v1/os/apps/{app_uuid}\x12\x65\n\x0b\x41pps_GetAll\x12\x1d.truffle.os.GetAllAppsRequest\x1a\x1e.truffle.os.GetAllAppsResponse\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/os/apps/all\x12T\n\x0f\x41pps_InstallApp\x12\x1d.truffle.os.AppInstallRequest\x1a\x1e.truffle.os.AppInstallResponse(\x01\x30\x01\x12\x8d\x01\n\x12\x42\x61\x63kground_GetFeed\x12$.truffle.os.GetBackgroundFeedRequest\x1a%.truffle.os.GetBackgroundFeedResponse\"*\x82\xd3\xe4\x93\x02$\"\x1f/v1/os/apps/background/feed:get:\x01*\x12\xa8\x01\n\x1f\x42\x61\x63kground_GetLatestFeedEntryID\x12\'.truffle.os.GetLatestFeedEntryIDRequest\x1a(.truffle.os.GetLatestFeedEntryIDResponse\"2\x82\xd3\xe4\x93\x02,\"\'/v1/os/apps/background/latestfeedid:get:\x01*\x12\xab\x01\n!Background_ApproveProactiveAction\x12).truffle.os.ApproveProactiveActionRequest\x1a*.truffle.os.ApproveProactiveActionResponse\"/\x82\xd3\xe4\x93\x02)\"$/v1/os/apps/background/approveAction:\x01*\x12\xa7\x01\n Background_CancelProactiveAction\x12(.truffle.os.CancelProactiveActionRequest\x1a).truffle.os.CancelProactiveActionResponse\".\x82\xd3\xe4\x93\x02(\"#/v1/os/apps/background/cancelAction:\x01*\x12\x90\x01\n\x19\x42uilder_StartBuildSession\x12$.truffle.os.StartBuildSessionRequest\x1a%.truffle.os.StartBuildSessionResponse\"&\x82\xd3\xe4\x93\x02 \"\x1b/v1/os/builder/builds:start:\x01*\x12\x94\x01\n\x1a\x42uilder_FinishBuildSession\x12%.truffle.os.FinishBuildSessionRequest\x1a&.truffle.os.FinishBuildSessionResponse\"\'\x82\xd3\xe4\x93\x02!\"\x1c/v1/os/builder/builds:finish:\x01*\x12\x83\x01\n\x16\x43lient_RegisterNewUser\x12\".truffle.os.RegisterNewUserRequest\x1a#.truffle.os.RegisterNewUserResponse\" \x82\xd3\xe4\x93\x02\x1a\"\x15/v1/os/users:register:\x01*\x12\x8f\x01\n\x19\x43lient_RegisterNewSession\x12%.truffle.os.RegisterNewSessionRequest\x1a&.truffle.os.RegisterNewSessionResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x18/v1/os/sessions:register:\x01*\x12\x82\x01\n\x15\x43lient_UserIDForToken\x12!.truffle.os.UserIDForTokenRequest\x1a\".truffle.os.UserIDForTokenResponse\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x1a/v1/os/tokens/{token}/user\x12\x8b\x01\n#Client_VerifyNewSessionRegistration\x12#.truffle.os.VerifyNewSessionRequest\x1a\x1c.truffle.os.NewSessionStatus\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/os/sessions:verify:\x01*\x12x\n\x1b\x43lient_GetUserRecoveryCodes\x12\x16.google.protobuf.Empty\x1a\x1d.truffle.os.UserRecoveryCodes\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x1a/v1/os/users/recoveryCodes\x12\x87\x01\n\x18\x43lient_UpdateClientState\x12$.truffle.os.UpdateClientStateRequest\x1a%.truffle.os.UpdateClientStateResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/os/client/state:\x01*\x12\x81\x01\n\x15\x43lient_GetClientState\x12!.truffle.os.GetClientStateRequest\x1a\".truffle.os.GetClientStateResponse\"!\x82\xd3\xe4\x93\x02\x1b\x12\x19/v1/os/client/state/{key}\x12\x88\x01\n\x19\x43lient_GetAllClientStates\x12%.truffle.os.GetAllClientStatesRequest\x1a&.truffle.os.GetAllClientStatesResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/v1/os/client/states\x12\x8b\x01\n\x18SubscribeToNotifications\x12+.truffle.os.SubscribeToNotificationsRequest\x1a\x18.truffle.os.Notification\"&\x82\xd3\xe4\x93\x02 \x12\x1e/v1/os/notifications:subscribe0\x01\x12o\n\x11Hardware_GetStats\x12 .truffle.os.HardwareStatsRequest\x1a\x19.truffle.os.HardwareStats\"\x1d\x82\xd3\xe4\x93\x02\x17\x12\x15/v1/os/hardware/stats\x12|\n\x15Hardware_StatsUpdates\x12 .truffle.os.HardwareStatsRequest\x1a\x19.truffle.os.HardwareStats\"$\x82\xd3\xe4\x93\x02\x1e\x12\x1c/v1/os/hardware/stats:stream0\x01\x12\x93\x01\n\x15Hardware_PowerControl\x12\'.truffle.os.HardwarePowerControlRequest\x1a(.truffle.os.HardwarePowerControlResponse\"\'\x82\xd3\xe4\x93\x02!\"\x1c/v1/os/hardware/powerControl:\x01*\x12i\n\x0cSystem_GetID\x12\x1e.truffle.os.SystemGetIDRequest\x1a\x1f.truffle.os.SystemGetIDResponse\"\x18\x82\xd3\xe4\x93\x02\x12\x12\x10/v1/os/system/id\x12h\n\x12System_GetSettings\x12\x16.google.protobuf.Empty\x1a\x1a.truffle.os.SystemSettings\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/v1/os/system/settings\x12o\n\x12System_SetSettings\x12\x1a.truffle.os.SystemSettings\x1a\x1a.truffle.os.SystemSettings\"!\x82\xd3\xe4\x93\x02\x1b\x32\x16/v1/os/system/settings:\x01*\x12\\\n\x0eSystem_GetInfo\x12\x16.google.protobuf.Empty\x1a\x16.truffle.os.SystemInfo\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/os/system/info\x12\x8e\x01\n\x15System_CheckForUpdate\x12\'.truffle.os.SystemCheckForUpdateRequest\x1a(.truffle.os.SystemCheckForUpdateResponse\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x1a/v1/os/system/check-update\x12\xb4\x01\n\x1dTask_TestExternalToolProvider\x12/.truffle.os.TaskTestExternalToolProviderRequest\x1a\x30.truffle.os.TaskTestExternalToolProviderResponse\"0\x82\xd3\xe4\x93\x02*\"%/v1/os/tasks:testExternalToolProvider:\x01*\x12j\n\rTask_OpenTask\x12\x1b.truffle.os.OpenTaskRequest\x1a\x1c.truffle.os.TaskStreamUpdate\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/os/tasks:open:\x01*0\x01\x12\x83\x01\n\x12Task_InterruptTask\x12 .truffle.os.InterruptTaskRequest\x1a\x1e.truffle.os.TaskActionResponse\"+\x82\xd3\xe4\x93\x02%\" /v1/os/tasks/{task_id}:interrupt:\x01*\x12\x81\x01\n\x12Task_RespondToTask\x12 .truffle.os.RespondToTaskRequest\x1a\x1e.truffle.os.TaskActionResponse\")\x82\xd3\xe4\x93\x02#\"\x1e/v1/os/tasks/{task_id}:respond:\x01*\x12\x9e\x01\n\x15Task_SetAvailableApps\x12\'.truffle.os.TaskSetAvailableAppsRequest\x1a(.truffle.os.TaskSetAvailableAppsResponse\"2\x82\xd3\xe4\x93\x02,\"\'/v1/os/tasks/{task_id}:setAvailableApps:\x01*\x12s\n\x10Task_SearchTasks\x12\x1e.truffle.os.SearchTasksRequest\x1a\x1f.truffle.os.SearchTasksResponse\"\x1e\x82\xd3\xe4\x93\x02\x18\"\x13/v1/os/tasks:search:\x01*\x12\x63\n\rTask_GetTasks\x12\x1b.truffle.os.GetTasksRequest\x1a\x10.truffle.os.Task\"!\x82\xd3\xe4\x93\x02\x1b\"\x16/v1/os/tasks:streamGet:\x01*0\x01\x12\x62\n\x0fTask_GetOneTask\x12\x1d.truffle.os.GetOneTaskRequest\x1a\x10.truffle.os.Task\"\x1e\x82\xd3\xe4\x93\x02\x18\x12\x16/v1/os/tasks/{task_id}\x12u\n\x11Task_GetTaskInfos\x12\x1f.truffle.os.GetTaskInfosRequest\x1a .truffle.os.GetTaskInfosResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x12/v1/os/tasks/infos:\x01*P\x01P\x02P\x03P\x04P\x05P\x06P\x07P\x08P\tP\nP\x0bP\x0cP\rP\x0eP\x0fP\x10P\x11\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -128,20 +134,16 @@ DESCRIPTOR._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['Apps_DeleteApp']._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['Apps_DeleteApp']._serialized_options = b'\202\323\344\223\002\030*\026/v1/os/apps/{app_uuid}' - _globals['_TRUFFLEOS'].methods_by_name['Apps_GetBackground']._loaded_options = None - _globals['_TRUFFLEOS'].methods_by_name['Apps_GetBackground']._serialized_options = b'\202\323\344\223\002\030\022\026/v1/os/apps/background' - _globals['_TRUFFLEOS'].methods_by_name['Apps_GetForeground']._loaded_options = None - _globals['_TRUFFLEOS'].methods_by_name['Apps_GetForeground']._serialized_options = b'\202\323\344\223\002\030\022\026/v1/os/apps/foreground' _globals['_TRUFFLEOS'].methods_by_name['Apps_GetAll']._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['Apps_GetAll']._serialized_options = b'\202\323\344\223\002\021\022\017/v1/os/apps/all' _globals['_TRUFFLEOS'].methods_by_name['Background_GetFeed']._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['Background_GetFeed']._serialized_options = b'\202\323\344\223\002$\"\037/v1/os/apps/background/feed:get:\001*' _globals['_TRUFFLEOS'].methods_by_name['Background_GetLatestFeedEntryID']._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['Background_GetLatestFeedEntryID']._serialized_options = b'\202\323\344\223\002,\"\'/v1/os/apps/background/latestfeedid:get:\001*' - _globals['_TRUFFLEOS'].methods_by_name['Background_LikeFeedEntry']._loaded_options = None - _globals['_TRUFFLEOS'].methods_by_name['Background_LikeFeedEntry']._serialized_options = b'\202\323\344\223\002+\"&/v1/os/apps/background/feed/entry:like:\001*' - _globals['_TRUFFLEOS'].methods_by_name['Background_SubmitFeedFeedback']._loaded_options = None - _globals['_TRUFFLEOS'].methods_by_name['Background_SubmitFeedFeedback']._serialized_options = b'\202\323\344\223\002)\"$/v1/os/apps/background/feed:feedback:\001*' + _globals['_TRUFFLEOS'].methods_by_name['Background_ApproveProactiveAction']._loaded_options = None + _globals['_TRUFFLEOS'].methods_by_name['Background_ApproveProactiveAction']._serialized_options = b'\202\323\344\223\002)\"$/v1/os/apps/background/approveAction:\001*' + _globals['_TRUFFLEOS'].methods_by_name['Background_CancelProactiveAction']._loaded_options = None + _globals['_TRUFFLEOS'].methods_by_name['Background_CancelProactiveAction']._serialized_options = b'\202\323\344\223\002(\"#/v1/os/apps/background/cancelAction:\001*' _globals['_TRUFFLEOS'].methods_by_name['Builder_StartBuildSession']._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['Builder_StartBuildSession']._serialized_options = b'\202\323\344\223\002 \"\033/v1/os/builder/builds:start:\001*' _globals['_TRUFFLEOS'].methods_by_name['Builder_FinishBuildSession']._loaded_options = None @@ -180,6 +182,8 @@ _globals['_TRUFFLEOS'].methods_by_name['System_GetInfo']._serialized_options = b'\202\323\344\223\002\024\022\022/v1/os/system/info' _globals['_TRUFFLEOS'].methods_by_name['System_CheckForUpdate']._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['System_CheckForUpdate']._serialized_options = b'\202\323\344\223\002\034\022\032/v1/os/system/check-update' + _globals['_TRUFFLEOS'].methods_by_name['Task_TestExternalToolProvider']._loaded_options = None + _globals['_TRUFFLEOS'].methods_by_name['Task_TestExternalToolProvider']._serialized_options = b'\202\323\344\223\002*\"%/v1/os/tasks:testExternalToolProvider:\001*' _globals['_TRUFFLEOS'].methods_by_name['Task_OpenTask']._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['Task_OpenTask']._serialized_options = b'\202\323\344\223\002\026\"\021/v1/os/tasks:open:\001*' _globals['_TRUFFLEOS'].methods_by_name['Task_InterruptTask']._loaded_options = None @@ -196,6 +200,6 @@ _globals['_TRUFFLEOS'].methods_by_name['Task_GetOneTask']._serialized_options = b'\202\323\344\223\002\030\022\026/v1/os/tasks/{task_id}' _globals['_TRUFFLEOS'].methods_by_name['Task_GetTaskInfos']._loaded_options = None _globals['_TRUFFLEOS'].methods_by_name['Task_GetTaskInfos']._serialized_options = b'\202\323\344\223\002\027\"\022/v1/os/tasks/infos:\001*' - _globals['_TRUFFLEOS']._serialized_start=637 - _globals['_TRUFFLEOS']._serialized_end=5318 + _globals['_TRUFFLEOS']._serialized_start=667 + _globals['_TRUFFLEOS']._serialized_end=5272 # @@protoc_insertion_point(module_scope) diff --git a/truffle/os/truffleos_pb2.pyi b/truffle/os/truffleos_pb2.pyi index da187f1..b3e4a9a 100644 --- a/truffle/os/truffleos_pb2.pyi +++ b/truffle/os/truffleos_pb2.pyi @@ -24,8 +24,10 @@ from truffle.os import task_user_response_pb2 as _task_user_response_pb2_1_1 from truffle.os import task_search_pb2 as _task_search_pb2 from truffle.os import builder_pb2 as _builder_pb2 from truffle.os import background_feed_queries_pb2 as _background_feed_queries_pb2 +from truffle.os import proactivity_pb2 as _proactivity_pb2 from truffle.os import app_queries_pb2 as _app_queries_pb2 from truffle.os import installer_pb2 as _installer_pb2 +from truffle.app import app_pb2 as _app_pb2 from google.api import annotations_pb2 as _annotations_pb2 from google.protobuf import descriptor as _descriptor from typing import ClassVar as _ClassVar @@ -63,6 +65,7 @@ from truffle.os.client_state_pb2 import GetAllClientStatesResponse as GetAllClie from truffle.os.task_pb2 import Task as Task from truffle.os.task_pb2 import TasksList as TasksList from truffle.os.task_pb2 import TaskNode as TaskNode +from truffle.os.task_pb2 import StreamingTaskStepResult as StreamingTaskStepResult from truffle.os.task_pb2 import TaskStreamUpdate as TaskStreamUpdate from truffle.os.task_queries_pb2 import GetTasksRequest as GetTasksRequest from truffle.os.task_queries_pb2 import GetOneTaskRequest as GetOneTaskRequest @@ -89,16 +92,13 @@ from truffle.os.builder_pb2 import BuildSessionError as BuildSessionError from truffle.os.builder_pb2 import FinishBuildSessionResponse as FinishBuildSessionResponse from truffle.os.background_feed_queries_pb2 import GetBackgroundFeedRequest as GetBackgroundFeedRequest from truffle.os.background_feed_queries_pb2 import GetBackgroundFeedResponse as GetBackgroundFeedResponse -from truffle.os.background_feed_queries_pb2 import LikeBackgroundFeedEntryRequest as LikeBackgroundFeedEntryRequest -from truffle.os.background_feed_queries_pb2 import LikeBackgroundFeedEntryResponse as LikeBackgroundFeedEntryResponse from truffle.os.background_feed_queries_pb2 import GetLatestFeedEntryIDRequest as GetLatestFeedEntryIDRequest from truffle.os.background_feed_queries_pb2 import GetLatestFeedEntryIDResponse as GetLatestFeedEntryIDResponse -from truffle.os.background_feed_queries_pb2 import BackgroundFeedFeedbackRequest as BackgroundFeedFeedbackRequest -from truffle.os.background_feed_queries_pb2 import BackgroundFeedFeedbackResponse as BackgroundFeedFeedbackResponse -from truffle.os.app_queries_pb2 import GetForegroundAppsRequest as GetForegroundAppsRequest -from truffle.os.app_queries_pb2 import GetForegroundAppsResponse as GetForegroundAppsResponse -from truffle.os.app_queries_pb2 import GetBackgroundAppsRequest as GetBackgroundAppsRequest -from truffle.os.app_queries_pb2 import GetBackgroundAppsResponse as GetBackgroundAppsResponse +from truffle.os.proactivity_pb2 import ProactiveAction as ProactiveAction +from truffle.os.proactivity_pb2 import ApproveProactiveActionRequest as ApproveProactiveActionRequest +from truffle.os.proactivity_pb2 import ApproveProactiveActionResponse as ApproveProactiveActionResponse +from truffle.os.proactivity_pb2 import CancelProactiveActionRequest as CancelProactiveActionRequest +from truffle.os.proactivity_pb2 import CancelProactiveActionResponse as CancelProactiveActionResponse from truffle.os.app_queries_pb2 import GetAllAppsRequest as GetAllAppsRequest from truffle.os.app_queries_pb2 import GetAllAppsResponse as GetAllAppsResponse from truffle.os.app_queries_pb2 import DeleteAppRequest as DeleteAppRequest diff --git a/truffle/os/truffleos_pb2_grpc.py b/truffle/os/truffleos_pb2_grpc.py index ac95a7b..86946ec 100644 --- a/truffle/os/truffleos_pb2_grpc.py +++ b/truffle/os/truffleos_pb2_grpc.py @@ -14,6 +14,7 @@ from truffle.os import hardware_stats_pb2 as truffle_dot_os_dot_hardware__stats__pb2 from truffle.os import installer_pb2 as truffle_dot_os_dot_installer__pb2 from truffle.os import notification_pb2 as truffle_dot_os_dot_notification__pb2 +from truffle.os import proactivity_pb2 as truffle_dot_os_dot_proactivity__pb2 from truffle.os import system_info_pb2 as truffle_dot_os_dot_system__info__pb2 from truffle.os import system_settings_pb2 as truffle_dot_os_dot_system__settings__pb2 from truffle.os import task_actions_pb2 as truffle_dot_os_dot_task__actions__pb2 @@ -22,7 +23,7 @@ from truffle.os import task_search_pb2 as truffle_dot_os_dot_task__search__pb2 from truffle.os import task_user_response_pb2 as truffle_dot_os_dot_task__user__response__pb2 -GRPC_GENERATED_VERSION = '1.72.0' +GRPC_GENERATED_VERSION = '1.76.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -35,7 +36,7 @@ if _version_not_supported: raise RuntimeError( f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in truffle/os/truffleos_pb2_grpc.py depends on' + + ' but the generated code in truffle/os/truffleos_pb2_grpc.py depends on' + f' grpcio>={GRPC_GENERATED_VERSION}.' + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' @@ -56,16 +57,6 @@ def __init__(self, channel): request_serializer=truffle_dot_os_dot_app__queries__pb2.DeleteAppRequest.SerializeToString, response_deserializer=truffle_dot_os_dot_app__queries__pb2.DeleteAppResponse.FromString, _registered_method=True) - self.Apps_GetBackground = channel.unary_unary( - '/truffle.os.TruffleOS/Apps_GetBackground', - request_serializer=truffle_dot_os_dot_app__queries__pb2.GetBackgroundAppsRequest.SerializeToString, - response_deserializer=truffle_dot_os_dot_app__queries__pb2.GetBackgroundAppsResponse.FromString, - _registered_method=True) - self.Apps_GetForeground = channel.unary_unary( - '/truffle.os.TruffleOS/Apps_GetForeground', - request_serializer=truffle_dot_os_dot_app__queries__pb2.GetForegroundAppsRequest.SerializeToString, - response_deserializer=truffle_dot_os_dot_app__queries__pb2.GetForegroundAppsResponse.FromString, - _registered_method=True) self.Apps_GetAll = channel.unary_unary( '/truffle.os.TruffleOS/Apps_GetAll', request_serializer=truffle_dot_os_dot_app__queries__pb2.GetAllAppsRequest.SerializeToString, @@ -86,15 +77,15 @@ def __init__(self, channel): request_serializer=truffle_dot_os_dot_background__feed__queries__pb2.GetLatestFeedEntryIDRequest.SerializeToString, response_deserializer=truffle_dot_os_dot_background__feed__queries__pb2.GetLatestFeedEntryIDResponse.FromString, _registered_method=True) - self.Background_LikeFeedEntry = channel.unary_unary( - '/truffle.os.TruffleOS/Background_LikeFeedEntry', - request_serializer=truffle_dot_os_dot_background__feed__queries__pb2.LikeBackgroundFeedEntryRequest.SerializeToString, - response_deserializer=truffle_dot_os_dot_background__feed__queries__pb2.LikeBackgroundFeedEntryResponse.FromString, + self.Background_ApproveProactiveAction = channel.unary_unary( + '/truffle.os.TruffleOS/Background_ApproveProactiveAction', + request_serializer=truffle_dot_os_dot_proactivity__pb2.ApproveProactiveActionRequest.SerializeToString, + response_deserializer=truffle_dot_os_dot_proactivity__pb2.ApproveProactiveActionResponse.FromString, _registered_method=True) - self.Background_SubmitFeedFeedback = channel.unary_unary( - '/truffle.os.TruffleOS/Background_SubmitFeedFeedback', - request_serializer=truffle_dot_os_dot_background__feed__queries__pb2.BackgroundFeedFeedbackRequest.SerializeToString, - response_deserializer=truffle_dot_os_dot_background__feed__queries__pb2.BackgroundFeedFeedbackResponse.FromString, + self.Background_CancelProactiveAction = channel.unary_unary( + '/truffle.os.TruffleOS/Background_CancelProactiveAction', + request_serializer=truffle_dot_os_dot_proactivity__pb2.CancelProactiveActionRequest.SerializeToString, + response_deserializer=truffle_dot_os_dot_proactivity__pb2.CancelProactiveActionResponse.FromString, _registered_method=True) self.Builder_StartBuildSession = channel.unary_unary( '/truffle.os.TruffleOS/Builder_StartBuildSession', @@ -191,6 +182,11 @@ def __init__(self, channel): request_serializer=truffle_dot_os_dot_system__info__pb2.SystemCheckForUpdateRequest.SerializeToString, response_deserializer=truffle_dot_os_dot_system__info__pb2.SystemCheckForUpdateResponse.FromString, _registered_method=True) + self.Task_TestExternalToolProvider = channel.unary_unary( + '/truffle.os.TruffleOS/Task_TestExternalToolProvider', + request_serializer=truffle_dot_os_dot_task__actions__pb2.TaskTestExternalToolProviderRequest.SerializeToString, + response_deserializer=truffle_dot_os_dot_task__actions__pb2.TaskTestExternalToolProviderResponse.FromString, + _registered_method=True) self.Task_OpenTask = channel.unary_stream( '/truffle.os.TruffleOS/Task_OpenTask', request_serializer=truffle_dot_os_dot_task__actions__pb2.OpenTaskRequest.SerializeToString, @@ -242,20 +238,6 @@ def Apps_DeleteApp(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def Apps_GetBackground(self, request, context): - """apps that can contribute to bg feed (ambients) - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Apps_GetForeground(self, request, context): - """apps used in tasks (focuses) - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - def Apps_GetAll(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) @@ -280,13 +262,13 @@ def Background_GetLatestFeedEntryID(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def Background_LikeFeedEntry(self, request, context): + def Background_ApproveProactiveAction(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def Background_SubmitFeedFeedback(self, request, context): + def Background_CancelProactiveAction(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -418,7 +400,7 @@ def System_CheckForUpdate(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def Task_OpenTask(self, request, context): + def Task_TestExternalToolProvider(self, request, context): """Task == Focus == Foreground App create or open an existing task and its update stream """ @@ -426,6 +408,12 @@ def Task_OpenTask(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def Task_OpenTask(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def Task_InterruptTask(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) @@ -476,16 +464,6 @@ def add_TruffleOSServicer_to_server(servicer, server): request_deserializer=truffle_dot_os_dot_app__queries__pb2.DeleteAppRequest.FromString, response_serializer=truffle_dot_os_dot_app__queries__pb2.DeleteAppResponse.SerializeToString, ), - 'Apps_GetBackground': grpc.unary_unary_rpc_method_handler( - servicer.Apps_GetBackground, - request_deserializer=truffle_dot_os_dot_app__queries__pb2.GetBackgroundAppsRequest.FromString, - response_serializer=truffle_dot_os_dot_app__queries__pb2.GetBackgroundAppsResponse.SerializeToString, - ), - 'Apps_GetForeground': grpc.unary_unary_rpc_method_handler( - servicer.Apps_GetForeground, - request_deserializer=truffle_dot_os_dot_app__queries__pb2.GetForegroundAppsRequest.FromString, - response_serializer=truffle_dot_os_dot_app__queries__pb2.GetForegroundAppsResponse.SerializeToString, - ), 'Apps_GetAll': grpc.unary_unary_rpc_method_handler( servicer.Apps_GetAll, request_deserializer=truffle_dot_os_dot_app__queries__pb2.GetAllAppsRequest.FromString, @@ -506,15 +484,15 @@ def add_TruffleOSServicer_to_server(servicer, server): request_deserializer=truffle_dot_os_dot_background__feed__queries__pb2.GetLatestFeedEntryIDRequest.FromString, response_serializer=truffle_dot_os_dot_background__feed__queries__pb2.GetLatestFeedEntryIDResponse.SerializeToString, ), - 'Background_LikeFeedEntry': grpc.unary_unary_rpc_method_handler( - servicer.Background_LikeFeedEntry, - request_deserializer=truffle_dot_os_dot_background__feed__queries__pb2.LikeBackgroundFeedEntryRequest.FromString, - response_serializer=truffle_dot_os_dot_background__feed__queries__pb2.LikeBackgroundFeedEntryResponse.SerializeToString, + 'Background_ApproveProactiveAction': grpc.unary_unary_rpc_method_handler( + servicer.Background_ApproveProactiveAction, + request_deserializer=truffle_dot_os_dot_proactivity__pb2.ApproveProactiveActionRequest.FromString, + response_serializer=truffle_dot_os_dot_proactivity__pb2.ApproveProactiveActionResponse.SerializeToString, ), - 'Background_SubmitFeedFeedback': grpc.unary_unary_rpc_method_handler( - servicer.Background_SubmitFeedFeedback, - request_deserializer=truffle_dot_os_dot_background__feed__queries__pb2.BackgroundFeedFeedbackRequest.FromString, - response_serializer=truffle_dot_os_dot_background__feed__queries__pb2.BackgroundFeedFeedbackResponse.SerializeToString, + 'Background_CancelProactiveAction': grpc.unary_unary_rpc_method_handler( + servicer.Background_CancelProactiveAction, + request_deserializer=truffle_dot_os_dot_proactivity__pb2.CancelProactiveActionRequest.FromString, + response_serializer=truffle_dot_os_dot_proactivity__pb2.CancelProactiveActionResponse.SerializeToString, ), 'Builder_StartBuildSession': grpc.unary_unary_rpc_method_handler( servicer.Builder_StartBuildSession, @@ -611,6 +589,11 @@ def add_TruffleOSServicer_to_server(servicer, server): request_deserializer=truffle_dot_os_dot_system__info__pb2.SystemCheckForUpdateRequest.FromString, response_serializer=truffle_dot_os_dot_system__info__pb2.SystemCheckForUpdateResponse.SerializeToString, ), + 'Task_TestExternalToolProvider': grpc.unary_unary_rpc_method_handler( + servicer.Task_TestExternalToolProvider, + request_deserializer=truffle_dot_os_dot_task__actions__pb2.TaskTestExternalToolProviderRequest.FromString, + response_serializer=truffle_dot_os_dot_task__actions__pb2.TaskTestExternalToolProviderResponse.SerializeToString, + ), 'Task_OpenTask': grpc.unary_stream_rpc_method_handler( servicer.Task_OpenTask, request_deserializer=truffle_dot_os_dot_task__actions__pb2.OpenTaskRequest.FromString, @@ -689,60 +672,6 @@ def Apps_DeleteApp(request, metadata, _registered_method=True) - @staticmethod - def Apps_GetBackground(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.os.TruffleOS/Apps_GetBackground', - truffle_dot_os_dot_app__queries__pb2.GetBackgroundAppsRequest.SerializeToString, - truffle_dot_os_dot_app__queries__pb2.GetBackgroundAppsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def Apps_GetForeground(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.os.TruffleOS/Apps_GetForeground', - truffle_dot_os_dot_app__queries__pb2.GetForegroundAppsRequest.SerializeToString, - truffle_dot_os_dot_app__queries__pb2.GetForegroundAppsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - @staticmethod def Apps_GetAll(request, target, @@ -852,7 +781,7 @@ def Background_GetLatestFeedEntryID(request, _registered_method=True) @staticmethod - def Background_LikeFeedEntry(request, + def Background_ApproveProactiveAction(request, target, options=(), channel_credentials=None, @@ -865,9 +794,9 @@ def Background_LikeFeedEntry(request, return grpc.experimental.unary_unary( request, target, - '/truffle.os.TruffleOS/Background_LikeFeedEntry', - truffle_dot_os_dot_background__feed__queries__pb2.LikeBackgroundFeedEntryRequest.SerializeToString, - truffle_dot_os_dot_background__feed__queries__pb2.LikeBackgroundFeedEntryResponse.FromString, + '/truffle.os.TruffleOS/Background_ApproveProactiveAction', + truffle_dot_os_dot_proactivity__pb2.ApproveProactiveActionRequest.SerializeToString, + truffle_dot_os_dot_proactivity__pb2.ApproveProactiveActionResponse.FromString, options, channel_credentials, insecure, @@ -879,7 +808,7 @@ def Background_LikeFeedEntry(request, _registered_method=True) @staticmethod - def Background_SubmitFeedFeedback(request, + def Background_CancelProactiveAction(request, target, options=(), channel_credentials=None, @@ -892,9 +821,9 @@ def Background_SubmitFeedFeedback(request, return grpc.experimental.unary_unary( request, target, - '/truffle.os.TruffleOS/Background_SubmitFeedFeedback', - truffle_dot_os_dot_background__feed__queries__pb2.BackgroundFeedFeedbackRequest.SerializeToString, - truffle_dot_os_dot_background__feed__queries__pb2.BackgroundFeedFeedbackResponse.FromString, + '/truffle.os.TruffleOS/Background_CancelProactiveAction', + truffle_dot_os_dot_proactivity__pb2.CancelProactiveActionRequest.SerializeToString, + truffle_dot_os_dot_proactivity__pb2.CancelProactiveActionResponse.FromString, options, channel_credentials, insecure, @@ -1418,6 +1347,33 @@ def System_CheckForUpdate(request, metadata, _registered_method=True) + @staticmethod + def Task_TestExternalToolProvider(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/truffle.os.TruffleOS/Task_TestExternalToolProvider', + truffle_dot_os_dot_task__actions__pb2.TaskTestExternalToolProviderRequest.SerializeToString, + truffle_dot_os_dot_task__actions__pb2.TaskTestExternalToolProviderResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def Task_OpenTask(request, target, From 83a2016b803454ae3b5f90a91a465fb30923a8e7 Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 21:30:10 -0800 Subject: [PATCH 2/9] example apps --- example-apps/ambient/hedge/app.py | 120 --- example-apps/ambient/hedge/icon.png | Bin 240951 -> 0 bytes example-apps/ambient/hedge/truffile.yaml | 27 - example-apps/focus/finance/app.py | 157 --- example-apps/focus/finance/icon.png | Bin 13371 -> 0 bytes example-apps/focus/finance/truffile.yaml | 20 - example-apps/focus/research/icon.png | Bin 8841 -> 0 bytes example-apps/focus/research/research.py | 87 -- example-apps/focus/research/truffile.yaml | 20 - example-apps/kalshi/bg_worker.py | 352 +++++++ example-apps/kalshi/client.py | 290 ++++++ example-apps/kalshi/config.py | 83 ++ example-apps/kalshi/icon.png | Bin 0 -> 79335 bytes example-apps/kalshi/kalshi_background.py | 150 +++ example-apps/kalshi/kalshi_foreground.py | 903 ++++++++++++++++++ example-apps/kalshi/truffile.yaml | 92 ++ example-apps/reddit/icon.png | Bin 0 -> 110411 bytes .../reddit/icon.png => reddit/old.png} | Bin example-apps/{ambient => }/reddit/reddit.py | 55 +- .../{ambient => }/reddit/truffile.yaml | 61 +- example-apps/truffile-example.yaml | 76 -- 21 files changed, 1931 insertions(+), 562 deletions(-) delete mode 100644 example-apps/ambient/hedge/app.py delete mode 100644 example-apps/ambient/hedge/icon.png delete mode 100644 example-apps/ambient/hedge/truffile.yaml delete mode 100644 example-apps/focus/finance/app.py delete mode 100644 example-apps/focus/finance/icon.png delete mode 100644 example-apps/focus/finance/truffile.yaml delete mode 100644 example-apps/focus/research/icon.png delete mode 100644 example-apps/focus/research/research.py delete mode 100644 example-apps/focus/research/truffile.yaml create mode 100644 example-apps/kalshi/bg_worker.py create mode 100755 example-apps/kalshi/client.py create mode 100755 example-apps/kalshi/config.py create mode 100644 example-apps/kalshi/icon.png create mode 100644 example-apps/kalshi/kalshi_background.py create mode 100755 example-apps/kalshi/kalshi_foreground.py create mode 100644 example-apps/kalshi/truffile.yaml create mode 100644 example-apps/reddit/icon.png rename example-apps/{ambient/reddit/icon.png => reddit/old.png} (100%) rename example-apps/{ambient => }/reddit/reddit.py (88%) rename example-apps/{ambient => }/reddit/truffile.yaml (54%) delete mode 100644 example-apps/truffile-example.yaml diff --git a/example-apps/ambient/hedge/app.py b/example-apps/ambient/hedge/app.py deleted file mode 100644 index f643e14..0000000 --- a/example-apps/ambient/hedge/app.py +++ /dev/null @@ -1,120 +0,0 @@ -import os -import requests -import urllib.parse -from datetime import datetime -from gourmet.ambient import run_ambient, AmbientContext -import logging - -logger = logging.getLogger("hedge") -logger.setLevel(logging.DEBUG) - -API_KEY = os.getenv("ALPHAVANTAGE_API_KEY", "go get your key dudes") -TICKERS = os.getenv("HEDGE_TICKERS", "AAPL,MSFT,GOOGL,IREN").split(",") -BASE_URL = "https://www.alphavantage.co/query" - - -def fetch_daily_data(symbol: str) -> dict | None: - try: - resp = requests.get(BASE_URL, params={ - "function": "TIME_SERIES_DAILY", - "symbol": symbol.strip().upper(), - "outputsize": "compact", - "apikey": API_KEY, - }, timeout=30) - resp.raise_for_status() - data = resp.json() - if "Time Series (Daily)" not in data: - logger.warning(f"No daily data for {symbol}: {data}") - return None - return data - except Exception as e: - logger.error(f"Failed to fetch {symbol}: {e}") - return None - - -def generate_chart_url(symbol: str, dates: list[str], prices: list[float]) -> str: - chart_config = { - "type": "line", - "data": { - "labels": dates, - "datasets": [{ - "label": symbol, - "data": prices, - "fill": False, - "borderColor": "#4CAF50", - "tension": 0.1, - "pointRadius": 2, - }] - }, - "options": { - "plugins": { - "legend": {"display": False}, - "title": {"display": True, "text": f"{symbol} - Last 5 Days"} - }, - "scales": { - "y": {"beginAtZero": False} - } - } - } - chart_json = str(chart_config).replace("'", '"').replace("False", "false").replace("True", "true") - encoded = urllib.parse.quote(chart_json, safe='') - return f"https://quickchart.io/chart?c={encoded}&w=400&h=200&bkg=white" - - -def hedge_ambient(ctx: AmbientContext): - logger.info(f"Hedge running for tickers: {TICKERS}") - - for symbol in TICKERS: - symbol = symbol.strip().upper() - if not symbol: - continue - - data = fetch_daily_data(symbol) - if not data: - continue - - ts = data["Time Series (Daily)"] - sorted_dates = sorted(ts.keys(), reverse=True)[:5] - sorted_dates.reverse() - - dates = [d[5:] for d in sorted_dates] - prices = [float(ts[d]["4. close"]) for d in sorted_dates] - - today = sorted_dates[-1] - today_data = ts[today] - current_price = float(today_data["4. close"]) - open_price = float(today_data["1. open"]) - high = float(today_data["2. high"]) - low = float(today_data["3. low"]) - volume = int(today_data["5. volume"]) - - change = current_price - open_price - change_pct = (change / open_price) * 100 if open_price else 0 - arrow = "📈" if change >= 0 else "📉" - - chart_url = generate_chart_url(symbol, dates, prices) - - title = f"{arrow} {symbol}: ${current_price:.2f}" - body = f""" -{symbol} Stock Update - -Price: ${current_price:.2f} -Change: {'+' if change >= 0 else ''}{change:.2f} ({'+' if change_pct >= 0 else ''}{change_pct:.2f}%) - -Today's Range: ${low:.2f} - ${high:.2f} -Volume: {volume:,} -""".strip() - - logger.info(f"Posting {symbol} to feed: {title}") - ctx.bg.post_to_feed( - title=title, - body=body, - src_uri=f"https://finance.yahoo.com/quote/{symbol}", - media_uris=[chart_url], - content_timestamp=datetime.now() - ) - logger.info(f"Posted {symbol} to feed") - - -if __name__ == "__main__": - run_ambient(hedge_ambient) diff --git a/example-apps/ambient/hedge/icon.png b/example-apps/ambient/hedge/icon.png deleted file mode 100644 index 27bcddfc69f0eb7f9ad18602400e5cb343f94c1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240951 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

fA=JaJILk}P9VIRj`5KX;~9IOCb4V>Do{jkFT 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%0D?p_4cc_Sed&rQtUh_;HPr0mu^>^#m2| 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 dict: - params["function"] = function - params["apikey"] = API_KEY - resp = requests.get(BASE_URL, params=params, timeout=30) - resp.raise_for_status() - return resp.json() - - -@mcp.tool("get_stock_price", description="Get current price and daily stats for a stock ticker (e.g. AAPL, MSFT, TSLA)") -async def get_stock_price(symbol: str) -> str: - data = _call_av("GLOBAL_QUOTE", symbol=symbol.upper()) - quote = data.get("Global Quote", {}) - if not quote: - return f"No data found for {symbol}" - return f""" -{quote.get('01. symbol', symbol)} -Price: ${quote.get('05. price', 'N/A')} -Change: {quote.get('09. change', 'N/A')} ({quote.get('10. change percent', 'N/A')}) -Open: ${quote.get('02. open', 'N/A')} -High: ${quote.get('03. high', 'N/A')} -Low: ${quote.get('04. low', 'N/A')} -Volume: {quote.get('06. volume', 'N/A')} -Previous Close: ${quote.get('08. previous close', 'N/A')} -""".strip() - - -@mcp.tool("get_stock_history", description="Get daily price history for a stock. Returns last 30 days by default.") -async def get_stock_history(symbol: str, days: int = 30) -> str: - data = _call_av("TIME_SERIES_DAILY", symbol=symbol.upper(), outputsize="compact") - ts = data.get("Time Series (Daily)", {}) - if not ts: - return f"No historical data for {symbol}" - lines = [f"{symbol.upper()} - Last {min(days, len(ts))} trading days:"] - for i, (date, vals) in enumerate(sorted(ts.items(), reverse=True)): - if i >= days: - break - lines.append(f"{date}: Open ${vals['1. open']} | High ${vals['2. high']} | Low ${vals['3. low']} | Close ${vals['4. close']} | Vol {vals['5. volume']}") - return "\n".join(lines) - - -@mcp.tool("get_company_overview", description="Get company profile, financials, and key metrics for a stock") -async def get_company_overview(symbol: str) -> str: - data = _call_av("OVERVIEW", symbol=symbol.upper()) - if not data or "Symbol" not in data: - return f"No company data for {symbol}" - return f""" -{data.get('Name', symbol)} ({data.get('Symbol', '')}) -Sector: {data.get('Sector', 'N/A')} | Industry: {data.get('Industry', 'N/A')} -Market Cap: ${data.get('MarketCapitalization', 'N/A')} -P/E Ratio: {data.get('PERatio', 'N/A')} | EPS: ${data.get('EPS', 'N/A')} -52-Week High: ${data.get('52WeekHigh', 'N/A')} | 52-Week Low: ${data.get('52WeekLow', 'N/A')} -Dividend Yield: {data.get('DividendYield', 'N/A')} -Description: {data.get('Description', 'N/A')[:500]}... -""".strip() - - -@mcp.tool("search_ticker", description="Search for stock ticker symbols by company name or keywords") -async def search_ticker(keywords: str) -> str: - data = _call_av("SYMBOL_SEARCH", keywords=keywords) - matches = data.get("bestMatches", []) - if not matches: - return f"No matches for '{keywords}'" - lines = [f"Search results for '{keywords}':"] - for m in matches[:10]: - lines.append(f" {m.get('1. symbol', '')} - {m.get('2. name', '')} ({m.get('4. region', '')})") - return "\n".join(lines) - - -@mcp.tool("get_market_news", description="Get latest market news and sentiment for a stock or topic") -async def get_market_news(tickers: str = "", topics: str = "", limit: int = 5) -> str: - params = {"limit": min(limit, 50)} - if tickers: - params["tickers"] = tickers.upper() - if topics: - params["topics"] = topics - data = _call_av("NEWS_SENTIMENT", **params) - feed = data.get("feed", []) - if not feed: - return "No news found" - lines = ["Latest Market News:"] - for article in feed[:limit]: - sentiment = article.get("overall_sentiment_label", "") - lines.append(f"[{sentiment}] {article.get('title', 'No title')}") - lines.append(f" Source: {article.get('source', 'Unknown')} | {article.get('time_published', '')[:10]}") - lines.append(f" {article.get('summary', '')[:200]}...") - lines.append("") - return "\n".join(lines) - - -@mcp.tool("get_top_movers", description="Get top gainers, losers, and most actively traded stocks today") -async def get_top_movers() -> str: - data = _call_av("TOP_GAINERS_LOSERS") - lines = [] - for category in ["top_gainers", "top_losers", "most_actively_traded"]: - items = data.get(category, [])[:5] - if items: - lines.append(f"\n{category.replace('_', ' ').title()}:") - for item in items: - lines.append(f" {item.get('ticker', '')} ${item.get('price', '')} ({item.get('change_percentage', '')})") - return "\n".join(lines).strip() or "No market data available" - - -@mcp.tool("get_crypto_price", description="Get current exchange rate for a cryptocurrency (e.g. BTC, ETH)") -async def get_crypto_price(crypto: str, currency: str = "USD") -> str: - data = _call_av("CURRENCY_EXCHANGE_RATE", from_currency=crypto.upper(), to_currency=currency.upper()) - rate = data.get("Realtime Currency Exchange Rate", {}) - if not rate: - return f"No data for {crypto}/{currency}" - return f""" -{rate.get('1. From_Currency Code', crypto)} → {rate.get('3. To_Currency Code', currency)} -Rate: {rate.get('5. Exchange Rate', 'N/A')} -Bid: {rate.get('8. Bid Price', 'N/A')} | Ask: {rate.get('9. Ask Price', 'N/A')} -Last Updated: {rate.get('6. Last Refreshed', 'N/A')} -""".strip() - - -@mcp.tool("get_economic_indicator", description="Get economic indicators: GDP, CPI, UNEMPLOYMENT, INTEREST_RATE, INFLATION") -async def get_economic_indicator(indicator: str) -> str: - indicator = indicator.upper() - func_map = { - "GDP": "REAL_GDP", - "CPI": "CPI", - "UNEMPLOYMENT": "UNEMPLOYMENT", - "INTEREST_RATE": "FEDERAL_FUNDS_RATE", - "INFLATION": "INFLATION", - } - func = func_map.get(indicator, indicator) - data = _call_av(func) - vals = data.get("data", [])[:10] - if not vals: - return f"No data for {indicator}" - name = data.get("name", indicator) - lines = [f"{name}:"] - for v in vals: - lines.append(f" {v.get('date', '')}: {v.get('value', 'N/A')}") - return "\n".join(lines) - - -def main(): - print(f"Starting Finance MCP server on {HOST}:{PORT}") - mcp.run(transport="streamable-http") - - -if __name__ == "__main__": - main() diff --git a/example-apps/focus/finance/icon.png b/example-apps/focus/finance/icon.png deleted file mode 100644 index 90f6234f6eb0c290a740b5e4154fce5286139854..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13371 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

s?5AM<2Oorv8m$Mro(EdA2{DqpjAjEON5e5( z7<~0L)P&({melB^@W1aYDZ{&W`tKwjx z5ATpzjqXy04)HHo->CC}73T5-&AO41%DEBGbtd-k;Snaix6J%QR!__mhufHy(d#g( z)om@L^yOp(kN8ti^5$F)N~1jwZwOVlw#U9AmXGD zovQ)?gc&rFMauVql~F!&Gb2r*e4}0+zDd|d3;>`^1YY?!xxpec27OuN**H zh|?B{E?6G=J;_j;`-gIlw1MEIemGarj^3;j;EBjiZc12gyqGuHEOJMa7)%$cvtVu_ z-)Hd=up<7lS~g~rN*Tp6L7^-`&u{Pe;%WkOC|O}dPFI(_DbcvR?E8RB*xWGulIHhr zY7+&Ph4p>yIBEq`6D>B^fH)ccE$Mcd+R5~MMkmwrEB8un+D+aTmIsM>Qh1A_s9r-i z6X6QB1_JyQa|g-w`Zu9ZQM-YR@mnHwQ+f-O8U6=f{t2R+0d#R8sL@-nC242(7tgUh z>t=tl@+ax7Rc{J!z3&$`Z>=^Koo82j@Z`pRSReB!{txcmr5Wnk(gjNBY_2W7B z_u0kmA9PO)IJr>6KTS5aW0~}po-!r_*g&3a!rafr2gucV^IX(EXX+k+)s~3>Le#Z` z3HM@ME9m~^!?4hfAD1pRNzN$)&AK1Sn9C_1USAyk6Y*+xN7K^eNC*czGRrP1Q_qXy-`p`r*snu^Hb=QLdkpX#^FF zT2@h<$@f+d1t{%FaNS&WCq$$-%n*IQeNN`y&l)%YUIASF5~@EM@xv@~Wnj46-l?Nt z?js9s7M{?Mi}neY)4BEZ_df|>e6^#xjha^4itk9}Xd)*giI~wjXz~H>syE0SZqVq% z!8z!UkpMVnVEYx3zT{Se!m7WXuh?7HGDDH^iIjN&+}rL)(i3Ne#A2b5>Nn(Z`@z#? z<0f*+od%oQn+&D0_a7b^pKZB}&fS?+auDUNq1+pNNSV3~u6dE;Q5TYa#);&-T#1UR}=Mvf(3Y(p@>c_t4|SLOQuP5-|J{!cV;%_2<{D>(hj SQv2o~MN}0vkrncm!T$p`X?SV? diff --git a/example-apps/focus/finance/truffile.yaml b/example-apps/focus/finance/truffile.yaml deleted file mode 100644 index 75c1a1e..0000000 --- a/example-apps/focus/finance/truffile.yaml +++ /dev/null @@ -1,20 +0,0 @@ -metadata: - name: Finance - type: foreground - description: | - Financial data analyst tools for your Truffle. Get stock prices, company info, - market news, crypto rates, and economic indicators powered by Alpha Vantage. - process: - cmd: - - python - - app.py - working_directory: / - environment: - PYTHONUNBUFFERED: "1" - icon_file: ./icon.png -files: - - source: ./app.py - destination: ./app.py -run: | - pip install --no-cache-dir mcp requests - diff --git a/example-apps/focus/research/icon.png b/example-apps/focus/research/icon.png deleted file mode 100644 index fbd6440c4f1fb099a17e34ef60231251288bb82c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8841 zcmeHNcSBQI(?1CTLJ@*gm1+S5L6D{bCJ4g1G!dmp6;yhWt`HJLiXg>h5v2<&NE47= zgA~yf1f=&SO-cwQAZhQ#=lKBd2YCKT&doV9ckbLNznRIi8%BCZ*pIUV0FGS$L+2&{ zaA*k!NEYa8CuraRfRi__>uB8yf~`+#y4wsVpWiGwjh8*oW zR-Jj!5*C>{+zvBnD8FUF1-Fp@$LNBBSt<`}^u)7k6R%|?jZPK#Tr2!HIKrZ%+afvt z%Y=V{ENM%-{`6kmUV23+8lRYPk1FA_aX_pZo}!GbN*U7$aPZGo?8jk-Jiy4HWdY_g zB;V=N0AQ~EZ}C4R{J$(=Cl;=}ST$@bd!9h`xul||M%`A*H%8NlQ)A>#^$tdN0-9dC zgsUkpHxWhuA`Y?Qgc%LpdI|q7E-r2+>`@3zBOAYsu>$BFjR^I;@xz!iG((JfSq*I$ zFNVHC?a6oH11C?n59ewr1bH*)o96g+1h|-Ok)d}tfj^{p+0h@peie5k_l7qP#iBYi z97Xuahw+311N+dj9CSmOg7u`{Yyzs+e>Gg(dv9aXhcVT3_aX`@E(il;6f=D%ySLMv zk6vB6nIIE`^J6w6PV|Z3Pa{B(!>yg34E5o(+w{9gro?c7d7Mk8&&@dy%@0_sG&ze) zO8B$U2ypXw2{}dHT8RE{Y{&H{gM)*2g}e(vy)h6^wwOK7b%(02ukT_-RnbwK`Lw&* zHmjAL1`$Q8G6JsaVFUs&rwl4*HN%sB~AVM^=m=YL7qfBU+AOxNO2%? z67=fl^5YD4lr*0JzbJqyZ5PFHk~?$={EW5sp_b$xwAv{kCkc8vOx7H0&yUgAal!4+ z!cK(8<4u`*ufRcuVCTL(jy$@lC^{y!aWoo*YI}kSMgl*xN(x%Icz=C6P}>hCZ_{{j zLKHkO2H8mtS#}MCv>aZP2+W+b&7BSS{aJiaYTK|#ZjSFif*<7t%g>#6^}Z5%_I8QF zFIOq!h`uAWJ_~=s@*wivoNfZOPp@q?xIsGRab<&;r#mOk2Ir&f>43sMVUf^6J&w0?%9cZ%Ycs@9_ zBlIRISWrv+f#jTvA*&i;{}=)8NU`6lq~xwotCrtv!Vzuj7&BUR4b?++)w|2b ze!R+eUZo(Rlb4~BXYPz-**0s0RF%rqO``3Oa_LHiROoA*KFRLS95+GFy^d9#Z zCnk_Xi??z&_nJ^~mznw=UBWIJxWtZdDF}jY6mX6RY9dZIAGj`d;3~)LEG@4G>uNR& zI`w3Ve6z27Ba3PaQ+scB9%BOsN`mY3+6kwZ9g^%g4QpSg%?0KLxm?zlIkF0?_L2w; z7CORjO0TeX(Va~Y@GaiAF>D~HTDP7?{HEsZz?mh{A*!nMyT(r;HE25fkBEaP8%C?%@VXF+Zi&-2O7zsFP zu7Y~s%Y>4!pl9C4Kc2tZl8_*lLL@MnK~gz0#xZY{EB+MLWPf;Qh<#mgT`p{I3Oj5V zuka>|-}!T;{PSZ?!uk6(#vxvzH1fD6X`OvHvSf`Y$+jR+i*U(EC#n{=yp+I zk=vd7EVw0BkkyUZQJx#(ye877+%}$@n7mz zr0LbVA1+6}R$c24+oI0wT@a0hyTiN@v9Ymn5SNnsGIW!D^6{K5)%Jk=St4by$eg3> z8ko6}V>3Q5fGZAg+|9cFv;fO!(7O1!5+^cE$(!Pe#`xFGhTZ;s7nefAw2}qQ3u?$) zJ-#PLnbb%D7*jOULz$LvEpgzD?0X*{IF+_}EnJN1!CyoXEZJN-aX|B|kPM&6uLsZx zg>s*RvNxJ5#ZELSHd861bm>tYQUYe==|z`^2VJk>uOVgpVH%EGU0rqQIUu~FiG8Rp zK7~?M;kFk~Ttfg!fosw%-(6#qr-p!OdaA3^f;5=W^KQ)pg z`B&bpl`y;$SLn~BU;(ZUW;%lSVg#^#_pFIA>w&Y~m(Ax!6)#GS$SgNplsh`CGn~hP z(S?Ja!GRx)xB4mUtZu7LSJ=vI(%Ik9t~yZD8wBz02#_gj_O8mDQT}B64jxLRr@>j4L` z(NvrXhSwaN;;`|U39tXkb)w86;%Dtx?U)T{!{3ttW%>sB!=5T>RI$Lnr>E;o^Vxfp zRX6}v3ffs8WY8#kCGNbvj_cXKKwt9&bPb-GsQhx)%JO1_|>V;x`B063lqXi5QvynxmII>w}H zqsG$J?8%K07CaLVfF7u9!p2JWc~N=CNw8)XJoF>mTt#~r2{dj~t|_CTt=jQ?>GQ*b?KC@~jCJii18gq;_l}TQ5~@bZdAp&)sYR_0tpVOpyA=r-zOX2*ySuw}sQTY5uj$Ok zTA3Zw6(vKm0XcqP`4qG*=9x*Kl%bbPp<}NK9whiO zcn(Dhv)X2Vmo$$tmQn_0xIg(3R3O34G(7HekgDRA1{_jbkW<$XK8Aqz`x^AS^xpMe&AulIWh z^eXGLKJU5WfkvLqS_#bKMZt&xSAlTOC5B!E?%~7oqzL8rN8(FsOGpsJY$A?90IPOj zPFnmo(~9WbT`v4=+GaTaPMy|lh})){SbTU#wnIn<`=$5YHWc!>Z6vJbW-A?m|KTc7 zp{L0Xc%D0S4Cm(%QhxpVHEZ4|J<_Rg!&iEc6Z}U^WZ73Kpv#OlJ9rp~>(af~uzx>T zD4MtcIaN*b#^i~B!1{e!39UyM&1-2~-;0K{sS-OeA0MI+AA_C1p`03wk2Du=eCp`w za847~>5F}jnb76tAB4Rj7BC`Zm@{Hh%g9$%bv_ZoLi;bU=2$?zJm1!F=Rl7UnBvP+8T*WEnCz3&{xVB&ZBNXuN4iuKwO21C#k=^?sSC$8c5SG^H9`mr7)K_D|WI>SoW?mrc!Mmb-gfQ$MV+6SD;_qM8 zYuJC~$PM2?dhR3mV@Up_jfpy?kgiWL#a44>AN2*#zjV3mIW0>5!H4lN!MGJCK6toD z{}gxP{{8!J(`_t-G8tktrFKG3fs`K8B(uT^VYZv-5- zvEZE*f6pO0DdRjATq8}+T@BT$NNP&m-6pFU0;lN21_GzqrB&S&epIH z$W__xdmA6^6GA<1>^S**;dpH~R>IA_Zyj0?`iCQ~DdChYDVXn?Vsqs1sJz?}Vhyp4 z+Tt8}(&%=(48hAaG@(oRxA&@;D<6xuy2caT&r0GK3FA&lQ9p*5yJ(x9z1;sN?!4lf zW$YjtJ1+`p#n$~VkDM!J-Gore`fQu3&Dk6i$mbZLmvD4yS#ICnZI5m;K`&zY1Z0t7 zj8(#)a+bs;yE^|O>SEI#S>t}MK>6c1j-Z@Pr^~T++Xc>60E#S$6KzmjkyAmtH%PL( zKgHK(lzHgF7QXpz=r0#RUUl=UZY-0iaO5#afDO0$TTZJ1tz}Tfbblyvn>`$JG?->N zn6AGvpLn`%1+q-a$&|Vn+4t{{bFqD^OlSVlZw zN{GkcpwcJN?!k8QdG8O*nCZh9>hU0xY2@tHmUCmD?5yi@Y;1mt!gi`**wa8Er2Vac z6_~%WKS|8HbMd6#Jz?^={=}3;EasXR+aYP@2I+QP6p!{xyDP^y-R}r%yU(9}YFRKE z_nn@ffB=Q1o_?@TllA^?GBuxR^l)x=t4N|+`Xy7>FMfgWM^J{t{wjHSn1JUUP7<#7 z9R%K9SAxQDjiHUGX|1^b5Ize z%vx3hmDk^&?yugVB^1>&yg*{+Q6N;<)LL*UY*J7$W4iqrC}KQ(cihvYIH|qI`#`A>TCsCeV{13uWUIGe+9u>^9Z4wHB_=ZWJ?N z?AV~36tL%6@RqDt+#FqTQ1!&Dn9863uxgw^W~>~hV9{GwRvW#^?sseMIQ=VjF|ApD zolN+}{yPC%WDKPDm2iJE z+fW^Kctb>hfCA0^Tfz5-SoPMyDrkG>BeG&<1>9QZI9eGfQU1~2$PKF=Cq4= zYgRgK-U-Xk@0ou-`5)ZH5=|TRH9|bMM7y)L&>5yA-`m0aJqzeV=#?)yRm$*wRJYj` zuB~x__svH-L+NWWE-x~Wv09e+6tpzEHXlJjRwMENW9~y$CS)VZRM&iQH&)axEV8N7mQXm^Dsha`1aDMQ2yrVHlVAO zuQPi{f=z4LBf?QJv0)QjwF`YPtAo7$8tbNjl7R>N3BQppS=$Y#@JXQMLs-k3aK4?f zQk8()eu=KEpZ6~jPJzVdpoK$)^iinIs>-U?#xGG0FyW1>6OY&4Irw@)js4y!uzA&R zJx*1zeNd%5C(G}4DUIO2*7H;BCknj!9LHkz2T3dR*Ve*clZ#I~zkRR+m1SkUpSQo8 zG44Sa>qTBYD9=(?Q!~GiR#}GrvymBfm(H&9(yp2pEL>Sgy=&~?W^8M+^e0Pvm?`aYIbG3mEEDx?3+wdZbt%eTSiG+?| z_A^#fU9tU910OzxdHxo|DWOLza~<$es7kWnE6R(^pJQU&VH$3%o{VLoswOqv5L}|A zpby?DzbOD5Hm88Mw4%n~Ee@4$kJWZ(r_FkTA72_9e9b;YrG_|i>#97gQt=xsA2!aU zI)!OR4<6v?yQOc>_T_aezZD^*s9R0-dHm`QCcb3@B;xcyGj4w-kRa>k=Ef|&@ZS!@ z#ixDE{-%Kko!wrfyRZwck)k~@J^w4;FH6%XWLp?bwgn(p=co#Jn?xRER2HAxp zZXQ0O;?Fn4oa^y7rYqsrjpltjJraHy!Qh>|=lj?OSSmdcw>KH6e<{Fye=nEj&`X3` z6Zzkpe%X~aUv4y)Se1=YV1waxHH7#UD#H06b3Rsaxu_|RsI;|Sr~F#VN-<}RcyxSj zNj{YR6pUoW;++kOcj#N)YH?BcvgT&nP{v1Aa)^~=^B$>ix_BzU8-`lz;RJEdxF=mS z8;)~-ZlCf=TKaP76%yaKm9@}j6sda`R}Gv8mPMh0onu7aEN$sqM#Y(4I0+l=7|PT2 zpgRCS89w`Au7gG#N`Xq@tOukpMh$`1%}W3KS~=}r@9wW3Nyd2}c9qUbfB>i)72^_x z7gDtnF20I8ATs2qaSZI6M?8&m+EB?>ksYnD@&yXaBS4qy%sFP67 zXTkt`-3R<|$eO=uA#3NuUY&Y2+iZN^Zeyo_Lju2s01p<&s@&SJ2bR&nOmma)d}c05 zb_U^`oJU6Wa@amS3&)_rkFkNSfvs$;##i4t6EOEJNNeVoZUpWwdyTqL8tSlOS5jL= zp>mMz&oLSg+Lf^K)NwZ0z0;tV-2(}esAi!%-oGz|xWLV!`!PY4F{*Q3o*f1Sm)_$f z`>utrDf%aLNCb5xtcJxVEG{nYFD-+uP6J&&MqORqUoTJ?C=8Yym0QA5eioUD$U^Wr z{e)eUZ+;-}eBV&Qb-KkmL1n+H$Af+yx~Bx_z3dU%awbMQqy$Uxa8hTAp~phMi-X=f zc>#emVsAW!W&S5xQJ*<~|T|H7cf?gmI+^jZX!Qrjt ziu?0W$yE^bM0$DoCkTP1*7bfa({d;YkT?I|P_mq8q4~)W+jl{2W=I6;5f3t=(A3$w zwij*suQsMcGVeH#@2OtBPF2D}zQaKe{z9DE^tnp&wcXA_-1pYe zMaal4)dRYiZu(^cOg?R=-?%O3sFwZ47afgt9=%g&`f~29Eki3or}`42o%E*!{yo$? zkB}DNf#=9>emdbbBJirbudf*$LLT)lS}c1Coa3Py?~6;=sF2~Fo>0*vwwr0JO!GpH zRCVp)<-<|HFFcOAseS3u)<5x>www{h%3}8xsZ)&GyuDn-lu}yC3;fx@jv3z@r~_(? za_%L!id6_zaWK9SQcz%b1TLN&w0dO&747DGsZnSn_6vQignoU53lBBAZ>EYN@JM)O zTZT2?L2bKKOz1?Lg8o$?40S?XO{gg^Uq)uYY{Hs4Tp1hLGkSeun)e1+pzSySjCIUs z)I1zOj(4=nweL;FD9G${vlLM*X7$`~ z1i@eRe<1PUJYXg|XGRD03b!Tju|S~lIx0ewo!+MPf*{xt+B-(vV#nRQ3Ji<@p5=LF zoJeFCuhb{HM!T3;wJ+*g$gR)0WoT$1VX-N8gq^xLGc3J>y)!BX$(_UlMGCUajq)b% z5G#eCvd(UwaV8MV3!mvgfb5i~An1x03^e^%hZZofEGGfTQUEkbB3h=P{XHn$8Uk<= z1-FNu;vWepFu_4$3<_8wfVhYRfIiRu4maX*68J9YN=4$Zl_#z#fq#ww_>+jl1OPUl z{`U12)vLe~2>=ujAh!i;TF7h&_u;Wf*v@O{kTB$?X8>r#|Mnoji-IqwW$^>at>wPl zt?})pC_;7j*4CC83eUy{DYSPHFjR2C800<{SL=fvJaYU~_90CGtFh7W=H^PG( zR@y|q6V?Q6>J2QSHkZb0tODI%1fT$radtySb76#eo@T^1g>L;oa~!s!gDa=T7a5Je z7B?@P66%A2cha8o>I^#Nt%rc1Acs4on_EMlP-UCn)S0V!0XW`UjhgvGTpbeWh>I1{ z`*b2S*~7C`okgTW5KtEYXA3JAYt$5nA-yN#alQH)DedzyS+LLdWU)Z@SI(o9T1=-H7UBVq{QLe{Q&^t5- str: - messages = [ - {"role": "system", "content": self.system_prompt}, - {"role": "user", "content": query} - ] - response = requests.post( - self.url, - json={ - "model": self.model, - "messages": messages - }, - headers={ - "accept": "application/json", - "Content-Type": "application/json", - "Authorization": f"Bearer {self.key}" - } - ) - return response.json()["choices"][0]["message"]["content"] - -HOST = "0.0.0.0" -PORT = 8000 - -mcp = FastMCP("research", stateless_http=True, host=HOST, port=PORT) - - -@mcp.tool("search_perplexity", description="Searches Perplexity AI for an answer to the given query.") -async def search_perplexity(query: str) -> str: - searcher = PerplexitySearcher() - result = searcher.run(query) - return result - -@mcp.tool("search_web") -async def search_web(query: str, num_results: int = 5) -> str: - - results : List[dict] = DDGS().text(query, max_results=num_results, region='us-en', safesearch='off') # type: ignore - formatted_results = "\n".join([f"{i+1}. {res['title']}: {res['href']}" for i, res in enumerate(results)]) - return formatted_results - -@mcp.tool("search_news") -async def search_news(query: str, num_results: int = 5) -> str: - results : List[dict] = DDGS().news(query, max_results=num_results, region='us-en', safesearch='off') # type: ignore - formatted_results = "\n".join([f"{i+1}. [{res['source']}] '{res['title']}': {res['body']} <{res['url']}>" for i, res in enumerate(results)]) - return formatted_results - -@mcp.tool("fetch_url_content", description="Fetches and extracts the main content from a given URL. Will return text and any images found in markdown format. Some sites may block scraping.") -async def fetch_url_content(url: str) -> str: - try: - content = extract_content_from_url(url.strip()) - if content is None: - return "Error: Failed to extract content from the URL. Was it valid? This site may block scraping." - - content_str = f"<{url}>\n\n{content.text}\n" - if content.images: - content_str += "\n\nImages:\n" + "\n".join(content.images) - if content.source_name: - content_str = f"Source: {content.source_name}\n\n" + content_str - return content_str - except Exception as e: - return f"Error: Exception occurred while fetching URL: {str(e)}" - -def main(): - print(f"Starting MCP server on {HOST}:{PORT}") - mcp.run(transport="streamable-http") - - - - - -if __name__ == "__main__": - main() - - - diff --git a/example-apps/focus/research/truffile.yaml b/example-apps/focus/research/truffile.yaml deleted file mode 100644 index 97da18c..0000000 --- a/example-apps/focus/research/truffile.yaml +++ /dev/null @@ -1,20 +0,0 @@ -metadata: - name: Research - type: foreground - description: | - Tools to help your Truffle¹ research and gather information from the web. - process: - cmd: - - python - - research.py - working_directory: / - environment: - PYTHONUNBUFFERED: "1" - icon_file: ./icon.png - -files: - - source: ./research.py - destination: ./research.py - -run: | - pip install --no-cache-dir mcp requests ddgs diff --git a/example-apps/kalshi/bg_worker.py b/example-apps/kalshi/bg_worker.py new file mode 100644 index 0000000..517d982 --- /dev/null +++ b/example-apps/kalshi/bg_worker.py @@ -0,0 +1,352 @@ +from __future__ import annotations + +import logging +from dataclasses import dataclass, field +from datetime import UTC, datetime +from typing import Any + +import httpx + +from client import KalshiClient +from config import ( + CATEGORY_KEYWORDS, + DEFAULT_WATCHED_TICKERS, + KALSHI_API_KEY, + KALSHI_BASE_URL, + KALSHI_CATEGORIES, + KALSHI_FEED_URL, + KALSHI_PRIVATE_KEY, + normalize_private_key, +) + +logger = logging.getLogger("kalshi.bg_worker") + +PRICE_CHANGE_THRESHOLD = 10 +FEED_ITEMS_PER_CYCLE = 3 + + +@dataclass +class BackgroundDigest: + generated_at: str + portfolio_summary: str = "" + price_alerts: list[dict[str, Any]] = field(default_factory=list) + settlement_alerts: list[dict[str, Any]] = field(default_factory=list) + order_updates: list[dict[str, Any]] = field(default_factory=list) + feed_items: list[dict[str, Any]] = field(default_factory=list) + error: str = "" + + +class KalshiBackgroundWorker: + def __init__(self) -> None: + if not KALSHI_API_KEY or not KALSHI_PRIVATE_KEY: + raise ValueError("Missing KALSHI_API_KEY or KALSHI_PRIVATE_KEY") + + self.client = KalshiClient( + api_key=KALSHI_API_KEY, + private_key_pem=normalize_private_key(KALSHI_PRIVATE_KEY), + base_url=KALSHI_BASE_URL, + ) + + self._last_prices: dict[str, int] = {} + self._last_order_ids: set[str] = set() + self._settled_tickers: set[str] = set() + self._watched_tickers: set[str] = set(DEFAULT_WATCHED_TICKERS) + self._is_seeded = False + self._categories: set[str] = KALSHI_CATEGORIES + self._seen_feed_events: set[str] = set() + self._feed_url_tickers: list[str] = self._parse_feed_url(KALSHI_FEED_URL) + + async def close(self) -> None: + try: + await self.client.close() + except Exception: + pass + + async def verify(self) -> tuple[bool, str]: + try: + data = await self.client.get_balance() + balance = int(data.get("balance", 0)) + return True, f"Kalshi auth OK, balance: {balance}c" + except httpx.HTTPStatusError as error: + return False, f"Kalshi API error: {error.response.status_code}" + except Exception as error: + return False, f"Kalshi verification failed: {error}" + + async def run_cycle(self) -> BackgroundDigest: + generated_at = datetime.now(UTC).replace(microsecond=0).isoformat() + + try: + balance_data = await self.client.get_balance() + except httpx.HTTPStatusError as error: + if error.response.status_code in {401, 403}: + return BackgroundDigest(generated_at=generated_at, error="auth_failure") + return BackgroundDigest(generated_at=generated_at, error=str(error)) + except Exception as error: + return BackgroundDigest(generated_at=generated_at, error=str(error)) + + balance = int(balance_data.get("balance", 0)) + portfolio_value = int(balance_data.get("portfolio_value", 0)) + + try: + positions_data = await self.client.get_positions(limit=100) + except httpx.HTTPStatusError as error: + if error.response.status_code in {401, 403}: + return BackgroundDigest(generated_at=generated_at, error="auth_failure") + return BackgroundDigest(generated_at=generated_at, error=str(error)) + except Exception as error: + return BackgroundDigest(generated_at=generated_at, error=str(error)) + + positions = positions_data.get("market_positions", []) + active_tickers: set[str] = set() + for position in positions: + count = int(position.get("position", 0)) + if count != 0: + ticker = (position.get("ticker") or "").strip().upper() + if ticker: + active_tickers.add(ticker) + + all_watched = self._watched_tickers | active_tickers + + try: + price_alerts = await self._check_price_changes(all_watched) + settlement_alerts = await self._check_settlements(active_tickers) + order_updates = await self._check_order_changes() + feed_items = await self._fetch_feed_items() + except httpx.HTTPStatusError as error: + if error.response.status_code in {401, 403}: + return BackgroundDigest(generated_at=generated_at, error="auth_failure") + return BackgroundDigest(generated_at=generated_at, error=str(error)) + except Exception as error: + return BackgroundDigest(generated_at=generated_at, error=str(error)) + + has_activity = balance > 0 or portfolio_value > 0 or active_tickers or all_watched + portfolio_summary = ( + f"Portfolio: ${balance/100:.2f} cash, ${portfolio_value/100:.2f} value. " + f"{len(active_tickers)} open positions. " + f"Watching {len(all_watched)} markets." + ) if has_activity else "" + + if not self._is_seeded: + self._is_seeded = True + return BackgroundDigest( + generated_at=generated_at, + portfolio_summary=portfolio_summary, + feed_items=feed_items, + ) + + return BackgroundDigest( + generated_at=generated_at, + portfolio_summary=portfolio_summary, + price_alerts=price_alerts, + settlement_alerts=settlement_alerts, + order_updates=order_updates, + feed_items=feed_items, + ) + + async def _check_price_changes(self, tickers: set[str]) -> list[dict[str, Any]]: + alerts: list[dict[str, Any]] = [] + for ticker in tickers: + if not ticker: + continue + try: + data = await self.client.get_market(ticker) + market = data.get("market", {}) + yes_bid = int(market.get("yes_bid") or 0) + title = market.get("title", ticker) + + previous = self._last_prices.get(ticker) + self._last_prices[ticker] = yes_bid + + if previous is not None: + diff = yes_bid - previous + if abs(diff) >= PRICE_CHANGE_THRESHOLD: + alerts.append( + { + "ticker": ticker, + "title": title, + "previous_price": previous, + "current_price": yes_bid, + "change": diff, + "direction": "up" if diff > 0 else "down", + } + ) + except httpx.HTTPStatusError as error: + if error.response.status_code in {401, 403}: + raise + logger.warning("Failed to check price for %s", ticker, exc_info=True) + except Exception: + logger.warning("Failed to check price for %s", ticker, exc_info=True) + return alerts + + async def _check_settlements(self, active_tickers: set[str]) -> list[dict[str, Any]]: + alerts: list[dict[str, Any]] = [] + try: + data = await self.client.get_settlements(limit=20) + for settlement in data.get("settlements", []): + ticker = (settlement.get("ticker") or "").strip().upper() + # Prefer active position settlements, but allow all as fallback context. + if active_tickers and ticker and ticker not in active_tickers: + continue + key = f"{ticker}:{settlement.get('settled_time', settlement.get('settled_at', ''))}" + if key in self._settled_tickers: + continue + self._settled_tickers.add(key) + + revenue = int(settlement.get("revenue") or 0) + alerts.append( + { + "ticker": ticker, + "revenue_cents": revenue, + "revenue_dollars": f"{revenue/100:.2f}", + "result": "profit" if revenue > 0 else ("loss" if revenue < 0 else "break-even"), + } + ) + except httpx.HTTPStatusError as error: + if error.response.status_code in {401, 403}: + raise + logger.warning("Failed to check settlements", exc_info=True) + except Exception: + logger.warning("Failed to check settlements", exc_info=True) + return alerts + + async def _check_order_changes(self) -> list[dict[str, Any]]: + alerts: list[dict[str, Any]] = [] + try: + data = await self.client.get_orders(status="resting", limit=100) + current_ids = { + order.get("order_id", "") + for order in data.get("orders", []) + if order.get("order_id") + } + + if self._last_order_ids: + filled_or_canceled = self._last_order_ids - current_ids + new_orders = current_ids - self._last_order_ids + + for order_id in filled_or_canceled: + alerts.append({"order_id": order_id, "change": "filled_or_canceled"}) + for order_id in new_orders: + alerts.append({"order_id": order_id, "change": "new_resting"}) + + self._last_order_ids = current_ids + except httpx.HTTPStatusError as error: + if error.response.status_code in {401, 403}: + raise + logger.warning("Failed to check order changes", exc_info=True) + except Exception: + logger.warning("Failed to check order changes", exc_info=True) + return alerts + + async def _fetch_feed_items(self) -> list[dict[str, Any]]: + items: list[dict[str, Any]] = [] + try: + data = await self.client.get_events( + status="open", with_nested_markets=True, limit=30, + ) + events = data.get("events", []) + + candidates: list[dict[str, Any]] = [] + for event in events: + event_ticker = (event.get("event_ticker") or "").strip() + if not event_ticker or event_ticker in self._seen_feed_events: + continue + + title = event.get("title", "") + markets = event.get("markets") or [] + total_volume = sum(int(m.get("volume") or 0) for m in markets) + + matched = self._match_categories(title) + score = total_volume + (1_000_000 if matched else 0) + tags = sorted(matched | {"trending"}) + + candidates.append({ + "event_ticker": event_ticker, + "title": title, + "categories": tags, + "total_volume": total_volume, + "market_count": len(markets), + "top_markets": self._format_top_markets(markets), + "_score": score, + }) + + candidates.sort(key=lambda e: e["_score"], reverse=True) + + for c in candidates[:FEED_ITEMS_PER_CYCLE]: + del c["_score"] + self._seen_feed_events.add(c["event_ticker"]) + items.append(c) + except httpx.HTTPStatusError as error: + if error.response.status_code in {401, 403}: + raise + logger.warning("Failed to fetch feed items", exc_info=True) + except Exception: + logger.warning("Failed to fetch feed items", exc_info=True) + + for ticker in self._feed_url_tickers: + if ticker in self._seen_feed_events: + continue + try: + data = await self.client.get_event(ticker, with_nested_markets=True) + event = data.get("event") + if event: + markets = data.get("markets") or [] + self._seen_feed_events.add(ticker) + items.append({ + "event_ticker": ticker, + "title": event.get("title", ticker), + "categories": ["followed"], + "total_volume": sum(int(m.get("volume") or 0) for m in markets), + "market_count": len(markets), + "top_markets": self._format_top_markets(markets), + }) + except httpx.HTTPStatusError as error: + if error.response.status_code in {401, 403}: + raise + logger.debug("URL ticker %s not found as event", ticker) + except Exception: + logger.debug("URL ticker %s not found as event", ticker) + + if len(self._seen_feed_events) > 500: + self._seen_feed_events.clear() + + return items + + def _match_categories(self, text: str) -> set[str]: + matched: set[str] = set() + text_lower = text.lower() + for category in self._categories: + if category == "trending": + continue + keywords = CATEGORY_KEYWORDS.get(category, []) + for keyword in keywords: + if keyword in text_lower: + matched.add(category) + break + return matched + + @staticmethod + def _format_top_markets(markets: list[dict[str, Any]]) -> list[dict[str, Any]]: + sorted_markets = sorted( + markets, key=lambda m: int(m.get("volume") or 0), reverse=True, + ) + return [ + { + "ticker": m.get("ticker", ""), + "title": m.get("title", ""), + "yes_bid": m.get("yes_bid"), + "volume": m.get("volume", 0), + } + for m in sorted_markets[:3] + ] + + @staticmethod + def _parse_feed_url(url: str) -> list[str]: + if not url: + return [] + from urllib.parse import urlparse + + path = urlparse(url).path.strip("/") + parts = [p for p in path.split("/") if p] + if parts: + return [parts[-1].upper()] + return [] diff --git a/example-apps/kalshi/client.py b/example-apps/kalshi/client.py new file mode 100755 index 0000000..48afbca --- /dev/null +++ b/example-apps/kalshi/client.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +"""Async Kalshi API client using API key + RSA-PSS authentication.""" + +from __future__ import annotations + +import base64 +import time +from typing import Any, Dict, Optional +from urllib.parse import urlparse + +import httpx +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding + + +class KalshiClient: + """Minimal async client for Kalshi REST endpoints used by the MCP tools.""" + + def __init__( + self, + api_key: str, + private_key_pem: str, + base_url: str, + timeout: float = 30.0, + ) -> None: + self._api_key = api_key + self._base_url = base_url.rstrip("/") + self._private_key = serialization.load_pem_private_key( + private_key_pem.encode("utf-8"), + password=None, + ) + self._http = httpx.AsyncClient(timeout=timeout) + + def _build_auth_headers(self, method: str, path: str) -> Dict[str, str]: + timestamp = str(int(time.time() * 1000)) + message = f"{timestamp}{method.upper()}{path}".encode("utf-8") + + signature = self._private_key.sign( + message, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=hashes.SHA256().digest_size, + ), + hashes.SHA256(), + ) + signature_b64 = base64.b64encode(signature).decode("utf-8") + + return { + "KALSHI-ACCESS-KEY": self._api_key, + "KALSHI-ACCESS-SIGNATURE": signature_b64, + "KALSHI-ACCESS-TIMESTAMP": timestamp, + } + + async def _request( + self, + method: str, + path: str, + *, + params: Optional[Dict[str, Any]] = None, + json_body: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + url = f"{self._base_url}{path}" + parsed = urlparse(url) + headers = self._build_auth_headers(method, parsed.path) + clean_params = ( + {k: v for k, v in params.items() if v is not None} + if params is not None + else None + ) + clean_json = ( + {k: v for k, v in json_body.items() if v is not None} + if json_body is not None + else None + ) + + response = await self._http.request( + method=method.upper(), + url=url, + params=clean_params, + json=clean_json, + headers=headers, + ) + response.raise_for_status() + return response.json() + + async def close(self) -> None: + try: + await self._http.aclose() + except Exception: + pass + + async def get_markets( + self, + *, + limit: Optional[int] = None, + cursor: Optional[str] = None, + event_ticker: Optional[str] = None, + series_ticker: Optional[str] = None, + status: Optional[str] = None, + tickers: Optional[str] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + "/markets", + params={ + "limit": limit, + "cursor": cursor, + "event_ticker": event_ticker, + "series_ticker": series_ticker, + "status": status, + "tickers": tickers, + }, + ) + + async def get_market(self, ticker: str) -> Dict[str, Any]: + return await self._request("GET", f"/markets/{ticker}") + + async def get_market_orderbook( + self, + ticker: str, + *, + depth: Optional[int] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + f"/markets/{ticker}/orderbook", + params={"depth": depth}, + ) + + async def get_trades( + self, + *, + limit: Optional[int] = None, + cursor: Optional[str] = None, + ticker: Optional[str] = None, + min_ts: Optional[int] = None, + max_ts: Optional[int] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + "/markets/trades", + params={ + "limit": limit, + "cursor": cursor, + "ticker": ticker, + "min_ts": min_ts, + "max_ts": max_ts, + }, + ) + + async def get_events( + self, + *, + limit: Optional[int] = None, + cursor: Optional[str] = None, + with_nested_markets: Optional[bool] = None, + status: Optional[str] = None, + series_ticker: Optional[str] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + "/events", + params={ + "limit": limit, + "cursor": cursor, + "with_nested_markets": with_nested_markets, + "status": status, + "series_ticker": series_ticker, + }, + ) + + async def get_event( + self, + event_ticker: str, + *, + with_nested_markets: Optional[bool] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + f"/events/{event_ticker}", + params={"with_nested_markets": with_nested_markets}, + ) + + async def get_balance(self) -> Dict[str, Any]: + return await self._request("GET", "/portfolio/balance") + + async def get_positions( + self, + *, + cursor: Optional[str] = None, + limit: Optional[int] = None, + count_filter: Optional[str] = None, + ticker: Optional[str] = None, + event_ticker: Optional[str] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + "/portfolio/positions", + params={ + "cursor": cursor, + "limit": limit, + "count_filter": count_filter, + "ticker": ticker, + "event_ticker": event_ticker, + }, + ) + + async def get_orders( + self, + *, + ticker: Optional[str] = None, + event_ticker: Optional[str] = None, + min_ts: Optional[int] = None, + max_ts: Optional[int] = None, + status: Optional[str] = None, + limit: Optional[int] = None, + cursor: Optional[str] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + "/portfolio/orders", + params={ + "ticker": ticker, + "event_ticker": event_ticker, + "min_ts": min_ts, + "max_ts": max_ts, + "status": status, + "limit": limit, + "cursor": cursor, + }, + ) + + async def create_order(self, payload: Dict[str, Any]) -> Dict[str, Any]: + return await self._request("POST", "/portfolio/orders", json_body=payload) + + async def cancel_order(self, order_id: str) -> Dict[str, Any]: + return await self._request("DELETE", f"/portfolio/orders/{order_id}") + + async def batch_cancel_orders(self, order_ids: list[str]) -> Dict[str, Any]: + return await self._request( + "DELETE", + "/portfolio/orders/batched", + json_body={"ids": order_ids}, + ) + + async def get_fills( + self, + *, + ticker: Optional[str] = None, + order_id: Optional[str] = None, + min_ts: Optional[int] = None, + max_ts: Optional[int] = None, + limit: Optional[int] = None, + cursor: Optional[str] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + "/portfolio/fills", + params={ + "ticker": ticker, + "order_id": order_id, + "min_ts": min_ts, + "max_ts": max_ts, + "limit": limit, + "cursor": cursor, + }, + ) + + async def get_settlements( + self, + *, + limit: Optional[int] = None, + cursor: Optional[str] = None, + ticker: Optional[str] = None, + event_ticker: Optional[str] = None, + min_ts: Optional[int] = None, + max_ts: Optional[int] = None, + ) -> Dict[str, Any]: + return await self._request( + "GET", + "/portfolio/settlements", + params={ + "limit": limit, + "cursor": cursor, + "ticker": ticker, + "event_ticker": event_ticker, + "min_ts": min_ts, + "max_ts": max_ts, + }, + ) diff --git a/example-apps/kalshi/config.py b/example-apps/kalshi/config.py new file mode 100755 index 0000000..92bf0f2 --- /dev/null +++ b/example-apps/kalshi/config.py @@ -0,0 +1,83 @@ +"""Configuration for the Kalshi Truffle app.""" + +from __future__ import annotations + +import os + +KALSHI_API_KEY: str = os.getenv("KALSHI_API_KEY", "") +KALSHI_PRIVATE_KEY: str = os.getenv("KALSHI_PRIVATE_KEY", "") +KALSHI_BASE_URL: str = os.getenv( + "KALSHI_BASE_PATH", + "https://api.elections.kalshi.com/trade-api/v2", +) + +DEFAULT_WATCHED_TICKERS: list[str] = [] + +KALSHI_CATEGORIES_RAW: str = os.getenv("KALSHI_CATEGORIES", "") +KALSHI_FEED_URL: str = os.getenv("KALSHI_FEED_URL", "").strip() + +CATEGORY_KEYWORDS: dict[str, list[str]] = { + "politics": [ + "president", "election", "nominee", "senate", "house", "governor", + "congress", "democrat", "republican", "vote", "party", "cabinet", + "trump", "biden", "vance", + ], + "sports": [ + "nfl", "nba", "mlb", "nhl", "super bowl", "world series", + "championship", "ufc", "boxing", "playoffs", "soccer", "fifa", + ], + "culture": [ + "oscar", "grammy", "emmy", "movie", "film", "music", + "celebrity", "award", "entertainment", "streaming", + ], + "crypto": [ + "bitcoin", "btc", "ethereum", "eth", "crypto", "solana", + "dogecoin", "blockchain", + ], + "climate": [ + "temperature", "weather", "hurricane", "climate", "wildfire", + "flood", "drought", "storm", "tornado", + ], + "economics": [ + "gdp", "inflation", "cpi", "fed", "interest rate", "unemployment", + "recession", "gas price", "oil", "spending", "treasury", + ], + "mentions": [ + "mention", "say", "speech", "briefing", "address", + "state of the union", + ], + "companies": [ + "company", "stock", "ipo", "acquisition", "merger", "earnings", + "tesla", "apple", "google", "amazon", "meta", + ], + "financials": [ + "s&p", "dow", "nasdaq", "index", "bond", "yield", "forex", + "close price", + ], + "tech & science": [ + "ai", "technology", "science", "space", "nasa", "launch", + "starship", "openai", "quantum", + ], +} + + +def parse_categories(raw: str) -> set[str]: + categories: set[str] = {"trending"} + if not raw.strip(): + return categories + for cat in raw.split(","): + cleaned = cat.strip().lower() + if cleaned in CATEGORY_KEYWORDS: + categories.add(cleaned) + return categories + + +KALSHI_CATEGORIES: set[str] = parse_categories(KALSHI_CATEGORIES_RAW) + + +def normalize_private_key(raw: str) -> str: + """Normalize a pasted PEM key from env text fields.""" + key = (raw or "").strip() + if "\\n" in key: + key = key.replace("\\n", "\n") + return key diff --git a/example-apps/kalshi/icon.png b/example-apps/kalshi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7cf17100d7e1b37572f2a413e6d420ef4634f95b GIT binary patch literal 79335 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 literal 0 HcmV?d00001 diff --git a/example-apps/kalshi/kalshi_background.py b/example-apps/kalshi/kalshi_background.py new file mode 100644 index 0000000..9128996 --- /dev/null +++ b/example-apps/kalshi/kalshi_background.py @@ -0,0 +1,150 @@ +from __future__ import annotations + +import asyncio +import atexit +import logging +import sys + +import httpx + +from app_runtime.background import BackgroundRunContext, run_background +from truffle.app.background_pb2 import BackgroundContext + +from bg_worker import KalshiBackgroundWorker + +logger = logging.getLogger("kalshi.background") +logger.setLevel(logging.INFO) + +_worker: KalshiBackgroundWorker | None = None +_loop: asyncio.AbstractEventLoop | None = None + + +def _is_verify_mode() -> bool: + return bool(sys.argv and len(sys.argv) > 1 and "verify" in sys.argv[1].lower()) + + +def _run(coro): + global _loop + if _loop is None or _loop.is_closed(): + _loop = asyncio.new_event_loop() + return _loop.run_until_complete(coro) + + +def _ensure_worker() -> KalshiBackgroundWorker: + global _worker + if _worker is None: + _worker = KalshiBackgroundWorker() + return _worker + + +async def _report_auth_failure(description: str) -> None: + from app_runtime import AppRuntimeErrorType, report_app_error + + await report_app_error( + error_message=f"Kalshi authentication failure: {description}", + error_type=AppRuntimeErrorType.APP_ERROR_AUTH, + needs_intervention=True, + is_fatal=False, + ) + + +def _submit(ctx: BackgroundRunContext, content: str, priority: int) -> None: + ctx.bg.submit_context(content=content, uris=[], priority=priority) + + +def kalshi_ambient(ctx: BackgroundRunContext) -> None: + worker = _ensure_worker() + + try: + result = _run(worker.run_cycle()) + except httpx.HTTPStatusError as error: + logger.exception("Kalshi API error in background cycle") + if error.response.status_code in {401, 403}: + try: + _run(_report_auth_failure(f"API returned {error.response.status_code}")) + except Exception: + logger.exception("Failed to report auth failure") + return + except Exception: + logger.exception("Kalshi background cycle crashed") + return + + if result.error: + logger.error("Kalshi background cycle failed", extra={"error": result.error}) + if result.error == "auth_failure": + try: + _run(_report_auth_failure("API returned 401/403")) + except Exception: + logger.exception("Failed to report auth failure") + return + + if result.portfolio_summary: + _submit(ctx, result.portfolio_summary, BackgroundContext.PRIORITY_LOW) + + for alert in result.price_alerts: + content = ( + f"Price alert: {alert['title']} ({alert['ticker']}) moved " + f"{alert['direction']} {abs(alert['change'])}c " + f"(was {alert['previous_price']}c, now {alert['current_price']}c)" + ) + _submit(ctx, content, BackgroundContext.PRIORITY_HIGH) + + for alert in result.settlement_alerts: + content = ( + f"Market settled: {alert['ticker']} — {alert['result']} " + f"(${alert['revenue_dollars']})" + ) + _submit(ctx, content, BackgroundContext.PRIORITY_HIGH) + + for update in result.order_updates: + content = f"Order {update['order_id']}: {update['change']}" + _submit(ctx, content, BackgroundContext.PRIORITY_DEFAULT) + + for item in result.feed_items: + top = item.get("top_markets", []) + top_str = ", ".join( + f"{m['title']} ({m.get('yes_bid', '?')}¢)" for m in top[:2] + ) + content = ( + f"Kalshi [{', '.join(item.get('categories', []))}]: " + f"{item['title']} — " + f"{item['total_volume']:,} vol, " + f"{item['market_count']} markets" + ) + if top_str: + content += f" — {top_str}" + _submit(ctx, content, BackgroundContext.PRIORITY_LOW) + + +def verify() -> int: + worker = _ensure_worker() + ok, message = _run(worker.verify()) + if ok: + logger.info(message) + return 0 + logger.error(message) + return 1 + + +def _cleanup() -> None: + global _worker + global _loop + + if _worker is not None: + try: + _run(_worker.close()) + except Exception: + logger.exception("Failed to close Kalshi background worker") + finally: + _worker = None + + if _loop is not None and not _loop.is_closed(): + _loop.close() + _loop = None + + +if __name__ == "__main__": + atexit.register(_cleanup) + if _is_verify_mode(): + sys.exit(verify()) + run_background(kalshi_ambient) diff --git a/example-apps/kalshi/kalshi_foreground.py b/example-apps/kalshi/kalshi_foreground.py new file mode 100755 index 0000000..2e52681 --- /dev/null +++ b/example-apps/kalshi/kalshi_foreground.py @@ -0,0 +1,903 @@ +#!/usr/bin/env python3 +"""Kalshi MCP server (streamable HTTP) for foreground Truffle app.""" + +from __future__ import annotations + +import atexit +import asyncio +import logging +from typing import Any + +import httpx +from app_runtime.mcp import create_mcp_server, run_mcp_server + +from client import KalshiClient +from config import ( + KALSHI_API_KEY, + KALSHI_BASE_URL, + KALSHI_PRIVATE_KEY, + normalize_private_key, +) + +logger = logging.getLogger("kalshi.foreground") +logger.setLevel(logging.INFO) + +mcp = create_mcp_server("kalshi") + +_api: KalshiClient | None = None +_watched_tickers: set[str] = set() + + +def _error(message: str, **extra: Any) -> dict[str, Any]: + payload: dict[str, Any] = {"status": "error", "message": message} + payload.update(extra) + return payload + + +def _success(message: str, **extra: Any) -> dict[str, Any]: + payload: dict[str, Any] = {"status": "success", "message": message} + payload.update(extra) + return payload + + +async def _report_auth_failure(description: str) -> None: + from app_runtime import AppRuntimeErrorType, report_app_error + + await report_app_error( + error_message=f"Kalshi authentication failure: {description}", + error_type=AppRuntimeErrorType.APP_ERROR_AUTH, + needs_intervention=True, + is_fatal=False, + ) + + +def _validate_range(name: str, value: int | None, min_value: int, max_value: int) -> None: + if value is None: + return + if value < min_value or value > max_value: + raise ValueError(f"{name} must be between {min_value} and {max_value}") + + +def _get_api() -> KalshiClient: + global _api + if _api is not None: + return _api + + if not KALSHI_API_KEY: + raise ValueError("Missing KALSHI_API_KEY") + if not KALSHI_PRIVATE_KEY: + raise ValueError("Missing KALSHI_PRIVATE_KEY") + + _api = KalshiClient( + api_key=KALSHI_API_KEY, + private_key_pem=normalize_private_key(KALSHI_PRIVATE_KEY), + base_url=KALSHI_BASE_URL, + ) + return _api + + +async def _handle_api_error(error: httpx.HTTPStatusError) -> dict[str, Any]: + status = error.response.status_code + if status in {401, 403}: + try: + await _report_auth_failure(f"Kalshi API returned {status}") + except Exception: + logger.exception("Failed to report Kalshi auth failure") + response_text = "" + try: + response_text = error.response.text + except Exception: + response_text = "" + return _error( + f"Kalshi API error: {status}", + response=response_text[:1500], + ) + + +async def _validate_order( + *, + ticker: str, + side: str, + action: str, + count: int, + price: int | None, +) -> dict[str, Any]: + api = _get_api() + errors: list[str] = [] + warnings: list[str] = [] + estimated_cost = 0 + current_balance = 0 + + market_resp = await api.get_market(ticker) + market = market_resp.get("market") or {} + market_status = str(market.get("status", "unknown")).lower() + # Kalshi market data can surface tradable markets as "active". + # Keep strict blocks for clearly non-tradable statuses, and let create_order + # API decide for ambiguous/unknown status values. + if market_status in {"closed", "settled", "paused", "unopened"}: + errors.append(f"Market {ticker} is {market.get('status')}, not open for trading") + elif market_status not in {"open", "active"}: + warnings.append( + f"Market {ticker} has unrecognized status '{market.get('status')}'. " + "Proceeding to API submission for final tradability check." + ) + + if side == "yes": + current_price = market.get("yes_ask") if action == "buy" else market.get("yes_bid") + else: + current_price = market.get("no_ask") if action == "buy" else market.get("no_bid") + + if price is not None and current_price is not None: + diff = abs(int(price) - int(current_price)) + if diff > 20: + warnings.append( + f"Provided price ({price}c) is {diff}c away from market ({current_price}c)" + ) + + balance_resp = await api.get_balance() + current_balance = int(balance_resp.get("balance") or 0) + + if action == "buy": + effective_price = int(price if price is not None else (current_price or 50)) + estimated_cost = count * effective_price + if estimated_cost > current_balance: + errors.append( + f"Insufficient balance: need {estimated_cost}c, have {current_balance}c" + ) + + if count <= 0: + errors.append("Order quantity must be positive") + if count > 1000: + warnings.append("Large order size may have poor execution") + + if price is not None and (price < 1 or price > 99): + errors.append("Price must be between 1 and 99 cents") + + return { + "valid": len(errors) == 0, + "errors": errors, + "warnings": warnings, + "estimated_cost": estimated_cost, + "current_balance": current_balance, + "market_status": market.get("status"), + "market_open_time": market.get("open_time"), + "market_close_time": market.get("close_time"), + } + + +@mcp.tool( + "get_markets", + description=( + "Search and list Kalshi prediction markets. " + "Parameters: limit (int, max 1000), cursor (str, pagination), event_ticker (str, filter by event), " + "series_ticker (str, filter by series), status (str: unopened|open|paused|closed|settled), " + "tickers (str, comma-separated). " + "Returns: JSON with fields: markets, count, cursor. " + "IMPORTANT: use cursor for pagination over large result sets. " + "Example: get_markets(status='open', limit=20)." + ), +) +async def get_markets( + limit: int | None = None, + cursor: str | None = None, + event_ticker: str | None = None, + series_ticker: str | None = None, + status: str | None = None, + tickers: str | None = None, +) -> dict[str, Any]: + """List and search Kalshi prediction markets.""" + try: + _validate_range("limit", limit, 1, 1000) + if status and status not in {"unopened", "open", "paused", "closed", "settled"}: + return _error("status must be one of unopened|open|paused|closed|settled") + + api = _get_api() + data = await api.get_markets( + limit=limit, + cursor=cursor, + event_ticker=event_ticker, + series_ticker=series_ticker, + status=status, + tickers=tickers, + ) + markets = data.get("markets", []) + return _success("Markets fetched", markets=markets, count=len(markets), cursor=data.get("cursor")) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_market", + description=( + "Get full details for a single market by ticker. " + "Parameters: ticker (str, required). " + "Returns: JSON with fields: market (ticker, title, yes_bid, yes_ask, no_bid, no_ask, volume, open_interest, status). " + "IMPORTANT: pass exact market ticker. " + "Example: get_market(ticker='PRES-2028-DEM')." + ), +) +async def get_market(ticker: str) -> dict[str, Any]: + """Get details for a specific market ticker.""" + try: + api = _get_api() + data = await api.get_market(ticker) + market = data.get("market") + if not market: + return _error(f"Market not found: {ticker}") + return _success("Market fetched", market=market) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_orderbook", + description=( + "Get the orderbook (bid/ask levels) for a market. " + "Parameters: ticker (str, required), depth (int, default None, max 100). " + "Returns: JSON with fields: yes_bids, no_bids, summary. " + "IMPORTANT: use small depth for faster responses. " + "Example: get_orderbook(ticker='PRES-2028-DEM', depth=10)." + ), +) +async def get_orderbook(ticker: str, depth: int | None = None) -> dict[str, Any]: + """Get orderbook levels for a market.""" + try: + _validate_range("depth", depth, 1, 100) + api = _get_api() + data = await api.get_market_orderbook(ticker, depth=depth) + orderbook = data.get("orderbook") + if not orderbook: + return _error(f"Orderbook not found: {ticker}") + yes_bids = orderbook.get("yes_dollars") or [] + no_bids = orderbook.get("no_dollars") or [] + return _success( + "Orderbook fetched", + ticker=ticker, + yes_bids=[{"price_dollars": x[0], "quantity": x[1]} for x in yes_bids], + no_bids=[{"price_dollars": x[0], "quantity": x[1]} for x in no_bids], + summary={ + "yes_levels": len(yes_bids), + "no_levels": len(no_bids), + "best_yes_bid": yes_bids[-1][0] if yes_bids else None, + "best_no_bid": no_bids[-1][0] if no_bids else None, + }, + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_trades", + description=( + "Get recent public trades for a market or globally. " + "Parameters: ticker (str), limit (int, max 1000), cursor (str), min_ts (int), max_ts (int). " + "Returns: JSON with fields: trades, count, cursor. " + "IMPORTANT: combine ticker + time filters for focused analysis. " + "Example: get_trades(ticker='PRES-2028-DEM', limit=50)." + ), +) +async def get_trades( + ticker: str | None = None, + limit: int | None = None, + cursor: str | None = None, + min_ts: int | None = None, + max_ts: int | None = None, +) -> dict[str, Any]: + """Get recent trades across markets or for a specific ticker.""" + try: + _validate_range("limit", limit, 1, 1000) + api = _get_api() + data = await api.get_trades( + ticker=ticker, + limit=limit, + cursor=cursor, + min_ts=min_ts, + max_ts=max_ts, + ) + trades = data.get("trades", []) + return _success("Trades fetched", trades=trades, count=len(trades), cursor=data.get("cursor")) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_events", + description=( + "List Kalshi events (each event can contain multiple markets). " + "Parameters: limit (int, max 200), cursor (str), status (str: open|closed|settled), " + "series_ticker (str), with_nested_markets (bool). " + "Returns: JSON with fields: events, count, cursor. " + "IMPORTANT: set with_nested_markets=true to include markets inline. " + "Example: get_events(status='open', with_nested_markets=True)." + ), +) +async def get_events( + limit: int | None = None, + cursor: str | None = None, + status: str | None = None, + series_ticker: str | None = None, + with_nested_markets: bool | None = None, +) -> dict[str, Any]: + """List Kalshi events.""" + try: + _validate_range("limit", limit, 1, 200) + if status and status not in {"open", "closed", "settled"}: + return _error("status must be one of open|closed|settled") + + api = _get_api() + data = await api.get_events( + limit=limit, + cursor=cursor, + status=status, + series_ticker=series_ticker, + with_nested_markets=with_nested_markets, + ) + events = data.get("events", []) + return _success("Events fetched", events=events, count=len(events), cursor=data.get("cursor")) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_event", + description=( + "Get details for a single event with optional nested markets. " + "Parameters: event_ticker (str, required), with_nested_markets (bool, default None). " + "Returns: JSON with fields: event, markets, markets_count. " + "IMPORTANT: use event ticker, not market ticker. " + "Example: get_event(event_ticker='FED-RATE-25MAR', with_nested_markets=True)." + ), +) +async def get_event(event_ticker: str, with_nested_markets: bool | None = None) -> dict[str, Any]: + """Get detailed info for a specific event ticker.""" + try: + api = _get_api() + data = await api.get_event(event_ticker, with_nested_markets=with_nested_markets) + event = data.get("event") + if not event: + return _error(f"Event not found: {event_ticker}") + markets = data.get("markets") or [] + return _success("Event fetched", event=event, markets=markets, markets_count=len(markets)) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_balance", + description=( + "Get account cash balance and portfolio value. " + "Parameters: none. " + "Returns: JSON with fields: balance_cents, balance_dollars, portfolio_value_cents, portfolio_value_dollars. " + "IMPORTANT: values are cents from API and dollars as formatted strings. " + "Example: get_balance()." + ), +) +async def get_balance() -> dict[str, Any]: + """Get account balance and portfolio value.""" + try: + api = _get_api() + data = await api.get_balance() + balance = int(data.get("balance") or 0) + portfolio_value = int(data.get("portfolio_value") or 0) + return _success( + "Balance fetched", + balance_cents=balance, + portfolio_value_cents=portfolio_value, + balance_dollars=f"{balance / 100:.2f}", + portfolio_value_dollars=f"{portfolio_value / 100:.2f}", + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_positions", + description=( + "Get current open positions. " + "Parameters: limit (int, max 100), cursor (str), ticker (str), event_ticker (str), " + "count_filter (str: position|total_traded). " + "Returns: JSON with fields: positions, summary, cursor. " + "IMPORTANT: summary.total_positions counts only non-zero positions. " + "Example: get_positions(limit=50)." + ), +) +async def get_positions( + limit: int | None = None, + cursor: str | None = None, + ticker: str | None = None, + event_ticker: str | None = None, + count_filter: str | None = None, +) -> dict[str, Any]: + """Get current positions.""" + try: + _validate_range("limit", limit, 1, 100) + if count_filter and count_filter not in {"position", "total_traded"}: + return _error("count_filter must be one of position|total_traded") + + api = _get_api() + data = await api.get_positions( + limit=limit, + cursor=cursor, + ticker=ticker, + event_ticker=event_ticker, + count_filter=count_filter, + ) + positions = data.get("market_positions", []) + total_positions = len([p for p in positions if int(p.get("position", 0)) != 0]) + return _success( + "Positions fetched", + positions=positions, + summary={"total_positions": total_positions, "total_returned": len(positions)}, + cursor=data.get("cursor"), + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_orders", + description=( + "Get order history or active resting orders. " + "Parameters: ticker (str), event_ticker (str), status (str: resting|canceled|executed), " + "limit (int, max 200), cursor (str), min_ts/max_ts (int). " + "Returns: JSON with fields: orders, summary, cursor. " + "IMPORTANT: use status='resting' to monitor currently open orders. " + "Example: get_orders(status='resting')." + ), +) +async def get_orders( + ticker: str | None = None, + event_ticker: str | None = None, + status: str | None = None, + limit: int | None = None, + cursor: str | None = None, + min_ts: int | None = None, + max_ts: int | None = None, +) -> dict[str, Any]: + """Get order history and active orders.""" + try: + _validate_range("limit", limit, 1, 200) + if status and status not in {"resting", "canceled", "executed"}: + return _error("status must be one of resting|canceled|executed") + + api = _get_api() + data = await api.get_orders( + ticker=ticker, + event_ticker=event_ticker, + status=status, + limit=limit, + cursor=cursor, + min_ts=min_ts, + max_ts=max_ts, + ) + orders = data.get("orders", []) + return _success( + "Orders fetched", + orders=orders, + summary={ + "total": len(orders), + "resting": len([o for o in orders if o.get("status") == "resting"]), + "executed": len([o for o in orders if o.get("status") == "executed"]), + }, + cursor=data.get("cursor"), + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "create_order", + description=( + "Place a new order on Kalshi (executes a real trade). " + "Parameters: ticker (str), side (yes|no), action (buy|sell), count (int), type (limit|market, default limit), " + "yes_price/no_price (int, cents 1-99), client_order_id (str), expiration_ts (int). " + "Returns: JSON with fields: order, warnings. " + "IMPORTANT: this tool pre-validates market status and available balance before submission. " + "Example: create_order(ticker='PRES-2028-DEM', side='yes', action='buy', count=10, type='limit', yes_price=55)." + ), +) +async def create_order( + ticker: str, + side: str, + action: str, + count: int, + type: str = "limit", + yes_price: int | None = None, + no_price: int | None = None, + client_order_id: str | None = None, + expiration_ts: int | None = None, +) -> dict[str, Any]: + """Place a new order on Kalshi.""" + try: + if side not in {"yes", "no"}: + return _error("side must be yes or no") + if action not in {"buy", "sell"}: + return _error("action must be buy or sell") + if type not in {"limit", "market"}: + return _error("type must be limit or market") + + # Keep price inputs aligned to side to avoid ambiguous/invalid payloads. + if side == "yes" and no_price is not None: + return _error("For side='yes', provide yes_price only") + if side == "no" and yes_price is not None: + return _error("For side='no', provide no_price only") + if type == "limit": + if side == "yes" and yes_price is None: + return _error("For limit yes orders, provide yes_price") + if side == "no" and no_price is None: + return _error("For limit no orders, provide no_price") + + selected_price = yes_price if side == "yes" else no_price + validation = await _validate_order( + ticker=ticker, + side=side, + action=action, + count=count, + price=selected_price, + ) + if not validation["valid"]: + return _error( + "Order validation failed", + errors=validation["errors"], + warnings=validation["warnings"], + estimated_cost=validation["estimated_cost"], + current_balance=validation["current_balance"], + ) + + payload = { + "ticker": ticker, + "side": side, + "action": action, + "count": count, + "type": type, + "yes_price": yes_price, + "no_price": no_price, + "client_order_id": client_order_id, + "expiration_ts": expiration_ts, + } + payload = {k: v for k, v in payload.items() if v is not None} + + api = _get_api() + data = await api.create_order(payload) + order = data.get("order") + if not order: + return _error("Order created but no order details were returned") + + return _success( + "Order created successfully", + order=order, + warnings=validation["warnings"], + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "cancel_order", + description=( + "Cancel a single resting order. " + "Parameters: order_id (str, required). " + "Returns: JSON with fields: order. " + "IMPORTANT: order must still be cancelable (resting). " + "Example: cancel_order(order_id='abc-123-def')." + ), +) +async def cancel_order(order_id: str) -> dict[str, Any]: + """Cancel a resting order by order ID.""" + try: + api = _get_api() + data = await api.cancel_order(order_id) + order = data.get("order") + if not order: + return _error(f"Order {order_id} canceled but no order details were returned") + return _success("Order canceled successfully", order=order) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "batch_cancel_orders", + description=( + "Cancel up to 20 orders in one request. " + "Parameters: order_ids (array of strings, required, max 20). " + "Returns: JSON with fields: cancelled_count, requested_count, cancelled_orders. " + "IMPORTANT: include only currently resting orders for best results. " + "Example: batch_cancel_orders(order_ids=['id1','id2'])." + ), +) +async def batch_cancel_orders(order_ids: list[str]) -> dict[str, Any]: + """Cancel up to 20 orders in one request.""" + try: + if not order_ids: + return _error("order_ids must contain at least one order ID") + if len(order_ids) > 20: + return _error("order_ids cannot exceed 20") + + api = _get_api() + data = await api.batch_cancel_orders(order_ids) + orders = data.get("orders", []) + return _success( + "Batch cancel executed", + cancelled_count=len(orders), + requested_count=len(order_ids), + cancelled_orders=orders, + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_fills", + description=( + "Get fill history (executed trades on your orders). " + "Parameters: ticker (str), order_id (str), limit (int, max 200), cursor (str), min_ts/max_ts (int). " + "Returns: JSON with fields: fills, summary, cursor. " + "IMPORTANT: summary includes total volume and buy/sell counts. " + "Example: get_fills(ticker='PRES-2028-DEM', limit=20)." + ), +) +async def get_fills( + ticker: str | None = None, + order_id: str | None = None, + limit: int | None = None, + cursor: str | None = None, + min_ts: int | None = None, + max_ts: int | None = None, +) -> dict[str, Any]: + """Get fill history (executed trades).""" + try: + _validate_range("limit", limit, 1, 200) + api = _get_api() + data = await api.get_fills( + ticker=ticker, + order_id=order_id, + limit=limit, + cursor=cursor, + min_ts=min_ts, + max_ts=max_ts, + ) + fills = data.get("fills", []) + return _success( + "Fills fetched", + fills=fills, + summary={ + "total": len(fills), + "total_volume": sum(int(f.get("count") or 0) for f in fills), + "buys": len([f for f in fills if f.get("action") == "buy"]), + "sells": len([f for f in fills if f.get("action") == "sell"]), + }, + cursor=data.get("cursor"), + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_settlements", + description=( + "Get settlement history for resolved markets. " + "Parameters: ticker (str), event_ticker (str), limit (int, max 200), cursor (str), min_ts/max_ts (int). " + "Returns: JSON with fields: settlements, summary, cursor. " + "IMPORTANT: summary includes aggregate realized revenue. " + "Example: get_settlements(limit=20)." + ), +) +async def get_settlements( + ticker: str | None = None, + event_ticker: str | None = None, + limit: int | None = None, + cursor: str | None = None, + min_ts: int | None = None, + max_ts: int | None = None, +) -> dict[str, Any]: + """Get settlement history.""" + try: + _validate_range("limit", limit, 1, 200) + api = _get_api() + data = await api.get_settlements( + ticker=ticker, + event_ticker=event_ticker, + limit=limit, + cursor=cursor, + min_ts=min_ts, + max_ts=max_ts, + ) + settlements = data.get("settlements", []) + total_revenue = sum(int(s.get("revenue") or 0) for s in settlements) + return _success( + "Settlements fetched", + settlements=settlements, + summary={ + "total": len(settlements), + "total_revenue_cents": total_revenue, + "total_revenue_dollars": f"{total_revenue / 100:.2f}", + "profitable_settlements": len([s for s in settlements if int(s.get("revenue") or 0) > 0]), + }, + cursor=data.get("cursor"), + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "get_portfolio_summary", + description=( + "Get a complete portfolio overview in one call. " + "Parameters: none. " + "Returns: JSON with fields: balance_cents, balance_dollars, portfolio_value_cents, portfolio_value_dollars, positions, total_positions. " + "IMPORTANT: enriches positions with current market info when available. " + "Example: get_portfolio_summary()." + ), +) +async def get_portfolio_summary() -> dict[str, Any]: + try: + api = _get_api() + balance_data = await api.get_balance() + positions_data = await api.get_positions(limit=100) + positions = positions_data.get("market_positions", []) + + enriched = [] + for pos in positions: + count = int(pos.get("position", 0)) + if count == 0: + continue + ticker = pos.get("ticker", "") + market_info = {} + try: + market_resp = await api.get_market(ticker) + market_info = market_resp.get("market", {}) + except Exception: + pass + enriched.append( + { + "ticker": ticker, + "title": market_info.get("title", ""), + "side": "yes" if count > 0 else "no", + "count": abs(count), + "yes_price": market_info.get("yes_bid"), + "no_price": market_info.get("no_bid"), + "status": market_info.get("status", "unknown"), + } + ) + + balance = int(balance_data.get("balance", 0)) + portfolio_value = int(balance_data.get("portfolio_value", 0)) + return _success( + f"Portfolio: ${balance/100:.2f} cash, ${portfolio_value/100:.2f} portfolio value, {len(enriched)} open positions", + balance_cents=balance, + balance_dollars=f"{balance/100:.2f}", + portfolio_value_cents=portfolio_value, + portfolio_value_dollars=f"{portfolio_value/100:.2f}", + positions=enriched, + total_positions=len(enriched), + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error)) + + +@mcp.tool( + "watchlist_manage", + description=( + "Manage foreground watchlist metadata for Kalshi tickers. " + "Parameters: action (list|add|remove|clear), tickers (array for add/remove). " + "Returns: JSON with fields: watched_tickers. " + "IMPORTANT: foreground and background run in separate processes; background monitoring is based on its own state and active positions. " + "Example: watchlist_manage(action='add', tickers=['PRES-2028-DEM'])." + ), +) +async def watchlist_manage(action: str, tickers: list[str] | None = None) -> dict[str, Any]: + global _watched_tickers + normalized_action = action.strip().lower() + + if normalized_action == "list": + return _success( + f"Watching {len(_watched_tickers)} tickers", + watched_tickers=sorted(_watched_tickers), + ) + + if normalized_action == "add": + if not tickers: + return _error("tickers list required for add") + for ticker in tickers: + cleaned = ticker.strip().upper() + if cleaned: + _watched_tickers.add(cleaned) + return _success( + f"Added {len(tickers)} tickers, now watching {len(_watched_tickers)}", + watched_tickers=sorted(_watched_tickers), + ) + + if normalized_action == "remove": + if not tickers: + return _error("tickers list required for remove") + for ticker in tickers: + cleaned = ticker.strip().upper() + if cleaned: + _watched_tickers.discard(cleaned) + return _success( + f"Removed tickers, now watching {len(_watched_tickers)}", + watched_tickers=sorted(_watched_tickers), + ) + + if normalized_action == "clear": + _watched_tickers.clear() + return _success("Watchlist cleared", watched_tickers=[]) + + return _error(f"Unknown action: {action}. Use list, add, remove, or clear.") + + +@mcp.tool( + "kalshi_health", + description=( + "Check health of Kalshi API connection and credentials. " + "Parameters: none. " + "Returns: JSON with fields: api_healthy, balance_cents, balance_dollars (when healthy). " + "IMPORTANT: auth failures are reported to runtime as APP_ERROR_AUTH. " + "Example: kalshi_health()." + ), +) +async def kalshi_health() -> dict[str, Any]: + try: + api = _get_api() + data = await api.get_balance() + balance = int(data.get("balance", 0)) + return _success( + "Kalshi API healthy", + api_healthy=True, + balance_cents=balance, + balance_dollars=f"{balance / 100:.2f}", + ) + except httpx.HTTPStatusError as error: + return await _handle_api_error(error) + except Exception as error: + return _error(str(error), api_healthy=False) + + +def _cleanup() -> None: + global _api + if _api is None: + return + try: + asyncio.run(_api.close()) + except Exception: + pass + _api = None + + +def main() -> None: + atexit.register(_cleanup) + run_mcp_server(mcp, logger) + + +if __name__ == "__main__": + main() diff --git a/example-apps/kalshi/truffile.yaml b/example-apps/kalshi/truffile.yaml new file mode 100644 index 0000000..dfd59d8 --- /dev/null +++ b/example-apps/kalshi/truffile.yaml @@ -0,0 +1,92 @@ +metadata: + name: Kalshi + bundle_id: org.deepshard.kalshi + description: | + Have Truffle Trade and monitor Kalshi prediction markets for you. + icon_file: ./icon.png + background: + process: + cmd: + - python + - kalshi_background.py + working_directory: / + environment: + PYTHONUNBUFFERED: "1" + default_schedule: + type: interval + interval: + duration: 30m + prod_duration: 60m + schedule: + daily_window: "00:00-23:59" + foreground: + process: + cmd: + - python + - kalshi_foreground.py + working_directory: / + environment: + PYTHONUNBUFFERED: "1" + +steps: + # - name: Welcome + # type: welcome + # content: | + # Welcome to Kalshi for Truffle. + + # This app lets your Truffle trade prediction markets, monitor + # positions, and get alerts on price changes and settlements. + + - name: Install dependencies + type: bash + run: | + apk add --no-cache gcc musl-dev libffi-dev openssl-dev + pip install --no-cache-dir "httpx>=0.27.0" "cryptography>=42.0.0" + + - name: Copy application files + type: files + files: + - source: ./config.py + destination: ./config.py + - source: ./client.py + destination: ./client.py + - source: ./bg_worker.py + destination: ./bg_worker.py + - source: ./kalshi_foreground.py + destination: ./kalshi_foreground.py + - source: ./kalshi_background.py + destination: ./kalshi_background.py + + - name: Configure Kalshi API credentials + type: text + ui_state_on_show: user_interaction_ready + ui_state_on_complete: move_to_background + content: | + Kalshi lets your Truffle trade prediction markets, monitor + positions, and get alerts on price changes and settlements. + By installing this app you agree that Deepshard is not liable for any losses incurred. + + Enter your Kalshi API credentials. + To get these: + 1. Go to https://kalshi.com/account/profile and log in + 2. Scroll down to the "API Keys" section and click "Create Key" + 3. Copy the API Key and Private Key + 4. Paste the API Key and Private Key into the fields below + fields: + - name: kalshi_api_key + label: Kalshi API Key (UUID format) + type: password + placeholder: your key goes here + env: KALSHI_API_KEY + - name: kalshi_private_key + label: Kalshi Private Key (RSA PEM) + type: password + placeholder: "-----BEGIN RSA PRIVATE KEY-----" + env: KALSHI_PRIVATE_KEY + validator: + type: bash + run: | + python ./kalshi_background.py --verify + timeout: 120 + error_message: | + Could not authenticate with Kalshi using the provided credentials. diff --git a/example-apps/reddit/icon.png b/example-apps/reddit/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..202faab130d223208d350d0e214d38d017f893f5 GIT binary patch literal 110411 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 str: def load_reddit_config_from_env() -> "RedditConfig": subreddits_str = os.getenv("SUBREDDITS", "").strip() user_feed_url = os.getenv("USER_FEED_URL", "").strip() + if user_feed_url.lower() in {"", "none", "null", "undefined", "n/a"}: + user_feed_url = "" subreddits: List[str] = [] @@ -163,6 +166,7 @@ def _fetch_listing(after: Optional[str] = None) -> Tuple[List[RedditPost], Optio for img in d['preview']['images']: if 'source' in img and 'url' in img['source']: image_urls.append(img['source']['url'].replace("&", "&")) + print("got image urls from preview:", image_urls) if not image_urls: thumb = d.get("thumbnail") @@ -353,7 +357,7 @@ def test_once(): print("article images:", post.images[0] if post and len(post.images) else "None") print("-----") -TESTING_MODE = False +TESTING_MODE = os.getenv("REDDIT_TEST_MODE", "0").lower() in ("1", "true", "yes") def run_test(): global reddit_config @@ -370,7 +374,7 @@ def run_test(): print("Exiting.") break -def process_reddit_item(ctx: AmbientContext, item: RedditPost): +def process_reddit_item(ctx: BackgroundRunContext, item: RedditPost): title, url, item_id, subreddit, image_urls = ( item.title, item.article_url, @@ -380,15 +384,25 @@ def process_reddit_item(ctx: AmbientContext, item: RedditPost): ) logger.info(f"Processing Reddit item {item_id}: {title} ({url}) {subreddit if subreddit else ''} {image_urls if image_urls else ''}") link_content, comments = get_content_for_reddit_item(item) - ctx.bg.post_to_feed( - title=link_content.title if link_content and link_content.title else title, - body=link_content.text if link_content and link_content.text else f'**{subreddit}** {item.domain} {item.score}', - src_uri=url, - media_uris=link_content.images if link_content and link_content.images else (image_urls if image_urls else []), - content_timestamp = link_content.date if link_content and link_content.date else item.created_utc + content_str = "" + if link_content and link_content.text: + content_str = f" {link_content.title or "unknown"} \n {link_content.text} \n {str(link_content.date) or ""} {str(link_content.source_name) or ""} " + post_str = ( + f"From Reddit: **{subreddit}** {item.domain} {item.score} points, {item.num_comments} comments\n" + f"\t Title: {title}, Link: {url}\n" + f"{content_str}\n" + f"{"top comments: " if comments else ""} {[str(c) for c in comments[:10]] if comments else ''}\n" + "image urls: " + (", ".join(image_urls) if image_urls else "None Found") ) + uris = [] + if url: + uris.append(url) + if link_content and link_content.source_url and link_content.source_url != url: + uris.append(link_content.source_url) -def reddit_ambient(ctx: AmbientContext): + ctx.bg.submit_context(content=post_str, uris=uris, priority=BackgroundContext.PRIORITY_LOW) + +def reddit_ambient(ctx: BackgroundRunContext): for _ in range(3): item = _next_new_reddit_item() if not item: @@ -399,19 +413,6 @@ def reddit_ambient(ctx: AmbientContext): return - #post_body = f"{f'**{subreddit}**' if subreddit else ''} {item.score }\n" - # - - # if link_content and link_content.text: - # post_body += f"\n\n{link_content.text[:2000]}" - - # if comments: - # post_body += "\n\n**Top Comments:**\n" - # for c in comments: - # post_body += f"- @{c.author if c.author else 'unknown'}: {c.body[:300].replace('\n', ' ')}...\n" - - - if __name__ == "__main__": import sys @@ -424,9 +425,9 @@ def reddit_ambient(ctx: AmbientContext): if TESTING_MODE: run_test() else: - run_ambient(reddit_ambient) + run_background(reddit_ambient) # :cp ./apps/ambient/reddit.py /app.py # :cp /home/dylan/ds/3fw/python/dist/gourmet-0.1.dev0-py3-none-any.whl /tmp/gourmet-0.1.dev0-py3-none-any.whl -# pip install /tmp/gourmet-0.1.dev0-py3-none-any.whl[abrasive] \ No newline at end of file +# pip install /tmp/gourmet-0.1.dev0-py3-none-any.whl[abrasive] diff --git a/example-apps/ambient/reddit/truffile.yaml b/example-apps/reddit/truffile.yaml similarity index 54% rename from example-apps/ambient/reddit/truffile.yaml rename to example-apps/reddit/truffile.yaml index 3162af9..4ceabe6 100644 --- a/example-apps/ambient/reddit/truffile.yaml +++ b/example-apps/reddit/truffile.yaml @@ -1,30 +1,31 @@ metadata: - name: Front Page of the Internet - type: background + name: Reddit + bundle_id: org.deepshard.reddit description: | Have your Truffle¹ browse Reddit and post relevant content to your feed. - process: - cmd: - - python - - /opt/reddit.py - working_directory: / - environment: - PYTHONUNBUFFERED: "1" - - icon_file: ./icon.png - default_schedule: - type: interval - interval: - duration: 15m - schedule: - daily_window: "01:00-22:30" + background: + process: + cmd: + - python + - /opt/reddit.py + working_directory: / + environment: + PYTHONUNBUFFERED: "1" + default_schedule: + type: interval + interval: + duration: 60m + prod_duration: 60m + schedule: + daily_window: "00:00-23:59" + icon_file: ./icon.png steps: - - name: Reddit Setup - type: welcome - content: | - The Reddit app will allow your Truffle to post relevant content from Reddit on your feed. - You can customize which subreddits to follow in the configuration step. - Please use this app in accordance with Reddit's terms of service. + # - name: Reddit Setup + # type: welcome + # content: | + # The Reddit app will allow your Truffle to post relevant content from Reddit on your feed. + # You can customize which subreddits to follow in the configuration step. + # Please use this app in accordance with Reddit's terms of service. - name: Install dependencies type: bash run: | @@ -36,23 +37,27 @@ steps: destination: ./opt/reddit.py - name: Configure Reddit type: text + ui_state_on_show: user_interaction_ready + ui_state_on_complete: move_to_background content: | Please provide either a comma separated list of subreddits to follow, or leave blank to follow r/all. Example: news, worldnews, technology, science, funny, pics, videos, gaming Alternatively, [copy the URL of your personal frontpage RSS feed (JSON) from here](https://old.reddit.com/prefs/feeds/) - If a personal feed URL is provided, that will be used instead of the subreddit list if the URL is valid. + If a personal feed URL is provided and is valid, that will be used instead of the subreddit list. fields: - name: subreddits label: Comma Separated Subreddits type: text placeholder: news, worldnews, technology, LosAngeles - default: all + default: "" + env_default_if_empty: "none" env: SUBREDDITS - name: user_feed_url label: Optional - Personal Frontpage RSS Feed URL (JSON) type: text - placeholder: - default: + placeholder: "Optional: leave blank to use subreddits (or paste https://old.reddit.com/.json?feed=...&user=...)" + default: "" + env_default_if_empty: "none" env: USER_FEED_URL validator: type: bash @@ -67,4 +72,4 @@ steps: - \ No newline at end of file + diff --git a/example-apps/truffile-example.yaml b/example-apps/truffile-example.yaml deleted file mode 100644 index 0e38580..0000000 --- a/example-apps/truffile-example.yaml +++ /dev/null @@ -1,76 +0,0 @@ -metadata: - name: Twitter App - type: background - process: - cmd: - - python - - app.py - working_directory: / - environment: - PYTHONUNBUFFERED: "1" - icon_file: ./icon.png - default_schedule: - type: interval - interval: - duration: 1h - schedule: - daily_window: "09:00-17:30" # optional - allowed_days: [mon, tue, wed, thu, fri] # optional, default = all days -steps: - - name: Copy application files - type: files - files: - - source: ./requirements.txt - destination: ./requirements.txt - - source: ./app.py - destination: ./app.py - - source: ./config.yaml - destination: ./config.yaml - permissions: 600 # optional - - name: Install dependencies - type: bash - run: | - pip install requests - apk add --no-cache btop - - name: Sign into X - type: vnc - cmd: - - python - - app.py - - --install - closes_on_complete: true - description: | - A VNC window will open. Please sign into your X account to continue the installation. - - name: Configure Email Account - type: text - content: | - Please provide an IMAP server, email address, and password to configure your email account. - fields: - - name: imap_server - label: IMAP Server - type: text # one of: text, password, number - placeholder: imap.example.com #optional - default: imap.gmail.com #optional - env: IMAP_SERVER - - name: email_address - label: Email Address - type: text - env: EMAIL_ADDRESS - placeholder: dude@wheresmycar.com - - name: password - label: Password - type: password - env: EMAIL_PASSWORD - validator: - cmd: - - python - - validate_email.py - args: - - ${inputs.imap_server} - - ${inputs.email_address} - - ${inputs.password} - - - - - \ No newline at end of file From 0212865ef9b1b17cbbc31f790b0699a040718f17 Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 21:30:37 -0800 Subject: [PATCH 3/9] sdk --- truffile/_version.py | 34 +++ truffile/deploy/__init__.py | 3 + truffile/deploy/builder.py | 201 ++++++++++++++ truffile/schema/__init__.py | 4 + truffile/schema/app_config.py | 131 +++++++++ truffile/schema/runtime_policy.py | 215 +++++++++++++++ truffile/transport/__init__.py | 15 ++ truffile/transport/client.py | 425 ++++++++++++++++++++++++++++++ 8 files changed, 1028 insertions(+) create mode 100644 truffile/_version.py create mode 100644 truffile/deploy/__init__.py create mode 100644 truffile/deploy/builder.py create mode 100644 truffile/schema/__init__.py create mode 100644 truffile/schema/app_config.py create mode 100644 truffile/schema/runtime_policy.py create mode 100644 truffile/transport/__init__.py create mode 100644 truffile/transport/client.py diff --git a/truffile/_version.py b/truffile/_version.py new file mode 100644 index 0000000..7442fa8 --- /dev/null +++ b/truffile/_version.py @@ -0,0 +1,34 @@ +# file generated by setuptools-scm +# don't change, don't track in version control + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple + from typing import Union + + VERSION_TUPLE = Tuple[Union[int, str], ...] + COMMIT_ID = Union[str, None] +else: + VERSION_TUPLE = object + COMMIT_ID = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE +commit_id: COMMIT_ID +__commit_id__: COMMIT_ID + +__version__ = version = '0.1.15.dev0' +__version_tuple__ = version_tuple = (0, 1, 15, 'dev0') + +__commit_id__ = commit_id = 'g599e9fda6' diff --git a/truffile/deploy/__init__.py b/truffile/deploy/__init__.py new file mode 100644 index 0000000..6c58e9d --- /dev/null +++ b/truffile/deploy/__init__.py @@ -0,0 +1,3 @@ +from .builder import deploy_with_builder + +__all__ = ["deploy_with_builder"] diff --git a/truffile/deploy/builder.py b/truffile/deploy/builder.py new file mode 100644 index 0000000..57e5026 --- /dev/null +++ b/truffile/deploy/builder.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +import asyncio +import json +from pathlib import Path +from typing import Any, Callable + +from truffile.transport.client import TruffleClient + + +def _normalize_cmd(cmd_list: list[str]) -> tuple[str, list[str]]: + cmd = cmd_list[0] if cmd_list[0].startswith("/") else f"/usr/bin/{cmd_list[0]}" + return cmd, cmd_list[1:] + + +def _env_map_to_list(env_dict: dict[str, str] | None) -> list[str]: + if not env_dict: + return [] + return [f"{k}={v}" for k, v in env_dict.items()] + + +def _bundle_id_from_name(name: str) -> str: + raw = "".join(ch.lower() if ch.isalnum() else "." for ch in name).strip(".") + normalized = ".".join([part for part in raw.split(".") if part]) + return normalized or "truffle.app" + + +def _extract_process(process_cfg: dict[str, Any] | None) -> tuple[str, list[str], str, list[str]]: + proc = process_cfg or {} + cmd_list = list(proc.get("cmd", ["python", "app.py"])) + cmd, args = _normalize_cmd(cmd_list) + cwd = proc.get("working_directory", proc.get("cwd", "/")) + env = _env_map_to_list(proc.get("environment", proc.get("env"))) + return cmd, args, cwd, env + + +async def _wait_for_build_session_ready(client: TruffleClient, timeout_sec: float = 45.0) -> None: + deadline = asyncio.get_event_loop().time() + timeout_sec + last_error: Exception | None = None + while asyncio.get_event_loop().time() < deadline: + try: + result = await client.exec("echo ready", cwd="/") + if result.exit_code == 0: + return + except Exception as e: + last_error = e + await asyncio.sleep(1.0) + if last_error is not None: + raise RuntimeError(f"build session endpoint did not become ready in time: {last_error}") + raise RuntimeError("build session endpoint did not become ready in time") + + +async def deploy_with_builder( + *, + client: TruffleClient, + config: dict[str, Any], + app_dir: Path, + app_type: str, + device: str, + interactive: bool, + spinner_cls: Any, + scrolling_log_cls: Any, + info: Callable[[str], None], + success: Callable[[str], None], + error: Callable[[str], None], + color_dim: str, + color_reset: str, + color_bold: str, + arrow: str, + interactive_shell: Callable[[str], Any], +) -> int: + meta = config["metadata"] + name = meta["name"] + description = meta.get("description", "") + bundle_id = meta.get("bundle_id") or _bundle_id_from_name(name) + icon_file = meta.get("icon_file") + icon_path = (app_dir / icon_file) if icon_file and (app_dir / icon_file).exists() else None + + fg_cfg = meta.get("foreground") + bg_cfg = meta.get("background") + new_style = isinstance(fg_cfg, dict) or isinstance(bg_cfg, dict) + + if new_style: + has_fg = isinstance(fg_cfg, dict) + has_bg = isinstance(bg_cfg, dict) + else: + has_fg = app_type == "focus" + has_bg = app_type == "ambient" + + if not has_fg and not has_bg: + raise RuntimeError("App must define foreground and/or background process config") + + fg_payload = None + bg_payload = None + exec_cwd = "/" + if has_fg: + fg_process = fg_cfg.get("process") if isinstance(fg_cfg, dict) else meta.get("process") + fg_cmd, fg_args, fg_cwd, fg_env = _extract_process(fg_process) + fg_payload = {"cmd": fg_cmd, "args": fg_args, "cwd": fg_cwd, "env": fg_env} + exec_cwd = fg_cwd + if has_bg: + bg_process = bg_cfg.get("process") if isinstance(bg_cfg, dict) else meta.get("process") + bg_cmd, bg_args, bg_cwd, bg_env = _extract_process(bg_process) + bg_payload = {"cmd": bg_cmd, "args": bg_args, "cwd": bg_cwd, "env": bg_env} + if exec_cwd == "/" and bg_cwd: + exec_cwd = bg_cwd + + spinner = spinner_cls(f"Connecting to {device}") + spinner.start() + await client.connect() + spinner.stop(success=True) + + spinner = spinner_cls("Starting build session") + spinner.start() + await client.start_build() + await _wait_for_build_session_ready(client) + spinner.stop(success=True) + print(f" {color_dim}Session: {client.app_uuid}{color_reset}") + + files_to_upload = [] + for step in config.get("steps", []): + if step.get("type") == "files": + files_to_upload.extend(step.get("files", [])) + files_to_upload.extend(config.get("files", [])) + + for f in files_to_upload: + src = app_dir / f["source"] + dest = f["destination"] + spinner = spinner_cls(f"Uploading {src.name} {arrow} {dest}") + spinner.start() + result = await client.upload(src, dest) + spinner.stop(success=True) + print(f" {color_dim}{result.bytes} bytes, sha256={result.sha256[:12]}...{color_reset}") + + bash_commands = [] + for step in config.get("steps", []): + if step.get("type") == "bash": + bash_commands.append((step.get("name", "bash"), step["run"])) + if config.get("run"): + bash_commands.append(("Install dependencies", config["run"])) + + for step_name, run_cmd in bash_commands: + info(f"Running: {step_name}") + log = scrolling_log_cls(height=6, prefix=" ") + exit_code = 0 + async for ev, data in client.exec_stream(run_cmd, cwd=exec_cwd): + if ev == "log": + try: + import json + obj = json.loads(data) + line = obj.get("line", "") + except Exception: + line = data + log.add(line) + elif ev == "exit": + try: + import json + exit_code = int(json.loads(data).get("code", 0)) + except (ValueError, KeyError): + pass + log.finish() + if exit_code != 0: + error(f"Step '{step_name}' failed with exit code {exit_code}") + raise RuntimeError(f"Step '{step_name}' failed with exit code {exit_code}") + + if interactive: + print() + info("Opening interactive shell (exit with Ctrl+D or 'exit' to finish deploy)") + ws_url = str(client.http_base or "").replace("http://", "ws://").replace("https://", "wss://") + "/term" + await interactive_shell(ws_url) + print() + + if has_fg and has_bg: + finish_label = "foreground+background" + elif has_fg: + finish_label = "foreground" + else: + finish_label = "background" + spinner = spinner_cls(f"Finishing as {finish_label} app") + spinner.start() + + default_schedule = None + if isinstance(bg_cfg, dict): + default_schedule = bg_cfg.get("default_schedule") + elif has_bg: + default_schedule = meta.get("default_schedule") + + await client.finish_app( + name=name, + bundle_id=bundle_id, + description=description, + icon=icon_path, + foreground=fg_payload, + background=bg_payload, + default_schedule=default_schedule, + ) + + spinner.stop(success=True) + print() + success(f"Deployed: {color_bold}{name}{color_reset} ({finish_label})") + return 0 diff --git a/truffile/schema/__init__.py b/truffile/schema/__init__.py new file mode 100644 index 0000000..278b425 --- /dev/null +++ b/truffile/schema/__init__.py @@ -0,0 +1,4 @@ +from .app_config import validate_app_dir +from .runtime_policy import parse_runtime_policy + +__all__ = ["validate_app_dir", "parse_runtime_policy"] diff --git a/truffile/schema/app_config.py b/truffile/schema/app_config.py new file mode 100644 index 0000000..53d9061 --- /dev/null +++ b/truffile/schema/app_config.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import ast +from pathlib import Path +from typing import Any + +import yaml + + +def _check_python_syntax(file_path: Path) -> tuple[bool, str]: + try: + source = file_path.read_text(encoding="utf-8") + ast.parse(source) + return True, "" + except SyntaxError as e: + return False, f"Line {e.lineno}: {e.msg}" + + +def validate_app_dir(app_dir: Path) -> tuple[bool, dict[str, Any] | None, str | None, list[str], list[str]]: + """Validate app directory and return (valid, config, app_type, warnings, errors).""" + warnings: list[str] = [] + errors: list[str] = [] + + truffile_path = app_dir / "truffile.yaml" + if not truffile_path.exists(): + errors.append(f"No truffile.yaml found in {app_dir}") + return False, None, None, warnings, errors + + try: + config = yaml.safe_load(truffile_path.read_text(encoding="utf-8")) + except yaml.YAMLError as e: + errors.append(f"Invalid truffile.yaml: {e}") + return False, None, None, warnings, errors + + if not isinstance(config, dict): + errors.append("truffile.yaml root must be a mapping") + return False, None, None, warnings, errors + + meta = config.get("metadata", {}) + if not isinstance(meta, dict): + errors.append("metadata must be a mapping") + return False, None, None, warnings, errors + + if not meta.get("name"): + errors.append("metadata.name is required in truffile.yaml") + return False, None, None, warnings, errors + + fg_cfg = meta.get("foreground") + bg_cfg = meta.get("background") + has_fg_cfg = isinstance(fg_cfg, dict) + has_bg_cfg = isinstance(bg_cfg, dict) + if has_fg_cfg or has_bg_cfg: + if has_fg_cfg and has_bg_cfg: + app_type = "hybrid" + elif has_fg_cfg: + app_type = "focus" + else: + app_type = "ambient" + else: + cfg_type = str(meta.get("type", "")).lower().strip() + if cfg_type in ("background", "ambient"): + app_type = "ambient" + elif cfg_type in ("foreground", "focus"): + app_type = "focus" + else: + app_type = "focus" + warnings.append("No type specified in truffile.yaml, defaulting to focus") + + if "bundle_id" not in meta: + warnings.append("No metadata.bundle_id specified; using derived default from metadata.name") + + if has_fg_cfg: + process = fg_cfg.get("process") + if not isinstance(process, dict): + errors.append("metadata.foreground.process must be an object") + elif not isinstance(process.get("cmd"), list) or len(process.get("cmd", [])) == 0: + errors.append("metadata.foreground.process.cmd must be a non-empty list") + + if has_bg_cfg: + process = bg_cfg.get("process") + if not isinstance(process, dict): + errors.append("metadata.background.process must be an object") + elif not isinstance(process.get("cmd"), list) or len(process.get("cmd", [])) == 0: + errors.append("metadata.background.process.cmd must be a non-empty list") + if not isinstance(bg_cfg.get("default_schedule"), dict): + errors.append("metadata.background.default_schedule must be an object") + if not has_fg_cfg and not has_bg_cfg: + process = meta.get("process") + if not isinstance(process, dict): + errors.append("metadata.process must be an object") + elif not isinstance(process.get("cmd"), list) or len(process.get("cmd", [])) == 0: + errors.append("metadata.process.cmd must be a non-empty list") + if app_type == "ambient" and "default_schedule" in meta and not isinstance(meta.get("default_schedule"), dict): + errors.append("metadata.default_schedule must be an object when provided") + + icon_file = meta.get("icon_file") + if icon_file: + icon_path = app_dir / str(icon_file) + if not icon_path.exists(): + warnings.append(f"Icon file not found: {icon_file}") + else: + warnings.append("No icon specified in truffile.yaml") + + files_to_check: list[dict[str, Any]] = [] + for step in config.get("steps", []): + if isinstance(step, dict) and step.get("type") == "files": + step_files = step.get("files", []) + if isinstance(step_files, list): + files_to_check.extend([f for f in step_files if isinstance(f, dict)]) + + top_files = config.get("files", []) + if isinstance(top_files, list): + files_to_check.extend([f for f in top_files if isinstance(f, dict)]) + + for f in files_to_check: + source = f.get("source") + if not isinstance(source, str): + errors.append("files entries must include a string 'source'") + continue + + src = app_dir / source + if not src.exists(): + errors.append(f"Source file not found: {src}") + continue + + if src.suffix == ".py": + ok, err = _check_python_syntax(src) + if not ok: + errors.append(f"Syntax error in {src.name}: {err}") + + return len(errors) == 0, config, app_type, warnings, errors diff --git a/truffile/schema/runtime_policy.py b/truffile/schema/runtime_policy.py new file mode 100644 index 0000000..bb90bb9 --- /dev/null +++ b/truffile/schema/runtime_policy.py @@ -0,0 +1,215 @@ +import re +from typing import Any, Dict, List, Optional, Tuple +from google.protobuf.duration_pb2 import Duration +from truffle.app import background_pb2 + +_DAY_BIT = { + "sat": 0, + "fri": 1, + "thu": 2, + "wed": 3, + "tue": 4, + "mon": 5, + "sun": 6, +} + +_TIME_RE = re.compile(r"^\s*(\d{1,2}):(\d{2})(?::(\d{2}))?\s*$") + +def _parse_time_of_day(s: str, *, ctx: str): + m = _TIME_RE.match(s or "") + if not m: + raise ValueError(f"{ctx}: invalid time '{s}', expected HH:MM or HH:MM:SS") + hh = int(m.group(1)) + mm = int(m.group(2)) + ss = int(m.group(3) or "0") + if not (0 <= hh <= 23): raise ValueError(f"{ctx}: hour out of range: {hh}") + if not (0 <= mm <= 59): raise ValueError(f"{ctx}: minute out of range: {mm}") + if not (0 <= ss <= 59): raise ValueError(f"{ctx}: second out of range: {ss}") + return hh, mm, ss + +def _set_time_of_day(msg_time_of_day, s: str, *, ctx: str) -> None: + hh, mm, ss = _parse_time_of_day(s, ctx=ctx) + msg_time_of_day.hour = hh + msg_time_of_day.minute = mm + msg_time_of_day.second = ss + +def _parse_daily_window(v: Any, *, ctx: str) -> Optional[Tuple[str, str]]: + if v is None: + return None + if isinstance(v, str): + parts = v.split("-", 1) + if len(parts) != 2: + raise ValueError(f"{ctx}: daily_window must be 'HH:MM-HH:MM[:SS]'") + start_s = parts[0].strip() + end_s = parts[1].strip() + _parse_time_of_day(start_s, ctx=f"{ctx}.daily_window start") + _parse_time_of_day(end_s, ctx=f"{ctx}.daily_window end") + return start_s, end_s + if isinstance(v, dict): + start_s = v.get("start") + end_s = v.get("end") + if not isinstance(start_s, str) or not isinstance(end_s, str): + raise ValueError(f"{ctx}: daily_window dict must have string start/end") + _parse_time_of_day(start_s, ctx=f"{ctx}.daily_window start") + _parse_time_of_day(end_s, ctx=f"{ctx}.daily_window end") + return start_s, end_s + raise ValueError(f"{ctx}: daily_window must be string or object") + +def _day_mask_from_allowed_days(days: List[str], *, ctx: str) -> int: + forbidden = 0 + allowed_bits = set() + for d in days: + if not isinstance(d, str): + raise ValueError(f"{ctx}: day entries must be strings") + k = d.strip().lower()[:3] + if k not in _DAY_BIT: + raise ValueError(f"{ctx}: unknown day '{d}' (use sun/mon/tue/wed/thu/fri/sat)") + allowed_bits.add(_DAY_BIT[k]) + if not allowed_bits: + raise ValueError(f"{ctx}: allowed_days cannot be empty") + for k, bit in _DAY_BIT.items(): + if bit not in allowed_bits: + forbidden |= (1 << bit) + return forbidden + +def _day_mask_from_forbidden_days(days: List[str], *, ctx: str) -> int: + forbidden = 0 + for d in days: + if not isinstance(d, str): + raise ValueError(f"{ctx}: day entries must be strings") + k = d.strip().lower()[:3] + if k not in _DAY_BIT: + raise ValueError(f"{ctx}: unknown day '{d}' (use sun/mon/tue/wed/thu/fri/sat)") + forbidden |= (1 << _DAY_BIT[k]) + if forbidden == 0b1111111: + raise ValueError(f"{ctx}: forbidden_days forbids all days (invalid)") + return forbidden + +_DUR_RE = re.compile(r"^\s*(\d+)\s*(ms|s|m|h|d)\s*$", re.IGNORECASE) + +def _parse_duration(s: str, *, ctx: str) -> Duration: + if not isinstance(s, str): + raise ValueError(f"{ctx}: duration must be a string like '15m' or '2h'") + m = _DUR_RE.match(s) + if not m: + raise ValueError(f"{ctx}: invalid duration '{s}' (use ms/s/m/h/d)") + n = int(m.group(1)) + unit = m.group(2).lower() + seconds = 0 + nanos = 0 + if unit == "ms": + seconds = n // 1000 + nanos = (n % 1000) * 1_000_000 + elif unit == "s": + seconds = n + elif unit == "m": + seconds = n * 60 + elif unit == "h": + seconds = n * 3600 + elif unit == "d": + seconds = n * 86400 + dur = Duration() + dur.seconds = seconds + dur.nanos = nanos + return dur + + +def parse_runtime_policy(schedule_cfg_data: Dict[str, Any]) -> background_pb2.BackgroundAppRuntimePolicy: + if not isinstance(schedule_cfg_data, dict): + raise ValueError("default_schedule must be an object") + + policy_type = schedule_cfg_data.get("type") + if policy_type not in ("interval", "times", "always"): + raise ValueError(f"Invalid default_schedule.type: {policy_type}") + + runtime_policy = background_pb2.BackgroundAppRuntimePolicy() + + if policy_type == "always": + runtime_policy.always.SetInParent() + return runtime_policy + + if policy_type == "interval": + interval_obj = schedule_cfg_data.get("interval") + if not isinstance(interval_obj, dict): + raise ValueError("default_schedule.interval must be an object") + + dur_s = interval_obj.get("duration", None) + if not isinstance(dur_s, str): + raise ValueError("default_schedule.interval.duration must be a string") + runtime_policy.interval.duration.CopyFrom(_parse_duration(dur_s, ctx="default_schedule.interval.duration")) + + sched = interval_obj.get("schedule", {}) + if sched is None: + sched = {} + if not isinstance(sched, dict): + raise ValueError("default_schedule.interval.schedule must be an object") + + allowed_days = sched.get("allowed_days") + forbidden_days = sched.get("forbidden_days") + if allowed_days is not None and forbidden_days is not None: + raise ValueError("Provide only one of schedule.allowed_days or schedule.forbidden_days") + + if allowed_days is not None: + if not isinstance(allowed_days, list): + raise ValueError("schedule.allowed_days must be a list") + runtime_policy.interval.schedule.weekly_window.day_mask = _day_mask_from_allowed_days( + allowed_days, ctx="default_schedule.interval.schedule.allowed_days" + ) + elif forbidden_days is not None: + if not isinstance(forbidden_days, list): + raise ValueError("schedule.forbidden_days must be a list") + runtime_policy.interval.schedule.weekly_window.day_mask = _day_mask_from_forbidden_days( + forbidden_days, ctx="default_schedule.interval.schedule.forbidden_days" + ) + else: + runtime_policy.interval.schedule.weekly_window.day_mask = 0 + + dw = _parse_daily_window(sched.get("daily_window"), ctx="default_schedule.interval.schedule") + if dw is not None: + start_s, end_s = dw + runtime_policy.interval.schedule.daily_window.SetInParent() + _set_time_of_day(runtime_policy.interval.schedule.daily_window.daily_start_time, start_s, + ctx="default_schedule.interval.schedule.daily_window.start") + _set_time_of_day(runtime_policy.interval.schedule.daily_window.daily_end_time, end_s, + ctx="default_schedule.interval.schedule.daily_window.end") + + return runtime_policy + + if policy_type == "times": + times_obj = schedule_cfg_data.get("times") + if not isinstance(times_obj, dict): + raise ValueError("default_schedule.times must be an object") + + run_times = times_obj.get("run_times") + if not isinstance(run_times, list) or not run_times: + raise ValueError("default_schedule.times.run_times must be a non-empty list of time strings") + + for i, t in enumerate(run_times): + if not isinstance(t, str): + raise ValueError("default_schedule.times.run_times must contain strings") + tod = runtime_policy.times.run_times.add() + _set_time_of_day(tod, t, ctx=f"default_schedule.times.run_times[{i}]") + + allowed_days = times_obj.get("allowed_days") + forbidden_days = times_obj.get("forbidden_days") + if allowed_days is not None and forbidden_days is not None: + raise ValueError("Provide only one of times.allowed_days or times.forbidden_days") + + if allowed_days is not None: + if not isinstance(allowed_days, list): + raise ValueError("times.allowed_days must be a list") + runtime_policy.times.weekly_window.day_mask = _day_mask_from_allowed_days( + allowed_days, ctx="default_schedule.times.allowed_days" + ) + elif forbidden_days is not None: + if not isinstance(forbidden_days, list): + raise ValueError("times.forbidden_days must be a list") + runtime_policy.times.weekly_window.day_mask = _day_mask_from_forbidden_days( + forbidden_days, ctx="default_schedule.times.forbidden_days" + ) + else: + runtime_policy.times.weekly_window.day_mask = 0 + + return runtime_policy + + raise RuntimeError("unreachable") diff --git a/truffile/transport/__init__.py b/truffile/transport/__init__.py new file mode 100644 index 0000000..60b0d18 --- /dev/null +++ b/truffile/transport/__init__.py @@ -0,0 +1,15 @@ +from .client import ( + ExecResult, + NewSessionStatus, + TruffleClient, + UploadResult, + resolve_mdns, +) + +__all__ = [ + "ExecResult", + "NewSessionStatus", + "TruffleClient", + "UploadResult", + "resolve_mdns", +] diff --git a/truffile/transport/client.py b/truffile/transport/client.py new file mode 100644 index 0000000..e88b123 --- /dev/null +++ b/truffile/transport/client.py @@ -0,0 +1,425 @@ +import asyncio +import json +import platform +import socket +from dataclasses import dataclass +from pathlib import Path +from typing import AsyncIterator +import grpc +from grpc import aio +import httpx +from google.protobuf import empty_pb2 +from truffle.os.truffleos_pb2_grpc import TruffleOSStub +from truffle.os.builder_pb2 import ( + StartBuildSessionRequest, + StartBuildSessionResponse, + FinishBuildSessionRequest, + FinishBuildSessionResponse, +) +from truffle.os.client_session_pb2 import ( + RegisterNewSessionRequest, + RegisterNewSessionResponse, + NewSessionStatus, +) +from truffle.os.client_metadata_pb2 import ClientMetadata +from truffle.os.app_queries_pb2 import GetAllAppsRequest, GetAllAppsResponse, DeleteAppRequest, DeleteAppResponse +from truffle.app.app_pb2 import App +from truffle.app.background_pb2 import BackgroundApp, BackgroundAppRuntimePolicy +from truffile.schedule import parse_runtime_policy + + +def get_client_metadata() -> ClientMetadata: + from truffile import __version__ + metadata = ClientMetadata() + metadata.device = platform.node() + metadata.platform = platform.platform() + metadata.version = f"truffile-{__version__}-{platform.python_version()}" + return metadata + + +async def resolve_mdns(hostname: str) -> str: + if ".local" not in hostname: + return hostname + loop = asyncio.get_event_loop() + try: + resolved = await loop.run_in_executor(None, socket.gethostbyname, hostname) + return resolved + except socket.gaierror as e: + raise RuntimeError(f"Failed to resolve {hostname} - is the device on the same network? ({e})") + + +@dataclass +class ExecResult: + exit_code: int + output: list[str] + + +@dataclass +class UploadResult: + path: str + bytes: int + sha256: str + + +class TruffleClient: + def __init__(self, address: str, token: str): + self.address = address + self.token = token + self.channel: aio.Channel | None = None + self.stub: TruffleOSStub | None = None + self.app_uuid: str | None = None + self.access_path: str | None = None + + @property + def http_base(self) -> str | None: + if not self.access_path: + return None + host = self.address if "://" in self.address else f"http://{self.address}" + return f"{host}/containers/{self.access_path}" + + @property + def _metadata(self) -> list: + return [("session", self.token)] + + async def connect(self, timeout: float = 15.0): + self.channel = aio.insecure_channel(self.address) + await asyncio.wait_for(self.channel.channel_ready(), timeout=timeout) + self.stub = TruffleOSStub(self.channel) + + def update_token(self, token: str): + self.token = token + + async def check_auth(self) -> bool: + if not self.stub or not self.token: + return False + try: + await self.stub.System_GetInfo(empty_pb2.Empty(), metadata=self._metadata) + return True + except aio.AioRpcError as e: + if e.code() == grpc.StatusCode.UNAUTHENTICATED: + return False + raise + + async def register_new_session(self, user_id: str) -> tuple[NewSessionStatus, str | None]: + if not self.stub: + raise RuntimeError("not connected") + req = RegisterNewSessionRequest() + req.user_id = user_id + req.metadata.CopyFrom(get_client_metadata()) + resp: RegisterNewSessionResponse = await self.stub.Client_RegisterNewSession(req) + if resp.status.error == NewSessionStatus.NEW_SESSION_SUCCESS: + self.token = resp.token + return resp.status, resp.token + return resp.status, None + + async def get_all_apps(self) -> list[App]: + if not self.stub: + raise RuntimeError("not connected") + req = GetAllAppsRequest() + resp: GetAllAppsResponse = await self.stub.Apps_GetAll(req, metadata=self._metadata) + return list(resp.apps) + + async def delete_app(self, app_uuid: str) -> DeleteAppResponse: + if not self.stub: + raise RuntimeError("not connected") + req = DeleteAppRequest() + req.app_uuid = app_uuid + resp: DeleteAppResponse = await self.stub.Apps_DeleteApp(req, metadata=self._metadata) + return resp + + async def start_build(self) -> StartBuildSessionResponse: + if not self.stub: + raise RuntimeError("not connected") + req = StartBuildSessionRequest() + resp: StartBuildSessionResponse = await self.stub.Builder_StartBuildSession( + req, metadata=self._metadata + ) + self.app_uuid = resp.app_uuid + self.access_path = resp.access_path + return resp + + @staticmethod + def _build_bundle_id(name: str) -> str: + raw = "".join(ch.lower() if ch.isalnum() else "." for ch in name).strip(".") + normalized = ".".join([part for part in raw.split(".") if part]) + return normalized or "truffle.app" + + def _apply_metadata( + self, + *, + req: FinishBuildSessionRequest, + name: str, + bundle_id: str | None, + description: str, + icon: str | Path | bytes | None, + ) -> None: + req.metadata.name = name + req.metadata.bundle_id = (bundle_id or self._build_bundle_id(name)).strip() + if description: + req.metadata.description = description + icon_data = self._load_icon(icon) + if icon_data: + req.metadata.icon.png_data = icon_data + + @staticmethod + def _apply_process(process_pb, *, cmd: str, args: list[str], cwd: str, env: list[str] | None) -> None: + process_pb.cmd = cmd + process_pb.args.extend(args) + if env: + process_pb.env.extend(env) + process_pb.cwd = cwd + + async def _sse_events(self, client: httpx.AsyncClient, url: str, body: dict) -> AsyncIterator[tuple[str, str]]: + async with client.stream("POST", url, json=body, timeout=None) as r: + r.raise_for_status() + event = "message" + data_parts = [] + async for raw in r.aiter_lines(): + if raw is None: + continue + line = raw.rstrip("\r") + if line == "": + if data_parts: + yield event, "\n".join(data_parts) + event, data_parts = "message", [] + continue + if line.startswith(":"): + continue + if line.startswith("event:"): + event = line[6:].strip() + elif line.startswith("data:"): + data_parts.append(line[5:].lstrip()) + if data_parts: + yield event, "\n".join(data_parts) + + async def exec(self, cmd: str, cwd: str = "/") -> ExecResult: + if not self.http_base: + raise RuntimeError("no active build session") + url = f"{self.http_base}/exec/stream" + body = {"cmd": ["bash", "-lc", f"cd {cwd} && {cmd}"], "cwd": cwd} + output = [] + exit_code = 0 + retries = 5 + backoff = 1.0 + async with httpx.AsyncClient(timeout=None) as client: + for attempt in range(retries): + try: + async for ev, data in self._sse_events(client, url, body): + if ev == "log": + try: + obj = json.loads(data) + line = obj.get("line", "") + except Exception: + line = data + output.append(line) + elif ev == "exit": + try: + exit_code = int(json.loads(data).get("code", 0)) + except Exception: + pass + return ExecResult(exit_code=exit_code, output=output) + except httpx.HTTPStatusError as e: + if e.response.status_code == 503 and attempt < retries - 1: + await asyncio.sleep(backoff * (attempt + 1)) + continue + raise + return ExecResult(exit_code=exit_code, output=output) + + async def exec_stream(self, cmd: str, cwd: str = "/") -> AsyncIterator[tuple[str, str]]: + if not self.http_base: + raise RuntimeError("no active build session") + url = f"{self.http_base}/exec/stream" + body = {"cmd": ["bash", "-lc", f"cd {cwd} && {cmd}"], "cwd": cwd} + retries = 5 + backoff = 1.0 + async with httpx.AsyncClient(timeout=None) as client: + for attempt in range(retries): + try: + async for ev, data in self._sse_events(client, url, body): + yield ev, data + return + except httpx.HTTPStatusError as e: + if e.response.status_code == 503 and attempt < retries - 1: + await asyncio.sleep(backoff * (attempt + 1)) + continue + raise + + async def upload(self, src: str | Path, dest: str) -> UploadResult: + if not self.http_base: + raise RuntimeError("no active build session") + path = Path(src).expanduser() + if not path.exists() or not path.is_file(): + raise FileNotFoundError(f"no such file: {path}") + url = f"{self.http_base}/upload" + retries = 5 + backoff = 1.0 + async with httpx.AsyncClient(timeout=None) as client: + for attempt in range(retries): + try: + with path.open("rb") as fh: + files = {"file": (path.name, fh)} + r = await client.post(url, params={"path": dest}, files=files) + r.raise_for_status() + data = r.json() + return UploadResult( + path=data.get("path", ""), + bytes=data.get("bytes", 0), + sha256=data.get("sha256", ""), + ) + except httpx.HTTPStatusError as e: + if e.response.status_code == 503 and attempt < retries - 1: + await asyncio.sleep(backoff * (attempt + 1)) + continue + raise + raise RuntimeError("upload failed after retries") + + def _load_icon(self, icon: str | Path | bytes | None) -> bytes | None: + if icon is None: + return None + if isinstance(icon, bytes): + return icon + path = Path(icon).expanduser() + if path.exists() and path.is_file(): + return path.read_bytes() + return None + + async def finish_foreground( + self, + name: str, + bundle_id: str | None, + cmd: str, + args: list[str], + cwd: str = "/", + env: list[str] | None = None, + description: str = "", + icon: str | Path | bytes | None = None, + ) -> FinishBuildSessionResponse: + return await self.finish_app( + name=name, + bundle_id=bundle_id, + description=description, + icon=icon, + foreground={ + "cmd": cmd, + "args": args, + "cwd": cwd, + "env": env or [], + }, + background=None, + default_schedule=None, + ) + + async def finish_background( + self, + name: str, + bundle_id: str | None, + cmd: str, + args: list[str], + cwd: str = "/", + env: list[str] | None = None, + description: str = "", + icon: str | Path | bytes | None = None, + default_schedule: dict | None = None, + ) -> FinishBuildSessionResponse: + return await self.finish_app( + name=name, + bundle_id=bundle_id, + description=description, + icon=icon, + foreground=None, + background={ + "cmd": cmd, + "args": args, + "cwd": cwd, + "env": env or [], + }, + default_schedule=default_schedule, + ) + + async def finish_app( + self, + *, + name: str, + bundle_id: str | None, + description: str = "", + icon: str | Path | bytes | None = None, + foreground: dict | None, + background: dict | None, + default_schedule: dict | None, + ) -> FinishBuildSessionResponse: + if not self.stub or not self.app_uuid: + raise RuntimeError("no active build session") + if foreground is None and background is None: + raise ValueError("finish_app requires foreground and/or background config") + + req = FinishBuildSessionRequest() + req.app_uuid = self.app_uuid + req.discard = False + self._apply_metadata( + req=req, + name=name, + bundle_id=bundle_id, + description=description, + icon=icon, + ) + + if foreground is not None: + self._apply_process( + req.foreground.process, + cmd=foreground["cmd"], + args=list(foreground.get("args", [])), + cwd=foreground.get("cwd", "/"), + env=list(foreground.get("env", [])), + ) + + if background is not None: + self._apply_process( + req.background.process, + cmd=background["cmd"], + args=list(background.get("args", [])), + cwd=background.get("cwd", "/"), + env=list(background.get("env", [])), + ) + if default_schedule: + runtime_policy = parse_runtime_policy(default_schedule) + req.background.runtime_policy.CopyFrom(runtime_policy) + else: + req.background.runtime_policy.interval.duration.seconds = 60 + + resp: FinishBuildSessionResponse = await self.stub.Builder_FinishBuildSession( + req, metadata=self._metadata + ) + self.app_uuid = None + self.access_path = None + if resp.HasField("error"): + raise RuntimeError(f"finish failed: {resp.error.error} - {resp.error.details}") + return resp + + async def discard(self) -> FinishBuildSessionResponse | None: + if not self.stub or not self.app_uuid: + return None + req = FinishBuildSessionRequest() + req.app_uuid = self.app_uuid + req.discard = True + resp: FinishBuildSessionResponse = await self.stub.Builder_FinishBuildSession( + req, metadata=self._metadata + ) + self.app_uuid = None + self.access_path = None + return resp + + async def close(self): + if self.channel: + await self.channel.close() + self.channel = None + self.stub = None + + async def __aenter__(self): + await self.connect() + await self.start_build() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.discard() + await self.close() + return False From 1d7fd54536dd8c01760ed92c4f875a552f742023 Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 21:57:44 -0800 Subject: [PATCH 4/9] working sdk --- APP.md | 445 ------------------------------ example-apps/kalshi/truffile.yaml | 44 +-- example-apps/reddit/truffile.yaml | 42 +-- truffile/__init__.py | 2 - truffile/_version.py | 6 +- truffile/cli.py | 213 ++------------ truffile/client.py | 373 +------------------------ truffile/deploy/builder.py | 2 +- 8 files changed, 57 insertions(+), 1070 deletions(-) delete mode 100644 APP.md diff --git a/APP.md b/APP.md deleted file mode 100644 index 4eeb74a..0000000 --- a/APP.md +++ /dev/null @@ -1,445 +0,0 @@ -# Building TruffleOS Apps -tell ralph i said hi - -this is without cli -sketch for what docs will be structured like and u can just feed this to whatever model with example app code to make u an app or ven an exisiting mcp and have it port it - -TruffleOS apps come in two flavors: **Focus** and **Background**. - -## App Types - -| Type | When it runs | What it does | -|------|--------------|--------------| -| **Focus** | Always on, waiting | Exposes tools the AI can call on demand | -| **Background** | On a schedule | Runs periodically, posts to user's feed | - ---- - -## Focus Apps - -Focus apps are MCP servers that expose tools to the device AI. When the user asks a question, the AI can call your tools. - -### Example: Finance App - -```python -from mcp.server.fastmcp import FastMCP - -HOST = "0.0.0.0" -PORT = 8000 - -mcp = FastMCP("finance", stateless_http=True, host=HOST, port=PORT) - -@mcp.tool("get_stock_price", description="Get current price for a stock ticker") -async def get_stock_price(symbol: str) -> str: - # fetch from API... - return f"{symbol}: $256.44" - -@mcp.tool("search_ticker", description="Search for stock ticker symbols") -async def search_ticker(keywords: str) -> str: - # search API... - return "AAPL - Apple Inc." - -def main(): - print(f"Starting MCP server on {HOST}:{PORT}") - mcp.run(transport="streamable-http") - -if __name__ == "__main__": - main() -``` - -### How Tool Calls Work - -1. User asks: "What's Apple stock at?" -2. Truffle sees your `get_stock_price` tool -3. calls `get_stock_price("AAPL")` -4. Your app returns the result -5. Truffle responds to you with the data - -### Focus App Requirements - -- Must run an MCP server on `0.0.0.0:8000` -- Use `transport="streamable-http"` -- Tools are defined with `@mcp.tool()` decorator -- Each tool needs a `description` for the AI to understand when to use it - ---- - -## Local Development (Focus Apps) - -You can run your MCP server locally on your machine instead of deploying to the device as well. This is great for fast iteration during development. - -### 1. Run the server locally - -```bash -cd your-app-directory -python app.py -``` - -You should see: -``` -Starting MCP server on 0.0.0.0:8000 -INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) -``` - -### 2. Get your machine's IP - -```bash -# macOS -ipconfig getifaddr en0 - -# Linux -hostname -I | awk '{print $1}' -``` - -### 3. Add MCP in TruffleOS Settings - -Go to Settings → Add New MCP and enter: - -| Field | Value | -|-------|-------| -| **Name** | Your App Name | -| **Server URL** | `192.168.X.X` (your IP from step 2) | -| **Port** | `8000` | -| **Path** | `mcp` | - -Now you can use your tools immediately without deploying. Changes to your code take effect as soon as you restart `python app.py`. - ---- - -## Background Apps - -Background apps run on a schedule and post content to the user's feed. - -### Example: Hedge App - -```python -import os -from datetime import datetime -from gourmet.ambient import run_ambient, AmbientContext - -TICKERS = os.getenv("HEDGE_TICKERS", "AAPL,MSFT").split(",") - -def hedge_ambient(ctx: AmbientContext): - for symbol in TICKERS: - # fetch stock data... - price = 256.44 - - ctx.bg.post_to_feed( - title=f"📈 {symbol}: ${price}", - body=f"{symbol} is currently trading at ${price}", - src_uri=f"https://finance.yahoo.com/quote/{symbol}", - media_uris=["https://example.com/chart.png"], # optional - content_timestamp=datetime.now() - ) - -if __name__ == "__main__": - run_ambient(hedge_ambient) -``` - -### post_to_feed() Parameters - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `title` | str | yes | Card title shown in feed | -| `body` | str | yes | Main content text | -| `src_uri` | str | no | Link to original source | -| `media_uris` | list[str] | no | List of image URLs to display | -| `content_timestamp` | datetime | no | When the content was created | - -### Background App Requirements - -- Import `run_ambient` and `AmbientContext` from `gourmet.ambient` -- Define a function that takes `ctx: AmbientContext` -- Call `run_ambient(your_function)` in main -- Use `ctx.bg.post_to_feed()` to post content - ---- - -## truffile.yaml - -The `truffile.yaml` defines your app's metadata and installation steps. - -### Focus App Example - -```yaml -metadata: - name: Finance - type: foreground - description: | - Financial data tools for your Truffle. - process: - cmd: - - python - - app.py - working_directory: / - environment: - PYTHONUNBUFFERED: "1" - icon_file: ./icon.png - -steps: - - name: Welcome - type: welcome - content: | - This app provides financial data tools. - - - name: Copy files - type: files - files: - - source: ./app.py - destination: ./app.py - - - name: Install dependencies - type: bash - run: | - pip install mcp requests -``` - -### Background App Example - -```yaml -metadata: - name: Hedge - type: background - description: | - Track your stock portfolio. - process: - cmd: - - python - - app.py - working_directory: / - environment: - PYTHONUNBUFFERED: "1" - HEDGE_TICKERS: "AAPL,MSFT,GOOGL" - icon_file: ./icon.png - default_schedule: - type: interval - interval: - duration: 5m - schedule: - daily_window: "06:00-22:00" - allowed_days: [mon, tue, wed, thu, fri] - -steps: - - name: Copy files - type: files - files: - - source: ./app.py - destination: ./app.py - - - name: Install dependencies - type: bash - run: | - pip install requests - pip install gourmet -``` - ---- - -## Metadata Fields - -| Field | Required | Description | -|-------|----------|-------------| -| `name` | yes | App name shown to user | -| `type` | yes | `foreground` or `background` | -| `description` | no | What the app does | -| `process.cmd` | yes | Command to run the app | -| `process.working_directory` | no | Working dir (default: `/`) | -| `process.environment` | no | Environment variables | -| `icon_file` | no | Path to PNG icon | -| `default_schedule` | background only | When the app runs | - ---- - -## Schedule Types (Background Apps Only) - -### 1. Interval - -Run every X time period. - -```yaml -default_schedule: - type: interval - interval: - duration: 5m # required: how often - schedule: # optional: constraints - daily_window: "09:00-17:00" - allowed_days: [mon, tue, wed, thu, fri] -``` - -### 2. Times - -Run at specific times each day. - -```yaml -default_schedule: - type: times - times: - run_times: # required: list of times - - "09:00" - - "12:00" - - "18:00" - allowed_days: [mon, wed, fri] # optional -``` - ---- - -## Duration Format - -| Format | Example | Meaning | -|--------|---------|---------| -| `Xms` | `500ms` | 500 milliseconds | -| `Xs` | `30s` | 30 seconds | -| `Xm` | `5m` | 5 minutes | -| `Xh` | `2h` | 2 hours | -| `Xd` | `1d` | 1 day | - ---- - -## Daily Window - -Restrict when the app can run during the day. - -```yaml -daily_window: "09:00-17:30" -``` - -Or verbose format: - -```yaml -daily_window: - start: "09:00" - end: "17:30" -``` - ---- - -## Day Restrictions - -Use ONE of these (not both): - -**allowed_days** - only run on these days: -```yaml -allowed_days: [mon, tue, wed, thu, fri] -``` - -**forbidden_days** - don't run on these days: -```yaml -forbidden_days: [sat, sun] -``` - -Valid day values: `sun`, `mon`, `tue`, `wed`, `thu`, `fri`, `sat` - ---- - -## Installation Step Types - -### files - -Copy files from your app directory to the container. - -```yaml -- name: Copy files - type: files - files: - - source: ./app.py - destination: ./app.py - - source: ./config.yaml - destination: ./config.yaml - permissions: 600 # optional -``` - -### bash - -Run shell commands. - -```yaml -- name: Install dependencies - type: bash - run: | - pip install requests - apk add --no-cache curl -``` - -### welcome - -Show a welcome message to the user. - -```yaml -- name: Welcome - type: welcome - content: | - Welcome to my app! - It does cool things. -``` - -### text - -Prompt user for text input (saved to env vars). - -```yaml -- name: Configure API Key - type: text - content: | - Enter your API key to continue. - fields: - - name: api_key - label: API Key - type: password - env: MY_API_KEY - placeholder: "sk-..." -``` - -Field types: `text`, `password`, `number` - -### vnc - -Open a VNC window for user interaction (login flows, etc). - -```yaml -- name: Sign into Twitter - type: vnc - cmd: - - python - - onboard.py - closes_on_complete: true - description: | - Sign into your account in the browser window. -``` - ---- - -## Environment Variables - -Set in `process.environment`: - -```yaml -process: - environment: - PYTHONUNBUFFERED: "1" # always use this for Python apps - MY_API_KEY: "secret" - DEBUG: "true" -``` - -Or collected from user input via `text` steps (uses `env` field). - ---- - -## Quick Reference - -### Minimal Focus App - -``` -my-focus-app/ -├── app.py # MCP server with @mcp.tool() functions -├── truffile.yaml # type: foreground -└── icon.png # optional -``` - -### Minimal Background App - -``` -my-bg-app/ -├── app.py # Uses run_ambient() + post_to_feed() -├── truffile.yaml # type: background + default_schedule -└── icon.png # optional -``` diff --git a/example-apps/kalshi/truffile.yaml b/example-apps/kalshi/truffile.yaml index dfd59d8..fb81377 100644 --- a/example-apps/kalshi/truffile.yaml +++ b/example-apps/kalshi/truffile.yaml @@ -12,6 +12,11 @@ metadata: working_directory: / environment: PYTHONUNBUFFERED: "1" + # Required secrets for Kalshi API auth. + KALSHI_API_KEY: "REPLACE_WITH_KALSHI_API_KEY" + # PEM private key. Use literal block style to preserve newlines. + KALSHI_PRIVATE_KEY: | + REPLACE_WITH_KALSHI_PRIVATE_KEY_PEM default_schedule: type: interval interval: @@ -27,6 +32,11 @@ metadata: working_directory: / environment: PYTHONUNBUFFERED: "1" + # Required secrets for Kalshi API auth. + KALSHI_API_KEY: "REPLACE_WITH_KALSHI_API_KEY" + # PEM private key. Use literal block style to preserve newlines. + KALSHI_PRIVATE_KEY: | + REPLACE_WITH_KALSHI_PRIVATE_KEY_PEM steps: # - name: Welcome @@ -56,37 +66,3 @@ steps: destination: ./kalshi_foreground.py - source: ./kalshi_background.py destination: ./kalshi_background.py - - - name: Configure Kalshi API credentials - type: text - ui_state_on_show: user_interaction_ready - ui_state_on_complete: move_to_background - content: | - Kalshi lets your Truffle trade prediction markets, monitor - positions, and get alerts on price changes and settlements. - By installing this app you agree that Deepshard is not liable for any losses incurred. - - Enter your Kalshi API credentials. - To get these: - 1. Go to https://kalshi.com/account/profile and log in - 2. Scroll down to the "API Keys" section and click "Create Key" - 3. Copy the API Key and Private Key - 4. Paste the API Key and Private Key into the fields below - fields: - - name: kalshi_api_key - label: Kalshi API Key (UUID format) - type: password - placeholder: your key goes here - env: KALSHI_API_KEY - - name: kalshi_private_key - label: Kalshi Private Key (RSA PEM) - type: password - placeholder: "-----BEGIN RSA PRIVATE KEY-----" - env: KALSHI_PRIVATE_KEY - validator: - type: bash - run: | - python ./kalshi_background.py --verify - timeout: 120 - error_message: | - Could not authenticate with Kalshi using the provided credentials. diff --git a/example-apps/reddit/truffile.yaml b/example-apps/reddit/truffile.yaml index 4ceabe6..1838adc 100644 --- a/example-apps/reddit/truffile.yaml +++ b/example-apps/reddit/truffile.yaml @@ -11,6 +11,10 @@ metadata: working_directory: / environment: PYTHONUNBUFFERED: "1" + # Comma-separated subreddit list. Use "none" to disable and rely on USER_FEED_URL. + SUBREDDITS: "news,worldnews,technology" + # Optional personal Reddit JSON feed URL. Use "none" when not used. + USER_FEED_URL: "none" default_schedule: type: interval interval: @@ -35,41 +39,3 @@ steps: files: - source: ./reddit.py destination: ./opt/reddit.py - - name: Configure Reddit - type: text - ui_state_on_show: user_interaction_ready - ui_state_on_complete: move_to_background - content: | - Please provide either a comma separated list of subreddits to follow, or leave blank to follow r/all. - Example: news, worldnews, technology, science, funny, pics, videos, gaming - Alternatively, [copy the URL of your personal frontpage RSS feed (JSON) from here](https://old.reddit.com/prefs/feeds/) - If a personal feed URL is provided and is valid, that will be used instead of the subreddit list. - fields: - - name: subreddits - label: Comma Separated Subreddits - type: text - placeholder: news, worldnews, technology, LosAngeles - default: "" - env_default_if_empty: "none" - env: SUBREDDITS - - name: user_feed_url - label: Optional - Personal Frontpage RSS Feed URL (JSON) - type: text - placeholder: "Optional: leave blank to use subreddits (or paste https://old.reddit.com/.json?feed=...&user=...)" - default: "" - env_default_if_empty: "none" - env: USER_FEED_URL - validator: - type: bash - run: | - python /opt/reddit.py --verify - timeout: 60 - error_message: | - Was unable to scrape Reddit with the provided configuration. - Please ensure the subreddit names are valid or the feed URL is correct. - - - - - - diff --git a/truffile/__init__.py b/truffile/__init__.py index 532ea71..0954d6e 100644 --- a/truffile/__init__.py +++ b/truffile/__init__.py @@ -5,7 +5,6 @@ from .client import TruffleClient, ExecResult, UploadResult, resolve_mdns, NewSessionStatus from .schedule import parse_runtime_policy -from truffle.app.app_type_pb2 import AppType __all__ = [ "__version__", @@ -14,6 +13,5 @@ "UploadResult", "resolve_mdns", "NewSessionStatus", - "AppType", "parse_runtime_policy", ] diff --git a/truffile/_version.py b/truffile/_version.py index 7442fa8..a621a39 100644 --- a/truffile/_version.py +++ b/truffile/_version.py @@ -28,7 +28,7 @@ commit_id: COMMIT_ID __commit_id__: COMMIT_ID -__version__ = version = '0.1.15.dev0' -__version_tuple__ = version_tuple = (0, 1, 15, 'dev0') +__version__ = version = '0.1.15.dev3' +__version_tuple__ = version_tuple = (0, 1, 15, 'dev3') -__commit_id__ = commit_id = 'g599e9fda6' +__commit_id__ = commit_id = 'g0212865ef' diff --git a/truffile/cli.py b/truffile/cli.py index fa61753..6463bbc 100644 --- a/truffile/cli.py +++ b/truffile/cli.py @@ -1,6 +1,5 @@ import argparse import asyncio -import ast import signal import socket import sys @@ -8,10 +7,10 @@ import time from pathlib import Path -import yaml - from truffile.storage import StorageService from truffile.client import TruffleClient, resolve_mdns, NewSessionStatus +from truffile.schema import validate_app_dir +from truffile.deploy.builder import deploy_with_builder import grpc from truffle.infer.infer_pb2_grpc import InferenceServiceStub @@ -261,189 +260,6 @@ def cmd_disconnect(args, storage: StorageService) -> int: return 0 -def check_python_syntax(file_path: Path) -> tuple[bool, str]: - try: - with open(file_path) as f: - source = f.read() - ast.parse(source) - return True, "" - except SyntaxError as e: - return False, f"Line {e.lineno}: {e.msg}" - - -def validate_app_dir(app_dir: Path) -> tuple[bool, dict | None, str | None, list[str]]: - """Validate app directory and return (valid, config, app_type, warnings).""" - warnings = [] - - truffile = app_dir / "truffile.yaml" - if not truffile.exists(): - error(f"No truffile.yaml found in {app_dir}") - return False, None, None, warnings - - try: - with open(truffile) as f: - config = yaml.safe_load(f) - except yaml.YAMLError as e: - error(f"Invalid truffile.yaml: {e}") - return False, None, None, warnings - - meta = config.get("metadata", {}) - if not meta.get("name"): - error("metadata.name is required in truffile.yaml") - return False, None, None, warnings - - cfg_type = meta.get("type", "").lower() - if cfg_type in ("background", "ambient"): - app_type = "ambient" - elif cfg_type in ("foreground", "focus"): - app_type = "focus" - else: - app_type = "focus" - warnings.append(f"No type specified in truffile.yaml, defaulting to focus") - - icon_file = meta.get("icon_file") - if icon_file: - icon_path = app_dir / icon_file - if not icon_path.exists(): - warnings.append(f"Icon file not found: {icon_file}") - else: - warnings.append("No icon specified in truffile.yaml") - - # Check files - either in steps or top-level files: - files_to_check = [] - for step in config.get("steps", []): - if step.get("type") == "files": - files_to_check.extend(step.get("files", [])) - # Also check top-level files: (simplified format) - files_to_check.extend(config.get("files", [])) - - for f in files_to_check: - src = app_dir / f["source"] - if not src.exists(): - error(f"Source file not found: {src}") - return False, None, None, warnings - if src.suffix == ".py": - ok, err = check_python_syntax(src) - if not ok: - error(f"Syntax error in {src.name}: {err}") - return False, None, None, warnings - - return True, config, app_type, warnings - - -async def _do_deploy(client: TruffleClient, config: dict, app_dir: Path, app_type: str, device: str, interactive: bool = False) -> int: - meta = config["metadata"] - name = meta["name"] - description = meta.get("description", "") - process = meta.get("process", {}) - cmd_list = process.get("cmd", ["python", "app.py"]) - cwd = process.get("working_directory", "/") - env_dict = process.get("environment", {}) - env = [f"{k}={v}" for k, v in env_dict.items()] - icon_file = meta.get("icon_file") - icon_path = (app_dir / icon_file) if icon_file and (app_dir / icon_file).exists() else None - - spinner = Spinner(f"Connecting to {device}") - spinner.start() - await client.connect() - spinner.stop(success=True) - - spinner = Spinner("Starting build session") - spinner.start() - await client.start_build() - await asyncio.sleep(5) - spinner.stop(success=True) - print(f" {C.DIM}Session: {client.app_uuid}{C.RESET}") - - # Always upload files first - files_to_upload = [] - for step in config.get("steps", []): - if step.get("type") == "files": - files_to_upload.extend(step.get("files", [])) - files_to_upload.extend(config.get("files", [])) - - for f in files_to_upload: - src = app_dir / f["source"] - dest = f["destination"] - spinner = Spinner(f"Uploading {src.name} {ARROW} {dest}") - spinner.start() - result = await client.upload(src, dest) - spinner.stop(success=True) - print(f" {C.DIM}{result.bytes} bytes, sha256={result.sha256[:12]}...{C.RESET}") - - # always run bash commands - bash_commands = [] - for step in config.get("steps", []): - if step.get("type") == "bash": - bash_commands.append((step.get("name", "bash"), step["run"])) - if config.get("run"): - bash_commands.append(("Install dependencies", config["run"])) - - for step_name, run_cmd in bash_commands: - info(f"Running: {step_name}") - log = ScrollingLog(height=6, prefix=" ") - exit_code = 0 - async for ev, data in client.exec_stream(run_cmd, cwd=cwd): - if ev == "log": - try: - import json - obj = json.loads(data) - line = obj.get("line", "") - except Exception: - line = data - log.add(line) - elif ev == "exit": - try: - import json - exit_code = int(json.loads(data).get("code", 0)) - except (ValueError, KeyError): - pass - log.finish() - if exit_code != 0: - error(f"Step '{step_name}' failed with exit code {exit_code}") - raise RuntimeError(f"Step '{step_name}' failed with exit code {exit_code}") - - if interactive: - # interactive mode: open shell after setup for testing/debugging - print() - info("Opening interactive shell (exit with Ctrl+D or 'exit' to finish deploy)") - ws_url = str(client.http_base or "").replace("http://", "ws://").replace("https://", "wss://") + "/term" - await _interactive_shell(ws_url) - print() - spinner = Spinner(f"Finishing as {app_type} app") - spinner.start() - - cmd = cmd_list[0] if cmd_list[0].startswith("/") else f"/usr/bin/{cmd_list[0]}" - - if app_type == "focus": - await client.finish_foreground( - name=name, - cmd=cmd, - args=cmd_list[1:], - cwd=cwd, - env=env, - description=description, - icon=icon_path, - ) - else: - default_schedule = meta.get("default_schedule") - await client.finish_background( - name=name, - cmd=cmd, - args=cmd_list[1:], - cwd=cwd, - env=env, - description=description, - icon=icon_path, - default_schedule=default_schedule, - ) - - spinner.stop(success=True) - print() - success(f"Deployed: {C.BOLD}{name}{C.RESET} ({app_type})") - return 0 - - async def cmd_deploy(args, storage: StorageService) -> int: app_path = args.path if args.path else "." app_dir = Path(app_path).resolve() @@ -453,8 +269,10 @@ async def cmd_deploy(args, storage: StorageService) -> int: return 1 info(f"Validating app in {app_dir.name}") - valid, config, app_type, warnings = validate_app_dir(app_dir) + valid, config, app_type, warnings, errors = validate_app_dir(app_dir) if not valid or not app_type: + for msg in errors: + error(msg) return 1 for w in warnings: @@ -496,7 +314,26 @@ def handle_sigint(): loop.add_signal_handler(signal.SIGINT, handle_sigint) try: - deploy_task = asyncio.create_task(_do_deploy(client, config, app_dir, app_type, device, interactive)) + deploy_task = asyncio.create_task( + deploy_with_builder( + client=client, + config=config, + app_dir=app_dir, + app_type=app_type, + device=device, + interactive=interactive, + spinner_cls=Spinner, + scrolling_log_cls=ScrollingLog, + info=info, + success=success, + error=error, + color_dim=C.DIM, + color_reset=C.RESET, + color_bold=C.BOLD, + arrow=ARROW, + interactive_shell=_interactive_shell, + ) + ) return await deploy_task except asyncio.CancelledError: print() diff --git a/truffile/client.py b/truffile/client.py index 1608159..0ad8a67 100644 --- a/truffile/client.py +++ b/truffile/client.py @@ -1,362 +1,17 @@ -import asyncio -import json -import platform -import socket -from dataclasses import dataclass -from pathlib import Path -from typing import AsyncIterator -import grpc -from grpc import aio -import httpx -from google.protobuf import empty_pb2 -from truffle.os.truffleos_pb2_grpc import TruffleOSStub -from truffle.os.builder_pb2 import ( - StartBuildSessionRequest, - StartBuildSessionResponse, - FinishBuildSessionRequest, - FinishBuildSessionResponse, -) -from truffle.os.client_session_pb2 import ( - RegisterNewSessionRequest, - RegisterNewSessionResponse, +"""import surface for transport client APIs.""" + +from truffile.transport.client import ( + ExecResult, NewSessionStatus, + TruffleClient, + UploadResult, + resolve_mdns, ) -from truffle.os.client_metadata_pb2 import ClientMetadata -from truffle.os.app_queries_pb2 import GetAllAppsRequest, GetAllAppsResponse, DeleteAppRequest, DeleteAppResponse -from truffle.app.app_type_pb2 import AppType -from truffle.app.foreground_pb2 import ForegroundApp -from truffle.app.background_pb2 import BackgroundApp, BackgroundAppRuntimePolicy -from truffile.schedule import parse_runtime_policy - - -def get_client_metadata() -> ClientMetadata: - from truffile import __version__ - metadata = ClientMetadata() - metadata.device = platform.node() - metadata.platform = platform.platform() - metadata.version = f"truffile-{__version__}-{platform.python_version()}" - return metadata - - -async def resolve_mdns(hostname: str) -> str: - if ".local" not in hostname: - return hostname - loop = asyncio.get_event_loop() - try: - resolved = await loop.run_in_executor(None, socket.gethostbyname, hostname) - return resolved - except socket.gaierror as e: - raise RuntimeError(f"Failed to resolve {hostname} - is the device on the same network? ({e})") - - -@dataclass -class ExecResult: - exit_code: int - output: list[str] - - -@dataclass -class UploadResult: - path: str - bytes: int - sha256: str - - -class TruffleClient: - def __init__(self, address: str, token: str): - self.address = address - self.token = token - self.channel: aio.Channel | None = None - self.stub: TruffleOSStub | None = None - self.app_uuid: str | None = None - self.access_path: str | None = None - - @property - def http_base(self) -> str | None: - if not self.access_path: - return None - host = self.address if "://" in self.address else f"http://{self.address}" - return f"{host}/containers/{self.access_path}" - - @property - def _metadata(self) -> list: - return [("session", self.token)] - - async def connect(self, timeout: float = 15.0): - self.channel = aio.insecure_channel(self.address) - await asyncio.wait_for(self.channel.channel_ready(), timeout=timeout) - self.stub = TruffleOSStub(self.channel) - - def update_token(self, token: str): - self.token = token - - async def check_auth(self) -> bool: - if not self.stub or not self.token: - return False - try: - await self.stub.System_GetInfo(empty_pb2.Empty(), metadata=self._metadata) - return True - except aio.AioRpcError as e: - if e.code() == grpc.StatusCode.UNAUTHENTICATED: - return False - raise - - async def register_new_session(self, user_id: str) -> tuple[NewSessionStatus, str | None]: - if not self.stub: - raise RuntimeError("not connected") - req = RegisterNewSessionRequest() - req.user_id = user_id - req.metadata.CopyFrom(get_client_metadata()) - resp: RegisterNewSessionResponse = await self.stub.Client_RegisterNewSession(req) - if resp.status.error == NewSessionStatus.NEW_SESSION_SUCCESS: - self.token = resp.token - return resp.status, resp.token - return resp.status, None - - async def get_all_apps(self) -> tuple[list[ForegroundApp], list[BackgroundApp]]: - if not self.stub: - raise RuntimeError("not connected") - req = GetAllAppsRequest() - resp: GetAllAppsResponse = await self.stub.Apps_GetAll(req, metadata=self._metadata) - return list(resp.foreground_apps), list(resp.background_apps) - - async def delete_app(self, app_uuid: str) -> DeleteAppResponse: - if not self.stub: - raise RuntimeError("not connected") - req = DeleteAppRequest() - req.app_uuid = app_uuid - resp: DeleteAppResponse = await self.stub.Apps_DeleteApp(req, metadata=self._metadata) - return resp - - async def start_build(self, app_type: AppType = AppType.APP_TYPE_BACKGROUND) -> StartBuildSessionResponse: - if not self.stub: - raise RuntimeError("not connected") - req = StartBuildSessionRequest() - req.app_type = app_type - resp: StartBuildSessionResponse = await self.stub.Builder_StartBuildSession( - req, metadata=self._metadata - ) - self.app_uuid = resp.app_uuid - self.access_path = resp.access_path - return resp - - async def _sse_events(self, client: httpx.AsyncClient, url: str, body: dict) -> AsyncIterator[tuple[str, str]]: - async with client.stream("POST", url, json=body, timeout=None) as r: - r.raise_for_status() - event = "message" - data_parts = [] - async for raw in r.aiter_lines(): - if raw is None: - continue - line = raw.rstrip("\r") - if line == "": - if data_parts: - yield event, "\n".join(data_parts) - event, data_parts = "message", [] - continue - if line.startswith(":"): - continue - if line.startswith("event:"): - event = line[6:].strip() - elif line.startswith("data:"): - data_parts.append(line[5:].lstrip()) - if data_parts: - yield event, "\n".join(data_parts) - - async def exec(self, cmd: str, cwd: str = "/") -> ExecResult: - if not self.http_base: - raise RuntimeError("no active build session") - url = f"{self.http_base}/exec/stream" - body = {"cmd": ["bash", "-lc", f"cd {cwd} && {cmd}"], "cwd": cwd} - output = [] - exit_code = 0 - retries = 5 - backoff = 1.0 - async with httpx.AsyncClient(timeout=None) as client: - for attempt in range(retries): - try: - async for ev, data in self._sse_events(client, url, body): - if ev == "log": - try: - obj = json.loads(data) - line = obj.get("line", "") - except Exception: - line = data - output.append(line) - elif ev == "exit": - try: - exit_code = int(json.loads(data).get("code", 0)) - except Exception: - pass - return ExecResult(exit_code=exit_code, output=output) - except httpx.HTTPStatusError as e: - if e.response.status_code == 503 and attempt < retries - 1: - await asyncio.sleep(backoff * (attempt + 1)) - continue - raise - return ExecResult(exit_code=exit_code, output=output) - - async def exec_stream(self, cmd: str, cwd: str = "/") -> AsyncIterator[tuple[str, str]]: - if not self.http_base: - raise RuntimeError("no active build session") - url = f"{self.http_base}/exec/stream" - body = {"cmd": ["bash", "-lc", f"cd {cwd} && {cmd}"], "cwd": cwd} - retries = 5 - backoff = 1.0 - async with httpx.AsyncClient(timeout=None) as client: - for attempt in range(retries): - try: - async for ev, data in self._sse_events(client, url, body): - yield ev, data - return - except httpx.HTTPStatusError as e: - if e.response.status_code == 503 and attempt < retries - 1: - await asyncio.sleep(backoff * (attempt + 1)) - continue - raise - - async def upload(self, src: str | Path, dest: str) -> UploadResult: - if not self.http_base: - raise RuntimeError("no active build session") - path = Path(src).expanduser() - if not path.exists() or not path.is_file(): - raise FileNotFoundError(f"no such file: {path}") - url = f"{self.http_base}/upload" - retries = 5 - backoff = 1.0 - async with httpx.AsyncClient(timeout=None) as client: - for attempt in range(retries): - try: - with path.open("rb") as fh: - files = {"file": (path.name, fh)} - r = await client.post(url, params={"path": dest}, files=files) - r.raise_for_status() - data = r.json() - return UploadResult( - path=data.get("path", ""), - bytes=data.get("bytes", 0), - sha256=data.get("sha256", ""), - ) - except httpx.HTTPStatusError as e: - if e.response.status_code == 503 and attempt < retries - 1: - await asyncio.sleep(backoff * (attempt + 1)) - continue - raise - raise RuntimeError("upload failed after retries") - - def _load_icon(self, icon: str | Path | bytes | None) -> bytes | None: - if icon is None: - return None - if isinstance(icon, bytes): - return icon - path = Path(icon).expanduser() - if path.exists() and path.is_file(): - return path.read_bytes() - return None - - async def finish_foreground( - self, - name: str, - cmd: str, - args: list[str], - cwd: str = "/", - env: list[str] | None = None, - description: str = "", - icon: str | Path | bytes | None = None, - ) -> FinishBuildSessionResponse: - if not self.stub or not self.app_uuid: - raise RuntimeError("no active build session") - req = FinishBuildSessionRequest() - req.app_uuid = self.app_uuid - req.discard = False - req.foreground.metadata.name = name - if description: - req.foreground.metadata.description = description - icon_data = self._load_icon(icon) - if icon_data: - req.foreground.metadata.icon.png_data = icon_data - req.process.cmd = cmd - req.process.args.extend(args) - if env: - req.process.env.extend(env) - req.process.cwd = cwd - resp: FinishBuildSessionResponse = await self.stub.Builder_FinishBuildSession( - req, metadata=self._metadata - ) - self.app_uuid = None - self.access_path = None - if resp.HasField("error"): - raise RuntimeError(f"finish failed: {resp.error.error} - {resp.error.details}") - return resp - - async def finish_background( - self, - name: str, - cmd: str, - args: list[str], - cwd: str = "/", - env: list[str] | None = None, - description: str = "", - icon: str | Path | bytes | None = None, - default_schedule: dict | None = None, - ) -> FinishBuildSessionResponse: - if not self.stub or not self.app_uuid: - raise RuntimeError("no active build session") - req = FinishBuildSessionRequest() - req.app_uuid = self.app_uuid - req.discard = False - req.background.metadata.name = name - if description: - req.background.metadata.description = description - icon_data = self._load_icon(icon) - if icon_data: - req.background.metadata.icon.png_data = icon_data - - if default_schedule: - runtime_policy = parse_runtime_policy(default_schedule) - req.background.runtime_policy.CopyFrom(runtime_policy) - else: - req.background.runtime_policy.interval.duration.seconds = 60 - - req.process.cmd = cmd - req.process.args.extend(args) - if env: - req.process.env.extend(env) - req.process.cwd = cwd - resp: FinishBuildSessionResponse = await self.stub.Builder_FinishBuildSession( - req, metadata=self._metadata - ) - self.app_uuid = None - self.access_path = None - if resp.HasField("error"): - raise RuntimeError(f"finish failed: {resp.error.error} - {resp.error.details}") - return resp - - async def discard(self) -> FinishBuildSessionResponse | None: - if not self.stub or not self.app_uuid: - return None - req = FinishBuildSessionRequest() - req.app_uuid = self.app_uuid - req.discard = True - resp: FinishBuildSessionResponse = await self.stub.Builder_FinishBuildSession( - req, metadata=self._metadata - ) - self.app_uuid = None - self.access_path = None - return resp - - async def close(self): - if self.channel: - await self.channel.close() - self.channel = None - self.stub = None - - async def __aenter__(self): - await self.connect() - await self.start_build() - return self - async def __aexit__(self, exc_type, exc_val, exc_tb): - await self.discard() - await self.close() - return False +__all__ = [ + "ExecResult", + "NewSessionStatus", + "TruffleClient", + "UploadResult", + "resolve_mdns", +] diff --git a/truffile/deploy/builder.py b/truffile/deploy/builder.py index 57e5026..5006f7e 100644 --- a/truffile/deploy/builder.py +++ b/truffile/deploy/builder.py @@ -47,7 +47,7 @@ async def _wait_for_build_session_ready(client: TruffleClient, timeout_sec: floa await asyncio.sleep(1.0) if last_error is not None: raise RuntimeError(f"build session endpoint did not become ready in time: {last_error}") - raise RuntimeError("build session endpoint did not become ready in time") + raise RuntimeError("build session endpoint did not become ready in time") async def deploy_with_builder( From 2ab875bbb538b9ff2fcc452cbb80d8532bd681f3 Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 22:02:24 -0800 Subject: [PATCH 5/9] working sdk --- truffile/cli.py | 55 +++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/truffile/cli.py b/truffile/cli.py index 6463bbc..57c70c1 100644 --- a/truffile/cli.py +++ b/truffile/cli.py @@ -389,47 +389,55 @@ async def cmd_list_apps(storage: StorageService) -> int: try: await client.connect() - foreground, background = await client.get_all_apps() + apps = await client.get_all_apps() spinner.stop(success=True) - - if not foreground and not background: + + if not apps: print(f" {C.DIM}No apps installed{C.RESET}") return 0 - + + focus_apps = [app for app in apps if app.HasField("foreground")] + ambient_apps = [app for app in apps if app.HasField("background")] + both_apps = [app for app in apps if app.HasField("foreground") and app.HasField("background")] + print() - if foreground: + if focus_apps: print(f"{C.BOLD}Focus Apps{C.RESET}") - for app in foreground: + for app in focus_apps: print(f" {C.CYAN}{DOT}{C.RESET} {app.metadata.name}") setattr(app.metadata, "description", getattr(app.metadata, "description", "")) if hasattr(app.metadata, "description") and app.metadata.description: desc = app.metadata.description.strip().split('\n')[0][:55] print(f" {C.DIM}{desc}{C.RESET}") - - if background: - if foreground: + + if ambient_apps: + if focus_apps: print() print(f"{C.BOLD}Ambient Apps{C.RESET}") - for app in background: + for app in ambient_apps: schedule = "" - if app.runtime_policy.HasField("interval"): - secs = app.runtime_policy.interval.duration.seconds + policy = app.background.runtime_policy + if policy.HasField("interval"): + secs = policy.interval.duration.seconds if secs >= 3600: schedule = f"every {secs // 3600}h" elif secs >= 60: schedule = f"every {secs // 60}m" else: schedule = f"every {secs}s" - elif app.runtime_policy.HasField("always"): + elif policy.HasField("always"): schedule = "always" print(f" {C.CYAN}{DOT}{C.RESET} {app.metadata.name} {C.DIM}({schedule}){C.RESET}") setattr(app.metadata, "description", getattr(app.metadata, "description", "")) if hasattr(app.metadata, "description") and app.metadata.description: desc = app.metadata.description.strip().split('\n')[0][:55] print(f" {C.DIM}{desc}{C.RESET}") - + print() - print(f"{C.DIM}Total: {len(foreground)} focus, {len(background)} ambient{C.RESET}") + print( + f"{C.DIM}Total: {len(focus_apps)} focus, {len(ambient_apps)} ambient, " + f"{len(both_apps)} both{C.RESET}" + ) return 0 except Exception as e: @@ -465,14 +473,21 @@ async def cmd_delete(args, storage: StorageService) -> int: try: await client.connect() - foreground, background = await client.get_all_apps() + apps = await client.get_all_apps() spinner.stop(success=True) all_apps = [] - for app in foreground: - all_apps.append(("focus", app.uuid, app.metadata.name, app.metadata.description.strip().split('\n')[0][:55] if app.metadata.description else "")) - for app in background: - all_apps.append(("ambient", app.uuid, app.metadata.name, app.metadata.description.strip().split('\n')[0][:55] if app.metadata.description else "")) + for app in apps: + if app.HasField("foreground") and app.HasField("background"): + kind = "both" + elif app.HasField("foreground"): + kind = "focus" + elif app.HasField("background"): + kind = "ambient" + else: + kind = "unknown" + desc = app.metadata.description.strip().split('\n')[0][:55] if app.metadata.description else "" + all_apps.append((kind, app.uuid, app.metadata.name, desc)) if not all_apps: print(f" {C.DIM}No apps installed{C.RESET}") From 39c2706a1725c5ea826c2db97d711344b826c59c Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 22:14:46 -0800 Subject: [PATCH 6/9] sdk goodies, validate and dry run --- truffile/cli.py | 80 +++++++++++++++++- truffile/deploy/__init__.py | 4 +- truffile/deploy/builder.py | 147 +++++++++++++++++++++------------- truffile/schema/app_config.py | 71 +++++++++++++--- 4 files changed, 231 insertions(+), 71 deletions(-) diff --git a/truffile/cli.py b/truffile/cli.py index 57c70c1..31a6f34 100644 --- a/truffile/cli.py +++ b/truffile/cli.py @@ -10,7 +10,7 @@ from truffile.storage import StorageService from truffile.client import TruffleClient, resolve_mdns, NewSessionStatus from truffile.schema import validate_app_dir -from truffile.deploy.builder import deploy_with_builder +from truffile.deploy import build_deploy_plan, deploy_with_builder import grpc from truffle.infer.infer_pb2_grpc import InferenceServiceStub @@ -264,6 +264,7 @@ async def cmd_deploy(args, storage: StorageService) -> int: app_path = args.path if args.path else "." app_dir = Path(app_path).resolve() interactive = args.interactive + dry_run = bool(getattr(args, "dry_run", False)) if not app_dir.exists() or not app_dir.is_dir(): error(f"{app_dir} is not a valid directory") return 1 @@ -277,6 +278,55 @@ async def cmd_deploy(args, storage: StorageService) -> int: for w in warnings: warn(w) + + if dry_run: + try: + plan = build_deploy_plan(config=config, app_dir=app_dir, app_type=app_type) + except Exception as e: + error(f"Failed to build deploy plan: {e}") + return 1 + print() + print(f"{C.BOLD}Dry Run: Deploy Plan{C.RESET}") + print(f" Name: {plan['name']}") + print(f" Bundle ID: {plan['bundle_id']}") + print(f" Mode: {plan['finish_label']}") + print(f" App Dir: {app_dir}") + print(f" Exec CWD: {plan['exec_cwd']}") + if plan["icon_path"] is not None: + print(f" Icon: {plan['icon_path']}") + else: + print(f" Icon: {C.DIM}{C.RESET}") + + fg = plan["fg_payload"] + if fg is not None: + fg_keys = [e.split("=", 1)[0] for e in fg.get("env", []) if "=" in e] + print(f" Foreground Cmd: {fg['cmd']} {' '.join(fg.get('args', []))}".rstrip()) + print(f" Foreground Env Keys: {', '.join(fg_keys) if fg_keys else ''}") + + bg = plan["bg_payload"] + if bg is not None: + bg_keys = [e.split('=', 1)[0] for e in bg.get("env", []) if "=" in e] + print(f" Background Cmd: {bg['cmd']} {' '.join(bg.get('args', []))}".rstrip()) + print(f" Background Env Keys: {', '.join(bg_keys) if bg_keys else ''}") + if plan["default_schedule"] is not None: + print(f" Background Schedule: configured") + else: + print(f" Background Schedule: {C.DIM}{C.RESET}") + + files = plan["files_to_upload"] + print(f" Files To Upload: {len(files)}") + for f in files: + src = f.get("source", "") + dst = f.get("destination", "") + print(f" - {src} {ARROW} {dst}") + + cmds = plan["bash_commands"] + print(f" Bash Steps: {len(cmds)}") + for name, _cmd in cmds: + print(f" - {name}") + print() + success("Dry run complete (no device changes made)") + return 0 device = storage.state.last_used_device if not device: @@ -913,6 +963,25 @@ class FakeArgs: return 1 +def cmd_validate(args) -> int: + app_dir = Path(args.path).resolve() + if not app_dir.exists() or not app_dir.is_dir(): + error(f"{app_dir} is not a valid directory") + return 1 + + info(f"Validating app in {app_dir.name}") + valid, _config, app_type, warnings, errors = validate_app_dir(app_dir) + for w in warnings: + warn(w) + if not valid: + for e in errors: + error(e) + return 1 + + success(f"Validation passed ({app_type})") + return 0 + + def print_help(): print(f"{MUSHROOM} {C.BOLD}truffile{C.RESET} - TruffleOS SDK") print() @@ -923,6 +992,7 @@ def print_help(): print(f" {C.BLUE}connect{C.RESET} Connect to a Truffle device") print(f" {C.BLUE}disconnect{C.RESET} Disconnect and clear credentials") print(f" {C.BLUE}deploy{C.RESET} [path] Deploy an app (reads type from truffile.yaml)") + print(f" {C.BLUE}validate{C.RESET} [path] Validate app config and files") print(f" {C.BLUE}delete{C.RESET} Delete installed apps from device") print(f" {C.BLUE}list{C.RESET} List installed apps or devices") print(f" {C.BLUE}models{C.RESET} List AI models on connected device") @@ -932,7 +1002,9 @@ def print_help(): print(f" {C.DIM}truffile scan{C.RESET} {C.DIM}# find devices on network{C.RESET}") print(f" {C.DIM}truffile connect truffle-6272{C.RESET}") print(f" {C.DIM}truffile deploy ./my-app{C.RESET}") + print(f" {C.DIM}truffile deploy --dry-run ./my-app{C.RESET}") print(f" {C.DIM}truffile deploy{C.RESET} {C.DIM}# uses current directory{C.RESET}") + print(f" {C.DIM}truffile validate ./my-app{C.RESET}") print(f" {C.DIM}truffile list apps{C.RESET}") print(f" {C.DIM}truffile models{C.RESET} {C.DIM}# show loaded models{C.RESET}") print(f" {C.DIM}truffile proxy{C.RESET} {C.DIM}# start proxy on :8080{C.RESET}") @@ -964,6 +1036,10 @@ def main() -> int: p_deploy = subparsers.add_parser("deploy", add_help=False) p_deploy.add_argument("path", nargs="?", default=".") p_deploy.add_argument("-i", "--interactive", action="store_true", help="Interactive terminal mode") + p_deploy.add_argument("--dry-run", action="store_true", help="Show deploy plan without mutating device") + + p_validate = subparsers.add_parser("validate", add_help=False) + p_validate.add_argument("path", nargs="?", default=".") p_delete = subparsers.add_parser("delete", add_help=False) @@ -1018,6 +1094,8 @@ def main() -> int: return run_async(cmd_models(storage)) elif args.command == "proxy": return cmd_proxy(args, storage) + elif args.command == "validate": + return cmd_validate(args) return 0 diff --git a/truffile/deploy/__init__.py b/truffile/deploy/__init__.py index 6c58e9d..bc40339 100644 --- a/truffile/deploy/__init__.py +++ b/truffile/deploy/__init__.py @@ -1,3 +1,3 @@ -from .builder import deploy_with_builder +from .builder import build_deploy_plan, deploy_with_builder -__all__ = ["deploy_with_builder"] +__all__ = ["build_deploy_plan", "deploy_with_builder"] diff --git a/truffile/deploy/builder.py b/truffile/deploy/builder.py index 5006f7e..561ccd4 100644 --- a/truffile/deploy/builder.py +++ b/truffile/deploy/builder.py @@ -34,41 +34,12 @@ def _extract_process(process_cfg: dict[str, Any] | None) -> tuple[str, list[str] return cmd, args, cwd, env -async def _wait_for_build_session_ready(client: TruffleClient, timeout_sec: float = 45.0) -> None: - deadline = asyncio.get_event_loop().time() + timeout_sec - last_error: Exception | None = None - while asyncio.get_event_loop().time() < deadline: - try: - result = await client.exec("echo ready", cwd="/") - if result.exit_code == 0: - return - except Exception as e: - last_error = e - await asyncio.sleep(1.0) - if last_error is not None: - raise RuntimeError(f"build session endpoint did not become ready in time: {last_error}") - raise RuntimeError("build session endpoint did not become ready in time") - - -async def deploy_with_builder( +def build_deploy_plan( *, - client: TruffleClient, config: dict[str, Any], app_dir: Path, app_type: str, - device: str, - interactive: bool, - spinner_cls: Any, - scrolling_log_cls: Any, - info: Callable[[str], None], - success: Callable[[str], None], - error: Callable[[str], None], - color_dim: str, - color_reset: str, - color_bold: str, - arrow: str, - interactive_shell: Callable[[str], Any], -) -> int: +) -> dict[str, Any]: meta = config["metadata"] name = meta["name"] description = meta.get("description", "") @@ -105,6 +76,95 @@ async def deploy_with_builder( if exec_cwd == "/" and bg_cwd: exec_cwd = bg_cwd + if has_fg and has_bg: + finish_label = "foreground+background" + elif has_fg: + finish_label = "foreground" + else: + finish_label = "background" + + default_schedule = None + if isinstance(bg_cfg, dict): + default_schedule = bg_cfg.get("default_schedule") + elif has_bg: + default_schedule = meta.get("default_schedule") + + files_to_upload = [] + for step in config.get("steps", []): + if isinstance(step, dict) and step.get("type") == "files": + files_to_upload.extend(step.get("files", [])) + files_to_upload.extend(config.get("files", [])) + + bash_commands = [] + for step in config.get("steps", []): + if isinstance(step, dict) and step.get("type") == "bash": + bash_commands.append((step.get("name", "bash"), step["run"])) + if config.get("run"): + bash_commands.append(("Install dependencies", config["run"])) + + return { + "name": name, + "description": description, + "bundle_id": bundle_id, + "icon_path": icon_path, + "fg_payload": fg_payload, + "bg_payload": bg_payload, + "exec_cwd": exec_cwd, + "finish_label": finish_label, + "default_schedule": default_schedule, + "files_to_upload": files_to_upload, + "bash_commands": bash_commands, + } + + +async def _wait_for_build_session_ready(client: TruffleClient, timeout_sec: float = 45.0) -> None: + deadline = asyncio.get_event_loop().time() + timeout_sec + last_error: Exception | None = None + while asyncio.get_event_loop().time() < deadline: + try: + result = await client.exec("echo ready", cwd="/") + if result.exit_code == 0: + return + except Exception as e: + last_error = e + await asyncio.sleep(1.0) + if last_error is not None: + raise RuntimeError(f"build session endpoint did not become ready in time: {last_error}") + raise RuntimeError("build session endpoint did not become ready in time") + + +async def deploy_with_builder( + *, + client: TruffleClient, + config: dict[str, Any], + app_dir: Path, + app_type: str, + device: str, + interactive: bool, + spinner_cls: Any, + scrolling_log_cls: Any, + info: Callable[[str], None], + success: Callable[[str], None], + error: Callable[[str], None], + color_dim: str, + color_reset: str, + color_bold: str, + arrow: str, + interactive_shell: Callable[[str], Any], +) -> int: + plan = build_deploy_plan(config=config, app_dir=app_dir, app_type=app_type) + name = plan["name"] + description = plan["description"] + bundle_id = plan["bundle_id"] + icon_path = plan["icon_path"] + fg_payload = plan["fg_payload"] + bg_payload = plan["bg_payload"] + exec_cwd = plan["exec_cwd"] + finish_label = plan["finish_label"] + default_schedule = plan["default_schedule"] + files_to_upload = plan["files_to_upload"] + bash_commands = plan["bash_commands"] + spinner = spinner_cls(f"Connecting to {device}") spinner.start() await client.connect() @@ -117,12 +177,6 @@ async def deploy_with_builder( spinner.stop(success=True) print(f" {color_dim}Session: {client.app_uuid}{color_reset}") - files_to_upload = [] - for step in config.get("steps", []): - if step.get("type") == "files": - files_to_upload.extend(step.get("files", [])) - files_to_upload.extend(config.get("files", [])) - for f in files_to_upload: src = app_dir / f["source"] dest = f["destination"] @@ -132,13 +186,6 @@ async def deploy_with_builder( spinner.stop(success=True) print(f" {color_dim}{result.bytes} bytes, sha256={result.sha256[:12]}...{color_reset}") - bash_commands = [] - for step in config.get("steps", []): - if step.get("type") == "bash": - bash_commands.append((step.get("name", "bash"), step["run"])) - if config.get("run"): - bash_commands.append(("Install dependencies", config["run"])) - for step_name, run_cmd in bash_commands: info(f"Running: {step_name}") log = scrolling_log_cls(height=6, prefix=" ") @@ -170,21 +217,9 @@ async def deploy_with_builder( await interactive_shell(ws_url) print() - if has_fg and has_bg: - finish_label = "foreground+background" - elif has_fg: - finish_label = "foreground" - else: - finish_label = "background" spinner = spinner_cls(f"Finishing as {finish_label} app") spinner.start() - default_schedule = None - if isinstance(bg_cfg, dict): - default_schedule = bg_cfg.get("default_schedule") - elif has_bg: - default_schedule = meta.get("default_schedule") - await client.finish_app( name=name, bundle_id=bundle_id, diff --git a/truffile/schema/app_config.py b/truffile/schema/app_config.py index 53d9061..29bdd3e 100644 --- a/truffile/schema/app_config.py +++ b/truffile/schema/app_config.py @@ -1,6 +1,7 @@ from __future__ import annotations import ast +import re from pathlib import Path from typing import Any @@ -16,6 +17,46 @@ def _check_python_syntax(file_path: Path) -> tuple[bool, str]: return False, f"Line {e.lineno}: {e.msg}" +_ENV_KEY_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") + + +def _validate_process_cfg( + process: Any, + *, + path: str, + warnings: list[str], + errors: list[str], +) -> None: + if not isinstance(process, dict): + errors.append(f"{path} must be an object") + return + + cmd = process.get("cmd") + if not isinstance(cmd, list) or len(cmd) == 0: + errors.append(f"{path}.cmd must be a non-empty list") + elif not all(isinstance(v, str) and v.strip() for v in cmd): + errors.append(f"{path}.cmd must be list[str] with non-empty values") + + for key in ("working_directory", "cwd"): + if key in process and not isinstance(process.get(key), str): + errors.append(f"{path}.{key} must be a string") + + env_obj = process.get("environment", process.get("env")) + if env_obj is None: + return + if not isinstance(env_obj, dict): + errors.append(f"{path}.environment must be a map") + return + for k, v in env_obj.items(): + if not isinstance(k, str): + errors.append(f"{path}.environment keys must be strings") + continue + if not _ENV_KEY_RE.match(k): + warnings.append(f"{path}.environment key '{k}' is non-standard") + if not isinstance(v, str): + errors.append(f"{path}.environment['{k}'] must be a string") + + def validate_app_dir(app_dir: Path) -> tuple[bool, dict[str, Any] | None, str | None, list[str], list[str]]: """Validate app directory and return (valid, config, app_type, warnings, errors).""" warnings: list[str] = [] @@ -71,25 +112,31 @@ def validate_app_dir(app_dir: Path) -> tuple[bool, dict[str, Any] | None, str | if has_fg_cfg: process = fg_cfg.get("process") - if not isinstance(process, dict): - errors.append("metadata.foreground.process must be an object") - elif not isinstance(process.get("cmd"), list) or len(process.get("cmd", [])) == 0: - errors.append("metadata.foreground.process.cmd must be a non-empty list") + _validate_process_cfg( + process, + path="metadata.foreground.process", + warnings=warnings, + errors=errors, + ) if has_bg_cfg: process = bg_cfg.get("process") - if not isinstance(process, dict): - errors.append("metadata.background.process must be an object") - elif not isinstance(process.get("cmd"), list) or len(process.get("cmd", [])) == 0: - errors.append("metadata.background.process.cmd must be a non-empty list") + _validate_process_cfg( + process, + path="metadata.background.process", + warnings=warnings, + errors=errors, + ) if not isinstance(bg_cfg.get("default_schedule"), dict): errors.append("metadata.background.default_schedule must be an object") if not has_fg_cfg and not has_bg_cfg: process = meta.get("process") - if not isinstance(process, dict): - errors.append("metadata.process must be an object") - elif not isinstance(process.get("cmd"), list) or len(process.get("cmd", [])) == 0: - errors.append("metadata.process.cmd must be a non-empty list") + _validate_process_cfg( + process, + path="metadata.process", + warnings=warnings, + errors=errors, + ) if app_type == "ambient" and "default_schedule" in meta and not isinstance(meta.get("default_schedule"), dict): errors.append("metadata.default_schedule must be an object when provided") From 57dd576318517d01d4cecaa65c810686f4e6cc26 Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 22:46:03 -0800 Subject: [PATCH 7/9] chat and inference feature --- .gitignore | 3 +- README.md | 29 +- pyproject.toml | 1 - scripts/test_oai_proxy.py | 124 --- truffile/cli.py | 560 ++++++++++---- truffile/infer/README.md | 18 - truffile/infer/__init__.py | 1 - truffile/infer/common.py | 7 - truffile/infer/prompts.py | 100 --- truffile/infer/proxy.py | 756 ------------------- truffile/infer/tooling.py | 19 - truffle/infer/__init__.py | 0 truffle/infer/convo/__init__.py | 0 truffle/infer/convo/conversation_pb2.py | 40 - truffle/infer/convo/conversation_pb2.pyi | 25 - truffle/infer/convo/conversation_pb2_grpc.py | 24 - truffle/infer/convo/msg_pb2.py | 38 - truffle/infer/convo/msg_pb2.pyi | 26 - truffle/infer/convo/msg_pb2_grpc.py | 24 - truffle/infer/embedding_pb2.py | 40 - truffle/infer/embedding_pb2.pyi | 33 - truffle/infer/embedding_pb2_grpc.py | 24 - truffle/infer/finishreason_pb2.py | 36 - truffle/infer/finishreason_pb2.pyi | 24 - truffle/infer/finishreason_pb2_grpc.py | 24 - truffle/infer/gencfg_pb2.py | 48 -- truffle/infer/gencfg_pb2.pyi | 88 --- truffle/infer/gencfg_pb2_grpc.py | 24 - truffle/infer/infer_pb2.py | 55 -- truffle/infer/infer_pb2.pyi | 43 -- truffle/infer/infer_pb2_grpc.py | 668 ---------------- truffle/infer/irequest_pb2.py | 46 -- truffle/infer/irequest_pb2.pyi | 44 -- truffle/infer/irequest_pb2_grpc.py | 24 - truffle/infer/iresponse_pb2.py | 40 - truffle/infer/iresponse_pb2.pyi | 27 - truffle/infer/iresponse_pb2_grpc.py | 24 - truffle/infer/model_pb2.py | 66 -- truffle/infer/model_pb2.pyi | 171 ----- truffle/infer/model_pb2_grpc.py | 24 - truffle/infer/tokenize_pb2.py | 38 - truffle/infer/tokenize_pb2.pyi | 21 - truffle/infer/tokenize_pb2_grpc.py | 24 - truffle/infer/usage_pb2.py | 40 - truffle/infer/usage_pb2.pyi | 51 -- truffle/infer/usage_pb2_grpc.py | 24 - 46 files changed, 462 insertions(+), 3104 deletions(-) delete mode 100644 scripts/test_oai_proxy.py delete mode 100644 truffile/infer/README.md delete mode 100644 truffile/infer/__init__.py delete mode 100644 truffile/infer/common.py delete mode 100644 truffile/infer/prompts.py delete mode 100644 truffile/infer/proxy.py delete mode 100644 truffile/infer/tooling.py delete mode 100644 truffle/infer/__init__.py delete mode 100644 truffle/infer/convo/__init__.py delete mode 100644 truffle/infer/convo/conversation_pb2.py delete mode 100644 truffle/infer/convo/conversation_pb2.pyi delete mode 100644 truffle/infer/convo/conversation_pb2_grpc.py delete mode 100644 truffle/infer/convo/msg_pb2.py delete mode 100644 truffle/infer/convo/msg_pb2.pyi delete mode 100644 truffle/infer/convo/msg_pb2_grpc.py delete mode 100644 truffle/infer/embedding_pb2.py delete mode 100644 truffle/infer/embedding_pb2.pyi delete mode 100644 truffle/infer/embedding_pb2_grpc.py delete mode 100644 truffle/infer/finishreason_pb2.py delete mode 100644 truffle/infer/finishreason_pb2.pyi delete mode 100644 truffle/infer/finishreason_pb2_grpc.py delete mode 100644 truffle/infer/gencfg_pb2.py delete mode 100644 truffle/infer/gencfg_pb2.pyi delete mode 100644 truffle/infer/gencfg_pb2_grpc.py delete mode 100644 truffle/infer/infer_pb2.py delete mode 100644 truffle/infer/infer_pb2.pyi delete mode 100644 truffle/infer/infer_pb2_grpc.py delete mode 100644 truffle/infer/irequest_pb2.py delete mode 100644 truffle/infer/irequest_pb2.pyi delete mode 100644 truffle/infer/irequest_pb2_grpc.py delete mode 100644 truffle/infer/iresponse_pb2.py delete mode 100644 truffle/infer/iresponse_pb2.pyi delete mode 100644 truffle/infer/iresponse_pb2_grpc.py delete mode 100644 truffle/infer/model_pb2.py delete mode 100644 truffle/infer/model_pb2.pyi delete mode 100644 truffle/infer/model_pb2_grpc.py delete mode 100644 truffle/infer/tokenize_pb2.py delete mode 100644 truffle/infer/tokenize_pb2.pyi delete mode 100644 truffle/infer/tokenize_pb2_grpc.py delete mode 100644 truffle/infer/usage_pb2.py delete mode 100644 truffle/infer/usage_pb2.pyi delete mode 100644 truffle/infer/usage_pb2_grpc.py diff --git a/.gitignore b/.gitignore index befe92f..85f5ffb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ __pycache__/ .DS_Store dist docs -whatsapp/ \ No newline at end of file +whatsapp/ +docs/ \ No newline at end of file diff --git a/README.md b/README.md index 910d4c4..8575f40 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,16 @@ TruffleOS SDK - deploy apps to Truffle devices +## proto sync + +`truffile` vendors generated protobuf modules from `pyfw/python/truffle`. + +To refresh them: + +```bash +./scripts/sync_protos.sh +``` + ## install ```bash @@ -39,11 +49,26 @@ truffile list apps # list connected devices truffile list devices +# list IF2 models on the connected device +truffile models + +# chat with IF2 model (streaming by default) +truffile chat "hello" + +# chat with explicit model + system prompt +truffile chat --model --system "You are concise" --prompt "Summarize this" + +# run OpenAI-compatible proxy backed by IF2 +truffile proxy --host 127.0.0.1 --port 8080 + # disconnect from a device truffile disconnect truffle-6272 # disconnect from all devices truffile disconnect all + +# OpenAI-compatible base URL (proxy mode) +# http://127.0.0.1:8080/v1 ``` ## truffile.yaml @@ -103,5 +128,5 @@ default_schedule: ## example apps see `example-apps/` for working examples: -- `example-apps/ambient/hedge` - background app -- `example-apps/focus/finance` - foreground app +- `example-apps/kalshi` - foreground + background app +- `example-apps/reddit` - background app diff --git a/pyproject.toml b/pyproject.toml index 238e887..4f4c3a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ dependencies = [ [project.scripts] truffile = "truffile.cli:main" -truffleinferproxy = "truffile.infer.proxy:main" [project.optional-dependencies] dev = [ diff --git a/scripts/test_oai_proxy.py b/scripts/test_oai_proxy.py deleted file mode 100644 index dcdf6a3..0000000 --- a/scripts/test_oai_proxy.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python3 -"""Smoke test for the local OpenAI-compatible proxy.""" - -from __future__ import annotations - -import argparse -import os -from typing import Any, Dict, List -try: - from openai import OpenAI -except ImportError: - raise ImportError("Please install the 'openai' package to run this test script.") - -def _print_header(title: str) -> None: - print("\n" + "=" * 8 + f" {title} " + "=" * 8) - - -def test_basic(client: OpenAI, model: str) -> None: - _print_header("basic") - resp = client.chat.completions.create( - model=model, - messages=[{"role": "user", "content": "Say hello in one sentence."}], - max_tokens=2048, - temperature=0.7, - top_p=0.9, - ) - msg = resp.choices[0].message - reasoning = getattr(msg, "reasoning_content", None) - if reasoning: - print("reasoning_content:", reasoning[:200], "..." if len(reasoning) > 200 else "") - print("content:", msg.content) - - -def test_json_schema(client: OpenAI, model: str) -> None: - _print_header("json_schema") - schema: Dict[str, Any] = { - "type": "object", - "properties": { - "answer": {"type": "string"}, - "confidence": {"type": "number"}, - }, - "required": ["answer", "confidence"], - } - resp = client.chat.completions.create( - model=model, - messages=[{"role": "user", "content": "What is 2+2? Respond as JSON."}], - response_format={"type": "json_schema", "json_schema": schema}, - max_tokens=2048, - ) - msg = resp.choices[0].message - print("content:", msg.content) - - -def test_tools(client: OpenAI, model: str) -> None: - _print_header("tools") - tools: List[Dict[str, Any]] = [ - { - "type": "function", - "function": { - "name": "get_time", - "description": "Return the current time in ISO-8601", - "parameters": { - "type": "object", - "properties": {"tz": {"type": "string"}}, - "required": [], - }, - }, - } - ] - resp = client.chat.completions.create( - model=model, - messages=[{"role": "user", "content": "What time is it? Use the tool."}], - tools=tools, - tool_choice="auto", - max_tokens=2048, - ) - msg = resp.choices[0].message - print("tool_calls:", msg.tool_calls) - print("content:", msg.content) - - -def test_stream(client: OpenAI, model: str) -> None: - _print_header("stream") - stream = client.chat.completions.create( - model=model, - messages=[{"role": "user", "content": "Stream a short haiku."}], - max_tokens=2048, - stream=True, - ) - parts: List[str] = [] - reasoning_parts: List[str] = [] - for chunk in stream: - delta = chunk.choices[0].delta - if delta: - if delta.content: - parts.append(delta.content) - reasoning = getattr(delta, "reasoning_content", None) - if reasoning: - reasoning_parts.append(reasoning) - if reasoning_parts: - full_reasoning = "".join(reasoning_parts) - print("reasoning_content:", full_reasoning[:200], "..." if len(full_reasoning) > 200 else "") - print("content:", "".join(parts)) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Smoke test for OpenAI proxy") - parser.add_argument("--base-url", default="http://127.0.0.1:8080/v1", help="Proxy base URL") - parser.add_argument("--model", default="auto", help="Model name or UUID") - parser.add_argument("--no-stream", action="store_true", help="Skip streaming test") - args = parser.parse_args() - - api_key = os.getenv("OPENAI_API_KEY", "test") - client = OpenAI(base_url=args.base_url, api_key=api_key) - - test_basic(client, args.model) - test_json_schema(client, args.model) - test_tools(client, args.model) - if not args.no_stream: - test_stream(client, args.model) - - -if __name__ == "__main__": - main() diff --git a/truffile/cli.py b/truffile/cli.py index 31a6f34..cb7335d 100644 --- a/truffile/cli.py +++ b/truffile/cli.py @@ -1,21 +1,20 @@ import argparse import asyncio +import json import signal import socket import sys import threading import time +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path +import httpx from truffile.storage import StorageService from truffile.client import TruffleClient, resolve_mdns, NewSessionStatus from truffile.schema import validate_app_dir from truffile.deploy import build_deploy_plan, deploy_with_builder -import grpc -from truffle.infer.infer_pb2_grpc import InferenceServiceStub -from truffle.infer.model_pb2 import GetModelListRequest, Model - # ANSI colors class C: @@ -702,7 +701,7 @@ def cmd_list(args, storage: StorageService) -> int: async def cmd_models(storage: StorageService) -> int: - """List models on the connected device.""" + """List IF2 models on the connected device.""" device = storage.state.last_used_device if not device: error("No device connected") @@ -717,140 +716,432 @@ async def cmd_models(storage: StorageService) -> int: except RuntimeError: spinner.fail(f"Could not resolve {device}.local") return 1 - + try: - channel = grpc.insecure_channel(f"{ip}:80") - stub = InferenceServiceStub(channel) - model_list = stub.GetModelList(GetModelListRequest(use_filter=False)) + url = f"http://{ip}/if2/v1/models" + with httpx.Client(timeout=15.0) as client: + resp = client.get(url) + resp.raise_for_status() + payload = resp.json() spinner.stop(success=True) except Exception as e: - spinner.fail(f"Failed to get models: {e}") + spinner.fail(f"Failed to get IF2 models: {e}") return 1 - - loaded = [m for m in model_list.models if m.state == Model.MODEL_STATE_LOADED] - available = [m for m in model_list.models if m.state == Model.MODEL_STATE_AVAILABLE] - + + models = payload.get("data", []) + if not isinstance(models, list): + spinner.fail("Invalid response: missing 'data' list") + return 1 + print() - print(f"{MUSHROOM} {C.BOLD}Models on {device}{C.RESET}") + print(f"{MUSHROOM} {C.BOLD}IF2 Models on {device}{C.RESET}") print() - - if loaded: - for m in loaded: - reasoner = f" {C.MAGENTA}reasoner{C.RESET}" if m.config.info.has_chain_of_thought else "" - print(f" {C.GREEN}{CHECK}{C.RESET} {m.name}{reasoner}") - print(f" {C.DIM}id: {m.uuid}{C.RESET}") - - if available: - for m in available: - print(f" {C.DIM}○ {m.name} (not loaded){C.RESET}") - - if not loaded and not available: + + if not models: print(f" {C.DIM}No models found{C.RESET}") - - print() - total_mb = model_list.total_memory // (1024 * 1024) if model_list.total_memory else 0 - used_mb = model_list.used_memory // (1024 * 1024) if model_list.used_memory else 0 - print(f"{C.DIM}Memory: {used_mb}MB / {total_mb}MB{C.RESET}") - + return 0 + + for m in models: + if not isinstance(m, dict): + continue + model_id = m.get("id", "") + name = m.get("name", model_id) + uuid = m.get("uuid", "") + ctx = m.get("context_length", "") + arch = m.get("architecture", {}) + tokenizer = arch.get("tokenizer", "") if isinstance(arch, dict) else "" + max_batch = m.get("max_batch_size", "") + print(f" {C.GREEN}{CHECK}{C.RESET} {name}") + print(f" {C.DIM}id: {model_id}{C.RESET}") + print(f" {C.DIM}uuid: {uuid}{C.RESET}") + print(f" {C.DIM}context: {ctx}, tokenizer: {tokenizer}, max_batch: {max_batch}{C.RESET}") + return 0 -def cmd_proxy(args, storage: StorageService) -> int: - """Start the OpenAI-compatible proxy.""" - device = args.device if hasattr(args, 'device') and args.device else storage.state.last_used_device +async def _resolve_connected_device(storage: StorageService) -> tuple[str, str] | tuple[None, None]: + device = storage.state.last_used_device + if not device: + error("No device connected") + print(f" {C.DIM}Run: truffile connect {C.RESET}") + return None, None + try: + ip = await resolve_mdns(f"{device}.local") + except RuntimeError: + error(f"Could not resolve {device}.local") + return None, None + return device, ip + + +async def _default_model(ip: str) -> str | None: + try: + with httpx.Client(timeout=10.0) as client: + resp = client.get(f"http://{ip}/if2/v1/models") + resp.raise_for_status() + payload = resp.json() + models = payload.get("data", []) + if not isinstance(models, list) or not models: + return None + first = models[0] + if not isinstance(first, dict): + return None + return str(first.get("uuid") or first.get("id") or "") + except Exception: + return None + + +async def cmd_chat(args, storage: StorageService) -> int: + device, ip = await _resolve_connected_device(storage) + if not device or not ip: + return 1 + + prompt = args.prompt + if not prompt and args.prompt_words: + prompt = " ".join(args.prompt_words).strip() + if not prompt: + error("Missing prompt") + print(f" {C.DIM}Usage: truffile chat --prompt \"hello\"{C.RESET}") + print(f" {C.DIM}Or: truffile chat \"hello\"{C.RESET}") + return 1 + + model = args.model + if not model: + spinner = Spinner("Resolving default model") + spinner.start() + model = await _default_model(ip) + if not model: + spinner.fail("Failed to resolve default model from IF2") + return 1 + spinner.stop(success=True) + + stream = not args.no_stream and not args.json + messages: list[dict[str, str]] = [] + if args.system: + messages.append({"role": "system", "content": args.system}) + messages.append({"role": "user", "content": prompt}) + + payload: dict = { + "model": model, + "messages": messages, + "stream": stream, + "reasoning": {"enabled": bool(args.reasoning)}, + } + if args.max_tokens is not None: + payload["max_tokens"] = args.max_tokens + else: + payload["max_tokens"] = 512 + if args.temperature is not None: + payload["temperature"] = args.temperature + if args.top_p is not None: + payload["top_p"] = args.top_p + if stream: + payload["stream_options"] = {"include_usage": True} + + url = f"http://{ip}/if2/v1/chat/completions" + headers = {"Content-Type": "application/json"} + + spinner = Spinner(f"Connecting to {device}") + spinner.start() + try: + with httpx.Client(timeout=None) as client: + if stream: + with client.stream("POST", url, headers=headers, json=payload) as resp: + resp.raise_for_status() + spinner.stop(success=True) + usage_printed = False + for raw in resp.iter_lines(): + if not raw: + continue + line = raw.strip() + if not line.startswith("data:"): + continue + data = line[len("data:"):].strip() + if data == "[DONE]": + break + try: + evt = json.loads(data) + except Exception: + continue + + choices = evt.get("choices") + if isinstance(choices, list) and choices: + c0 = choices[0] + if isinstance(c0, dict): + delta = c0.get("delta", {}) + if isinstance(delta, dict): + txt = delta.get("content") + if isinstance(txt, str) and txt: + print(txt, end="", flush=True) + reasoning = delta.get("reasoning") + if args.reasoning and isinstance(reasoning, str) and reasoning: + print(reasoning, end="", flush=True) + + usage = evt.get("usage") + if isinstance(usage, dict) and not usage_printed: + usage_printed = True + print(f"\n{C.DIM}[usage] {usage}{C.RESET}", flush=True) + print() + else: + resp = client.post(url, headers=headers, json=payload, timeout=120.0) + resp.raise_for_status() + spinner.stop(success=True) + body = resp.json() + if args.json: + print(json.dumps(body, indent=2)) + else: + content = "" + try: + choices = body.get("choices", []) + if isinstance(choices, list) and choices: + msg = choices[0].get("message", {}) + if isinstance(msg, dict): + content = str(msg.get("content", "")) + except Exception: + content = "" + print(content) + return 0 + except Exception as e: + spinner.fail(f"Chat request failed: {e}") + return 1 + + +def _inject_reasoning_into_chunk(chunk: dict, state: dict) -> dict: + choices = chunk.get("choices") + if not isinstance(choices, list) or not choices: + return chunk + c0 = choices[0] + if not isinstance(c0, dict): + return chunk + delta = c0.get("delta") + if not isinstance(delta, dict): + return chunk + + reasoning = delta.get("reasoning") + content = delta.get("content") + merged = "" + + if isinstance(reasoning, str) and reasoning: + if not state.get("thinking_open", False): + merged += "\n" + state["thinking_open"] = True + merged += reasoning + + if isinstance(content, str) and content: + if state.get("thinking_open", False): + merged += "\n\n" + state["thinking_open"] = False + merged += content + + if merged: + delta["content"] = merged + if "reasoning" in delta: + del delta["reasoning"] + return chunk + + +def _inject_reasoning_into_response(body: dict) -> dict: + choices = body.get("choices") + if not isinstance(choices, list): + return body + for c in choices: + if not isinstance(c, dict): + continue + msg = c.get("message") + if not isinstance(msg, dict): + continue + reasoning = msg.get("reasoning") + content = msg.get("content", "") + if isinstance(reasoning, str) and reasoning: + content_text = content if isinstance(content, str) else str(content) + msg["content"] = f"\n{reasoning}\n\n{content_text}" + if "reasoning" in msg: + del msg["reasoning"] + return body + + +async def cmd_proxy(args, storage: StorageService) -> int: + device = args.device if args.device else storage.state.last_used_device if not device: error("No device specified or connected") print(f" {C.DIM}Run: truffile connect {C.RESET}") print(f" {C.DIM}Or: truffile proxy --device {C.RESET}") return 1 - - port = args.port if hasattr(args, 'port') else 8080 - host = args.host if hasattr(args, 'host') else "127.0.0.1" - debug = args.debug if hasattr(args, 'debug') else False - - spinner = None - + + spinner = Spinner(f"Resolving {device}.local") + spinner.start() try: - print(f"{MUSHROOM} {C.BOLD}Starting OpenAI proxy{C.RESET}") - print() - - spinner = Spinner(f"Resolving {device}.local") - spinner.start() - - hostname = f"{device}.local" - ip = socket.gethostbyname(hostname) - spinner.stop(success=True) - - grpc_address = f"{ip}:80" - - spinner = Spinner("Connecting to inference service") - spinner.start() - - from truffile.infer.proxy import OpenAIProxy, OpenAIProxyHandler - from http.server import ThreadingHTTPServer - - proxy = OpenAIProxy(grpc_address, include_debug=debug) - - channel = grpc.insecure_channel(grpc_address) - stub = InferenceServiceStub(channel) - model_list = stub.GetModelList(GetModelListRequest(use_filter=False)) - loaded = [m for m in model_list.models if m.state == Model.MODEL_STATE_LOADED] + ip = await resolve_mdns(f"{device}.local") spinner.stop(success=True) - spinner = None - - print(f" {C.DIM}Device: {device} ({ip}){C.RESET}") - print(f" {C.DIM}Models: {len(loaded)} loaded{C.RESET}") - - print() - print(f"{C.GREEN}{CHECK}{C.RESET} Proxy running at {C.BOLD}http://{host}:{port}/v1{C.RESET}") - print() - print(f" {C.DIM}Use with OpenAI SDK:{C.RESET}") - print(f" {C.CYAN}from openai import OpenAI{C.RESET}") - print(f" {C.CYAN}client = OpenAI(base_url=\"http://{host}:{port}/v1\", api_key=\"x\"){C.RESET}") - print() - print(f" {C.DIM}Or set environment variables:{C.RESET}") - print(f" {C.CYAN}export OPENAI_BASE_URL=http://{host}:{port}/v1{C.RESET}") - print(f" {C.CYAN}export OPENAI_API_KEY=anything{C.RESET}") - print() - print(f" {C.DIM}Press Ctrl+C to stop{C.RESET}") - print() - - class _Server(ThreadingHTTPServer): - def __init__(self, server_address, handler_cls): - super().__init__(server_address, handler_cls) - self.proxy = proxy - - server = _Server((host, port), OpenAIProxyHandler) + except RuntimeError: + spinner.fail(f"Could not resolve {device}.local") + return 1 + + target_base = f"http://{ip}" + host = args.host + port = args.port + include_think_tags = not args.no_think_tags + + class ProxyHandler(BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + + def log_message(self, _format, *_args): + return + + def _send_json(self, code: int, body: dict): + raw = json.dumps(body).encode("utf-8") + self.send_response(code) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(raw))) + self.end_headers() + self.wfile.write(raw) + + def _map_path(self, path: str) -> str | None: + if path == "/v1/models": + return "/if2/v1/models" + if path == "/v1/chat/completions": + return "/if2/v1/chat/completions" + return None + + def _forward_headers(self) -> dict[str, str]: + out: dict[str, str] = {"Content-Type": "application/json"} + auth = self.headers.get("Authorization") + if auth: + out["Authorization"] = auth + return out + + def do_GET(self): + mapped = self._map_path(self.path) + if not mapped: + self._send_json(404, {"error": {"message": "Not found"}}) + return + + try: + with httpx.Client(timeout=30.0) as client: + resp = client.get(f"{target_base}{mapped}", headers=self._forward_headers()) + self.send_response(resp.status_code) + self.send_header("Content-Type", resp.headers.get("content-type", "application/json")) + self.send_header("Content-Length", str(len(resp.content))) + self.end_headers() + self.wfile.write(resp.content) + except Exception as e: + self._send_json(502, {"error": {"message": f"Upstream GET failed: {e}"}}) + + def do_POST(self): + mapped = self._map_path(self.path) + if not mapped: + self._send_json(404, {"error": {"message": "Not found"}}) + return + + raw_body = b"" + try: + content_len = int(self.headers.get("Content-Length", "0")) + raw_body = self.rfile.read(content_len) if content_len > 0 else b"{}" + body = json.loads(raw_body.decode("utf-8")) + except Exception as e: + self._send_json(400, {"error": {"message": f"Invalid JSON body: {e}"}}) + return + + if mapped == "/if2/v1/chat/completions": + if "reasoning" not in body: + body["reasoning"] = {"enabled": False} + + stream_mode = bool(body.get("stream")) and mapped == "/if2/v1/chat/completions" + + try: + with httpx.Client(timeout=None) as client: + if stream_mode: + with client.stream( + "POST", + f"{target_base}{mapped}", + headers=self._forward_headers(), + json=body, + ) as resp: + self.send_response(resp.status_code) + self.send_header("Content-Type", "text/event-stream; charset=utf-8") + self.send_header("Cache-Control", "no-cache") + self.send_header("Connection", "keep-alive") + self.end_headers() + + state = {"thinking_open": False} + for raw_line in resp.iter_lines(): + line = raw_line if isinstance(raw_line, str) else raw_line.decode("utf-8", errors="replace") + if not line: + self.wfile.write(b"\n") + self.wfile.flush() + continue + if line.startswith("data:"): + payload = line[5:].strip() + if payload == "[DONE]": + if include_think_tags and state.get("thinking_open", False): + close_evt = { + "choices": [{"delta": {"content": "\n\n"}, "index": 0}] + } + out = f"data: {json.dumps(close_evt, separators=(',', ':'))}\n\n" + self.wfile.write(out.encode("utf-8")) + self.wfile.write(b"data: [DONE]\n\n") + self.wfile.flush() + break + try: + evt = json.loads(payload) + if include_think_tags: + evt = _inject_reasoning_into_chunk(evt, state) + out = f"data: {json.dumps(evt, separators=(',', ':'))}\n\n" + except Exception: + out = f"{line}\n\n" + self.wfile.write(out.encode("utf-8")) + else: + self.wfile.write((line + "\n").encode("utf-8")) + self.wfile.flush() + else: + resp = client.post( + f"{target_base}{mapped}", + headers=self._forward_headers(), + json=body, + timeout=120.0, + ) + content = resp.content + if ( + mapped == "/if2/v1/chat/completions" + and include_think_tags + and "application/json" in resp.headers.get("content-type", "") + ): + try: + parsed = json.loads(content.decode("utf-8")) + parsed = _inject_reasoning_into_response(parsed) + content = json.dumps(parsed).encode("utf-8") + except Exception: + pass + self.send_response(resp.status_code) + self.send_header("Content-Type", resp.headers.get("content-type", "application/json")) + self.send_header("Content-Length", str(len(content))) + self.end_headers() + self.wfile.write(content) + except Exception as e: + self._send_json(502, {"error": {"message": f"Upstream POST failed: {e}"}}) + + print(f"{MUSHROOM} {C.BOLD}truffile proxy{C.RESET}") + print() + print(f" {C.DIM}Device:{C.RESET} {device} ({ip})") + print(f" {C.DIM}Listen:{C.RESET} http://{host}:{port}") + print(f" {C.DIM}Upstream:{C.RESET} {target_base}/if2/v1/*") + print(f" {C.DIM}Reasoning tags:{C.RESET} {'on' if include_think_tags else 'off'}") + print() + print(f" {C.DIM}OpenAI-compatible base URL:{C.RESET}") + print(f" {C.CYAN}http://{host}:{port}/v1{C.RESET}") + print() + print(f" {C.DIM}Press Ctrl+C to stop{C.RESET}") + print() + + try: + server = ThreadingHTTPServer((host, port), ProxyHandler) server.serve_forever() - except KeyboardInterrupt: - if spinner: - spinner.running = False - sys.stdout.write("\r\033[K") - sys.stdout.flush() print(f"{C.RED}{CROSS} Cancelled{C.RESET}") return 130 - except socket.gaierror: - if spinner: - spinner.fail(f"Could not resolve {device}.local") - else: - error(f"Could not resolve {device}.local") - print(f" {C.DIM}Try: ping {device}.local{C.RESET}") - return 1 except OSError as e: - if spinner: - spinner.fail(str(e)) - else: - error(f"Could not start server: {e}") - print(f" {C.DIM}Port {port} may already be in use{C.RESET}") - return 1 - except Exception as e: - if spinner: - spinner.fail(str(e)) - else: - error(str(e)) + error(f"Could not start proxy: {e}") return 1 - + return 0 @@ -995,8 +1286,9 @@ def print_help(): print(f" {C.BLUE}validate{C.RESET} [path] Validate app config and files") print(f" {C.BLUE}delete{C.RESET} Delete installed apps from device") print(f" {C.BLUE}list{C.RESET} List installed apps or devices") - print(f" {C.BLUE}models{C.RESET} List AI models on connected device") - print(f" {C.BLUE}proxy{C.RESET} Start OpenAI-compatible inference proxy") + print(f" {C.BLUE}models{C.RESET} List IF2 models on connected device") + print(f" {C.BLUE}chat{C.RESET} [prompt] Chat with IF2 model on connected device") + print(f" {C.BLUE}proxy{C.RESET} Run OpenAI-compatible IF2 proxy") print() print(f"{C.BOLD}Examples:{C.RESET}") print(f" {C.DIM}truffile scan{C.RESET} {C.DIM}# find devices on network{C.RESET}") @@ -1006,9 +1298,9 @@ def print_help(): print(f" {C.DIM}truffile deploy{C.RESET} {C.DIM}# uses current directory{C.RESET}") print(f" {C.DIM}truffile validate ./my-app{C.RESET}") print(f" {C.DIM}truffile list apps{C.RESET}") - print(f" {C.DIM}truffile models{C.RESET} {C.DIM}# show loaded models{C.RESET}") - print(f" {C.DIM}truffile proxy{C.RESET} {C.DIM}# start proxy on :8080{C.RESET}") - print(f" {C.DIM}truffile proxy --port 9000{C.RESET}") + print(f" {C.DIM}truffile models{C.RESET} {C.DIM}# show IF2 models{C.RESET}") + print(f" {C.DIM}truffile chat \"hello\"{C.RESET} {C.DIM}# run IF2 chat completion{C.RESET}") + print(f" {C.DIM}truffile proxy{C.RESET} {C.DIM}# run local /v1 proxy{C.RESET}") print() @@ -1047,12 +1339,24 @@ def main() -> int: p_list.add_argument("what", choices=["apps", "devices"], nargs="?") p_models = subparsers.add_parser("models", add_help=False) - + + p_chat = subparsers.add_parser("chat", add_help=False) + p_chat.add_argument("prompt_words", nargs="*", help="Prompt text (alternative to --prompt)") + p_chat.add_argument("-p", "--prompt", help="Prompt text") + p_chat.add_argument("-m", "--model", help="Model id/uuid (default: first model from IF2 list)") + p_chat.add_argument("--system", help="System prompt") + p_chat.add_argument("--reasoning", action="store_true", help="Enable reasoning mode") + p_chat.add_argument("--max-tokens", type=int, help="Max response tokens") + p_chat.add_argument("--temperature", type=float, help="Sampling temperature") + p_chat.add_argument("--top-p", type=float, help="Nucleus sampling top-p") + p_chat.add_argument("--no-stream", action="store_true", help="Disable streaming output") + p_chat.add_argument("--json", action="store_true", help="Print full JSON response (non-stream)") + p_proxy = subparsers.add_parser("proxy", add_help=False) - p_proxy.add_argument("--device", "-d", help="Device name (defaults to last connected)") - p_proxy.add_argument("--port", "-p", type=int, default=8080, help="Port to listen on") - p_proxy.add_argument("--host", default="127.0.0.1", help="Host to bind to") - p_proxy.add_argument("--debug", action="store_true", help="Include reasoning in responses") + p_proxy.add_argument("--device", "-d", help="Device name (default: last connected)") + p_proxy.add_argument("--host", default="127.0.0.1", help="Host to bind") + p_proxy.add_argument("--port", "-p", type=int, default=8080, help="Port to bind") + p_proxy.add_argument("--no-think-tags", action="store_true", help="Do not inject tags") args = parser.parse_args() @@ -1092,8 +1396,10 @@ def main() -> int: return cmd_list(args, storage) elif args.command == "models": return run_async(cmd_models(storage)) + elif args.command == "chat": + return run_async(cmd_chat(args, storage)) elif args.command == "proxy": - return cmd_proxy(args, storage) + return run_async(cmd_proxy(args, storage)) elif args.command == "validate": return cmd_validate(args) diff --git a/truffile/infer/README.md b/truffile/infer/README.md deleted file mode 100644 index f267b20..0000000 --- a/truffile/infer/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Accessing Inference APIs on the Truffle - -The Truffle currently uses its own non-standard set of APIs for inference. - -Provided here is a proxy that both demonstrates the usage of these APIs and allows for easier compatibility with existing clients. - -This is experimental and may not be fully API compatible, but should serve as a good starting point for exploring the Truffle while core software improves. - -### Usage - -```bash -truffleinferproxy --truffle truffle-5970 --host 127.0.0.1 --port 8080 - -truffleinferproxy --help -``` - - - diff --git a/truffile/infer/__init__.py b/truffile/infer/__init__.py deleted file mode 100644 index d9b5eaa..0000000 --- a/truffile/infer/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Standalone OpenAI-compatible proxy for Truffle gRPC inference.""" diff --git a/truffile/infer/common.py b/truffile/infer/common.py deleted file mode 100644 index 53537dd..0000000 --- a/truffile/infer/common.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -THINK_TAGS = ["", ""] - - -def clean_response(response: str) -> str: - return response.strip().replace("�", "") diff --git a/truffile/infer/prompts.py b/truffile/infer/prompts.py deleted file mode 100644 index 56daf62..0000000 --- a/truffile/infer/prompts.py +++ /dev/null @@ -1,100 +0,0 @@ -from __future__ import annotations - -from typing import List, Tuple -import json -import re - -from truffle.infer.gencfg_pb2 import ResponseFormat - -from .common import THINK_TAGS -from .tooling import Tool - - -TOOL_TAGS = ["", ""] -tool_tag_pattern = re.compile(f"{TOOL_TAGS[0]}(.*?){TOOL_TAGS[1]}", re.DOTALL) - - -class AgentPromptBuilder: - def extract_tool_calls(self, response: str) -> Tuple[List[dict], str]: - tool_calls: List[dict] = [] - matches = tool_tag_pattern.findall(response) - if not matches: - return tool_calls, response - for match in matches: - try: - tool_call = json.loads(match.strip()) - tool_calls.append(tool_call) - except json.JSONDecodeError: - continue - clean_response = tool_tag_pattern.sub("", response).strip() - return tool_calls, clean_response - - -def _build_tool_call_response_format_non_reasoning( - req, available_tools: List[Tool], allow_parallel: bool = False -) -> None: - def get_tag_for_tool(tool: Tool) -> dict: - begin = f"{TOOL_TAGS[0]}\n" + '{"tool": ' + f'"{tool.name}", "args": ' - end = "}" + f"{TOOL_TAGS[1]}\n" - return { - "begin": begin, - "content": {"type": "json_schema", "json_schema": tool.input_schema}, - "end": end, - } - - structural_tag = { - "type": "structural_tag", - "format": { - "type": "triggered_tags", - "triggers": [TOOL_TAGS[0]], - "tags": [get_tag_for_tool(tool) for tool in available_tools], - "stop_after_first": not allow_parallel, - }, - } - try: - fmt = ResponseFormat.STRUCTURAL_TAG - except AttributeError: - # older proto or server; fall back to prompt-only tool guidance. - return - req.cfg.response_format.format = fmt - req.cfg.response_format.schema = json.dumps(structural_tag, indent=0) - - -def _build_tool_call_response_format( - req, available_tools: List[Tool], allow_parallel: bool = False -) -> None: - def get_tag_for_tool(tool: Tool) -> dict: - begin = f"{TOOL_TAGS[0]}\n" + '{"tool": ' + f'"{tool.name}", "args": ' - end = "}" + f"{TOOL_TAGS[1]}\n" - return { - "begin": begin, - "content": {"type": "json_schema", "json_schema": tool.input_schema}, - "end": end, - } - structural_tag = { - "type": "structural_tag", - "format": { - "type": "sequence", - "elements": [ - { - "type": "tag", - "begin": "", - "content": {"type": "any_text"}, - "end": THINK_TAGS[1], - }, - { - "type": "triggered_tags", - "triggers": [TOOL_TAGS[0]], - "tags": [get_tag_for_tool(tool) for tool in available_tools], - "stop_after_first": not allow_parallel, - }, - ], - }, - } - try: - fmt = ResponseFormat.STRUCTURAL_TAG - except AttributeError: - # older proto or server; fall back to prompt-only tool guidance. - return - req.cfg.response_format.format = fmt - req.cfg.response_format.schema = json.dumps(structural_tag, indent=0) diff --git a/truffile/infer/proxy.py b/truffile/infer/proxy.py deleted file mode 100644 index 262d1f1..0000000 --- a/truffile/infer/proxy.py +++ /dev/null @@ -1,756 +0,0 @@ -#!/usr/bin/env python3 -"""Minimal OpenAI-compatible /v1/chat/completions proxy for Truffle gRPC inference.""" - -from __future__ import annotations - -import argparse -import json -import threading -import time -import uuid -import os -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from typing import Any, Dict, List, Optional, Tuple - -import grpc - -from .common import THINK_TAGS, clean_response -from .prompts import ( - TOOL_TAGS, - AgentPromptBuilder, - _build_tool_call_response_format, - _build_tool_call_response_format_non_reasoning, -) -from .tooling import Tool - -from truffle.infer.convo.conversation_pb2 import Conversation, Message -from truffle.infer.finishreason_pb2 import FinishReason -from truffle.infer.gencfg_pb2 import ResponseFormat -from truffle.infer.irequest_pb2 import IRequest -from truffle.infer.infer_pb2_grpc import InferenceServiceStub -from truffle.infer.model_pb2 import GetModelListRequest, Model - - -_MODEL_LOCK = threading.Lock() -_MODEL_CACHE: Dict[str, Model] = {} -_MODEL_LIST: List[Model] = [] - - -def _now_ts() -> int: - return int(time.time()) - - -def _gen_id(prefix: str) -> str: - return f"{prefix}-{uuid.uuid4().hex}" - - -def _load_models(stub: InferenceServiceStub) -> None: - global _MODEL_CACHE, _MODEL_LIST - model_list = stub.GetModelList(GetModelListRequest(use_filter=False)) - models = [m for m in model_list.models if m.state == Model.MODEL_STATE_LOADED] - cache: Dict[str, Model] = {} - for m in models: - cache[m.uuid] = m - cache[m.name.lower()] = m - _MODEL_LIST = models - _MODEL_CACHE = cache - - -def _get_models(stub: InferenceServiceStub) -> List[Model]: - with _MODEL_LOCK: - if not _MODEL_LIST: - _load_models(stub) - return list(_MODEL_LIST) - - -def _resolve_model(stub: InferenceServiceStub, model_str: Optional[str]) -> Tuple[Model, bool]: - models = _get_models(stub) - model_key = (model_str or "").strip() - if model_key and model_key.lower() not in {"auto", "default"}: - with _MODEL_LOCK: - m = _MODEL_CACHE.get(model_key) or _MODEL_CACHE.get(model_key.lower()) - if m is not None: - return m, bool(m.config.info.has_chain_of_thought) - for m in models: - if m.config.info.has_chain_of_thought: - return m, True - if not models: - raise RuntimeError("No loaded models available") - return models[0], bool(models[0].config.info.has_chain_of_thought) - - -def _flatten_content(content: Any) -> str: - if content is None: - return "" - if isinstance(content, str): - return content - if isinstance(content, list): - parts: List[str] = [] - for p in content: - if isinstance(p, dict) and p.get("type") == "text": - parts.append(p.get("text") or "") - return "".join(parts) - return str(content) - - -def _build_tool_list(tools_spec: List[Dict[str, Any]]) -> List[Tool]: - tools: List[Tool] = [] - for t in tools_spec: - if t.get("type") != "function": - continue - fn = t.get("function", {}) - name = fn.get("name") - if not name: - continue - tools.append( - Tool( - name=name, - description=fn.get("description") or "", - input_schema=fn.get("parameters") or {"type": "object"}, - display_name=name, - ) - ) - return tools - - -def _tool_system_prompt(tools: List[Tool]) -> str: - tool_desc = "\n".join([t.get_for_system_prompt() for t in tools]) - return ( - "You have access to the following tools:\n" - f"{tool_desc}\n" - f"When you decide to use a tool, respond with a JSON object enclosed by {TOOL_TAGS[0]} and {TOOL_TAGS[1]} tags in this format:\n" - f"{TOOL_TAGS[0]}\n{{\n \"tool\": \"\",\n \"args\": {{}}\n}}\n{TOOL_TAGS[1]}\n" - "Only use tools listed above, and ensure your JSON is valid." - ) - - -def _apply_tool_prompt(messages: List[Dict[str, Any]], prompt: str) -> None: - for msg in messages: - if msg.get("role") == "system": - content = _flatten_content(msg.get("content")) - msg["content"] = content + "\n\n" + prompt - return - messages.insert(0, {"role": "system", "content": prompt}) - - -def _serialize_tool_calls(tool_calls: List[Dict[str, Any]]) -> str: - chunks: List[str] = [] - for tc in tool_calls: - if tc.get("type") != "function": - continue - fn = tc.get("function", {}) - name = fn.get("name") - args_raw = fn.get("arguments") - args: Any - if isinstance(args_raw, str): - try: - args = json.loads(args_raw) - except json.JSONDecodeError: - args = {"_raw": args_raw} - else: - args = args_raw or {} - payload = {"tool": name, "args": args} - chunks.append(f"{TOOL_TAGS[0]}\n{json.dumps(payload)}\n{TOOL_TAGS[1]}") - return "\n".join(chunks) - - -def _build_conversation(messages: List[Dict[str, Any]]) -> Conversation: - convo = Conversation() - tool_name_by_id: Dict[str, str] = {} - for msg in messages: - if msg.get("role") == "assistant": - for tc in msg.get("tool_calls", []) or []: - tc_id = tc.get("id") - fn = (tc.get("function") or {}) - if tc_id and fn.get("name"): - tool_name_by_id[tc_id] = fn["name"] - - for msg in messages: - role = msg.get("role") - content = _flatten_content(msg.get("content")) - if role == "assistant" and msg.get("tool_calls"): - tool_blob = _serialize_tool_calls(msg.get("tool_calls") or []) - content = (content + "\n" + tool_blob).strip() - elif role == "tool": - tool_name = msg.get("name") or tool_name_by_id.get(msg.get("tool_call_id"), "") - content = f" \"tool\" : \"{tool_name}\" \"output\": \"{content}\" " - - if role == "system": - convo.messages.add(role=Message.ROLE_SYSTEM, content=content) - elif role == "user": - convo.messages.add(role=Message.ROLE_USER, content=content) - elif role == "assistant": - convo.messages.add(role=Message.ROLE_ASSISTANT, content=content) - elif role == "tool": - convo.messages.add(role=Message.ROLE_TOOL, content=content) - - return convo - - -def _safe_parse_cot(raw: str) -> Tuple[str, str]: - if THINK_TAGS[1] in raw: - pre, post = raw.split(THINK_TAGS[1], 1) - cot = pre.replace(THINK_TAGS[0], "").replace(THINK_TAGS[1], "").strip() - return cot, post - return "", raw - - -def _map_finish_reason(fr: Optional[int]) -> Optional[str]: - if fr is None: - return None - if fr == FinishReason.FINISH_STOP: - return "stop" - if fr == FinishReason.FINISH_LENGTH: - return "length" - if fr == FinishReason.FINISH_TOOLCALLS: - return "tool_calls" - return "stop" - - -def _usage_to_openai(usage: Any) -> Dict[str, int]: - tokens = getattr(usage, "tokens", None) - if tokens is None: - return {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0} - prompt = int(getattr(tokens, "prompt", 0)) - completion = int(getattr(tokens, "completion", 0)) - return { - "prompt_tokens": prompt, - "completion_tokens": completion, - "total_tokens": prompt + completion, - } - - -def _set_structural_tag(req: IRequest, structural_tag: Dict[str, Any]) -> bool: - try: - fmt = ResponseFormat.STRUCTURAL_TAG - except AttributeError: - # older proto or server; fall back to prompt-only constraints. - return False - req.cfg.response_format.format = fmt - req.cfg.response_format.schema = json.dumps(structural_tag, indent=0) - return True - - -class _StreamFilter: - """Streaming filter that separates visible content, reasoning, and toolcall tags. - - ``feed()`` and ``finalize()`` return a ``(visible, reasoning)`` tuple so - that callers can emit ``delta.reasoning_content`` alongside ``delta.content`` - in OpenAI-compatible SSE chunks (matching DeepSeek / OpenAI convention). - """ - - def __init__(self, hide_cot: bool = False) -> None: - self._buffer = "" - self._mode = "normal" # normal | think | toolcall - self._max_tag = max(len(""), len(""), len(""), len("")) - self._passed_cot = not hide_cot - self._cot_open_stripped = False # whether we've consumed the opening in phase 1 - - def finalize(self) -> Tuple[str, str]: - """Flush remaining buffer. Returns ``(visible, reasoning)``.""" - if not self._passed_cot: - # Stream ended mid-thinking (never saw ). - reasoning = self._buffer - self._buffer = "" - return "", reasoning - if self._mode == "think": - reasoning = self._buffer - self._buffer = "" - return "", reasoning - if self._mode == "toolcall": - self._buffer = "" - return "", "" - tail = self._buffer - self._buffer = "" - return tail, "" - - def feed(self, chunk: str) -> Tuple[str, str]: - """Process *chunk* and return ``(visible, reasoning)`` text.""" - if not chunk: - return "", "" - buf = self._buffer + chunk - reasoning_parts: List[str] = [] - - # Phase 1: skip initial CoT block for reasoner models, capturing it. - if not self._passed_cot: - # Strip opening tag once, before emitting any reasoning. - if not self._cot_open_stripped: - tag_pos = buf.find("") - if tag_pos != -1: - buf = buf[tag_pos + len(""):] - buf = buf.lstrip("\n") # drop leading newline after - self._cot_open_stripped = True - else: - # Haven't seen the full opening tag yet — could be split. - # Keep buffering without emitting anything as reasoning. - keep = len("") - 1 - self._buffer = buf[-keep:] if keep > 0 else "" - return "", "" - - end = buf.find("") - if end == -1: - keep = len("") - 1 - # Everything except the safety buffer is reasoning. - if len(buf) > keep: - text = buf[:-keep] if keep > 0 else buf - reasoning_parts.append(text) - self._buffer = buf[-keep:] if keep > 0 else "" - return "", "".join(reasoning_parts) - # Capture everything before as reasoning. - cot_text = buf[:end] - if cot_text: - reasoning_parts.append(cot_text) - buf = buf[end + len(""):] - self._passed_cot = True - - # Phase 2: state-machine pass over visible / think / toolcall segments. - out_parts: List[str] = [] - while buf: - if self._mode == "think": - end = buf.find("") - if end == -1: - keep = self._max_tag - 1 - if len(buf) > keep: - reasoning_parts.append(buf[:-keep]) - self._buffer = buf[-keep:] - return "".join(out_parts), "".join(reasoning_parts) - reasoning_parts.append(buf[:end]) - buf = buf[end + len(""):] - self._mode = "normal" - continue - - if self._mode == "toolcall": - end = buf.find("") - if end == -1: - self._buffer = buf[-(self._max_tag - 1):] - return "".join(out_parts), "".join(reasoning_parts) - buf = buf[end + len(""):] - self._mode = "normal" - continue - - next_think = buf.find("") - next_tool = buf.find("") - if next_think == -1 and next_tool == -1: - if len(buf) >= self._max_tag: - out_parts.append(buf[:-(self._max_tag - 1)]) - self._buffer = buf[-(self._max_tag - 1):] - else: - self._buffer = buf - return "".join(out_parts), "".join(reasoning_parts) - - if next_think == -1 or (next_tool != -1 and next_tool < next_think): - if next_tool > 0: - out_parts.append(buf[:next_tool]) - buf = buf[next_tool + len(""):] - self._mode = "toolcall" - continue - - if next_think > 0: - out_parts.append(buf[:next_think]) - buf = buf[next_think + len(""):] - self._mode = "think" - - self._buffer = "" - return "".join(out_parts), "".join(reasoning_parts) - - -class OpenAIProxy: - def __init__(self, grpc_address: str, include_debug: bool = False) -> None: - self.grpc_address = grpc_address - self.include_debug = include_debug - self.channel = grpc.insecure_channel(grpc_address) - self.stub = InferenceServiceStub(self.channel) - self.prompt_builder = AgentPromptBuilder() - - def build_request(self, payload: Dict[str, Any]) -> Tuple[IRequest, Model, bool, List[Tool], bool]: - model_name = payload.get("model") - model, is_reasoner = _resolve_model(self.stub, model_name) - - messages = list(payload.get("messages") or []) - tools_spec = list(payload.get("tools") or []) - tool_choice = payload.get("tool_choice") - tool_choice_name = None - if isinstance(tool_choice, dict): - fn = tool_choice.get("function") or {} - tool_choice_name = fn.get("name") - allow_tools = tool_choice != "none" - - tools = _build_tool_list(tools_spec) if allow_tools else [] - if tool_choice_name: - tools = [t for t in tools if t.name == tool_choice_name] - - if tools: - _apply_tool_prompt(messages, _tool_system_prompt(tools)) - - convo = _build_conversation(messages) - convo.model_uuid = model.uuid - - req = IRequest() - req.id = _gen_id("openai-proxy") - req.model_uuid = model.uuid - req.convo.CopyFrom(convo) - - if payload.get("max_tokens", 0) > 0: - req.cfg.max_tokens = int(payload["max_tokens"]) - else: - req.cfg.max_tokens = 16384 - if payload.get("temperature") is not None: - req.cfg.temp = float(payload["temperature"]) - if payload.get("top_p") is not None: - req.cfg.top_p = float(payload["top_p"]) - - response_format = payload.get("response_format") or {"type": "text"} - rf_type = response_format.get("type") if isinstance(response_format, dict) else "text" - - if tools: - if is_reasoner: - _build_tool_call_response_format(req, tools) - else: - _build_tool_call_response_format_non_reasoning(req, tools) - elif rf_type in {"json_schema", "json_object"}: - if rf_type == "json_schema": - schema = response_format.get("json_schema") - else: - schema = {"type": "object"} - if is_reasoner: - structural_tag = { - "type": "structural_tag", - "format": { - "type": "sequence", - "elements": [ - { - "type": "tag", - "begin": "", - "content": {"type": "any_text"}, - "end": THINK_TAGS[1], - }, - { - "type": "tag", - "begin": "", - "content": {"type": "json_schema", "json_schema": schema}, - "end": "", - }, - ], - }, - } - _set_structural_tag(req, structural_tag) - else: - req.cfg.response_format.format = ResponseFormat.JSON - req.cfg.response_format.schema = json.dumps(schema) - - stream = bool(payload.get("stream")) - return req, model, is_reasoner, tools, stream - - def run_sync(self, req: IRequest) -> Any: - return self.stub.GenerateSync(req) - - def run_stream(self, req: IRequest): - return self.stub.Generate(req) - - -class OpenAIProxyHandler(BaseHTTPRequestHandler): - server_version = "TruffleOpenAIProxy/0.1" - def _set_cors_headers(self) -> None: - self.send_header("Access-Control-Allow-Origin", "*") - self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization") - - def _send_json(self, status: int, payload: Dict[str, Any]) -> None: - data = json.dumps(payload).encode("utf-8") - self.send_response(status) - self._set_cors_headers() - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(data))) - self.end_headers() - self.wfile.write(data) - def do_OPTIONS(self) -> None: - self.send_response(204) - self._set_cors_headers() - self.end_headers() - - def _read_body(self) -> Dict[str, Any]: - length = int(self.headers.get("Content-Length", "0")) - if length <= 0: - return {} - raw = self.rfile.read(length) - return json.loads(raw.decode("utf-8")) - - def _send_sse(self, payload: Dict[str, Any]) -> bool: - data = json.dumps(payload) - try: - self.wfile.write(f"data: {data}\n\n".encode("utf-8")) - self.wfile.flush() - except (BrokenPipeError, ConnectionResetError, OSError): - # Client disconnected; stop streaming gracefully. - self.close_connection = True - return False - return True - - def do_GET(self) -> None: - if self.path == "/health": - self._send_json(200, {"status": "ok"}) - return - if self.path in {"/v1/models", "/models"}: - proxy: OpenAIProxy = self.server.proxy # type: ignore[attr-defined] - models = _get_models(proxy.stub) - data = [ - {"id": m.uuid, "object": "model", "owned_by": m.provider or "truffle", "name": m.name} - for m in models - ] - self._send_json(200, {"object": "list", "data": data}) - return - if self.path.startswith("/v1/models/"): - proxy: OpenAIProxy = self.server.proxy # type: ignore[attr-defined] - model_id = self.path.split("/v1/models/", 1)[1] - models = _get_models(proxy.stub) - model = next((m for m in models if m.uuid == model_id or m.name == model_id), None) - if model is None: - self._send_json(404, {"error": {"message": "model not found", "type": "not_found_error"}}) - return - self._send_json( - 200, - { - "id": model.uuid, - "object": "model", - "owned_by": model.provider or "truffle", - "name": model.name, - }, - ) - return - self.send_error(404, "Not Found") - - def do_POST(self) -> None: - if self.path != "/v1/chat/completions": - self.send_error(404, "Not Found") - return - try: - payload = self._read_body() - except Exception as e: - print(f"\tError reading request body: {e}") - self._send_json(400, {"error": {"message": str(e), "type": "invalid_request_error"}}) - return - - proxy: OpenAIProxy = self.server.proxy # type: ignore[attr-defined] - - try: - req, model, is_reasoner, _tools, stream = proxy.build_request(payload) - except Exception as e: - print(f"\tError building request: {e}") - self._send_json(400, {"error": {"message": str(e), "type": "invalid_request_error"}}) - return - - if stream: - self.send_response(200) - self._set_cors_headers() - self.send_header("Content-Type", "text/event-stream; charset=utf-8") - self.send_header("Cache-Control", "no-cache, no-transform") - self.send_header("Connection", "keep-alive") - self.send_header("X-Accel-Buffering", "no") - self.end_headers() - - stream_id = _gen_id("chatcmpl") - created = _now_ts() - if not self._send_sse( - { - "id": stream_id, - "object": "chat.completion.chunk", - "created": created, - "model": model.name, - "choices": [ - {"index": 0, "delta": {"role": "assistant"}, "finish_reason": None} - ], - } - ): - return - - raw_content = "" - last_finish = None - filter_state = _StreamFilter(hide_cot=is_reasoner) - log_output = os.getenv("TRUFFLE_PROXY_LOG_STREAM_OUTPUT", "0") == "1" - if log_output: - print("Streaming output:") - for ir in proxy.run_stream(req): - raw_content += ir.content - if log_output: - print(ir.content, end="", flush=True) - if ir.HasField("finish_reason") and ir.finish_reason != FinishReason.FINISH_UNSPECIFIED: - last_finish = ir.finish_reason - visible, reasoning = filter_state.feed(ir.content) - delta: Dict[str, Any] = {} - if visible: - delta["content"] = visible - if reasoning: - delta["reasoning_content"] = reasoning - if delta: - if not self._send_sse( - { - "id": stream_id, - "object": "chat.completion.chunk", - "created": created, - "model": model.name, - "choices": [ - { - "index": 0, - "delta": delta, - "finish_reason": None, - } - ], - } - ): - return - tail, tail_reasoning = filter_state.finalize() - tail_delta: Dict[str, Any] = {} - if tail: - tail_delta["content"] = tail - if tail_reasoning: - tail_delta["reasoning_content"] = tail_reasoning - if tail_delta: - if not self._send_sse( - { - "id": stream_id, - "object": "chat.completion.chunk", - "created": created, - "model": model.name, - "choices": [ - { - "index": 0, - "delta": tail_delta, - "finish_reason": None, - } - ], - } - ): - return - _cot, after_cot = _safe_parse_cot(raw_content) - tool_calls, _clean = proxy.prompt_builder.extract_tool_calls(after_cot) - if tool_calls: - tc_list = [] - for i, tc in enumerate(tool_calls): - name = tc.get("tool") or "" - args = json.dumps(tc.get("args") or {}, separators=(",", ":")) - tc_list.append( - { - "id": f"call_{i+1}", - "type": "function", - "index": i, - "function": {"name": name, "arguments": args}, - } - ) - if not self._send_sse( - { - "id": stream_id, - "object": "chat.completion.chunk", - "created": created, - "model": model.name, - "choices": [ - { - "index": 0, - "delta": {"tool_calls": tc_list}, - "finish_reason": None, - } - ], - } - ): - return - finish_reason = _map_finish_reason(last_finish) or "stop" - if not self._send_sse( - { - "id": stream_id, - "object": "chat.completion.chunk", - "created": created, - "model": model.name, - "choices": [ - {"index": 0, "delta": {}, "finish_reason": finish_reason} - ], - } - ): - return - try: - self.wfile.write(b"data: [DONE]\n\n") - self.wfile.flush() - except (BrokenPipeError, ConnectionResetError, OSError): - self.close_connection = True - else: - self.close_connection = True - return - - resp = proxy.run_sync(req) - raw = resp.content - cot, after_cot = _safe_parse_cot(raw) - tool_calls, clean = proxy.prompt_builder.extract_tool_calls(after_cot) - message = clean_response(clean) - - finish_reason = _map_finish_reason(resp.finish_reason if resp.HasField("finish_reason") else None) - openai_tool_calls = [] - for i, tc in enumerate(tool_calls): - name = tc.get("tool") or "" - args = json.dumps(tc.get("args") or {}, separators=(",", ":")) - openai_tool_calls.append( - { - "id": f"call_{i+1}", - "type": "function", - "function": {"name": name, "arguments": args}, - } - ) - - msg: Dict[str, Any] = {"role": "assistant", "content": message} - if cot: - msg["reasoning_content"] = cot - if openai_tool_calls: - msg["tool_calls"] = openai_tool_calls - if not message: - msg["content"] = None - - response = { - "id": _gen_id("chatcmpl"), - "object": "chat.completion", - "created": _now_ts(), - "model": model.name, - "choices": [ - {"index": 0, "message": msg, "finish_reason": finish_reason} - ], - "usage": _usage_to_openai(resp.usage if resp.HasField("usage") else None), - } - - debug_req = bool(payload.get("debug") or payload.get("debug_reasoning")) - if proxy.include_debug or debug_req: - response["debug"] = {"reasoning": cot} - - self._send_json(200, response) - -def normalize_grpc_address(address: str, default_port : int = 80) -> str: - import socket - if '.local' in address: - try: - ip = socket.gethostbyname(address) - address = ip - except socket.gaierror as e: - raise RuntimeError(f"Failed to resolve mDNS address {address}: {e}") - if ':' not in address: - address += f":{default_port}" - return address - -def main() -> None: - parser = argparse.ArgumentParser(description="OpenAI-compatible proxy for Truffle gRPC inference") - parser.add_argument("--truffle", default="truffle-1234", help="truffle id: e.g. truffle-1234") - parser.add_argument("--host", default="127.0.0.1", help="HTTP host") - parser.add_argument("--port", type=int, default=8080, help="HTTP port") - parser.add_argument("--debug", action="store_true", help="Include debug.reasoning in responses") - args = parser.parse_args() - print(f"Connecting to {args.truffle}") - grpc_address = normalize_grpc_address(f"{args.truffle}.local", default_port=80) - print(f"Found {args.truffle} at {grpc_address}") - proxy = OpenAIProxy(grpc_address, include_debug=args.debug) - - class _Server(ThreadingHTTPServer): - def __init__(self, server_address, handler_cls): - super().__init__(server_address, handler_cls) - self.proxy = proxy - - server = _Server((args.host, args.port), OpenAIProxyHandler) - print(f"OpenAI proxy listening on http://{args.host}:{args.port} -> Truffle @ {grpc_address}") - server.serve_forever() - - -if __name__ == "__main__": - main() diff --git a/truffile/infer/tooling.py b/truffile/infer/tooling.py deleted file mode 100644 index 8d13889..0000000 --- a/truffile/infer/tooling.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import Dict -import json - - -@dataclass -class Tool: - name: str - description: str - input_schema: Dict - display_name: str - - def schema_str(self, indent: int = 2) -> str: - return json.dumps(self.input_schema, indent=indent) - - def get_for_system_prompt(self) -> str: - return f"{self.name}: {self.description}\nArg Schema: {self.schema_str(indent=2)}" diff --git a/truffle/infer/__init__.py b/truffle/infer/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/truffle/infer/convo/__init__.py b/truffle/infer/convo/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/truffle/infer/convo/conversation_pb2.py b/truffle/infer/convo/conversation_pb2.py deleted file mode 100644 index bb3ec89..0000000 --- a/truffle/infer/convo/conversation_pb2.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/convo/conversation.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/convo/conversation.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from truffle.infer.convo import msg_pb2 as truffle_dot_infer_dot_convo_dot_msg__pb2 - -from truffle.infer.convo.msg_pb2 import * - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&truffle/infer/convo/conversation.proto\x12\x13truffle.infer.convo\x1a\x1dtruffle/infer/convo/msg.proto\"R\n\x0c\x43onversation\x12.\n\x08messages\x18\x01 \x03(\x0b\x32\x1c.truffle.infer.convo.Message\x12\x12\n\nmodel_uuid\x18\x03 \x01(\t\"3\n\x0c\x42uiltContext\x12\x0f\n\x07\x63ontext\x18\x01 \x01(\t\x12\x12\n\nmodel_uuid\x18\x02 \x01(\tP\x00\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.convo.conversation_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_CONVERSATION']._serialized_start=94 - _globals['_CONVERSATION']._serialized_end=176 - _globals['_BUILTCONTEXT']._serialized_start=178 - _globals['_BUILTCONTEXT']._serialized_end=229 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/convo/conversation_pb2.pyi b/truffle/infer/convo/conversation_pb2.pyi deleted file mode 100644 index 8366e78..0000000 --- a/truffle/infer/convo/conversation_pb2.pyi +++ /dev/null @@ -1,25 +0,0 @@ -from truffle.infer.convo import msg_pb2 as _msg_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union -from truffle.infer.convo.msg_pb2 import Message as Message - -DESCRIPTOR: _descriptor.FileDescriptor - -class Conversation(_message.Message): - __slots__ = ("messages", "model_uuid") - MESSAGES_FIELD_NUMBER: _ClassVar[int] - MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - messages: _containers.RepeatedCompositeFieldContainer[_msg_pb2.Message] - model_uuid: str - def __init__(self, messages: _Optional[_Iterable[_Union[_msg_pb2.Message, _Mapping]]] = ..., model_uuid: _Optional[str] = ...) -> None: ... - -class BuiltContext(_message.Message): - __slots__ = ("context", "model_uuid") - CONTEXT_FIELD_NUMBER: _ClassVar[int] - MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - context: str - model_uuid: str - def __init__(self, context: _Optional[str] = ..., model_uuid: _Optional[str] = ...) -> None: ... diff --git a/truffle/infer/convo/conversation_pb2_grpc.py b/truffle/infer/convo/conversation_pb2_grpc.py deleted file mode 100644 index 4195d2c..0000000 --- a/truffle/infer/convo/conversation_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/convo/conversation_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/convo/msg_pb2.py b/truffle/infer/convo/msg_pb2.py deleted file mode 100644 index 9726e77..0000000 --- a/truffle/infer/convo/msg_pb2.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/convo/msg.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/convo/msg.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dtruffle/infer/convo/msg.proto\x12\x13truffle.infer.convo\"\xa8\x01\n\x07Message\x12/\n\x04role\x18\x01 \x01(\x0e\x32!.truffle.infer.convo.Message.Role\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\"[\n\x04Role\x12\x10\n\x0cROLE_INVALID\x10\x00\x12\x0f\n\x0bROLE_SYSTEM\x10\x01\x12\r\n\tROLE_USER\x10\x02\x12\x12\n\x0eROLE_ASSISTANT\x10\x03\x12\r\n\tROLE_TOOL\x10\x04\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.convo.msg_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_MESSAGE']._serialized_start=55 - _globals['_MESSAGE']._serialized_end=223 - _globals['_MESSAGE_ROLE']._serialized_start=132 - _globals['_MESSAGE_ROLE']._serialized_end=223 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/convo/msg_pb2.pyi b/truffle/infer/convo/msg_pb2.pyi deleted file mode 100644 index 1594747..0000000 --- a/truffle/infer/convo/msg_pb2.pyi +++ /dev/null @@ -1,26 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class Message(_message.Message): - __slots__ = ("role", "content") - class Role(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - ROLE_INVALID: _ClassVar[Message.Role] - ROLE_SYSTEM: _ClassVar[Message.Role] - ROLE_USER: _ClassVar[Message.Role] - ROLE_ASSISTANT: _ClassVar[Message.Role] - ROLE_TOOL: _ClassVar[Message.Role] - ROLE_INVALID: Message.Role - ROLE_SYSTEM: Message.Role - ROLE_USER: Message.Role - ROLE_ASSISTANT: Message.Role - ROLE_TOOL: Message.Role - ROLE_FIELD_NUMBER: _ClassVar[int] - CONTENT_FIELD_NUMBER: _ClassVar[int] - role: Message.Role - content: str - def __init__(self, role: _Optional[_Union[Message.Role, str]] = ..., content: _Optional[str] = ...) -> None: ... diff --git a/truffle/infer/convo/msg_pb2_grpc.py b/truffle/infer/convo/msg_pb2_grpc.py deleted file mode 100644 index d719b1c..0000000 --- a/truffle/infer/convo/msg_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/convo/msg_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/embedding_pb2.py b/truffle/infer/embedding_pb2.py deleted file mode 100644 index 20c805c..0000000 --- a/truffle/infer/embedding_pb2.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/embedding.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/embedding.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dtruffle/infer/embedding.proto\x12\rtruffle.infer\")\n\nEmbeddable\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\r\n\x05query\x18\x02 \x01(\x08\"V\n\x10\x45mbeddingRequest\x12.\n\x0b\x65mbeddables\x18\x01 \x03(\x0b\x32\x19.truffle.infer.Embeddable\x12\x12\n\nmodel_uuid\x18\x02 \x01(\t\"H\n\x11\x45mbeddingResponse\x12\x12\n\nembeddings\x18\x01 \x03(\x0c\x12\x0b\n\x03\x64im\x18\x02 \x01(\x04\x12\x12\n\ndtype_size\x18\x03 \x01(\x04\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.embedding_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_EMBEDDABLE']._serialized_start=48 - _globals['_EMBEDDABLE']._serialized_end=89 - _globals['_EMBEDDINGREQUEST']._serialized_start=91 - _globals['_EMBEDDINGREQUEST']._serialized_end=177 - _globals['_EMBEDDINGRESPONSE']._serialized_start=179 - _globals['_EMBEDDINGRESPONSE']._serialized_end=251 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/embedding_pb2.pyi b/truffle/infer/embedding_pb2.pyi deleted file mode 100644 index 6608a08..0000000 --- a/truffle/infer/embedding_pb2.pyi +++ /dev/null @@ -1,33 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class Embeddable(_message.Message): - __slots__ = ("text", "query") - TEXT_FIELD_NUMBER: _ClassVar[int] - QUERY_FIELD_NUMBER: _ClassVar[int] - text: str - query: bool - def __init__(self, text: _Optional[str] = ..., query: bool = ...) -> None: ... - -class EmbeddingRequest(_message.Message): - __slots__ = ("embeddables", "model_uuid") - EMBEDDABLES_FIELD_NUMBER: _ClassVar[int] - MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - embeddables: _containers.RepeatedCompositeFieldContainer[Embeddable] - model_uuid: str - def __init__(self, embeddables: _Optional[_Iterable[_Union[Embeddable, _Mapping]]] = ..., model_uuid: _Optional[str] = ...) -> None: ... - -class EmbeddingResponse(_message.Message): - __slots__ = ("embeddings", "dim", "dtype_size") - EMBEDDINGS_FIELD_NUMBER: _ClassVar[int] - DIM_FIELD_NUMBER: _ClassVar[int] - DTYPE_SIZE_FIELD_NUMBER: _ClassVar[int] - embeddings: _containers.RepeatedScalarFieldContainer[bytes] - dim: int - dtype_size: int - def __init__(self, embeddings: _Optional[_Iterable[bytes]] = ..., dim: _Optional[int] = ..., dtype_size: _Optional[int] = ...) -> None: ... diff --git a/truffle/infer/embedding_pb2_grpc.py b/truffle/infer/embedding_pb2_grpc.py deleted file mode 100644 index 36a3d32..0000000 --- a/truffle/infer/embedding_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/embedding_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/finishreason_pb2.py b/truffle/infer/finishreason_pb2.py deleted file mode 100644 index e6dfe75..0000000 --- a/truffle/infer/finishreason_pb2.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/finishreason.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/finishreason.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n truffle/infer/finishreason.proto\x12\rtruffle.infer*\xab\x01\n\x0c\x46inishReason\x12\x16\n\x12\x46INISH_UNSPECIFIED\x10\x00\x12\x0f\n\x0b\x46INISH_STOP\x10\x01\x12\x11\n\rFINISH_LENGTH\x10\x02\x12\x14\n\x10\x46INISH_TOOLCALLS\x10\x03\x12\x10\n\x0c\x46INISH_ERROR\x10\x04\x12\x10\n\x0c\x46INISH_ABORT\x10\x05\x12\x12\n\x0e\x46INISH_UNKNOWN\x10\x06\x12\x11\n\rFINISH_GOAWAY\x10\x07\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.finishreason_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_FINISHREASON']._serialized_start=52 - _globals['_FINISHREASON']._serialized_end=223 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/finishreason_pb2.pyi b/truffle/infer/finishreason_pb2.pyi deleted file mode 100644 index c244ecd..0000000 --- a/truffle/infer/finishreason_pb2.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from typing import ClassVar as _ClassVar - -DESCRIPTOR: _descriptor.FileDescriptor - -class FinishReason(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - FINISH_UNSPECIFIED: _ClassVar[FinishReason] - FINISH_STOP: _ClassVar[FinishReason] - FINISH_LENGTH: _ClassVar[FinishReason] - FINISH_TOOLCALLS: _ClassVar[FinishReason] - FINISH_ERROR: _ClassVar[FinishReason] - FINISH_ABORT: _ClassVar[FinishReason] - FINISH_UNKNOWN: _ClassVar[FinishReason] - FINISH_GOAWAY: _ClassVar[FinishReason] -FINISH_UNSPECIFIED: FinishReason -FINISH_STOP: FinishReason -FINISH_LENGTH: FinishReason -FINISH_TOOLCALLS: FinishReason -FINISH_ERROR: FinishReason -FINISH_ABORT: FinishReason -FINISH_UNKNOWN: FinishReason -FINISH_GOAWAY: FinishReason diff --git a/truffle/infer/finishreason_pb2_grpc.py b/truffle/infer/finishreason_pb2_grpc.py deleted file mode 100644 index 6733980..0000000 --- a/truffle/infer/finishreason_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/finishreason_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/gencfg_pb2.py b/truffle/infer/gencfg_pb2.py deleted file mode 100644 index 83c3156..0000000 --- a/truffle/infer/gencfg_pb2.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/gencfg.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/gencfg.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1atruffle/infer/gencfg.proto\x12\rtruffle.infer\"\xcf\x02\n\x0eResponseFormat\x12\x34\n\x06\x66ormat\x18\x01 \x01(\x0e\x32$.truffle.infer.ResponseFormat.Format\x12\x13\n\x06schema\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x45\n\x0c\x65xperimental\x18\x03 \x01(\x0b\x32*.truffle.infer.ResponseFormat.ExperimentalH\x01\x88\x01\x01\x1aS\n\x0c\x45xperimental\x12\x17\n\x0f\x61\x64\x64itional_ebnf\x18\x01 \x01(\t\x12\x14\n\x0cold_root_key\x18\x02 \x01(\t\x12\x14\n\x0cnew_root_key\x18\x03 \x01(\t\":\n\x06\x46ormat\x12\x08\n\x04TEXT\x10\x00\x12\x08\n\x04JSON\x10\x01\x12\x08\n\x04\x45\x42NF\x10\x02\x12\x12\n\x0eSTRUCTURAL_TAG\x10\x03\x42\t\n\x07_schemaB\x0f\n\r_experimental\"\xda\x03\n\x10GenerationConfig\x12\x11\n\x04temp\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x12\n\x05top_p\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12\x19\n\x0c\x66req_penalty\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12\x19\n\x0cpres_penalty\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x18\n\x0brep_penalty\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x11\n\x04seed\x18\x06 \x01(\x05H\x05\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x07 \x01(\rH\x06\x88\x01\x01\x12\x11\n\tstop_strs\x18\x08 \x03(\t\x12\x10\n\x08stop_ids\x18\t \x03(\r\x12\x36\n\x0fresponse_format\x18\n \x01(\x0b\x32\x1d.truffle.infer.ResponseFormat\x12\x34\n\x05\x64\x65\x62ug\x18\x0b \x01(\x0b\x32%.truffle.infer.GenerationConfig.Debug\x1a\x33\n\x05\x44\x65\x62ug\x12\x12\n\nignore_eos\x18\x01 \x01(\x08\x12\x16\n\x0epinned_context\x18\x02 \x01(\x08\x42\x07\n\x05_tempB\x08\n\x06_top_pB\x0f\n\r_freq_penaltyB\x0f\n\r_pres_penaltyB\x0e\n\x0c_rep_penaltyB\x07\n\x05_seedB\r\n\x0b_max_tokens\"Y\n\x15ValidateConfigRequest\x12,\n\x03\x63\x66g\x18\x01 \x01(\x0b\x32\x1f.truffle.infer.GenerationConfig\x12\x12\n\nmodel_uuid\x18\x02 \x01(\t\"H\n\x16ValidateConfigResponse\x12\r\n\x05valid\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x10\n\x08warnings\x18\x03 \x03(\tb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.gencfg_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_RESPONSEFORMAT']._serialized_start=46 - _globals['_RESPONSEFORMAT']._serialized_end=381 - _globals['_RESPONSEFORMAT_EXPERIMENTAL']._serialized_start=210 - _globals['_RESPONSEFORMAT_EXPERIMENTAL']._serialized_end=293 - _globals['_RESPONSEFORMAT_FORMAT']._serialized_start=295 - _globals['_RESPONSEFORMAT_FORMAT']._serialized_end=353 - _globals['_GENERATIONCONFIG']._serialized_start=384 - _globals['_GENERATIONCONFIG']._serialized_end=858 - _globals['_GENERATIONCONFIG_DEBUG']._serialized_start=714 - _globals['_GENERATIONCONFIG_DEBUG']._serialized_end=765 - _globals['_VALIDATECONFIGREQUEST']._serialized_start=860 - _globals['_VALIDATECONFIGREQUEST']._serialized_end=949 - _globals['_VALIDATECONFIGRESPONSE']._serialized_start=951 - _globals['_VALIDATECONFIGRESPONSE']._serialized_end=1023 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/gencfg_pb2.pyi b/truffle/infer/gencfg_pb2.pyi deleted file mode 100644 index a2a6830..0000000 --- a/truffle/infer/gencfg_pb2.pyi +++ /dev/null @@ -1,88 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class ResponseFormat(_message.Message): - __slots__ = ("format", "schema", "experimental") - class Format(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - TEXT: _ClassVar[ResponseFormat.Format] - JSON: _ClassVar[ResponseFormat.Format] - EBNF: _ClassVar[ResponseFormat.Format] - STRUCTURAL_TAG: _ClassVar[ResponseFormat.Format] - TEXT: ResponseFormat.Format - JSON: ResponseFormat.Format - EBNF: ResponseFormat.Format - STRUCTURAL_TAG: ResponseFormat.Format - class Experimental(_message.Message): - __slots__ = ("additional_ebnf", "old_root_key", "new_root_key") - ADDITIONAL_EBNF_FIELD_NUMBER: _ClassVar[int] - OLD_ROOT_KEY_FIELD_NUMBER: _ClassVar[int] - NEW_ROOT_KEY_FIELD_NUMBER: _ClassVar[int] - additional_ebnf: str - old_root_key: str - new_root_key: str - def __init__(self, additional_ebnf: _Optional[str] = ..., old_root_key: _Optional[str] = ..., new_root_key: _Optional[str] = ...) -> None: ... - FORMAT_FIELD_NUMBER: _ClassVar[int] - SCHEMA_FIELD_NUMBER: _ClassVar[int] - EXPERIMENTAL_FIELD_NUMBER: _ClassVar[int] - format: ResponseFormat.Format - schema: str - experimental: ResponseFormat.Experimental - def __init__(self, format: _Optional[_Union[ResponseFormat.Format, str]] = ..., schema: _Optional[str] = ..., experimental: _Optional[_Union[ResponseFormat.Experimental, _Mapping]] = ...) -> None: ... - -class GenerationConfig(_message.Message): - __slots__ = ("temp", "top_p", "freq_penalty", "pres_penalty", "rep_penalty", "seed", "max_tokens", "stop_strs", "stop_ids", "response_format", "debug") - class Debug(_message.Message): - __slots__ = ("ignore_eos", "pinned_context") - IGNORE_EOS_FIELD_NUMBER: _ClassVar[int] - PINNED_CONTEXT_FIELD_NUMBER: _ClassVar[int] - ignore_eos: bool - pinned_context: bool - def __init__(self, ignore_eos: bool = ..., pinned_context: bool = ...) -> None: ... - TEMP_FIELD_NUMBER: _ClassVar[int] - TOP_P_FIELD_NUMBER: _ClassVar[int] - FREQ_PENALTY_FIELD_NUMBER: _ClassVar[int] - PRES_PENALTY_FIELD_NUMBER: _ClassVar[int] - REP_PENALTY_FIELD_NUMBER: _ClassVar[int] - SEED_FIELD_NUMBER: _ClassVar[int] - MAX_TOKENS_FIELD_NUMBER: _ClassVar[int] - STOP_STRS_FIELD_NUMBER: _ClassVar[int] - STOP_IDS_FIELD_NUMBER: _ClassVar[int] - RESPONSE_FORMAT_FIELD_NUMBER: _ClassVar[int] - DEBUG_FIELD_NUMBER: _ClassVar[int] - temp: float - top_p: float - freq_penalty: float - pres_penalty: float - rep_penalty: float - seed: int - max_tokens: int - stop_strs: _containers.RepeatedScalarFieldContainer[str] - stop_ids: _containers.RepeatedScalarFieldContainer[int] - response_format: ResponseFormat - debug: GenerationConfig.Debug - def __init__(self, temp: _Optional[float] = ..., top_p: _Optional[float] = ..., freq_penalty: _Optional[float] = ..., pres_penalty: _Optional[float] = ..., rep_penalty: _Optional[float] = ..., seed: _Optional[int] = ..., max_tokens: _Optional[int] = ..., stop_strs: _Optional[_Iterable[str]] = ..., stop_ids: _Optional[_Iterable[int]] = ..., response_format: _Optional[_Union[ResponseFormat, _Mapping]] = ..., debug: _Optional[_Union[GenerationConfig.Debug, _Mapping]] = ...) -> None: ... - -class ValidateConfigRequest(_message.Message): - __slots__ = ("cfg", "model_uuid") - CFG_FIELD_NUMBER: _ClassVar[int] - MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - cfg: GenerationConfig - model_uuid: str - def __init__(self, cfg: _Optional[_Union[GenerationConfig, _Mapping]] = ..., model_uuid: _Optional[str] = ...) -> None: ... - -class ValidateConfigResponse(_message.Message): - __slots__ = ("valid", "error", "warnings") - VALID_FIELD_NUMBER: _ClassVar[int] - ERROR_FIELD_NUMBER: _ClassVar[int] - WARNINGS_FIELD_NUMBER: _ClassVar[int] - valid: bool - error: str - warnings: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, valid: bool = ..., error: _Optional[str] = ..., warnings: _Optional[_Iterable[str]] = ...) -> None: ... diff --git a/truffle/infer/gencfg_pb2_grpc.py b/truffle/infer/gencfg_pb2_grpc.py deleted file mode 100644 index f4b8609..0000000 --- a/truffle/infer/gencfg_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/gencfg_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/infer_pb2.py b/truffle/infer/infer_pb2.py deleted file mode 100644 index bcef3df..0000000 --- a/truffle/infer/infer_pb2.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/infer.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/infer.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from truffle.infer import irequest_pb2 as truffle_dot_infer_dot_irequest__pb2 -from truffle.infer import iresponse_pb2 as truffle_dot_infer_dot_iresponse__pb2 -from truffle.infer import model_pb2 as truffle_dot_infer_dot_model__pb2 -from truffle.infer import embedding_pb2 as truffle_dot_infer_dot_embedding__pb2 -from truffle.infer.convo import conversation_pb2 as truffle_dot_infer_dot_convo_dot_conversation__pb2 -try: - truffle_dot_infer_dot_convo_dot_msg__pb2 = truffle_dot_infer_dot_convo_dot_conversation__pb2.truffle_dot_infer_dot_convo_dot_msg__pb2 -except AttributeError: - truffle_dot_infer_dot_convo_dot_msg__pb2 = truffle_dot_infer_dot_convo_dot_conversation__pb2.truffle.infer.convo.msg_pb2 -from truffle.infer import tokenize_pb2 as truffle_dot_infer_dot_tokenize__pb2 -from truffle.infer import gencfg_pb2 as truffle_dot_infer_dot_gencfg__pb2 - -from truffle.infer.irequest_pb2 import * -from truffle.infer.iresponse_pb2 import * -from truffle.infer.model_pb2 import * -from truffle.infer.embedding_pb2 import * -from truffle.infer.convo.conversation_pb2 import * -from truffle.infer.tokenize_pb2 import * -from truffle.infer.gencfg_pb2 import * - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19truffle/infer/infer.proto\x12\rtruffle.infer\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1ctruffle/infer/irequest.proto\x1a\x1dtruffle/infer/iresponse.proto\x1a\x19truffle/infer/model.proto\x1a\x1dtruffle/infer/embedding.proto\x1a&truffle/infer/convo/conversation.proto\x1a\x1ctruffle/infer/tokenize.proto\x1a\x1atruffle/infer/gencfg.proto2\xfb\x08\n\x10InferenceService\x12?\n\x08Generate\x12\x17.truffle.infer.IRequest\x1a\x18.truffle.infer.IResponse0\x01\x12\x41\n\x0cGenerateSync\x12\x17.truffle.infer.IRequest\x1a\x18.truffle.infer.IResponse\x12L\n\rGenerateBatch\x12\x1c.truffle.infer.BatchIRequest\x1a\x1d.truffle.infer.BatchIResponse\x12J\n\x05\x45mbed\x12\x1f.truffle.infer.EmbeddingRequest\x1a .truffle.infer.EmbeddingResponse\x12Q\n\x0c\x45mbedQueries\x12\x1f.truffle.infer.EmbeddingRequest\x1a .truffle.infer.EmbeddingResponse\x12L\n\x0cGetModelList\x12\".truffle.infer.GetModelListRequest\x1a\x18.truffle.infer.ModelList\x12@\n\x08GetModel\x12\x1e.truffle.infer.GetModelRequest\x1a\x14.truffle.infer.Model\x12N\n\tSetModels\x12\x1f.truffle.infer.SetModelsRequest\x1a .truffle.infer.SetModelsResponse\x12P\n\rGetModelState\x12\x1e.truffle.infer.GetModelRequest\x1a\x1f.truffle.infer.ModelStateUpdate\x12W\n\x12OnModelStateChange\x12\x1e.truffle.infer.GetModelRequest\x1a\x1f.truffle.infer.ModelStateUpdate0\x01\x12R\n\x15GetEmbeddingModelList\x12\x16.google.protobuf.Empty\x1a!.truffle.infer.EmbeddingModelList\x12Z\n\x15GetEmbeddingModelInfo\x12\x1e.truffle.infer.GetModelRequest\x1a!.truffle.infer.EmbeddingModelInfo\x12R\n\nBuildConvo\x12!.truffle.infer.convo.Conversation\x1a!.truffle.infer.convo.BuiltContext\x12g\n\x18ValidateGenerationConfig\x12$.truffle.infer.ValidateConfigRequest\x1a%.truffle.infer.ValidateConfigResponseP\x01P\x02P\x03P\x04P\x05P\x06P\x07\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.infer_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_INFERENCESERVICE']._serialized_start=291 - _globals['_INFERENCESERVICE']._serialized_end=1438 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/infer_pb2.pyi b/truffle/infer/infer_pb2.pyi deleted file mode 100644 index 27a1470..0000000 --- a/truffle/infer/infer_pb2.pyi +++ /dev/null @@ -1,43 +0,0 @@ -from google.protobuf import empty_pb2 as _empty_pb2 -from truffle.infer import irequest_pb2 as _irequest_pb2 -from truffle.infer import iresponse_pb2 as _iresponse_pb2 -from truffle.infer import model_pb2 as _model_pb2 -from truffle.infer import embedding_pb2 as _embedding_pb2 -from truffle.infer.convo import conversation_pb2 as _conversation_pb2 -from truffle.infer.convo import msg_pb2 as _msg_pb2 -from truffle.infer import tokenize_pb2 as _tokenize_pb2 -from truffle.infer import gencfg_pb2 as _gencfg_pb2 -from google.protobuf import descriptor as _descriptor -from typing import ClassVar as _ClassVar -from truffle.infer.irequest_pb2 import IRequest as IRequest -from truffle.infer.irequest_pb2 import BatchIRequest as BatchIRequest -from truffle.infer.irequest_pb2 import RequestPriority as RequestPriority -from truffle.infer.iresponse_pb2 import IResponse as IResponse -from truffle.infer.iresponse_pb2 import BatchIResponse as BatchIResponse -from truffle.infer.model_pb2 import EmbeddingModelInfo as EmbeddingModelInfo -from truffle.infer.model_pb2 import EmbeddingModelList as EmbeddingModelList -from truffle.infer.model_pb2 import ModelConfig as ModelConfig -from truffle.infer.model_pb2 import Model as Model -from truffle.infer.model_pb2 import ModelStateUpdate as ModelStateUpdate -from truffle.infer.model_pb2 import ModelList as ModelList -from truffle.infer.model_pb2 import GetModelRequest as GetModelRequest -from truffle.infer.model_pb2 import GetModelListRequest as GetModelListRequest -from truffle.infer.model_pb2 import SetModelsResponse as SetModelsResponse -from truffle.infer.model_pb2 import SetModelsRequest as SetModelsRequest -from truffle.infer.embedding_pb2 import Embeddable as Embeddable -from truffle.infer.embedding_pb2 import EmbeddingRequest as EmbeddingRequest -from truffle.infer.embedding_pb2 import EmbeddingResponse as EmbeddingResponse -from truffle.infer.convo.conversation_pb2 import Conversation as Conversation -from truffle.infer.convo.conversation_pb2 import BuiltContext as BuiltContext -from truffle.infer.tokenize_pb2 import TokenizeRequest as TokenizeRequest -from truffle.infer.tokenize_pb2 import TokenizeResponse as TokenizeResponse -from truffle.infer.gencfg_pb2 import ResponseFormat as ResponseFormat -from truffle.infer.gencfg_pb2 import GenerationConfig as GenerationConfig -from truffle.infer.gencfg_pb2 import ValidateConfigRequest as ValidateConfigRequest -from truffle.infer.gencfg_pb2 import ValidateConfigResponse as ValidateConfigResponse - -DESCRIPTOR: _descriptor.FileDescriptor -REQUEST_PRIORITY_UNSPECIFIED: _irequest_pb2.RequestPriority -REQUEST_PRIORITY_LOW: _irequest_pb2.RequestPriority -REQUEST_PRIORITY_NORMAL: _irequest_pb2.RequestPriority -REQUEST_PRIORITY_REALTIME: _irequest_pb2.RequestPriority diff --git a/truffle/infer/infer_pb2_grpc.py b/truffle/infer/infer_pb2_grpc.py deleted file mode 100644 index 290012e..0000000 --- a/truffle/infer/infer_pb2_grpc.py +++ /dev/null @@ -1,668 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from truffle.infer.convo import conversation_pb2 as truffle_dot_infer_dot_convo_dot_conversation__pb2 -from truffle.infer import embedding_pb2 as truffle_dot_infer_dot_embedding__pb2 -from truffle.infer import gencfg_pb2 as truffle_dot_infer_dot_gencfg__pb2 -from truffle.infer import irequest_pb2 as truffle_dot_infer_dot_irequest__pb2 -from truffle.infer import iresponse_pb2 as truffle_dot_infer_dot_iresponse__pb2 -from truffle.infer import model_pb2 as truffle_dot_infer_dot_model__pb2 - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/infer_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) - - -class InferenceServiceStub(object): - """ - legacy inference service apis (v1) - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.Generate = channel.unary_stream( - '/truffle.infer.InferenceService/Generate', - request_serializer=truffle_dot_infer_dot_irequest__pb2.IRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_iresponse__pb2.IResponse.FromString, - _registered_method=True) - self.GenerateSync = channel.unary_unary( - '/truffle.infer.InferenceService/GenerateSync', - request_serializer=truffle_dot_infer_dot_irequest__pb2.IRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_iresponse__pb2.IResponse.FromString, - _registered_method=True) - self.GenerateBatch = channel.unary_unary( - '/truffle.infer.InferenceService/GenerateBatch', - request_serializer=truffle_dot_infer_dot_irequest__pb2.BatchIRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_iresponse__pb2.BatchIResponse.FromString, - _registered_method=True) - self.Embed = channel.unary_unary( - '/truffle.infer.InferenceService/Embed', - request_serializer=truffle_dot_infer_dot_embedding__pb2.EmbeddingRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_embedding__pb2.EmbeddingResponse.FromString, - _registered_method=True) - self.EmbedQueries = channel.unary_unary( - '/truffle.infer.InferenceService/EmbedQueries', - request_serializer=truffle_dot_infer_dot_embedding__pb2.EmbeddingRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_embedding__pb2.EmbeddingResponse.FromString, - _registered_method=True) - self.GetModelList = channel.unary_unary( - '/truffle.infer.InferenceService/GetModelList', - request_serializer=truffle_dot_infer_dot_model__pb2.GetModelListRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_model__pb2.ModelList.FromString, - _registered_method=True) - self.GetModel = channel.unary_unary( - '/truffle.infer.InferenceService/GetModel', - request_serializer=truffle_dot_infer_dot_model__pb2.GetModelRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_model__pb2.Model.FromString, - _registered_method=True) - self.SetModels = channel.unary_unary( - '/truffle.infer.InferenceService/SetModels', - request_serializer=truffle_dot_infer_dot_model__pb2.SetModelsRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_model__pb2.SetModelsResponse.FromString, - _registered_method=True) - self.GetModelState = channel.unary_unary( - '/truffle.infer.InferenceService/GetModelState', - request_serializer=truffle_dot_infer_dot_model__pb2.GetModelRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_model__pb2.ModelStateUpdate.FromString, - _registered_method=True) - self.OnModelStateChange = channel.unary_stream( - '/truffle.infer.InferenceService/OnModelStateChange', - request_serializer=truffle_dot_infer_dot_model__pb2.GetModelRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_model__pb2.ModelStateUpdate.FromString, - _registered_method=True) - self.GetEmbeddingModelList = channel.unary_unary( - '/truffle.infer.InferenceService/GetEmbeddingModelList', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=truffle_dot_infer_dot_model__pb2.EmbeddingModelList.FromString, - _registered_method=True) - self.GetEmbeddingModelInfo = channel.unary_unary( - '/truffle.infer.InferenceService/GetEmbeddingModelInfo', - request_serializer=truffle_dot_infer_dot_model__pb2.GetModelRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_model__pb2.EmbeddingModelInfo.FromString, - _registered_method=True) - self.BuildConvo = channel.unary_unary( - '/truffle.infer.InferenceService/BuildConvo', - request_serializer=truffle_dot_infer_dot_convo_dot_conversation__pb2.Conversation.SerializeToString, - response_deserializer=truffle_dot_infer_dot_convo_dot_conversation__pb2.BuiltContext.FromString, - _registered_method=True) - self.ValidateGenerationConfig = channel.unary_unary( - '/truffle.infer.InferenceService/ValidateGenerationConfig', - request_serializer=truffle_dot_infer_dot_gencfg__pb2.ValidateConfigRequest.SerializeToString, - response_deserializer=truffle_dot_infer_dot_gencfg__pb2.ValidateConfigResponse.FromString, - _registered_method=True) - - -class InferenceServiceServicer(object): - """ - legacy inference service apis (v1) - """ - - def Generate(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GenerateSync(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GenerateBatch(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Embed(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def EmbedQueries(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetModelList(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetModel(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetModels(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetModelState(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def OnModelStateChange(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetEmbeddingModelList(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetEmbeddingModelInfo(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def BuildConvo(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ValidateGenerationConfig(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_InferenceServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Generate': grpc.unary_stream_rpc_method_handler( - servicer.Generate, - request_deserializer=truffle_dot_infer_dot_irequest__pb2.IRequest.FromString, - response_serializer=truffle_dot_infer_dot_iresponse__pb2.IResponse.SerializeToString, - ), - 'GenerateSync': grpc.unary_unary_rpc_method_handler( - servicer.GenerateSync, - request_deserializer=truffle_dot_infer_dot_irequest__pb2.IRequest.FromString, - response_serializer=truffle_dot_infer_dot_iresponse__pb2.IResponse.SerializeToString, - ), - 'GenerateBatch': grpc.unary_unary_rpc_method_handler( - servicer.GenerateBatch, - request_deserializer=truffle_dot_infer_dot_irequest__pb2.BatchIRequest.FromString, - response_serializer=truffle_dot_infer_dot_iresponse__pb2.BatchIResponse.SerializeToString, - ), - 'Embed': grpc.unary_unary_rpc_method_handler( - servicer.Embed, - request_deserializer=truffle_dot_infer_dot_embedding__pb2.EmbeddingRequest.FromString, - response_serializer=truffle_dot_infer_dot_embedding__pb2.EmbeddingResponse.SerializeToString, - ), - 'EmbedQueries': grpc.unary_unary_rpc_method_handler( - servicer.EmbedQueries, - request_deserializer=truffle_dot_infer_dot_embedding__pb2.EmbeddingRequest.FromString, - response_serializer=truffle_dot_infer_dot_embedding__pb2.EmbeddingResponse.SerializeToString, - ), - 'GetModelList': grpc.unary_unary_rpc_method_handler( - servicer.GetModelList, - request_deserializer=truffle_dot_infer_dot_model__pb2.GetModelListRequest.FromString, - response_serializer=truffle_dot_infer_dot_model__pb2.ModelList.SerializeToString, - ), - 'GetModel': grpc.unary_unary_rpc_method_handler( - servicer.GetModel, - request_deserializer=truffle_dot_infer_dot_model__pb2.GetModelRequest.FromString, - response_serializer=truffle_dot_infer_dot_model__pb2.Model.SerializeToString, - ), - 'SetModels': grpc.unary_unary_rpc_method_handler( - servicer.SetModels, - request_deserializer=truffle_dot_infer_dot_model__pb2.SetModelsRequest.FromString, - response_serializer=truffle_dot_infer_dot_model__pb2.SetModelsResponse.SerializeToString, - ), - 'GetModelState': grpc.unary_unary_rpc_method_handler( - servicer.GetModelState, - request_deserializer=truffle_dot_infer_dot_model__pb2.GetModelRequest.FromString, - response_serializer=truffle_dot_infer_dot_model__pb2.ModelStateUpdate.SerializeToString, - ), - 'OnModelStateChange': grpc.unary_stream_rpc_method_handler( - servicer.OnModelStateChange, - request_deserializer=truffle_dot_infer_dot_model__pb2.GetModelRequest.FromString, - response_serializer=truffle_dot_infer_dot_model__pb2.ModelStateUpdate.SerializeToString, - ), - 'GetEmbeddingModelList': grpc.unary_unary_rpc_method_handler( - servicer.GetEmbeddingModelList, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=truffle_dot_infer_dot_model__pb2.EmbeddingModelList.SerializeToString, - ), - 'GetEmbeddingModelInfo': grpc.unary_unary_rpc_method_handler( - servicer.GetEmbeddingModelInfo, - request_deserializer=truffle_dot_infer_dot_model__pb2.GetModelRequest.FromString, - response_serializer=truffle_dot_infer_dot_model__pb2.EmbeddingModelInfo.SerializeToString, - ), - 'BuildConvo': grpc.unary_unary_rpc_method_handler( - servicer.BuildConvo, - request_deserializer=truffle_dot_infer_dot_convo_dot_conversation__pb2.Conversation.FromString, - response_serializer=truffle_dot_infer_dot_convo_dot_conversation__pb2.BuiltContext.SerializeToString, - ), - 'ValidateGenerationConfig': grpc.unary_unary_rpc_method_handler( - servicer.ValidateGenerationConfig, - request_deserializer=truffle_dot_infer_dot_gencfg__pb2.ValidateConfigRequest.FromString, - response_serializer=truffle_dot_infer_dot_gencfg__pb2.ValidateConfigResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'truffle.infer.InferenceService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - server.add_registered_method_handlers('truffle.infer.InferenceService', rpc_method_handlers) - - - # This class is part of an EXPERIMENTAL API. -class InferenceService(object): - """ - legacy inference service apis (v1) - """ - - @staticmethod - def Generate(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/truffle.infer.InferenceService/Generate', - truffle_dot_infer_dot_irequest__pb2.IRequest.SerializeToString, - truffle_dot_infer_dot_iresponse__pb2.IResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GenerateSync(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/GenerateSync', - truffle_dot_infer_dot_irequest__pb2.IRequest.SerializeToString, - truffle_dot_infer_dot_iresponse__pb2.IResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GenerateBatch(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/GenerateBatch', - truffle_dot_infer_dot_irequest__pb2.BatchIRequest.SerializeToString, - truffle_dot_infer_dot_iresponse__pb2.BatchIResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def Embed(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/Embed', - truffle_dot_infer_dot_embedding__pb2.EmbeddingRequest.SerializeToString, - truffle_dot_infer_dot_embedding__pb2.EmbeddingResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def EmbedQueries(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/EmbedQueries', - truffle_dot_infer_dot_embedding__pb2.EmbeddingRequest.SerializeToString, - truffle_dot_infer_dot_embedding__pb2.EmbeddingResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetModelList(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/GetModelList', - truffle_dot_infer_dot_model__pb2.GetModelListRequest.SerializeToString, - truffle_dot_infer_dot_model__pb2.ModelList.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetModel(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/GetModel', - truffle_dot_infer_dot_model__pb2.GetModelRequest.SerializeToString, - truffle_dot_infer_dot_model__pb2.Model.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def SetModels(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/SetModels', - truffle_dot_infer_dot_model__pb2.SetModelsRequest.SerializeToString, - truffle_dot_infer_dot_model__pb2.SetModelsResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetModelState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/GetModelState', - truffle_dot_infer_dot_model__pb2.GetModelRequest.SerializeToString, - truffle_dot_infer_dot_model__pb2.ModelStateUpdate.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def OnModelStateChange(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream( - request, - target, - '/truffle.infer.InferenceService/OnModelStateChange', - truffle_dot_infer_dot_model__pb2.GetModelRequest.SerializeToString, - truffle_dot_infer_dot_model__pb2.ModelStateUpdate.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetEmbeddingModelList(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/GetEmbeddingModelList', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - truffle_dot_infer_dot_model__pb2.EmbeddingModelList.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def GetEmbeddingModelInfo(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/GetEmbeddingModelInfo', - truffle_dot_infer_dot_model__pb2.GetModelRequest.SerializeToString, - truffle_dot_infer_dot_model__pb2.EmbeddingModelInfo.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def BuildConvo(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/BuildConvo', - truffle_dot_infer_dot_convo_dot_conversation__pb2.Conversation.SerializeToString, - truffle_dot_infer_dot_convo_dot_conversation__pb2.BuiltContext.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) - - @staticmethod - def ValidateGenerationConfig(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary( - request, - target, - '/truffle.infer.InferenceService/ValidateGenerationConfig', - truffle_dot_infer_dot_gencfg__pb2.ValidateConfigRequest.SerializeToString, - truffle_dot_infer_dot_gencfg__pb2.ValidateConfigResponse.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - _registered_method=True) diff --git a/truffle/infer/irequest_pb2.py b/truffle/infer/irequest_pb2.py deleted file mode 100644 index 3d073a7..0000000 --- a/truffle/infer/irequest_pb2.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/irequest.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/irequest.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from truffle.infer import gencfg_pb2 as truffle_dot_infer_dot_gencfg__pb2 -from truffle.infer.convo import conversation_pb2 as truffle_dot_infer_dot_convo_dot_conversation__pb2 -try: - truffle_dot_infer_dot_convo_dot_msg__pb2 = truffle_dot_infer_dot_convo_dot_conversation__pb2.truffle_dot_infer_dot_convo_dot_msg__pb2 -except AttributeError: - truffle_dot_infer_dot_convo_dot_msg__pb2 = truffle_dot_infer_dot_convo_dot_conversation__pb2.truffle.infer.convo.msg_pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/infer/irequest.proto\x12\rtruffle.infer\x1a\x1atruffle/infer/gencfg.proto\x1a&truffle/infer/convo/conversation.proto\"\xd8\x01\n\x08IRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x03raw\x18\x02 \x01(\tH\x00\x12\x32\n\x05\x63onvo\x18\x03 \x01(\x0b\x32!.truffle.infer.convo.ConversationH\x00\x12,\n\x03\x63\x66g\x18\x04 \x01(\x0b\x32\x1f.truffle.infer.GenerationConfig\x12\x12\n\nmodel_uuid\x18\x05 \x01(\t\x12\x30\n\x08priority\x18\x06 \x01(\x0e\x32\x1e.truffle.infer.RequestPriorityB\t\n\x07\x63ontext\":\n\rBatchIRequest\x12)\n\x08requests\x18\x01 \x03(\x0b\x32\x17.truffle.infer.IRequest*\x89\x01\n\x0fRequestPriority\x12 \n\x1cREQUEST_PRIORITY_UNSPECIFIED\x10\x00\x12\x18\n\x14REQUEST_PRIORITY_LOW\x10\x01\x12\x1b\n\x17REQUEST_PRIORITY_NORMAL\x10\x02\x12\x1d\n\x19REQUEST_PRIORITY_REALTIME\x10\x03\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.irequest_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_REQUESTPRIORITY']._serialized_start=395 - _globals['_REQUESTPRIORITY']._serialized_end=532 - _globals['_IREQUEST']._serialized_start=116 - _globals['_IREQUEST']._serialized_end=332 - _globals['_BATCHIREQUEST']._serialized_start=334 - _globals['_BATCHIREQUEST']._serialized_end=392 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/irequest_pb2.pyi b/truffle/infer/irequest_pb2.pyi deleted file mode 100644 index 8332e8c..0000000 --- a/truffle/infer/irequest_pb2.pyi +++ /dev/null @@ -1,44 +0,0 @@ -from truffle.infer import gencfg_pb2 as _gencfg_pb2 -from truffle.infer.convo import conversation_pb2 as _conversation_pb2 -from truffle.infer.convo import msg_pb2 as _msg_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class RequestPriority(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - REQUEST_PRIORITY_UNSPECIFIED: _ClassVar[RequestPriority] - REQUEST_PRIORITY_LOW: _ClassVar[RequestPriority] - REQUEST_PRIORITY_NORMAL: _ClassVar[RequestPriority] - REQUEST_PRIORITY_REALTIME: _ClassVar[RequestPriority] -REQUEST_PRIORITY_UNSPECIFIED: RequestPriority -REQUEST_PRIORITY_LOW: RequestPriority -REQUEST_PRIORITY_NORMAL: RequestPriority -REQUEST_PRIORITY_REALTIME: RequestPriority - -class IRequest(_message.Message): - __slots__ = ("id", "raw", "convo", "cfg", "model_uuid", "priority") - ID_FIELD_NUMBER: _ClassVar[int] - RAW_FIELD_NUMBER: _ClassVar[int] - CONVO_FIELD_NUMBER: _ClassVar[int] - CFG_FIELD_NUMBER: _ClassVar[int] - MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - PRIORITY_FIELD_NUMBER: _ClassVar[int] - id: str - raw: str - convo: _conversation_pb2.Conversation - cfg: _gencfg_pb2.GenerationConfig - model_uuid: str - priority: RequestPriority - def __init__(self, id: _Optional[str] = ..., raw: _Optional[str] = ..., convo: _Optional[_Union[_conversation_pb2.Conversation, _Mapping]] = ..., cfg: _Optional[_Union[_gencfg_pb2.GenerationConfig, _Mapping]] = ..., model_uuid: _Optional[str] = ..., priority: _Optional[_Union[RequestPriority, str]] = ...) -> None: ... - -class BatchIRequest(_message.Message): - __slots__ = ("requests",) - REQUESTS_FIELD_NUMBER: _ClassVar[int] - requests: _containers.RepeatedCompositeFieldContainer[IRequest] - def __init__(self, requests: _Optional[_Iterable[_Union[IRequest, _Mapping]]] = ...) -> None: ... diff --git a/truffle/infer/irequest_pb2_grpc.py b/truffle/infer/irequest_pb2_grpc.py deleted file mode 100644 index eeb5ec9..0000000 --- a/truffle/infer/irequest_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/irequest_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/iresponse_pb2.py b/truffle/infer/iresponse_pb2.py deleted file mode 100644 index 47071c3..0000000 --- a/truffle/infer/iresponse_pb2.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/iresponse.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/iresponse.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from truffle.infer import usage_pb2 as truffle_dot_infer_dot_usage__pb2 -from truffle.infer import finishreason_pb2 as truffle_dot_infer_dot_finishreason__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dtruffle/infer/iresponse.proto\x12\rtruffle.infer\x1a\x19truffle/infer/usage.proto\x1a truffle/infer/finishreason.proto\"\xa7\x01\n\tIResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12(\n\x05usage\x18\x03 \x01(\x0b\x32\x14.truffle.infer.UsageH\x00\x88\x01\x01\x12\x37\n\rfinish_reason\x18\x04 \x01(\x0e\x32\x1b.truffle.infer.FinishReasonH\x01\x88\x01\x01\x42\x08\n\x06_usageB\x10\n\x0e_finish_reason\"=\n\x0e\x42\x61tchIResponse\x12+\n\tresponses\x18\x01 \x03(\x0b\x32\x18.truffle.infer.IResponseb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.iresponse_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_IRESPONSE']._serialized_start=110 - _globals['_IRESPONSE']._serialized_end=277 - _globals['_BATCHIRESPONSE']._serialized_start=279 - _globals['_BATCHIRESPONSE']._serialized_end=340 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/iresponse_pb2.pyi b/truffle/infer/iresponse_pb2.pyi deleted file mode 100644 index 43d8a58..0000000 --- a/truffle/infer/iresponse_pb2.pyi +++ /dev/null @@ -1,27 +0,0 @@ -from truffle.infer import usage_pb2 as _usage_pb2 -from truffle.infer import finishreason_pb2 as _finishreason_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class IResponse(_message.Message): - __slots__ = ("id", "content", "usage", "finish_reason") - ID_FIELD_NUMBER: _ClassVar[int] - CONTENT_FIELD_NUMBER: _ClassVar[int] - USAGE_FIELD_NUMBER: _ClassVar[int] - FINISH_REASON_FIELD_NUMBER: _ClassVar[int] - id: str - content: str - usage: _usage_pb2.Usage - finish_reason: _finishreason_pb2.FinishReason - def __init__(self, id: _Optional[str] = ..., content: _Optional[str] = ..., usage: _Optional[_Union[_usage_pb2.Usage, _Mapping]] = ..., finish_reason: _Optional[_Union[_finishreason_pb2.FinishReason, str]] = ...) -> None: ... - -class BatchIResponse(_message.Message): - __slots__ = ("responses",) - RESPONSES_FIELD_NUMBER: _ClassVar[int] - responses: _containers.RepeatedCompositeFieldContainer[IResponse] - def __init__(self, responses: _Optional[_Iterable[_Union[IResponse, _Mapping]]] = ...) -> None: ... diff --git a/truffle/infer/iresponse_pb2_grpc.py b/truffle/infer/iresponse_pb2_grpc.py deleted file mode 100644 index 734b976..0000000 --- a/truffle/infer/iresponse_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/iresponse_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/model_pb2.py b/truffle/infer/model_pb2.py deleted file mode 100644 index e6e3dc5..0000000 --- a/truffle/infer/model_pb2.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/model.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/model.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19truffle/infer/model.proto\x12\rtruffle.infer\"\xda\x01\n\x12\x45mbeddingModelInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x38\n\x06\x63onfig\x18\x04 \x01(\x0b\x32(.truffle.infer.EmbeddingModelInfo.Config\x12\x0c\n\x04uuid\x18\x05 \x01(\t\x1aQ\n\x06\x43onfig\x12\x18\n\x10max_input_length\x18\x01 \x01(\x03\x12\x16\n\x0emax_batch_size\x18\x02 \x01(\x03\x12\x15\n\rembedding_dim\x18\x03 \x01(\x03\"G\n\x12\x45mbeddingModelList\x12\x31\n\x06models\x18\x01 \x03(\x0b\x32!.truffle.infer.EmbeddingModelInfo\"\xba\x04\n\x0bModelConfig\x12=\n\x04info\x18\x01 \x01(\x0b\x32*.truffle.infer.ModelConfig.ModelConfigInfoH\x00\x88\x01\x01\x12\x1b\n\x0e\x63ontext_length\x18\x02 \x01(\rH\x01\x88\x01\x01\x12\x1b\n\x0emax_batch_size\x18\x03 \x01(\rH\x02\x88\x01\x01\x12\x16\n\tdata_type\x18\x04 \x01(\tH\x03\x88\x01\x01\x12\x13\n\x06loaded\x18\x05 \x01(\x08H\x04\x88\x01\x01\x1a\xbc\x02\n\x0fModelConfigInfo\x12 \n\x18\x63ontext_length_limit_max\x18\x01 \x01(\r\x12 \n\x18\x63ontext_length_limit_min\x18\x02 \x01(\r\x12\x1c\n\x14model_context_length\x18\n \x01(\r\x12\x1c\n\x14\x62\x61tch_size_limit_max\x18\x03 \x01(\r\x12\x1c\n\x14\x62\x61tch_size_limit_min\x18\x04 \x01(\r\x12\x1c\n\x14has_chain_of_thought\x18\x05 \x01(\x08\x12\x12\n\nis_agentic\x18\x06 \x01(\x08\x12\x1b\n\x13memory_usage_params\x18\x07 \x01(\x03\x12\x1e\n\x16memory_usage_inference\x18\x08 \x01(\x03\x12\x1c\n\x14\x61vailable_data_types\x18\t \x03(\tB\x07\n\x05_infoB\x11\n\x0f_context_lengthB\x11\n\x0f_max_batch_sizeB\x0c\n\n_data_typeB\t\n\x07_loaded\"\xa0\x02\n\x05Model\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x10\n\x08provider\x18\x03 \x01(\t\x12*\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x1a.truffle.infer.ModelConfig\x12.\n\x05state\x18\x05 \x01(\x0e\x32\x1f.truffle.infer.Model.ModelState\"\x8c\x01\n\nModelState\x12\x17\n\x13MODEL_STATE_INVALID\x10\x00\x12\x19\n\x15MODEL_STATE_AVAILABLE\x10\x01\x12\x17\n\x13MODEL_STATE_LOADING\x10\x02\x12\x19\n\x15MODEL_STATE_UNLOADING\x10\x03\x12\x16\n\x12MODEL_STATE_LOADED\x10\x04\"z\n\x10ModelStateUpdate\x12\x12\n\nmodel_uuid\x18\x01 \x01(\t\x12.\n\x05state\x18\x02 \x01(\x0e\x32\x1f.truffle.infer.Model.ModelState\x12\x15\n\x08progress\x18\x03 \x01(\x05H\x00\x88\x01\x01\x42\x0b\n\t_progress\"\\\n\tModelList\x12$\n\x06models\x18\x01 \x03(\x0b\x32\x14.truffle.infer.Model\x12\x14\n\x0ctotal_memory\x18\x02 \x01(\x04\x12\x13\n\x0bused_memory\x18\x03 \x01(\x04\"%\n\x0fGetModelRequest\x12\x12\n\nmodel_uuid\x18\x01 \x01(\t\"m\n\x13GetModelListRequest\x12\x12\n\nuse_filter\x18\x01 \x01(\x08\x12\x13\n\tavailable\x18\x02 \x01(\x08H\x00\x12\x10\n\x06loaded\x18\x03 \x01(\x08H\x00\x12\x11\n\x07\x61gentic\x18\x04 \x01(\x08H\x00\x42\x08\n\x06\x66ilter\"\x88\x02\n\x11SetModelsResponse\x12\x41\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x33.truffle.infer.SetModelsResponse.SetModelsErrorCode\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x33\n\x0cupdated_list\x18\x03 \x01(\x0b\x32\x18.truffle.infer.ModelListH\x00\x88\x01\x01\"Y\n\x12SetModelsErrorCode\x12\x06\n\x02OK\x10\x00\x12\x12\n\x0eINVALID_CONFIG\x10\x01\x12\x15\n\x11NOT_ENOUGH_MEMORY\x10\x02\x12\x10\n\x0cMODEL_IN_USE\x10\x03\x42\x0f\n\r_updated_list\"\x9d\x01\n\x10SetModelsRequest\x12=\n\x07updates\x18\x01 \x03(\x0b\x32,.truffle.infer.SetModelsRequest.UpdatesEntry\x1aJ\n\x0cUpdatesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x1a.truffle.infer.ModelConfig:\x02\x38\x01\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.model_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_SETMODELSREQUEST_UPDATESENTRY']._loaded_options = None - _globals['_SETMODELSREQUEST_UPDATESENTRY']._serialized_options = b'8\001' - _globals['_EMBEDDINGMODELINFO']._serialized_start=45 - _globals['_EMBEDDINGMODELINFO']._serialized_end=263 - _globals['_EMBEDDINGMODELINFO_CONFIG']._serialized_start=182 - _globals['_EMBEDDINGMODELINFO_CONFIG']._serialized_end=263 - _globals['_EMBEDDINGMODELLIST']._serialized_start=265 - _globals['_EMBEDDINGMODELLIST']._serialized_end=336 - _globals['_MODELCONFIG']._serialized_start=339 - _globals['_MODELCONFIG']._serialized_end=909 - _globals['_MODELCONFIG_MODELCONFIGINFO']._serialized_start=521 - _globals['_MODELCONFIG_MODELCONFIGINFO']._serialized_end=837 - _globals['_MODEL']._serialized_start=912 - _globals['_MODEL']._serialized_end=1200 - _globals['_MODEL_MODELSTATE']._serialized_start=1060 - _globals['_MODEL_MODELSTATE']._serialized_end=1200 - _globals['_MODELSTATEUPDATE']._serialized_start=1202 - _globals['_MODELSTATEUPDATE']._serialized_end=1324 - _globals['_MODELLIST']._serialized_start=1326 - _globals['_MODELLIST']._serialized_end=1418 - _globals['_GETMODELREQUEST']._serialized_start=1420 - _globals['_GETMODELREQUEST']._serialized_end=1457 - _globals['_GETMODELLISTREQUEST']._serialized_start=1459 - _globals['_GETMODELLISTREQUEST']._serialized_end=1568 - _globals['_SETMODELSRESPONSE']._serialized_start=1571 - _globals['_SETMODELSRESPONSE']._serialized_end=1835 - _globals['_SETMODELSRESPONSE_SETMODELSERRORCODE']._serialized_start=1729 - _globals['_SETMODELSRESPONSE_SETMODELSERRORCODE']._serialized_end=1818 - _globals['_SETMODELSREQUEST']._serialized_start=1838 - _globals['_SETMODELSREQUEST']._serialized_end=1995 - _globals['_SETMODELSREQUEST_UPDATESENTRY']._serialized_start=1921 - _globals['_SETMODELSREQUEST_UPDATESENTRY']._serialized_end=1995 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/model_pb2.pyi b/truffle/infer/model_pb2.pyi deleted file mode 100644 index 218c5d7..0000000 --- a/truffle/infer/model_pb2.pyi +++ /dev/null @@ -1,171 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable, Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class EmbeddingModelInfo(_message.Message): - __slots__ = ("id", "name", "version", "config", "uuid") - class Config(_message.Message): - __slots__ = ("max_input_length", "max_batch_size", "embedding_dim") - MAX_INPUT_LENGTH_FIELD_NUMBER: _ClassVar[int] - MAX_BATCH_SIZE_FIELD_NUMBER: _ClassVar[int] - EMBEDDING_DIM_FIELD_NUMBER: _ClassVar[int] - max_input_length: int - max_batch_size: int - embedding_dim: int - def __init__(self, max_input_length: _Optional[int] = ..., max_batch_size: _Optional[int] = ..., embedding_dim: _Optional[int] = ...) -> None: ... - ID_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - VERSION_FIELD_NUMBER: _ClassVar[int] - CONFIG_FIELD_NUMBER: _ClassVar[int] - UUID_FIELD_NUMBER: _ClassVar[int] - id: int - name: str - version: str - config: EmbeddingModelInfo.Config - uuid: str - def __init__(self, id: _Optional[int] = ..., name: _Optional[str] = ..., version: _Optional[str] = ..., config: _Optional[_Union[EmbeddingModelInfo.Config, _Mapping]] = ..., uuid: _Optional[str] = ...) -> None: ... - -class EmbeddingModelList(_message.Message): - __slots__ = ("models",) - MODELS_FIELD_NUMBER: _ClassVar[int] - models: _containers.RepeatedCompositeFieldContainer[EmbeddingModelInfo] - def __init__(self, models: _Optional[_Iterable[_Union[EmbeddingModelInfo, _Mapping]]] = ...) -> None: ... - -class ModelConfig(_message.Message): - __slots__ = ("info", "context_length", "max_batch_size", "data_type", "loaded") - class ModelConfigInfo(_message.Message): - __slots__ = ("context_length_limit_max", "context_length_limit_min", "model_context_length", "batch_size_limit_max", "batch_size_limit_min", "has_chain_of_thought", "is_agentic", "memory_usage_params", "memory_usage_inference", "available_data_types") - CONTEXT_LENGTH_LIMIT_MAX_FIELD_NUMBER: _ClassVar[int] - CONTEXT_LENGTH_LIMIT_MIN_FIELD_NUMBER: _ClassVar[int] - MODEL_CONTEXT_LENGTH_FIELD_NUMBER: _ClassVar[int] - BATCH_SIZE_LIMIT_MAX_FIELD_NUMBER: _ClassVar[int] - BATCH_SIZE_LIMIT_MIN_FIELD_NUMBER: _ClassVar[int] - HAS_CHAIN_OF_THOUGHT_FIELD_NUMBER: _ClassVar[int] - IS_AGENTIC_FIELD_NUMBER: _ClassVar[int] - MEMORY_USAGE_PARAMS_FIELD_NUMBER: _ClassVar[int] - MEMORY_USAGE_INFERENCE_FIELD_NUMBER: _ClassVar[int] - AVAILABLE_DATA_TYPES_FIELD_NUMBER: _ClassVar[int] - context_length_limit_max: int - context_length_limit_min: int - model_context_length: int - batch_size_limit_max: int - batch_size_limit_min: int - has_chain_of_thought: bool - is_agentic: bool - memory_usage_params: int - memory_usage_inference: int - available_data_types: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, context_length_limit_max: _Optional[int] = ..., context_length_limit_min: _Optional[int] = ..., model_context_length: _Optional[int] = ..., batch_size_limit_max: _Optional[int] = ..., batch_size_limit_min: _Optional[int] = ..., has_chain_of_thought: bool = ..., is_agentic: bool = ..., memory_usage_params: _Optional[int] = ..., memory_usage_inference: _Optional[int] = ..., available_data_types: _Optional[_Iterable[str]] = ...) -> None: ... - INFO_FIELD_NUMBER: _ClassVar[int] - CONTEXT_LENGTH_FIELD_NUMBER: _ClassVar[int] - MAX_BATCH_SIZE_FIELD_NUMBER: _ClassVar[int] - DATA_TYPE_FIELD_NUMBER: _ClassVar[int] - LOADED_FIELD_NUMBER: _ClassVar[int] - info: ModelConfig.ModelConfigInfo - context_length: int - max_batch_size: int - data_type: str - loaded: bool - def __init__(self, info: _Optional[_Union[ModelConfig.ModelConfigInfo, _Mapping]] = ..., context_length: _Optional[int] = ..., max_batch_size: _Optional[int] = ..., data_type: _Optional[str] = ..., loaded: bool = ...) -> None: ... - -class Model(_message.Message): - __slots__ = ("uuid", "name", "provider", "config", "state") - class ModelState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - MODEL_STATE_INVALID: _ClassVar[Model.ModelState] - MODEL_STATE_AVAILABLE: _ClassVar[Model.ModelState] - MODEL_STATE_LOADING: _ClassVar[Model.ModelState] - MODEL_STATE_UNLOADING: _ClassVar[Model.ModelState] - MODEL_STATE_LOADED: _ClassVar[Model.ModelState] - MODEL_STATE_INVALID: Model.ModelState - MODEL_STATE_AVAILABLE: Model.ModelState - MODEL_STATE_LOADING: Model.ModelState - MODEL_STATE_UNLOADING: Model.ModelState - MODEL_STATE_LOADED: Model.ModelState - UUID_FIELD_NUMBER: _ClassVar[int] - NAME_FIELD_NUMBER: _ClassVar[int] - PROVIDER_FIELD_NUMBER: _ClassVar[int] - CONFIG_FIELD_NUMBER: _ClassVar[int] - STATE_FIELD_NUMBER: _ClassVar[int] - uuid: str - name: str - provider: str - config: ModelConfig - state: Model.ModelState - def __init__(self, uuid: _Optional[str] = ..., name: _Optional[str] = ..., provider: _Optional[str] = ..., config: _Optional[_Union[ModelConfig, _Mapping]] = ..., state: _Optional[_Union[Model.ModelState, str]] = ...) -> None: ... - -class ModelStateUpdate(_message.Message): - __slots__ = ("model_uuid", "state", "progress") - MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - STATE_FIELD_NUMBER: _ClassVar[int] - PROGRESS_FIELD_NUMBER: _ClassVar[int] - model_uuid: str - state: Model.ModelState - progress: int - def __init__(self, model_uuid: _Optional[str] = ..., state: _Optional[_Union[Model.ModelState, str]] = ..., progress: _Optional[int] = ...) -> None: ... - -class ModelList(_message.Message): - __slots__ = ("models", "total_memory", "used_memory") - MODELS_FIELD_NUMBER: _ClassVar[int] - TOTAL_MEMORY_FIELD_NUMBER: _ClassVar[int] - USED_MEMORY_FIELD_NUMBER: _ClassVar[int] - models: _containers.RepeatedCompositeFieldContainer[Model] - total_memory: int - used_memory: int - def __init__(self, models: _Optional[_Iterable[_Union[Model, _Mapping]]] = ..., total_memory: _Optional[int] = ..., used_memory: _Optional[int] = ...) -> None: ... - -class GetModelRequest(_message.Message): - __slots__ = ("model_uuid",) - MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - model_uuid: str - def __init__(self, model_uuid: _Optional[str] = ...) -> None: ... - -class GetModelListRequest(_message.Message): - __slots__ = ("use_filter", "available", "loaded", "agentic") - USE_FILTER_FIELD_NUMBER: _ClassVar[int] - AVAILABLE_FIELD_NUMBER: _ClassVar[int] - LOADED_FIELD_NUMBER: _ClassVar[int] - AGENTIC_FIELD_NUMBER: _ClassVar[int] - use_filter: bool - available: bool - loaded: bool - agentic: bool - def __init__(self, use_filter: bool = ..., available: bool = ..., loaded: bool = ..., agentic: bool = ...) -> None: ... - -class SetModelsResponse(_message.Message): - __slots__ = ("code", "message", "updated_list") - class SetModelsErrorCode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - OK: _ClassVar[SetModelsResponse.SetModelsErrorCode] - INVALID_CONFIG: _ClassVar[SetModelsResponse.SetModelsErrorCode] - NOT_ENOUGH_MEMORY: _ClassVar[SetModelsResponse.SetModelsErrorCode] - MODEL_IN_USE: _ClassVar[SetModelsResponse.SetModelsErrorCode] - OK: SetModelsResponse.SetModelsErrorCode - INVALID_CONFIG: SetModelsResponse.SetModelsErrorCode - NOT_ENOUGH_MEMORY: SetModelsResponse.SetModelsErrorCode - MODEL_IN_USE: SetModelsResponse.SetModelsErrorCode - CODE_FIELD_NUMBER: _ClassVar[int] - MESSAGE_FIELD_NUMBER: _ClassVar[int] - UPDATED_LIST_FIELD_NUMBER: _ClassVar[int] - code: SetModelsResponse.SetModelsErrorCode - message: str - updated_list: ModelList - def __init__(self, code: _Optional[_Union[SetModelsResponse.SetModelsErrorCode, str]] = ..., message: _Optional[str] = ..., updated_list: _Optional[_Union[ModelList, _Mapping]] = ...) -> None: ... - -class SetModelsRequest(_message.Message): - __slots__ = ("updates",) - class UpdatesEntry(_message.Message): - __slots__ = ("key", "value") - KEY_FIELD_NUMBER: _ClassVar[int] - VALUE_FIELD_NUMBER: _ClassVar[int] - key: str - value: ModelConfig - def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[ModelConfig, _Mapping]] = ...) -> None: ... - UPDATES_FIELD_NUMBER: _ClassVar[int] - updates: _containers.MessageMap[str, ModelConfig] - def __init__(self, updates: _Optional[_Mapping[str, ModelConfig]] = ...) -> None: ... diff --git a/truffle/infer/model_pb2_grpc.py b/truffle/infer/model_pb2_grpc.py deleted file mode 100644 index a6fb854..0000000 --- a/truffle/infer/model_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/model_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/tokenize_pb2.py b/truffle/infer/tokenize_pb2.py deleted file mode 100644 index 4957b72..0000000 --- a/truffle/infer/tokenize_pb2.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/tokenize.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/tokenize.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ctruffle/infer/tokenize.proto\x12\rtruffle.infer\"4\n\x0fTokenizeRequest\x12\r\n\x05texts\x18\x01 \x03(\t\x12\x12\n\nmodel_uuid\x18\x02 \x01(\t\"#\n\x10TokenizeResponse\x12\x0f\n\x07lengths\x18\x01 \x03(\rb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.tokenize_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_TOKENIZEREQUEST']._serialized_start=47 - _globals['_TOKENIZEREQUEST']._serialized_end=99 - _globals['_TOKENIZERESPONSE']._serialized_start=101 - _globals['_TOKENIZERESPONSE']._serialized_end=136 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/tokenize_pb2.pyi b/truffle/infer/tokenize_pb2.pyi deleted file mode 100644 index 8f9a640..0000000 --- a/truffle/infer/tokenize_pb2.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from google.protobuf.internal import containers as _containers -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Iterable as _Iterable -from typing import ClassVar as _ClassVar, Optional as _Optional - -DESCRIPTOR: _descriptor.FileDescriptor - -class TokenizeRequest(_message.Message): - __slots__ = ("texts", "model_uuid") - TEXTS_FIELD_NUMBER: _ClassVar[int] - MODEL_UUID_FIELD_NUMBER: _ClassVar[int] - texts: _containers.RepeatedScalarFieldContainer[str] - model_uuid: str - def __init__(self, texts: _Optional[_Iterable[str]] = ..., model_uuid: _Optional[str] = ...) -> None: ... - -class TokenizeResponse(_message.Message): - __slots__ = ("lengths",) - LENGTHS_FIELD_NUMBER: _ClassVar[int] - lengths: _containers.RepeatedScalarFieldContainer[int] - def __init__(self, lengths: _Optional[_Iterable[int]] = ...) -> None: ... diff --git a/truffle/infer/tokenize_pb2_grpc.py b/truffle/infer/tokenize_pb2_grpc.py deleted file mode 100644 index c9231c3..0000000 --- a/truffle/infer/tokenize_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/tokenize_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) diff --git a/truffle/infer/usage_pb2.py b/truffle/infer/usage_pb2.py deleted file mode 100644 index 2d01766..0000000 --- a/truffle/infer/usage_pb2.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: truffle/infer/usage.proto -# Protobuf Python Version: 6.31.1 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, - '', - 'truffle/infer/usage.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19truffle/infer/usage.proto\x12\rtruffle.infer\"\xbb\x02\n\x05Usage\x12\x12\n\ntotal_time\x18\x01 \x01(\x01\x12\x14\n\x0cprefill_time\x18\x02 \x01(\x01\x12\x13\n\x0b\x64\x65\x63ode_time\x18\x03 \x01(\x01\x12\x0c\n\x04ttft\x18\x04 \x01(\x01\x12\x1b\n\x13inter_token_latency\x18\x05 \x01(\x01\x12\x12\n\ndecode_tps\x18\x06 \x01(\x01\x12\x13\n\x0bprefill_tps\x18\x07 \x01(\x01\x12+\n\x06tokens\x18\x08 \x01(\x0b\x32\x1b.truffle.infer.Usage.Tokens\x1ar\n\x06Tokens\x12\x0e\n\x06prompt\x18\x01 \x01(\x04\x12\x12\n\ncompletion\x18\x02 \x01(\x04\x12\x0f\n\x07prefill\x18\x03 \x01(\x04\x12\x0e\n\x06\x64\x65\x63ode\x18\x04 \x01(\x04\x12\x14\n\x0cjump_forward\x18\x05 \x01(\x04\x12\r\n\x05image\x18\x06 \x01(\x04\"R\n\x0bSystemUsage\x12\x13\n\x0b\x64\x65vice_name\x18\x01 \x01(\t\x12\x14\n\x0ctotal_memory\x18\x02 \x01(\x04\x12\x18\n\x10\x61vailable_memory\x18\x03 \x01(\x04\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'truffle.infer.usage_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_USAGE']._serialized_start=45 - _globals['_USAGE']._serialized_end=360 - _globals['_USAGE_TOKENS']._serialized_start=246 - _globals['_USAGE_TOKENS']._serialized_end=360 - _globals['_SYSTEMUSAGE']._serialized_start=362 - _globals['_SYSTEMUSAGE']._serialized_end=444 -# @@protoc_insertion_point(module_scope) diff --git a/truffle/infer/usage_pb2.pyi b/truffle/infer/usage_pb2.pyi deleted file mode 100644 index bb2758e..0000000 --- a/truffle/infer/usage_pb2.pyi +++ /dev/null @@ -1,51 +0,0 @@ -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from collections.abc import Mapping as _Mapping -from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union - -DESCRIPTOR: _descriptor.FileDescriptor - -class Usage(_message.Message): - __slots__ = ("total_time", "prefill_time", "decode_time", "ttft", "inter_token_latency", "decode_tps", "prefill_tps", "tokens") - class Tokens(_message.Message): - __slots__ = ("prompt", "completion", "prefill", "decode", "jump_forward", "image") - PROMPT_FIELD_NUMBER: _ClassVar[int] - COMPLETION_FIELD_NUMBER: _ClassVar[int] - PREFILL_FIELD_NUMBER: _ClassVar[int] - DECODE_FIELD_NUMBER: _ClassVar[int] - JUMP_FORWARD_FIELD_NUMBER: _ClassVar[int] - IMAGE_FIELD_NUMBER: _ClassVar[int] - prompt: int - completion: int - prefill: int - decode: int - jump_forward: int - image: int - def __init__(self, prompt: _Optional[int] = ..., completion: _Optional[int] = ..., prefill: _Optional[int] = ..., decode: _Optional[int] = ..., jump_forward: _Optional[int] = ..., image: _Optional[int] = ...) -> None: ... - TOTAL_TIME_FIELD_NUMBER: _ClassVar[int] - PREFILL_TIME_FIELD_NUMBER: _ClassVar[int] - DECODE_TIME_FIELD_NUMBER: _ClassVar[int] - TTFT_FIELD_NUMBER: _ClassVar[int] - INTER_TOKEN_LATENCY_FIELD_NUMBER: _ClassVar[int] - DECODE_TPS_FIELD_NUMBER: _ClassVar[int] - PREFILL_TPS_FIELD_NUMBER: _ClassVar[int] - TOKENS_FIELD_NUMBER: _ClassVar[int] - total_time: float - prefill_time: float - decode_time: float - ttft: float - inter_token_latency: float - decode_tps: float - prefill_tps: float - tokens: Usage.Tokens - def __init__(self, total_time: _Optional[float] = ..., prefill_time: _Optional[float] = ..., decode_time: _Optional[float] = ..., ttft: _Optional[float] = ..., inter_token_latency: _Optional[float] = ..., decode_tps: _Optional[float] = ..., prefill_tps: _Optional[float] = ..., tokens: _Optional[_Union[Usage.Tokens, _Mapping]] = ...) -> None: ... - -class SystemUsage(_message.Message): - __slots__ = ("device_name", "total_memory", "available_memory") - DEVICE_NAME_FIELD_NUMBER: _ClassVar[int] - TOTAL_MEMORY_FIELD_NUMBER: _ClassVar[int] - AVAILABLE_MEMORY_FIELD_NUMBER: _ClassVar[int] - device_name: str - total_memory: int - available_memory: int - def __init__(self, device_name: _Optional[str] = ..., total_memory: _Optional[int] = ..., available_memory: _Optional[int] = ...) -> None: ... diff --git a/truffle/infer/usage_pb2_grpc.py b/truffle/infer/usage_pb2_grpc.py deleted file mode 100644 index b686b44..0000000 --- a/truffle/infer/usage_pb2_grpc.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc -import warnings - - -GRPC_GENERATED_VERSION = '1.76.0' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + ' but the generated code in truffle/infer/usage_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) From d841eeea049c0016f9c5689dcba8f8c8ee44485e Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 23:01:50 -0800 Subject: [PATCH 8/9] final touches --- README.md | 171 +++++++++------------- truffile/cli.py | 381 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 448 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 8575f40..1907272 100644 --- a/README.md +++ b/README.md @@ -1,132 +1,95 @@ -# truffile +# 🍄‍🟫 Truffile -TruffleOS SDK - deploy apps to Truffle devices +Python SDK/CLI for Truffle devices. -## proto sync +## What It Does -`truffile` vendors generated protobuf modules from `pyfw/python/truffle`. +- discovers and connects to your Truffle (`scan`, `connect`, `disconnect`) +- validates and deploys apps from `truffile.yaml` (`validate`, `deploy`) +- manages installed apps (`list apps`, `delete`) +- talks to inference directly (`models`, `chat`) +- exposes an OpenAI-compatible local proxy (`proxy`) -To refresh them: +## Start making your Own Apps -```bash -./scripts/sync_protos.sh -``` +- app schema and validation: `truffile/truffile/schema/app_config.py` +- schedule parsing: `truffile/truffile/schedule.py` +- deploy planning + builder flow: `truffile/truffile/deploy/builder.py` +- generated TruffleOS protos vendored in: `truffile/truffle/` +- examples: + - `truffile/example-apps/kalshi` + - `truffile/example-apps/reddit` -## install +`truffile.yaml` defines: +- metadata (`name`, `description`, `type`) +- process (`cmd`, `working_directory`, `environment`) +- files to upload +- optional run/build commands +- background schedule policy (for BG apps) -```bash -pip install truffile -``` +## App Types and Runtime Model -or from source: -```bash -git clone -cd truffile -pip install -e . -``` +Apps can be: -## commands +- foreground (`fg`): exposes MCP tools that tasks/agents can call during active execution +- background (`bg`): runs on schedule and emits context for proactivity, enabling the device to trigger actions and write/update memory +- both (`fg` + `bg`): one app package can provide MCP tools and scheduled context emission -```bash -# find truffle devices on your network -truffile scan +How to think about it: -# connect to a device (first time requires approval on device) -truffile connect truffle-6272 +- FG path is tool-serving: app process is used as a callable capability surface (MCP) +- BG path is context/proactivity: scheduled runs feed the proactive agent with fresh signals +- Proactivity can take actions and persist memory based on BG outputs -# deploy an app from current directory -truffile deploy +In practice: -# deploy an app from a specific path -truffile deploy ./my-app +- use `fg` when you need direct tool invocation from tasks +- use `bg` when you need periodic monitoring, summaries, or event-driven context +- use `both` when the same app should both expose tools and continuously feed proactivity/memory -# deploy with interactive shell (for debugging) -truffile deploy -i +## Core Commands -# list installed apps on connected device +```bash +truffile scan +truffile connect +truffile validate [app_dir] +truffile deploy [app_dir] +truffile deploy --dry-run [app_dir] truffile list apps - -# list connected devices -truffile list devices - -# list IF2 models on the connected device +truffile delete truffile models - -# chat with IF2 model (streaming by default) truffile chat "hello" +truffile proxy --host 127.0.0.1 --port 8080 +``` -# chat with explicit model + system prompt -truffile chat --model --system "You are concise" --prompt "Summarize this" +## Inference Interfaces -# run OpenAI-compatible proxy backed by IF2 -truffile proxy --host 127.0.0.1 --port 8080 +Direct IF2: +- list models: `GET /if2/v1/models` +- chat completions: `POST /if2/v1/chat/completions` -# disconnect from a device -truffile disconnect truffle-6272 +CLI wrappers: +- `truffile models` +- `truffile chat` (streaming by default) -# disconnect from all devices -truffile disconnect all +## Proxy -# OpenAI-compatible base URL (proxy mode) -# http://127.0.0.1:8080/v1 -``` +`truffile proxy` serves OpenAI-compatible routes locally and forwards to device IF2: -## truffile.yaml - -apps need a `truffile.yaml` in their directory: - -```yaml -metadata: - name: My App - description: does cool stuff - type: background # or foreground - icon_file: ./icon.png - process: - cmd: [python, app.py] - working_directory: / - environment: - MY_VAR: value - # schedule for background apps only: - default_schedule: - type: interval # interval | times - interval: - duration: "1h" # 15m, 2h, 1d, etc. - schedule: - daily_window: "09:00-17:30" # optional - allowed_days: [mon, tue, wed, thu, fri] # optional - -files: - - source: ./app.py - destination: ./app.py - -run: | - pip install requests -``` +- `GET /v1/models` +- `POST /v1/chat/completions` -### schedule types - -**interval** - run every N minutes/hours: -```yaml -default_schedule: - type: interval - interval: - duration: "30m" - schedule: - daily_window: "06:00-22:00" - allowed_days: [mon, tue, wed, thu, fri] -``` +Default local base URL: +- `http://127.0.0.1:8080/v1` -**times** - run at specific times: -```yaml -default_schedule: - type: times - times: - run_times: ["08:00", "12:00", "18:00"] - allowed_days: [mon, tue, wed, thu, fri] -``` +Reasoning behavior: +- default: proxy can inject reasoning into `content` as `...` +- `--no-think-tags`: keeps reasoning separate as `reasoning_content` in stream deltas + +## Proto Sync -## example apps +Refresh vendored protos from firmware repo: -see `example-apps/` for working examples: -- `example-apps/kalshi` - foreground + background app -- `example-apps/reddit` - background app +```bash +./scripts/sync_protos.sh +``` diff --git a/truffile/cli.py b/truffile/cli.py index cb7335d..045c448 100644 --- a/truffile/cli.py +++ b/truffile/cli.py @@ -1,6 +1,7 @@ import argparse import asyncio import json +import re import signal import socket import sys @@ -36,6 +37,8 @@ class C: ARROW = "→" DOT = "•" WARN = "⚠" +TOOL_TAGS = ("", "") +TOOL_TAG_PATTERN = re.compile(r"\s*(.*?)\s*", re.DOTALL) class Spinner: @@ -939,6 +942,209 @@ def _inject_reasoning_into_chunk(chunk: dict, state: dict) -> dict: return chunk +def _normalize_finish_reason(fr: str | None) -> str | None: + if fr is None: + return None + s = str(fr).strip().lower() + if s in {"stop", "finish_stop"}: + return "stop" + if s in {"length", "finish_length"}: + return "length" + if s in {"tool_calls", "toolcalls", "finish_toolcalls"}: + return "tool_calls" + if s in {"content_filter"}: + return "content_filter" + return "stop" + + +def _normalize_usage_dict(usage: dict | None) -> dict | None: + if not isinstance(usage, dict): + return usage + if {"prompt_tokens", "completion_tokens", "total_tokens"}.issubset(set(usage.keys())): + return usage + tokens = usage.get("tokens") + if isinstance(tokens, dict): + prompt = int(tokens.get("prompt", 0) or 0) + completion = int(tokens.get("completion", 0) or 0) + out = dict(usage) + out["prompt_tokens"] = prompt + out["completion_tokens"] = completion + out["total_tokens"] = prompt + completion + return out + return usage + + +def _flatten_content(content: object) -> str: + if content is None: + return "" + if isinstance(content, str): + return content + if isinstance(content, list): + parts: list[str] = [] + for p in content: + if isinstance(p, dict) and p.get("type") == "text": + parts.append(str(p.get("text", ""))) + return "".join(parts) + return str(content) + + +def _extract_tool_calls_and_clean(text: str) -> tuple[list[dict], str]: + calls: list[dict] = [] + for m in TOOL_TAG_PATTERN.findall(text): + try: + obj = json.loads(m.strip()) + if isinstance(obj, dict): + calls.append(obj) + except Exception: + continue + cleaned = TOOL_TAG_PATTERN.sub("", text).strip() + return calls, cleaned + + +def _tool_prompt(tools_spec: list[dict]) -> str: + desc_lines: list[str] = [] + for t in tools_spec: + if not isinstance(t, dict) or t.get("type") != "function": + continue + fn = t.get("function", {}) + if not isinstance(fn, dict): + continue + name = fn.get("name") + if not isinstance(name, str) or not name: + continue + description = str(fn.get("description") or "") + params = fn.get("parameters") if isinstance(fn.get("parameters"), dict) else {"type": "object"} + desc_lines.append(f"{name}: {description}\nArg Schema: {json.dumps(params, indent=2)}") + if not desc_lines: + return "" + open_tag, close_tag = TOOL_TAGS + return ( + "You have access to the following tools:\n" + + "\n".join(desc_lines) + + "\nWhen you decide to use a tool, respond with a JSON object enclosed by " + + f"{open_tag} and {close_tag} tags in this format:\n" + + f"{open_tag}\n" + + '{\n "tool": "",\n "args": {}\n}\n' + + f"{close_tag}\n" + + "Only use tools listed above, and ensure your JSON is valid." + ) + + +def _serialize_tool_calls(tool_calls: list[dict]) -> str: + blocks: list[str] = [] + open_tag, close_tag = TOOL_TAGS + for tc in tool_calls: + if not isinstance(tc, dict) or tc.get("type") != "function": + continue + fn = tc.get("function", {}) + if not isinstance(fn, dict): + continue + name = fn.get("name") + if not isinstance(name, str) or not name: + continue + args_raw = fn.get("arguments") + args = {} + if isinstance(args_raw, str): + try: + maybe = json.loads(args_raw) + if isinstance(maybe, dict): + args = maybe + except Exception: + args = {"_raw": args_raw} + elif isinstance(args_raw, dict): + args = args_raw + blocks.append(f"{open_tag}\n{json.dumps({'tool': name, 'args': args})}\n{close_tag}") + return "\n".join(blocks) + + +def _massage_messages_for_tools(messages: list[dict], tools_spec: list[dict], tool_choice: object) -> list[dict]: + out: list[dict] = [] + prompt = _tool_prompt(tools_spec) if tool_choice != "none" else "" + injected = False + + tool_name_by_id: dict[str, str] = {} + for msg in messages: + if isinstance(msg, dict) and msg.get("role") == "assistant": + for tc in msg.get("tool_calls", []) or []: + if isinstance(tc, dict): + tc_id = tc.get("id") + fn = tc.get("function", {}) + if isinstance(tc_id, str) and isinstance(fn, dict) and isinstance(fn.get("name"), str): + tool_name_by_id[tc_id] = fn["name"] + + for msg in messages: + if not isinstance(msg, dict): + continue + role = msg.get("role") + content = _flatten_content(msg.get("content")) + + if role == "assistant" and isinstance(msg.get("tool_calls"), list): + serialized = _serialize_tool_calls(msg.get("tool_calls") or []) + if serialized: + content = (content + "\n" + serialized).strip() + + if role == "tool": + tool_name = msg.get("name") + if not isinstance(tool_name, str) or not tool_name: + tcid = msg.get("tool_call_id") + if isinstance(tcid, str): + tool_name = tool_name_by_id.get(tcid, "") + content = f' "tool" : "{tool_name or ""}" "output": "{content}" ' + + if role == "system" and prompt and not injected: + content = (content + "\n\n" + prompt).strip() + injected = True + + out.append({"role": role, "content": content}) + + if prompt and not injected: + out.insert(0, {"role": "system", "content": prompt}) + return out + + +class _ToolTagStreamFilter: + def __init__(self): + self.buf = "" + + def feed(self, text: str) -> str: + if not text: + return "" + s = self.buf + text + self.buf = "" + out: list[str] = [] + open_tag, close_tag = TOOL_TAGS + while s: + start = s.find(open_tag) + if start == -1: + keep = len(open_tag) - 1 + if len(s) > keep: + out.append(s[:-keep] if keep > 0 else s) + self.buf = s[-keep:] if keep > 0 else "" + else: + self.buf = s + break + if start > 0: + out.append(s[:start]) + s = s[start:] + end = s.find(close_tag) + if end == -1: + self.buf = s + break + s = s[end + len(close_tag):] + return "".join(out) + + def finalize(self) -> str: + if not self.buf: + return "" + open_tag, _ = TOOL_TAGS + if open_tag in self.buf: + self.buf = "" + return "" + tail = self.buf + self.buf = "" + return tail + + def _inject_reasoning_into_response(body: dict) -> dict: choices = body.get("choices") if not isinstance(choices, list): @@ -1044,6 +1250,17 @@ def do_POST(self): if mapped == "/if2/v1/chat/completions": if "reasoning" not in body: body["reasoning"] = {"enabled": False} + if isinstance(body.get("tools"), list): + messages = body.get("messages", []) + if isinstance(messages, list): + body["messages"] = _massage_messages_for_tools( + messages=messages, + tools_spec=body.get("tools") or [], + tool_choice=body.get("tool_choice"), + ) + # Let proxy map tool tags back to OpenAI tool_calls. + body.pop("tools", None) + body.pop("tool_choice", None) stream_mode = bool(body.get("stream")) and mapped == "/if2/v1/chat/completions" @@ -1063,6 +1280,12 @@ def do_POST(self): self.end_headers() state = {"thinking_open": False} + tool_filter = _ToolTagStreamFilter() + acc_text_parts: list[str] = [] + seen_finish_reason: str | None = None + stream_id = None + created = None + model_name = None for raw_line in resp.iter_lines(): line = raw_line if isinstance(raw_line, str) else raw_line.decode("utf-8", errors="replace") if not line: @@ -1072,6 +1295,63 @@ def do_POST(self): if line.startswith("data:"): payload = line[5:].strip() if payload == "[DONE]": + clean_tail = tool_filter.finalize() + if clean_tail: + chunk = { + "choices": [{"index": 0, "delta": {"content": clean_tail}, "finish_reason": None}] + } + if stream_id is not None: + chunk["id"] = stream_id + if created is not None: + chunk["created"] = created + if model_name is not None: + chunk["model"] = model_name + out = f"data: {json.dumps(chunk, separators=(',', ':'))}\n\n" + self.wfile.write(out.encode("utf-8")) + + if acc_text_parts: + tool_calls, _clean = _extract_tool_calls_and_clean("".join(acc_text_parts)) + if tool_calls: + tc_list = [] + for i, tc in enumerate(tool_calls): + name = str(tc.get("tool", "")) + args = tc.get("args", {}) + if not isinstance(args, dict): + args = {"_raw": str(args)} + tc_list.append( + { + "id": f"call_{i+1}", + "type": "function", + "index": i, + "function": {"name": name, "arguments": json.dumps(args, separators=(',', ':'))}, + } + ) + tc_chunk = { + "choices": [{"index": 0, "delta": {"tool_calls": tc_list}, "finish_reason": None}] + } + if stream_id is not None: + tc_chunk["id"] = stream_id + if created is not None: + tc_chunk["created"] = created + if model_name is not None: + tc_chunk["model"] = model_name + out = f"data: {json.dumps(tc_chunk, separators=(',', ':'))}\n\n" + self.wfile.write(out.encode("utf-8")) + seen_finish_reason = "tool_calls" + + if seen_finish_reason is None: + fin = { + "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}] + } + if stream_id is not None: + fin["id"] = stream_id + if created is not None: + fin["created"] = created + if model_name is not None: + fin["model"] = model_name + out = f"data: {json.dumps(fin, separators=(',', ':'))}\n\n" + self.wfile.write(out.encode("utf-8")) + if include_think_tags and state.get("thinking_open", False): close_evt = { "choices": [{"delta": {"content": "\n\n"}, "index": 0}] @@ -1083,8 +1363,41 @@ def do_POST(self): break try: evt = json.loads(payload) + if stream_id is None and isinstance(evt, dict): + stream_id = evt.get("id") + created = evt.get("created") + model_name = evt.get("model") if include_think_tags: evt = _inject_reasoning_into_chunk(evt, state) + else: + # OpenAI-style proxy field for reasoning deltas. + choices = evt.get("choices") + if isinstance(choices, list) and choices: + c0 = choices[0] + if isinstance(c0, dict): + delta = c0.get("delta") + if isinstance(delta, dict) and isinstance(delta.get("reasoning"), str): + delta["reasoning_content"] = delta.pop("reasoning") + choices = evt.get("choices") + if isinstance(choices, list) and choices: + c0 = choices[0] + if isinstance(c0, dict): + fr = c0.get("finish_reason") + mapped_fr = _normalize_finish_reason(fr) if fr is not None else None + if fr is not None: + c0["finish_reason"] = mapped_fr + seen_finish_reason = mapped_fr + delta = c0.get("delta") + if isinstance(delta, dict): + content = delta.get("content") + if isinstance(content, str) and content: + acc_text_parts.append(content) + filtered = tool_filter.feed(content) + if filtered != content: + if filtered: + delta["content"] = filtered + else: + delta.pop("content", None) out = f"data: {json.dumps(evt, separators=(',', ':'))}\n\n" except Exception: out = f"{line}\n\n" @@ -1108,6 +1421,74 @@ def do_POST(self): try: parsed = json.loads(content.decode("utf-8")) parsed = _inject_reasoning_into_response(parsed) + choices = parsed.get("choices") + if isinstance(choices, list) and choices: + c0 = choices[0] + if isinstance(c0, dict): + msg = c0.get("message") + if isinstance(msg, dict): + msg_content = msg.get("content") + if isinstance(msg_content, str): + tool_calls, cleaned = _extract_tool_calls_and_clean(msg_content) + if tool_calls: + tc_list = [] + for i, tc in enumerate(tool_calls): + name = str(tc.get("tool", "")) + args = tc.get("args", {}) + if not isinstance(args, dict): + args = {"_raw": str(args)} + tc_list.append( + { + "id": f"call_{i+1}", + "type": "function", + "function": {"name": name, "arguments": json.dumps(args, separators=(',', ':'))}, + } + ) + msg["tool_calls"] = tc_list + msg["content"] = cleaned if cleaned else None + c0["finish_reason"] = "tool_calls" + fr = c0.get("finish_reason") + c0["finish_reason"] = _normalize_finish_reason(fr) if fr is not None else None + usage = parsed.get("usage") + if isinstance(usage, dict): + parsed["usage"] = _normalize_usage_dict(usage) + content = json.dumps(parsed).encode("utf-8") + except Exception: + pass + elif mapped == "/if2/v1/chat/completions" and "application/json" in resp.headers.get("content-type", ""): + try: + parsed = json.loads(content.decode("utf-8")) + choices = parsed.get("choices") + if isinstance(choices, list) and choices: + c0 = choices[0] + if isinstance(c0, dict): + msg = c0.get("message") + if isinstance(msg, dict): + msg_content = msg.get("content") + if isinstance(msg_content, str): + tool_calls, cleaned = _extract_tool_calls_and_clean(msg_content) + if tool_calls: + tc_list = [] + for i, tc in enumerate(tool_calls): + name = str(tc.get("tool", "")) + args = tc.get("args", {}) + if not isinstance(args, dict): + args = {"_raw": str(args)} + tc_list.append( + { + "id": f"call_{i+1}", + "type": "function", + "function": {"name": name, "arguments": json.dumps(args, separators=(',', ':'))}, + } + ) + msg["tool_calls"] = tc_list + msg["content"] = cleaned if cleaned else None + c0["finish_reason"] = "tool_calls" + fr = c0.get("finish_reason") + c0["finish_reason"] = _normalize_finish_reason(fr) if fr is not None else None + usage = parsed.get("usage") + if isinstance(usage, dict): + parsed["usage"] = _normalize_usage_dict(usage) content = json.dumps(parsed).encode("utf-8") except Exception: pass From 7e18c4297076d0ab13a1ccb6323caec164337f8f Mon Sep 17 00:00:00 2001 From: notabd7-deepshard Date: Sat, 7 Mar 2026 23:15:14 -0800 Subject: [PATCH 9/9] good to go --- truffile/cli.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/truffile/cli.py b/truffile/cli.py index 045c448..ca489aa 100644 --- a/truffile/cli.py +++ b/truffile/cli.py @@ -704,7 +704,7 @@ def cmd_list(args, storage: StorageService) -> int: async def cmd_models(storage: StorageService) -> int: - """List IF2 models on the connected device.""" + """List models on your Truffle.""" device = storage.state.last_used_device if not device: error("No device connected") @@ -1667,9 +1667,9 @@ def print_help(): print(f" {C.BLUE}validate{C.RESET} [path] Validate app config and files") print(f" {C.BLUE}delete{C.RESET} Delete installed apps from device") print(f" {C.BLUE}list{C.RESET} List installed apps or devices") - print(f" {C.BLUE}models{C.RESET} List IF2 models on connected device") - print(f" {C.BLUE}chat{C.RESET} [prompt] Chat with IF2 model on connected device") - print(f" {C.BLUE}proxy{C.RESET} Run OpenAI-compatible IF2 proxy") + print(f" {C.BLUE}models{C.RESET} List models on your Truffle") + print(f" {C.BLUE}chat{C.RESET} [prompt] Chat with any model on your Truffle") + print(f" {C.BLUE}proxy{C.RESET} Run OpenAI-compatible proxy") print() print(f"{C.BOLD}Examples:{C.RESET}") print(f" {C.DIM}truffile scan{C.RESET} {C.DIM}# find devices on network{C.RESET}") @@ -1679,8 +1679,8 @@ def print_help(): print(f" {C.DIM}truffile deploy{C.RESET} {C.DIM}# uses current directory{C.RESET}") print(f" {C.DIM}truffile validate ./my-app{C.RESET}") print(f" {C.DIM}truffile list apps{C.RESET}") - print(f" {C.DIM}truffile models{C.RESET} {C.DIM}# show IF2 models{C.RESET}") - print(f" {C.DIM}truffile chat \"hello\"{C.RESET} {C.DIM}# run IF2 chat completion{C.RESET}") + print(f" {C.DIM}truffile models{C.RESET} {C.DIM}# show models on your Truffle{C.RESET}") + print(f" {C.DIM}truffile chat \"hello\"{C.RESET} {C.DIM}# run chat completion on your Truffle{C.RESET}") print(f" {C.DIM}truffile proxy{C.RESET} {C.DIM}# run local /v1 proxy{C.RESET}") print()