iatoolkit 2.59.0__tar.gz → 2.60.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. {iatoolkit-2.59.0/src/iatoolkit.egg-info → iatoolkit-2.60.0}/PKG-INFO +1 -1
  2. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/__init__.py +1 -1
  3. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_tools_pack.yaml +87 -0
  4. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/core.py +4 -0
  5. iatoolkit-2.60.0/src/iatoolkit/infra/connectors/google_drive_connector.py +308 -0
  6. iatoolkit-2.60.0/src/iatoolkit/repositories/memory_question_repo.py +121 -0
  7. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/models.py +62 -0
  8. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/context_builder_service.py +3 -1
  9. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/knowledge_base_service.py +158 -0
  10. iatoolkit-2.60.0/src/iatoolkit/services/memory_question_service.py +449 -0
  11. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/tool_service.py +40 -0
  12. {iatoolkit-2.59.0 → iatoolkit-2.60.0/src/iatoolkit.egg-info}/PKG-INFO +1 -1
  13. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit.egg-info/SOURCES.txt +2 -0
  14. iatoolkit-2.59.0/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -118
  15. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/LICENSE +0 -0
  16. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/LICENSE_COMMUNITY.md +0 -0
  17. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/pyproject.toml +0 -0
  18. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/readme.md +0 -0
  19. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/setup.cfg +0 -0
  20. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/base_company.py +0 -0
  21. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/cli_commands.py +0 -0
  22. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/__init__.py +0 -0
  23. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/bridge_validation.py +0 -0
  24. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/company_paths.py +0 -0
  25. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/exceptions.py +0 -0
  26. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/interfaces/__init__.py +0 -0
  27. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
  28. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
  29. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
  30. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
  31. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
  32. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
  33. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/model_registry.py +0 -0
  34. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/routes.py +0 -0
  35. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/secret_resolver.py +0 -0
  36. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/session_manager.py +0 -0
  37. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/text_splitter.py +0 -0
  38. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/url_safety.py +0 -0
  39. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/common/util.py +0 -0
  40. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/company_registry.py +0 -0
  41. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/__init__.py +0 -0
  42. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
  43. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
  44. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
  45. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
  46. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
  47. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
  48. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
  49. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
  50. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
  51. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
  52. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
  53. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
  54. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
  55. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
  56. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
  57. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
  58. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
  59. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
  60. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
  61. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
  62. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
  63. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
  64. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
  65. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
  66. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
  67. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/sql_rules.prompt +0 -0
  68. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
  69. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
  70. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/__init__.py +0 -0
  71. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
  72. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/call_service.py +0 -0
  73. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
  74. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
  75. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
  76. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
  77. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
  78. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
  79. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/google_auth_client.py +0 -0
  80. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
  81. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
  82. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/inference_handler.py +0 -0
  83. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
  84. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
  85. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
  86. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
  87. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
  88. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
  89. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
  90. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
  91. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
  92. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
  93. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/llm_response.py +0 -0
  94. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
  95. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/locales/en.yaml +0 -0
  96. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/locales/es.yaml +0 -0
  97. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/__init__.py +0 -0
  98. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/api_key_repo.py +0 -0
  99. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/bridge_repo.py +0 -0
  100. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/database_manager.py +0 -0
  101. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/document_repo.py +0 -0
  102. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
  103. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
  104. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/knowledge_wiki_repo.py +0 -0
  105. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
  106. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
  107. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/memory_repo.py +0 -0
  108. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/profile_repo.py +0 -0
  109. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
  110. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
  111. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
  112. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/repositories/vs_repo.py +0 -0
  113. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/runtime_logging.py +0 -0
  114. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/__init__.py +0 -0
  115. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/api_key_service.py +0 -0
  116. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/attachment_policy_service.py +0 -0
  117. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/auth_service.py +0 -0
  118. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/branding_service.py +0 -0
  119. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/company_context_service.py +0 -0
  120. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/configuration_service.py +0 -0
  121. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/dispatcher_service.py +0 -0
  122. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/embedding_service.py +0 -0
  123. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/excel_service.py +0 -0
  124. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/file_processor_service.py +0 -0
  125. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/history_manager_service.py +0 -0
  126. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/http_tool_service.py +0 -0
  127. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/i18n_service.py +0 -0
  128. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/inference_service.py +0 -0
  129. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/jwt_service.py +0 -0
  130. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/language_service.py +0 -0
  131. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/license_service.py +0 -0
  132. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/llm_client_service.py +0 -0
  133. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/mail_service.py +0 -0
  134. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
  135. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/mcp_token_service.py +0 -0
  136. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/memory_compiler_service.py +0 -0
  137. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
  138. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/memory_service.py +0 -0
  139. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/memory_wiki_service.py +0 -0
  140. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
  141. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/operator_identity_service.py +0 -0
  142. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/__init__.py +0 -0
  143. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/contracts.py +0 -0
  144. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
  145. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
  146. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
  147. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
  148. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
  149. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
  150. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
  151. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
  152. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/parsers/validator.py +0 -0
  153. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/pdf_service.py +0 -0
  154. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/profile_service.py +0 -0
  155. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/prompt_resource_service.py +0 -0
  156. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/prompt_service.py +0 -0
  157. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/query_service.py +0 -0
  158. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
  159. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/sql_dataset_service.py +0 -0
  160. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/sql_service.py +0 -0
  161. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/sql_source_service.py +0 -0
  162. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/storage_service.py +0 -0
  163. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/structured_output_service.py +0 -0
  164. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
  165. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/system_tools.py +0 -0
  166. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/telemetry_service.py +0 -0
  167. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/tenant_wiki_service.py +0 -0
  168. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/tool_output_contract.py +0 -0
  169. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
  170. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
  171. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/visual_kb_service.py +0 -0
  172. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/visual_tool_service.py +0 -0
  173. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/warmup_service.py +0 -0
  174. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/web_search/__init__.py +0 -0
  175. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
  176. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
  177. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
  178. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/services/web_search_service.py +0 -0
  179. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/images/favicon.ico +0 -0
  180. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/images/favicon.png +0 -0
  181. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
  182. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
  183. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
  184. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
  185. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
  186. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_header_navigation.js +0 -0
  187. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_help_content.js +0 -0
  188. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_history_button.js +0 -0
  189. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
  190. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_main.js +0 -0
  191. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
  192. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
  193. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
  194. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
  195. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
  196. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/styles/account.css +0 -0
  197. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
  198. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
  199. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/styles/chat_public.css +0 -0
  200. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/styles/documents.css +0 -0
  201. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
  202. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
  203. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
  204. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/_chat_header.html +0 -0
  205. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/_company_header.html +0 -0
  206. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/_login_widget.html +0 -0
  207. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/account.html +0 -0
  208. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/base.html +0 -0
  209. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/change_password.html +0 -0
  210. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/chat.html +0 -0
  211. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
  212. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/chat_modals.html +0 -0
  213. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/error.html +0 -0
  214. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/forgot_password.html +0 -0
  215. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/home_hosted_default.html +0 -0
  216. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/login_global.html +0 -0
  217. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
  218. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
  219. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/pdf/base.html +0 -0
  220. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/pdf/letter.html +0 -0
  221. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/pdf/report.html +0 -0
  222. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/pdf/simple.html +0 -0
  223. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/templates/signup.html +0 -0
  224. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/__init__.py +0 -0
  225. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/account_view.py +0 -0
  226. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/api_key_api_view.py +0 -0
  227. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/base_login_view.py +0 -0
  228. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/categories_api_view.py +0 -0
  229. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/change_password_view.py +0 -0
  230. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/chat_context_preview_api_view.py +0 -0
  231. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/chat_view.py +0 -0
  232. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/configuration_api_view.py +0 -0
  233. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/connectors_api_view.py +0 -0
  234. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/embedding_api_view.py +0 -0
  235. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
  236. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/help_content_api_view.py +0 -0
  237. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/history_api_view.py +0 -0
  238. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/home_view.py +0 -0
  239. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
  240. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
  241. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/load_document_api_view.py +0 -0
  242. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/login_view.py +0 -0
  243. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/logout_api_view.py +0 -0
  244. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/memory_api_view.py +0 -0
  245. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/profile_api_view.py +0 -0
  246. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/prompt_api_view.py +0 -0
  247. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/prompt_context_preview_api_view.py +0 -0
  248. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/prompt_resource_api_view.py +0 -0
  249. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/rag_api_view.py +0 -0
  250. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/root_redirect_view.py +0 -0
  251. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/signup_view.py +0 -0
  252. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/static_page_view.py +0 -0
  253. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/tool_api_view.py +0 -0
  254. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
  255. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/users_api_view.py +0 -0
  256. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit/views/verify_user_view.py +0 -0
  257. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
  258. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit.egg-info/requires.txt +0 -0
  259. {iatoolkit-2.59.0 → iatoolkit-2.60.0}/src/iatoolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 2.59.0
