cade-cli 0.15.5__tar.gz → 0.15.7__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.
- {cade_cli-0.15.5 → cade_cli-0.15.7}/PKG-INFO +2 -2
- {cade_cli-0.15.5 → cade_cli-0.15.7}/pyproject.toml +2 -2
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/resources.py +1 -1
- cade_cli-0.15.7/src/cade_mcp_local/web/extract.py +115 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/mem.py +142 -5
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/display.py +12 -1
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/session.py +10 -4
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/slash.py +1 -1
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/audio.py +49 -0
- cade_cli-0.15.5/src/cade_mcp_local/web/extract.py +0 -54
- {cade_cli-0.15.5 → cade_cli-0.15.7}/.gitignore +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/LICENSE +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/README.md +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/server.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/agents.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/context.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/shell.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/snippets.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/utils.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ai/prompts.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/app.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/agents.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/mcp.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/serve.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/commands/tools.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/cli/onboarding.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/agent.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/config.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/constants.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/runtime.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/secrets.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/spawn.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/orchestrator.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/anthropic.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/providers/openai.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/base.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/desktop.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/registry.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/stub.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/adapters/telegram.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/allowlist.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/chunker.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/commands.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/config.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/cron.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/daemon.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/elicitation.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/format.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/install.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/lock.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/observability.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/owner.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/progress_sink.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/router.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/secrets.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/services.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/sinks.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/serve/worker.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/lock.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/scheduler.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/serve/serve.toml.example +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/composite.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/manager/mcp.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/session.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.15.5 → cade_cli-0.15.7}/src/cadecoder/voice/tts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cade-cli
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.7
|
|
4
4
|
Summary: Cade - The CLI Agent from Arcade.dev
|
|
5
5
|
Project-URL: Homepage, https://arcade.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.arcade.dev
|
|
@@ -43,7 +43,7 @@ Classifier: Topic :: Software Development
|
|
|
43
43
|
Classifier: Topic :: Software Development :: Code Generators
|
|
44
44
|
Classifier: Typing :: Typed
|
|
45
45
|
Requires-Python: >=3.11
|
|
46
|
-
Requires-Dist: agent-library<1.0.0,>=0.14.
|
|
46
|
+
Requires-Dist: agent-library<1.0.0,>=0.14.1
|
|
47
47
|
Requires-Dist: anthropic<1.0.0,>=0.34.0
|
|
48
48
|
Requires-Dist: arcade-core<5.0.0,>=4.7.0
|
|
49
49
|
Requires-Dist: arcade-mcp-server<2.0.0,>=1.22.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cade-cli"
|
|
3
|
-
version = "0.15.
|
|
3
|
+
version = "0.15.7"
|
|
4
4
|
description = "Cade - The CLI Agent from Arcade.dev"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -38,7 +38,7 @@ dependencies = [
|
|
|
38
38
|
"prompt-toolkit>=3.0.52,<4.0.0",
|
|
39
39
|
"httpx>=0.27.0,<1.0.0",
|
|
40
40
|
"filelock>=3.0.0,<4.0.0",
|
|
41
|
-
"agent-library>=0.14.
|
|
41
|
+
"agent-library>=0.14.1,<1.0.0",
|
|
42
42
|
"croniter>=2.0.0,<4.0.0",
|
|
43
43
|
"python-telegram-bot>=21.0,<22.0",
|
|
44
44
|
"keyring>=24.0,<26.0",
|
|
@@ -116,7 +116,7 @@ def register_resources(app: MCPApp, instructions: str) -> None:
|
|
|
116
116
|
title="cade mem sources (active persona)",
|
|
117
117
|
description=(
|
|
118
118
|
"JSON listing of memory sources registered for the active persona "
|
|
119
|
-
"(equivalent to `cade mem
|
|
119
|
+
"(equivalent to `cade mem sources`). Read each call so the listing "
|
|
120
120
|
"reflects sources added after server start."
|
|
121
121
|
),
|
|
122
122
|
mime_type="application/json",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Secondary-model extraction for WebFetch.
|
|
2
|
+
|
|
3
|
+
Single non-streaming ``Provider.complete()`` call so the same code path
|
|
4
|
+
works for Anthropic, OpenAI, and Ollama users alike. The extraction model is
|
|
5
|
+
resolved from (in priority order) an explicit override, ``CADE_WEB_FETCH_MODEL``,
|
|
6
|
+
the user's configured default model, then ``DEFAULT_EXTRACT_MODEL``. When the
|
|
7
|
+
preferred model's provider isn't configured (e.g. a Claude default on an
|
|
8
|
+
OpenAI-only setup), extraction falls back to whatever provider the user *did*
|
|
9
|
+
configure rather than failing outright.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
from cade_mcp_local.errors import WebFetchError
|
|
17
|
+
from cade_mcp_local.web.prompts import make_secondary_model_prompt
|
|
18
|
+
from cadecoder.providers import get_provider_for_model, provider_registry
|
|
19
|
+
from cadecoder.providers.base import Provider, ProviderRequest, ProviderType
|
|
20
|
+
|
|
21
|
+
DEFAULT_EXTRACT_MODEL = "claude-haiku-4-5-20251001"
|
|
22
|
+
|
|
23
|
+
# Cheap/fast per-provider extraction models, used only as a fallback when the
|
|
24
|
+
# preferred model's provider is unavailable but another provider is registered.
|
|
25
|
+
# Extraction is a one-shot summarization pass, so a small model is sufficient.
|
|
26
|
+
_FALLBACK_MODEL_BY_PROVIDER: dict[ProviderType, str] = {
|
|
27
|
+
ProviderType.ANTHROPIC: DEFAULT_EXTRACT_MODEL,
|
|
28
|
+
ProviderType.OPENAI: "gpt-5-mini",
|
|
29
|
+
ProviderType.OLLAMA: "llama3.1",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _configured_default_model() -> str | None:
|
|
34
|
+
"""Return the user's configured default model, or None if unavailable.
|
|
35
|
+
|
|
36
|
+
Reads the global settings directly so the extraction step honors whatever
|
|
37
|
+
provider/model the user set up during onboarding.
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
from cadecoder.core.config import _load_global_settings
|
|
41
|
+
|
|
42
|
+
return _load_global_settings().model or None
|
|
43
|
+
except Exception: # noqa: BLE001 - config is best-effort here; fall back to default
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _resolve_extraction_target(model: str | None) -> tuple[str, Provider]:
|
|
48
|
+
"""Resolve the extraction model and a usable provider.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
model: An explicit model override, if any.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A ``(model, provider)`` tuple ready to run an extraction.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
LookupError: If no LLM provider is registered at all.
|
|
58
|
+
"""
|
|
59
|
+
chosen = (
|
|
60
|
+
model
|
|
61
|
+
or os.environ.get("CADE_WEB_FETCH_MODEL")
|
|
62
|
+
or _configured_default_model()
|
|
63
|
+
or DEFAULT_EXTRACT_MODEL
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
provider = get_provider_for_model(chosen)
|
|
67
|
+
if provider is not None:
|
|
68
|
+
return chosen, provider
|
|
69
|
+
|
|
70
|
+
# The preferred model's provider isn't configured. Fall back to whatever
|
|
71
|
+
# provider the user did configure (the registry's default) so non-preapproved
|
|
72
|
+
# fetches still work on OpenAI/Ollama-only setups.
|
|
73
|
+
fallback = provider_registry.get_default()
|
|
74
|
+
if fallback is None:
|
|
75
|
+
raise LookupError("no LLM provider is registered")
|
|
76
|
+
|
|
77
|
+
fallback_model = _FALLBACK_MODEL_BY_PROVIDER.get(fallback.provider_type, chosen)
|
|
78
|
+
return fallback_model, fallback
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def extract_with_model(
|
|
82
|
+
prompt: str,
|
|
83
|
+
markdown: str,
|
|
84
|
+
*,
|
|
85
|
+
model: str | None = None,
|
|
86
|
+
url: str = "",
|
|
87
|
+
) -> tuple[str, str]:
|
|
88
|
+
"""Run a one-shot extraction. Returns (answer, model_used).
|
|
89
|
+
|
|
90
|
+
``url`` is only used to make the error message useful when no provider is
|
|
91
|
+
available or the provider rejects the request.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
chosen, provider = _resolve_extraction_target(model)
|
|
95
|
+
except LookupError:
|
|
96
|
+
raise WebFetchError(
|
|
97
|
+
url or "(no url)",
|
|
98
|
+
"no provider available for extraction; set ANTHROPIC_API_KEY/OPENAI_API_KEY, "
|
|
99
|
+
"configure a model via cade onboarding, or override CADE_WEB_FETCH_MODEL",
|
|
100
|
+
) from None
|
|
101
|
+
|
|
102
|
+
full_prompt = make_secondary_model_prompt(prompt, markdown)
|
|
103
|
+
request = ProviderRequest(
|
|
104
|
+
messages=[{"role": "user", "content": full_prompt}],
|
|
105
|
+
model=chosen,
|
|
106
|
+
max_tokens=2048,
|
|
107
|
+
temperature=0.2,
|
|
108
|
+
stream=False,
|
|
109
|
+
)
|
|
110
|
+
try:
|
|
111
|
+
response = await provider.complete(request)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
raise WebFetchError(url or "(no url)", f"extraction model failed: {e}") from e
|
|
114
|
+
|
|
115
|
+
return (response.content or "").strip(), chosen
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import os
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import Annotated, Any
|
|
14
14
|
|
|
15
15
|
import typer
|
|
16
16
|
from rich.console import Console
|
|
@@ -43,20 +43,29 @@ def _apply_memory_env() -> Path:
|
|
|
43
43
|
os.environ["DOCUMENTS_PATH"] = str(librarian_dir / "documents")
|
|
44
44
|
os.environ["SOURCES_CONFIG_PATH"] = str(librarian_dir / "sources.json")
|
|
45
45
|
os.environ["HOME"] = str(persona_dir)
|
|
46
|
+
# Rebrand librarian's usage hints (e.g. "Add a source with: libr add ...")
|
|
47
|
+
# to "cade mem". Must be set before librarian.cli is imported, which only
|
|
48
|
+
# happens later via _load_librarian_app().
|
|
49
|
+
os.environ["LIBRARIAN_PROG_NAME"] = "cade mem"
|
|
46
50
|
return persona_dir
|
|
47
51
|
|
|
48
52
|
|
|
49
53
|
def _load_librarian_app() -> Any:
|
|
50
54
|
"""Import and return librarian's Typer app (lazy, after the env is set).
|
|
51
55
|
|
|
52
|
-
The app is rebranded under ``cade mem``:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
The app is rebranded under ``cade mem``:
|
|
57
|
+
|
|
58
|
+
* the low-level ``index`` group becomes a top-level ``update`` command (the
|
|
59
|
+
former ``index build``);
|
|
60
|
+
* a cade-native ``status`` command reports the current agent's memory under
|
|
61
|
+
"Agent Memory" branding;
|
|
62
|
+
* ``list`` shows the agent's memory **documents** (what users expect from
|
|
63
|
+
"list"), while the source-directory listing moves to ``sources``.
|
|
56
64
|
"""
|
|
57
65
|
from librarian.cli import app
|
|
58
66
|
|
|
59
67
|
_remap_index_to_update(app)
|
|
68
|
+
_remap_list_to_docs(app)
|
|
60
69
|
_add_status_command(app)
|
|
61
70
|
return app
|
|
62
71
|
|
|
@@ -93,6 +102,52 @@ def _remap_index_to_update(app: Any) -> None:
|
|
|
93
102
|
)(build_callback)
|
|
94
103
|
|
|
95
104
|
|
|
105
|
+
def _remap_list_to_docs(app: Any) -> None:
|
|
106
|
+
"""Make ``cade mem list`` show memory *documents*, not source directories.
|
|
107
|
+
|
|
108
|
+
librarian's top-level ``list`` lists registered source directories — a
|
|
109
|
+
power-user concept. Under ``cade mem``, "list" should show what's actually
|
|
110
|
+
in the agent's memory (its indexed documents, including anything the agent
|
|
111
|
+
saved via ``Memory_*`` tools). So the source listing moves to
|
|
112
|
+
``cade mem sources`` and ``list`` is replaced by a cade-native command that
|
|
113
|
+
renders documents with the same output (and flags) as the in-chat
|
|
114
|
+
``/mem list``.
|
|
115
|
+
|
|
116
|
+
Power users keep librarian's ``cade mem docs list`` (``--source`` /
|
|
117
|
+
``--format`` / ``paths``) untouched.
|
|
118
|
+
|
|
119
|
+
Idempotent: safe to call more than once on the same app singleton.
|
|
120
|
+
"""
|
|
121
|
+
if "sources" in _command_names(app):
|
|
122
|
+
return # already remapped
|
|
123
|
+
|
|
124
|
+
commands = list(app.registered_commands)
|
|
125
|
+
list_sources_callback = next(
|
|
126
|
+
(
|
|
127
|
+
rc.callback
|
|
128
|
+
for rc in commands
|
|
129
|
+
if (rc.name or getattr(rc.callback, "__name__", "")) == "list"
|
|
130
|
+
),
|
|
131
|
+
None,
|
|
132
|
+
)
|
|
133
|
+
if list_sources_callback is None:
|
|
134
|
+
return # librarian build without a top-level `list` — leave as-is
|
|
135
|
+
|
|
136
|
+
# Drop the original top-level `list` (sources), re-register it as `sources`,
|
|
137
|
+
# and install the cade-native document listing as `list`.
|
|
138
|
+
app.registered_commands = [
|
|
139
|
+
rc for rc in commands if (rc.name or getattr(rc.callback, "__name__", "")) != "list"
|
|
140
|
+
]
|
|
141
|
+
app.command(
|
|
142
|
+
name="sources",
|
|
143
|
+
help="List the source directories that feed this agent's memory.",
|
|
144
|
+
)(list_sources_callback)
|
|
145
|
+
app.command(
|
|
146
|
+
name="list",
|
|
147
|
+
help="List the documents stored in this agent's memory.",
|
|
148
|
+
)(_cade_mem_list)
|
|
149
|
+
|
|
150
|
+
|
|
96
151
|
def _add_status_command(app: Any) -> None:
|
|
97
152
|
"""Register the cade-native ``status`` command (idempotent)."""
|
|
98
153
|
if "status" in _command_names(app):
|
|
@@ -157,6 +212,86 @@ def _mem_status() -> None:
|
|
|
157
212
|
out.print(table)
|
|
158
213
|
|
|
159
214
|
|
|
215
|
+
def _mem_list(*, json_output: bool = False, limit: int = 50) -> None:
|
|
216
|
+
"""Render the documents stored in the current agent's memory.
|
|
217
|
+
|
|
218
|
+
The single source of truth for both ``cade mem list`` (CLI) and the in-chat
|
|
219
|
+
``/mem list`` command, so the two surfaces always agree. Reads the agent's
|
|
220
|
+
index directly via ``Database(db_path=...)`` (no env / no HOME side effects)
|
|
221
|
+
so it never clobbers a long-lived session, mirroring ``_read_memory_stats``.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
json_output: Emit a JSON array (id/title/path/type) to stdout instead of
|
|
225
|
+
a table — for scripting and piping.
|
|
226
|
+
limit: Maximum number of documents to display.
|
|
227
|
+
"""
|
|
228
|
+
import json
|
|
229
|
+
|
|
230
|
+
from rich.console import Console as _Console
|
|
231
|
+
from rich.table import Table
|
|
232
|
+
|
|
233
|
+
runtime = get_runtime()
|
|
234
|
+
memory_dir = ensure_memory_dir(runtime.persona_name)
|
|
235
|
+
db_path = memory_dir / ".librarian" / "index.db"
|
|
236
|
+
|
|
237
|
+
out = _Console()
|
|
238
|
+
|
|
239
|
+
documents: list[Any] = []
|
|
240
|
+
if db_path.exists():
|
|
241
|
+
try:
|
|
242
|
+
from librarian.storage.database import Database
|
|
243
|
+
|
|
244
|
+
documents = Database(db_path=str(db_path)).list_documents()
|
|
245
|
+
except Exception as exc: # noqa: BLE001
|
|
246
|
+
if json_output:
|
|
247
|
+
print("[]")
|
|
248
|
+
else:
|
|
249
|
+
out.print(f"[red]Could not read this agent's memory:[/red] {exc}")
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
if json_output:
|
|
253
|
+
payload = [
|
|
254
|
+
{
|
|
255
|
+
"id": doc.id,
|
|
256
|
+
"title": doc.title,
|
|
257
|
+
"path": doc.path,
|
|
258
|
+
"type": doc.asset_type.value if getattr(doc, "asset_type", None) else "text",
|
|
259
|
+
}
|
|
260
|
+
for doc in documents[:limit]
|
|
261
|
+
]
|
|
262
|
+
print(json.dumps(payload, indent=2, default=str))
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
if not documents:
|
|
266
|
+
out.print(
|
|
267
|
+
"[dim]No documents in this agent's memory yet — add docs or run 'cade mem update'.[/dim]"
|
|
268
|
+
)
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
home = str(Path.home())
|
|
272
|
+
table = Table(
|
|
273
|
+
title=f"Agent Memory — {len(documents)} document(s)",
|
|
274
|
+
show_header=True,
|
|
275
|
+
header_style="bold cyan",
|
|
276
|
+
)
|
|
277
|
+
table.add_column("ID", style="dim", width=4)
|
|
278
|
+
table.add_column("Title", style="green", max_width=44)
|
|
279
|
+
table.add_column("Path", style="blue", overflow="fold")
|
|
280
|
+
for doc in documents[:limit]:
|
|
281
|
+
table.add_row(str(doc.id), doc.title or "Untitled", doc.path.replace(home, "~"))
|
|
282
|
+
out.print(table)
|
|
283
|
+
if len(documents) > limit:
|
|
284
|
+
out.print(f"[dim]Showing {limit} of {len(documents)}. Use --limit to see more.[/dim]")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _cade_mem_list(
|
|
288
|
+
json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
|
|
289
|
+
limit: Annotated[int, typer.Option("--limit", "-l", help="Max documents to show")] = 50,
|
|
290
|
+
) -> None:
|
|
291
|
+
"""List the documents stored in this agent's memory."""
|
|
292
|
+
_mem_list(json_output=json_output, limit=limit)
|
|
293
|
+
|
|
294
|
+
|
|
160
295
|
def _run_mem(args: list[str]) -> int:
|
|
161
296
|
"""Run librarian's CLI in-process, branded as ``cade mem``.
|
|
162
297
|
|
|
@@ -244,7 +379,9 @@ def mem_command(ctx: typer.Context) -> None:
|
|
|
244
379
|
__all__ = (
|
|
245
380
|
"MEM_CONTEXT_SETTINGS",
|
|
246
381
|
"_apply_memory_env",
|
|
382
|
+
"_cade_mem_list",
|
|
247
383
|
"_load_librarian_app",
|
|
384
|
+
"_mem_list",
|
|
248
385
|
"_run_mem",
|
|
249
386
|
"_warm_memory",
|
|
250
387
|
"mem_command",
|
|
@@ -465,7 +465,6 @@ _PRIMARY_ARG_KEYS: tuple[str, ...] = (
|
|
|
465
465
|
# Generic last-resort labels.
|
|
466
466
|
"name",
|
|
467
467
|
"title",
|
|
468
|
-
"tool_name",
|
|
469
468
|
)
|
|
470
469
|
|
|
471
470
|
|
|
@@ -811,6 +810,18 @@ def get_display_tool_name(tool_name: str, args: dict[str, Any]) -> str:
|
|
|
811
810
|
|
|
812
811
|
def get_tool_context(tool_name: str, args: dict[str, Any]) -> str:
|
|
813
812
|
"""Return parenthesized context for the tool (used by session for 'Calling tool' line)."""
|
|
813
|
+
# Unwrap the Arcade meta-tools so the context describes the *inner* tool
|
|
814
|
+
# (e.g. the doc title) instead of echoing the wrapper's tool_name arg —
|
|
815
|
+
# which produced redundant headlines like
|
|
816
|
+
# ``GoogleDocs_CreateDocumentFromText("GoogleDocs_CreateDocumentFromText")``.
|
|
817
|
+
if tool_name in ("Arcade_UseTool", "Arcade_ExecuteTool"):
|
|
818
|
+
inner_name = get_display_tool_name(tool_name, args)
|
|
819
|
+
if inner_name and inner_name != tool_name:
|
|
820
|
+
inner_args = args.get("inputs")
|
|
821
|
+
if not isinstance(inner_args, dict):
|
|
822
|
+
inner_args = {}
|
|
823
|
+
return get_tool_context(inner_name, inner_args)
|
|
824
|
+
|
|
814
825
|
server, tool = _parse_tool_name(tool_name)
|
|
815
826
|
handler = _find_registry_handler(server, tool)
|
|
816
827
|
if handler:
|
|
@@ -884,10 +884,10 @@ class ChatSession:
|
|
|
884
884
|
console.print("[cyan]Switching to voice mode — keeping this conversation…[/cyan]")
|
|
885
885
|
|
|
886
886
|
async def _handle_mem_command(self, cmd: str) -> None:
|
|
887
|
-
"""Handle /mem (
|
|
887
|
+
"""Handle /mem (status), /mem list (documents), and /mem search <query>.
|
|
888
888
|
|
|
889
|
-
The
|
|
890
|
-
so the Agent Memory experience is identical in both surfaces.
|
|
889
|
+
The status and list views reuse the same renderers as the ``cade mem``
|
|
890
|
+
CLI so the Agent Memory experience is identical in both surfaces.
|
|
891
891
|
"""
|
|
892
892
|
parts = cmd.split(None, 2)
|
|
893
893
|
sub = parts[1] if len(parts) >= 2 else "status"
|
|
@@ -898,6 +898,12 @@ class ChatSession:
|
|
|
898
898
|
_mem_status()
|
|
899
899
|
return
|
|
900
900
|
|
|
901
|
+
if sub in ("list", "ls", "docs"):
|
|
902
|
+
from cadecoder.cli.commands.mem import _mem_list
|
|
903
|
+
|
|
904
|
+
_mem_list()
|
|
905
|
+
return
|
|
906
|
+
|
|
901
907
|
if sub == "search" and len(parts) >= 3:
|
|
902
908
|
query = parts[2]
|
|
903
909
|
try:
|
|
@@ -913,7 +919,7 @@ class ChatSession:
|
|
|
913
919
|
)
|
|
914
920
|
return
|
|
915
921
|
|
|
916
|
-
console.print("[dim]Usage: /mem [status] | /mem search <query>[/dim]")
|
|
922
|
+
console.print("[dim]Usage: /mem [status] | /mem list | /mem search <query>[/dim]")
|
|
917
923
|
|
|
918
924
|
@staticmethod
|
|
919
925
|
def _print_mem_search_results(result: object) -> None:
|
|
@@ -51,7 +51,7 @@ SLASH_COMMANDS: tuple[SlashCommand, ...] = (
|
|
|
51
51
|
("list", "add", "remove", "enable", "disable"),
|
|
52
52
|
),
|
|
53
53
|
SlashCommand("/hooks", " …", "Manage lifecycle hooks", ("list", "reload")),
|
|
54
|
-
SlashCommand("/mem", " …", "Search/manage memory", ("status", "search")),
|
|
54
|
+
SlashCommand("/mem", " …", "Search/manage memory", ("status", "list", "search")),
|
|
55
55
|
SlashCommand("/notify", " <msg>", "Send a push notification"),
|
|
56
56
|
SlashCommand("/pwd", "", "Show current directory", hidden=True),
|
|
57
57
|
SlashCommand("/cd", " <path>", "Change directory", hidden=True),
|
|
@@ -52,6 +52,34 @@ def _rms(audio: np.ndarray) -> float:
|
|
|
52
52
|
return float(np.sqrt(np.mean(audio.astype(np.float64) ** 2)))
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
# Substrings that identify a Bluetooth headset reported as an input device.
|
|
56
|
+
# Such devices frequently return all-zero samples on macOS because the input
|
|
57
|
+
# route fails to switch to the headset mic — a routing issue, not a permission
|
|
58
|
+
# one — so the silence error gives different advice in that case.
|
|
59
|
+
_BLUETOOTH_MIC_HINTS: Final[tuple[str, ...]] = (
|
|
60
|
+
"airpods",
|
|
61
|
+
"beats",
|
|
62
|
+
"bluetooth",
|
|
63
|
+
"headphones",
|
|
64
|
+
"headset",
|
|
65
|
+
"buds",
|
|
66
|
+
"powerbeats",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _looks_like_bluetooth_mic(device_name: str) -> bool:
|
|
71
|
+
"""Return True when *device_name* looks like a Bluetooth headset.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
device_name: The human-readable input device name from PortAudio.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
True if the name matches a known Bluetooth-headset substring.
|
|
78
|
+
"""
|
|
79
|
+
name = device_name.lower()
|
|
80
|
+
return any(hint in name for hint in _BLUETOOTH_MIC_HINTS)
|
|
81
|
+
|
|
82
|
+
|
|
55
83
|
def check_mic_permission(
|
|
56
84
|
device: str | int | None = None,
|
|
57
85
|
sample_rate: int = DEFAULT_SAMPLE_RATE,
|
|
@@ -103,6 +131,27 @@ def check_mic_permission(
|
|
|
103
131
|
import platform
|
|
104
132
|
|
|
105
133
|
if platform.system() == "Darwin":
|
|
134
|
+
if _looks_like_bluetooth_mic(device_name):
|
|
135
|
+
# Bluetooth headsets (AirPods, Beats, etc.) commonly report
|
|
136
|
+
# silence on macOS because the input route fails to switch to
|
|
137
|
+
# the headset's mic — a routing problem, not a permission one.
|
|
138
|
+
return False, (
|
|
139
|
+
f"Microphone '{device_name}' returns silence (all zeros).\n"
|
|
140
|
+
"Bluetooth headsets (AirPods, Beats, etc.) often can't be "
|
|
141
|
+
"used as a mic on macOS.\n"
|
|
142
|
+
"\n"
|
|
143
|
+
"Try one of these:\n"
|
|
144
|
+
" • Switch input to the built-in mic: System Settings > "
|
|
145
|
+
"Sound > Input > MacBook/Built-in Microphone\n"
|
|
146
|
+
" • Or pin a specific device in ~/.cade/config/cade.toml "
|
|
147
|
+
'under [voice]: audio_input_device = "MacBook Pro Microphone"\n'
|
|
148
|
+
" (type /devices in the chat to list available devices)\n"
|
|
149
|
+
" • Or disconnect/reconnect the headset and try again\n"
|
|
150
|
+
"\n"
|
|
151
|
+
"If you intended to use the built-in mic, also confirm your "
|
|
152
|
+
"terminal has mic access in System Settings > Privacy & "
|
|
153
|
+
"Security > Microphone."
|
|
154
|
+
)
|
|
106
155
|
return False, (
|
|
107
156
|
f"Microphone '{device_name}' returns silence (all zeros).\n"
|
|
108
157
|
"This usually means macOS is blocking microphone access.\n"
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""Secondary-model extraction for WebFetch.
|
|
2
|
-
|
|
3
|
-
Single non-streaming ``Provider.complete()`` call so the same code path
|
|
4
|
-
works for Anthropic, OpenAI, and Ollama users alike. Default model is
|
|
5
|
-
``claude-haiku-4-5-20251001``; override via ``CADE_WEB_FETCH_MODEL``.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
|
|
12
|
-
from cade_mcp_local.errors import WebFetchError
|
|
13
|
-
from cade_mcp_local.web.prompts import make_secondary_model_prompt
|
|
14
|
-
from cadecoder.providers import get_provider_for_model
|
|
15
|
-
from cadecoder.providers.base import ProviderRequest
|
|
16
|
-
|
|
17
|
-
DEFAULT_EXTRACT_MODEL = "claude-haiku-4-5-20251001"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
async def extract_with_model(
|
|
21
|
-
prompt: str,
|
|
22
|
-
markdown: str,
|
|
23
|
-
*,
|
|
24
|
-
model: str | None = None,
|
|
25
|
-
url: str = "",
|
|
26
|
-
) -> tuple[str, str]:
|
|
27
|
-
"""Run a one-shot extraction. Returns (answer, model_used).
|
|
28
|
-
|
|
29
|
-
``url`` is only used to make the error message useful when the
|
|
30
|
-
provider rejects the request.
|
|
31
|
-
"""
|
|
32
|
-
chosen = model or os.environ.get("CADE_WEB_FETCH_MODEL") or DEFAULT_EXTRACT_MODEL
|
|
33
|
-
provider = get_provider_for_model(chosen)
|
|
34
|
-
if provider is None:
|
|
35
|
-
raise WebFetchError(
|
|
36
|
-
url or "(no url)",
|
|
37
|
-
f"no provider available for extraction model {chosen!r}; "
|
|
38
|
-
"set ANTHROPIC_API_KEY/OPENAI_API_KEY or CADE_WEB_FETCH_MODEL",
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
full_prompt = make_secondary_model_prompt(prompt, markdown)
|
|
42
|
-
request = ProviderRequest(
|
|
43
|
-
messages=[{"role": "user", "content": full_prompt}],
|
|
44
|
-
model=chosen,
|
|
45
|
-
max_tokens=2048,
|
|
46
|
-
temperature=0.2,
|
|
47
|
-
stream=False,
|
|
48
|
-
)
|
|
49
|
-
try:
|
|
50
|
-
response = await provider.complete(request)
|
|
51
|
-
except Exception as e:
|
|
52
|
-
raise WebFetchError(url or "(no url)", f"extraction model failed: {e}") from e
|
|
53
|
-
|
|
54
|
-
return (response.content or "").strip(), chosen
|
|
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
|
|
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
|
|
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
|