multacd 2.0.0b4__tar.gz → 2.0.0b6__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.
- {multacd-2.0.0b4 → multacd-2.0.0b6}/.github/workflows/release.yml +1 -1
- {multacd-2.0.0b4 → multacd-2.0.0b6}/CHANGELOG.md +14 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/PKG-INFO +1 -1
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/agent_loop.py +5 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/providers/openai_compat.py +24 -1
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/version.py +1 -1
- {multacd-2.0.0b4 → multacd-2.0.0b6}/memory/context.py +21 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/pyproject.toml +1 -1
- {multacd-2.0.0b4 → multacd-2.0.0b6}/scripts/install.sh +1 -1
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/integration/test_agent_loop.py +23 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/.github/pull_request_template.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/.github/workflows/test.yml +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/.gitignore +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/CONTRIBUTING.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/DESIGN.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/LICENSE +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/README.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/briefing/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/briefing/generator.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/briefing/privacy.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/briefing/sources/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/briefing/sources/git_source.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/briefing/sources/news_source.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/briefing/sources/todo_source.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/clipboard.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/codebase.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/config.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/llm_client.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/mode_manager.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/permissions.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/plugin_loader.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/prompt_composer.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/providers/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/providers/__main__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/providers/anthropic.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/research/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/research/bus.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/research/orchestrator.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/research/query_generator.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/core/updater.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/daemon/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/daemon/ipc.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/daemon/process.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/configuration.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/index.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/install.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/mkdocs.yml +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/modes/coding.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/modes/personal.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/modes/research.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/plugins.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/docs/quickstart.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/main.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/memory/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/memory/store.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/multacd.spec +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/plan/PLAN-phase1.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/plan/PLAN-phase2.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/plugins/README.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/plugins/example_plugin.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/requirements.txt +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/roadmap/ROADMAP.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/scheduler/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/scheduler/engine.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/scheduler/jobs/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/scheduler/jobs/briefing_job.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/scheduler/jobs/research_job.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/scripts/install.ps1 +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/search_providers/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/search_providers/__main__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/search_providers/base.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/search_providers/brave.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/search_providers/duckduckgo.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/search_providers/exa.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/search_providers/serpapi.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/search_providers/tavily.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/soul.md +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/conftest.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/integration/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/test_selftests.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_binary.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_config.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_docs.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_entrypoint.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_github.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_installer.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_permissions.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_plugin_loader.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_privacy_filter.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_search_providers/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_search_providers/test_tavily.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_tools/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_tools/test_bash.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_tools/test_read_file.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_tools/test_web_scrape.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_tools/test_write_file.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tests/unit/test_updater.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tg/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tg/access_control.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tg/agent.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tg/bot.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tg/file_handler.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tg/formatter.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tg/handlers.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/agent/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/agent/ask.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/agent/skill.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/agent/task.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/agent/todo_write.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/code/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/code/lint_python.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/code/run_python.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/code/run_tests.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/codebase/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/codebase/scan_codebase.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/common.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/apply_patch.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/delete_file.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/edit_file.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/glob.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/grep.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/list_dir.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/move_file.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/multi_edit.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/read_file.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/read_many_files.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/filesystem/write_file.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/_helper.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_add.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_branch.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_checkout.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_commit.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_diff.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_log.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_merge.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_pull.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_push.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/git/git_status.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/memory/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/memory/forget.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/memory/recall.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/memory/remember.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/personal/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/personal/cancel_job.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/personal/daemon_status.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/personal/generate_briefing.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/personal/get_jobs.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/personal/schedule_job.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/personal/send_telegram.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/personal/user_manager.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/registry.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/research/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/research/deep_research.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/research/export_research.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/research/quick_research.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/research/web_scrape.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/research/web_search.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/shell/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/shell/bash.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/streaming.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/web/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tools/web/web_fetch.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/app.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/icons.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/screens/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/screens/main_screen.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/screens/setup_wizard.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/themes/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/themes/__main__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/tokens.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/__init__.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/chat_panel.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/confirm_dialog.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/diff_viewer.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/file_tree.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/info_panel.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/input_bar.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/model_selector.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/permission_popup.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/slash_palette.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/sources_panel.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/status_bar.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/thinking_bar.py +0 -0
- {multacd-2.0.0b4 → multacd-2.0.0b6}/tui/widgets/tool_activity.py +0 -0
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Format: Keep a Changelog. Versi: Semantic Versioning.
|
|
4
4
|
|
|
5
|
+
## [2.0.0b6] - 2026-09-12
|
|
6
|
+
|
|
7
|
+
Beta v2 keenam — fix mode lupa ingatan (#26): system message di-refresh
|
|
8
|
+
tiap turn dari composer (ala opencode: system disusun ulang tiap request).
|
|
9
|
+
Ganti /code /research /personal sekarang beneran ganti persona yang dibaca
|
|
10
|
+
LLM. Install: `pip install "multacd==2.0.0b6"`.
|
|
11
|
+
|
|
12
|
+
## [2.0.0b5] - 2026-09-12
|
|
13
|
+
|
|
14
|
+
Beta v2 kelima — fix chat 2-turn 400 (#25): `add_assistant_tool_calls`
|
|
15
|
+
omit key `tool_calls` saat tanpa tool + `_sanitize_messages` safety net
|
|
16
|
+
di openai_compat. Belajar dari opencode `transform.ts` (part kosong
|
|
17
|
+
tidak pernah dikirim). Install: `pip install "multacd==2.0.0b5"`.
|
|
18
|
+
|
|
5
19
|
## [2.0.0b4] - 2026-09-12
|
|
6
20
|
|
|
7
21
|
Beta v2 keempat — Termux harusnya tembus sekarang (#24): `pydantic`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: multacd
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0b6
|
|
4
4
|
Summary: Agentic TUI for coding, research, and personal automation
|
|
5
5
|
Project-URL: Homepage, https://github.com/prototypeall850-creator/multacd
|
|
6
6
|
Project-URL: Issues, https://github.com/prototypeall850-creator/multacd/issues
|
|
@@ -174,6 +174,11 @@ async def run_agent(
|
|
|
174
174
|
|
|
175
175
|
if not context.get_messages():
|
|
176
176
|
context.add_message("system", _system_prompt())
|
|
177
|
+
elif composer is not None:
|
|
178
|
+
# #26: mode ganti → composer baru, tapi history masih bawa
|
|
179
|
+
# system lama. Refresh tiap turn (ala opencode: system disusun
|
|
180
|
+
# ulang tiap request) biar LLM baca persona yang benar.
|
|
181
|
+
context.set_system(_system_prompt())
|
|
177
182
|
context.add_message("user", user_input)
|
|
178
183
|
|
|
179
184
|
iteration = 0
|
|
@@ -136,6 +136,29 @@ def map_http_error(status: int, body: str, model: str) -> ProviderError:
|
|
|
136
136
|
return ProviderError(f"Provider error HTTP {status}. {snippet}".strip())
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def _sanitize_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
140
|
+
"""Bersihkan messages sebelum kirim (ala opencode transform.ts).
|
|
141
|
+
|
|
142
|
+
- `tool_calls: []` / None → key di-drop (provider strict 400 kalau
|
|
143
|
+
array kosong ikut terkirim — issue #25).
|
|
144
|
+
- `content: None` tanpa tool_calls → `""` (beberapa gateway nolak null).
|
|
145
|
+
- `content: None` + tool_calls non-kosong → dibiarkan (valid OpenAI).
|
|
146
|
+
Pure function — input tidak dimutasi.
|
|
147
|
+
"""
|
|
148
|
+
clean: list[dict[str, Any]] = []
|
|
149
|
+
for m in messages:
|
|
150
|
+
if not isinstance(m, dict):
|
|
151
|
+
continue
|
|
152
|
+
msg = dict(m)
|
|
153
|
+
tcs = msg.get("tool_calls")
|
|
154
|
+
if not tcs:
|
|
155
|
+
msg.pop("tool_calls", None)
|
|
156
|
+
if msg.get("content") is None and not msg.get("tool_calls"):
|
|
157
|
+
msg["content"] = ""
|
|
158
|
+
clean.append(msg)
|
|
159
|
+
return clean
|
|
160
|
+
|
|
161
|
+
|
|
139
162
|
async def stream_chat(spec: ProviderSpec, api_key: str,
|
|
140
163
|
messages: list[dict[str, Any]],
|
|
141
164
|
tools: list[dict[str, Any]] | None,
|
|
@@ -146,7 +169,7 @@ async def stream_chat(spec: ProviderSpec, api_key: str,
|
|
|
146
169
|
"""Kirim chat completions streaming. `client` injeksi buat test."""
|
|
147
170
|
body: dict[str, Any] = {
|
|
148
171
|
"model": spec.model,
|
|
149
|
-
"messages": messages,
|
|
172
|
+
"messages": _sanitize_messages(messages),
|
|
150
173
|
"stream": True,
|
|
151
174
|
"stream_options": {"include_usage": True},
|
|
152
175
|
"max_tokens": max_tokens,
|
|
@@ -8,7 +8,7 @@ Saat bump versi (Step 10 → 1.0.0): ubah pyproject + FALLBACK di sini.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
FALLBACK = "2.0.
|
|
11
|
+
FALLBACK = "2.0.0b6" # beta v2; samakan dengan pyproject.toml
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def get_version() -> str:
|
|
@@ -56,9 +56,18 @@ class ConversationContext:
|
|
|
56
56
|
(mis. ToolCallRequest dari core.llm_client). Wajib ada agar
|
|
57
57
|
provider tidak 400 pada turn berikutnya (tool_call_id harus
|
|
58
58
|
merujuk ke assistant message sebelumnya).
|
|
59
|
+
|
|
60
|
+
Belajar dari opencode (AI SDK): tool adalah content part —
|
|
61
|
+
part kosong tidak pernah dikirim. Jadi kalau `calls` kosong,
|
|
62
|
+
key `tool_calls` di-OMIT total (bukan `[]`): provider strict
|
|
63
|
+
(400 `tool_calls must be non-empty if provided`) nolak array
|
|
64
|
+
kosong. Lihat issue #25.
|
|
59
65
|
"""
|
|
60
66
|
import json as _json
|
|
61
67
|
|
|
68
|
+
if not calls:
|
|
69
|
+
self._messages.append({"role": "assistant", "content": text or ""})
|
|
70
|
+
return
|
|
62
71
|
self._messages.append({
|
|
63
72
|
"role": "assistant",
|
|
64
73
|
"content": text or None,
|
|
@@ -72,6 +81,18 @@ class ConversationContext:
|
|
|
72
81
|
],
|
|
73
82
|
})
|
|
74
83
|
|
|
84
|
+
def set_system(self, content: str) -> None:
|
|
85
|
+
"""Ganti system message dengan prompt terbaru (issue #26).
|
|
86
|
+
|
|
87
|
+
Ganti mode (/code→/research) cuma update composer — history
|
|
88
|
+
masih bawa system lama kalau tidak di-refresh. Panggil tiap
|
|
89
|
+
turn sebelum kirim ke LLM (lihat core/agent_loop.py).
|
|
90
|
+
"""
|
|
91
|
+
if self._messages and self._messages[0].get("role") == "system":
|
|
92
|
+
self._messages[0] = {"role": "system", "content": content}
|
|
93
|
+
else:
|
|
94
|
+
self._messages.insert(0, {"role": "system", "content": content})
|
|
95
|
+
|
|
75
96
|
def get_messages(self) -> list[dict[str, Any]]:
|
|
76
97
|
"""Return semua messages (copy list — jangan mutasi hasilnya)."""
|
|
77
98
|
return list(self._messages)
|
|
@@ -78,7 +78,7 @@ termux_pip_guide() {
|
|
|
78
78
|
echo " (ringan: tanpa kompilasi Rust — dependensi wheel murni)"
|
|
79
79
|
echo ""
|
|
80
80
|
echo " 2. Install multacd:"
|
|
81
|
-
echo " pip install \"multacd==2.0.
|
|
81
|
+
echo " pip install \"multacd==2.0.0b6\""
|
|
82
82
|
echo ""
|
|
83
83
|
echo " 3. Jalankan: multacd"
|
|
84
84
|
echo ""
|
|
@@ -13,6 +13,7 @@ import asyncio
|
|
|
13
13
|
|
|
14
14
|
from core.agent_loop import AgentDone, AgentError, run_agent
|
|
15
15
|
from core.llm_client import StreamDone, ToolCallRequest
|
|
16
|
+
from core.prompt_composer import PromptComposer
|
|
16
17
|
from memory.context import ConversationContext
|
|
17
18
|
|
|
18
19
|
|
|
@@ -34,6 +35,10 @@ def test_plain_text_no_tool(sample_config, fake_llm_factory):
|
|
|
34
35
|
events = _run(_drain(run_agent("hi", ctx, sample_config, llm_client=fake)))
|
|
35
36
|
assert isinstance(events[-1], AgentDone)
|
|
36
37
|
assert "halo" in events[-1].text
|
|
38
|
+
# #25: assistant tanpa tool TIDAK boleh bawa key tool_calls
|
|
39
|
+
# (provider strict 400 kalau array kosong ikut terkirim).
|
|
40
|
+
assts = [m for m in ctx.get_messages() if m.get("role") == "assistant"]
|
|
41
|
+
assert assts and all("tool_calls" not in m for m in assts), assts
|
|
37
42
|
|
|
38
43
|
|
|
39
44
|
def test_single_auto_tool_runs(sample_config, fake_llm_factory, tmp_path):
|
|
@@ -51,6 +56,24 @@ def test_single_auto_tool_runs(sample_config, fake_llm_factory, tmp_path):
|
|
|
51
56
|
assert "a.txt" in blob or "a.txt" in events[-1].text
|
|
52
57
|
|
|
53
58
|
|
|
59
|
+
def test_mode_switch_refreshes_system_prompt(sample_config, fake_llm_factory):
|
|
60
|
+
# #26: ganti mode harus ganti system yang dilihat LLM berikutnya.
|
|
61
|
+
composer = PromptComposer(soul="S")
|
|
62
|
+
composer.update_mode("MODE-A")
|
|
63
|
+
ctx = ConversationContext()
|
|
64
|
+
fake = fake_llm_factory([StreamDone("a", [])])
|
|
65
|
+
_run(_drain(run_agent("hi", ctx, sample_config, llm_client=fake,
|
|
66
|
+
composer=composer)))
|
|
67
|
+
assert "MODE-A" in ctx.get_messages()[0]["content"]
|
|
68
|
+
composer.update_mode("MODE-B")
|
|
69
|
+
fake = fake_llm_factory([StreamDone("b", [])])
|
|
70
|
+
_run(_drain(run_agent("halo lagi", ctx, sample_config, llm_client=fake,
|
|
71
|
+
composer=composer)))
|
|
72
|
+
sys_msgs = [m for m in ctx.get_messages() if m["role"] == "system"]
|
|
73
|
+
assert len(sys_msgs) == 1 and "MODE-B" in sys_msgs[0]["content"], sys_msgs
|
|
74
|
+
assert "MODE-B" in fake.seen[-1][0]["content"]
|
|
75
|
+
|
|
76
|
+
|
|
54
77
|
def test_max_iterations_stops_gracefully(sample_config, fake_llm_factory):
|
|
55
78
|
sample_config.max_tool_iterations = 2
|
|
56
79
|
script = [StreamDone("", [ToolCallRequest(id=f"c{i}", name="list_dir",
|
|
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
|
|
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
|