3
+ Version: 2.60.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- __version__ = "2.59.0"
6
+ __version__ = "2.60.0"
7
7
 
8
8
  # Expose main classes and functions at the top level of the package
9
9
 
@@ -282,6 +282,93 @@ tools:
282
282
  description: "Opcional en la práctica. Usa null cuando no necesites filtrar por metadata. No inventes filtros ni envíes [] por defecto. Ejemplo: [{'key':'doc.type','value':'invoice'},{'key':'chunk.source_type','value':'table'}]."
283
283
  required:
284
284
  - query
285
+ - function_name: iat_document_list
286
+ description: Lista los documentos de una colección de la empresa con su ficha (título, resumen, tipo, vigencia), del más reciente al más antiguo. Úsala para preguntas de catálogo ("¿qué contratos tenemos?", "¿cuál es la lista de precios vigente?", "¿qué hay en la carpeta Comercial?"), no para buscar un dato dentro de los documentos (para eso, `iat_document_search`). Los documentos reemplazados u obsoletos no aparecen salvo que lo pidas.
287
+ routing:
288
+ force_include_capability: has_kb_collections
289
+ routing_profile:
290
+ tags:
291
+ - document
292
+ - catalog
293
+ - list
294
+ - knowledge
295
+ intents:
296
+ - List the documents of a company collection with their cards.
297
+ - Find which documents exist of a given type, or which one is valid today.
298
+ examples:
299
+ - iat_document_list(collection="comercial", document_type="contrato")
300
+ - iat_document_list(collection="legal", valid_on="2026-10-14")
301
+ cost:
302
+ penalty: 0.05
303
+ latency_class: low
304
+ expensive: false
305
+ parameters:
306
+ type: object
307
+ properties:
308
+ collection:
309
+ anyOf:
310
+ - type: string
311
+ - type: "null"
312
+ description: Colección a listar (su nombre exacto). null lista todas.
313
+ document_type:
314
+ anyOf:
315
+ - type: string
316
+ - type: "null"
317
+ description: Opcional. Tipo de documento según su ficha (ej. contrato, política, lista de precios, procedimiento).
318
+ text:
319
+ anyOf:
320
+ - type: string
321
+ - type: "null"
322
+ description: Opcional. Texto a buscar en el nombre, título, resumen o palabras clave de la ficha.
323
+ valid_on:
324
+ anyOf:
325
+ - type: string
326
+ - type: "null"
327
+ description: Opcional. Fecha YYYY-MM-DD; deja los documentos vigentes ese día o sin vigencia indicada.
328
+ include_inactive:
329
+ type: boolean
330
+ description: Incluir documentos reemplazados u obsoletos. Por defecto false.
331
+ limit:
332
+ type: integer
333
+ description: Cuántos documentos devolver (máximo 100). Por defecto 30.
334
+ offset:
335
+ type: integer
336
+ description: Desde qué posición seguir (usa next_offset de la respuesta anterior).
337
+ required:
338
+ - collection
339
+ - function_name: iat_document_read
340
+ description: Lee el texto de un documento de la empresa, completo o por partes, con su ficha. Úsala cuando la respuesta necesite el documento entero (resumirlo, compararlo, revisar todas sus cláusulas) y no solo los fragmentos que trae `iat_document_search`. El document_id viene de `iat_document_search` o `iat_document_list`. Si la respuesta trae has_more=true, sigue con offset=next_offset.
341
+ routing:
342
+ force_include_capability: has_kb_collections
343
+ routing_profile:
344
+ tags:
345
+ - document
346
+ - read
347
+ - knowledge
348
+ intents:
349
+ - Read a whole company document, or a part of it, by id.
350
+ - Summarize or compare full documents.
351
+ examples:
352
+ - iat_document_read(document_id=42)
353
+ - iat_document_read(document_id=42, offset=20000)
354
+ cost:
355
+ penalty: 0.1
356
+ latency_class: medium
357
+ expensive: false
358
+ parameters:
359
+ type: object
360
+ properties:
361
+ document_id:
362
+ type: integer
363
+ description: Identificador del documento, devuelto por `iat_document_search` o `iat_document_list`.
364
+ offset:
365
+ type: integer
366
+ description: Desde qué carácter leer. Por defecto 0.
367
+ limit:
368
+ type: integer
369
+ description: Cuántos caracteres leer (máximo 60000). Por defecto 20000.
370
+ required:
371
+ - document_id
285
372
  - function_name: iat_wiki_search
