claude-smart 0.2.45 → 0.2.47
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.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +47 -10
- package/bin/claude-smart.js +535 -23
- package/package.json +22 -3
- package/plugin/.claude-plugin/plugin.json +4 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +27 -6
- package/plugin/commands/clear-all.md +1 -0
- package/plugin/commands/dashboard.md +1 -0
- package/plugin/commands/learn.md +1 -0
- package/plugin/commands/restart.md +1 -0
- package/plugin/commands/show.md +1 -0
- package/plugin/dashboard/app/configure/env/page.tsx +40 -14
- package/plugin/dashboard/app/configure/server/page.tsx +51 -1
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
- package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
- package/plugin/dashboard/lib/reflexio-client.ts +16 -0
- package/plugin/dashboard/lib/status.ts +10 -3
- package/plugin/dashboard/lib/types.ts +18 -6
- package/plugin/hooks/codex-hooks.json +0 -1
- package/plugin/opencode/assistant-buffer.ts +108 -0
- package/plugin/opencode/dist/assistant-buffer.js +96 -0
- package/plugin/opencode/dist/internal.js +12 -0
- package/plugin/opencode/dist/payload.js +85 -0
- package/plugin/opencode/dist/server.mjs +232 -0
- package/plugin/opencode/internal.ts +18 -0
- package/plugin/opencode/package.json +3 -0
- package/plugin/opencode/payload.ts +90 -0
- package/plugin/opencode/server.mts +251 -0
- package/plugin/opencode/tsconfig.json +13 -0
- package/plugin/pyproject.toml +25 -4
- package/plugin/scripts/_lib.sh +5 -1
- package/plugin/scripts/backend-service.sh +22 -2
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +1 -1
- package/plugin/scripts/opencode-claude-compat +4 -0
- package/plugin/scripts/opencode-claude-compat.cmd +3 -0
- package/plugin/scripts/opencode-claude-compat.js +225 -0
- package/plugin/scripts/smart-install.sh +17 -9
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +498 -12
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/src/claude_smart/env_config.py +12 -4
- package/plugin/src/claude_smart/events/session_start.py +26 -7
- package/plugin/src/claude_smart/events/stop.py +1 -1
- package/plugin/src/claude_smart/ids.py +1 -1
- package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
- package/plugin/src/claude_smart/runtime.py +7 -1
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +54 -30
- package/plugin/vendor/reflexio/README.md +17 -11
- package/plugin/vendor/reflexio/pyproject.toml +13 -1
- package/plugin/vendor/reflexio/reflexio/README.md +4 -1
- package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +212 -52
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +154 -13
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +246 -10
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
- package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +97 -3
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +104 -7
- package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
- package/plugin/vendor/reflexio/reflexio/server/README.md +58 -33
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +899 -103
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
- package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
- package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +278 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +28 -11
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +47 -16
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +18 -11
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +334 -178
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +31 -22
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -6
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +16 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +644 -197
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +31 -1369
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +40 -57
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +4 -677
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +372 -34
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
- package/scripts/setup-claude-smart.sh +8 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +0 -25
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
- /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
|
@@ -11,11 +11,14 @@ from __future__ import annotations
|
|
|
11
11
|
import json
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
|
+
import threading
|
|
14
15
|
import time
|
|
15
16
|
from typing import Any, Literal
|
|
16
17
|
|
|
17
18
|
import httpx
|
|
18
19
|
|
|
20
|
+
from reflexio.server.tracing import profile_step
|
|
21
|
+
|
|
19
22
|
_LOGGER = logging.getLogger(__name__)
|
|
20
23
|
|
|
21
24
|
EmbeddingProviderMode = Literal[
|
|
@@ -41,10 +44,22 @@ _DEFAULT_LOCAL_SERVICE_PROBE_TIMEOUT_MS = 200
|
|
|
41
44
|
# daemon. Cap texts per request and concatenate; encode batching is a separate
|
|
42
45
|
# server-side knob (REFLEXIO_EMBED_BATCH_SIZE).
|
|
43
46
|
_DEFAULT_MAX_TEXTS_PER_REQUEST = 32
|
|
44
|
-
|
|
47
|
+
# A reachable daemon is stable, so positive probe results are cached long
|
|
48
|
+
# enough that steady-state requests skip the sequential /health round trip.
|
|
49
|
+
# Failures stay short-lived so a restarted daemon is re-adopted quickly and
|
|
50
|
+
# the inprocess-fallback window stays small.
|
|
51
|
+
_LOCAL_SERVICE_PROBE_CACHE_SECONDS = 60.0
|
|
52
|
+
_LOCAL_SERVICE_PROBE_FAILURE_CACHE_SECONDS = 5.0
|
|
53
|
+
# Keep-alive must expire below the ALB's default 60s idle timeout so the pool
|
|
54
|
+
# never hands out a connection the load balancer already closed.
|
|
55
|
+
_HTTP_KEEPALIVE_EXPIRY_SECONDS = 50.0
|
|
56
|
+
_EMBEDDING_RETRY_BACKOFF_SECONDS = 0.1
|
|
45
57
|
_SERVICE_MODES = {"local_service", "internal_service"}
|
|
46
58
|
_VALID_MODES = {"cloud", *_SERVICE_MODES, "inprocess", "off"}
|
|
47
59
|
_local_service_probe_cache: tuple[float, bool, str | None] | None = None
|
|
60
|
+
_http_client_lock = threading.Lock()
|
|
61
|
+
_http_client_instance: httpx.Client | None = None
|
|
62
|
+
_http_client_pid: int | None = None
|
|
48
63
|
|
|
49
64
|
|
|
50
65
|
class EmbeddingUnavailableError(RuntimeError):
|
|
@@ -113,17 +128,46 @@ def _is_local_model(model: str | None) -> bool:
|
|
|
113
128
|
return bool(model and model.startswith("local/"))
|
|
114
129
|
|
|
115
130
|
|
|
131
|
+
def _http_client() -> httpx.Client:
|
|
132
|
+
"""Shared keep-alive client for health probes and embedding POSTs.
|
|
133
|
+
|
|
134
|
+
A per-request client pays DNS resolution plus a TCP handshake on every
|
|
135
|
+
embedding call. The client is recreated after fork (PID check) so worker
|
|
136
|
+
children never reuse sockets inherited from the parent process.
|
|
137
|
+
"""
|
|
138
|
+
global _http_client_instance, _http_client_pid
|
|
139
|
+
pid = os.getpid()
|
|
140
|
+
with _http_client_lock:
|
|
141
|
+
if _http_client_instance is None or _http_client_pid != pid:
|
|
142
|
+
if _http_client_instance is not None and _http_client_pid != pid:
|
|
143
|
+
try:
|
|
144
|
+
_http_client_instance.close()
|
|
145
|
+
except Exception:
|
|
146
|
+
_LOGGER.debug(
|
|
147
|
+
"Failed to close stale embedding HTTP client", exc_info=True
|
|
148
|
+
)
|
|
149
|
+
_http_client_instance = httpx.Client(
|
|
150
|
+
limits=httpx.Limits(keepalive_expiry=_HTTP_KEEPALIVE_EXPIRY_SECONDS)
|
|
151
|
+
)
|
|
152
|
+
_http_client_pid = pid
|
|
153
|
+
return _http_client_instance
|
|
154
|
+
|
|
155
|
+
|
|
116
156
|
def _local_service_status() -> tuple[bool, str | None]:
|
|
117
157
|
global _local_service_probe_cache
|
|
118
158
|
now = time.monotonic()
|
|
119
|
-
if
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
159
|
+
if _local_service_probe_cache is not None:
|
|
160
|
+
cached_at, cached_reachable, cached_model = _local_service_probe_cache
|
|
161
|
+
ttl = (
|
|
162
|
+
_LOCAL_SERVICE_PROBE_CACHE_SECONDS
|
|
163
|
+
if cached_reachable
|
|
164
|
+
else _LOCAL_SERVICE_PROBE_FAILURE_CACHE_SECONDS
|
|
165
|
+
)
|
|
166
|
+
if now - cached_at < ttl:
|
|
167
|
+
return cached_reachable, cached_model
|
|
124
168
|
|
|
125
169
|
try:
|
|
126
|
-
response =
|
|
170
|
+
response = _http_client().get(
|
|
127
171
|
f"{_local_service_url()}/health",
|
|
128
172
|
timeout=_local_service_probe_timeout_seconds(),
|
|
129
173
|
)
|
|
@@ -192,9 +236,9 @@ def embedding_provider_mode(model: str | None = None) -> EmbeddingProviderMode:
|
|
|
192
236
|
"""Resolve the embedding provider mode for a model.
|
|
193
237
|
|
|
194
238
|
Explicit legacy env modes keep their historical behavior. With no explicit
|
|
195
|
-
env mode, routing is model-driven: ``local/*`` uses
|
|
196
|
-
|
|
197
|
-
models use their provider directly.
|
|
239
|
+
env mode, routing is model-driven: ``local/*`` uses a configured daemon host
|
|
240
|
+
authoritatively, otherwise it uses the local daemon when reachable and falls
|
|
241
|
+
back to the in-process embedder. Cloud models use their provider directly.
|
|
198
242
|
"""
|
|
199
243
|
configured = os.environ.get(_ENV_PROVIDER)
|
|
200
244
|
if configured:
|
|
@@ -212,6 +256,9 @@ def embedding_provider_mode(model: str | None = None) -> EmbeddingProviderMode:
|
|
|
212
256
|
if os.environ.get(_ENV_SERVICE_URL):
|
|
213
257
|
return "internal_service"
|
|
214
258
|
|
|
259
|
+
if _is_local_model(model) and os.environ.get(_ENV_DAEMON_HOST, "").strip():
|
|
260
|
+
return "local_service"
|
|
261
|
+
|
|
215
262
|
if _is_local_model(model):
|
|
216
263
|
return "local_service" if _local_service_supports_model(model) else "inprocess"
|
|
217
264
|
return "cloud"
|
|
@@ -286,17 +333,29 @@ def _post_embedding_batch(
|
|
|
286
333
|
last_error: Exception | None = None
|
|
287
334
|
for attempt in range(2):
|
|
288
335
|
try:
|
|
289
|
-
|
|
290
|
-
response = client.post(url, json=payload)
|
|
336
|
+
response = _http_client().post(url, json=payload, timeout=timeout)
|
|
291
337
|
response.raise_for_status()
|
|
292
338
|
body = response.json()
|
|
293
339
|
return _ordered_embeddings_from_response(body.get("data"), len(texts))
|
|
294
|
-
except (
|
|
295
|
-
|
|
296
|
-
|
|
340
|
+
except (
|
|
341
|
+
httpx.ConnectError,
|
|
342
|
+
httpx.ConnectTimeout,
|
|
343
|
+
httpx.RemoteProtocolError,
|
|
344
|
+
) as exc:
|
|
345
|
+
# Connection never established, or the server closed a pooled
|
|
346
|
+
# keep-alive connection before sending a response (stale-reuse
|
|
347
|
+
# race) — the request was not processed, so a single retry is
|
|
348
|
+
# safe and cannot duplicate work.
|
|
297
349
|
last_error = exc
|
|
298
350
|
if attempt == 0:
|
|
299
|
-
|
|
351
|
+
with profile_step(
|
|
352
|
+
"search.embedding.api.retry_backoff",
|
|
353
|
+
retry_reason=type(exc).__name__,
|
|
354
|
+
retry_backoff_ms=int(_EMBEDDING_RETRY_BACKOFF_SECONDS * 1000),
|
|
355
|
+
attempt=attempt + 1,
|
|
356
|
+
max_attempts=2,
|
|
357
|
+
):
|
|
358
|
+
time.sleep(_EMBEDDING_RETRY_BACKOFF_SECONDS)
|
|
300
359
|
except (httpx.HTTPError, json.JSONDecodeError, ValueError) as exc:
|
|
301
360
|
# The server already received the request: a read timeout means it
|
|
302
361
|
# is still encoding, and retrying would queue a second identical
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""openClaw CLI as a LiteLLM custom provider.
|
|
2
|
+
|
|
3
|
+
Routes ``litellm.completion(model="openclaw/...", ...)`` through the
|
|
4
|
+
user's locally-installed ``openclaw`` CLI by spawning
|
|
5
|
+
``openclaw infer model run --prompt <p> --json [--model <m>]``.
|
|
6
|
+
|
|
7
|
+
Activation is opt-in via ``OPENCLAW_SMART_USE_LOCAL_CLI=1``. Without it,
|
|
8
|
+
the provider does not register and reflexio falls back to its normal
|
|
9
|
+
provider priority. Every spawn sets ``OPENCLAW_SMART_INTERNAL=1`` so the
|
|
10
|
+
openclaw-smart plugin's hooks short-circuit when this provider invokes
|
|
11
|
+
the CLI (recursion guard).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess # noqa: S404 — subprocess is the integration point.
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
import litellm
|
|
25
|
+
from litellm.llms.custom_llm import CustomLLM
|
|
26
|
+
from litellm.types.utils import Choices, Message, ModelResponse, Usage
|
|
27
|
+
|
|
28
|
+
_LOGGER = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
PROVIDER_KEY = "openclaw"
|
|
31
|
+
ENV_ENABLE = "OPENCLAW_SMART_USE_LOCAL_CLI"
|
|
32
|
+
ENV_CLI_PATH = "OPENCLAW_BIN"
|
|
33
|
+
ENV_DEFAULT_MODEL = "OPENCLAW_DEFAULT_MODEL"
|
|
34
|
+
ENV_TIMEOUT = "OPENCLAW_CLI_TIMEOUT"
|
|
35
|
+
_DEFAULT_TIMEOUT_SECONDS = 180
|
|
36
|
+
|
|
37
|
+
_TRUTHY = {"1", "true", "yes"}
|
|
38
|
+
|
|
39
|
+
# Module-level state reset by tests via the _reset_module_state fixture.
|
|
40
|
+
_REGISTERED: bool = False
|
|
41
|
+
_HANDLER: OpenClawLLM | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class OpenClawCLIError(RuntimeError):
|
|
45
|
+
"""Raised when the openclaw CLI subprocess fails."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _env_enabled() -> bool:
|
|
49
|
+
"""Return True when OPENCLAW_SMART_USE_LOCAL_CLI is truthy.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
bool: True if the opt-in env var is set to a truthy value, else False.
|
|
53
|
+
"""
|
|
54
|
+
return os.environ.get(ENV_ENABLE, "").lower() in _TRUTHY
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _resolve_cli_path() -> str | None:
|
|
58
|
+
"""Resolve the openclaw CLI absolute path.
|
|
59
|
+
|
|
60
|
+
Honors ``OPENCLAW_BIN`` env override, otherwise falls back to
|
|
61
|
+
``shutil.which("openclaw")``.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
str | None: Absolute path to the openclaw binary, or None if not found.
|
|
65
|
+
"""
|
|
66
|
+
override = os.environ.get(ENV_CLI_PATH)
|
|
67
|
+
if override:
|
|
68
|
+
path = Path(override)
|
|
69
|
+
if path.is_file() and os.access(override, os.X_OK):
|
|
70
|
+
return override
|
|
71
|
+
return shutil.which("openclaw")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def is_openclaw_available() -> bool:
|
|
75
|
+
"""Return True when the openclaw provider is usable right now.
|
|
76
|
+
|
|
77
|
+
Both the opt-in env var *and* a resolvable CLI path are required.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
bool: True iff ``OPENCLAW_SMART_USE_LOCAL_CLI`` is truthy AND a
|
|
81
|
+
CLI binary is resolvable.
|
|
82
|
+
"""
|
|
83
|
+
return _env_enabled() and _resolve_cli_path() is not None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _extract_text(payload: Any) -> str:
|
|
87
|
+
"""Extract the model's text reply from openclaw's JSON output.
|
|
88
|
+
|
|
89
|
+
The exact field name in ``openclaw infer model run --json`` output
|
|
90
|
+
couldn't be probed at design time (no model providers were
|
|
91
|
+
auth-configured). Scan common keys in priority order: ``text`` →
|
|
92
|
+
``content`` → ``output`` → ``message.content``. The first end-to-end
|
|
93
|
+
test will surface the real key; we can pin it then if needed.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
payload (Any): Parsed JSON response from openclaw.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
str: Completion text, or empty string if no known key matches.
|
|
100
|
+
"""
|
|
101
|
+
if not isinstance(payload, dict):
|
|
102
|
+
return ""
|
|
103
|
+
for key in ("text", "content", "output"):
|
|
104
|
+
value = payload.get(key)
|
|
105
|
+
if isinstance(value, str):
|
|
106
|
+
return value
|
|
107
|
+
msg = payload.get("message")
|
|
108
|
+
if isinstance(msg, dict) and isinstance(msg.get("content"), str):
|
|
109
|
+
return msg["content"]
|
|
110
|
+
return ""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _call_cli(*, prompt: str, model: str | None, timeout_s: int) -> str:
|
|
114
|
+
"""Invoke openclaw CLI for a one-shot completion.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
prompt (str): User prompt text passed via ``--prompt``.
|
|
118
|
+
model (str | None): Optional model id passed via ``--model``.
|
|
119
|
+
timeout_s (int): Subprocess timeout in seconds.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
str: Completion text extracted from the CLI's JSON stdout.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
OpenClawCLIError: If the CLI is not found, exits non-zero, returns
|
|
126
|
+
non-JSON stdout, or returns JSON with no recognized text field.
|
|
127
|
+
"""
|
|
128
|
+
cli = _resolve_cli_path()
|
|
129
|
+
if not cli:
|
|
130
|
+
raise OpenClawCLIError("openclaw CLI not found; set OPENCLAW_BIN")
|
|
131
|
+
|
|
132
|
+
argv = [cli, "infer", "model", "run", "--prompt", prompt, "--json"]
|
|
133
|
+
if model:
|
|
134
|
+
argv += ["--model", model]
|
|
135
|
+
|
|
136
|
+
env = os.environ.copy()
|
|
137
|
+
env["OPENCLAW_SMART_INTERNAL"] = "1"
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
proc = subprocess.run( # noqa: S603 — argv is fully constructed.
|
|
141
|
+
argv,
|
|
142
|
+
env=env,
|
|
143
|
+
capture_output=True,
|
|
144
|
+
text=True,
|
|
145
|
+
timeout=timeout_s,
|
|
146
|
+
check=False,
|
|
147
|
+
)
|
|
148
|
+
except subprocess.TimeoutExpired as exc:
|
|
149
|
+
raise OpenClawCLIError(f"openclaw timed out after {timeout_s}s") from exc
|
|
150
|
+
|
|
151
|
+
if proc.returncode != 0:
|
|
152
|
+
raise OpenClawCLIError(
|
|
153
|
+
proc.stderr.strip() or f"openclaw exit {proc.returncode}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
payload = json.loads(proc.stdout)
|
|
158
|
+
except json.JSONDecodeError as exc:
|
|
159
|
+
raise OpenClawCLIError(
|
|
160
|
+
f"openclaw returned non-JSON: {proc.stdout[:200]}"
|
|
161
|
+
) from exc
|
|
162
|
+
|
|
163
|
+
text = _extract_text(payload)
|
|
164
|
+
if not text:
|
|
165
|
+
raise OpenClawCLIError(
|
|
166
|
+
f"openclaw JSON had no completion text: {list(payload)[:5]}"
|
|
167
|
+
)
|
|
168
|
+
return text
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class OpenClawLLM(CustomLLM):
|
|
172
|
+
"""LiteLLM CustomLLM that routes completions through the openclaw CLI."""
|
|
173
|
+
|
|
174
|
+
def completion(self, *args: Any, **kwargs: Any) -> ModelResponse: # noqa: ARG002 — LiteLLM CustomLLM signature
|
|
175
|
+
"""Synchronous completion via ``openclaw infer model run``.
|
|
176
|
+
|
|
177
|
+
The ``openclaw/`` prefix is stripped from the requested model id.
|
|
178
|
+
If no model is provided, ``OPENCLAW_DEFAULT_MODEL`` is used; if
|
|
179
|
+
that's also unset, the CLI's own default is used.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
*args: Unused (LiteLLM signature).
|
|
183
|
+
**kwargs: LiteLLM completion kwargs (``messages``, ``model``).
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
ModelResponse: Reply text in ``choices[0].message.content``.
|
|
187
|
+
"""
|
|
188
|
+
# ``kwargs.get("model")`` can be ``None`` — coercing via ``str(...)``
|
|
189
|
+
# would yield the literal "None" and skip the default-model fallback.
|
|
190
|
+
raw_model = kwargs.get("model")
|
|
191
|
+
model_kwarg = "" if raw_model is None else str(raw_model)
|
|
192
|
+
if "/" in model_kwarg:
|
|
193
|
+
model = model_kwarg.split("/", 1)[1] or None
|
|
194
|
+
else:
|
|
195
|
+
model = model_kwarg or None
|
|
196
|
+
if not model:
|
|
197
|
+
model = os.environ.get(ENV_DEFAULT_MODEL) or None
|
|
198
|
+
|
|
199
|
+
messages = kwargs.get("messages", [])
|
|
200
|
+
prompt = _messages_to_prompt(messages)
|
|
201
|
+
# An invalid OPENCLAW_CLI_TIMEOUT must not crash the completion path —
|
|
202
|
+
# fall back to the default with a warning so misconfiguration degrades
|
|
203
|
+
# to working-but-slow rather than working-then-erroring.
|
|
204
|
+
raw_timeout = os.environ.get(ENV_TIMEOUT)
|
|
205
|
+
try:
|
|
206
|
+
timeout_s = (
|
|
207
|
+
int(raw_timeout)
|
|
208
|
+
if raw_timeout is not None
|
|
209
|
+
else _DEFAULT_TIMEOUT_SECONDS
|
|
210
|
+
)
|
|
211
|
+
except ValueError:
|
|
212
|
+
_LOGGER.warning(
|
|
213
|
+
"Invalid %s=%r; using default %d",
|
|
214
|
+
ENV_TIMEOUT,
|
|
215
|
+
raw_timeout,
|
|
216
|
+
_DEFAULT_TIMEOUT_SECONDS,
|
|
217
|
+
)
|
|
218
|
+
timeout_s = _DEFAULT_TIMEOUT_SECONDS
|
|
219
|
+
|
|
220
|
+
text = _call_cli(prompt=prompt, model=model, timeout_s=timeout_s)
|
|
221
|
+
|
|
222
|
+
return ModelResponse(
|
|
223
|
+
choices=[Choices(message=Message(content=text, role="assistant"))],
|
|
224
|
+
usage=Usage(prompt_tokens=0, completion_tokens=0, total_tokens=0),
|
|
225
|
+
model=model_kwarg or "openclaw",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _messages_to_prompt(messages: list[dict[str, Any]]) -> str:
|
|
230
|
+
"""Flatten chat messages into a single prompt string.
|
|
231
|
+
|
|
232
|
+
``openclaw infer model run`` takes a single ``--prompt``; we serialize
|
|
233
|
+
the chat as ``role: content`` lines separated by blank lines. Content
|
|
234
|
+
blocks (lists) are flattened to text.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
messages (list[dict[str, Any]]): LiteLLM-style chat messages.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
str: Newline-joined role:content lines.
|
|
241
|
+
"""
|
|
242
|
+
parts: list[str] = []
|
|
243
|
+
for m in messages:
|
|
244
|
+
role = m.get("role", "user")
|
|
245
|
+
content = m.get("content", "")
|
|
246
|
+
if isinstance(content, list):
|
|
247
|
+
content = "".join(c.get("text", "") for c in content if isinstance(c, dict))
|
|
248
|
+
parts.append(f"{role}: {content}")
|
|
249
|
+
return "\n\n".join(parts)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def register_if_enabled() -> bool:
|
|
253
|
+
"""Register the openclaw provider with LiteLLM if the opt-in env is set.
|
|
254
|
+
|
|
255
|
+
Idempotent: safe to call multiple times.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
bool: True if the provider is now registered, False if env is off or
|
|
259
|
+
the CLI is missing.
|
|
260
|
+
"""
|
|
261
|
+
global _REGISTERED, _HANDLER
|
|
262
|
+
if _REGISTERED:
|
|
263
|
+
return True
|
|
264
|
+
if not _env_enabled():
|
|
265
|
+
return False
|
|
266
|
+
if not _resolve_cli_path():
|
|
267
|
+
_LOGGER.warning(
|
|
268
|
+
"%s=1 but openclaw CLI not found; set %s",
|
|
269
|
+
ENV_ENABLE,
|
|
270
|
+
ENV_CLI_PATH,
|
|
271
|
+
)
|
|
272
|
+
return False
|
|
273
|
+
_HANDLER = OpenClawLLM()
|
|
274
|
+
litellm.custom_provider_map = [
|
|
275
|
+
*getattr(litellm, "custom_provider_map", []),
|
|
276
|
+
{"provider": PROVIDER_KEY, "custom_handler": _HANDLER},
|
|
277
|
+
]
|
|
278
|
+
_REGISTERED = True
|
|
279
|
+
_LOGGER.info("Registered openclaw LiteLLM provider")
|
|
280
|
+
return True
|
|
@@ -150,7 +150,18 @@ def score_pairs(query: str, docs: list[str]) -> list[float]:
|
|
|
150
150
|
return []
|
|
151
151
|
model = _get_model()
|
|
152
152
|
pairs = [(query, doc) for doc in docs]
|
|
153
|
-
|
|
153
|
+
try:
|
|
154
|
+
from torch import nn
|
|
155
|
+
except ImportError as e:
|
|
156
|
+
raise CrossEncoderUnavailableError(
|
|
157
|
+
"torch is not installed; cannot use the cross-encoder reranker"
|
|
158
|
+
) from e
|
|
159
|
+
|
|
160
|
+
raw_scores = model.predict(
|
|
161
|
+
pairs,
|
|
162
|
+
show_progress_bar=False,
|
|
163
|
+
activation_fn=nn.Identity(),
|
|
164
|
+
)
|
|
154
165
|
# ``predict`` returns a numpy array; convert to plain Python floats so
|
|
155
166
|
# the caller can serialise the result without numpy as a dependency.
|
|
156
167
|
return [float(s) for s in raw_scores]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Plain, dependency-free per-run token accounting (OSS-safe).
|
|
2
|
+
|
|
3
|
+
Folds the per-turn token counts already present on a ToolLoopTrace into a single
|
|
4
|
+
run total. The enterprise billing layer converts this into a TokenUsage; OSS never
|
|
5
|
+
imports reflexio_ext.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(slots=True)
|
|
15
|
+
class RunTokenTotals:
|
|
16
|
+
"""Accumulated token counts for a single extraction agent run."""
|
|
17
|
+
|
|
18
|
+
prompt_tokens: int = 0
|
|
19
|
+
completion_tokens: int = 0
|
|
20
|
+
|
|
21
|
+
def add(self, *, prompt_tokens: int | None, completion_tokens: int | None) -> None:
|
|
22
|
+
"""Accumulate token counts, treating None as 0.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
prompt_tokens: Prompt token count for one turn, or None if unavailable.
|
|
26
|
+
completion_tokens: Completion token count for one turn, or None if unavailable.
|
|
27
|
+
"""
|
|
28
|
+
self.prompt_tokens += int(prompt_tokens or 0)
|
|
29
|
+
self.completion_tokens += int(completion_tokens or 0)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sum_trace_tokens(trace: Any) -> RunTokenTotals:
|
|
33
|
+
"""Fold a ToolLoopTrace's per-turn token counts into one RunTokenTotals.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
trace: A ToolLoopTrace (or duck-typed equivalent) with a ``turns`` attribute.
|
|
37
|
+
Each turn may have ``prompt_tokens`` and ``completion_tokens`` attributes.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
RunTokenTotals with the summed prompt and completion tokens across all turns.
|
|
41
|
+
"""
|
|
42
|
+
totals = RunTokenTotals()
|
|
43
|
+
for turn in getattr(trace, "turns", []) or []:
|
|
44
|
+
totals.add(
|
|
45
|
+
prompt_tokens=getattr(turn, "prompt_tokens", None),
|
|
46
|
+
completion_tokens=getattr(turn, "completion_tokens", None),
|
|
47
|
+
)
|
|
48
|
+
return totals
|