tavus-cli 0.10.2__tar.gz → 0.10.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.
- tavus_cli-0.10.4/.release-please-manifest.json +3 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/CHANGELOG.md +14 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/PKG-INFO +1 -1
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/packages/tavus/pyproject.toml +2 -2
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/packages/tavus-mcp/pyproject.toml +2 -2
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/pyproject.toml +1 -1
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/skills/tavus-agentic-pals/SKILL.md +15 -1
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/skills/tavus-agentic-pals/references/build-and-verify.md +4 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/skills/tavus-agentic-pals/references/chat-mode.md +18 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/__init__.py +1 -1
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/build_and_verify.py +2 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/carrier_builder.py +36 -19
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/schemas/pal.py +10 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/server.py +14 -1
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/integration/test_mcp_server.py +21 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_build_and_verify.py +151 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_pal_schema.py +24 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/uv.lock +1 -1
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/package-lock.json +2 -2
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/package.json +1 -1
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/src/tavus.ts +13 -5
- tavus_cli-0.10.4/workers/tavus-mcp/src/version.ts +1 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/test/tavus.test.ts +42 -1
- tavus_cli-0.10.2/.release-please-manifest.json +0 -3
- tavus_cli-0.10.2/workers/tavus-mcp/src/version.ts +0 -1
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.agents/plugins/marketplace.json +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.env.example +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.github/workflows/ci.yml +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.github/workflows/deploy-cloudflare-worker.yml +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.github/workflows/pr-reviews.yml +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.github/workflows/pypi-cli.yml +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.github/workflows/tail-cloudflare-worker.yml +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.github/workflows/validate-staging-deployment.yml +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/.gitignore +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/AGENTS.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/PROMPT.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/README.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/docs/builder-guidance.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/docs/chatgpt-plugin.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/docs/gemini-enterprise/README.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/docs/gemini-enterprise/listing.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/docs/gemini-enterprise/test-queries.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/docs/usage-observability.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/packages/tavus/README.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/packages/tavus-mcp/README.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/plugins/tavus-pal/assets/icon-dark.svg +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/plugins/tavus-pal/assets/icon.svg +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/plugins/tavus-pal/mcp.json +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/plugins/tavus-pal/plugin.json +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/release-please-config.json +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/scripts/__init__.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/scripts/build_chatgpt_plugin.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/scripts/check_release_versions.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/skills/tavus-agentic-pals/agents/openai.yaml +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/skills/tavus-agentic-pals/references/builder-workflow.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/skills/tavus-agentic-pals/references/examples.md +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/builder_guidance.json +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/builder_guidance.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/cli/__init__.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/cli/main.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/__init__.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/auth/__init__.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/auth/keyring_store.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/auth/oauth.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/auth/session.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/client/__init__.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/client/http.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/daily_transport.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/env.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/errors.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/pal_urls.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/patch.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/__init__.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/capabilities.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/deployments.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/knowledge.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/options.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/quickstart.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/scaffold_embed.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/templates.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/recipes/tool_reuse.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/schemas/__init__.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/schemas/deployment.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/schemas/file_manifest.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/schemas/guardrail.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/schemas/objective.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/schemas/pronunciation.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/src/tavus_mcp/sdk/schemas/tool.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/conftest.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/integration/test_cli.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/integration/test_client.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/integration/test_oauth.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_auth.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_builder.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_capabilities.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_daily_transport.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_deployments.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_env.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_guardrail_schema.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_objective_schema.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_patch.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_pronunciation_schema.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_release_versions.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_scaffold_embed.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_tool_reuse.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/tests/unit/test_tool_schema.py +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/.dev.vars.example +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/src/index.ts +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/src/oauth-grant.ts +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/src/oauth-session.ts +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/src/oauth.ts +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/test/builder-guidance.test.ts +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/test/oauth-session.test.ts +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/test/parity.test.ts +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/test/worker-runtime.ts +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/tsconfig.json +0 -0
- {tavus_cli-0.10.2 → tavus_cli-0.10.4}/workers/tavus-mcp/wrangler.jsonc +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.4](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.10.3...v0.10.4) (2026-10-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* support PAL meeting setup, dispatch, and all verification probes ([#80](https://github.com/Tavus-Engineering/tavus-mcp/issues/80)) ([c635462](https://github.com/Tavus-Engineering/tavus-mcp/commit/c635462493098269f3361c2f0ff2d2fc3869bece))
|
|
9
|
+
|
|
10
|
+
## [0.10.3](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.10.2...v0.10.3) (2026-10-16)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* preserve every received Builder reply in MCP history ([#78](https://github.com/Tavus-Engineering/tavus-mcp/issues/78)) ([7dab660](https://github.com/Tavus-Engineering/tavus-mcp/commit/7dab660ddaa3110f208f343e90981217c10cdb20))
|
|
16
|
+
|
|
3
17
|
## [0.10.2](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.10.1...v0.10.2) (2026-10-16)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tavus"
|
|
3
|
-
version = "0.10.
|
|
3
|
+
version = "0.10.4" # x-release-please-version
|
|
4
4
|
description = "Official alias for tavus-cli, the Tavus CLI and MCP server."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"tavus-cli==0.10.
|
|
8
|
+
"tavus-cli==0.10.4", # x-release-please-version
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
[project.urls]
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tavus-mcp"
|
|
3
|
-
version = "0.10.
|
|
3
|
+
version = "0.10.4" # x-release-please-version
|
|
4
4
|
description = "Official alias for tavus-cli, which provides the tavus-mcp MCP server."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"tavus-cli==0.10.
|
|
8
|
+
"tavus-cli==0.10.4", # x-release-please-version
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
[project.urls]
|
|
@@ -136,7 +136,7 @@ Knowledge document tools:
|
|
|
136
136
|
Conversation and resource tools:
|
|
137
137
|
|
|
138
138
|
- `tavus_face_list(limit, stock)`
|
|
139
|
-
- `tavus_conversation_create(pal_id
|
|
139
|
+
- `tavus_conversation_create(pal_id?, face_id?, conversation_name?, meeting_url?)`
|
|
140
140
|
- `tavus_conversation_end(conversation_id)`
|
|
141
141
|
- `tavus_quickstart(system_prompt, pal_name, face_id)`
|
|
142
142
|
- `tavus_resource_list(resource, limit)` for `guardrails`, `objectives`, `documents`, `voices`, `skills`, or `tools`
|
|
@@ -279,6 +279,20 @@ Prefer focused patches over full rebuilds:
|
|
|
279
279
|
|
|
280
280
|
Common patch targets include `/system_prompt`, `/greeting`, `/context`, `/pal_name`, `/default_face_id`, `/objectives_id`, `/guardrail_ids`, `/guardrail_tags`, `/document_ids`, `/document_tags`, and nested layer paths under `/layers/llm`, `/layers/tts`, `/layers/stt`, `/layers/perception`, `/layers/conversational_flow`, `/layers/capabilities`, and `/layers/knowledge_base`.
|
|
281
281
|
|
|
282
|
+
For meeting participation, set `wake_phrase` and `sleep_phrase` under
|
|
283
|
+
`/layers/conversational_flow`. Save both phrases together; a sleep phrase
|
|
284
|
+
requires a wake phrase. Configure `/layers/conferencing` in a separate patch
|
|
285
|
+
with `username`, `allowlist`, and `whitelabel`. The API rejects a patch that
|
|
286
|
+
mixes conferencing with behavior settings. A default face must be selected
|
|
287
|
+
before claiming a conferencing identity. Read back `layers.conferencing.email`
|
|
288
|
+
for the invite address; do not construct it yourself. Before joining a meeting,
|
|
289
|
+
ask the creator for its link and confirm that the PAL is expected there.
|
|
290
|
+
Creating a conferencing identity does not join a call. Test waking and sleeping
|
|
291
|
+
in a real voice call before claiming the meeting behavior works.
|
|
292
|
+
To send the PAL to the supplied meeting link, call `tavus_conversation_create`
|
|
293
|
+
with `pal_id` and `meeting_url`. A successful response confirms dispatch, not
|
|
294
|
+
admission. Check the meeting participant list before saying the PAL joined.
|
|
295
|
+
|
|
282
296
|
## Creation Patterns
|
|
283
297
|
|
|
284
298
|
Use `tavus_quickstart` or `uv run tavus quickstart` when the user wants a pal plus a conversation quickly. It uses the first stock face unless a face ID is provided.
|
{tavus_cli-0.10.2 → tavus_cli-0.10.4}/skills/tavus-agentic-pals/references/build-and-verify.md
RENAMED
|
@@ -47,6 +47,10 @@ tavus_pal_build_and_verify(
|
|
|
47
47
|
)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
The hosted connector accepts up to five supplied probes and retains all of
|
|
51
|
+
them. Generated probes are capped at three. The smoke test has a shared time
|
|
52
|
+
budget; a probe that cannot run is reported in the transcript with an error.
|
|
53
|
+
|
|
50
54
|
### Changing an existing PAL (hosted connector)
|
|
51
55
|
|
|
52
56
|
Pass `pal_id` to run the same drafters on an existing PAL. Put the requested
|
|
@@ -88,6 +88,24 @@ timeout covers the join, but budget the first `collect_turn` generously.
|
|
|
88
88
|
Always `close()` the session and end the conversation in `finally:` —
|
|
89
89
|
orphaned chat sessions still consume quota.
|
|
90
90
|
|
|
91
|
+
## Conversation history
|
|
92
|
+
|
|
93
|
+
Reuse the returned `conversation_id` for each turn of the same test. CVI owns
|
|
94
|
+
that conversation's user and assistant history; starting another conversation
|
|
95
|
+
does not restore the previous test. In chat mode, CVI must commit each reply
|
|
96
|
+
before delivering it, without waiting for audio playback. The MCP client does
|
|
97
|
+
not replay earlier turns into the prompt. If a PAL repeats answered questions,
|
|
98
|
+
check that its delivered replies appear in the next CVI model input; seeing
|
|
99
|
+
them in the tool result alone does not prove they were retained.
|
|
100
|
+
|
|
101
|
+
Builder history is separate. `tavus_pal_builder_history` reads the creator's
|
|
102
|
+
saved dialogue with Charlie, not the PAL's test conversation. The local
|
|
103
|
+
Builder recipe saves each received reply before executing tools or requesting
|
|
104
|
+
a continuation, so a later timeout does not erase earlier dialogue. Automatic
|
|
105
|
+
drafting instructions and test/refinement prompts are not saved as creator
|
|
106
|
+
messages. A resumed Builder receives a bounded snapshot from request-handler;
|
|
107
|
+
the full saved dialogue remains available through the history tools.
|
|
108
|
+
|
|
91
109
|
For fully offline unit tests, monkeypatch
|
|
92
110
|
`daily_transport.open_conversation_session` with a scripted fake session (see
|
|
93
111
|
`tests/unit/test_build_and_verify.py::FakeSession`).
|
|
@@ -114,6 +114,8 @@ async def build_and_verify(
|
|
|
114
114
|
answer_provider=None,
|
|
115
115
|
on_builder_reply=None,
|
|
116
116
|
transcript=build_transcript,
|
|
117
|
+
record_creator_prompt=False,
|
|
118
|
+
creator_request=prompt,
|
|
117
119
|
)
|
|
118
120
|
await client.builders.publish(builder_id)
|
|
119
121
|
spec = await _assemble_spec(client, pal_id)
|
|
@@ -95,15 +95,19 @@ async def run_carrier_build(
|
|
|
95
95
|
answer_provider: AnswerProvider | None,
|
|
96
96
|
on_builder_reply: BuildProgress | None,
|
|
97
97
|
transcript: list[dict[str, Any]],
|
|
98
|
+
record_creator_prompt: bool = True,
|
|
99
|
+
creator_request: str | None = None,
|
|
98
100
|
) -> str:
|
|
99
101
|
"""Build or refine one PAL through a real, headless Charlie conversation."""
|
|
102
|
+
initial_request = creator_request if creator_request is not None else prompt
|
|
100
103
|
runtime: dict[str, Any] = {
|
|
101
104
|
"pal_id": pal_id,
|
|
102
105
|
"builder_id": builder_id,
|
|
103
106
|
"read_documents": {},
|
|
104
107
|
"drafted": False,
|
|
108
|
+
"creator_request": initial_request,
|
|
105
109
|
}
|
|
106
|
-
initial_context = await build_carrier_context(client, runtime, initial_request=
|
|
110
|
+
initial_context = await build_carrier_context(client, runtime, initial_request=initial_request)
|
|
107
111
|
created = await client.conversations.create_headless_builder(
|
|
108
112
|
{
|
|
109
113
|
"builder_id": builder_id,
|
|
@@ -134,6 +138,7 @@ async def run_carrier_build(
|
|
|
134
138
|
human_driven = answer_provider is not None or answers is not None
|
|
135
139
|
answer_index = 0
|
|
136
140
|
next_message = _complete_brief(prompt) if not human_driven else prompt
|
|
141
|
+
creator_message = prompt if record_creator_prompt else None
|
|
137
142
|
max_creator_turns = max_rounds + 1 if human_driven else max_rounds
|
|
138
143
|
|
|
139
144
|
try:
|
|
@@ -157,7 +162,8 @@ async def run_carrier_build(
|
|
|
157
162
|
client,
|
|
158
163
|
runtime,
|
|
159
164
|
next_message,
|
|
160
|
-
|
|
165
|
+
creator_message=creator_message,
|
|
166
|
+
initial_request=initial_request,
|
|
161
167
|
transcript=transcript,
|
|
162
168
|
)
|
|
163
169
|
if on_builder_reply is not None:
|
|
@@ -176,6 +182,7 @@ async def run_carrier_build(
|
|
|
176
182
|
answer_index += 1
|
|
177
183
|
else:
|
|
178
184
|
break
|
|
185
|
+
creator_message = next_message
|
|
179
186
|
continue
|
|
180
187
|
|
|
181
188
|
if runtime["drafted"]:
|
|
@@ -185,6 +192,7 @@ async def run_carrier_build(
|
|
|
185
192
|
"Do not ask me another setup question: name the PAL and dispatch "
|
|
186
193
|
"a complete first draft now."
|
|
187
194
|
)
|
|
195
|
+
creator_message = None
|
|
188
196
|
|
|
189
197
|
if not runtime["drafted"]:
|
|
190
198
|
await _run_creator_turn(
|
|
@@ -192,7 +200,8 @@ async def run_carrier_build(
|
|
|
192
200
|
runtime,
|
|
193
201
|
"The build must complete now. Infer any remaining choices from the "
|
|
194
202
|
"original brief and call draft_pal for a complete first draft.",
|
|
195
|
-
|
|
203
|
+
creator_message=None,
|
|
204
|
+
initial_request=initial_request,
|
|
196
205
|
transcript=transcript,
|
|
197
206
|
)
|
|
198
207
|
if not runtime["drafted"]:
|
|
@@ -221,27 +230,43 @@ async def _run_creator_turn(
|
|
|
221
230
|
runtime: dict[str, Any],
|
|
222
231
|
message: str,
|
|
223
232
|
*,
|
|
233
|
+
creator_message: str | None,
|
|
224
234
|
initial_request: str,
|
|
225
235
|
transcript: list[dict[str, Any]],
|
|
226
236
|
) -> dict[str, Any]:
|
|
227
237
|
builder_id = str(runtime["builder_id"])
|
|
228
238
|
session = runtime["session"]
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
239
|
+
if creator_message:
|
|
240
|
+
runtime["creator_request"] = creator_message
|
|
241
|
+
await client.builders.append_messages(
|
|
242
|
+
builder_id, [{"role": "user", "content": creator_message}]
|
|
243
|
+
)
|
|
244
|
+
transcript.append({"role": "user", "text": creator_message})
|
|
233
245
|
|
|
234
246
|
reply = await _send_and_collect(session, message)
|
|
235
247
|
tool_names: list[str] = []
|
|
236
|
-
|
|
248
|
+
reply_texts: list[str] = []
|
|
249
|
+
for hop in range(MAX_TOOL_HOPS + 1):
|
|
250
|
+
text = str(reply.get("text") or "").strip()
|
|
251
|
+
if text:
|
|
252
|
+
# Save before tool work or another inference can fail and lose this reply.
|
|
253
|
+
await client.builders.append_messages(
|
|
254
|
+
builder_id, [{"role": "assistant", "content": text}]
|
|
255
|
+
)
|
|
256
|
+
transcript.append({"role": "assistant", "text": text})
|
|
257
|
+
reply_texts.append(text)
|
|
237
258
|
calls = list(reply.pop("tool_calls", []))
|
|
238
|
-
if not calls:
|
|
259
|
+
if not calls or hop == MAX_TOOL_HOPS:
|
|
239
260
|
break
|
|
240
261
|
outcomes: list[str] = []
|
|
241
262
|
for call in calls:
|
|
242
263
|
name = str(call.get("name") or "")
|
|
243
264
|
tool_names.append(name)
|
|
244
|
-
|
|
265
|
+
# RQH persists the drafting message too. Keep transport instructions
|
|
266
|
+
# and automated nudges out of the creator's saved dialogue.
|
|
267
|
+
outcome = await _execute_carrier_tool(
|
|
268
|
+
client, runtime, call, str(runtime["creator_request"])
|
|
269
|
+
)
|
|
245
270
|
outcomes.append(outcome)
|
|
246
271
|
tool_call_id = str(call.get("tool_call_id") or "")
|
|
247
272
|
if tool_call_id:
|
|
@@ -270,21 +295,13 @@ async def _run_creator_turn(
|
|
|
270
295
|
)
|
|
271
296
|
reply = await _send_and_collect(session, followup)
|
|
272
297
|
|
|
273
|
-
text = str(reply.get("text") or "").strip()
|
|
274
|
-
if not text:
|
|
275
|
-
text = _tool_narration(reply, tool_names)
|
|
276
298
|
structured_reply = {
|
|
277
|
-
"text":
|
|
299
|
+
"text": "\n\n".join(reply_texts) or _tool_narration(reply, tool_names),
|
|
278
300
|
"tool_names": tool_names,
|
|
279
301
|
"draft_ready": bool(runtime["drafted"]),
|
|
280
302
|
"targets": [],
|
|
281
303
|
"suggestions": [],
|
|
282
304
|
}
|
|
283
|
-
transcript.append({"role": "assistant", **structured_reply})
|
|
284
|
-
if text:
|
|
285
|
-
await client.builders.append_messages(
|
|
286
|
-
builder_id, [{"role": "assistant", "content": text}]
|
|
287
|
-
)
|
|
288
305
|
return structured_reply
|
|
289
306
|
|
|
290
307
|
|
|
@@ -74,9 +74,18 @@ class ConversationalFlowLayerPatchModel(BaseModel):
|
|
|
74
74
|
voice_isolation: str | None = None
|
|
75
75
|
noise_cancellation: str | None = None
|
|
76
76
|
wake_phrase: str | None = None
|
|
77
|
+
sleep_phrase: str | None = None
|
|
77
78
|
idle_engagement: str | None = None
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
class ConferencingLayerPatchModel(BaseModel):
|
|
82
|
+
model_config = ConfigDict(extra="forbid")
|
|
83
|
+
|
|
84
|
+
username: str | None = None
|
|
85
|
+
allowlist: list[str] | None = None
|
|
86
|
+
whitelabel: bool | None = None
|
|
87
|
+
|
|
88
|
+
|
|
80
89
|
class CapabilitiesLayerPatchModel(BaseModel):
|
|
81
90
|
model_config = ConfigDict(extra="forbid")
|
|
82
91
|
|
|
@@ -106,6 +115,7 @@ class LayersPatchModel(BaseModel):
|
|
|
106
115
|
transport: dict[str, Any] | None = None
|
|
107
116
|
perception: PerceptionLayerPatchModel | None = None
|
|
108
117
|
conversational_flow: ConversationalFlowLayerPatchModel | None = None
|
|
118
|
+
conferencing: ConferencingLayerPatchModel | None = None
|
|
109
119
|
capabilities: CapabilitiesLayerPatchModel | None = None
|
|
110
120
|
knowledge_base: KnowledgeBaseLayerPatchModel | None = None
|
|
111
121
|
|
|
@@ -427,7 +427,12 @@ async def tavus_patch_pal(pal_id: str, ops: list[dict[str, Any]]) -> Any:
|
|
|
427
427
|
|
|
428
428
|
Tools are first-class now: prefer tavus_tool_create + tavus_pal_tools_attach
|
|
429
429
|
over writing tools inline here. Inline layer tool paths are rejected by the
|
|
430
|
-
patch validator.
|
|
430
|
+
patch validator. Set wake_phrase and sleep_phrase under
|
|
431
|
+
/layers/conversational_flow. Patch /layers/conferencing separately from
|
|
432
|
+
behavior settings; it accepts username, allowlist, and whitelabel.
|
|
433
|
+
Configuring an identity does not join a call. Ask the creator for the
|
|
434
|
+
meeting link before joining.
|
|
435
|
+
"""
|
|
431
436
|
async with TavusClient.from_env(surface="mcp") as client:
|
|
432
437
|
return await client.pals.patch(pal_id, ops)
|
|
433
438
|
|
|
@@ -853,13 +858,21 @@ async def tavus_conversation_create(
|
|
|
853
858
|
pal_id: str | None = None,
|
|
854
859
|
face_id: str | None = None,
|
|
855
860
|
conversation_name: str | None = None,
|
|
861
|
+
meeting_url: str | None = None,
|
|
856
862
|
) -> Any:
|
|
863
|
+
"""Create a conversation, or send a PAL to an existing meeting.
|
|
864
|
+
|
|
865
|
+
Ask the creator for the meeting link before setting meeting_url. The PAL
|
|
866
|
+
needs a conferencing identity and a default face. Creation confirms the
|
|
867
|
+
request was sent; verify admission in the meeting before reporting joined.
|
|
868
|
+
"""
|
|
857
869
|
# /v2/conversations is not part of the pal/face alias, so the body keeps the
|
|
858
870
|
# underlying persona_id/replica_id wire fields (id values are identical).
|
|
859
871
|
body = {
|
|
860
872
|
"persona_id": pal_id,
|
|
861
873
|
"replica_id": face_id,
|
|
862
874
|
"conversation_name": conversation_name,
|
|
875
|
+
"meeting_url": meeting_url,
|
|
863
876
|
}
|
|
864
877
|
async with TavusClient.from_env(surface="mcp") as client:
|
|
865
878
|
return await client.conversations.create({k: v for k, v in body.items() if v})
|
|
@@ -23,6 +23,27 @@ from tavus_mcp.server import (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
@pytest.mark.asyncio
|
|
27
|
+
@respx.mock
|
|
28
|
+
async def test_mcp_dispatches_pal_to_requested_meeting(clean_env: Path) -> None:
|
|
29
|
+
(clean_env / ".env").write_text(
|
|
30
|
+
"TAVUS_ENV=TEST\nTAVUS_API_KEY=test-key\n"
|
|
31
|
+
"TAVUS_PUBLIC_API_BASE_URL=https://api.example.test/v2\n"
|
|
32
|
+
)
|
|
33
|
+
route = respx.post("https://api.example.test/v2/conversations").respond(
|
|
34
|
+
200, json={"conversation_id": "c-meeting", "status": "active"}
|
|
35
|
+
)
|
|
36
|
+
async with Client(mcp) as client:
|
|
37
|
+
await client.call_tool("tavus_conversation_create", {
|
|
38
|
+
"pal_id": "p-meeting",
|
|
39
|
+
"meeting_url": "https://meet.google.com/abc-defg-hij",
|
|
40
|
+
})
|
|
41
|
+
assert json.loads(route.calls.last.request.content) == {
|
|
42
|
+
"persona_id": "p-meeting",
|
|
43
|
+
"meeting_url": "https://meet.google.com/abc-defg-hij",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
26
47
|
@pytest.mark.asyncio
|
|
27
48
|
async def test_builder_guidance_reaches_mcp_clients() -> None:
|
|
28
49
|
async with Client(mcp) as client:
|
|
@@ -398,6 +398,10 @@ async def test_build_uses_headless_carrier_and_pal_maker_model(
|
|
|
398
398
|
assert len(builder_session.sent_texts) == 2
|
|
399
399
|
assert routes["fanout"].call_count == 1
|
|
400
400
|
fanout_body = json.loads(routes["fanout"].calls.last.request.read())
|
|
401
|
+
assert fanout_body["message"] == (
|
|
402
|
+
"Build a warm office greeter for delivery drivers. Route packages to "
|
|
403
|
+
"the lobby shelf and never request access codes."
|
|
404
|
+
)
|
|
401
405
|
assert fanout_body["briefs"] == {
|
|
402
406
|
"personality": "Warm office greeter for delivery drivers.",
|
|
403
407
|
"objectives": "Route each delivery to the lobby shelf.",
|
|
@@ -490,6 +494,153 @@ def test_map_draft_briefs_merges_facts_into_personality() -> None:
|
|
|
490
494
|
}
|
|
491
495
|
|
|
492
496
|
|
|
497
|
+
@pytest.mark.asyncio
|
|
498
|
+
@pytest.mark.parametrize("closeout", ["The greeting is updated.", None])
|
|
499
|
+
@respx.mock
|
|
500
|
+
async def test_carrier_preserves_replies_before_a_tool_followup(
|
|
501
|
+
clean_env: Path, monkeypatch: pytest.MonkeyPatch, closeout: str | None
|
|
502
|
+
) -> None:
|
|
503
|
+
"""Regression: tool continuations lost earlier replies, especially on timeout."""
|
|
504
|
+
_write_env(clean_env)
|
|
505
|
+
routes = _register_build_routes()
|
|
506
|
+
saved: list[dict[str, str]] = []
|
|
507
|
+
|
|
508
|
+
def append(request):
|
|
509
|
+
saved.extend(json.loads(request.content)["messages"])
|
|
510
|
+
return Response(200, json=saved)
|
|
511
|
+
|
|
512
|
+
routes["append"].mock(side_effect=append)
|
|
513
|
+
respx.get(f"{PUBLIC}/builder/b1").mock(
|
|
514
|
+
side_effect=lambda _: Response(200, json={"session": {"chat_history": saved}})
|
|
515
|
+
)
|
|
516
|
+
first = "I will keep the no-Memory decision and update the greeting."
|
|
517
|
+
turns = [
|
|
518
|
+
{
|
|
519
|
+
"text": first,
|
|
520
|
+
"tool_calls": [
|
|
521
|
+
{
|
|
522
|
+
"name": "draft_pal",
|
|
523
|
+
"tool_call_id": "greeting",
|
|
524
|
+
"arguments": json.dumps({"greeting_brief": "Ask about deliveries."}),
|
|
525
|
+
}
|
|
526
|
+
],
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
if closeout:
|
|
530
|
+
turns.append({"text": closeout, "tool_calls": []})
|
|
531
|
+
session = FakeSession("c_builder", turns)
|
|
532
|
+
|
|
533
|
+
async def open_session(*_args: Any, **_kwargs: Any) -> FakeSession:
|
|
534
|
+
return session
|
|
535
|
+
|
|
536
|
+
monkeypatch.setattr(daily_transport, "open_conversation_session", open_session)
|
|
537
|
+
transcript: list[dict[str, Any]] = []
|
|
538
|
+
async with await _make_client() as client:
|
|
539
|
+
|
|
540
|
+
async def run():
|
|
541
|
+
return await carrier_builder.run_carrier_build(
|
|
542
|
+
client,
|
|
543
|
+
builder_id="b1",
|
|
544
|
+
pal_id="p1",
|
|
545
|
+
prompt="Change the greeting.",
|
|
546
|
+
max_rounds=1,
|
|
547
|
+
answers=[],
|
|
548
|
+
answer_provider=None,
|
|
549
|
+
on_builder_reply=None,
|
|
550
|
+
transcript=transcript,
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
if closeout:
|
|
554
|
+
await run()
|
|
555
|
+
else:
|
|
556
|
+
with pytest.raises(TavusError, match="No CVI reply"):
|
|
557
|
+
await run()
|
|
558
|
+
history = await client.builders.chat_history("b1")
|
|
559
|
+
|
|
560
|
+
expected = [
|
|
561
|
+
{"role": "user", "content": "Change the greeting."},
|
|
562
|
+
{"role": "assistant", "content": first},
|
|
563
|
+
]
|
|
564
|
+
if closeout:
|
|
565
|
+
expected.append({"role": "assistant", "content": closeout})
|
|
566
|
+
assert history["messages"] == expected
|
|
567
|
+
assert [{"role": row["role"], "content": row["text"]} for row in transcript] == expected
|
|
568
|
+
assert session.closed
|
|
569
|
+
assert routes["builder_end"].called
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@pytest.mark.asyncio
|
|
573
|
+
@pytest.mark.parametrize("max_rounds", [1, 2])
|
|
574
|
+
@respx.mock
|
|
575
|
+
async def test_carrier_does_not_save_automatic_nudges_as_creator_messages(
|
|
576
|
+
clean_env: Path, monkeypatch: pytest.MonkeyPatch, max_rounds: int
|
|
577
|
+
) -> None:
|
|
578
|
+
"""Regression: resuming a build must not attribute client instructions to its creator."""
|
|
579
|
+
_write_env(clean_env)
|
|
580
|
+
routes = _register_build_routes()
|
|
581
|
+
session = FakeSession(
|
|
582
|
+
"c_builder",
|
|
583
|
+
[
|
|
584
|
+
{"text": "Who will use this PAL?", "tool_calls": []},
|
|
585
|
+
{
|
|
586
|
+
"text": "I will draft the greeter.",
|
|
587
|
+
"tool_calls": [
|
|
588
|
+
{
|
|
589
|
+
"name": "draft_pal",
|
|
590
|
+
"tool_call_id": "draft",
|
|
591
|
+
"arguments": json.dumps({"sections_brief": "An office greeter."}),
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
},
|
|
595
|
+
{"text": "The greeter is ready.", "tool_calls": []},
|
|
596
|
+
],
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
refinement = FakeSession("c_builder", [
|
|
600
|
+
{"text": "I will refine the greeting.", "tool_calls": [{
|
|
601
|
+
"name": "draft_pal", "tool_call_id": "refine",
|
|
602
|
+
"arguments": json.dumps({"greeting_brief": "Ask about deliveries."}),
|
|
603
|
+
}]},
|
|
604
|
+
{"text": "The greeting is ready.", "tool_calls": []},
|
|
605
|
+
])
|
|
606
|
+
builders = iter([session, refinement])
|
|
607
|
+
|
|
608
|
+
async def open_session(conversation_id: str, *_args: Any, **_kwargs: Any) -> FakeSession:
|
|
609
|
+
if conversation_id == "c_builder":
|
|
610
|
+
return next(builders)
|
|
611
|
+
return FakeSession("c_smoke", [{"text": "Hello!", "tool_calls": []}])
|
|
612
|
+
|
|
613
|
+
routes["judge"].mock(side_effect=[
|
|
614
|
+
Response(200, json={"overall": "partial", "summary": "Improve the greeting."}),
|
|
615
|
+
Response(200, json={"overall": "pass"}),
|
|
616
|
+
])
|
|
617
|
+
monkeypatch.setattr(daily_transport, "open_conversation_session", open_session)
|
|
618
|
+
async with await _make_client() as client:
|
|
619
|
+
result = await build_and_verify(
|
|
620
|
+
client, prompt="Build an office greeter.", face_id="rabc",
|
|
621
|
+
max_rounds=max_rounds, probes=["Hello"], auto_refine=True, max_refine_rounds=1,
|
|
622
|
+
)
|
|
623
|
+
transcript = result["build_transcript"]
|
|
624
|
+
|
|
625
|
+
saved = [
|
|
626
|
+
row
|
|
627
|
+
for call in routes["append"].calls
|
|
628
|
+
for row in json.loads(call.request.content)["messages"]
|
|
629
|
+
]
|
|
630
|
+
assert [row for row in saved if row["role"] == "user"] == [
|
|
631
|
+
{"role": "user", "content": "Build an office greeter."},
|
|
632
|
+
]
|
|
633
|
+
assert [row for row in transcript if row["role"] == "user"] == [
|
|
634
|
+
{"role": "user", "text": "Build an office greeter."},
|
|
635
|
+
]
|
|
636
|
+
assert len(session.sent_texts) == 3
|
|
637
|
+
# RQH also saves this field; automated drafting must not add a fake user turn.
|
|
638
|
+
assert all(
|
|
639
|
+
json.loads(call.request.content)["message"] == "Build an office greeter."
|
|
640
|
+
for call in routes["fanout"].calls
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
|
|
493
644
|
@pytest.mark.parametrize(
|
|
494
645
|
"args",
|
|
495
646
|
[
|
|
@@ -3,9 +3,33 @@ from __future__ import annotations
|
|
|
3
3
|
import pytest
|
|
4
4
|
from pydantic import ValidationError
|
|
5
5
|
|
|
6
|
+
from tavus_mcp.sdk.patch import validate_patch_operations
|
|
6
7
|
from tavus_mcp.sdk.schemas.pal import PalCreate
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
def test_meeting_pal_accepts_activation_phrases_and_conferencing_settings() -> None:
|
|
11
|
+
layers = {
|
|
12
|
+
"conversational_flow": {"wake_phrase": "Hey Morgan", "sleep_phrase": "Goodbye Morgan"},
|
|
13
|
+
"conferencing": {
|
|
14
|
+
"username": "morgan-rehearsal",
|
|
15
|
+
"allowlist": ["host@example.com"],
|
|
16
|
+
"whitelabel": False,
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
pal = PalCreate.model_validate({"system_prompt": "Be a skeptical customer.", "layers": layers})
|
|
20
|
+
saved = pal.model_dump(exclude_none=True)["layers"]
|
|
21
|
+
assert saved["conversational_flow"] == layers["conversational_flow"]
|
|
22
|
+
assert saved["conferencing"] == layers["conferencing"]
|
|
23
|
+
for layer, fields in layers.items():
|
|
24
|
+
for field, value in fields.items():
|
|
25
|
+
validate_patch_operations(
|
|
26
|
+
[{"op": "add", "path": f"/layers/{layer}/{field}", "value": value}]
|
|
27
|
+
)
|
|
28
|
+
validate_patch_operations(
|
|
29
|
+
[{"op": "add", "path": "/layers/conferencing/allowlist/-", "value": "other@example.com"}]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
9
33
|
def test_full_pipeline_requires_system_prompt() -> None:
|
|
10
34
|
with pytest.raises(ValidationError, match="system_prompt is required"):
|
|
11
35
|
PalCreate.model_validate({"pal_name": "p"})
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tavus/tavus-mcp-worker",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@tavus/tavus-mcp-worker",
|
|
9
|
-
"version": "0.10.
|
|
9
|
+
"version": "0.10.4",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
12
12
|
"agents": "^0.12.2",
|
|
@@ -385,6 +385,12 @@ const PATCHABLE_PATHS = [
|
|
|
385
385
|
"/layers/capabilities/mcp_enabled",
|
|
386
386
|
"/layers/capabilities/search_enabled",
|
|
387
387
|
"/layers/capabilities/search_results_prompt_injection_prevention_enabled",
|
|
388
|
+
"/layers/conferencing",
|
|
389
|
+
"/layers/conferencing/allowlist",
|
|
390
|
+
"/layers/conferencing/allowlist/-",
|
|
391
|
+
"/layers/conferencing/allowlist/0",
|
|
392
|
+
"/layers/conferencing/username",
|
|
393
|
+
"/layers/conferencing/whitelabel",
|
|
388
394
|
"/layers/conversational_flow",
|
|
389
395
|
"/layers/conversational_flow/active_listening",
|
|
390
396
|
"/layers/conversational_flow/idle_engagement",
|
|
@@ -395,6 +401,7 @@ const PATCHABLE_PATHS = [
|
|
|
395
401
|
"/layers/conversational_flow/turn_taking_patience",
|
|
396
402
|
"/layers/conversational_flow/voice_isolation",
|
|
397
403
|
"/layers/conversational_flow/wake_phrase",
|
|
404
|
+
"/layers/conversational_flow/sleep_phrase",
|
|
398
405
|
"/layers/knowledge_base",
|
|
399
406
|
"/layers/knowledge_base/enable_rag_observability_app_messages",
|
|
400
407
|
"/layers/knowledge_base/rag_n_chunks",
|
|
@@ -696,7 +703,7 @@ export function createTavusMcpServer(
|
|
|
696
703
|
|
|
697
704
|
server.tool(
|
|
698
705
|
"tavus_patch_pal",
|
|
699
|
-
"Patch a PAL with validated JSON Patch operations for an exact field edit. To change what a PAL does or shows, such as adding an on-screen scorecard, use tavus_pal_build_and_verify with pal_id instead. Attach first-class tools through top-level tool_ids or tavus_pal_tools_attach; legacy layers.llm.tools writes are rejected.",
|
|
706
|
+
"Patch a PAL with validated JSON Patch operations for an exact field edit. To change what a PAL does or shows, such as adding an on-screen scorecard, use tavus_pal_build_and_verify with pal_id instead. Set wake_phrase and sleep_phrase under /layers/conversational_flow. Patch /layers/conferencing separately from behavior settings; it accepts username, allowlist, and whitelabel. Configuring an identity does not join a call. Ask the creator for the meeting link before joining. Attach first-class tools through top-level tool_ids or tavus_pal_tools_attach; legacy layers.llm.tools writes are rejected.",
|
|
700
707
|
{
|
|
701
708
|
pal_id: z.string().min(1),
|
|
702
709
|
ops: z.array(JsonPatchOperationSchema).min(1),
|
|
@@ -1671,11 +1678,12 @@ export function createTavusMcpServer(
|
|
|
1671
1678
|
|
|
1672
1679
|
server.tool(
|
|
1673
1680
|
"tavus_conversation_create",
|
|
1674
|
-
"Create a Tavus conversation for a
|
|
1681
|
+
"Create a Tavus conversation for a PAL and/or face, or send a PAL to an existing meeting. Ask the creator for the meeting link before setting meeting_url. The PAL needs a conferencing identity and a default face. Creation confirms the request was sent; verify admission in the meeting before reporting joined.",
|
|
1675
1682
|
{
|
|
1676
1683
|
pal_id: z.string().optional(),
|
|
1677
1684
|
face_id: z.string().optional(),
|
|
1678
1685
|
conversation_name: z.string().optional(),
|
|
1686
|
+
meeting_url: z.string().optional(),
|
|
1679
1687
|
},
|
|
1680
1688
|
{
|
|
1681
1689
|
title: "Create conversation",
|
|
@@ -1686,7 +1694,7 @@ export function createTavusMcpServer(
|
|
|
1686
1694
|
},
|
|
1687
1695
|
// /v2/conversations is not part of the pal/face alias, so the body keeps the
|
|
1688
1696
|
// underlying persona_id/replica_id wire fields (id values are identical).
|
|
1689
|
-
async ({ pal_id, face_id, conversation_name }) =>
|
|
1697
|
+
async ({ pal_id, face_id, conversation_name, meeting_url }) =>
|
|
1690
1698
|
toolResult(
|
|
1691
1699
|
await client.create(
|
|
1692
1700
|
"conversations",
|
|
@@ -1694,6 +1702,7 @@ export function createTavusMcpServer(
|
|
|
1694
1702
|
persona_id: pal_id,
|
|
1695
1703
|
replica_id: face_id,
|
|
1696
1704
|
conversation_name,
|
|
1705
|
+
meeting_url,
|
|
1697
1706
|
}),
|
|
1698
1707
|
),
|
|
1699
1708
|
),
|
|
@@ -3130,8 +3139,7 @@ async function runGuidedBuild(
|
|
|
3130
3139
|
|
|
3131
3140
|
let probes = (options.probes ?? [])
|
|
3132
3141
|
.map((probe) => probe.trim())
|
|
3133
|
-
.filter(Boolean)
|
|
3134
|
-
.slice(0, MAX_SMOKE_PROBES);
|
|
3142
|
+
.filter(Boolean);
|
|
3135
3143
|
if (!probes.length) {
|
|
3136
3144
|
const generated = await safeCall(() =>
|
|
3137
3145
|
client.post("persona-builder/llm/generate-probes", { persona_spec: spec }),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = "0.10.4"; // x-release-please-version
|
|
@@ -72,6 +72,25 @@ function registeredTools(
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
describe("Cloudflare Tavus MCP helpers", () => {
|
|
75
|
+
it("dispatches a PAL to the requested meeting through the hosted tool", async () => {
|
|
76
|
+
const requests: Array<{ url: string; body: unknown }> = [];
|
|
77
|
+
vi.stubGlobal("fetch", vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
78
|
+
requests.push({ url: String(input), body: JSON.parse(String(init?.body)) });
|
|
79
|
+
return Response.json({ conversation_id: "c-meeting", status: "active" });
|
|
80
|
+
}));
|
|
81
|
+
try {
|
|
82
|
+
const tool = registeredTools(createTavusMcpServer({ TAVUS_API_KEY: "secret" }))
|
|
83
|
+
.tavus_conversation_create;
|
|
84
|
+
await tool.handler({ pal_id: "p-meeting", meeting_url: "https://meet.google.com/abc-defg-hij" });
|
|
85
|
+
expect(requests).toEqual([{
|
|
86
|
+
url: "https://tavusapi.com/v2/conversations",
|
|
87
|
+
body: { persona_id: "p-meeting", meeting_url: "https://meet.google.com/abc-defg-hij" },
|
|
88
|
+
}]);
|
|
89
|
+
} finally {
|
|
90
|
+
vi.unstubAllGlobals();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
75
94
|
it("reads RQH builder history across MCP sessions and saves creator turns there", async () => {
|
|
76
95
|
const history = [{ role: "assistant", content: "Would you like Memory?" }];
|
|
77
96
|
vi.stubGlobal("fetch", vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
@@ -591,7 +610,8 @@ describe("Cloudflare Tavus MCP helpers", () => {
|
|
|
591
610
|
await expect(build({ pal_id: "p_live", prompt: request })).rejects.toThrow(/at least one brief/);
|
|
592
611
|
expect(calls).toEqual([]);
|
|
593
612
|
|
|
594
|
-
const
|
|
613
|
+
const probes = ["Start practice.", "My first answer.", "My second answer.", "Score me.", "Wrap up."];
|
|
614
|
+
const pending = build({ pal_id: "p_live", prompt: request, briefs: { skills: request }, probes });
|
|
595
615
|
let settled = false;
|
|
596
616
|
void pending.finally(() => {
|
|
597
617
|
settled = true;
|
|
@@ -600,6 +620,9 @@ describe("Cloudflare Tavus MCP helpers", () => {
|
|
|
600
620
|
await vi.advanceTimersByTimeAsync(500);
|
|
601
621
|
}
|
|
602
622
|
const result = JSON.parse((await pending).content[0].text);
|
|
623
|
+
expect(result.probes).toEqual(probes);
|
|
624
|
+
expect(result.smoke_transcript.filter((turn: { role: string }) => turn.role === "user")
|
|
625
|
+
.map((turn: { text: string }) => turn.text)).toEqual(probes);
|
|
603
626
|
|
|
604
627
|
expect(calls.find((call) => call.path === "/builder")?.body).toEqual({
|
|
605
628
|
name: "Leasing Coach",
|
|
@@ -648,6 +671,24 @@ describe("Cloudflare Tavus MCP helpers", () => {
|
|
|
648
671
|
).toThrow(/Unsupported pal patch path/);
|
|
649
672
|
});
|
|
650
673
|
|
|
674
|
+
it("accepts meeting settings and rejects unsupported leaf paths", () => {
|
|
675
|
+
const ops = [
|
|
676
|
+
{ op: "add" as const, path: "/layers/conversational_flow/wake_phrase", value: "Hey Morgan" },
|
|
677
|
+
{ op: "add" as const, path: "/layers/conversational_flow/sleep_phrase", value: "Goodbye Morgan" },
|
|
678
|
+
{ op: "add" as const, path: "/layers/conferencing", value: { username: "morgan-rehearsal" } },
|
|
679
|
+
{ op: "replace" as const, path: "/layers/conferencing/username", value: "morgan-demo" },
|
|
680
|
+
{ op: "replace" as const, path: "/layers/conferencing/allowlist", value: ["host@example.com"] },
|
|
681
|
+
{ op: "add" as const, path: "/layers/conferencing/allowlist/-", value: "other@example.com" },
|
|
682
|
+
{ op: "replace" as const, path: "/layers/conferencing/whitelabel", value: false },
|
|
683
|
+
];
|
|
684
|
+
expect(validatePatchOperations(ops)).toEqual(ops);
|
|
685
|
+
for (const field of ["email", "aliased_from", "usernmae"]) {
|
|
686
|
+
expect(() => validatePatchOperations([
|
|
687
|
+
{ op: "replace", path: `/layers/conferencing/${field}`, value: "invalid" },
|
|
688
|
+
])).toThrow(/Unsupported pal patch path/);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
|
|
651
692
|
it("patches guardrails by id and tag, not by the removed grouped id", () => {
|
|
652
693
|
expect(
|
|
653
694
|
validatePatchOperations([
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const VERSION = "0.10.2"; // x-release-please-version
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tavus_cli-0.10.2 → tavus_cli-0.10.4}/skills/tavus-agentic-pals/references/builder-workflow.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|