286
373
  description: Busca páginas publicadas dentro de las wikis de conocimiento compartidas por la empresa. Úsala como acelerador cuando necesites encontrar rápidamente una página relevante, pero no como punto de entrada por defecto. Si no conoces un `path` válido, primero llama `iat_wiki_get_page` con `path="/"` para leer el índice raíz de la wiki y seguir sus links exactos. `wiki_key` es el identificador estable de una wiki publicada, por ejemplo `sales`, `ops` o `hr`; úsalo cuando ya sepas en qué wiki buscar y omítelo cuando quieras buscar en todas las wikis publicadas disponibles en esta conexión MCP. Esta tool devuelve candidatos; luego usa `iat_wiki_get_page` para leer la página completa antes de responder.
287
374
  routing:
@@ -392,6 +392,7 @@ class IAToolkit:
392
392
  from iatoolkit.repositories.profile_repo import ProfileRepo
393
393
  from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
394
394
  from iatoolkit.repositories.memory_repo import MemoryRepo
395
+ from iatoolkit.repositories.memory_question_repo import MemoryQuestionRepo
395
396
  from iatoolkit.repositories.mcp_token_repo import McpTokenRepo
396
397
  from iatoolkit.repositories.sql_dataset_repo import SqlDatasetRepo
397
398
  from iatoolkit.repositories.sql_source_repo import SqlSourceRepo
