@tencent-rtc/trtc-agent-skills 0.1.3 → 0.1.4
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/.cursor/rules/main.mdc +12 -0
- package/AGENTS.md +14 -104
- package/CLAUDE.md +15 -127
- package/CODEBUDDY.md +32 -118
- package/README.md +7 -5
- package/README.zh.md +7 -5
- package/bin/cli.js +133 -41
- package/hooks/__pycache__/cursor-adapter.cpython-313.pyc +0 -0
- package/hooks/cursor-adapter.py +45 -18
- package/hooks/hooks-cursor.json +5 -18
- package/hooks/hooks.json +6 -31
- package/knowledge-base/conference/web/index.yaml +143 -0
- package/knowledge-base/platform-slice-template.md +1041 -133
- package/knowledge-base/products.yaml +76 -0
- package/knowledge-base/scenario-spec.md +1316 -115
- package/knowledge-base/scenarios/conference/base/general-conference.md +22 -2
- package/knowledge-base/scenarios/conference/base/webinar-conference.md +14 -0
- package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +19 -0
- package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +13 -0
- package/knowledge-base/scenarios/live/entertainment-live-room.md +12 -0
- package/knowledge-base/slice-spec.md +2377 -299
- package/knowledge-base/slices/conference/web/integration-audit.md +1 -1
- package/knowledge-base/slices/conference/web/login-auth.md +14 -1
- package/knowledge-base/tooling/aliases.yaml +92 -0
- package/knowledge-base/tooling/intent-signals.yaml +181 -0
- package/knowledge-base/tooling/symptom-keywords.yaml +21 -0
- package/package.json +1 -1
- package/skills/trtc/SKILL.md +202 -245
- package/skills/trtc/hooks/__pycache__/report_prompt.cpython-313.pyc +0 -0
- package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_read.py +12 -8
- package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_write.py +16 -12
- package/skills/{trtc-topic/guardrails → trtc/hooks}/stop_require_apply_evidence.py +22 -14
- package/skills/trtc/hooks/topic_phase_gate.py +161 -0
- package/skills/{trtc-topic → trtc}/runtime/README.md +2 -2
- package/skills/{trtc-onboarding/reference/reporting-protocol.md → trtc/runtime/REPORTING.md} +5 -5
- package/skills/{trtc-topic → trtc}/runtime/RUNTIME.md +5 -5
- package/skills/trtc/runtime/lib/__init__.py +0 -0
- package/skills/{trtc-topic → trtc}/runtime/package-lock.json +2 -2
- package/skills/{trtc-topic → trtc}/runtime/package.json +2 -2
- package/skills/{trtc-topic → trtc}/runtime/telemetry-bridge.mjs +1 -1
- package/skills/{trtc-topic/scripts → trtc/tools}/STATE-MACHINE-GUIDE.md +23 -23
- package/skills/trtc/tools/__init__.py +2 -0
- package/skills/trtc/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/query_classifier.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/reporting.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/search.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/session.cpython-313.pyc +0 -0
- package/skills/trtc/tools/apply.py +540 -0
- package/skills/trtc/tools/docs.py +712 -0
- package/skills/trtc/tools/docsbot.py +182 -0
- package/skills/trtc/tools/entry/render_ai_instructions.py +92 -0
- package/skills/trtc/tools/flow.py +1089 -0
- package/skills/{trtc-topic/scripts → trtc/tools}/init_slice_queue.py +5 -4
- package/skills/{trtc-topic/scripts → trtc/tools}/next_slice.py +5 -4
- package/skills/trtc/tools/query_classifier.py +301 -0
- package/skills/trtc/tools/reporting.py +447 -0
- package/skills/trtc/tools/search.py +817 -0
- package/skills/trtc/tools/session.py +1261 -0
- package/skills/trtc/tools/state_machine.py +690 -0
- package/skills/trtc-ai-service/README.md +195 -0
- package/skills/trtc-ai-service/README.zh-CN.md +193 -0
- package/skills/trtc-ai-service/SKILL.md +945 -0
- package/skills/trtc-ai-service/auto_adapters/README.md +40 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/README.md +27 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/angular/voice-agent.component.ts.tpl +131 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/manifest.yaml +57 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/react/VoiceAgent.tsx.tpl +142 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/vue/VoiceAgent.vue.tpl +121 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-backend.md +45 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-frontend.md +51 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-rest-api.md +93 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/README.md +25 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/manifest.yaml +30 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/quarkus/VoiceAgentFilter.java.tpl +64 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/springboot/VoiceAgentFilter.java.tpl +91 -0
- package/skills/trtc-ai-service/auto_adapters/manifest.yaml +43 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/README.md +25 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/express.js.tpl +40 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/fastify.js.tpl +27 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/koa.js.tpl +31 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/manifest.yaml +47 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/README.md +22 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/django.py.tpl +32 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/fastapi.py.tpl +35 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/flask.py.tpl +31 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/manifest.yaml +45 -0
- package/skills/trtc-ai-service/capabilities/__init__.py +43 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/.env.example +29 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/INTEGRATION.md +134 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/INTERFACE_ADAPT.md +111 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/QUICK_START.md +62 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/manifest.yaml +250 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/requirements.txt +6 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/__init__.py +10 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/_capability_loader.py +218 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/agent.py +231 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/credentials.py +132 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/health.py +355 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/log_filter.py +76 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/modality.py +109 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/server.py +312 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/trtc_client.py +315 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/usersig.py +90 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/tests/test_skeleton.py +216 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/README.md +48 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/app.js +415 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/index.html +68 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/styles.css +136 -0
- package/skills/trtc-ai-service/capabilities/digital-human/README.md +31 -0
- package/skills/trtc-ai-service/capabilities/digital-human/manifest.yaml +64 -0
- package/skills/trtc-ai-service/capabilities/digital-human/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/digital-human/src/router.py +43 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/INTERFACE_ADAPT.md +353 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/README.md +44 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/manifest.yaml +227 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/__init__.py +9 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/default_rest.py +242 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/factory.py +89 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/local_queue.py +258 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/mock.py +132 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/__init__.py +25 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/intent_detector.py +75 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/models.py +163 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/service.py +192 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/feedback_store.py +54 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/__init__.py +4 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/handoff_client.py +86 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/queue.py +62 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/router.py +201 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/summary_link.py +77 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/trigger.py +25 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/INTERFACE_ADAPT.md +297 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/README.md +51 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/data/faq.json +20 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/manifest.yaml +211 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/__init__.py +8 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/__init__.py +9 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/default_rest.py +209 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/factory.py +86 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/local_json.py +172 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/mock.py +91 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/__init__.py +12 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/models.py +77 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/scoring.py +73 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/service.py +78 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/__init__.py +4 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/kb_client.py +61 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/retriever.py +56 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/router.py +85 -0
- package/skills/trtc-ai-service/capabilities/session-summary/INTERFACE_ADAPT.md +99 -0
- package/skills/trtc-ai-service/capabilities/session-summary/README.md +47 -0
- package/skills/trtc-ai-service/capabilities/session-summary/data/test_session.json +18 -0
- package/skills/trtc-ai-service/capabilities/session-summary/manifest.yaml +165 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/__init__.py +5 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/base.py +31 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/default_rest.py +67 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/factory.py +51 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/local_json.py +42 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/mock.py +22 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/recorder.py +210 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/router.py +93 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/summarizer.py +163 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/INTERFACE_ADAPT.md +158 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/README.md +50 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/data/tools.yaml +58 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/examples/__init__.py +1 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/examples/local_tools.py +101 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/manifest.yaml +146 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/__init__.py +8 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/dispatcher.py +54 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/registry.py +219 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/router.py +50 -0
- package/skills/trtc-ai-service/references/business-contract-spec.md +263 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/README.md +86 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/output-templates/recipe.yaml.j2 +194 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q1-business-scenario.md +43 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q2-io-modality.md +57 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q3-ui-form.md +55 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q4-capabilities.md +78 -0
- package/skills/trtc-ai-service/scenarios/customer-service/README.md +114 -0
- package/skills/trtc-ai-service/scenarios/customer-service/recipe.yaml +154 -0
- package/skills/trtc-ai-service/scenarios/customer-service/sample-data/README.md +32 -0
- package/skills/trtc-ai-service/scenarios/customer-service/sample-data/faq-sample.json +37 -0
- package/skills/trtc-ai-service/scenarios/customer-service/system-prompt.template.md +94 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/app.js +347 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/index.html +125 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/styles.css +487 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/tokens.css +71 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/design-system/DESIGN_GUIDELINES.md +370 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/README.md +68 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/app.js +1307 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/data.js +40 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/index.html +233 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/mock-shop.json +21 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/styles.css +603 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/tokens.css +71 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/agent-link.js +323 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/app.js +458 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/index.html +109 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/styles.css +489 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/tokens.css +59 -0
- package/skills/trtc-ai-service/scripts/add-capability.py +364 -0
- package/skills/trtc-ai-service/scripts/contract-adapt.py +334 -0
- package/skills/trtc-ai-service/scripts/detect-stack.py +40 -0
- package/skills/trtc-ai-service/scripts/lib/__init__.py +20 -0
- package/skills/trtc-ai-service/scripts/lib/adapter_codegen.py +509 -0
- package/skills/trtc-ai-service/scripts/lib/arbitrator.py +152 -0
- package/skills/trtc-ai-service/scripts/lib/contract_resolver.py +519 -0
- package/skills/trtc-ai-service/scripts/lib/credential_validators.py +253 -0
- package/skills/trtc-ai-service/scripts/lib/curl_parser.py +303 -0
- package/skills/trtc-ai-service/scripts/lib/degrader.py +140 -0
- package/skills/trtc-ai-service/scripts/lib/injector.py +347 -0
- package/skills/trtc-ai-service/scripts/lib/manifest_resolver.py +288 -0
- package/skills/trtc-ai-service/scripts/lib/openapi_parser.py +289 -0
- package/skills/trtc-ai-service/scripts/lib/stack_detector.py +159 -0
- package/skills/trtc-ai-service/scripts/lib/tokens_compile.py +204 -0
- package/skills/trtc-ai-service/scripts/post-install-patch.py +225 -0
- package/skills/trtc-ai-service/scripts/setup-credentials.py +393 -0
- package/skills/trtc-ai-service/scripts/verify-credentials.py +108 -0
- package/skills/trtc-ai-service/start.sh +111 -0
- package/skills/trtc-ai-service/tests/__init__.py +1 -0
- package/skills/trtc-ai-service/tests/test_arbitrator.py +64 -0
- package/skills/trtc-ai-service/tests/test_capability_overlay.py +85 -0
- package/skills/trtc-ai-service/tests/test_contract_resolver.py +190 -0
- package/skills/trtc-ai-service/tests/test_handoff_ports.py +195 -0
- package/skills/trtc-ai-service/tests/test_kb_ports.py +195 -0
- package/skills/trtc-ai-service/tests/test_manifest_resolver.py +95 -0
- package/skills/trtc-ai-service/tests/test_recipe_assembly.py +175 -0
- package/skills/trtc-ai-service/tests/test_stack_and_degrader.py +101 -0
- package/skills/trtc-ai-service/tests/test_verify_credentials.py +285 -0
- package/skills/trtc-ai-service/triggers.yaml +29 -0
- package/skills/trtc-conference/SKILL.md +324 -0
- package/skills/trtc-conference/flows/onboarding.md +205 -0
- package/skills/trtc-conference/flows/topic.md +474 -0
- package/skills/trtc-conference/flows/troubleshoot.md +85 -0
- package/skills/trtc-conference/hooks/pretooluse_require_business_decisions.py +213 -0
- package/skills/trtc-conference/playbooks/medical-quickstart.md +84 -0
- package/skills/trtc-conference/playbooks/official-roomkit.md +97 -0
- package/skills/trtc-conference/references/local-usersig/basic-info-config.ts +39 -0
- package/skills/trtc-conference/references/usersig-handling.md +134 -0
- package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
- package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_onboarding_contract.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_topic_flow_contract.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-conference/tests/test_conference_index_contract.py +43 -0
- package/skills/trtc-conference/tests/test_conference_onboarding_contract.py +103 -0
- package/skills/trtc-conference/tests/test_conference_template_contract.py +25 -0
- package/skills/trtc-conference/tests/test_conference_topic_flow_contract.py +132 -0
- package/skills/trtc-conference/tools/apply_checks.py +328 -0
- package/skills/trtc-conference/verify_lib/__init__.py +0 -0
- package/skills/{trtc-apply/guardrails/apply_lib → trtc-conference/verify_lib}/rule_parser.py +1 -1
- package/skills/trtc-docs/SKILL.md +91 -119
- package/.cursor/rules/ui-mode.mdc +0 -99
- package/ai-instructions/base.md +0 -13
- package/ai-instructions/ui-mode.md +0 -93
- package/knowledge-base/index.yaml +0 -462
- package/skills/trtc/room-builder/SKILL.md +0 -133
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +0 -108
- package/skills/trtc/room-builder/tools/render_ai_instructions.py +0 -226
- package/skills/trtc-apply/SKILL.md +0 -97
- package/skills/trtc-onboarding/SKILL.md +0 -841
- package/skills/trtc-onboarding/reference/path-a1-demo.md +0 -103
- package/skills/trtc-onboarding/reference/path-a2-integrate.md +0 -737
- package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +0 -186
- package/skills/trtc-onboarding/reference/path-c-expand.md +0 -43
- package/skills/trtc-onboarding/reference/supported-matrix.md +0 -100
- package/skills/trtc-search/SKILL.md +0 -228
- package/skills/trtc-topic/SKILL.md +0 -622
- package/skills/trtc-topic/scripts/apply.py +0 -581
- package/skills/trtc-topic/scripts/lib/state_machine.py +0 -328
- package/skills/trtc-topic/tests/README.md +0 -70
- package/skills/trtc-topic/tests/conftest.py +0 -72
- package/skills/trtc-topic/tests/test_apply_cli.py +0 -480
- package/skills/trtc-topic/tests/test_end_to_end.py +0 -305
- package/skills/trtc-topic/tests/test_finalize_session.py +0 -51
- package/skills/trtc-topic/tests/test_gates.py +0 -316
- package/skills/trtc-topic/tests/test_session_resolver.py +0 -260
- package/skills/trtc-topic/tests/test_state_machine.py +0 -414
- package/skills/trtc-topic/tests/test_stop_require_apply.py +0 -99
- package/skills/trtc-topic/tests/test_topic_skill_invariants.py +0 -130
- /package/skills/{trtc-topic → trtc}/runtime/lib/platforms.py +0 -0
- /package/skills/{trtc-topic → trtc}/runtime/telemetry_collector.py +0 -0
- /package/skills/{trtc-onboarding/reference → trtc/runtime}/usersig-handling.md +0 -0
- /package/skills/{trtc-topic/scripts → trtc/tools}/finalize_session.py +0 -0
- /package/skills/{trtc-apply/guardrails/apply_lib → trtc-ai-service/capabilities/conversation-core/tests}/__init__.py +0 -0
- /package/skills/{trtc-topic → trtc-conference}/references/execution-units.yaml +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios/medical-consultation/src/config → trtc-conference/references/local-usersig}/lib-generate-test-usersig-es.min.d.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios/medical-consultation/src/config → trtc-conference/references/local-usersig}/lib-generate-test-usersig-es.min.js +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/backend-contract.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/integration.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/theme.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/index.html +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/package.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/postcss.config.js +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/App.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/ConsultationManagePanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LanguageSwitch.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LoadingSpinner.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalAlert.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalBusinessPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalButton.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalConfirmDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalDataPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalRecordPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/PrescriptionPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/basic-info-config.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/runtime-config.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/env.d.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/types.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationChat.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationDevices.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationParticipants.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationPermissions.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/utils.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/en-US/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/medicalTranslate.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/state.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/zh-CN/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/main.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/appointments.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/users.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/router/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/appointmentService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/authService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/launchContext.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/userService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/appointmentService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/authService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/userService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/types.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/shared/icons.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/index.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/tailwind.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/theme.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/auth.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/format.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/navigation.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/session.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorConsultationView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorDashboardView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/LoginView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationFinishedView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientSelectDoctorView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientWaitingView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.node.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/vite.config.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md" +0 -0
- /package/skills/{trtc-topic/runtime/lib → trtc-conference/tools}/__init__.py +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Three-key validation function module (Phase 3 Stage 5).
|
|
2
|
+
|
|
3
|
+
Design purpose:
|
|
4
|
+
- Share the same validation functions between ``scripts/setup-credentials.py``
|
|
5
|
+
(developer interactive fallback) and ``scripts/verify-credentials.py``
|
|
6
|
+
(AI-driven credential-less validation).
|
|
7
|
+
- The validation logic itself comes from ``capabilities/conversation-core/src/health.py``;
|
|
8
|
+
this module only handles "credential loading + result normalization to JSON."
|
|
9
|
+
|
|
10
|
+
Core API:
|
|
11
|
+
- ``validate_tencent(env)`` / ``validate_trtc(env, deep=True)`` / ``validate_llm(env)``
|
|
12
|
+
→ All return a ``ValidationResult`` dataclass that can be serialized via ``to_dict()``
|
|
13
|
+
to ``{ok, type, error, message, latency_ms}``.
|
|
14
|
+
|
|
15
|
+
Security constraints (must follow):
|
|
16
|
+
- The entire process **only reads credentials from .env / process env**, never accepts keys via CLI arguments.
|
|
17
|
+
- Output JSON **does not contain** credential plaintext; the error field only holds error codes / brief messages.
|
|
18
|
+
- The caller (CLI / AI) should treat stdout as parseable JSON and not echo keys in the terminal.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Dict, Iterable, List, Optional
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# 1) Load conversation-core credential / health check modules
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
_HERE = Path(__file__).resolve().parent
|
|
33
|
+
_ROOT = _HERE.parent.parent
|
|
34
|
+
_CORE_DIR = _ROOT / "capabilities" / "conversation-core"
|
|
35
|
+
|
|
36
|
+
if str(_CORE_DIR) not in sys.path:
|
|
37
|
+
sys.path.insert(0, str(_CORE_DIR))
|
|
38
|
+
|
|
39
|
+
# Lazy imports: for verify-credentials.py to call after .env is loaded
|
|
40
|
+
def _imports():
|
|
41
|
+
from src.credentials import ( # type: ignore # noqa: WPS433
|
|
42
|
+
LlmCredential,
|
|
43
|
+
TencentCloudCredential,
|
|
44
|
+
TrtcCredential,
|
|
45
|
+
load_from_env,
|
|
46
|
+
)
|
|
47
|
+
from src.health import ( # type: ignore # noqa: WPS433
|
|
48
|
+
check_llm,
|
|
49
|
+
check_tencent_cloud,
|
|
50
|
+
check_trtc,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"LlmCredential": LlmCredential,
|
|
55
|
+
"TencentCloudCredential": TencentCloudCredential,
|
|
56
|
+
"TrtcCredential": TrtcCredential,
|
|
57
|
+
"load_from_env": load_from_env,
|
|
58
|
+
"check_llm": check_llm,
|
|
59
|
+
"check_tencent_cloud": check_tencent_cloud,
|
|
60
|
+
"check_trtc": check_trtc,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# 2) Error codes → AI response hints (aligned with SKILL.md §5.5 lookup table)
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
_ERROR_HINTS: Dict[str, str] = {
|
|
68
|
+
"E001": "Tencent Cloud SecretId/SecretKey verification failed (AuthFailure / STS not enabled on account).",
|
|
69
|
+
"E002": "TRTC app credentials verification failed (SDKAppID does not belong to this account / SDKSecretKey mismatch).",
|
|
70
|
+
"E003": "LLM verification failed (auth 401/403 or non-200 response).",
|
|
71
|
+
"E004": "Network unreachable / timeout (check proxy / firewall).",
|
|
72
|
+
"E000": "Credential not configured or empty.",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# 3) Unified return structures
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
@dataclass
|
|
80
|
+
class ValidationResult:
|
|
81
|
+
"""Validation result for a single key, serialized as verify-credentials.py stdout JSON."""
|
|
82
|
+
|
|
83
|
+
ok: bool
|
|
84
|
+
type: str # "tencent" | "trtc" | "llm" | "all"
|
|
85
|
+
error: str = "" # Error code (E000~E004 or empty)
|
|
86
|
+
message: str = "" # Human-readable description (no key plaintext)
|
|
87
|
+
latency_ms: int = 0
|
|
88
|
+
|
|
89
|
+
def to_dict(self) -> Dict:
|
|
90
|
+
return {
|
|
91
|
+
"ok": self.ok,
|
|
92
|
+
"type": self.type,
|
|
93
|
+
"error": self.error,
|
|
94
|
+
"message": self.message,
|
|
95
|
+
"latency_ms": self.latency_ms,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class BatchResult:
|
|
101
|
+
"""Aggregate validation result (used when type=all)."""
|
|
102
|
+
|
|
103
|
+
ok: bool
|
|
104
|
+
items: List[ValidationResult] = field(default_factory=list)
|
|
105
|
+
|
|
106
|
+
def to_dict(self) -> Dict:
|
|
107
|
+
return {
|
|
108
|
+
"ok": self.ok,
|
|
109
|
+
"type": "all",
|
|
110
|
+
"items": [r.to_dict() for r in self.items],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# 4) .env loading (independent of dotenv, avoids extra dependencies)
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
def load_dotenv(env_path: Optional[Path] = None) -> Dict[str, str]:
|
|
118
|
+
"""Read ``.env`` into ``os.environ``; returns newly added/overwritten key-value pairs.
|
|
119
|
+
|
|
120
|
+
Path priority: parameter > capabilities/conversation-core/.env > repo root .env.
|
|
121
|
+
Does not raise; returns empty dict if no file found.
|
|
122
|
+
"""
|
|
123
|
+
candidates: List[Path] = []
|
|
124
|
+
if env_path is not None:
|
|
125
|
+
candidates.append(Path(env_path))
|
|
126
|
+
candidates.append(_CORE_DIR / ".env")
|
|
127
|
+
candidates.append(_ROOT / ".env")
|
|
128
|
+
|
|
129
|
+
seen: Dict[str, str] = {}
|
|
130
|
+
for c in candidates:
|
|
131
|
+
if not c.exists() or not c.is_file():
|
|
132
|
+
continue
|
|
133
|
+
try:
|
|
134
|
+
text = c.read_text(encoding="utf-8")
|
|
135
|
+
except OSError:
|
|
136
|
+
continue
|
|
137
|
+
for line in text.splitlines():
|
|
138
|
+
line = line.strip()
|
|
139
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
140
|
+
continue
|
|
141
|
+
k, _, v = line.partition("=")
|
|
142
|
+
k = k.strip()
|
|
143
|
+
v = v.strip().strip('"').strip("'")
|
|
144
|
+
if k and k not in seen:
|
|
145
|
+
seen[k] = v
|
|
146
|
+
# 不覆盖已存在的进程级环境变量(CI / 容器优先)
|
|
147
|
+
os.environ.setdefault(k, v)
|
|
148
|
+
# 找到第一个就够;不再往下找以避免覆盖
|
|
149
|
+
if seen:
|
|
150
|
+
break
|
|
151
|
+
return seen
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
# 5) Single-key validation functions
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
def validate_tencent() -> ValidationResult:
|
|
158
|
+
"""Validate Tencent Cloud API SecretId/SecretKey."""
|
|
159
|
+
mods = _imports()
|
|
160
|
+
creds = mods["load_from_env"]()
|
|
161
|
+
tc = creds.tencent_cloud
|
|
162
|
+
if not tc.configured:
|
|
163
|
+
return ValidationResult(
|
|
164
|
+
ok=False,
|
|
165
|
+
type="tencent",
|
|
166
|
+
error="E000",
|
|
167
|
+
message="TENCENT_CLOUD_SECRET_ID / TENCENT_CLOUD_SECRET_KEY not configured",
|
|
168
|
+
)
|
|
169
|
+
r = mods["check_tencent_cloud"](tc)
|
|
170
|
+
return ValidationResult(
|
|
171
|
+
ok=r.ok,
|
|
172
|
+
type="tencent",
|
|
173
|
+
error="" if r.ok else (r.error_code or "E001"),
|
|
174
|
+
message=r.detail if not r.ok else f"sts/GetFederationToken ok (region={tc.region})",
|
|
175
|
+
latency_ms=r.latency_ms,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def validate_trtc(deep: bool = True) -> ValidationResult:
|
|
180
|
+
"""Validate TRTC SDKAppId / SDKSecretKey.
|
|
181
|
+
|
|
182
|
+
When deep=True and Tencent Cloud API credentials are configured, calls
|
|
183
|
+
``DescribeTRTCRealTimeQualityData`` for ownership verification;
|
|
184
|
+
otherwise only does local UserSig self-consistency check.
|
|
185
|
+
"""
|
|
186
|
+
mods = _imports()
|
|
187
|
+
creds = mods["load_from_env"]()
|
|
188
|
+
trtc = creds.trtc
|
|
189
|
+
tc = creds.tencent_cloud if deep else None
|
|
190
|
+
if not trtc.configured:
|
|
191
|
+
return ValidationResult(
|
|
192
|
+
ok=False,
|
|
193
|
+
type="trtc",
|
|
194
|
+
error="E000",
|
|
195
|
+
message="TRTC_SDK_APP_ID / TRTC_SDK_SECRET_KEY not configured",
|
|
196
|
+
)
|
|
197
|
+
r = mods["check_trtc"](trtc, tencent=tc if (tc and tc.configured) else None)
|
|
198
|
+
return ValidationResult(
|
|
199
|
+
ok=r.ok,
|
|
200
|
+
type="trtc",
|
|
201
|
+
error="" if r.ok else (r.error_code or "E002"),
|
|
202
|
+
message=r.detail or ("usersig/openapi ok" if r.ok else "trtc check failed"),
|
|
203
|
+
latency_ms=r.latency_ms,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def validate_llm() -> ValidationResult:
|
|
208
|
+
"""Validate LLM API Key (OpenAI-compatible protocol)."""
|
|
209
|
+
mods = _imports()
|
|
210
|
+
creds = mods["load_from_env"]()
|
|
211
|
+
llm = creds.llm
|
|
212
|
+
if not llm.configured:
|
|
213
|
+
return ValidationResult(
|
|
214
|
+
ok=False,
|
|
215
|
+
type="llm",
|
|
216
|
+
error="E000",
|
|
217
|
+
message="LLM_API_KEY / LLM_API_URL / LLM_MODEL not configured",
|
|
218
|
+
)
|
|
219
|
+
r = mods["check_llm"](llm)
|
|
220
|
+
return ValidationResult(
|
|
221
|
+
ok=r.ok,
|
|
222
|
+
type="llm",
|
|
223
|
+
error="" if r.ok else (r.error_code or "E003"),
|
|
224
|
+
message=r.detail or (f"chat/completions 200 ok (model={llm.model})" if r.ok else "llm failed"),
|
|
225
|
+
latency_ms=r.latency_ms,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def validate_all() -> BatchResult:
|
|
230
|
+
"""Validate all three keys sequentially; any failure → ok=False."""
|
|
231
|
+
items = [validate_tencent(), validate_trtc(deep=True), validate_llm()]
|
|
232
|
+
return BatchResult(ok=all(i.ok for i in items), items=items)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
# 6) Error code → hint (for CLI non-JSON mode; AI does not read this)
|
|
237
|
+
# ---------------------------------------------------------------------------
|
|
238
|
+
def hint(error_code: str) -> str:
|
|
239
|
+
return _ERROR_HINTS.get(error_code, "")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
# 7) Self-test: allows `python -m scripts.lib.credential_validators` to run independently
|
|
244
|
+
# ---------------------------------------------------------------------------
|
|
245
|
+
def _self_test() -> int:
|
|
246
|
+
load_dotenv()
|
|
247
|
+
out = validate_all()
|
|
248
|
+
print(json.dumps(out.to_dict(), ensure_ascii=False, indent=2))
|
|
249
|
+
return 0 if out.ok else 1
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
if __name__ == "__main__": # pragma: no cover
|
|
253
|
+
sys.exit(_self_test())
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""curl command parser (Phase 3 Stage 4).
|
|
2
|
+
|
|
3
|
+
Parses curl commands pasted by users in the chat into unified ``ParsedApi`` data structures,
|
|
4
|
+
consumed by ``contract_resolver`` and ``adapter_codegen``.
|
|
5
|
+
|
|
6
|
+
Supported:
|
|
7
|
+
- Multi-line (trailing ``\\``) / single-line
|
|
8
|
+
- ``-X / --request <METHOD>``
|
|
9
|
+
- ``-H / --header 'k: v'`` (accumulates on repeated appearances)
|
|
10
|
+
- ``-d / --data / --data-raw / --data-binary '<body>'``
|
|
11
|
+
- ``--url <url>`` or URL as a positional argument
|
|
12
|
+
- Single-quoted / double-quoted strings (including escapes)
|
|
13
|
+
- Auto-detect Authorization → ``bearer``; custom ``X-*-Token`` → ``api_key``
|
|
14
|
+
- Auto-detect if body is JSON and extract ``request_schema`` fields accordingly
|
|
15
|
+
|
|
16
|
+
Not supported (when protocol_mismatch=True, contract_resolver triggers L3 fallback):
|
|
17
|
+
- multipart / form-data uploads
|
|
18
|
+
- ``--cookie``
|
|
19
|
+
- Non-JSON body (XML / GraphQL string body)
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import re
|
|
25
|
+
import shlex
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
28
|
+
from urllib.parse import urlparse, urlsplit
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# 数据结构
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
@dataclass
|
|
35
|
+
class AuthSpec:
|
|
36
|
+
type: str = "none" # bearer | api_key | basic | none
|
|
37
|
+
location: str = "header" # header | query
|
|
38
|
+
name: str = "" # header 名 / query 键名
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> Dict:
|
|
41
|
+
return {"type": self.type, "location": self.location, "name": self.name}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ParsedApi:
|
|
46
|
+
"""curl / OpenAPI 解析后的统一表示。"""
|
|
47
|
+
|
|
48
|
+
method: str = "GET"
|
|
49
|
+
base_url: str = "" # https://crm.example.com
|
|
50
|
+
path: str = "/" # /api/v2/work_orders(不带 query)
|
|
51
|
+
query: Dict[str, str] = field(default_factory=dict)
|
|
52
|
+
headers: Dict[str, str] = field(default_factory=dict)
|
|
53
|
+
auth: AuthSpec = field(default_factory=AuthSpec)
|
|
54
|
+
request_schema: Dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
response_schema: Dict[str, Any] = field(default_factory=dict)
|
|
56
|
+
body_format: str = "none" # json | form | raw | none
|
|
57
|
+
body_sample: Any = None # 原始 body(已解析为 dict / str)
|
|
58
|
+
source: str = "curl" # curl | openapi
|
|
59
|
+
raw: str = "" # 原始输入(供 codegen 调试)
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> Dict:
|
|
62
|
+
return {
|
|
63
|
+
"method": self.method,
|
|
64
|
+
"base_url": self.base_url,
|
|
65
|
+
"path": self.path,
|
|
66
|
+
"query": dict(self.query),
|
|
67
|
+
"auth": self.auth.to_dict(),
|
|
68
|
+
# 注意:schema 已经在 parse 阶段标准化过;这里**不再**重复 normalize,
|
|
69
|
+
# 否则像 "string[]" 这类已 normalized 的字面量会被二次降级为 "string"
|
|
70
|
+
"request_schema": dict(self.request_schema),
|
|
71
|
+
"response_schema": dict(self.response_schema),
|
|
72
|
+
"body_format": self.body_format,
|
|
73
|
+
"source": self.source,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# 工具函数
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
def _normalize_schema(node: Any) -> Any:
|
|
81
|
+
"""把任意 dict / list 标准化为"字段 → 类型字符串"递归视图。"""
|
|
82
|
+
if isinstance(node, dict):
|
|
83
|
+
out: Dict[str, Any] = {}
|
|
84
|
+
for k, v in node.items():
|
|
85
|
+
out[k] = _normalize_schema(v)
|
|
86
|
+
return out
|
|
87
|
+
if isinstance(node, list):
|
|
88
|
+
if not node:
|
|
89
|
+
return "array<any>"
|
|
90
|
+
# 同质数组取第一个递归
|
|
91
|
+
first = _normalize_schema(node[0])
|
|
92
|
+
if isinstance(first, str):
|
|
93
|
+
return f"{first}[]"
|
|
94
|
+
return [first]
|
|
95
|
+
if isinstance(node, str):
|
|
96
|
+
return "string"
|
|
97
|
+
if isinstance(node, bool): # bool 必须在 int 之前判断
|
|
98
|
+
return "bool"
|
|
99
|
+
if isinstance(node, int):
|
|
100
|
+
return "int"
|
|
101
|
+
if isinstance(node, float):
|
|
102
|
+
return "float"
|
|
103
|
+
if node is None:
|
|
104
|
+
return "null"
|
|
105
|
+
return type(node).__name__
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _split_url(url: str) -> Tuple[str, str, Dict[str, str]]:
|
|
109
|
+
"""``https://a.com/b?c=1`` → (``https://a.com``, ``/b``, {c:'1'})"""
|
|
110
|
+
parts = urlsplit(url)
|
|
111
|
+
if not parts.scheme:
|
|
112
|
+
# 用户可能贴了相对路径 /tickets
|
|
113
|
+
return ("", url.split("?", 1)[0], _parse_query(url.split("?", 1)[1] if "?" in url else ""))
|
|
114
|
+
base = f"{parts.scheme}://{parts.netloc}"
|
|
115
|
+
path = parts.path or "/"
|
|
116
|
+
return (base, path, _parse_query(parts.query))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _parse_query(qs: str) -> Dict[str, str]:
|
|
120
|
+
out: Dict[str, str] = {}
|
|
121
|
+
for pair in qs.split("&"):
|
|
122
|
+
if not pair:
|
|
123
|
+
continue
|
|
124
|
+
if "=" in pair:
|
|
125
|
+
k, _, v = pair.partition("=")
|
|
126
|
+
out[k] = v
|
|
127
|
+
else:
|
|
128
|
+
out[pair] = ""
|
|
129
|
+
return out
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
_BEARER_RE = re.compile(r"^\s*Bearer\s+\S+\s*$", re.IGNORECASE)
|
|
133
|
+
_BASIC_RE = re.compile(r"^\s*Basic\s+\S+\s*$", re.IGNORECASE)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _detect_auth(headers: Dict[str, str]) -> AuthSpec:
|
|
137
|
+
for k, v in headers.items():
|
|
138
|
+
if k.lower() == "authorization":
|
|
139
|
+
if _BEARER_RE.match(v):
|
|
140
|
+
return AuthSpec(type="bearer", location="header", name=k)
|
|
141
|
+
if _BASIC_RE.match(v):
|
|
142
|
+
return AuthSpec(type="basic", location="header", name=k)
|
|
143
|
+
return AuthSpec(type="bearer", location="header", name=k)
|
|
144
|
+
# 常见自定义鉴权头
|
|
145
|
+
if k.lower() in {"x-auth-token", "x-api-key", "x-access-token", "x-token"}:
|
|
146
|
+
return AuthSpec(type="api_key", location="header", name=k)
|
|
147
|
+
return AuthSpec(type="none", location="header", name="")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# 主解析逻辑
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
class CurlParseError(ValueError):
|
|
154
|
+
"""curl 解析失败。"""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def parse_curl(raw: str) -> ParsedApi:
|
|
158
|
+
"""主入口:解析任意 curl 字符串。
|
|
159
|
+
|
|
160
|
+
Raises
|
|
161
|
+
------
|
|
162
|
+
CurlParseError
|
|
163
|
+
当输入不是 curl 命令、缺少 URL、或无法 tokenize。
|
|
164
|
+
"""
|
|
165
|
+
if not raw or not raw.strip():
|
|
166
|
+
raise CurlParseError("empty input")
|
|
167
|
+
|
|
168
|
+
# 1) 行尾 \ 拼接 + 去掉首个 curl 命令名
|
|
169
|
+
text = re.sub(r"\\\s*\n", " ", raw.strip())
|
|
170
|
+
# 替换非 ASCII 全角空格
|
|
171
|
+
text = text.replace("\u3000", " ")
|
|
172
|
+
try:
|
|
173
|
+
tokens = shlex.split(text, posix=True)
|
|
174
|
+
except ValueError as exc: # 不闭合引号
|
|
175
|
+
raise CurlParseError(f"shell tokenize failed: {exc}") from exc
|
|
176
|
+
if not tokens:
|
|
177
|
+
raise CurlParseError("no tokens after split")
|
|
178
|
+
|
|
179
|
+
# 跳过 curl 命令头(兼容用户漏贴的情况)
|
|
180
|
+
if tokens[0].lower().endswith("curl"):
|
|
181
|
+
tokens = tokens[1:]
|
|
182
|
+
|
|
183
|
+
method: Optional[str] = None
|
|
184
|
+
url: Optional[str] = None
|
|
185
|
+
headers: Dict[str, str] = {}
|
|
186
|
+
body_raw: Optional[str] = None
|
|
187
|
+
body_format = "none"
|
|
188
|
+
|
|
189
|
+
i = 0
|
|
190
|
+
while i < len(tokens):
|
|
191
|
+
t = tokens[i]
|
|
192
|
+
if t in ("-X", "--request"):
|
|
193
|
+
method = tokens[i + 1].upper()
|
|
194
|
+
i += 2
|
|
195
|
+
continue
|
|
196
|
+
if t in ("-H", "--header"):
|
|
197
|
+
kv = tokens[i + 1]
|
|
198
|
+
if ":" in kv:
|
|
199
|
+
k, _, v = kv.partition(":")
|
|
200
|
+
headers[k.strip()] = v.strip()
|
|
201
|
+
i += 2
|
|
202
|
+
continue
|
|
203
|
+
if t in ("-d", "--data", "--data-raw", "--data-binary", "--data-ascii", "--json"):
|
|
204
|
+
body_raw = tokens[i + 1]
|
|
205
|
+
body_format = "json" if t == "--json" else "raw"
|
|
206
|
+
i += 2
|
|
207
|
+
continue
|
|
208
|
+
if t == "--url":
|
|
209
|
+
url = tokens[i + 1]
|
|
210
|
+
i += 2
|
|
211
|
+
continue
|
|
212
|
+
if t in ("-G", "--get"):
|
|
213
|
+
method = method or "GET"
|
|
214
|
+
i += 1
|
|
215
|
+
continue
|
|
216
|
+
if t in ("-u", "--user"):
|
|
217
|
+
# basic auth: user:pass → 仅识别为 basic,不存原值
|
|
218
|
+
headers.setdefault("Authorization", "Basic <redacted>")
|
|
219
|
+
i += 2
|
|
220
|
+
continue
|
|
221
|
+
if t in ("-X", "--include", "-i", "-I", "--head", "-v", "--verbose",
|
|
222
|
+
"-s", "--silent", "-L", "--location", "-k", "--insecure",
|
|
223
|
+
"--compressed", "-f", "--fail"):
|
|
224
|
+
# 控制类参数,跳过
|
|
225
|
+
i += 1
|
|
226
|
+
continue
|
|
227
|
+
if t.startswith("--") or t.startswith("-"):
|
|
228
|
+
# 不认识的选项,吃掉它(防御性:可能带值)
|
|
229
|
+
if i + 1 < len(tokens) and not tokens[i + 1].startswith("-"):
|
|
230
|
+
i += 2
|
|
231
|
+
else:
|
|
232
|
+
i += 1
|
|
233
|
+
continue
|
|
234
|
+
# 位置参数:第一个非选项 token 当作 URL
|
|
235
|
+
if url is None:
|
|
236
|
+
url = t
|
|
237
|
+
i += 1
|
|
238
|
+
|
|
239
|
+
if not url:
|
|
240
|
+
raise CurlParseError("no URL found in curl command")
|
|
241
|
+
|
|
242
|
+
# 推断 method
|
|
243
|
+
if method is None:
|
|
244
|
+
method = "POST" if body_raw is not None else "GET"
|
|
245
|
+
|
|
246
|
+
base_url, path, query = _split_url(url)
|
|
247
|
+
|
|
248
|
+
# 解析 body 为 JSON(best effort)
|
|
249
|
+
request_schema: Dict[str, Any] = {}
|
|
250
|
+
body_sample: Any = body_raw
|
|
251
|
+
if body_raw:
|
|
252
|
+
stripped = body_raw.strip()
|
|
253
|
+
if stripped.startswith("{") or stripped.startswith("["):
|
|
254
|
+
try:
|
|
255
|
+
parsed = json.loads(stripped)
|
|
256
|
+
body_sample = parsed
|
|
257
|
+
body_format = "json"
|
|
258
|
+
if isinstance(parsed, dict):
|
|
259
|
+
request_schema = _normalize_schema(parsed) # type: ignore[assignment]
|
|
260
|
+
except json.JSONDecodeError:
|
|
261
|
+
body_format = "raw"
|
|
262
|
+
elif "&" in stripped and "=" in stripped:
|
|
263
|
+
# 简易 form-urlencoded
|
|
264
|
+
body_format = "form"
|
|
265
|
+
request_schema = {k: "string" for k in _parse_query(stripped).keys()}
|
|
266
|
+
|
|
267
|
+
auth = _detect_auth(headers)
|
|
268
|
+
|
|
269
|
+
return ParsedApi(
|
|
270
|
+
method=method,
|
|
271
|
+
base_url=base_url,
|
|
272
|
+
path=path,
|
|
273
|
+
query=query,
|
|
274
|
+
headers=headers,
|
|
275
|
+
auth=auth,
|
|
276
|
+
request_schema=request_schema,
|
|
277
|
+
response_schema={}, # curl 命令本身没有响应;用户可在第 2 步贴响应
|
|
278
|
+
body_format=body_format,
|
|
279
|
+
body_sample=body_sample,
|
|
280
|
+
source="curl",
|
|
281
|
+
raw=raw,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# 可选:从 curl 注释或额外的"# response: {...}"块中提取响应样例
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
_RESPONSE_HINT_RE = re.compile(
|
|
289
|
+
r"#\s*(?:resp|response)\s*[:=]\s*(\{.*?\}|\[.*?\])",
|
|
290
|
+
re.IGNORECASE | re.DOTALL,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def parse_curl_with_response(raw: str) -> ParsedApi:
|
|
295
|
+
"""额外尝试从注释行中提取 ``# response: {...}`` 作为 response_schema。"""
|
|
296
|
+
api = parse_curl(raw)
|
|
297
|
+
m = _RESPONSE_HINT_RE.search(raw)
|
|
298
|
+
if m:
|
|
299
|
+
try:
|
|
300
|
+
api.response_schema = _normalize_schema(json.loads(m.group(1))) # type: ignore[assignment]
|
|
301
|
+
except json.JSONDecodeError:
|
|
302
|
+
pass
|
|
303
|
+
return api
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Three-tier fallback decision engine.
|
|
2
|
+
|
|
3
|
+
Input: tech stack detection result + adapter execution result.
|
|
4
|
+
Output: fallback level (L1 / L2 / L3) + user guidance content path.
|
|
5
|
+
|
|
6
|
+
| Level | Trigger Condition | Agent Behavior |
|
|
7
|
+
|:-----:|:--------------------------------------------|:--------------------------------------------|
|
|
8
|
+
| L1 | tech_stack matches adapter + codegen OK | Write directly into user project |
|
|
9
|
+
| L2 | tech_stack matches but codegen failed/conflict | Output INTEGRATION_GUIDE.md template + template code |
|
|
10
|
+
| L3 | tech_stack not recognized or not supported | Output generic REST API docs + SDK install commands |
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DegradeLevel(str, Enum):
|
|
21
|
+
L1_AUTO = "L1"
|
|
22
|
+
L2_GUIDED = "L2"
|
|
23
|
+
L3_MANUAL = "L3"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class DegradeDecision:
|
|
28
|
+
level: DegradeLevel
|
|
29
|
+
reason: str
|
|
30
|
+
adapter: Optional[str] = None
|
|
31
|
+
tech_stack: Optional[str] = None
|
|
32
|
+
artifacts: List[str] = None # type: ignore[assignment]
|
|
33
|
+
remediation: str = ""
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> dict:
|
|
36
|
+
return {
|
|
37
|
+
"level": self.level.value,
|
|
38
|
+
"reason": self.reason,
|
|
39
|
+
"tech_stack": self.tech_stack,
|
|
40
|
+
"adapter": self.adapter,
|
|
41
|
+
"artifacts": self.artifacts or [],
|
|
42
|
+
"remediation": self.remediation,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def decide(
|
|
47
|
+
tech_stack: Optional[str],
|
|
48
|
+
adapter: Optional[str],
|
|
49
|
+
code_gen_ok: bool,
|
|
50
|
+
*,
|
|
51
|
+
fallback: Optional[Dict] = None,
|
|
52
|
+
code_gen_error: str = "",
|
|
53
|
+
) -> DegradeDecision:
|
|
54
|
+
"""根据上游信号产出降级决策。
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
tech_stack
|
|
59
|
+
stack_detector.detect() 的 ``primary`` 字段。
|
|
60
|
+
adapter
|
|
61
|
+
manifest.integration.auto_adapters 中匹配到的适配器名。None 表示不支持。
|
|
62
|
+
code_gen_ok
|
|
63
|
+
L1 阶段实际尝试生成代码后的成功标志。
|
|
64
|
+
fallback
|
|
65
|
+
manifest.integration.fallback 节点内容(提供 guided_templates / manual_api)。
|
|
66
|
+
code_gen_error
|
|
67
|
+
L1 失败时的错误描述(写入 reason)。
|
|
68
|
+
"""
|
|
69
|
+
fb = fallback or {}
|
|
70
|
+
if tech_stack and adapter and code_gen_ok:
|
|
71
|
+
return DegradeDecision(
|
|
72
|
+
level=DegradeLevel.L1_AUTO,
|
|
73
|
+
reason="tech stack matched and code generation succeeded",
|
|
74
|
+
tech_stack=tech_stack,
|
|
75
|
+
adapter=adapter,
|
|
76
|
+
artifacts=[],
|
|
77
|
+
remediation="",
|
|
78
|
+
)
|
|
79
|
+
if tech_stack and adapter and not code_gen_ok:
|
|
80
|
+
templates = list(fb.get("guided_templates") or [])
|
|
81
|
+
return DegradeDecision(
|
|
82
|
+
level=DegradeLevel.L2_GUIDED,
|
|
83
|
+
reason=code_gen_error
|
|
84
|
+
or "tech stack matched but code generation failed; provide manual guide",
|
|
85
|
+
tech_stack=tech_stack,
|
|
86
|
+
adapter=adapter,
|
|
87
|
+
artifacts=templates,
|
|
88
|
+
remediation=(
|
|
89
|
+
"Agent outputs INTEGRATION_GUIDE.md: includes template code + injection position notes; "
|
|
90
|
+
"user completes integration manually following the docs"
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
# L3:未识别或无适配器
|
|
94
|
+
manual = fb.get("manual_api") or {}
|
|
95
|
+
artifacts = []
|
|
96
|
+
if manual.get("rest_endpoint"):
|
|
97
|
+
artifacts.append(f"rest_endpoint:{manual['rest_endpoint']}")
|
|
98
|
+
for sdk in manual.get("sdk_packages") or []:
|
|
99
|
+
for ecos, pkg in sdk.items():
|
|
100
|
+
artifacts.append(f"sdk:{ecos}:{pkg}")
|
|
101
|
+
return DegradeDecision(
|
|
102
|
+
level=DegradeLevel.L3_MANUAL,
|
|
103
|
+
reason=(
|
|
104
|
+
"tech stack not recognised or not in supported list"
|
|
105
|
+
if not tech_stack
|
|
106
|
+
else f"no adapter for tech_stack={tech_stack}"
|
|
107
|
+
),
|
|
108
|
+
tech_stack=tech_stack,
|
|
109
|
+
adapter=None,
|
|
110
|
+
artifacts=artifacts,
|
|
111
|
+
remediation=(
|
|
112
|
+
"Agent outputs generic REST API integration docs (base URL /api/v1)"
|
|
113
|
+
" + SDK package install commands; integrator connects manually."
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# I/O modality fallback matrix: 4 independent channel toggles → 16 combinations, ensuring a viable path for each
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
def channel_combinations_matrix() -> List[Dict]:
|
|
122
|
+
"""Return the fallback paths for all 16 channel combinations (used for docs & testing)."""
|
|
123
|
+
rows: List[Dict] = []
|
|
124
|
+
channels = ["voice_input", "text_input", "voice_output", "text_output"]
|
|
125
|
+
for mask in range(16):
|
|
126
|
+
state = {c: bool(mask & (1 << i)) for i, c in enumerate(channels)}
|
|
127
|
+
usable_in = state["voice_input"] or state["text_input"]
|
|
128
|
+
usable_out = state["voice_output"] or state["text_output"]
|
|
129
|
+
if not usable_in and not usable_out:
|
|
130
|
+
verdict = "silent_wait" # upper layer enters silent wait
|
|
131
|
+
elif not usable_in:
|
|
132
|
+
verdict = "output_only_broadcast"
|
|
133
|
+
elif not usable_out:
|
|
134
|
+
verdict = "input_only_logging"
|
|
135
|
+
else:
|
|
136
|
+
primary_in = "voice_input" if state["voice_input"] else "text_input"
|
|
137
|
+
primary_out = "voice_output" if state["voice_output"] else "text_output"
|
|
138
|
+
verdict = f"primary={primary_in}->{primary_out}"
|
|
139
|
+
rows.append({"state": state, "verdict": verdict})
|
|
140
|
+
return rows
|