timbal 2.3.2__tar.gz → 2.3.4__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.3.2 → timbal-2.3.4}/PKG-INFO +1 -1
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/_version.py +2 -2
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/tool.py +26 -16
- timbal-2.3.4/python/timbal/server/playground.py +277 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/voice.html +667 -79
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/voice.py +34 -24
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/config.py +5 -1
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/turn_detection.py +35 -21
- {timbal-2.3.2 → timbal-2.3.4}/.gitignore +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/LICENSE +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/README.md +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/chat-completions.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/create-from-workforce.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/jobs/cancel.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/jobs/create.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/jobs/get.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/jobs/list.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/jobs/retry.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/link.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/list-context-vars.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/list-policies.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/docs/api-reference/ace/unlink.mdx +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/pyproject.toml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/pyrightconfig.json +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/__main__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/cli_utils.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/cst_utils.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/flow.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/format.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/model_discovery.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/test.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/tool_discovery.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/add_edge.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/add_mcp.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/add_step.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/add_tool.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/remove_edge.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/remove_step.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/remove_tool.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/set_config.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/set_param.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/codegen/transformers/set_position.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/base.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/impl/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/impl/anthropic.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/impl/default.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/impl/message.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/impl/openai.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/impl/string.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/impl/timbal.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/collectors/registry.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/agent.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/fallback_model.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/llm_router.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/mcp.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/memory_compaction.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/models.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/runnable.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/skill.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/test_model.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/tool_set.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/core/workflow.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/errors.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/cli.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/display.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/fixtures/agent.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/fixtures/eval_examples.yaml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/fixtures/eval_flow_validators.yaml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/fixtures/eval_negations.yaml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/fixtures/eval_parallel.yaml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/fixtures/eval_subagent.yaml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/fixtures/eval_transforms.yaml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/fixtures/test_invalid_validator.yaml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/models.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/reporters.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/runner.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/utils.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/base.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/comparison_base.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/contains.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/contains_all.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/contains_any.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/context.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/email.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/ends_with.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/eq.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/gt.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/gte.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/json.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/language.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/length.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/llm_base.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/lt.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/lte.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/max_length.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/min_length.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/not_null.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/parallel.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/pattern.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/prompt.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/semantic.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/seq.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/starts_with.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/evals/validators/type.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/logs.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/models.yaml +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/platform/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/platform/integrations.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/platform/knowledge_bases/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/platform/knowledge_bases/query.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/platform/tool_proxy.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/platform/types.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/platform/utils.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/__main__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/http.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/jobs.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/recording_upload.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/rtc.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/server/single_session.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/config.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/config_loader.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/context.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/dependency_analyzer.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/exporters/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/exporters/otel.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/providers/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/providers/base.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/providers/in_memory.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/providers/jsonl.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/providers/platform.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/providers/sqlite.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/span.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/state/tracing/trace.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/_creds.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/aircall.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/asana.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/bash.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/cala.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/cloudflare.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/connectif.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/dynamics_business_central.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/dynamics_sales.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/edit.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/elasticsearch.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/elevenlabs.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/excel.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/fal.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/fathom.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/firecrawl.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/gemini_images.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/gmail.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_analytics.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_business.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_calendar.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_docs.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_drive.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_maps.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_merchant_center.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_search_console.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/google_sheets.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/happy_scribe.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/higgsfield.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/hubspot.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/interaction.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/jira.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/klaviyo.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/knowledge_base.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/krea.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/lancedb.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/linkedin.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/mongodb.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/netsuite.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/onedrive.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/outlook.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/parallel.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/pinecone.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/powerbi.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/quiver_ai.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/read.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/replicate.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/resend.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/salesforce.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/scraperapi.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/sharepoint.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/shopify.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/slack.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/stripe.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/tavily.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/web_search.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/write.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/xai.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/zendesk.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/tools/zoho_crm.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/approval.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/content/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/content/base.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/content/custom.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/content/file.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/content/text.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/content/thinking.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/content/tool_result.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/content/tool_use.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/events/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/events/approval.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/events/base.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/events/delta.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/events/interaction.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/events/output.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/events/start.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/file.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/message.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/types/run_status.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/utils/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/utils/import_spec.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/utils/model.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/utils/net.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/utils/schema.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/utils/serialization.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/__init__.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/_whisper_features.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/ambience.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/deepgram.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/elevenlabs.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/endpointing.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/eou.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/events.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/metrics.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/namo.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/playback.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/providers.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/realtime.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/recording.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/session.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/smart_turn.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/vad.py +0 -0
- {timbal-2.3.2 → timbal-2.3.4}/python/timbal/voice/webrtc.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.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 3,
|
|
21
|
+
__version__ = version = '2.3.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 3, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -142,27 +142,37 @@ class Tool(Runnable):
|
|
|
142
142
|
``ToolProxyUnavailable`` so the user fixes platform setup rather than
|
|
143
143
|
local provider credentials.
|
|
144
144
|
"""
|
|
145
|
+
cred_error: CredentialNotAvailable | None = None
|
|
145
146
|
try:
|
|
146
147
|
async for event, final_output, collector in super()._execute_handler(
|
|
147
148
|
validated_input, run_context, span, event_queue
|
|
148
149
|
):
|
|
149
150
|
yield (event, final_output, collector)
|
|
150
|
-
except CredentialNotAvailable as
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
151
|
+
except CredentialNotAvailable as e:
|
|
152
|
+
# Capture and fall through instead of calling the proxy here: anything
|
|
153
|
+
# raised inside this except block would implicitly chain the credential
|
|
154
|
+
# error as __context__, making unrelated proxy failures read as auth
|
|
155
|
+
# problems in tracebacks.
|
|
156
|
+
cred_error = e
|
|
157
|
+
|
|
158
|
+
if cred_error is None:
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
output = await execute_tool_proxy(self.name, validated_input)
|
|
163
|
+
except ToolProxyUnavailable:
|
|
164
|
+
if run_context.platform_config is None:
|
|
165
|
+
# Local run without platform config — fall back to credential error.
|
|
166
|
+
raise cred_error from None
|
|
167
|
+
raise
|
|
168
|
+
except PlatformError as proxy_error:
|
|
169
|
+
# 403 is what the platform returns when no proxy is available for this
|
|
170
|
+
# tool (no service-account credentials configured). 404/501 are kept as
|
|
171
|
+
# fallbacks. In all these cases, surface the actionable credential error.
|
|
172
|
+
if proxy_error.status_code in (403, 404, 501):
|
|
173
|
+
raise cred_error from None
|
|
174
|
+
raise
|
|
175
|
+
yield (None, output, None)
|
|
166
176
|
|
|
167
177
|
@override
|
|
168
178
|
def nest(self, parent_path: str) -> None:
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Standalone voice playground launcher: ``python -m timbal.server.playground``.
|
|
2
|
+
|
|
3
|
+
Serves ``voice.html`` *without* a running agent underneath. The page detects it
|
|
4
|
+
was served raw (no injected runnable meta) and switches to standalone mode,
|
|
5
|
+
where you pick a target:
|
|
6
|
+
|
|
7
|
+
* **Local server** — pick an agent file (``path/to/agent.py::object``) and
|
|
8
|
+
press Start: the page asks *this* launcher to spawn ``uv run python -m
|
|
9
|
+
timbal.server --import_spec … --port …`` from the agent file's directory (so
|
|
10
|
+
``uv`` resolves that project's environment and ``.env``), waits for the
|
|
11
|
+
healthcheck, and dials it. The port is picked automatically unless fixed in
|
|
12
|
+
the form. Changing the agent or port respawns on the next Start.
|
|
13
|
+
* **Platform** — a deployed workforce through ``api.timbal.ai`` /
|
|
14
|
+
``api.dev.timbal.ai``. The page talks to the platform directly (ticket mint
|
|
15
|
+
for WS, bearer-authenticated POST for RTC); the launcher is not involved.
|
|
16
|
+
|
|
17
|
+
Deliberately stdlib-only: the launcher must work even when no agent project
|
|
18
|
+
(and therefore no fastapi/uvicorn environment) is set up yet.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import shutil
|
|
26
|
+
import socket
|
|
27
|
+
import subprocess
|
|
28
|
+
import threading
|
|
29
|
+
import webbrowser
|
|
30
|
+
from collections import deque
|
|
31
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from urllib.request import urlopen
|
|
34
|
+
|
|
35
|
+
from .. import __version__
|
|
36
|
+
|
|
37
|
+
_HTML_PATH = Path(__file__).parent / "voice.html"
|
|
38
|
+
|
|
39
|
+
_LOG_LINES = 400
|
|
40
|
+
_STOP_GRACE_SECS = 5.0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _free_port() -> int:
|
|
44
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
45
|
+
s.bind(("127.0.0.1", 0))
|
|
46
|
+
return s.getsockname()[1]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _healthy(port: int) -> bool:
|
|
50
|
+
try:
|
|
51
|
+
with urlopen(f"http://127.0.0.1:{port}/healthcheck", timeout=0.5) as resp:
|
|
52
|
+
return resp.status in (200, 204)
|
|
53
|
+
except Exception:
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ChildServer:
|
|
58
|
+
"""The single ``timbal.server`` subprocess this launcher manages."""
|
|
59
|
+
|
|
60
|
+
def __init__(self) -> None:
|
|
61
|
+
self._lock = threading.Lock()
|
|
62
|
+
self._proc: subprocess.Popen | None = None
|
|
63
|
+
self._port: int | None = None
|
|
64
|
+
self._import_spec: str | None = None
|
|
65
|
+
self._requested_spec: str | None = None
|
|
66
|
+
"""The spec exactly as the client sent it — echoed in status so the page
|
|
67
|
+
can tell whether the running child matches its form without having to
|
|
68
|
+
replicate the launcher's path resolution."""
|
|
69
|
+
self._logs: deque[str] = deque(maxlen=_LOG_LINES)
|
|
70
|
+
|
|
71
|
+
def _reader(self, proc: subprocess.Popen) -> None:
|
|
72
|
+
assert proc.stdout is not None
|
|
73
|
+
for line in proc.stdout:
|
|
74
|
+
self._logs.append(line.rstrip("\n"))
|
|
75
|
+
|
|
76
|
+
def spawn(self, import_spec: str, port: int | None = None) -> dict:
|
|
77
|
+
spec = import_spec.strip()
|
|
78
|
+
parts = spec.split("::")
|
|
79
|
+
if len(parts) != 2 or not parts[0] or not parts[1]:
|
|
80
|
+
raise ValueError("Import spec must look like 'path/to/agent.py::object_name'.")
|
|
81
|
+
spec_path = Path(parts[0]).expanduser()
|
|
82
|
+
if not spec_path.is_absolute():
|
|
83
|
+
spec_path = Path.cwd() / spec_path
|
|
84
|
+
spec_path = spec_path.resolve()
|
|
85
|
+
if not spec_path.is_file():
|
|
86
|
+
raise ValueError(f"No such file: {spec_path}")
|
|
87
|
+
if shutil.which("uv") is None:
|
|
88
|
+
raise ValueError("'uv' was not found on PATH — install uv or start the server manually.")
|
|
89
|
+
resolved_spec = f"{spec_path}::{parts[1]}"
|
|
90
|
+
|
|
91
|
+
with self._lock:
|
|
92
|
+
self._stop_locked()
|
|
93
|
+
child_port = port or _free_port()
|
|
94
|
+
cmd = [
|
|
95
|
+
"uv",
|
|
96
|
+
"run",
|
|
97
|
+
"python",
|
|
98
|
+
"-m",
|
|
99
|
+
"timbal.server",
|
|
100
|
+
"--import_spec",
|
|
101
|
+
resolved_spec,
|
|
102
|
+
"--host",
|
|
103
|
+
"127.0.0.1",
|
|
104
|
+
"--port",
|
|
105
|
+
str(child_port),
|
|
106
|
+
]
|
|
107
|
+
self._logs.clear()
|
|
108
|
+
self._logs.append(f"$ {' '.join(cmd)} (cwd: {spec_path.parent})")
|
|
109
|
+
# cwd = the agent file's directory: `uv run` walks up from there to
|
|
110
|
+
# find the agent project's pyproject/venv, and load_dotenv picks up
|
|
111
|
+
# that project's .env — not the launcher's.
|
|
112
|
+
proc = subprocess.Popen( # noqa: S603
|
|
113
|
+
cmd,
|
|
114
|
+
cwd=spec_path.parent,
|
|
115
|
+
stdout=subprocess.PIPE,
|
|
116
|
+
stderr=subprocess.STDOUT,
|
|
117
|
+
text=True,
|
|
118
|
+
)
|
|
119
|
+
self._proc = proc
|
|
120
|
+
self._port = child_port
|
|
121
|
+
self._import_spec = resolved_spec
|
|
122
|
+
self._requested_spec = spec
|
|
123
|
+
threading.Thread(target=self._reader, args=(proc,), daemon=True).start()
|
|
124
|
+
return self.status_locked()
|
|
125
|
+
|
|
126
|
+
def stop(self) -> dict:
|
|
127
|
+
with self._lock:
|
|
128
|
+
self._stop_locked()
|
|
129
|
+
return self.status_locked()
|
|
130
|
+
|
|
131
|
+
def _stop_locked(self) -> None:
|
|
132
|
+
proc = self._proc
|
|
133
|
+
if proc is None:
|
|
134
|
+
return
|
|
135
|
+
if proc.poll() is None:
|
|
136
|
+
proc.terminate()
|
|
137
|
+
try:
|
|
138
|
+
proc.wait(timeout=_STOP_GRACE_SECS)
|
|
139
|
+
except subprocess.TimeoutExpired:
|
|
140
|
+
proc.kill()
|
|
141
|
+
proc.wait(timeout=_STOP_GRACE_SECS)
|
|
142
|
+
self._proc = None
|
|
143
|
+
self._port = None
|
|
144
|
+
self._import_spec = None
|
|
145
|
+
self._requested_spec = None
|
|
146
|
+
|
|
147
|
+
def status(self) -> dict:
|
|
148
|
+
with self._lock:
|
|
149
|
+
return self.status_locked()
|
|
150
|
+
|
|
151
|
+
def status_locked(self) -> dict:
|
|
152
|
+
proc = self._proc
|
|
153
|
+
if proc is None:
|
|
154
|
+
# Keep the last logs around so a crash is inspectable after stop.
|
|
155
|
+
return {"state": "stopped", "logs": list(self._logs)}
|
|
156
|
+
exit_code = proc.poll()
|
|
157
|
+
if exit_code is not None:
|
|
158
|
+
return {
|
|
159
|
+
"state": "exited",
|
|
160
|
+
"exit_code": exit_code,
|
|
161
|
+
"port": self._port,
|
|
162
|
+
"import_spec": self._import_spec,
|
|
163
|
+
"requested_import_spec": self._requested_spec,
|
|
164
|
+
"logs": list(self._logs),
|
|
165
|
+
}
|
|
166
|
+
state = "running" if _healthy(self._port) else "starting"
|
|
167
|
+
return {
|
|
168
|
+
"state": state,
|
|
169
|
+
"pid": proc.pid,
|
|
170
|
+
"port": self._port,
|
|
171
|
+
"import_spec": self._import_spec,
|
|
172
|
+
"requested_import_spec": self._requested_spec,
|
|
173
|
+
"logs": list(self._logs),
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _make_handler(child: ChildServer, default_import_spec: str) -> type[BaseHTTPRequestHandler]:
|
|
178
|
+
class Handler(BaseHTTPRequestHandler):
|
|
179
|
+
server_version = f"timbal-playground/{__version__}"
|
|
180
|
+
|
|
181
|
+
def log_message(self, format: str, *args) -> None: # noqa: A002
|
|
182
|
+
pass # keep the terminal usable; the page shows child logs itself
|
|
183
|
+
|
|
184
|
+
def _send_json(self, status: int, payload: dict) -> None:
|
|
185
|
+
body = json.dumps(payload).encode("utf-8")
|
|
186
|
+
self.send_response(status)
|
|
187
|
+
self.send_header("Content-Type", "application/json")
|
|
188
|
+
self.send_header("Content-Length", str(len(body)))
|
|
189
|
+
self.end_headers()
|
|
190
|
+
self.wfile.write(body)
|
|
191
|
+
|
|
192
|
+
def do_GET(self) -> None: # noqa: N802
|
|
193
|
+
path = self.path.split("?", 1)[0].rstrip("/") or "/"
|
|
194
|
+
if path in ("/", "/voice"):
|
|
195
|
+
body = _HTML_PATH.read_bytes()
|
|
196
|
+
self.send_response(200)
|
|
197
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
198
|
+
self.send_header("Content-Length", str(len(body)))
|
|
199
|
+
self.end_headers()
|
|
200
|
+
self.wfile.write(body)
|
|
201
|
+
return
|
|
202
|
+
if path == "/launcher/status":
|
|
203
|
+
self._send_json(
|
|
204
|
+
200,
|
|
205
|
+
{
|
|
206
|
+
"launcher": {"version": __version__, "default_import_spec": default_import_spec},
|
|
207
|
+
"child": child.status(),
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
return
|
|
211
|
+
self._send_json(404, {"error": "not found"})
|
|
212
|
+
|
|
213
|
+
def do_POST(self) -> None: # noqa: N802
|
|
214
|
+
path = self.path.split("?", 1)[0].rstrip("/")
|
|
215
|
+
length = int(self.headers.get("Content-Length") or 0)
|
|
216
|
+
try:
|
|
217
|
+
body = json.loads(self.rfile.read(length) or b"{}")
|
|
218
|
+
except json.JSONDecodeError:
|
|
219
|
+
self._send_json(400, {"error": "invalid JSON body"})
|
|
220
|
+
return
|
|
221
|
+
if path == "/launcher/spawn":
|
|
222
|
+
import_spec = str(body.get("import_spec") or "").strip()
|
|
223
|
+
if not import_spec:
|
|
224
|
+
self._send_json(400, {"error": "import_spec is required"})
|
|
225
|
+
return
|
|
226
|
+
port = body.get("port")
|
|
227
|
+
try:
|
|
228
|
+
status = child.spawn(import_spec, port=int(port) if port else None)
|
|
229
|
+
except ValueError as e:
|
|
230
|
+
self._send_json(400, {"error": str(e)})
|
|
231
|
+
return
|
|
232
|
+
except OSError as e:
|
|
233
|
+
self._send_json(500, {"error": f"spawn failed: {e}"})
|
|
234
|
+
return
|
|
235
|
+
self._send_json(200, {"child": status})
|
|
236
|
+
return
|
|
237
|
+
if path == "/launcher/stop":
|
|
238
|
+
self._send_json(200, {"child": child.stop()})
|
|
239
|
+
return
|
|
240
|
+
self._send_json(404, {"error": "not found"})
|
|
241
|
+
|
|
242
|
+
return Handler
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def main(argv: list[str] | None = None) -> None:
|
|
246
|
+
parser = argparse.ArgumentParser(description="Standalone Timbal voice playground.")
|
|
247
|
+
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to bind the playground to.")
|
|
248
|
+
parser.add_argument("--port", type=int, default=7777, help="Port to bind the playground to.")
|
|
249
|
+
parser.add_argument(
|
|
250
|
+
"--import_spec",
|
|
251
|
+
"--import-spec",
|
|
252
|
+
dest="import_spec",
|
|
253
|
+
type=str,
|
|
254
|
+
default="",
|
|
255
|
+
help="Default import spec prefilled in the page's spawn field (path/to/agent.py::object).",
|
|
256
|
+
)
|
|
257
|
+
parser.add_argument("--no-open", action="store_true", help="Don't open the browser automatically.")
|
|
258
|
+
args = parser.parse_args(argv)
|
|
259
|
+
|
|
260
|
+
child = ChildServer()
|
|
261
|
+
handler = _make_handler(child, args.import_spec)
|
|
262
|
+
httpd = ThreadingHTTPServer((args.host, args.port), handler)
|
|
263
|
+
url = f"http://{'localhost' if args.host in ('0.0.0.0', '127.0.0.1') else args.host}:{args.port}/"
|
|
264
|
+
print(f"Voice playground at {url}") # noqa: T201
|
|
265
|
+
if not args.no_open:
|
|
266
|
+
webbrowser.open(url)
|
|
267
|
+
try:
|
|
268
|
+
httpd.serve_forever()
|
|
269
|
+
except KeyboardInterrupt:
|
|
270
|
+
pass
|
|
271
|
+
finally:
|
|
272
|
+
child.stop()
|
|
273
|
+
httpd.server_close()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
if __name__ == "__main__":
|
|
277
|
+
main()
|