timbal 2.1.7__tar.gz → 2.2.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.
- {timbal-2.1.7 → timbal-2.2.0}/PKG-INFO +1 -1
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/_version.py +2 -2
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/tool.py +45 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/errors.py +51 -1
- timbal-2.2.0/python/timbal/platform/tool_proxy.py +98 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/platform/utils.py +8 -4
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/config.py +2 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/config_loader.py +15 -1
- timbal-2.2.0/python/timbal/tools/_creds.py +82 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/cala.py +13 -14
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/elevenlabs.py +12 -25
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/fal.py +5 -23
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/firecrawl.py +31 -16
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/gemini_images.py +5 -20
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_maps.py +5 -20
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/happy_scribe.py +7 -12
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/klaviyo.py +36 -52
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/krea.py +6 -23
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/linkedin.py +5 -21
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/mongodb.py +49 -30
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/pinecone.py +8 -24
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/powerbi.py +7 -23
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/quiver_ai.py +5 -21
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/replicate.py +29 -23
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/resend.py +73 -24
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/salesforce.py +169 -41
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/scraperapi.py +31 -16
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/slack.py +190 -43
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/stripe.py +51 -67
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/tavily.py +25 -15
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/web_search.py +25 -8
- timbal-2.1.7/python/timbal/tools/_creds.py +0 -39
- {timbal-2.1.7 → timbal-2.2.0}/.gitignore +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/LICENSE +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/README.md +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/chat-completions.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/create-from-workforce.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/jobs/cancel.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/jobs/get.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/jobs/list.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/jobs/retry.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/link.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/list-context-vars.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/list-policies.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/messages.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/responses.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/docs/api-reference/ace/unlink.mdx +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/pyproject.toml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/pyrightconfig.json +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/__main__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/cli_utils.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/cst_utils.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/flow.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/format.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/model_discovery.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/test.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/tool_discovery.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/add_edge.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/add_step.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/add_tool.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/remove_edge.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/remove_step.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/remove_tool.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/set_config.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/set_param.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/codegen/transformers/set_position.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/base.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/impl/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/impl/anthropic.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/impl/default.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/impl/message.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/impl/openai.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/impl/string.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/impl/timbal.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/collectors/registry.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/agent.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/fallback_model.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/llm_router.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/mcp.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/memory_compaction.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/models.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/runnable.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/skill.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/test_model.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/tool_set.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/core/workflow.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/cli.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/display.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/fixtures/agent.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/fixtures/eval_examples.yaml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/fixtures/eval_flow_validators.yaml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/fixtures/eval_negations.yaml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/fixtures/eval_parallel.yaml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/fixtures/eval_subagent.yaml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/fixtures/eval_transforms.yaml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/fixtures/test_invalid_validator.yaml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/models.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/runner.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/utils.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/base.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/comparison_base.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/contains.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/contains_all.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/contains_any.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/context.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/email.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/ends_with.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/eq.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/gt.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/gte.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/json.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/language.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/length.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/llm_base.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/lt.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/lte.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/max_length.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/min_length.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/not_null.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/parallel.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/pattern.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/prompt.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/semantic.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/seq.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/starts_with.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/evals/validators/type.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/logs.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/models.yaml +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/platform/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/platform/integrations.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/platform/knowledge_bases/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/platform/knowledge_bases/query.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/platform/types.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/server/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/server/__main__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/server/http.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/server/jobs.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/server/voice.html +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/server/voice.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/context.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/dependency_analyzer.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/exporters/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/exporters/otel.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/providers/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/providers/base.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/providers/in_memory.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/providers/jsonl.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/providers/platform.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/providers/sqlite.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/span.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/state/tracing/trace.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/asana.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/bash.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/cloudflare.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/dynamics_business_central.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/dynamics_sales.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/edit.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/elasticsearch.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/excel.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/fathom.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/gmail.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_analytics.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_business.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_calendar.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_docs.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_drive.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_merchant_center.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_search_console.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/google_sheets.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/hubspot.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/interaction.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/jira.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/knowledge_base.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/lancedb.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/netsuite.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/onedrive.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/outlook.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/read.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/sharepoint.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/shopify.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/write.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/xai.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/zendesk.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/tools/zoho_crm.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/approval.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/content/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/content/base.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/content/custom.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/content/file.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/content/text.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/content/thinking.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/content/tool_result.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/content/tool_use.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/events/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/events/approval.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/events/base.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/events/delta.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/events/interaction.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/events/output.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/events/start.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/file.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/message.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/types/run_status.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/utils/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/utils/import_spec.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/utils/model.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/utils/net.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/utils/schema.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/utils/serialization.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/voice/__init__.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/voice/elevenlabs.py +0 -0
- {timbal-2.1.7 → timbal-2.2.0}/python/timbal/voice/session.py +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 2, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -11,6 +11,8 @@ except ImportError:
|
|
|
11
11
|
|
|
12
12
|
from pydantic import BaseModel, Field, SkipValidation, computed_field, model_validator
|
|
13
13
|
|
|
14
|
+
from ..errors import CredentialNotAvailable, PlatformError, ToolProxyUnavailable
|
|
15
|
+
from ..platform.tool_proxy import execute_tool_proxy
|
|
14
16
|
from ..utils import create_model_from_handler
|
|
15
17
|
from .runnable import Runnable
|
|
16
18
|
|
|
@@ -119,6 +121,49 @@ class Tool(Runnable):
|
|
|
119
121
|
self._is_async_gen = inspect_result["is_async_gen"]
|
|
120
122
|
self._dependencies = inspect_result["dependencies"]
|
|
121
123
|
|
|
124
|
+
@override
|
|
125
|
+
async def _execute_handler(
|
|
126
|
+
self,
|
|
127
|
+
validated_input: dict[str, Any],
|
|
128
|
+
run_context: Any,
|
|
129
|
+
span: Any,
|
|
130
|
+
event_queue: Any = None,
|
|
131
|
+
):
|
|
132
|
+
"""Execute handler locally; on missing credentials, run via platform tool proxy.
|
|
133
|
+
|
|
134
|
+
If no proxy is reachable for this tool — the platform has no proxy for it
|
|
135
|
+
(HTTP 403, or 404/501 fallbacks), or there's no platform config at all
|
|
136
|
+
(e.g. running locally) — re-raise the original ``CredentialNotAvailable``
|
|
137
|
+
so the user gets the actionable "configure credentials locally" message
|
|
138
|
+
instead of an opaque platform error.
|
|
139
|
+
|
|
140
|
+
When platform auth is configured but org/subject is missing (e.g.
|
|
141
|
+
``TIMBAL_API_KEY`` without ``TIMBAL_ORG_ID``), propagate
|
|
142
|
+
``ToolProxyUnavailable`` so the user fixes platform setup rather than
|
|
143
|
+
local provider credentials.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
async for event, final_output, collector in super()._execute_handler(
|
|
147
|
+
validated_input, run_context, span, event_queue
|
|
148
|
+
):
|
|
149
|
+
yield (event, final_output, collector)
|
|
150
|
+
except CredentialNotAvailable as cred_error:
|
|
151
|
+
try:
|
|
152
|
+
output = await execute_tool_proxy(self.name, validated_input)
|
|
153
|
+
except ToolProxyUnavailable:
|
|
154
|
+
if run_context.platform_config is None:
|
|
155
|
+
# Local run without platform config — fall back to credential error.
|
|
156
|
+
raise cred_error from None
|
|
157
|
+
raise
|
|
158
|
+
except PlatformError as proxy_error:
|
|
159
|
+
# 403 is what the platform returns when no proxy is available for this
|
|
160
|
+
# tool (no service-account credentials configured). 404/501 are kept as
|
|
161
|
+
# fallbacks. In all these cases, surface the actionable credential error.
|
|
162
|
+
if proxy_error.status_code in (403, 404, 501):
|
|
163
|
+
raise cred_error from None
|
|
164
|
+
raise
|
|
165
|
+
yield (None, output, None)
|
|
166
|
+
|
|
122
167
|
@override
|
|
123
168
|
def nest(self, parent_path: str) -> None:
|
|
124
169
|
"""See base class."""
|
|
@@ -9,6 +9,38 @@ class APIKeyNotFoundError(TimbalError):
|
|
|
9
9
|
"""Error raised when an API key is not found."""
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
class CredentialNotAvailable(APIKeyNotFoundError, ValueError):
|
|
13
|
+
"""Raised when a tool credential can't be resolved from any source.
|
|
14
|
+
|
|
15
|
+
Subclasses ValueError for backward compat with tool code/tests. Carries
|
|
16
|
+
structured fields (provider_name, missing, env_vars) so callers (e.g. a
|
|
17
|
+
remote service-account executor) can react without parsing the message.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
provider_name: str,
|
|
23
|
+
*,
|
|
24
|
+
missing: list[str] | None = None,
|
|
25
|
+
env_vars: list[str] | None = None,
|
|
26
|
+
message: str | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
self.provider_name = provider_name
|
|
29
|
+
self.missing = list(missing or [])
|
|
30
|
+
self.env_vars = list(env_vars or [])
|
|
31
|
+
super().__init__(message or self._default_message())
|
|
32
|
+
|
|
33
|
+
def _default_message(self) -> str:
|
|
34
|
+
parts = [f"{self.provider_name} credentials not found."]
|
|
35
|
+
if self.missing:
|
|
36
|
+
parts.append(f"Missing: {', '.join(self.missing)}.")
|
|
37
|
+
hints = ["pass them on the tool", "configure an Integration"]
|
|
38
|
+
if self.env_vars:
|
|
39
|
+
hints.append(f"set the {', '.join(self.env_vars)} environment variable(s)")
|
|
40
|
+
parts.append("To fix: " + ", or ".join(hints) + ".")
|
|
41
|
+
return " ".join(parts)
|
|
42
|
+
|
|
43
|
+
|
|
12
44
|
class EarlyExit(TimbalError):
|
|
13
45
|
"""Error raised when an early exit is requested.
|
|
14
46
|
|
|
@@ -133,7 +165,25 @@ class PDFProcessingError(TimbalError):
|
|
|
133
165
|
|
|
134
166
|
|
|
135
167
|
class PlatformError(TimbalError):
|
|
136
|
-
"""Error raised when a platform API call fails.
|
|
168
|
+
"""Error raised when a platform API call fails.
|
|
169
|
+
|
|
170
|
+
Carries the HTTP ``status_code`` (when the failure came from an HTTP
|
|
171
|
+
response) so callers can branch on it — e.g. treat 404/501 as "endpoint
|
|
172
|
+
not implemented" rather than a hard failure.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def __init__(self, message: str, *, status_code: int | None = None) -> None:
|
|
176
|
+
super().__init__(message)
|
|
177
|
+
self.status_code = status_code
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class ToolProxyUnavailable(TimbalError):
|
|
181
|
+
"""Raised when the platform tool proxy can't be used for a tool execution.
|
|
182
|
+
|
|
183
|
+
Signals that no proxy is reachable for this run — e.g. no platform config
|
|
184
|
+
(running locally without TIMBAL_API_KEY / TIMBAL_ORG_ID). Callers fall back
|
|
185
|
+
to the original credential error so the user gets an actionable message.
|
|
186
|
+
"""
|
|
137
187
|
|
|
138
188
|
|
|
139
189
|
class FallbackExhausted(TimbalError):
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Platform tool proxy — remote tool execution without exporting credentials."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import structlog
|
|
7
|
+
|
|
8
|
+
from .. import __version__
|
|
9
|
+
from ..errors import ToolProxyUnavailable
|
|
10
|
+
from ..state import get_call_id, get_or_create_run_context
|
|
11
|
+
from .utils import _request
|
|
12
|
+
|
|
13
|
+
logger = structlog.get_logger("timbal.platform.tool_proxy")
|
|
14
|
+
|
|
15
|
+
_PROXY_PREFIX = "/proxies/v1/tools"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _subject_header_env_map() -> dict[str, str]:
|
|
19
|
+
"""Map x-timbal-* header names to TIMBAL_* env vars (fallback when subject fields are unset)."""
|
|
20
|
+
return {
|
|
21
|
+
"x-timbal-app-id": "TIMBAL_APP_ID",
|
|
22
|
+
"x-timbal-project-id": "TIMBAL_PROJECT_ID",
|
|
23
|
+
"x-timbal-rev": "TIMBAL_REV",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_tool_proxy_headers() -> dict[str, str]:
|
|
28
|
+
"""Build per-request headers for tool proxy calls (run context + subject + version)."""
|
|
29
|
+
run_context = get_or_create_run_context()
|
|
30
|
+
headers: dict[str, str] = {
|
|
31
|
+
"x-timbal-run-id": run_context.id,
|
|
32
|
+
"x-timbal-version": __version__,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Match llm_router: always attach call_id when the context var is set (tool runs always set it).
|
|
36
|
+
call_id = get_call_id()
|
|
37
|
+
if call_id is not None:
|
|
38
|
+
headers["x-timbal-call-id"] = call_id
|
|
39
|
+
|
|
40
|
+
platform_config = run_context.platform_config
|
|
41
|
+
if platform_config and platform_config.subject:
|
|
42
|
+
subject = platform_config.subject
|
|
43
|
+
if subject.app_id:
|
|
44
|
+
headers["x-timbal-app-id"] = subject.app_id
|
|
45
|
+
if subject.project_id:
|
|
46
|
+
headers["x-timbal-project-id"] = subject.project_id
|
|
47
|
+
if subject.rev:
|
|
48
|
+
headers["x-timbal-rev"] = subject.rev
|
|
49
|
+
|
|
50
|
+
for header_name, env_var in _subject_header_env_map().items():
|
|
51
|
+
if header_name not in headers:
|
|
52
|
+
value = os.getenv(env_var)
|
|
53
|
+
if value:
|
|
54
|
+
headers[header_name] = value
|
|
55
|
+
|
|
56
|
+
return headers
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _tool_proxy_path(org_id: str, tool_name: str) -> str:
|
|
60
|
+
return f"orgs/{org_id}{_PROXY_PREFIX}/{tool_name}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def execute_tool_proxy(tool_name: str, params: dict[str, Any]) -> Any:
|
|
64
|
+
"""Execute a tool via the platform proxy (no credentials in the client runtime).
|
|
65
|
+
|
|
66
|
+
POST ``/orgs/{org_id}/proxies/v1/tools/{tool_name}`` with handler params as JSON body.
|
|
67
|
+
Returns the tool handler result JSON as-is (no response wrapping).
|
|
68
|
+
"""
|
|
69
|
+
run_context = get_or_create_run_context()
|
|
70
|
+
platform_config = run_context.platform_config
|
|
71
|
+
if platform_config is None or platform_config.subject is None:
|
|
72
|
+
# The run context may have skipped platform_config resolution (e.g. tracing was
|
|
73
|
+
# explicitly disabled). Resolve it on demand — the proxy needs the org subject.
|
|
74
|
+
# force_refresh bypasses a stale cached default (a default call could have cached
|
|
75
|
+
# None before TIMBAL_API_KEY / TIMBAL_ORG_ID were set in this process).
|
|
76
|
+
from ..state.config_loader import resolve_platform_config
|
|
77
|
+
|
|
78
|
+
platform_config = resolve_platform_config(platform_config, force_refresh=True)
|
|
79
|
+
run_context.platform_config = platform_config
|
|
80
|
+
|
|
81
|
+
if platform_config is None or platform_config.subject is None:
|
|
82
|
+
raise ToolProxyUnavailable(
|
|
83
|
+
"Tool proxy requires platform_config with org subject. Set TIMBAL_API_KEY and TIMBAL_ORG_ID."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
org_id = platform_config.subject.org_id
|
|
87
|
+
path = _tool_proxy_path(org_id, tool_name)
|
|
88
|
+
headers = build_tool_proxy_headers()
|
|
89
|
+
|
|
90
|
+
logger.debug("tool_proxy_execute", tool=tool_name, path=path)
|
|
91
|
+
|
|
92
|
+
response = await _request(
|
|
93
|
+
"POST",
|
|
94
|
+
path,
|
|
95
|
+
headers=headers,
|
|
96
|
+
json=params,
|
|
97
|
+
)
|
|
98
|
+
return response.json()
|
|
@@ -134,7 +134,8 @@ async def _request(
|
|
|
134
134
|
f"\n"
|
|
135
135
|
f" URL: {exc.request.url}\n"
|
|
136
136
|
f" Status: {exc.response.status_code} {exc.response.reason_phrase}\n"
|
|
137
|
-
f" Response body: {error_body or None}"
|
|
137
|
+
f" Response body: {error_body or None}",
|
|
138
|
+
status_code=exc.response.status_code,
|
|
138
139
|
) from exc
|
|
139
140
|
# Retry on 429, 5xx, or other errors
|
|
140
141
|
if attempt == max_retries:
|
|
@@ -142,7 +143,8 @@ async def _request(
|
|
|
142
143
|
f"\n"
|
|
143
144
|
f" URL: {exc.request.url}\n"
|
|
144
145
|
f" Status: {exc.response.status_code} {exc.response.reason_phrase}\n"
|
|
145
|
-
f" Response body: {error_body or None}"
|
|
146
|
+
f" Response body: {error_body or None}",
|
|
147
|
+
status_code=exc.response.status_code,
|
|
146
148
|
) from exc
|
|
147
149
|
wait_time = 0.1 * (2**attempt) # Exponential backoff: 100ms, 200ms, 400ms
|
|
148
150
|
if exc.response.status_code == 429:
|
|
@@ -236,7 +238,8 @@ async def _stream(
|
|
|
236
238
|
f"\n"
|
|
237
239
|
f" URL: {exc.request.url}\n"
|
|
238
240
|
f" Status: {exc.response.status_code} {exc.response.reason_phrase}\n"
|
|
239
|
-
f" Response body: {error_body or None}"
|
|
241
|
+
f" Response body: {error_body or None}",
|
|
242
|
+
status_code=exc.response.status_code,
|
|
240
243
|
) from exc
|
|
241
244
|
# Retry on 429, 5xx, or other errors
|
|
242
245
|
if attempt == max_retries:
|
|
@@ -244,7 +247,8 @@ async def _stream(
|
|
|
244
247
|
f"\n"
|
|
245
248
|
f" URL: {exc.request.url}\n"
|
|
246
249
|
f" Status: {exc.response.status_code} {exc.response.reason_phrase}\n"
|
|
247
|
-
f" Response body: {error_body or None}"
|
|
250
|
+
f" Response body: {error_body or None}",
|
|
251
|
+
status_code=exc.response.status_code,
|
|
248
252
|
) from exc
|
|
249
253
|
wait_time = 0.1 * (2**attempt)
|
|
250
254
|
if exc.response.status_code == 429:
|
|
@@ -54,6 +54,8 @@ class PlatformSubject(BaseModel):
|
|
|
54
54
|
"""Organization identifier."""
|
|
55
55
|
app_id: str | None = None
|
|
56
56
|
"""Application identifier. Either project or app must be specified."""
|
|
57
|
+
project_id: str | None = None
|
|
58
|
+
"""Project identifier (platform project scope)."""
|
|
57
59
|
rev: str | None = None
|
|
58
60
|
"""Git revision the run was executed against. Internal use."""
|
|
59
61
|
|
|
@@ -150,6 +150,7 @@ def resolve_platform_config(
|
|
|
150
150
|
platform_config: PlatformConfig | None = None,
|
|
151
151
|
profile: str | None = None,
|
|
152
152
|
config_dir: Path | None = None,
|
|
153
|
+
force_refresh: bool = False,
|
|
153
154
|
) -> PlatformConfig | None:
|
|
154
155
|
"""Resolve platform configuration by merging sources with precedence.
|
|
155
156
|
|
|
@@ -170,11 +171,14 @@ def resolve_platform_config(
|
|
|
170
171
|
platform_config: Existing config to fill in missing fields for.
|
|
171
172
|
profile: Profile name for file config. Defaults to TIMBAL_PROFILE env var or "default".
|
|
172
173
|
config_dir: Override the config directory (for testing).
|
|
174
|
+
force_refresh: Re-read env/file sources even on a default call, bypassing
|
|
175
|
+
(and refreshing) the cached default. Use when env credentials may have
|
|
176
|
+
been set after an earlier default resolution cached ``None``.
|
|
173
177
|
"""
|
|
174
178
|
global _cached_default_config, _default_config_resolved
|
|
175
179
|
|
|
176
180
|
is_default_call = platform_config is None and profile is None and config_dir is None
|
|
177
|
-
if is_default_call and _default_config_resolved:
|
|
181
|
+
if is_default_call and _default_config_resolved and not force_refresh:
|
|
178
182
|
return _cached_default_config.model_copy() if _cached_default_config else None
|
|
179
183
|
|
|
180
184
|
file_config = load_file_config(profile=profile, config_dir=config_dir)
|
|
@@ -241,17 +245,27 @@ def resolve_platform_config(
|
|
|
241
245
|
if not app_id:
|
|
242
246
|
app_id = os.getenv("TIMBAL_APP_ID")
|
|
243
247
|
|
|
248
|
+
project_id = None
|
|
249
|
+
if existing_subject:
|
|
250
|
+
project_id = existing_subject.project_id
|
|
251
|
+
if not project_id:
|
|
252
|
+
project_id = os.getenv("TIMBAL_PROJECT_ID")
|
|
253
|
+
|
|
244
254
|
rev = existing_subject.rev if existing_subject else None
|
|
255
|
+
if not rev:
|
|
256
|
+
rev = os.getenv("TIMBAL_REV")
|
|
245
257
|
|
|
246
258
|
platform_config.subject = PlatformSubject(
|
|
247
259
|
org_id=org_id,
|
|
248
260
|
app_id=app_id,
|
|
261
|
+
project_id=project_id,
|
|
249
262
|
rev=rev,
|
|
250
263
|
)
|
|
251
264
|
logger.debug(
|
|
252
265
|
"Resolved platform subject.",
|
|
253
266
|
org_id=org_id,
|
|
254
267
|
app_id=app_id,
|
|
268
|
+
project_id=project_id,
|
|
255
269
|
rev=rev,
|
|
256
270
|
)
|
|
257
271
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Credential resolution for tools.
|
|
2
|
+
|
|
3
|
+
Per credential, first match wins: explicit value on the tool → Integration
|
|
4
|
+
credentials → env var. Raises CredentialNotAvailable when nothing resolves.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from collections.abc import Mapping, Sequence
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import SecretStr
|
|
12
|
+
|
|
13
|
+
from ..errors import CredentialNotAvailable
|
|
14
|
+
from ..platform.integrations import Integration
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"resolve_api_key",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _secret_value(value: Any) -> str:
|
|
22
|
+
if isinstance(value, SecretStr):
|
|
23
|
+
return value.get_secret_value()
|
|
24
|
+
return str(value)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _first_present(credentials: Any, keys: Sequence[str]) -> Any:
|
|
28
|
+
"""First truthy value for `keys` in a credentials dict or object."""
|
|
29
|
+
if isinstance(credentials, Mapping):
|
|
30
|
+
for key in keys:
|
|
31
|
+
value = credentials.get(key)
|
|
32
|
+
if value:
|
|
33
|
+
return value
|
|
34
|
+
return None
|
|
35
|
+
for key in keys:
|
|
36
|
+
value = getattr(credentials, key, None)
|
|
37
|
+
if value:
|
|
38
|
+
return value
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def resolve_api_key(
|
|
43
|
+
*,
|
|
44
|
+
provider_name: str,
|
|
45
|
+
env_var: str | None = None,
|
|
46
|
+
integration: Any = None,
|
|
47
|
+
api_key: SecretStr | str | None = None,
|
|
48
|
+
tool: Any = None,
|
|
49
|
+
explicit_attr: str = "api_key",
|
|
50
|
+
integration_keys: Sequence[str] = ("api_key",),
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Resolve a single provider secret.
|
|
53
|
+
|
|
54
|
+
Pass `tool=self` to pull `integration` / `explicit_attr` off it, or pass
|
|
55
|
+
`integration` / `api_key` explicitly. `integration_keys` are the keys tried
|
|
56
|
+
in the resolved integration credentials.
|
|
57
|
+
"""
|
|
58
|
+
if tool is not None:
|
|
59
|
+
if integration is None:
|
|
60
|
+
integration = getattr(tool, "integration", None)
|
|
61
|
+
if api_key is None:
|
|
62
|
+
api_key = getattr(tool, explicit_attr, None)
|
|
63
|
+
|
|
64
|
+
if api_key is not None:
|
|
65
|
+
return _secret_value(api_key)
|
|
66
|
+
|
|
67
|
+
if isinstance(integration, Integration):
|
|
68
|
+
credentials = await integration.resolve()
|
|
69
|
+
value = _first_present(credentials, integration_keys)
|
|
70
|
+
if value is not None:
|
|
71
|
+
return str(value)
|
|
72
|
+
|
|
73
|
+
if env_var:
|
|
74
|
+
env_value = os.getenv(env_var)
|
|
75
|
+
if env_value:
|
|
76
|
+
return env_value
|
|
77
|
+
|
|
78
|
+
raise CredentialNotAvailable(
|
|
79
|
+
provider_name,
|
|
80
|
+
missing=[explicit_attr],
|
|
81
|
+
env_vars=[env_var] if env_var else [],
|
|
82
|
+
)
|
|
@@ -4,24 +4,13 @@ from pydantic import Field, SecretStr
|
|
|
4
4
|
|
|
5
5
|
from ..core.tool import Tool
|
|
6
6
|
from ..platform.integrations import Integration
|
|
7
|
+
from ._creds import resolve_api_key
|
|
7
8
|
|
|
8
9
|
_DEFAULT_BASE_URL = "https://api.cala.ai/v1"
|
|
9
10
|
# Back-compat alias for web_search imports
|
|
10
11
|
_BASE_URL = _DEFAULT_BASE_URL
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
async def _resolve_api_key(*, integration: Any = None, api_key: SecretStr | None = None) -> str:
|
|
14
|
-
"""Resolve Cala API key from integration, explicit field, or env var."""
|
|
15
|
-
from ._creds import resolve_api_key
|
|
16
|
-
|
|
17
|
-
return await resolve_api_key(
|
|
18
|
-
env_var="CALA_API_KEY",
|
|
19
|
-
provider_name="Cala",
|
|
20
|
-
integration=integration,
|
|
21
|
-
api_key=api_key,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
14
|
def _normalize_base_url(base_url: str | None) -> str:
|
|
26
15
|
return (base_url or _DEFAULT_BASE_URL).rstrip("/")
|
|
27
16
|
|
|
@@ -106,7 +95,12 @@ class CalaSearch(_CalaKnowledgeTool):
|
|
|
106
95
|
explainability: bool = True,
|
|
107
96
|
return_entities: bool = True,
|
|
108
97
|
) -> dict:
|
|
109
|
-
api_key = await
|
|
98
|
+
api_key = await resolve_api_key(
|
|
99
|
+
env_var="CALA_API_KEY",
|
|
100
|
+
provider_name="Cala",
|
|
101
|
+
integration=self.integration,
|
|
102
|
+
api_key=self.api_key,
|
|
103
|
+
)
|
|
110
104
|
return await _post_cala(
|
|
111
105
|
path="/knowledge/search",
|
|
112
106
|
api_key=api_key,
|
|
@@ -137,7 +131,12 @@ class CalaQuery(_CalaKnowledgeTool):
|
|
|
137
131
|
),
|
|
138
132
|
return_entities: bool = True,
|
|
139
133
|
) -> dict:
|
|
140
|
-
api_key = await
|
|
134
|
+
api_key = await resolve_api_key(
|
|
135
|
+
env_var="CALA_API_KEY",
|
|
136
|
+
provider_name="Cala",
|
|
137
|
+
integration=self.integration,
|
|
138
|
+
api_key=self.api_key,
|
|
139
|
+
)
|
|
141
140
|
return await _post_cala(
|
|
142
141
|
path="/knowledge/query",
|
|
143
142
|
api_key=api_key,
|
|
@@ -5,24 +5,11 @@ from pydantic import Field, SecretStr
|
|
|
5
5
|
|
|
6
6
|
from ..core.tool import Tool
|
|
7
7
|
from ..platform.integrations import Integration
|
|
8
|
+
from ._creds import resolve_api_key
|
|
8
9
|
|
|
9
10
|
_ELEVENLABS_BASE = "https://api.elevenlabs.io/v1"
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
async def _resolve_api_key(tool: Any) -> str:
|
|
13
|
-
if isinstance(tool.integration, Integration):
|
|
14
|
-
credentials = await tool.integration.resolve()
|
|
15
|
-
return credentials["api_key"]
|
|
16
|
-
if tool.api_key is not None:
|
|
17
|
-
return tool.api_key.get_secret_value()
|
|
18
|
-
env_key = os.getenv("ELEVENLABS_API_KEY")
|
|
19
|
-
if env_key:
|
|
20
|
-
return env_key
|
|
21
|
-
raise ValueError(
|
|
22
|
-
"ElevenLabs API key not found. Set ELEVENLABS_API_KEY, pass api_key, or configure an integration."
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
13
|
class ElevenLabsTextToSpeech(Tool):
|
|
27
14
|
name: str = "elevenlabs_text_to_speech"
|
|
28
15
|
description: str | None = (
|
|
@@ -49,7 +36,7 @@ class ElevenLabsTextToSpeech(Tool):
|
|
|
49
36
|
style: float = Field(0.0, description="Style exaggeration (0.0–1.0, only for v2 models)."),
|
|
50
37
|
use_speaker_boost: bool = Field(True, description="Improve speaker clarity (slight latency increase)."),
|
|
51
38
|
) -> Any:
|
|
52
|
-
api_key = await
|
|
39
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
53
40
|
import base64
|
|
54
41
|
|
|
55
42
|
import httpx
|
|
@@ -99,7 +86,7 @@ class ElevenLabsListPhoneNumbers(Tool):
|
|
|
99
86
|
|
|
100
87
|
def __init__(self, **kwargs: Any) -> None:
|
|
101
88
|
async def _list_phone_numbers() -> Any:
|
|
102
|
-
api_key = await
|
|
89
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
103
90
|
import httpx
|
|
104
91
|
|
|
105
92
|
async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=10.0)) as client:
|
|
@@ -140,7 +127,7 @@ class ElevenLabsMakeOutboundCall(Tool):
|
|
|
140
127
|
description="Destination phone number in E.164 format, e.g. '+14155552671'.",
|
|
141
128
|
),
|
|
142
129
|
) -> Any:
|
|
143
|
-
api_key = await
|
|
130
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
144
131
|
import httpx
|
|
145
132
|
|
|
146
133
|
payload: dict[str, Any] = {
|
|
@@ -177,7 +164,7 @@ class ElevenLabsListAgents(Tool):
|
|
|
177
164
|
|
|
178
165
|
def __init__(self, **kwargs: Any) -> None:
|
|
179
166
|
async def _list_agents() -> Any:
|
|
180
|
-
api_key = await
|
|
167
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
181
168
|
import httpx
|
|
182
169
|
|
|
183
170
|
async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=10.0)) as client:
|
|
@@ -205,7 +192,7 @@ class ElevenLabsGetModels(Tool):
|
|
|
205
192
|
|
|
206
193
|
def __init__(self, **kwargs: Any) -> None:
|
|
207
194
|
async def _get_models() -> Any:
|
|
208
|
-
api_key = await
|
|
195
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
209
196
|
import httpx
|
|
210
197
|
|
|
211
198
|
async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=10.0)) as client:
|
|
@@ -238,7 +225,7 @@ class ElevenLabsGetVoicesWithDescriptions(Tool):
|
|
|
238
225
|
async def _get_voices_with_descriptions(
|
|
239
226
|
show_legacy: bool = Field(False, description="Include legacy pre-XTTS voices in the response."),
|
|
240
227
|
) -> Any:
|
|
241
|
-
api_key = await
|
|
228
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
242
229
|
import httpx
|
|
243
230
|
|
|
244
231
|
async with httpx.AsyncClient(timeout=httpx.Timeout(120.0, connect=10.0)) as client:
|
|
@@ -292,7 +279,7 @@ class ElevenLabsListHistory(Tool):
|
|
|
292
279
|
page_size: int = Field(100, description="Max items to return (default 100)."),
|
|
293
280
|
source: str | None = Field(None, description="Filter by source: 'TTS', 'STS', or 'ConvAI'."),
|
|
294
281
|
) -> Any:
|
|
295
|
-
api_key = await
|
|
282
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
296
283
|
import httpx
|
|
297
284
|
|
|
298
285
|
params: dict[str, Any] = {"page_size": page_size}
|
|
@@ -329,7 +316,7 @@ class ElevenLabsGetAudioFromHistoryItem(Tool):
|
|
|
329
316
|
async def _get_audio_from_history_item(
|
|
330
317
|
history_item_id: str = Field(..., description="The ID of the history item to retrieve audio for."),
|
|
331
318
|
) -> Any:
|
|
332
|
-
api_key = await
|
|
319
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
333
320
|
import base64
|
|
334
321
|
|
|
335
322
|
import httpx
|
|
@@ -369,7 +356,7 @@ class ElevenLabsDownloadHistoryItems(Tool):
|
|
|
369
356
|
async def _download_history_items(
|
|
370
357
|
history_item_ids: list[str] = Field(..., description="List of history item IDs to download. Single ID returns audio; multiple IDs return a .zip."),
|
|
371
358
|
) -> Any:
|
|
372
|
-
api_key = await
|
|
359
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
373
360
|
import base64
|
|
374
361
|
import io
|
|
375
362
|
import zipfile
|
|
@@ -435,7 +422,7 @@ class ElevenLabsCreateAgent(Tool):
|
|
|
435
422
|
max_duration_seconds: int = Field(600, description="Maximum call/conversation length in seconds."),
|
|
436
423
|
turn_timeout: int = Field(20, description="Seconds of silence before the agent ends its turn."),
|
|
437
424
|
) -> Any:
|
|
438
|
-
api_key = await
|
|
425
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
439
426
|
import httpx
|
|
440
427
|
|
|
441
428
|
payload: dict[str, Any] = {
|
|
@@ -505,7 +492,7 @@ class ElevenLabsAddVoice(Tool):
|
|
|
505
492
|
description="Optional metadata. Keys: language (BCP-47: 'ca', 'es', 'en', 'fr'), accent, gender, age. E.g. {'language': 'ca', 'accent': 'British'}.",
|
|
506
493
|
),
|
|
507
494
|
) -> Any:
|
|
508
|
-
api_key = await
|
|
495
|
+
api_key = await resolve_api_key(tool=self, provider_name="ElevenLabs", env_var="ELEVENLABS_API_KEY")
|
|
509
496
|
import base64
|
|
510
497
|
|
|
511
498
|
import httpx
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
1
|
from typing import Annotated, Any
|
|
3
2
|
from urllib.parse import quote, urlparse
|
|
4
3
|
|
|
@@ -6,28 +5,11 @@ from pydantic import Field, SecretStr
|
|
|
6
5
|
|
|
7
6
|
from ..core.tool import Tool
|
|
8
7
|
from ..platform.integrations import Integration
|
|
8
|
+
from ._creds import resolve_api_key
|
|
9
9
|
|
|
10
10
|
_QUEUE_BASE = "https://queue.fal.run"
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
async def _resolve_fal_key(tool: Any) -> str:
|
|
14
|
-
"""Resolve fal API key from integration, explicit field, or env var."""
|
|
15
|
-
if isinstance(tool.integration, Integration):
|
|
16
|
-
credentials = await tool.integration.resolve()
|
|
17
|
-
key = credentials.get("api_key")
|
|
18
|
-
if key:
|
|
19
|
-
return str(key)
|
|
20
|
-
if tool.api_key is not None:
|
|
21
|
-
return tool.api_key.get_secret_value()
|
|
22
|
-
env_key = os.getenv("FAL_KEY")
|
|
23
|
-
if env_key:
|
|
24
|
-
return env_key
|
|
25
|
-
raise ValueError(
|
|
26
|
-
"fal API key not found. Set FAL_KEY environment variable, "
|
|
27
|
-
"pass api_key in config, or configure an integration."
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
13
|
def _fal_auth_headers(api_key: str) -> dict[str, str]:
|
|
32
14
|
return {"Authorization": f"Key {api_key}", "Content-Type": "application/json"}
|
|
33
15
|
|
|
@@ -91,7 +73,7 @@ class FalQueueSubmit(Tool):
|
|
|
91
73
|
description="If set, fal POSTs the result to this URL when the job completes.",
|
|
92
74
|
),
|
|
93
75
|
) -> dict[str, Any]:
|
|
94
|
-
api_key = await
|
|
76
|
+
api_key = await resolve_api_key(tool=self, provider_name="fal", env_var="FAL_KEY")
|
|
95
77
|
import httpx
|
|
96
78
|
|
|
97
79
|
path = _encode_model_path(model_id)
|
|
@@ -149,7 +131,7 @@ class FalQueueStatus(Tool):
|
|
|
149
131
|
request_id: str = Field(..., description="request_id returned by fal_queue_submit."),
|
|
150
132
|
include_logs: bool = Field(False, description="If True, append ?logs=1 for runner log lines."),
|
|
151
133
|
) -> dict[str, Any]:
|
|
152
|
-
api_key = await
|
|
134
|
+
api_key = await resolve_api_key(tool=self, provider_name="fal", env_var="FAL_KEY")
|
|
153
135
|
import httpx
|
|
154
136
|
|
|
155
137
|
path = _encode_model_path(model_id)
|
|
@@ -197,7 +179,7 @@ class FalQueueResult(Tool):
|
|
|
197
179
|
),
|
|
198
180
|
request_id: str = Field(..., description="request_id returned by fal_queue_submit."),
|
|
199
181
|
) -> dict[str, Any]:
|
|
200
|
-
api_key = await
|
|
182
|
+
api_key = await resolve_api_key(tool=self, provider_name="fal", env_var="FAL_KEY")
|
|
201
183
|
import httpx
|
|
202
184
|
|
|
203
185
|
path = _encode_model_path(model_id)
|
|
@@ -242,7 +224,7 @@ class FalQueueCancel(Tool):
|
|
|
242
224
|
),
|
|
243
225
|
request_id: str = Field(..., description="request_id returned by fal_queue_submit."),
|
|
244
226
|
) -> dict[str, Any]:
|
|
245
|
-
api_key = await
|
|
227
|
+
api_key = await resolve_api_key(tool=self, provider_name="fal", env_var="FAL_KEY")
|
|
246
228
|
import httpx
|
|
247
229
|
|
|
248
230
|
path = _encode_model_path(model_id)
|