@@ -407,6 +408,7 @@ class IAToolkit:
407
408
  binder.bind(ProfileRepo, to=ProfileRepo)
408
409
  binder.bind(LLMQueryRepo, to=LLMQueryRepo)
409
410
  binder.bind(MemoryRepo, to=MemoryRepo)
411
+ binder.bind(MemoryQuestionRepo, to=MemoryQuestionRepo)
410
412
  binder.bind(McpTokenRepo, to=McpTokenRepo)
411
413
  binder.bind(SqlDatasetRepo, to=SqlDatasetRepo)
412
414
  binder.bind(SqlSourceRepo, to=SqlSourceRepo)
@@ -436,6 +438,7 @@ class IAToolkit:
436
438
  from iatoolkit.services.pdf_service import PdfService
437
439
  from iatoolkit.services.mail_service import MailService
438
440
  from iatoolkit.services.memory_compiler_service import MemoryCompilerService
441
+ from iatoolkit.services.memory_question_service import MemoryQuestionService
439
442
  from iatoolkit.services.memory_service import MemoryService
440
443
  from iatoolkit.services.mcp_token_service import McpTokenService
441
444
  from iatoolkit.services.memory_wiki_service import MemoryWikiService
@@ -481,6 +484,7 @@ class IAToolkit:
481
484
  binder.bind(MailService, to=MailService)
482
485
  binder.bind(MemoryWikiService, to=MemoryWikiService)
483
486
  binder.bind(MemoryCompilerService, to=MemoryCompilerService)
487
+ binder.bind(MemoryQuestionService, to=MemoryQuestionService)
484
488
  binder.bind(MemoryService, to=MemoryService)
485
489
  binder.bind(McpTokenService, to=McpTokenService)
486
490
  binder.bind(ProfileService, to=ProfileService)
