timbal 2.4.2__tar.gz → 2.6.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.4.2 → timbal-2.6.0}/.gitignore +4 -0
- {timbal-2.4.2 → timbal-2.6.0}/PKG-INFO +6 -2
- {timbal-2.4.2 → timbal-2.6.0}/pyproject.toml +5 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/_version.py +2 -2
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/__main__.py +2 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/flow.py +35 -7
- timbal-2.6.0/python/timbal/codegen/guardrail_specs.py +161 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/__init__.py +27 -16
- timbal-2.6.0/python/timbal/codegen/transformers/add_guardrail.py +113 -0
- timbal-2.6.0/python/timbal/codegen/transformers/remove_guardrail.py +91 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/set_config.py +26 -12
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/agent.py +389 -5
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/fallback_model.py +7 -1
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/llm/retry.py +21 -2
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/llm/router.py +4 -1
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/runnable.py +180 -3
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/workflow.py +118 -35
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/errors.py +26 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/__init__.py +2 -0
- timbal-2.6.0/python/timbal/evals/validators/rubric.py +105 -0
- timbal-2.6.0/python/timbal/guardrails/__init__.py +89 -0
- timbal-2.6.0/python/timbal/guardrails/apply.py +114 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/__init__.py +20 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/injection.py +99 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/judge.py +150 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/keywords.py +44 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/length.py +44 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/moderate.py +120 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/pii.py +90 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/secrets.py +53 -0
- timbal-2.6.0/python/timbal/guardrails/builtins/topic.py +69 -0
- timbal-2.6.0/python/timbal/guardrails/judge_llm.py +56 -0
- timbal-2.6.0/python/timbal/guardrails/presets.py +106 -0
- timbal-2.6.0/python/timbal/guardrails/rubric.py +228 -0
- timbal-2.6.0/python/timbal/guardrails/runner.py +342 -0
- timbal-2.6.0/python/timbal/guardrails/testing.py +74 -0
- timbal-2.6.0/python/timbal/guardrails/trace.py +75 -0
- timbal-2.6.0/python/timbal/guardrails/types.py +388 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/models.yaml +4 -4
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/platform/utils.py +105 -7
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/http.py +12 -0
- timbal-2.6.0/python/timbal/server/livekit_session.py +367 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/playground.py +176 -4
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/single_session.py +110 -10
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/voice.html +243 -16
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/voice.py +73 -17
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/context.py +7 -1
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/providers/base.py +66 -1
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/events/__init__.py +3 -1
- timbal-2.6.0/python/timbal/types/events/guardrail.py +57 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/run_status.py +1 -1
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/__init__.py +10 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/config.py +3 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/elevenlabs.py +19 -1
- timbal-2.6.0/python/timbal/voice/fish_audio.py +294 -0
- timbal-2.6.0/python/timbal/voice/livekit.py +237 -0
- timbal-2.6.0/python/timbal/voice/munsit.py +166 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/providers.py +36 -1
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/telephony.py +16 -0
- {timbal-2.4.2 → timbal-2.6.0}/LICENSE +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/README.md +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/chat-completions.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/create-from-workforce.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/jobs/cancel.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/jobs/create.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/jobs/get.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/jobs/list.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/jobs/retry.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/link.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/list-context-vars.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/list-policies.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/docs/api-reference/ace/unlink.mdx +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/pyrightconfig.json +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/_slots.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/cli_utils.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/cst_utils.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/format.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/library.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/model_discovery.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/test.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/tool_discovery.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/add_edge.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/add_mcp.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/add_step.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/add_tool.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/remove_edge.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/remove_step.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/remove_tool.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/set_param.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/codegen/transformers/set_position.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/base.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/impl/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/impl/anthropic.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/impl/default.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/impl/message.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/impl/openai.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/impl/string.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/impl/timbal.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/collectors/registry.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/llm/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/llm/chat_completions.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/llm/clients.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/llm/messages.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/llm/registry.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/llm/responses.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/mcp.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/memory_compaction.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/models.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/provider_errors.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/skill.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/test_model.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/tool.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/tool_result_offload.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/core/tool_set.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/cli.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/display.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/fixtures/agent.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/fixtures/eval_examples.yaml +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/fixtures/eval_flow_validators.yaml +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/fixtures/eval_negations.yaml +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/fixtures/eval_parallel.yaml +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/fixtures/eval_subagent.yaml +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/fixtures/eval_transforms.yaml +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/fixtures/test_invalid_validator.yaml +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/models.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/reporters.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/runner.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/utils.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/_spans.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/base.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/comparison_base.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/contains.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/contains_all.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/contains_any.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/context.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/email.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/ends_with.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/eq.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/gt.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/gte.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/json.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/language.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/length.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/llm_base.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/lt.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/lte.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/max_length.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/min_length.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/not_null.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/parallel.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/pattern.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/prompt.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/semantic.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/seq.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/starts_with.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/evals/validators/type.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/logs.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/platform/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/platform/integrations.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/platform/knowledge_bases/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/platform/knowledge_bases/query.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/platform/tool_proxy.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/platform/types.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/__main__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/jobs.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/recording_upload.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/rtc.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/server/telephony.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/config.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/config_loader.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/dependency_analyzer.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/exporters/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/exporters/otel.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/providers/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/providers/in_memory.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/providers/jsonl.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/providers/platform.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/providers/sqlite.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/span.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/state/tracing/trace.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/__init__.py +87 -87
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/_creds.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/aircall.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/asana.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/bash.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/cala.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/cloudflare.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/connectif.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/covermanager.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/dynamics_business_central.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/dynamics_sales.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/edit.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/elasticsearch.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/elevenlabs.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/excel.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/fal.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/fathom.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/firecrawl.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/gemini_images.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/gmail.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_analytics.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_business.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_calendar.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_docs.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_drive.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_maps.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_merchant_center.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_search_console.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/google_sheets.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/happy_scribe.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/higgsfield.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/hubspot.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/interaction.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/jira.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/klaviyo.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/knowledge_base.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/krea.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/lancedb.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/linkedin.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/mongodb.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/netsuite.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/onedrive.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/outlook.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/parallel.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/pinecone.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/powerbi.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/quiver_ai.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/read.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/replicate.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/resend.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/salesforce.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/scraperapi.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/sharepoint.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/shopify.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/slack.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/stripe.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/tavily.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/web_search.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/write.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/xai.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/zendesk.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/tools/zoho_crm.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/approval.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/content/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/content/base.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/content/custom.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/content/file.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/content/text.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/content/thinking.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/content/tool_result.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/content/tool_use.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/events/approval.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/events/base.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/events/delta.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/events/interaction.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/events/output.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/events/start.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/file.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/types/message.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/utils/__init__.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/utils/import_spec.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/utils/model.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/utils/net.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/utils/schema.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/utils/serialization.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/_whisper_features.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/ambience.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/deepgram.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/endpointing.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/eou.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/events.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/metrics.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/namo.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/playback.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/realtime.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/recording.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/session.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/smart_turn.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/turn_detection.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/vad.py +0 -0
- {timbal-2.4.2 → timbal-2.6.0}/python/timbal/voice/webrtc.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: timbal
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: Simple, performant, battle-tested framework for building reliable AI applications
|
|
5
5
|
Project-URL: Source, https://github.com/timbal-ai/timbal
|
|
6
6
|
Author-email: Timbal <team@timbal.ai>
|
|
@@ -238,6 +238,7 @@ Requires-Dist: huggingface-hub>=0.28.0; extra == 'all'
|
|
|
238
238
|
Requires-Dist: libcst>=1.8.6; extra == 'all'
|
|
239
239
|
Requires-Dist: onnxruntime>=1.20.0; extra == 'all'
|
|
240
240
|
Requires-Dist: openpyxl>=3.1.5; extra == 'all'
|
|
241
|
+
Requires-Dist: ormsgpack>=1.5.0; extra == 'all'
|
|
241
242
|
Requires-Dist: pymupdf>=1.27.1; extra == 'all'
|
|
242
243
|
Requires-Dist: python-docx>=1.1.2; extra == 'all'
|
|
243
244
|
Requires-Dist: rich>=14.2.0; extra == 'all'
|
|
@@ -256,6 +257,7 @@ Provides-Extra: evals
|
|
|
256
257
|
Requires-Dist: rich>=14.2.0; extra == 'evals'
|
|
257
258
|
Provides-Extra: server
|
|
258
259
|
Requires-Dist: fastapi>=0.115.8; extra == 'server'
|
|
260
|
+
Requires-Dist: ormsgpack>=1.5.0; extra == 'server'
|
|
259
261
|
Requires-Dist: uvicorn>=0.34.0; extra == 'server'
|
|
260
262
|
Requires-Dist: websockets>=15.0.1; extra == 'server'
|
|
261
263
|
Provides-Extra: voice
|
|
@@ -263,6 +265,8 @@ Requires-Dist: aiortc>=1.9.0; extra == 'voice'
|
|
|
263
265
|
Requires-Dist: huggingface-hub>=0.28.0; extra == 'voice'
|
|
264
266
|
Requires-Dist: onnxruntime>=1.20.0; extra == 'voice'
|
|
265
267
|
Requires-Dist: transformers>=4.40.0; extra == 'voice'
|
|
268
|
+
Provides-Extra: voice-livekit
|
|
269
|
+
Requires-Dist: livekit>=1.1.14; extra == 'voice-livekit'
|
|
266
270
|
Description-Content-Type: text/markdown
|
|
267
271
|
|
|
268
272
|
# Timbal
|
|
@@ -57,6 +57,7 @@ evals = [
|
|
|
57
57
|
]
|
|
58
58
|
server = [
|
|
59
59
|
"fastapi>=0.115.8",
|
|
60
|
+
"ormsgpack>=1.5.0",
|
|
60
61
|
"uvicorn>=0.34.0",
|
|
61
62
|
"websockets>=15.0.1",
|
|
62
63
|
]
|
|
@@ -66,6 +67,9 @@ voice = [
|
|
|
66
67
|
"onnxruntime>=1.20.0",
|
|
67
68
|
"transformers>=4.40.0",
|
|
68
69
|
]
|
|
70
|
+
"voice-livekit" = [
|
|
71
|
+
"livekit>=1.1.14",
|
|
72
|
+
]
|
|
69
73
|
all = [
|
|
70
74
|
"timbal[codegen]",
|
|
71
75
|
"timbal[documents]",
|
|
@@ -108,6 +112,7 @@ exclude = [
|
|
|
108
112
|
"/sandbox",
|
|
109
113
|
"/scripts",
|
|
110
114
|
"/benchmarks",
|
|
115
|
+
"/rust",
|
|
111
116
|
]
|
|
112
117
|
|
|
113
118
|
[tool.uv]
|
|
@@ -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.6.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 6, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -10,10 +10,12 @@ from pathlib import Path
|
|
|
10
10
|
# A test asserts this table stays in sync with the modules on disk.
|
|
11
11
|
_TRANSFORMER_OPS: dict[str, tuple[str, str]] = {
|
|
12
12
|
"add-edge": ("add_edge", "Add an ordering or conditional edge between two workflow steps."),
|
|
13
|
+
"add-guardrail": ("add_guardrail", "Add a guardrail shorthand to the agent's guardrails list."),
|
|
13
14
|
"add-mcp": ("add_mcp", "Add an MCP server to the agent's tools list."),
|
|
14
15
|
"add-step": ("add_step", "Add a step to the workflow."),
|
|
15
16
|
"add-tool": ("add_tool", "Add a tool to the agent's tools list."),
|
|
16
17
|
"remove-edge": ("remove_edge", "Remove an edge between two workflow steps."),
|
|
18
|
+
"remove-guardrail": ("remove_guardrail", "Remove a guardrail from the agent's guardrails list by name."),
|
|
17
19
|
"remove-step": ("remove_step", "Remove a step from the workflow by name."),
|
|
18
20
|
"remove-tool": ("remove_tool", "Remove a tool from the agent's tools list by name."),
|
|
19
21
|
"set-config": ("set_config", "Set configuration on the agent/step or on a specific tool."),
|
|
@@ -39,6 +39,20 @@ def _get_when_source(step: Any) -> str | None:
|
|
|
39
39
|
return _get_callable_source(step.when["callable"])
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
def _get_while_info(step: Any) -> dict[str, Any] | None:
|
|
43
|
+
"""Describe a step's while_ loop config for JSON output.
|
|
44
|
+
|
|
45
|
+
Returns ``{"count": N}`` for count loops, ``{"expr": "<source>"}`` for
|
|
46
|
+
callable conditions, or None when the step doesn't loop.
|
|
47
|
+
"""
|
|
48
|
+
while_ = getattr(step, "while_", None)
|
|
49
|
+
if not while_:
|
|
50
|
+
return None
|
|
51
|
+
if "count" in while_:
|
|
52
|
+
return {"count": while_["count"]}
|
|
53
|
+
return {"expr": _get_callable_source(while_["callable"]) or "<callable>"}
|
|
54
|
+
|
|
55
|
+
|
|
42
56
|
def _extract_key_from_lambda(fn: Any, source_step: str) -> str | None:
|
|
43
57
|
"""Extract the dot-notation key path from a runtime lambda's source.
|
|
44
58
|
|
|
@@ -208,9 +222,9 @@ def _edge_kind(kinds: set[str] | None) -> str:
|
|
|
208
222
|
|
|
209
223
|
A dependency can arise from several sources at once (e.g. an explicit
|
|
210
224
|
``depends_on`` that also reads the step's output via a param map). Precedence
|
|
211
|
-
is ``ordering > when > param`` so explicit sequencing is never masked
|
|
212
|
-
param wiring. Unknown/missing (e.g. edges from a direct ``_link``) default
|
|
213
|
-
``ordering`` — the safe choice, since ordering edges are never reduced away.
|
|
225
|
+
is ``ordering > when > while > param`` so explicit sequencing is never masked
|
|
226
|
+
by param wiring. Unknown/missing (e.g. edges from a direct ``_link``) default
|
|
227
|
+
to ``ordering`` — the safe choice, since ordering edges are never reduced away.
|
|
214
228
|
"""
|
|
215
229
|
if not kinds:
|
|
216
230
|
return "ordering"
|
|
@@ -218,6 +232,8 @@ def _edge_kind(kinds: set[str] | None) -> str:
|
|
|
218
232
|
return "ordering"
|
|
219
233
|
if "when" in kinds:
|
|
220
234
|
return "when"
|
|
235
|
+
if "while" in kinds:
|
|
236
|
+
return "while"
|
|
221
237
|
return "param"
|
|
222
238
|
|
|
223
239
|
|
|
@@ -253,7 +269,11 @@ def get_flow(workspace_path: str | Path) -> dict[str, Any]:
|
|
|
253
269
|
|
|
254
270
|
if isinstance(runnable, Workflow):
|
|
255
271
|
for step in runnable._steps.values():
|
|
256
|
-
|
|
272
|
+
node = _build_node(step, include_tools=isinstance(step, Agent))
|
|
273
|
+
while_info = _get_while_info(step)
|
|
274
|
+
if while_info is not None:
|
|
275
|
+
node["data"]["while"] = while_info
|
|
276
|
+
nodes.append(node)
|
|
257
277
|
|
|
258
278
|
for _step_name, step in runnable._steps.items():
|
|
259
279
|
when_source = _get_when_source(step) if step.when else None
|
|
@@ -407,6 +427,13 @@ def _fmt_node_lines(node: dict, indent: str) -> list[str]:
|
|
|
407
427
|
elif ntype == "workflow":
|
|
408
428
|
lines.append(f"{indent}workflow {name}")
|
|
409
429
|
|
|
430
|
+
while_info = node["data"].get("while")
|
|
431
|
+
if while_info:
|
|
432
|
+
if "count" in while_info:
|
|
433
|
+
lines.append(f"{indent} while: count={while_info['count']}")
|
|
434
|
+
else:
|
|
435
|
+
lines.append(f"{indent} while: {while_info.get('expr', '<callable>')}")
|
|
436
|
+
|
|
410
437
|
return lines
|
|
411
438
|
|
|
412
439
|
|
|
@@ -420,9 +447,10 @@ def _reduce_param_edges(edges: list[dict]) -> list[dict]:
|
|
|
420
447
|
first step.
|
|
421
448
|
|
|
422
449
|
Only edges whose single ``kind`` is ``param`` are eligible for removal —
|
|
423
|
-
explicit ordering (``depends_on`` / hooks) and ``
|
|
424
|
-
kept so user-added structure never silently disappears from the
|
|
425
|
-
view. The full edge set (including these) is retained in the JSON
|
|
450
|
+
explicit ordering (``depends_on`` / hooks), ``when``, and ``while`` edges
|
|
451
|
+
are always kept so user-added structure never silently disappears from the
|
|
452
|
+
compact view. The full edge set (including these) is retained in the JSON
|
|
453
|
+
output.
|
|
426
454
|
"""
|
|
427
455
|
adj: dict[str, list[str]] = defaultdict(list)
|
|
428
456
|
for e in edges:
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Shared CST helpers for the add-guardrail / remove-guardrail transformers.
|
|
2
|
+
|
|
3
|
+
Lives outside ``transformers/`` on purpose. That package is scanned by ``pkgutil`` and
|
|
4
|
+
every module in it is imported as an operation, so a helper module placed there would be
|
|
5
|
+
registered as a bogus operation — and, worse, having one transformer import another's
|
|
6
|
+
privates couples unrelated operations together at import time.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import libcst as cst
|
|
10
|
+
|
|
11
|
+
from .cst_utils import (
|
|
12
|
+
collect_assignments,
|
|
13
|
+
collect_step_names,
|
|
14
|
+
is_bare_function_step,
|
|
15
|
+
resolve_entry_point_type,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"guardrails_kwarg_index",
|
|
20
|
+
"literal_shorthands",
|
|
21
|
+
"rail_name",
|
|
22
|
+
"string_element",
|
|
23
|
+
"string_value",
|
|
24
|
+
"validate_guardrail_target",
|
|
25
|
+
"validate_guardrails_value",
|
|
26
|
+
"validate_shorthand",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def validate_shorthand(spec: str) -> None:
|
|
31
|
+
"""Validate one ``name[:action]`` shorthand at the CLI boundary.
|
|
32
|
+
|
|
33
|
+
Unknown names/actions re-raise the presets error (it lists the valid options).
|
|
34
|
+
Rails that are valid but have required constructor params (topic, judge, keywords,
|
|
35
|
+
length) get a pointer to configure them in code instead of a pydantic dump.
|
|
36
|
+
"""
|
|
37
|
+
from timbal.guardrails.presets import coerce_rail
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
coerce_rail(spec)
|
|
41
|
+
except ValueError as e:
|
|
42
|
+
if "Unknown guardrail" in str(e):
|
|
43
|
+
raise
|
|
44
|
+
reason = e.errors()[0]["msg"].removeprefix("Value error, ") if hasattr(e, "errors") else str(e)
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"Guardrail {spec.partition(':')[0].strip()!r} needs configuration that a shorthand cannot "
|
|
47
|
+
f"express: {reason} Configure it in code instead — e.g. "
|
|
48
|
+
"Agent(guardrails=[TopicGuard(allow=[...])]) from timbal.guardrails."
|
|
49
|
+
) from e
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def validate_guardrails_value(rails: object) -> None:
|
|
53
|
+
"""Validate a JSON ``guardrails`` value (agent- or tool-level) at the CLI.
|
|
54
|
+
|
|
55
|
+
Accepts the whole-value string ``"default"``, a single shorthand string, or a list
|
|
56
|
+
of shorthand strings. ``"default"`` inside a *list* is rejected: at runtime the
|
|
57
|
+
preset is only recognized as the whole value, so emitting it as a list entry would
|
|
58
|
+
fail at construction with a misleading "unknown shorthand" error.
|
|
59
|
+
"""
|
|
60
|
+
from timbal.guardrails.presets import DEFAULT_SHORTHANDS
|
|
61
|
+
|
|
62
|
+
if isinstance(rails, str):
|
|
63
|
+
if rails.strip().lower() != "default":
|
|
64
|
+
validate_shorthand(rails)
|
|
65
|
+
return
|
|
66
|
+
if not isinstance(rails, list) or not all(isinstance(s, str) for s in rails):
|
|
67
|
+
raise ValueError(
|
|
68
|
+
'guardrails must be a JSON list of shorthand strings (e.g. ["pii:redact", "injection:block"]) '
|
|
69
|
+
'or the string "default".'
|
|
70
|
+
)
|
|
71
|
+
for spec in rails:
|
|
72
|
+
if spec.strip().lower() == "default":
|
|
73
|
+
raise ValueError(
|
|
74
|
+
'"default" is the whole-value preset, not a list entry. Use '
|
|
75
|
+
'{"guardrails": "default"}, or list its shorthands explicitly: '
|
|
76
|
+
f"{list(DEFAULT_SHORTHANDS)}."
|
|
77
|
+
)
|
|
78
|
+
validate_shorthand(spec)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def validate_guardrail_target(
|
|
82
|
+
tree: cst.Module | None,
|
|
83
|
+
entry_point: str,
|
|
84
|
+
step: str | None,
|
|
85
|
+
operation: str,
|
|
86
|
+
) -> tuple[str, dict]:
|
|
87
|
+
"""Resolve and validate the Agent the operation targets. Mirrors remove-tool."""
|
|
88
|
+
if tree is not None:
|
|
89
|
+
ep_type = resolve_entry_point_type(tree, entry_point)
|
|
90
|
+
if step:
|
|
91
|
+
if ep_type is not None and ep_type != "Workflow":
|
|
92
|
+
raise ValueError(f"--step requires a Workflow entry point, but '{entry_point}' is a {ep_type}.")
|
|
93
|
+
else:
|
|
94
|
+
if ep_type is not None and ep_type != "Agent":
|
|
95
|
+
raise ValueError(f"{operation} requires an Agent entry point, but '{entry_point}' is a {ep_type}.")
|
|
96
|
+
|
|
97
|
+
target = step if step else entry_point
|
|
98
|
+
assignments = collect_assignments(tree) if tree else {}
|
|
99
|
+
|
|
100
|
+
if tree is not None:
|
|
101
|
+
if step:
|
|
102
|
+
step_names = collect_step_names(tree, entry_point, assignments)
|
|
103
|
+
if (
|
|
104
|
+
step not in step_names
|
|
105
|
+
and step not in assignments
|
|
106
|
+
and not is_bare_function_step(tree, entry_point, step, assignments)
|
|
107
|
+
):
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"Workflow step '{step}' not found. "
|
|
110
|
+
"Use the step variable name from .step(...), not the runtime name."
|
|
111
|
+
)
|
|
112
|
+
elif entry_point not in assignments:
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"Entry point variable '{entry_point}' not found in source. "
|
|
115
|
+
"Ensure timbal.yaml fqn matches the Agent/Workflow variable name."
|
|
116
|
+
)
|
|
117
|
+
return target, assignments
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def string_value(node: cst.BaseExpression) -> str | None:
|
|
121
|
+
if isinstance(node, cst.SimpleString):
|
|
122
|
+
return node.evaluated_value if isinstance(node.evaluated_value, str) else None
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def string_element(value: str) -> cst.Element:
|
|
127
|
+
return cst.Element(value=cst.SimpleString(f'"{value}"'))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def rail_name(spec: str) -> str:
|
|
131
|
+
"""The rail identity of a shorthand — ``"pii:redact"`` and ``"pii"`` are the same rail."""
|
|
132
|
+
return spec.partition(":")[0].strip().lower()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def guardrails_kwarg_index(call: cst.Call) -> int | None:
|
|
136
|
+
for i, arg in enumerate(call.args):
|
|
137
|
+
if isinstance(arg.keyword, cst.Name) and arg.keyword.value == "guardrails":
|
|
138
|
+
return i
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def literal_shorthands(value: cst.BaseExpression) -> list[str] | None:
|
|
143
|
+
"""The kwarg's current shorthand list, or None when it isn't literal strings.
|
|
144
|
+
|
|
145
|
+
A ``"default"`` string expands to its preset shorthands so list edits compose.
|
|
146
|
+
"""
|
|
147
|
+
if (s := string_value(value)) is not None:
|
|
148
|
+
if s.strip().lower() == "default":
|
|
149
|
+
from timbal.guardrails.presets import DEFAULT_SHORTHANDS
|
|
150
|
+
|
|
151
|
+
return list(DEFAULT_SHORTHANDS)
|
|
152
|
+
return [s]
|
|
153
|
+
if isinstance(value, cst.List):
|
|
154
|
+
out: list[str] = []
|
|
155
|
+
for el in value.elements:
|
|
156
|
+
s = string_value(el.value)
|
|
157
|
+
if s is None:
|
|
158
|
+
return None
|
|
159
|
+
out.append(s)
|
|
160
|
+
return out
|
|
161
|
+
return None
|
|
@@ -16,22 +16,36 @@ from timbal.codegen import parse_fqn
|
|
|
16
16
|
from timbal.codegen.cst_utils import collect_assignments, resolve_runnable_name
|
|
17
17
|
from timbal.codegen.format import format_code
|
|
18
18
|
|
|
19
|
-
_transformer_modules = None
|
|
20
|
-
|
|
21
19
|
|
|
22
20
|
def load_modules() -> dict:
|
|
23
|
-
|
|
24
|
-
for
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
"""Import every transformer module. Used for discovery/introspection, not dispatch —
|
|
22
|
+
see :func:`_load_operation` for why running an operation must not import them all."""
|
|
23
|
+
return {
|
|
24
|
+
info.name: importlib.import_module(f"timbal.codegen.transformers.{info.name}")
|
|
25
|
+
for info in pkgutil.iter_modules([str(Path(__file__).parent)])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
28
|
|
|
29
|
+
def _module_names() -> set[str]:
|
|
30
|
+
"""Available operation names, discovered without importing anything."""
|
|
31
|
+
return {info.name for info in pkgutil.iter_modules([str(Path(__file__).parent)])}
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
def _load_operation(operation: str):
|
|
35
|
+
"""Import the single transformer an operation needs.
|
|
36
|
+
|
|
37
|
+
Deliberately not ``load_modules()``: importing every transformer to run one means a
|
|
38
|
+
failure in any of them (a bad import, a syntax error mid-edit) breaks every unrelated
|
|
39
|
+
operation too.
|
|
40
|
+
"""
|
|
41
|
+
if operation not in _module_names():
|
|
42
|
+
raise ValueError(f"unknown operation: {operation}")
|
|
43
|
+
try:
|
|
44
|
+
return importlib.import_module(f"timbal.codegen.transformers.{operation}")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"operation '{operation.replace('_', '-')}' failed to load: {type(e).__name__}: {e}"
|
|
48
|
+
) from e
|
|
35
49
|
|
|
36
50
|
|
|
37
51
|
class _UsageAnalyzer(cst.CSTVisitor):
|
|
@@ -301,10 +315,7 @@ def apply_operation(workspace_path: str | Path, operation: str, **kwargs) -> str
|
|
|
301
315
|
except cst.ParserSyntaxError as e:
|
|
302
316
|
raise ValueError(f"Cannot parse {spec.path}: {e}") from e
|
|
303
317
|
|
|
304
|
-
|
|
305
|
-
mod = modules.get(operation)
|
|
306
|
-
if mod is None:
|
|
307
|
-
raise ValueError(f"unknown operation: {operation}")
|
|
318
|
+
mod = _load_operation(operation)
|
|
308
319
|
|
|
309
320
|
args = SimpleNamespace(**kwargs)
|
|
310
321
|
result = mod.run(spec.target, args, tree=tree)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""add-guardrail: wire a guardrail shorthand into the Agent's guardrails list.
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
python -m timbal.codegen add-guardrail --spec "pii:redact"
|
|
5
|
+
python -m timbal.codegen add-guardrail --spec default # set guardrails="default"
|
|
6
|
+
python -m timbal.codegen add-guardrail --spec "moderation:warn" --step agent_a
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Semantics on the existing ``guardrails=`` kwarg:
|
|
10
|
+
|
|
11
|
+
- absent → ``guardrails=["<spec>"]`` (or ``guardrails="default"`` for the default preset)
|
|
12
|
+
- string ``"default"`` → expanded to its shorthand list, then the spec is merged in
|
|
13
|
+
- string shorthand → converted to a two-element list
|
|
14
|
+
- list of string literals → the spec is appended; an entry with the same rail *name*
|
|
15
|
+
(the part before ``:``) is replaced, since duplicate rail names are invalid at runtime
|
|
16
|
+
- anything non-literal (a variable, rail instances) → loud error; edit the code directly
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
|
|
21
|
+
import libcst as cst
|
|
22
|
+
|
|
23
|
+
from ..guardrail_specs import (
|
|
24
|
+
guardrails_kwarg_index,
|
|
25
|
+
literal_shorthands,
|
|
26
|
+
rail_name,
|
|
27
|
+
string_element,
|
|
28
|
+
validate_guardrail_target,
|
|
29
|
+
validate_shorthand,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
34
|
+
sp = subparsers.add_parser(
|
|
35
|
+
"add-guardrail",
|
|
36
|
+
help="Add a guardrail shorthand to the agent's guardrails list.",
|
|
37
|
+
)
|
|
38
|
+
sp.add_argument(
|
|
39
|
+
"--spec",
|
|
40
|
+
required=True,
|
|
41
|
+
help='Guardrail shorthand: "default", or "<name>[:action]" (e.g. "pii:redact", "injection:block").',
|
|
42
|
+
)
|
|
43
|
+
sp.add_argument(
|
|
44
|
+
"--step",
|
|
45
|
+
default=None,
|
|
46
|
+
help="Target step name within a Workflow. When provided, the guardrail is added to that step's Agent.",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _validate_spec(spec: str) -> None:
|
|
51
|
+
"""Reject unknown shorthands loudly at the CLI boundary."""
|
|
52
|
+
if spec.strip().lower() == "default":
|
|
53
|
+
return
|
|
54
|
+
validate_shorthand(spec)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def run(entry_point: str, args: argparse.Namespace, *, tree: cst.Module | None = None) -> cst.CSTTransformer:
|
|
58
|
+
_validate_spec(args.spec)
|
|
59
|
+
target, _assignments = validate_guardrail_target(
|
|
60
|
+
tree, entry_point, getattr(args, "step", None), "add-guardrail"
|
|
61
|
+
)
|
|
62
|
+
return GuardrailAdder(target, args.spec.strip())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class GuardrailAdder(cst.CSTTransformer):
|
|
66
|
+
def __init__(self, target: str, spec: str) -> None:
|
|
67
|
+
self.target = target
|
|
68
|
+
self.spec = spec
|
|
69
|
+
self.matched = False
|
|
70
|
+
|
|
71
|
+
def _edit_call(self, call: cst.Call) -> cst.Call:
|
|
72
|
+
self.matched = True
|
|
73
|
+
idx = guardrails_kwarg_index(call)
|
|
74
|
+
|
|
75
|
+
if self.spec.lower() == "default":
|
|
76
|
+
new_value: cst.BaseExpression = cst.SimpleString('"default"')
|
|
77
|
+
else:
|
|
78
|
+
current = [] if idx is None else literal_shorthands(call.args[idx].value)
|
|
79
|
+
if current is None:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"The existing guardrails= value is not a literal string/list of shorthands "
|
|
82
|
+
"(it may hold rail instances or a variable). Edit the source directly."
|
|
83
|
+
)
|
|
84
|
+
name = rail_name(self.spec)
|
|
85
|
+
merged = [s for s in current if rail_name(s) != name]
|
|
86
|
+
merged.append(self.spec)
|
|
87
|
+
if merged == current:
|
|
88
|
+
return call # already present — idempotent
|
|
89
|
+
new_value = cst.List(elements=[string_element(s) for s in merged])
|
|
90
|
+
|
|
91
|
+
new_arg = cst.Arg(keyword=cst.Name("guardrails"), value=new_value)
|
|
92
|
+
if idx is None:
|
|
93
|
+
return call.with_changes(args=[*call.args, new_arg])
|
|
94
|
+
return call.with_changes(args=[*call.args[:idx], new_arg, *call.args[idx + 1 :]])
|
|
95
|
+
|
|
96
|
+
def leave_Assign(self, original_node: cst.Assign, updated_node: cst.Assign) -> cst.Assign: # noqa: ARG002
|
|
97
|
+
for assign_target in updated_node.targets:
|
|
98
|
+
if (
|
|
99
|
+
isinstance(assign_target.target, cst.Name)
|
|
100
|
+
and assign_target.target.value == self.target
|
|
101
|
+
and isinstance(updated_node.value, cst.Call)
|
|
102
|
+
):
|
|
103
|
+
return updated_node.with_changes(value=self._edit_call(updated_node.value))
|
|
104
|
+
return updated_node
|
|
105
|
+
|
|
106
|
+
def leave_AnnAssign(self, original_node: cst.AnnAssign, updated_node: cst.AnnAssign) -> cst.AnnAssign: # noqa: ARG002
|
|
107
|
+
if (
|
|
108
|
+
isinstance(updated_node.target, cst.Name)
|
|
109
|
+
and updated_node.target.value == self.target
|
|
110
|
+
and isinstance(updated_node.value, cst.Call)
|
|
111
|
+
):
|
|
112
|
+
return updated_node.with_changes(value=self._edit_call(updated_node.value))
|
|
113
|
+
return updated_node
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""remove-guardrail: remove a guardrail from the Agent's guardrails list by rail name.
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
python -m timbal.codegen remove-guardrail --name pii
|
|
5
|
+
python -m timbal.codegen remove-guardrail --name injection --step agent_a
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
Matches list entries by rail *name* (the part before ``:``), so ``--name pii`` removes
|
|
9
|
+
``"pii"``, ``"pii:redact"``, or ``"pii:block"``. A ``guardrails="default"`` string is
|
|
10
|
+
expanded to its shorthand list first. Removing the last rail drops the kwarg entirely.
|
|
11
|
+
Removing an already-absent rail is an idempotent success.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
import libcst as cst
|
|
17
|
+
|
|
18
|
+
from ..guardrail_specs import (
|
|
19
|
+
guardrails_kwarg_index,
|
|
20
|
+
literal_shorthands,
|
|
21
|
+
rail_name,
|
|
22
|
+
string_element,
|
|
23
|
+
validate_guardrail_target,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
28
|
+
sp = subparsers.add_parser(
|
|
29
|
+
"remove-guardrail",
|
|
30
|
+
help="Remove a guardrail from the agent's guardrails list by rail name.",
|
|
31
|
+
)
|
|
32
|
+
sp.add_argument("--name", required=True, help='The rail name to remove (e.g. "pii", "injection").')
|
|
33
|
+
sp.add_argument(
|
|
34
|
+
"--step",
|
|
35
|
+
default=None,
|
|
36
|
+
help="Target step name within a Workflow. When provided, the guardrail is removed from that step's Agent.",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def run(entry_point: str, args: argparse.Namespace, *, tree: cst.Module | None = None) -> cst.CSTTransformer:
|
|
41
|
+
target, _assignments = validate_guardrail_target(
|
|
42
|
+
tree, entry_point, getattr(args, "step", None), "remove-guardrail"
|
|
43
|
+
)
|
|
44
|
+
return GuardrailRemover(target, args.name.strip().lower())
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class GuardrailRemover(cst.CSTTransformer):
|
|
48
|
+
# Removing an absent rail is an idempotent success.
|
|
49
|
+
allow_noop = True
|
|
50
|
+
|
|
51
|
+
def __init__(self, target: str, name: str) -> None:
|
|
52
|
+
self.target = target
|
|
53
|
+
self.name = name
|
|
54
|
+
|
|
55
|
+
def _edit_call(self, call: cst.Call) -> cst.Call:
|
|
56
|
+
idx = guardrails_kwarg_index(call)
|
|
57
|
+
if idx is None:
|
|
58
|
+
return call # nothing configured — idempotent
|
|
59
|
+
current = literal_shorthands(call.args[idx].value)
|
|
60
|
+
if current is None:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
"The existing guardrails= value is not a literal string/list of shorthands "
|
|
63
|
+
"(it may hold rail instances or a variable). Edit the source directly."
|
|
64
|
+
)
|
|
65
|
+
remaining = [s for s in current if rail_name(s) != self.name]
|
|
66
|
+
if remaining == current:
|
|
67
|
+
return call # not present — idempotent (still normalizes "default" only on a hit)
|
|
68
|
+
if not remaining:
|
|
69
|
+
return call.with_changes(args=[*call.args[:idx], *call.args[idx + 1 :]])
|
|
70
|
+
new_value = cst.List(elements=[string_element(s) for s in remaining])
|
|
71
|
+
new_arg = cst.Arg(keyword=cst.Name("guardrails"), value=new_value)
|
|
72
|
+
return call.with_changes(args=[*call.args[:idx], new_arg, *call.args[idx + 1 :]])
|
|
73
|
+
|
|
74
|
+
def leave_Assign(self, original_node: cst.Assign, updated_node: cst.Assign) -> cst.Assign: # noqa: ARG002
|
|
75
|
+
for assign_target in updated_node.targets:
|
|
76
|
+
if (
|
|
77
|
+
isinstance(assign_target.target, cst.Name)
|
|
78
|
+
and assign_target.target.value == self.target
|
|
79
|
+
and isinstance(updated_node.value, cst.Call)
|
|
80
|
+
):
|
|
81
|
+
return updated_node.with_changes(value=self._edit_call(updated_node.value))
|
|
82
|
+
return updated_node
|
|
83
|
+
|
|
84
|
+
def leave_AnnAssign(self, original_node: cst.AnnAssign, updated_node: cst.AnnAssign) -> cst.AnnAssign: # noqa: ARG002
|
|
85
|
+
if (
|
|
86
|
+
isinstance(updated_node.target, cst.Name)
|
|
87
|
+
and updated_node.target.value == self.target
|
|
88
|
+
and isinstance(updated_node.value, cst.Call)
|
|
89
|
+
):
|
|
90
|
+
return updated_node.with_changes(value=self._edit_call(updated_node.value))
|
|
91
|
+
return updated_node
|