tina4-python 3.13.113__tar.gz → 3.13.115__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.
- {tina4_python-3.13.113 → tina4_python-3.13.115}/PKG-INFO +1 -1
- {tina4_python-3.13.113 → tina4_python-3.13.115}/pyproject.toml +1 -1
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/ai/client.py +367 -26
- {tina4_python-3.13.113 → tina4_python-3.13.115}/.gitignore +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/README.md +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/port_takeover.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/database_url.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/sql_translator.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/env.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/frond/parser.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/graph/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/graph/adapter.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/graph/adapters/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/graph/adapters/arango.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/graph/adapters/bolt.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/graph/adapters/ultipa.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/graph/graph_url.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/mqtt/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/orm/point.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue/amqp_url.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/memcached_handler.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/sso.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -110,16 +110,45 @@ class Ai:
|
|
|
110
110
|
stream: bool = False,
|
|
111
111
|
timeout: float | None = None,
|
|
112
112
|
provider: str | None = None,
|
|
113
|
+
tools: list[dict[str, Any]] | None = None,
|
|
114
|
+
tool_choice: str | dict[str, Any] | None = None,
|
|
113
115
|
) -> "ChatResponse | Iterator[AiEvent]":
|
|
114
116
|
"""Send a chat completion request.
|
|
115
117
|
|
|
116
118
|
``stream=True`` returns an iterator of ``AiEvent`` records
|
|
117
119
|
(``text_delta`` / ``tool_call`` / ``done`` / ``error``) per
|
|
118
120
|
ADR-0060; ``stream=False`` returns a single ``ChatResponse``.
|
|
121
|
+
|
|
122
|
+
``tools`` (ADR-0061) is an optional list of neutral tool
|
|
123
|
+
declarations - each ``{"name", "description", "parameters"}``
|
|
124
|
+
where ``parameters`` is a JSON Schema object. The client
|
|
125
|
+
translates the list to the current provider's outbound body:
|
|
126
|
+
OpenAI/local gets ``[{"type": "function", "function":
|
|
127
|
+
{"name", "description", "parameters"}}]``; Anthropic gets
|
|
128
|
+
``[{"name", "description", "input_schema"}]``.
|
|
129
|
+
|
|
130
|
+
``tool_choice`` (ADR-0061) is one of ``"auto"``, ``"none"``,
|
|
131
|
+
``"required"``, or ``{"name": str}``. The client translates
|
|
132
|
+
each value to the provider's shape (OpenAI's ``tool_choice``
|
|
133
|
+
keyword; Anthropic's ``tool_choice`` object). On Anthropic
|
|
134
|
+
``"none"`` the ``tools`` field is omitted entirely (Anthropic
|
|
135
|
+
has no ``"none"`` mode).
|
|
136
|
+
|
|
137
|
+
Messages may carry a tool result in either OpenAI form
|
|
138
|
+
(``{"role": "tool", "tool_call_id", "content"}``) or
|
|
139
|
+
Anthropic form (a user turn with ``{"type": "tool_result",
|
|
140
|
+
"tool_use_id", "content"}`` content parts). The client
|
|
141
|
+
normalises to whichever the current provider expects, so the
|
|
142
|
+
agent loop stays provider-neutral.
|
|
119
143
|
"""
|
|
120
144
|
Ai._validate_messages(messages)
|
|
145
|
+
Ai._validate_tools(tools)
|
|
146
|
+
Ai._validate_tool_choice(tool_choice)
|
|
121
147
|
config = Ai._config("chat", model=model, timeout=timeout, provider=provider)
|
|
122
|
-
body = Ai._chat_body(
|
|
148
|
+
body = Ai._chat_body(
|
|
149
|
+
config, messages, temperature, max_tokens, stream,
|
|
150
|
+
tools=tools, tool_choice=tool_choice,
|
|
151
|
+
)
|
|
123
152
|
headers = Ai._headers(config)
|
|
124
153
|
if stream:
|
|
125
154
|
return Ai._stream(config, headers, body)
|
|
@@ -179,18 +208,103 @@ class Ai:
|
|
|
179
208
|
for message in messages:
|
|
180
209
|
if not isinstance(message, dict):
|
|
181
210
|
raise AiConfigError("Each AI message must be an object")
|
|
182
|
-
|
|
211
|
+
role = message.get("role")
|
|
212
|
+
if role not in {"system", "user", "assistant", "tool"}:
|
|
183
213
|
raise AiConfigError("Each AI message needs a supported role")
|
|
214
|
+
if role == "tool":
|
|
215
|
+
# ADR-0061: OpenAI-style tool-result message.
|
|
216
|
+
tool_call_id = message.get("tool_call_id")
|
|
217
|
+
if not isinstance(tool_call_id, str) or not tool_call_id:
|
|
218
|
+
raise AiConfigError(
|
|
219
|
+
"tool message needs a non-empty 'tool_call_id' string")
|
|
220
|
+
if not isinstance(message.get("content"), str):
|
|
221
|
+
raise AiConfigError("tool message needs a string 'content'")
|
|
222
|
+
continue
|
|
223
|
+
if role == "assistant" and message.get("tool_calls") is not None:
|
|
224
|
+
# OpenAI-style assistant tool_calls; content may be None/absent.
|
|
225
|
+
Ai._validate_tool_calls(message["tool_calls"])
|
|
226
|
+
content = message.get("content")
|
|
227
|
+
if content is not None:
|
|
228
|
+
Ai._validate_content(content)
|
|
229
|
+
continue
|
|
184
230
|
Ai._validate_content(message.get("content"))
|
|
185
231
|
|
|
232
|
+
@staticmethod
|
|
233
|
+
def _validate_tool_calls(tool_calls: Any) -> None:
|
|
234
|
+
"""Validate an OpenAI-style assistant.tool_calls list (ADR-0061)."""
|
|
235
|
+
if not isinstance(tool_calls, list) or not tool_calls:
|
|
236
|
+
raise AiConfigError("assistant tool_calls must be a non-empty list")
|
|
237
|
+
for tc in tool_calls:
|
|
238
|
+
if not isinstance(tc, dict):
|
|
239
|
+
raise AiConfigError("Each tool_call must be an object")
|
|
240
|
+
if not isinstance(tc.get("id"), str) or not tc["id"]:
|
|
241
|
+
raise AiConfigError(
|
|
242
|
+
"tool_call needs a non-empty 'id' string")
|
|
243
|
+
fn = tc.get("function")
|
|
244
|
+
if not isinstance(fn, dict):
|
|
245
|
+
raise AiConfigError("tool_call needs a 'function' object")
|
|
246
|
+
if not isinstance(fn.get("name"), str) or not fn["name"]:
|
|
247
|
+
raise AiConfigError(
|
|
248
|
+
"tool_call function needs a non-empty 'name' string")
|
|
249
|
+
args = fn.get("arguments")
|
|
250
|
+
if not isinstance(args, (str, dict)):
|
|
251
|
+
raise AiConfigError(
|
|
252
|
+
"tool_call function 'arguments' must be a JSON string or object")
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def _validate_tools(tools: Any) -> None:
|
|
256
|
+
"""Validate an outbound tools list (ADR-0061)."""
|
|
257
|
+
if tools is None:
|
|
258
|
+
return
|
|
259
|
+
if not isinstance(tools, list) or not tools:
|
|
260
|
+
raise AiConfigError(
|
|
261
|
+
"AI tools must be a non-empty list when provided")
|
|
262
|
+
for tool in tools:
|
|
263
|
+
if not isinstance(tool, dict):
|
|
264
|
+
raise AiConfigError("Each AI tool must be an object")
|
|
265
|
+
name = tool.get("name")
|
|
266
|
+
if not isinstance(name, str) or not name:
|
|
267
|
+
raise AiConfigError(
|
|
268
|
+
"Each AI tool needs a non-empty 'name' string")
|
|
269
|
+
description = tool.get("description")
|
|
270
|
+
if description is not None and not isinstance(description, str):
|
|
271
|
+
raise AiConfigError(
|
|
272
|
+
"AI tool 'description' must be a string when provided")
|
|
273
|
+
params = tool.get("parameters")
|
|
274
|
+
if params is not None and not isinstance(params, dict):
|
|
275
|
+
raise AiConfigError(
|
|
276
|
+
"AI tool 'parameters' must be a JSON Schema object")
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def _validate_tool_choice(tool_choice: Any) -> None:
|
|
280
|
+
"""Validate a tool_choice value (ADR-0061)."""
|
|
281
|
+
if tool_choice is None:
|
|
282
|
+
return
|
|
283
|
+
if isinstance(tool_choice, str):
|
|
284
|
+
if tool_choice not in ("auto", "none", "required"):
|
|
285
|
+
raise AiConfigError(
|
|
286
|
+
"AI tool_choice string must be 'auto', 'none', or 'required'")
|
|
287
|
+
return
|
|
288
|
+
if isinstance(tool_choice, dict):
|
|
289
|
+
name = tool_choice.get("name")
|
|
290
|
+
if not isinstance(name, str) or not name:
|
|
291
|
+
raise AiConfigError(
|
|
292
|
+
"AI tool_choice dict needs a non-empty 'name' string")
|
|
293
|
+
return
|
|
294
|
+
raise AiConfigError("AI tool_choice must be a string or dict")
|
|
295
|
+
|
|
186
296
|
@staticmethod
|
|
187
297
|
def _validate_content(content: Any) -> None:
|
|
188
298
|
"""Accept a plain string OR a non-empty list of content parts.
|
|
189
299
|
|
|
190
|
-
Parts (ADR-0060 multimodal):
|
|
300
|
+
Parts (ADR-0060 multimodal + ADR-0061 tool-loop):
|
|
191
301
|
- ``{"type": "text", "text": <str>}``
|
|
192
302
|
- ``{"type": "image", "source": <str>}`` where ``source`` is a
|
|
193
303
|
``data:<media_type>;base64,<payload>`` URI or an http(s) URL.
|
|
304
|
+
- ``{"type": "tool_result", "tool_use_id": <str>, "content": <str>}``
|
|
305
|
+
(Anthropic-form tool-result inside a user turn).
|
|
306
|
+
- ``{"type": "tool_use", "id": <str>, "name": <str>, "input": <dict>}``
|
|
307
|
+
(Anthropic-form assistant tool-call block).
|
|
194
308
|
|
|
195
309
|
Anything else raises ``AiConfigError`` before the request goes
|
|
196
310
|
out. Both the OpenAI and Anthropic provider builders trust the
|
|
@@ -217,8 +331,28 @@ class Ai:
|
|
|
217
331
|
or source.startswith("http://")):
|
|
218
332
|
raise AiConfigError(
|
|
219
333
|
"image part 'source' must be a data: URI or http(s) URL")
|
|
334
|
+
elif kind == "tool_result":
|
|
335
|
+
tool_use_id = part.get("tool_use_id")
|
|
336
|
+
if not isinstance(tool_use_id, str) or not tool_use_id:
|
|
337
|
+
raise AiConfigError(
|
|
338
|
+
"tool_result part needs a non-empty 'tool_use_id' string")
|
|
339
|
+
if not isinstance(part.get("content"), str):
|
|
340
|
+
raise AiConfigError(
|
|
341
|
+
"tool_result part needs a string 'content'")
|
|
342
|
+
elif kind == "tool_use":
|
|
343
|
+
if not isinstance(part.get("id"), str) or not part["id"]:
|
|
344
|
+
raise AiConfigError(
|
|
345
|
+
"tool_use part needs a non-empty 'id' string")
|
|
346
|
+
if not isinstance(part.get("name"), str) or not part["name"]:
|
|
347
|
+
raise AiConfigError(
|
|
348
|
+
"tool_use part needs a non-empty 'name' string")
|
|
349
|
+
input_val = part.get("input")
|
|
350
|
+
if input_val is not None and not isinstance(input_val, dict):
|
|
351
|
+
raise AiConfigError(
|
|
352
|
+
"tool_use part 'input' must be a JSON object")
|
|
220
353
|
else:
|
|
221
|
-
raise AiConfigError(
|
|
354
|
+
raise AiConfigError(
|
|
355
|
+
"Content part 'type' must be text, image, tool_result, or tool_use")
|
|
222
356
|
|
|
223
357
|
@staticmethod
|
|
224
358
|
def _number(name: str, default: str, *, minimum: float, integer: bool = False) -> float | int:
|
|
@@ -298,14 +432,37 @@ class Ai:
|
|
|
298
432
|
temperature: float | None,
|
|
299
433
|
max_tokens: int | None,
|
|
300
434
|
stream: bool,
|
|
435
|
+
*,
|
|
436
|
+
tools: list[dict[str, Any]] | None = None,
|
|
437
|
+
tool_choice: str | dict[str, Any] | None = None,
|
|
301
438
|
) -> dict[str, Any]:
|
|
302
|
-
translated = [
|
|
303
|
-
|
|
439
|
+
translated: list[dict[str, Any]] = []
|
|
440
|
+
for message in messages:
|
|
441
|
+
result = Ai._translate_message(config.provider, message)
|
|
442
|
+
if isinstance(result, list):
|
|
443
|
+
translated.extend(result)
|
|
444
|
+
else:
|
|
445
|
+
translated.append(result)
|
|
304
446
|
body: dict[str, Any] = {"model": config.model, "messages": translated, "stream": stream}
|
|
305
447
|
if temperature is not None:
|
|
306
448
|
body["temperature"] = temperature
|
|
307
449
|
if max_tokens is not None:
|
|
308
450
|
body["max_tokens"] = max_tokens
|
|
451
|
+
|
|
452
|
+
# Tools + tool_choice - ADR-0061. Anthropic has no "none" mode,
|
|
453
|
+
# so tool_choice='none' on Anthropic OMITS the tools field
|
|
454
|
+
# entirely (which achieves the same effect: the model cannot
|
|
455
|
+
# emit a tool_call because it does not know any tool exists).
|
|
456
|
+
omit_tools_for_anthropic_none = (
|
|
457
|
+
config.provider == "anthropic" and tool_choice == "none"
|
|
458
|
+
)
|
|
459
|
+
if tools and not omit_tools_for_anthropic_none:
|
|
460
|
+
body["tools"] = Ai._translate_tools(config.provider, tools)
|
|
461
|
+
if tool_choice is not None:
|
|
462
|
+
translated_choice = Ai._translate_tool_choice(config.provider, tool_choice)
|
|
463
|
+
if translated_choice is not None:
|
|
464
|
+
body["tool_choice"] = translated_choice
|
|
465
|
+
|
|
309
466
|
if config.provider == "anthropic":
|
|
310
467
|
# System messages are hoisted to the top-level `system` field on
|
|
311
468
|
# Anthropic; only string system content is supported (multimodal
|
|
@@ -313,13 +470,13 @@ class Ai:
|
|
|
313
470
|
system_texts: list[str] = []
|
|
314
471
|
non_system: list[dict[str, Any]] = []
|
|
315
472
|
for message in translated:
|
|
316
|
-
if message
|
|
317
|
-
content = message
|
|
473
|
+
if message.get("role") == "system":
|
|
474
|
+
content = message.get("content")
|
|
318
475
|
if isinstance(content, str):
|
|
319
476
|
system_texts.append(content)
|
|
320
477
|
else:
|
|
321
478
|
# Extract text parts only; drop images from system prompt.
|
|
322
|
-
for part in content:
|
|
479
|
+
for part in content or []:
|
|
323
480
|
if isinstance(part, dict) and part.get("type") == "text":
|
|
324
481
|
system_texts.append(part.get("text", ""))
|
|
325
482
|
else:
|
|
@@ -331,33 +488,217 @@ class Ai:
|
|
|
331
488
|
return body
|
|
332
489
|
|
|
333
490
|
@staticmethod
|
|
334
|
-
def
|
|
491
|
+
def _translate_tools(provider: str, tools: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
492
|
+
"""Translate a neutral tools list to the provider's shape (ADR-0061)."""
|
|
493
|
+
if provider == "anthropic":
|
|
494
|
+
return [
|
|
495
|
+
{
|
|
496
|
+
"name": tool["name"],
|
|
497
|
+
"description": tool.get("description", ""),
|
|
498
|
+
"input_schema": tool.get("parameters") or {},
|
|
499
|
+
}
|
|
500
|
+
for tool in tools
|
|
501
|
+
]
|
|
502
|
+
# openai / local (llama.cpp, ollama openai-shim, etc.)
|
|
503
|
+
return [
|
|
504
|
+
{
|
|
505
|
+
"type": "function",
|
|
506
|
+
"function": {
|
|
507
|
+
"name": tool["name"],
|
|
508
|
+
"description": tool.get("description", ""),
|
|
509
|
+
"parameters": tool.get("parameters") or {},
|
|
510
|
+
},
|
|
511
|
+
}
|
|
512
|
+
for tool in tools
|
|
513
|
+
]
|
|
514
|
+
|
|
515
|
+
@staticmethod
|
|
516
|
+
def _translate_tool_choice(provider: str, tool_choice: Any) -> Any:
|
|
517
|
+
"""Translate a Tina4 tool_choice value to the provider's shape (ADR-0061).
|
|
518
|
+
|
|
519
|
+
Returns ``None`` when the caller's value maps to "omit the field"
|
|
520
|
+
for this provider (currently only Anthropic + ``"none"``).
|
|
521
|
+
"""
|
|
522
|
+
if provider == "anthropic":
|
|
523
|
+
if tool_choice == "auto":
|
|
524
|
+
return {"type": "auto"}
|
|
525
|
+
if tool_choice == "none":
|
|
526
|
+
# Anthropic has no "none" mode; the effect is achieved by
|
|
527
|
+
# omitting the `tools` field in _chat_body. No tool_choice
|
|
528
|
+
# goes on the wire either.
|
|
529
|
+
return None
|
|
530
|
+
if tool_choice == "required":
|
|
531
|
+
return {"type": "any"}
|
|
532
|
+
if isinstance(tool_choice, dict) and isinstance(tool_choice.get("name"), str):
|
|
533
|
+
return {"type": "tool", "name": tool_choice["name"]}
|
|
534
|
+
return None
|
|
535
|
+
# openai / local
|
|
536
|
+
if tool_choice in ("auto", "none", "required"):
|
|
537
|
+
return tool_choice
|
|
538
|
+
if isinstance(tool_choice, dict) and isinstance(tool_choice.get("name"), str):
|
|
539
|
+
return {
|
|
540
|
+
"type": "function",
|
|
541
|
+
"function": {"name": tool_choice["name"]},
|
|
542
|
+
}
|
|
543
|
+
return None
|
|
544
|
+
|
|
545
|
+
@staticmethod
|
|
546
|
+
def _translate_message(
|
|
547
|
+
provider: str,
|
|
548
|
+
message: dict[str, Any],
|
|
549
|
+
) -> dict[str, Any] | list[dict[str, Any]]:
|
|
335
550
|
"""Translate a Tina4-shape message to the provider-native shape.
|
|
336
551
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
552
|
+
Returns a single message dict OR a list of dicts (a user turn
|
|
553
|
+
carrying multiple Anthropic tool_result parts becomes multiple
|
|
554
|
+
OpenAI ``role='tool'`` messages, one per part).
|
|
555
|
+
|
|
556
|
+
Handles:
|
|
557
|
+
- String content passthrough on every provider.
|
|
558
|
+
- Multimodal parts (text/image) translated per provider.
|
|
559
|
+
- Tool-result messages (OpenAI ``role='tool'`` form or
|
|
560
|
+
Anthropic user turn with ``tool_result`` parts) normalised
|
|
561
|
+
to the current provider's shape (ADR-0061).
|
|
562
|
+
- Assistant tool_calls (OpenAI form) or assistant content
|
|
563
|
+
with ``tool_use`` parts (Anthropic form) translated per
|
|
564
|
+
provider (ADR-0061).
|
|
340
565
|
"""
|
|
566
|
+
role = message.get("role")
|
|
567
|
+
|
|
568
|
+
# ADR-0061: OpenAI-style tool-result message.
|
|
569
|
+
if role == "tool":
|
|
570
|
+
if provider == "anthropic":
|
|
571
|
+
return {
|
|
572
|
+
"role": "user",
|
|
573
|
+
"content": [{
|
|
574
|
+
"type": "tool_result",
|
|
575
|
+
"tool_use_id": message["tool_call_id"],
|
|
576
|
+
"content": message["content"],
|
|
577
|
+
}],
|
|
578
|
+
}
|
|
579
|
+
# openai / local: passthrough, but keep only the wire fields.
|
|
580
|
+
return {
|
|
581
|
+
"role": "tool",
|
|
582
|
+
"tool_call_id": message["tool_call_id"],
|
|
583
|
+
"content": message["content"],
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
# ADR-0061: assistant tool_calls (OpenAI form).
|
|
587
|
+
if role == "assistant" and isinstance(message.get("tool_calls"), list):
|
|
588
|
+
if provider != "anthropic":
|
|
589
|
+
# openai / local: passthrough.
|
|
590
|
+
out: dict[str, Any] = {
|
|
591
|
+
"role": "assistant",
|
|
592
|
+
"tool_calls": message["tool_calls"],
|
|
593
|
+
}
|
|
594
|
+
content = message.get("content")
|
|
595
|
+
if content is None:
|
|
596
|
+
out["content"] = None
|
|
597
|
+
elif isinstance(content, str):
|
|
598
|
+
out["content"] = content
|
|
599
|
+
return out
|
|
600
|
+
# Anthropic: fold tool_calls into a content list with tool_use parts.
|
|
601
|
+
parts: list[dict[str, Any]] = []
|
|
602
|
+
content = message.get("content")
|
|
603
|
+
if isinstance(content, str) and content:
|
|
604
|
+
parts.append({"type": "text", "text": content})
|
|
605
|
+
for tc in message["tool_calls"]:
|
|
606
|
+
fn = tc.get("function", {})
|
|
607
|
+
args_val = fn.get("arguments")
|
|
608
|
+
if isinstance(args_val, str):
|
|
609
|
+
try:
|
|
610
|
+
parsed = json.loads(args_val) if args_val else {}
|
|
611
|
+
except (json.JSONDecodeError, ValueError):
|
|
612
|
+
parsed = {}
|
|
613
|
+
else:
|
|
614
|
+
parsed = args_val or {}
|
|
615
|
+
parts.append({
|
|
616
|
+
"type": "tool_use",
|
|
617
|
+
"id": tc.get("id"),
|
|
618
|
+
"name": fn.get("name"),
|
|
619
|
+
"input": parsed,
|
|
620
|
+
})
|
|
621
|
+
return {"role": "assistant", "content": parts}
|
|
622
|
+
|
|
623
|
+
# ADR-0061: Anthropic-form tool_result parts on a user turn.
|
|
624
|
+
if role == "user" and isinstance(message.get("content"), list):
|
|
625
|
+
has_tool_result = any(
|
|
626
|
+
isinstance(p, dict) and p.get("type") == "tool_result"
|
|
627
|
+
for p in message["content"]
|
|
628
|
+
)
|
|
629
|
+
if has_tool_result:
|
|
630
|
+
if provider == "anthropic":
|
|
631
|
+
# Passthrough — the content shape is native.
|
|
632
|
+
return {"role": "user", "content": list(message["content"])}
|
|
633
|
+
# openai / local: split into one role='tool' message per part.
|
|
634
|
+
# Non-tool_result parts (e.g. text) are dropped: OpenAI wants
|
|
635
|
+
# tool_result content on a role='tool' message alone.
|
|
636
|
+
results: list[dict[str, Any]] = []
|
|
637
|
+
for part in message["content"]:
|
|
638
|
+
if isinstance(part, dict) and part.get("type") == "tool_result":
|
|
639
|
+
results.append({
|
|
640
|
+
"role": "tool",
|
|
641
|
+
"tool_call_id": part["tool_use_id"],
|
|
642
|
+
"content": part.get("content", ""),
|
|
643
|
+
})
|
|
644
|
+
return results
|
|
645
|
+
|
|
646
|
+
# ADR-0061: Anthropic-form assistant with tool_use content parts.
|
|
647
|
+
if role == "assistant" and isinstance(message.get("content"), list):
|
|
648
|
+
has_tool_use = any(
|
|
649
|
+
isinstance(p, dict) and p.get("type") == "tool_use"
|
|
650
|
+
for p in message["content"]
|
|
651
|
+
)
|
|
652
|
+
if has_tool_use:
|
|
653
|
+
if provider == "anthropic":
|
|
654
|
+
return {"role": "assistant", "content": list(message["content"])}
|
|
655
|
+
# openai / local: fold tool_use parts into tool_calls.
|
|
656
|
+
tool_calls: list[dict[str, Any]] = []
|
|
657
|
+
text_parts: list[str] = []
|
|
658
|
+
for part in message["content"]:
|
|
659
|
+
if not isinstance(part, dict):
|
|
660
|
+
continue
|
|
661
|
+
if part.get("type") == "tool_use":
|
|
662
|
+
tool_calls.append({
|
|
663
|
+
"id": part.get("id"),
|
|
664
|
+
"type": "function",
|
|
665
|
+
"function": {
|
|
666
|
+
"name": part.get("name"),
|
|
667
|
+
"arguments": json.dumps(part.get("input") or {}),
|
|
668
|
+
},
|
|
669
|
+
})
|
|
670
|
+
elif part.get("type") == "text":
|
|
671
|
+
text_parts.append(part.get("text", ""))
|
|
672
|
+
out = {"role": "assistant", "tool_calls": tool_calls}
|
|
673
|
+
out["content"] = "".join(text_parts) if text_parts else None
|
|
674
|
+
return out
|
|
675
|
+
|
|
676
|
+
# Default: string content passthrough, absent content preserved,
|
|
677
|
+
# or multimodal parts translated per provider.
|
|
341
678
|
content = message.get("content")
|
|
342
|
-
if isinstance(content, str):
|
|
679
|
+
if isinstance(content, str) or content is None:
|
|
343
680
|
return dict(message)
|
|
681
|
+
|
|
344
682
|
translated: list[dict[str, Any]] = []
|
|
345
683
|
for part in content or []:
|
|
346
|
-
kind = part.get("type")
|
|
684
|
+
kind = part.get("type") if isinstance(part, dict) else None
|
|
347
685
|
if kind == "text":
|
|
348
686
|
translated.append({"type": "text", "text": part.get("text", "")})
|
|
349
687
|
continue
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
688
|
+
if kind == "image":
|
|
689
|
+
source = part["source"]
|
|
690
|
+
if provider == "anthropic":
|
|
691
|
+
translated.append(Ai._anthropic_image_part(source))
|
|
692
|
+
else:
|
|
693
|
+
# openai + local (llama.cpp, ollama openai-shim, etc.) share
|
|
694
|
+
# OpenAI's image_url shape.
|
|
695
|
+
translated.append({
|
|
696
|
+
"type": "image_url",
|
|
697
|
+
"image_url": {"url": source},
|
|
698
|
+
})
|
|
699
|
+
continue
|
|
700
|
+
# Unknown part type after validation - pass through as-is.
|
|
701
|
+
translated.append(dict(part) if isinstance(part, dict) else part)
|
|
361
702
|
new_message = dict(message)
|
|
362
703
|
new_message["content"] = translated
|
|
363
704
|
return new_message
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/gallery/error-overlay/meta.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/js/tina4-dev-admin.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue_backends/kafka_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue_backends/mongo_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/realtime/models/ChannelMember.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/memcached_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.113 → tina4_python-3.13.115}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|