@@ -0,0 +1,308 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from __future__ import annotations
7
+
8
+ import io
9
+ from dataclasses import dataclass, field
10
+ from typing import Iterator, List
11
+
12
+ from google.oauth2.service_account import Credentials
13
+ from googleapiclient.discovery import build
14
+ from googleapiclient.errors import HttpError
15
+ from googleapiclient.http import MediaIoBaseDownload, MediaIoBaseUpload
16
+
17
+ from iatoolkit.infra.connectors.file_connector import FileConnector
18
+
19
+ SCOPE_FULL = "https://www.googleapis.com/auth/drive"
20
+ SCOPE_READ_ONLY = "https://www.googleapis.com/auth/drive.readonly"
21
+
22
+ FOLDER_MIME_TYPE = "application/vnd.google-apps.folder"
23
+ SHORTCUT_MIME_TYPE = "application/vnd.google-apps.shortcut"
24
+ GOOGLE_APPS_PREFIX = "application/vnd.google-apps."
25
+
26
+ # Google's own formats have no bytes to download: they are exported. Each one
27
+ # goes to a format the document parsers already read.
28
+ EXPORT_FORMATS = {
29
+ "application/vnd.google-apps.document": (
30
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx"),
31
+ "application/vnd.google-apps.spreadsheet": (
32
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx"),
33
+ "application/vnd.google-apps.presentation": ("application/pdf", ".pdf"),
34
+ }
35
+
36
+ FILE_FIELDS = "id, name, mimeType, parents, md5Checksum, modifiedTime, size, trashed, headRevisionId"
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class DriveFile:
41
+ """A file as Drive describes it, with its path below the folder it was found from."""
42
+ id: str
43
+ name: str
44
+ mime_type: str
45
+ path: str = ""
46
+ parents: tuple[str, ...] = ()
47
+ md5_checksum: str | None = None
48
+ modified_time: str | None = None
49
+ size: int | None = None
50
+ trashed: bool = False
51
+
52
+ @property
53
+ def is_folder(self) -> bool:
54
+ return self.mime_type == FOLDER_MIME_TYPE
55
+
56
+ @property
57
+ def is_shortcut(self) -> bool:
58
+ return self.mime_type == SHORTCUT_MIME_TYPE
59
+
60
+ @property
61
+ def is_google_native(self) -> bool:
62
+ return self.mime_type.startswith(GOOGLE_APPS_PREFIX)
63
+
64
+ @property
65
+ def exportable(self) -> bool:
66
+ return self.mime_type in EXPORT_FORMATS
67
+
68
+ @property
69
+ def fingerprint(self) -> str:
70
+ """What tells one version of the file from the next: the content hash for
71
+ uploaded files, the modification time for Google's own formats (they have
72
+ no md5)."""
73
+ return str(self.md5_checksum or self.modified_time or "")
74
+
75
+ @classmethod
76
+ def from_api(cls, payload: dict, *, path: str = "") -> "DriveFile":
77
+ size = payload.get("size")
78
+ return cls(
79
+ id=str(payload.get("id") or ""),
80
+ name=str(payload.get("name") or ""),
81
+ mime_type=str(payload.get("mimeType") or ""),
82
+ path=path,
83
+ parents=tuple(payload.get("parents") or ()),
84
+ md5_checksum=payload.get("md5Checksum"),
85
+ modified_time=payload.get("modifiedTime"),
86
+ size=int(size) if str(size or "").isdigit() else None,
87
+ trashed=bool(payload.get("trashed")),
88
+ )
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class DriveChange:
93
+ file_id: str
94
+ removed: bool
95
+ file: DriveFile | None = None
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class DriveChangesPage:
100
+ changes: list[DriveChange] = field(default_factory=list)
101
+ next_page_token: str | None = None
102
+ new_start_page_token: str | None = None
103
+
104
+
105
+ class GoogleDriveConnector(FileConnector):
106
+ PAGE_SIZE = 1000
107
+
108
+ def __init__(
109
+ self,
110
+ folder_id: str,
111
+ service_account_path: str = "service_account.json",
112
+ service_account_info: dict | None = None,
113
+ read_only: bool = False,
114
+ ):
115
+ """
116
+ Google Drive through a service account.
117
+
118
+ :param folder_id: the folder `list_files` lists and `upload_file` writes to.
119
+ :param read_only: ask only for `drive.readonly` (whoever only reads, such
120
+ as Memoria's sync, should); the default keeps the full scope that
121
+ uploads and deletes need.
122
+ """
123
+ self.folder_id = folder_id
124
+ self.service_account_path = service_account_path
125
+ self.service_account_info = service_account_info
126
+ self.scopes = [SCOPE_READ_ONLY if read_only else SCOPE_FULL]
127
+ self.drive_service = self._authenticate()
128
+
129
+ def _authenticate(self):
130
+ if self.service_account_info:
131
+ credentials = Credentials.from_service_account_info(self.service_account_info, scopes=self.scopes)
132
+ else:
133
+ credentials = Credentials.from_service_account_file(self.service_account_path, scopes=self.scopes)
134
+ return build('drive', 'v3', credentials=credentials, cache_discovery=False)
135
+
136
+ @property
137
+ def service_account_email(self) -> str | None:
138
+ info = self.service_account_info or {}
139
+ return str(info.get("client_email") or "").strip() or None
140
+
141
+ # --- FileConnector ------------------------------------------------------
142
+
143
+ def list_files(self, prefix: str | None = None) -> List[dict]:
144
+ """
145
+ Every direct child of the configured folder, as dicts with 'path' (the
146
+ file id), 'name' and 'metadata'. Follows every page of results: a folder
147
+ with more than one page used to be cut silently at the first.
148
+ """
149
+ return [
150
+ {
151
+ "path": item.id,
152
+ "name": item.name,
153
+ "metadata": {
154
+ "mime_type": item.mime_type,
155
+ "md5_checksum": item.md5_checksum,
156
+ "modified_time": item.modified_time,
157
+ "size": item.size,
158
+ },
159
+ }
160
+ for item in self.list_children(self.folder_id)
161
+ ]
162
+
163
+ def get_file_content(self, file_path: str) -> bytes:
164
+ """Bytes of a file by id (exported first if it is a Google Docs/Sheets/Slides file)."""
165
+ content, _, _ = self.download(file_path)
166
+ return content
167
+
168
+ def upload_file(self, file_path: str, content: bytes, content_type: str = None) -> None:
169
+ """
170
+ Uploads to the configured folder. Drive addresses folders by id, so
171
+ `file_path` is used as the file name.
172
+ """
173
+ file_metadata = {'name': file_path, 'parents': [self.folder_id]}
174
+ media = MediaIoBaseUpload(io.BytesIO(content), mimetype=content_type, resumable=True)
175
+ self.drive_service.files().create(body=file_metadata, media_body=media, fields='id').execute()
176
+
177
+ def delete_file(self, file_path: str) -> None:
178
+ """Deletes by file id; a file that is already gone is not an error."""
179
+ try:
180
+ self.drive_service.files().delete(fileId=file_path).execute()
181
+ except Exception:
182
+ pass
183
+
184
+ def generate_presigned_url(self, file_path: str, expiration: int = 3600) -> str:
185
+ raise NotImplementedError("not implemented yet")
186
+
187
+ # --- Reading a folder tree ---------------------------------------------
188
+
189
+ def get_file(self, file_id: str) -> DriveFile | None:
190
+ """The file or folder, or None when it does not exist or is not shared with this account."""
191
+ try:
192
+ payload = self.drive_service.files().get(
193
+ fileId=file_id,
194
+ fields=FILE_FIELDS,
195
+ supportsAllDrives=True,
196
+ ).execute()
197
+ except HttpError as exc:
198
+ if getattr(exc, "status_code", None) in (403, 404) or getattr(getattr(exc, "resp", None), "status", None) in (403, 404):
199
+ return None
200
+ raise
201
+ return DriveFile.from_api(payload)
202
+
203
+ def list_children(self, folder_id: str) -> Iterator[DriveFile]:
204
+ """Direct children of a folder (files, folders and shortcuts), every page."""
205
+ page_token = None
206
+ while True:
207
+ response = self.drive_service.files().list(
208
+ q=f"'{folder_id}' in parents and trashed=false",
209
+ fields=f"nextPageToken, files({FILE_FIELDS})",
210
+ pageSize=self.PAGE_SIZE,
211
+ pageToken=page_token,
212
+ supportsAllDrives=True,
213
+ includeItemsFromAllDrives=True,
214
+ ).execute()
215
+ for payload in response.get("files", []):
216
+ yield DriveFile.from_api(payload)
217
+ page_token = response.get("nextPageToken")
218
+ if not page_token:
219
+ return
220
+
221
+ def walk(self, folder_id: str | None = None) -> Iterator[DriveFile]:
222
+ """
223
+ Every file below a folder, with its path relative to it ("Contratos/2026").
224
+
225
+ Folders are entered, never returned. Shortcuts are skipped: following one
226
+ would bring in a file from outside the folder that was shared.
227
+ """
228
+ root = folder_id or self.folder_id
229
+ pending = [(root, "")]
230
+ seen = {root}
231
+ while pending:
232
+ current, path = pending.pop(0)
233
+ for item in self.list_children(current):
234
+ if item.is_folder:
235
+ if item.id not in seen:
236
+ seen.add(item.id)
237
+ pending.append((item.id, f"{path}/{item.name}".strip("/")))
238
+ continue
239
+ if item.is_shortcut:
240
+ continue
241
+ yield DriveFile(
242
+ id=item.id, name=item.name, mime_type=item.mime_type, path=path,
243
+ parents=item.parents, md5_checksum=item.md5_checksum,
244
+ modified_time=item.modified_time, size=item.size, trashed=item.trashed,
245
+ )
246
+
247
+ def download(self, file_id: str, mime_type: str | None = None) -> tuple[bytes, str | None, str | None]:
248
+ """
249
+ The file's bytes, the extension to give it and their MIME type. Google
250
+ Docs, Sheets and Slides are exported (docx, xlsx, pdf); other Google
251
+ formats (forms, maps) cannot be read and raise ValueError.
252
+ """
253
+ if mime_type is None:
254
+ meta = self.get_file(file_id)
255
+ mime_type = meta.mime_type if meta else ""
256
+ if str(mime_type).startswith(GOOGLE_APPS_PREFIX):
257
+ export = EXPORT_FORMATS.get(mime_type)
258
+ if not export:
259
+ raise ValueError(f"Google format '{mime_type}' cannot be exported")
260
+ export_mime, extension = export
261
+ request = self.drive_service.files().export_media(fileId=file_id, mimeType=export_mime)
262
+ return self._read(request), extension, export_mime
263
+ request = self.drive_service.files().get_media(fileId=file_id, supportsAllDrives=True)
264
+ return self._read(request), None, mime_type or None
265
+
266
+ @staticmethod
267
+ def _read(request) -> bytes:
268
+ buffer = io.BytesIO()
269
+ downloader = MediaIoBaseDownload(buffer, request)
270
+ done = False
271
+ while not done:
272
+ _, done = downloader.next_chunk()
273
+ return buffer.getvalue()
274
+
275
+ # --- Changes ------------------------------------------------------------
276
+
277
+ def get_start_page_token(self) -> str:
278
+ """Where to start reading changes from: everything after now."""
279
+ response = self.drive_service.changes().getStartPageToken(supportsAllDrives=True).execute()
280
+ return str(response.get("startPageToken") or "")
281
+
282
+ def list_changes(self, page_token: str) -> DriveChangesPage:
283
+ """
284
+ One page of what changed since `page_token`, for everything this account
285
+ can see. When `new_start_page_token` is set the caller is up to date and
286
+ stores it; otherwise it keeps reading with `next_page_token`.
287
+ """
288
+ response = self.drive_service.changes().list(
289
+ pageToken=page_token,
290
+ pageSize=self.PAGE_SIZE,
291
+ fields=f"nextPageToken, newStartPageToken, changes(fileId, removed, file({FILE_FIELDS}))",
292
+ supportsAllDrives=True,
293
+ includeItemsFromAllDrives=True,
294
+ spaces="drive",
295
+ ).execute()
296
+ changes = [
297
+ DriveChange(
298
+ file_id=str(change.get("fileId") or ""),
299
+ removed=bool(change.get("removed")),
300
+ file=DriveFile.from_api(change["file"]) if isinstance(change.get("file"), dict) else None,
301
+ )
302
+ for change in response.get("changes", [])
303
+ ]
304
+ return DriveChangesPage(
305
+ changes=changes,
306
+ next_page_token=response.get("nextPageToken"),
307
+ new_start_page_token=response.get("newStartPageToken"),
308
+ )
@@ -0,0 +1,121 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from __future__ import annotations
7
+
8
+ from injector import inject
9
+ from sqlalchemy import desc
10
+
11
+ from iatoolkit.repositories.database_manager import DatabaseManager
12
+ from iatoolkit.repositories.models import (
13
+ LLMQuery,
14
+ MemoryQuestion,
15
+ MemoryQuestionCandidate,
16
+ MemoryQuestionScanCursor,
17
+ )
18
+
19
+
20
+ class MemoryQuestionRepo:
21
+ """Memoria's unanswered questions (BL-005, Fase 1) and what the scan reads."""
22
+
23
+ @inject
24
+ def __init__(self, db_manager: DatabaseManager):
25
+ self.session = db_manager.get_session()
26
+
27
+ def commit(self) -> None:
28
+ try:
29
+ self.session.commit()
30
+ except Exception:
31
+ self.session.rollback()
32
+ raise
33
+
34
+ def rollback(self) -> None:
35
+ self.session.rollback()
36
+
37
+ def add(self, entity):
38
+ self.session.add(entity)
39
+ return entity
40
+
41
+ # --- questions -------------------------------------------------------------
42
+
43
+ def list_questions(self, company_id: int, status: str | None = MemoryQuestion.STATUS_OPEN,
44
+ limit: int = 200) -> list[MemoryQuestion]:
45
+ query = self.session.query(MemoryQuestion).filter(MemoryQuestion.company_id == company_id)
46
+ if status:
47
+ query = query.filter(MemoryQuestion.status == status)
48
+ return (
49
+ query.order_by(desc(MemoryQuestion.times_asked), desc(MemoryQuestion.last_asked_at), desc(MemoryQuestion.id))
50
+ .limit(limit)
51
+ .all()
52
+ )
53
+
54
+ def get_question(self, company_id: int, question_id: int) -> MemoryQuestion | None:
55
+ return (
56
+ self.session.query(MemoryQuestion)
57
+ .filter(MemoryQuestion.company_id == company_id, MemoryQuestion.id == question_id)
58
+ .first()
59
+ )
60
+
61
+ def count_open(self, company_id: int) -> int:
62
+ return (
63
+ self.session.query(MemoryQuestion)
64
+ .filter(MemoryQuestion.company_id == company_id, MemoryQuestion.status == MemoryQuestion.STATUS_OPEN)
65
+ .count()
66
+ )
67
+
68
+ # --- what the scan reads ---------------------------------------------------
69
+
70
+ def get_cursor(self, company_id: int) -> MemoryQuestionScanCursor:
71
+ cursor = (
72
+ self.session.query(MemoryQuestionScanCursor)
73
+ .filter(MemoryQuestionScanCursor.company_id == company_id)
74
+ .first()
75
+ )
76
+ if cursor is None:
77
+ cursor = MemoryQuestionScanCursor(company_id=company_id, last_query_id=0)
78
+ self.session.add(cursor)
79
+ self.commit()
80
+ return cursor
81
+
82
+ def list_queries_after(self, company_id: int, last_query_id: int, limit: int) -> list[LLMQuery]:
83
+ return (
84
+ self.session.query(LLMQuery)
85
+ .filter(LLMQuery.company_id == company_id, LLMQuery.id > int(last_query_id or 0))
86
+ .order_by(LLMQuery.id.asc())
87
+ .limit(limit)
88
+ .all()
89
+ )
90
+
91
+ def has_queries_after(self, company_id: int, last_query_id: int) -> bool:
92
+ return (
93
+ self.session.query(LLMQuery.id)
94
+ .filter(LLMQuery.company_id == company_id, LLMQuery.id > int(last_query_id or 0))
95
+ .first()
96
+ is not None
97
+ )
98
+
99
+ def find_cursor(self, company_id: int) -> MemoryQuestionScanCursor | None:
100
+ return (
101
+ self.session.query(MemoryQuestionScanCursor)
102
+ .filter(MemoryQuestionScanCursor.company_id == company_id)
103
+ .first()
104
+ )
105
+
106
+ def list_pending_candidates(self, company_id: int, limit: int) -> list[MemoryQuestionCandidate]:
107
+ return (
108
+ self.session.query(MemoryQuestionCandidate)
109
+ .filter(MemoryQuestionCandidate.company_id == company_id, MemoryQuestionCandidate.processed_at.is_(None))
110
+ .order_by(MemoryQuestionCandidate.id.asc())
111
+ .limit(limit)
112
+ .all()
113
+ )
114
+
115
+ def has_pending_candidates(self, company_id: int) -> bool:
116
+ return (
117
+ self.session.query(MemoryQuestionCandidate.id)
118
+ .filter(MemoryQuestionCandidate.company_id == company_id, MemoryQuestionCandidate.processed_at.is_(None))
119
+ .first()
120
+ is not None
121
+ )
@@ -564,6 +564,68 @@ class MemoryWikiPageSource(Base):
564
564
  return {column.key: getattr(self, column.key) for column in class_mapper(self.__class__).columns}
565
565
 
566
566
 
567
+ class MemoryQuestion(Base):
568
+ """
569
+ A question about the company that the AI could not answer (BL-005, Fase 1).
570
+
571
+ Written neutrally and grouped: the same question asked again only adds one
572
+ to `times_asked`. Who asked is never kept; the count is enough to rank them.
573
+ Answering it saves a normal Memoria capture, attributed to whoever answered.
574
+ """
575
+ __tablename__ = "iat_memory_questions"
576
+
577
+ STATUS_OPEN = "open"
578
+ STATUS_ANSWERED = "answered"
579
+ STATUS_DISMISSED = "dismissed"
580
+
581
+ id = Column(Integer, primary_key=True, autoincrement=True)
582
+ company_id = Column(Integer, ForeignKey(f"{ORM_SCHEMA}.iat_companies.id", ondelete="CASCADE"), nullable=False, index=True)
583
+ question = Column(Text, nullable=False)
584
+ topic = Column(String(200), nullable=True)
585
+ times_asked = Column(Integer, nullable=False, default=1)
586
+ # How many times each channel brought it: {"chat": 3, "mcp": 1}.
587
+ channels = Column(JSON_NATIVE, nullable=True)
588
+ first_asked_at = Column(DateTime, default=datetime.now, nullable=False)
589
+ last_asked_at = Column(DateTime, default=datetime.now, nullable=False)
590
+ status = Column(String(16), nullable=False, default=STATUS_OPEN, index=True)
591
+ answer_text = Column(Text, nullable=True)
592
+ answer_capture_id = Column(Integer, ForeignKey(f"{ORM_SCHEMA}.iat_memory_captures.id", ondelete="SET NULL"), nullable=True)
593
+ resolved_by = Column(String, nullable=True)
594
+ resolved_at = Column(DateTime, nullable=True)
595
+ created_at = Column(DateTime, default=datetime.now, nullable=False)
596
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
597
+
598
+ def to_dict(self):
599
+ return {column.key: getattr(self, column.key) for column in class_mapper(self.__class__).columns}
600
+
601
+
602
+ class MemoryQuestionCandidate(Base):
603
+ """
604
+ A Memoria search that found nothing outside the chat (MCP: Claude, ChatGPT),
605
+ whose questions never reach `iat_queries`. The next scan classifies it with
606
+ the chat's turns. Only the text is kept, not who searched.
607
+ """
608
+ __tablename__ = "iat_memory_question_candidates"
609
+
610
+ id = Column(Integer, primary_key=True, autoincrement=True)
611
+ company_id = Column(Integer, ForeignKey(f"{ORM_SCHEMA}.iat_companies.id", ondelete="CASCADE"), nullable=False, index=True)
612
+ query_text = Column(Text, nullable=False)
613
+ source = Column(String(16), nullable=False, default="mcp")
614
+ processed_at = Column(DateTime, nullable=True, index=True)
615
+ created_at = Column(DateTime, default=datetime.now, nullable=False)
616
+
617
+
618
+ class MemoryQuestionScanCursor(Base):
619
+ """Up to which `iat_queries.id` a company's turns were already reviewed for unanswered questions."""
620
+ __tablename__ = "iat_memory_question_scan_cursors"
621
+
622
+ id = Column(Integer, primary_key=True, autoincrement=True)
623
+ company_id = Column(Integer, ForeignKey(f"{ORM_SCHEMA}.iat_companies.id", ondelete="CASCADE"), nullable=False, unique=True)
624
+ last_query_id = Column(Integer, nullable=False, default=0)
625
+ last_scan_at = Column(DateTime, nullable=True)
626
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
627
+
628
+
567
629
  #: request_source of the iat_queries row that records a context initialization:
568
630
  #: the company context sent once to start a server-side conversation. Its tokens
569
631
  #: are billed, but it is not a question, so conversation readers skip it.
@@ -481,8 +481,10 @@ class ContextBuilderService:
481
481
  lines = [
482
482
  "## Colecciones documentales disponibles",
483
483
  "Usa `iat_document_search` cuando documentos internos de la empresa puedan ayudar a responder al usuario.",
484
+ "Usa `iat_document_list` para saber qué documentos hay en una colección y `iat_document_read` para leer uno completo.",
484
485
  "### Colecciones",
485
486
  ]
487
+ header_lines = len(lines)
486
488
 
487
489
  for name, description in selected_entries[:20]:
488
490
  if description:
@@ -490,7 +492,7 @@ class ContextBuilderService:
490
492
  else:
491
493
  lines.append(f"- {name}")
492
494
 
493
- if len(lines) <= 3:
495
+ if len(lines) <= header_lines:
494
496
  return ""
495
497
 
496
498
  lines.append(