@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,152 @@
|
|
|
1
|
+
"""α/β dual-track tool-call arbitration.
|
|
2
|
+
|
|
3
|
+
| Track | Implementation | Triggered When |
|
|
4
|
+
|:-----:|:---------------|:-----------------------------------------|
|
|
5
|
+
| α | Local function | Default priority (low latency, zero network overhead) |
|
|
6
|
+
| β | Remote API | α unavailable (not registered / failed / timed out) |
|
|
7
|
+
|
|
8
|
+
Arbitration rules (manifest.tool_calling node):
|
|
9
|
+
priority: alpha | beta | manifest_order
|
|
10
|
+
timeout_ms_alpha / timeout_ms_beta
|
|
11
|
+
retry_alpha / retry_beta
|
|
12
|
+
|
|
13
|
+
Risk P1 mitigation: when both tracks exist, decide by the priority field; auto-fallback when α is unavailable.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class AlphaTool:
|
|
24
|
+
name: str
|
|
25
|
+
func: Callable[..., Any]
|
|
26
|
+
timeout_ms: int = 1000
|
|
27
|
+
description: str = ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class BetaTool:
|
|
32
|
+
name: str
|
|
33
|
+
endpoint: str
|
|
34
|
+
method: str = "POST"
|
|
35
|
+
timeout_ms: int = 5000
|
|
36
|
+
headers: Dict[str, str] = field(default_factory=dict)
|
|
37
|
+
description: str = ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ToolCallResult:
|
|
42
|
+
tool: str
|
|
43
|
+
track: str # alpha | beta
|
|
44
|
+
ok: bool
|
|
45
|
+
output: Any = None
|
|
46
|
+
error: str = ""
|
|
47
|
+
latency_ms: int = 0
|
|
48
|
+
fallback_chain: List[str] = field(default_factory=list)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ToolRegistry:
|
|
52
|
+
"""同名工具可同时注册 α 与 β,由 ``call`` 仲裁。"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, *, default_priority: str = "alpha") -> None:
|
|
55
|
+
if default_priority not in ("alpha", "beta", "manifest_order"):
|
|
56
|
+
raise ValueError(f"invalid priority: {default_priority}")
|
|
57
|
+
self._alpha: Dict[str, AlphaTool] = {}
|
|
58
|
+
self._beta: Dict[str, BetaTool] = {}
|
|
59
|
+
self._priority = default_priority
|
|
60
|
+
|
|
61
|
+
# 注册 -----------------------------------------------------------------
|
|
62
|
+
def register_alpha(self, tool: AlphaTool) -> None:
|
|
63
|
+
self._alpha[tool.name] = tool
|
|
64
|
+
|
|
65
|
+
def register_beta(self, tool: BetaTool) -> None:
|
|
66
|
+
self._beta[tool.name] = tool
|
|
67
|
+
|
|
68
|
+
def list_tools(self) -> List[Dict[str, Any]]:
|
|
69
|
+
names = sorted(set(self._alpha) | set(self._beta))
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
"name": n,
|
|
73
|
+
"alpha": self._alpha[n].description if n in self._alpha else None,
|
|
74
|
+
"beta": self._beta[n].description if n in self._beta else None,
|
|
75
|
+
"priority": self._priority,
|
|
76
|
+
}
|
|
77
|
+
for n in names
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# 调用 -----------------------------------------------------------------
|
|
81
|
+
def call(
|
|
82
|
+
self,
|
|
83
|
+
name: str,
|
|
84
|
+
params: Optional[Dict[str, Any]] = None,
|
|
85
|
+
*,
|
|
86
|
+
priority: Optional[str] = None,
|
|
87
|
+
beta_invoker: Optional[Callable[[BetaTool, Dict[str, Any]], Any]] = None,
|
|
88
|
+
) -> ToolCallResult:
|
|
89
|
+
"""执行工具调用,按 priority 决策并自动降级。
|
|
90
|
+
|
|
91
|
+
beta_invoker 由调用方注入(避免本模块依赖 requests),
|
|
92
|
+
默认只在 α 可用时跑 α,β 路径必须由调用方实现网络调用。
|
|
93
|
+
"""
|
|
94
|
+
params = params or {}
|
|
95
|
+
priority = priority or self._priority
|
|
96
|
+
chain: List[str] = []
|
|
97
|
+
order = self._resolve_order(name, priority)
|
|
98
|
+
last_error = ""
|
|
99
|
+
for track in order:
|
|
100
|
+
chain.append(track)
|
|
101
|
+
if track == "alpha":
|
|
102
|
+
tool = self._alpha.get(name)
|
|
103
|
+
if not tool:
|
|
104
|
+
last_error = f"alpha not registered: {name}"
|
|
105
|
+
continue
|
|
106
|
+
start = time.perf_counter()
|
|
107
|
+
try:
|
|
108
|
+
out = tool.func(**params)
|
|
109
|
+
return ToolCallResult(
|
|
110
|
+
tool=name, track="alpha", ok=True, output=out,
|
|
111
|
+
latency_ms=int((time.perf_counter() - start) * 1000),
|
|
112
|
+
fallback_chain=chain,
|
|
113
|
+
)
|
|
114
|
+
except Exception as exc: # noqa: BLE001
|
|
115
|
+
last_error = f"alpha:{type(exc).__name__}:{exc}"
|
|
116
|
+
else: # beta
|
|
117
|
+
tool = self._beta.get(name)
|
|
118
|
+
if not tool:
|
|
119
|
+
last_error = f"beta not registered: {name}"
|
|
120
|
+
continue
|
|
121
|
+
if beta_invoker is None:
|
|
122
|
+
last_error = "beta_invoker not provided"
|
|
123
|
+
continue
|
|
124
|
+
start = time.perf_counter()
|
|
125
|
+
try:
|
|
126
|
+
out = beta_invoker(tool, params)
|
|
127
|
+
return ToolCallResult(
|
|
128
|
+
tool=name, track="beta", ok=True, output=out,
|
|
129
|
+
latency_ms=int((time.perf_counter() - start) * 1000),
|
|
130
|
+
fallback_chain=chain,
|
|
131
|
+
)
|
|
132
|
+
except Exception as exc: # noqa: BLE001
|
|
133
|
+
last_error = f"beta:{type(exc).__name__}:{exc}"
|
|
134
|
+
return ToolCallResult(
|
|
135
|
+
tool=name, track=chain[-1] if chain else "none",
|
|
136
|
+
ok=False, error=last_error or "no track available",
|
|
137
|
+
fallback_chain=chain,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# 仲裁 -----------------------------------------------------------------
|
|
141
|
+
def _resolve_order(self, name: str, priority: str) -> List[str]:
|
|
142
|
+
has_a = name in self._alpha
|
|
143
|
+
has_b = name in self._beta
|
|
144
|
+
if priority == "alpha":
|
|
145
|
+
base = ["alpha", "beta"]
|
|
146
|
+
elif priority == "beta":
|
|
147
|
+
base = ["beta", "alpha"]
|
|
148
|
+
else: # manifest_order:以注册顺序为准(首次注册者优先)
|
|
149
|
+
base = ["alpha", "beta"] if has_a and not has_b else (
|
|
150
|
+
["beta", "alpha"] if has_b and not has_a else ["alpha", "beta"]
|
|
151
|
+
)
|
|
152
|
+
return [t for t in base if (t == "alpha" and has_a) or (t == "beta" and has_b)]
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
"""business_contract field parsing & diff utilities (Phase 3 Stage 4).
|
|
2
|
+
|
|
3
|
+
Responsibilities:
|
|
4
|
+
1. Load a capability's ``manifest.yaml.business_contract`` block and perform static
|
|
5
|
+
validation (BC001~BC006) per ``business-contract-spec.md`` §7 rules.
|
|
6
|
+
2. Convert a manifest-declared ``external_apis[name]`` entry into ``ParsedApi`` form (default contract).
|
|
7
|
+
3. ``diff_contracts(default, user)`` returns structured differences and infers
|
|
8
|
+
fallback level L1 / L2 / L3.
|
|
9
|
+
|
|
10
|
+
Fallback Level Determination:
|
|
11
|
+
|
|
12
|
+
| Level | Trigger Condition |
|
|
13
|
+
|---|---|
|
|
14
|
+
| L1 | Only field differences within the ``adapter_slots`` list (name remapping / enum value mapping) |
|
|
15
|
+
| L2 | Parsed successfully but structural differences exist (nested level differences, type differences, missing required fields) |
|
|
16
|
+
| L3 | Protocol-level differences (significant method mismatch, non-REST/JSON user API, body_format=raw, etc.) |
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
25
|
+
|
|
26
|
+
import yaml
|
|
27
|
+
|
|
28
|
+
from .curl_parser import AuthSpec, ParsedApi, _normalize_schema
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Error codes / exceptions
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
class ContractError(RuntimeError):
|
|
35
|
+
"""business_contract field error."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, code: str, message: str) -> None:
|
|
38
|
+
super().__init__(f"[{code}] {message}")
|
|
39
|
+
self.code = code
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DegradeLevel(str, Enum):
|
|
43
|
+
L1 = "L1"
|
|
44
|
+
L2 = "L2"
|
|
45
|
+
L3 = "L3"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Data models
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
@dataclass
|
|
52
|
+
class ExternalApi:
|
|
53
|
+
name: str
|
|
54
|
+
direction: str
|
|
55
|
+
method: str
|
|
56
|
+
path: str
|
|
57
|
+
description: str = ""
|
|
58
|
+
request_schema: Dict[str, Any] = field(default_factory=dict)
|
|
59
|
+
response_schema: Dict[str, Any] = field(default_factory=dict)
|
|
60
|
+
adapter_slots: List[str] = field(default_factory=list)
|
|
61
|
+
auth: AuthSpec = field(default_factory=AuthSpec)
|
|
62
|
+
timeout_ms: int = 5000
|
|
63
|
+
|
|
64
|
+
def to_parsed_api(self, base_url: str = "") -> ParsedApi:
|
|
65
|
+
# manifest.yaml field values are already normalized literals ("string" / "int" / "enum[...]" / "string[]"),
|
|
66
|
+
# so do **not** call _normalize_schema (avoids degrading "string[]" to "string")
|
|
67
|
+
return ParsedApi(
|
|
68
|
+
method=self.method.upper(),
|
|
69
|
+
base_url=base_url,
|
|
70
|
+
path=self.path,
|
|
71
|
+
headers={},
|
|
72
|
+
auth=self.auth,
|
|
73
|
+
request_schema=dict(self.request_schema),
|
|
74
|
+
response_schema=dict(self.response_schema),
|
|
75
|
+
body_format="json" if self.method.upper() in {"POST", "PUT", "PATCH"} else "none",
|
|
76
|
+
source="manifest",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class BusinessContract:
|
|
82
|
+
capability: str
|
|
83
|
+
port_class: str = ""
|
|
84
|
+
default_adapter: str = ""
|
|
85
|
+
mock_adapter: str = ""
|
|
86
|
+
customization_sop: str = "INTERFACE_ADAPT.md"
|
|
87
|
+
external_apis: List[ExternalApi] = field(default_factory=list)
|
|
88
|
+
raw: Dict[str, Any] = field(default_factory=dict)
|
|
89
|
+
|
|
90
|
+
def get_api(self, name: str) -> Optional[ExternalApi]:
|
|
91
|
+
for a in self.external_apis:
|
|
92
|
+
if a.name == name:
|
|
93
|
+
return a
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def outbound_apis(self) -> List[ExternalApi]:
|
|
97
|
+
return [a for a in self.external_apis if a.direction == "outbound"]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class FieldDiff:
|
|
102
|
+
"""Single field diff."""
|
|
103
|
+
|
|
104
|
+
path: str # request.subject / response.ticket_id
|
|
105
|
+
kind: str # rename | type_mismatch | missing_in_user | extra_in_user | enum_mismatch
|
|
106
|
+
default: Any = None # default contract field name / type / enum set
|
|
107
|
+
user: Any = None # user's actual value
|
|
108
|
+
in_slot: bool = False # whether within adapter_slots (determines L1 vs L2)
|
|
109
|
+
|
|
110
|
+
def to_dict(self) -> Dict:
|
|
111
|
+
return {
|
|
112
|
+
"path": self.path,
|
|
113
|
+
"kind": self.kind,
|
|
114
|
+
"default": self.default,
|
|
115
|
+
"user": self.user,
|
|
116
|
+
"in_slot": self.in_slot,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass
|
|
121
|
+
class ContractDiff:
|
|
122
|
+
api_name: str
|
|
123
|
+
method_match: bool
|
|
124
|
+
path_match: bool
|
|
125
|
+
direction: str
|
|
126
|
+
fields: List[FieldDiff] = field(default_factory=list)
|
|
127
|
+
protocol_mismatch: bool = False
|
|
128
|
+
protocol_reason: str = ""
|
|
129
|
+
suggested_mapping: Dict[str, Any] = field(default_factory=dict)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def level(self) -> DegradeLevel:
|
|
133
|
+
return decide_level(self)
|
|
134
|
+
|
|
135
|
+
def to_dict(self) -> Dict:
|
|
136
|
+
return {
|
|
137
|
+
"api_name": self.api_name,
|
|
138
|
+
"method_match": self.method_match,
|
|
139
|
+
"path_match": self.path_match,
|
|
140
|
+
"direction": self.direction,
|
|
141
|
+
"fields": [f.to_dict() for f in self.fields],
|
|
142
|
+
"protocol_mismatch": self.protocol_mismatch,
|
|
143
|
+
"protocol_reason": self.protocol_reason,
|
|
144
|
+
"suggested_mapping": self.suggested_mapping,
|
|
145
|
+
"level": self.level.value,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
# Manifest loading & validation
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
def _import_dotted(path: str) -> bool:
|
|
153
|
+
"""Attempt to import a dotted path; only validates syntax / module file existence, no instantiation."""
|
|
154
|
+
if not path:
|
|
155
|
+
return False
|
|
156
|
+
# Only validate form: a.b.c.ClassName
|
|
157
|
+
if not re.match(r"^[A-Za-z_][\w\.]*$", path):
|
|
158
|
+
return False
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def load_business_contract(capability_dir: Path) -> BusinessContract:
|
|
163
|
+
"""Load a capability manifest.yaml and parse its business_contract block.
|
|
164
|
+
|
|
165
|
+
Raises
|
|
166
|
+
------
|
|
167
|
+
ContractError
|
|
168
|
+
manifest missing / business_contract missing / invalid fields.
|
|
169
|
+
"""
|
|
170
|
+
manifest_path = Path(capability_dir) / "manifest.yaml"
|
|
171
|
+
if not manifest_path.exists():
|
|
172
|
+
raise ContractError("BC000", f"manifest not found: {manifest_path}")
|
|
173
|
+
raw = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) or {}
|
|
174
|
+
cap_name = str(raw.get("name", "") or capability_dir.name)
|
|
175
|
+
bc_raw = raw.get("business_contract")
|
|
176
|
+
if not isinstance(bc_raw, dict):
|
|
177
|
+
raise ContractError("BC000", f"{cap_name}: missing business_contract block")
|
|
178
|
+
|
|
179
|
+
# tool-calling uses §5 dedicated contract block; not yet supported by this tool
|
|
180
|
+
if "alpha_track" in bc_raw or "beta_track" in bc_raw:
|
|
181
|
+
raise ContractError(
|
|
182
|
+
"BC000",
|
|
183
|
+
f"{cap_name}: tool-calling-style contract not supported by contract-adapt yet",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
port_class = str(bc_raw.get("port_class", ""))
|
|
187
|
+
default_adapter = str(bc_raw.get("default_adapter", ""))
|
|
188
|
+
mock_adapter = str(bc_raw.get("mock_adapter", ""))
|
|
189
|
+
sop = str(bc_raw.get("customization_sop", "INTERFACE_ADAPT.md"))
|
|
190
|
+
|
|
191
|
+
# BC001: all three dotted paths must be valid
|
|
192
|
+
for label, p in (("port_class", port_class), ("default_adapter", default_adapter), ("mock_adapter", mock_adapter)):
|
|
193
|
+
if not _import_dotted(p):
|
|
194
|
+
raise ContractError("BC001", f"{cap_name}: invalid {label}={p!r}")
|
|
195
|
+
|
|
196
|
+
apis: List[ExternalApi] = []
|
|
197
|
+
seen_names: set = set()
|
|
198
|
+
for entry in bc_raw.get("external_apis") or []:
|
|
199
|
+
if not isinstance(entry, dict):
|
|
200
|
+
continue
|
|
201
|
+
name = str(entry.get("name", "")).strip()
|
|
202
|
+
if not name:
|
|
203
|
+
raise ContractError("BC003", f"{cap_name}: external_api missing 'name'")
|
|
204
|
+
# BC002: duplicate name
|
|
205
|
+
if name in seen_names:
|
|
206
|
+
raise ContractError("BC002", f"{cap_name}: duplicate external_api name {name!r}")
|
|
207
|
+
seen_names.add(name)
|
|
208
|
+
direction = str(entry.get("direction", "outbound"))
|
|
209
|
+
method = str(entry.get("method", "")).upper()
|
|
210
|
+
path = str(entry.get("path", ""))
|
|
211
|
+
if direction == "outbound" and (not method or not path):
|
|
212
|
+
raise ContractError("BC003", f"{cap_name}.{name}: outbound requires method+path")
|
|
213
|
+
|
|
214
|
+
auth_raw = entry.get("auth") or {}
|
|
215
|
+
auth = AuthSpec(
|
|
216
|
+
type=str(auth_raw.get("type", "none")),
|
|
217
|
+
location=str(auth_raw.get("location", "header")),
|
|
218
|
+
name=str(auth_raw.get("name", "")),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
api = ExternalApi(
|
|
222
|
+
name=name,
|
|
223
|
+
direction=direction,
|
|
224
|
+
method=method,
|
|
225
|
+
path=path,
|
|
226
|
+
description=str(entry.get("description", "")),
|
|
227
|
+
request_schema=dict(entry.get("request_schema") or {}),
|
|
228
|
+
response_schema=dict(entry.get("response_schema") or {}),
|
|
229
|
+
adapter_slots=list(entry.get("adapter_slots") or []),
|
|
230
|
+
auth=auth,
|
|
231
|
+
timeout_ms=int(entry.get("timeout_ms", 5000)),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# BC004: adapter_slots should be resolvable in schema (warning only, no exception)
|
|
235
|
+
# Simplified: only checks top-level fields
|
|
236
|
+
for slot in api.adapter_slots:
|
|
237
|
+
if not (slot.startswith("request.") or slot.startswith("response.")):
|
|
238
|
+
# 视为 warning,无副作用
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
apis.append(api)
|
|
242
|
+
|
|
243
|
+
return BusinessContract(
|
|
244
|
+
capability=cap_name,
|
|
245
|
+
port_class=port_class,
|
|
246
|
+
default_adapter=default_adapter,
|
|
247
|
+
mock_adapter=mock_adapter,
|
|
248
|
+
customization_sop=sop,
|
|
249
|
+
external_apis=apis,
|
|
250
|
+
raw=bc_raw,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
# Default contract vs user contract diff
|
|
256
|
+
# ---------------------------------------------------------------------------
|
|
257
|
+
def _walk_schema(schema: Any, prefix: str = "") -> Dict[str, Any]:
|
|
258
|
+
"""Flatten arbitrary nested type descriptions to ``{ 'a.b.c': type_string }``."""
|
|
259
|
+
out: Dict[str, Any] = {}
|
|
260
|
+
if isinstance(schema, dict):
|
|
261
|
+
for k, v in schema.items():
|
|
262
|
+
sub = f"{prefix}.{k}" if prefix else k
|
|
263
|
+
if isinstance(v, (dict, list)):
|
|
264
|
+
out.update(_walk_schema(v, sub))
|
|
265
|
+
else:
|
|
266
|
+
out[sub] = v
|
|
267
|
+
el if isinstance(schema, list):
|
|
268
|
+
# Array: recurse into first element
|
|
269
|
+
if schema:
|
|
270
|
+
out.update(_walk_schema(schema[0], f"{prefix}[]"))
|
|
271
|
+
else:
|
|
272
|
+
out[prefix or "$"] = schema
|
|
273
|
+
return out
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _slot_match(field_path: str, slots: List[str]) -> bool:
|
|
277
|
+
"""``request.priority`` ∈ slots (exact match, no wildcards)."""
|
|
278
|
+
return field_path in set(slots)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _detect_protocol_mismatch(default: ExternalApi, user: ParsedApi) -> Tuple[bool, str]:
|
|
282
|
+
if default.method.upper() != user.method.upper():
|
|
283
|
+
return (True, f"method mismatch: default={default.method}, user={user.method}")
|
|
284
|
+
if user.body_format == "raw":
|
|
285
|
+
return (True, "user body is non-JSON raw payload")
|
|
286
|
+
# Path mismatch is not a protocol-level diff (adapter can override path), but record it
|
|
287
|
+
return (False, "")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def diff_contracts(default: ExternalApi, user: ParsedApi) -> ContractDiff:
|
|
291
|
+
"""Compare default contract with user API; produce structured differences.
|
|
292
|
+
|
|
293
|
+
Notes
|
|
294
|
+
-----
|
|
295
|
+
- Request field comparison: uses default schema as baseline, checks whether user schema
|
|
296
|
+
provides same-named fields; if not same-named but user has extra fields, record as
|
|
297
|
+
``rename`` candidate (paired only when default field is missing).
|
|
298
|
+
- Response fields same logic; if user did not provide response (response_schema is empty),
|
|
299
|
+
only generate ``missing_in_user`` but do not count as protocol mismatch.
|
|
300
|
+
- Type differences (``string`` vs ``int``) recorded as ``type_mismatch``.
|
|
301
|
+
- Enum differences not visible in curl input, only OpenAPI input; recorded as ``enum_mismatch``.
|
|
302
|
+
"""
|
|
303
|
+
diff = ContractDiff(
|
|
304
|
+
api_name=default.name,
|
|
305
|
+
method_match=default.method.upper() == user.method.upper(),
|
|
306
|
+
path_match=default.path == user.path,
|
|
307
|
+
direction=default.direction,
|
|
308
|
+
)
|
|
309
|
+
pmm, reason = _detect_protocol_mismatch(default, user)
|
|
310
|
+
diff.protocol_mismatch = pmm
|
|
311
|
+
diff.protocol_reason = reason
|
|
312
|
+
|
|
313
|
+
default_req = _walk_schema(default.request_schema, "request")
|
|
314
|
+
user_req = _walk_schema(user.request_schema, "request")
|
|
315
|
+
default_resp = _walk_schema(default.response_schema, "response")
|
|
316
|
+
user_resp = _walk_schema(user.response_schema, "response") if user.response_schema else {}
|
|
317
|
+
|
|
318
|
+
suggested: Dict[str, Any] = {"request": {}, "response": {}, "enum_map": {}}
|
|
319
|
+
|
|
320
|
+
# Request comparison
|
|
321
|
+
diff.fields.extend(
|
|
322
|
+
_diff_section(default_req, user_req, default.adapter_slots, "request", suggested["request"])
|
|
323
|
+
)
|
|
324
|
+
# Response comparison (if user provided one)
|
|
325
|
+
if user_resp:
|
|
326
|
+
diff.fields.extend(
|
|
327
|
+
_diff_section(default_resp, user_resp, default.adapter_slots, "response", suggested["response"])
|
|
328
|
+
)
|
|
329
|
+
else:
|
|
330
|
+
for k, t in default_resp.items():
|
|
331
|
+
diff.fields.append(
|
|
332
|
+
FieldDiff(
|
|
333
|
+
path=k, kind="user_response_missing", default=t, user=None, in_slot=_slot_match(k, default.adapter_slots)
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
diff.suggested_mapping = {k: v for k, v in suggested.items() if v}
|
|
338
|
+
return diff
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _diff_section(
|
|
342
|
+
default_flat: Dict[str, Any],
|
|
343
|
+
user_flat: Dict[str, Any],
|
|
344
|
+
slots: List[str],
|
|
345
|
+
section: str,
|
|
346
|
+
mapping_out: Dict[str, str],
|
|
347
|
+
) -> List[FieldDiff]:
|
|
348
|
+
out: List[FieldDiff] = []
|
|
349
|
+
default_keys = list(default_flat.keys())
|
|
350
|
+
user_keys = list(user_flat.keys())
|
|
351
|
+
|
|
352
|
+
# 1) 同名字段:核对类型
|
|
353
|
+
common = [k for k in default_keys if k in user_flat]
|
|
354
|
+
for k in common:
|
|
355
|
+
if not _types_compatible(default_flat[k], user_flat[k]):
|
|
356
|
+
out.append(
|
|
357
|
+
FieldDiff(
|
|
358
|
+
path=k,
|
|
359
|
+
kind="type_mismatch",
|
|
360
|
+
default=default_flat[k],
|
|
361
|
+
user=user_flat[k],
|
|
362
|
+
in_slot=_slot_match(k, slots),
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
# 同名同类型 → 不进 diff
|
|
366
|
+
mapping_out[k.split(".", 1)[1]] = k.split(".", 1)[1] # request.x → x:x
|
|
367
|
+
|
|
368
|
+
# 2) 默认有 / 用户没同名:尝试在用户 keys 里找同类型字段做 rename 候选
|
|
369
|
+
missing_default = [k for k in default_keys if k not in user_flat]
|
|
370
|
+
extra_user = [k for k in user_keys if k not in default_flat]
|
|
371
|
+
used_extra: set = set()
|
|
372
|
+
for dk in missing_default:
|
|
373
|
+
candidate = _find_rename_candidate(dk, default_flat[dk], extra_user, user_flat, used_extra)
|
|
374
|
+
if candidate:
|
|
375
|
+
used_extra.add(candidate)
|
|
376
|
+
out.append(
|
|
377
|
+
FieldDiff(
|
|
378
|
+
path=dk,
|
|
379
|
+
kind="rename",
|
|
380
|
+
default=dk.split(".", 1)[1],
|
|
381
|
+
user=candidate.split(".", 1)[1],
|
|
382
|
+
in_slot=_slot_match(dk, slots),
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
mapping_out[dk.split(".", 1)[1]] = candidate.split(".", 1)[1]
|
|
386
|
+
else:
|
|
387
|
+
out.append(
|
|
388
|
+
FieldDiff(
|
|
389
|
+
path=dk,
|
|
390
|
+
kind="missing_in_user",
|
|
391
|
+
default=default_flat[dk],
|
|
392
|
+
user=None,
|
|
393
|
+
in_slot=_slot_match(dk, slots),
|
|
394
|
+
)
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# 3) 用户多出的字段(未被认领为 rename 目标)
|
|
398
|
+
for uk in extra_user:
|
|
399
|
+
if uk in used_extra:
|
|
400
|
+
continue
|
|
401
|
+
out.append(
|
|
402
|
+
FieldDiff(
|
|
403
|
+
path=uk,
|
|
404
|
+
kind="extra_in_user",
|
|
405
|
+
default=None,
|
|
406
|
+
user=user_flat[uk],
|
|
407
|
+
in_slot=False,
|
|
408
|
+
)
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
return out
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# ---------------------------------------------------------------------------
|
|
415
|
+
# Type compatibility / rename candidates
|
|
416
|
+
# ---------------------------------------------------------------------------
|
|
417
|
+
def _types_compatible(a: Any, b: Any) -> bool:
|
|
418
|
+
if a == b:
|
|
419
|
+
return True
|
|
420
|
+
a_s = str(a)
|
|
421
|
+
b_s = str(b)
|
|
422
|
+
# enum[...] and string are treated as compatible (adapter layer handles enum mapping)
|
|
423
|
+
if a_s.startswith("enum[") and b_s == "string":
|
|
424
|
+
return True
|
|
425
|
+
if b_s.startswith("enum[") and a_s == "string":
|
|
426
|
+
return True
|
|
427
|
+
# int / float mutually compatible
|
|
428
|
+
if {a_s, b_s} <= {"int", "float", "number"}:
|
|
429
|
+
return True
|
|
430
|
+
# array type mismatch → incompatible
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _find_rename_candidate(
|
|
435
|
+
default_path: str,
|
|
436
|
+
default_type: Any,
|
|
437
|
+
extra_user_keys: List[str],
|
|
438
|
+
user_flat: Dict[str, Any],
|
|
439
|
+
used: set,
|
|
440
|
+
) -> Optional[str]:
|
|
441
|
+
"""Try to find a type-compatible + semantically similar path-segment candidate among extra user keys."""
|
|
442
|
+
default_seg = default_path.split(".")[-1]
|
|
443
|
+
candidates = [k for k in extra_user_keys if k not in used and _types_compatible(default_type, user_flat[k])]
|
|
444
|
+
if not candidates:
|
|
445
|
+
return None
|
|
446
|
+
# Simple semantic similarity: contains / contained-by / same-category synonyms
|
|
447
|
+
synonyms = {
|
|
448
|
+
"user_id": ["customer_id", "uid", "user", "client_id"],
|
|
449
|
+
"subject": ["title", "summary", "topic"],
|
|
450
|
+
"description": ["body", "content", "message", "detail"],
|
|
451
|
+
"priority": ["level", "severity", "rank"],
|
|
452
|
+
"transcript": ["messages", "history", "log", "conversation"],
|
|
453
|
+
"ticket_id": ["id", "order_id", "case_id", "wo_id"],
|
|
454
|
+
"queue_position": ["rank", "position", "queue", "order"],
|
|
455
|
+
"eta_seconds": ["wait_estimate", "wait_seconds", "eta"],
|
|
456
|
+
"status": ["state", "stage", "phase"],
|
|
457
|
+
"agent_id": ["operator", "assignee", "owner"],
|
|
458
|
+
"query": ["q", "keyword", "search", "term"],
|
|
459
|
+
"top_k": ["k", "limit", "size"],
|
|
460
|
+
"min_score": ["threshold", "score_min"],
|
|
461
|
+
"answer": ["reply", "content"],
|
|
462
|
+
"question": ["query", "title"],
|
|
463
|
+
"keywords": ["tags", "labels"],
|
|
464
|
+
}
|
|
465
|
+
syn_for_default = set(synonyms.get(default_seg, []))
|
|
466
|
+
for cand in candidates:
|
|
467
|
+
cand_seg = cand.split(".")[-1]
|
|
468
|
+
if cand_seg == default_seg:
|
|
469
|
+
return cand
|
|
470
|
+
if cand_seg in syn_for_default or default_seg in synonyms.get(cand_seg, []):
|
|
471
|
+
return cand
|
|
472
|
+
# Fallback to first candidate when types match (best effort)
|
|
473
|
+
return candidates[0]
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# ---------------------------------------------------------------------------
|
|
477
|
+
# Fallback level decision
|
|
478
|
+
# ---------------------------------------------------------------------------
|
|
479
|
+
def decide_level(diff: ContractDiff) -> DegradeLevel:
|
|
480
|
+
"""L1 / L2 / L3 determination."""
|
|
481
|
+
if diff.protocol_mismatch:
|
|
482
|
+
return DegradeLevel.L3
|
|
483
|
+
# Any non-slot diff → L2
|
|
484
|
+
for f in diff.fields:
|
|
485
|
+
if f.kind in ("user_response_missing",):
|
|
486
|
+
# Missing user response is not an L2/L3 obstacle (codegen uses default contract field names)
|
|
487
|
+
continue
|
|
488
|
+
if f.kind == "extra_in_user":
|
|
489
|
+
continue
|
|
490
|
+
if not f.in_slot:
|
|
491
|
+
return DegradeLevel.L2
|
|
492
|
+
if f.kind in ("type_mismatch", "missing_in_user"):
|
|
493
|
+
return DegradeLevel.L2
|
|
494
|
+
return DegradeLevel.L1
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
# ---------------------------------------------------------------------------
|
|
498
|
+
# Static validation entry point (per spec §7)
|
|
499
|
+
# ---------------------------------------------------------------------------
|
|
500
|
+
def validate_manifest(capability_dir: Path) -> List[str]:
|
|
501
|
+
"""Perform full static validation on a capability's manifest.business_contract.
|
|
502
|
+
|
|
503
|
+
Returns a list of warning strings; fatal errors (BC001~BC003/BC005) raise ContractError directly.
|
|
504
|
+
"""
|
|
505
|
+
bc = load_business_contract(capability_dir)
|
|
506
|
+
warnings: List[str] = []
|
|
507
|
+
for api in bc.external_apis:
|
|
508
|
+
flat_req = _walk_schema(api.request_schema, "request")
|
|
509
|
+
flat_resp = _walk_schema(api.response_schema, "response")
|
|
510
|
+
valid_paths = set(flat_req) | set(flat_resp)
|
|
511
|
+
for slot in api.adapter_slots:
|
|
512
|
+
base = slot.split("[]", 1)[0]
|
|
513
|
+
if base not in valid_paths and not any(p.startswith(base + ".") for p in valid_paths):
|
|
514
|
+
warnings.append(
|
|
515
|
+
f"BC004 [{bc.capability}.{api.name}] adapter_slot {slot!r} not found in schemas"
|
|
516
|
+
)
|
|
517
|
+
if api.auth.type == "bearer" and not api.auth.name:
|
|
518
|
+
warnings.append(f"BC006 [{bc.capability}.{api.name}] bearer auth missing header name")
|
|
519
|
+
return warnings
|