@oh-story/dsh 0.1.8 → 0.1.9
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/README.md +11 -11
- package/lib/client.js +35 -14
- package/lib/client.js.map +2 -2
- package/lib/drama/manifest.json +165 -69
- package/lib/drama/skills/short-drama/SKILL.md +5 -1
- package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +126 -9
- package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
- package/lib/drama/skills/short-drama-assets/references/character-and-look.md +18 -0
- package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +10 -1
- package/lib/drama/skills/short-drama-edit/SKILL.md +148 -0
- package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
- package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
- package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
- package/lib/drama/skills/short-drama-edit/references/cut-craft.md +81 -0
- package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +58 -0
- package/lib/drama/skills/short-drama-edit/references/generated-footage.md +96 -0
- package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +113 -0
- package/lib/drama/skills/short-drama-edit/references/stage-contract.md +55 -0
- package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1041 -0
- package/lib/drama/skills/short-drama-edit/scripts/selftest.py +456 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
- package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
- package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
- package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
- package/lib/drama/skills/short-drama-produce/SKILL.md +24 -3
- package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +28 -0
- package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +24 -5
- package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
- package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
- package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
- package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +57 -2
- package/lib/drama/skills/short-drama-review/SKILL.md +3 -0
- package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +20 -0
- package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +36 -0
- package/lib/drama/skills/short-drama-storyboard/SKILL.md +7 -0
- package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
- package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +39 -0
- package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
- package/lib/drama/skills/short-drama-video-prompts/SKILL.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +101 -6
- package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +7 -1
- package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +35 -0
- package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +3 -1
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +13 -3
- package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +35 -4
- package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -1
- package/lib/drama/skills/short-drama-write/SKILL.md +13 -2
- package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
- package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
- package/lib/index.js +4 -2
- package/lib/index.js.map +2 -2
- package/lib/novel-to-game/VERSION +1 -1
- package/lib/novel-to-game/manifest.json +28 -28
- package/lib/novel-to-game/skills/game-art-direction/SKILL.md +6 -27
- package/lib/novel-to-game/skills/game-build/SKILL.md +12 -29
- package/lib/novel-to-game/skills/game-concept/SKILL.md +10 -26
- package/lib/novel-to-game/skills/game-qa/SKILL.md +5 -15
- package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +1 -1
- package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -28
- package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +1 -2
- package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +6 -9
- package/lib/novel-to-game/skills/novel-to-game/SKILL.md +6 -11
- package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -4
- package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +1 -3
- package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +3 -5
- package/lib/oh-story/manifest.json +292 -172
- package/lib/oh-story/roles/character-designer.md +1 -1
- package/lib/oh-story/roles/narrative-writer.md +56 -242
- package/lib/oh-story/roles/story-architect.md +1 -1
- package/lib/oh-story/skills/story/SKILL.md +1 -1
- package/lib/oh-story/skills/story/VERSION +1 -1
- package/lib/oh-story/skills/story/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/SKILL.md +20 -182
- package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-deslop/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-import/SKILL.md +2 -2
- package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-import/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-analyze/SKILL.md +1 -1
- package/lib/oh-story/skills/story-long-write/SKILL.md +20 -193
- package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +62 -7
- package/lib/oh-story/skills/story-long-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
- package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +2 -2
- package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
- package/lib/oh-story/skills/story-long-write/references/project-files.md +108 -0
- package/lib/oh-story/skills/story-long-write/references/reference-index.md +87 -0
- package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
- package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +51 -52
- package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +8 -7
- package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +36 -15
- package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
- package/lib/oh-story/skills/story-long-write/references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +410 -0
- package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +176 -12
- package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +283 -0
- package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-review/SKILL.md +13 -3
- package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-review/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-review/references/review-quality.md +2 -2
- package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
- package/lib/oh-story/skills/story-review/references/tracking-transaction.md +2 -29
- package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
- package/lib/oh-story/skills/story-setup/SKILL.md +17 -10
- package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +19 -47
- package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
- package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
- package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +69 -2
- package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
- package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/SKILL.md +18 -186
- package/lib/oh-story/skills/story-short-write/references/author-memory.md +10 -8
- package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
- package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +6 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
- package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
- package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
- package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
- package/lib/oh-story/skills/story-short-write/references/reference-index.md +53 -0
- package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
- package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
- package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
- package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
- package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
- package/lib/oh-story/skills/story-short-write/references/workflow-design.md +77 -0
- package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
- package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
- package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
- package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
- package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
- package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
- package/lib/types/client/drama-production-view.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/production-prompts.d.ts.map +1 -1
- package/lib/types/client/production-runtime.d.ts +17 -0
- package/lib/types/client/production-runtime.d.ts.map +1 -1
- package/lib/types/skill-provider.d.ts.map +1 -1
- package/lib/video-recap/CHANGELOG.md +31 -1
- package/lib/video-recap/manifest.json +162 -162
- package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
- package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
- package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
- package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
- package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
- package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
- package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
- package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
- package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
- package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
- package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
- package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
- package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
- package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
- package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
- package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
- package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
- package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
- package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
- package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
- package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
- package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
- package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
- package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
- package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
- package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
- package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
- package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
- package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
- package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
- package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
- package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
- package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
- package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
- package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
- package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
- package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
- package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
- package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
- package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
- package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
- package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
- package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
- package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
- package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
- package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
- package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
- package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
- package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
- package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
- package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
- package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
- package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
- package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
- package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
- package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
- package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
- package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
- package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
- package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
- package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
- package/package.json +41 -41
|
@@ -2,18 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
import hashlib
|
|
7
|
-
|
|
8
6
|
import json
|
|
9
|
-
|
|
10
|
-
|
|
11
7
|
import os
|
|
12
|
-
|
|
13
|
-
|
|
14
8
|
from pathlib import Path
|
|
15
|
-
|
|
16
|
-
from
|
|
9
|
+
from typing import Any
|
|
10
|
+
from collections.abc import Mapping, Sequence
|
|
17
11
|
|
|
18
12
|
import qc_contract
|
|
19
13
|
from mimo_qc_client import DEFAULT_CONFIG
|
|
@@ -62,13 +56,8 @@ def _fingerprint_value(value: Any) -> str:
|
|
|
62
56
|
return hashlib.sha256(_stable_json(value).encode("utf-8")).hexdigest()
|
|
63
57
|
|
|
64
58
|
|
|
65
|
-
def _redact(value: Any) -> Any:
|
|
66
|
-
return qc_contract.redact_secrets(value)
|
|
67
|
-
|
|
68
|
-
|
|
69
59
|
def _load_json(path: Path) -> Any:
|
|
70
|
-
|
|
71
|
-
return _redact(json.load(handle))
|
|
60
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
72
61
|
|
|
73
62
|
|
|
74
63
|
def _read_text_sample(path: Path, *, max_chars: int = 4000) -> dict[str, Any]:
|
|
@@ -77,7 +66,7 @@ def _read_text_sample(path: Path, *, max_chars: int = 4000) -> dict[str, Any]:
|
|
|
77
66
|
"kind": "text",
|
|
78
67
|
"bytes": path.stat().st_size,
|
|
79
68
|
"truncated": len(text) > max_chars,
|
|
80
|
-
"sample":
|
|
69
|
+
"sample": text[:max_chars],
|
|
81
70
|
}
|
|
82
71
|
|
|
83
72
|
|
|
@@ -85,7 +74,6 @@ def _summarize(
|
|
|
85
74
|
value: Any, *, max_items: int = 8, max_string: int = 700, depth: int = 0
|
|
86
75
|
) -> Any:
|
|
87
76
|
"""Keep request/report evidence bounded while retaining useful structure."""
|
|
88
|
-
value = _redact(value)
|
|
89
77
|
if depth >= 4:
|
|
90
78
|
return {"type": type(value).__name__, "fingerprint": _fingerprint_value(value)}
|
|
91
79
|
if isinstance(value, Mapping):
|
|
@@ -118,22 +106,17 @@ def _collect_file(work_dir: Path, name: str) -> dict[str, Any] | None:
|
|
|
118
106
|
path = work_dir / name
|
|
119
107
|
if not path.is_file():
|
|
120
108
|
return None
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"fingerprint": qc_contract.artifact_fingerprint(path),
|
|
133
|
-
"summary": summary,
|
|
134
|
-
}
|
|
135
|
-
except Exception as exc: # evidence collection is advisory too
|
|
136
|
-
return {"path": name, "kind": "unreadable", "error": type(exc).__name__}
|
|
109
|
+
if path.suffix.lower() == ".json":
|
|
110
|
+
kind, summary = "json", _summarize(_load_json(path))
|
|
111
|
+
else:
|
|
112
|
+
kind, summary = "text", _summarize(_read_text_sample(path))
|
|
113
|
+
return {
|
|
114
|
+
"path": name,
|
|
115
|
+
"kind": kind,
|
|
116
|
+
"bytes": path.stat().st_size,
|
|
117
|
+
"fingerprint": qc_contract.artifact_fingerprint(path),
|
|
118
|
+
"summary": summary,
|
|
119
|
+
}
|
|
137
120
|
|
|
138
121
|
|
|
139
122
|
def _first_existing(work_dir: Path, names: Sequence[str]) -> str | None:
|
|
@@ -145,32 +128,21 @@ def _collect_multi_source_asr(work_dir: Path) -> dict[str, Any]:
|
|
|
145
128
|
manifest_path = work_dir / "multi_source_manifest.json"
|
|
146
129
|
if not manifest_path.is_file():
|
|
147
130
|
return {}
|
|
148
|
-
|
|
149
|
-
manifest = _load_json(manifest_path)
|
|
150
|
-
except Exception:
|
|
151
|
-
return {}
|
|
152
|
-
sources = manifest.get("sources") if isinstance(manifest, Mapping) else None
|
|
153
|
-
if not isinstance(sources, list):
|
|
154
|
-
return {}
|
|
131
|
+
root = work_dir.resolve(strict=False)
|
|
155
132
|
collected = {}
|
|
156
|
-
for source in sources:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
relative_dir = str(source.get("source_work_dir") or "").strip()
|
|
161
|
-
if not source_id or not relative_dir:
|
|
162
|
-
continue
|
|
163
|
-
source_dir = (work_dir / relative_dir).resolve(strict=False)
|
|
164
|
-
try:
|
|
165
|
-
source_dir.relative_to(work_dir.resolve(strict=False))
|
|
166
|
-
except ValueError:
|
|
133
|
+
for source in _load_json(manifest_path)["sources"]:
|
|
134
|
+
relative_dir = source["source_work_dir"]
|
|
135
|
+
source_dir = (root / relative_dir).resolve(strict=False)
|
|
136
|
+
if not source_dir.is_relative_to(root):
|
|
167
137
|
continue
|
|
168
|
-
|
|
169
|
-
if not
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
collected[source_id] =
|
|
138
|
+
name = _first_existing(source_dir, _SOURCE_ASR_ARTIFACTS)
|
|
139
|
+
if name is not None:
|
|
140
|
+
evidence_path = (source_dir / name).resolve(strict=False)
|
|
141
|
+
if not evidence_path.is_relative_to(root):
|
|
142
|
+
continue
|
|
143
|
+
collected[source["source_id"]] = _collect_file(
|
|
144
|
+
root, str(evidence_path.relative_to(root))
|
|
145
|
+
)
|
|
174
146
|
return collected
|
|
175
147
|
|
|
176
148
|
|
|
@@ -187,14 +159,7 @@ def _final_output_candidates(
|
|
|
187
159
|
raw.append(final_output)
|
|
188
160
|
manifest_path = work_dir / "assembly_manifest.json"
|
|
189
161
|
if manifest_path.is_file():
|
|
190
|
-
|
|
191
|
-
manifest = _load_json(manifest_path)
|
|
192
|
-
if isinstance(manifest, Mapping):
|
|
193
|
-
for key in ("final_output", "output", "output_path", "video_path"):
|
|
194
|
-
if manifest.get(key):
|
|
195
|
-
raw.append(str(manifest[key]))
|
|
196
|
-
except Exception:
|
|
197
|
-
pass
|
|
162
|
+
raw.append(_load_json(manifest_path)["final_output"])
|
|
198
163
|
raw.extend(("output.mp4", "recap.mp4", "final.mp4"))
|
|
199
164
|
seen: set[str] = set()
|
|
200
165
|
result = []
|
|
@@ -221,7 +186,7 @@ def _final_output_metadata(
|
|
|
221
186
|
}
|
|
222
187
|
)
|
|
223
188
|
outputs.append(item)
|
|
224
|
-
return {"candidates":
|
|
189
|
+
return {"candidates": outputs}
|
|
225
190
|
|
|
226
191
|
|
|
227
192
|
def _existing_final_output(
|
|
@@ -279,41 +244,28 @@ def collect_evidence(
|
|
|
279
244
|
if not evidence["source_asr"]:
|
|
280
245
|
evidence["source_asr"] = _collect_multi_source_asr(root)
|
|
281
246
|
evidence["fingerprint"] = _fingerprint_value(_cache_evidence(evidence))
|
|
282
|
-
|
|
247
|
+
# The evidence goes into the MiMo request as well as the persisted report, so it is
|
|
248
|
+
# redacted here, before either.
|
|
249
|
+
return qc_contract.redact_secrets(evidence)
|
|
283
250
|
|
|
284
251
|
|
|
285
252
|
def _cache_file_group(group: Mapping[str, Any]) -> dict[str, Any]:
|
|
286
253
|
return {
|
|
287
|
-
name: {
|
|
288
|
-
key: item.get(key)
|
|
289
|
-
for key in ("kind", "bytes", "fingerprint")
|
|
290
|
-
if isinstance(item, Mapping) and item.get(key) is not None
|
|
291
|
-
}
|
|
254
|
+
name: {key: item[key] for key in ("kind", "bytes", "fingerprint")}
|
|
292
255
|
for name, item in sorted(group.items())
|
|
293
256
|
}
|
|
294
257
|
|
|
295
258
|
|
|
296
259
|
def _cache_evidence(evidence: Mapping[str, Any]) -> dict[str, Any]:
|
|
297
|
-
final_items = []
|
|
298
|
-
final_output = evidence.get("final_output")
|
|
299
|
-
if isinstance(final_output, Mapping):
|
|
300
|
-
for item in final_output.get("candidates", []):
|
|
301
|
-
if isinstance(item, Mapping):
|
|
302
|
-
final_items.append(
|
|
303
|
-
{
|
|
304
|
-
key: item.get(key)
|
|
305
|
-
for key in ("exists", "bytes", "fingerprint")
|
|
306
|
-
if key in item
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
260
|
return {
|
|
310
|
-
"artifacts": _cache_file_group(evidence
|
|
311
|
-
"source_asr": _cache_file_group(evidence
|
|
312
|
-
"generated_subtitles": _cache_file_group(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
261
|
+
"artifacts": _cache_file_group(evidence["artifacts"]),
|
|
262
|
+
"source_asr": _cache_file_group(evidence["source_asr"]),
|
|
263
|
+
"generated_subtitles": _cache_file_group(evidence["generated_subtitles"]),
|
|
264
|
+
"visual_metadata": _cache_file_group(evidence["visual_metadata"]),
|
|
265
|
+
"final_output": [
|
|
266
|
+
{key: item[key] for key in ("exists", "bytes", "fingerprint") if key in item}
|
|
267
|
+
for item in evidence["final_output"]["candidates"]
|
|
268
|
+
],
|
|
317
269
|
}
|
|
318
270
|
|
|
319
271
|
|
|
@@ -325,7 +277,7 @@ def _effective_config(config: Mapping[str, Any] | None = None) -> dict[str, Any]
|
|
|
325
277
|
source["mimo_qc_model"] = (
|
|
326
278
|
config.get("mimo_video_model")
|
|
327
279
|
or config.get("mimo_model")
|
|
328
|
-
or source
|
|
280
|
+
or source["mimo_qc_model"]
|
|
329
281
|
)
|
|
330
282
|
# MIMO_QC_MODEL is intentionally read at call time for embedded/CLI tests and
|
|
331
283
|
# long-running agent processes whose environment may be adjusted between runs.
|
|
@@ -354,18 +306,15 @@ def safe_mimo_config(config: Mapping[str, Any] | None = None) -> dict[str, Any]:
|
|
|
354
306
|
"mimo_media_resolution",
|
|
355
307
|
"mimo_media_resolution_source",
|
|
356
308
|
)
|
|
357
|
-
safe = {key: source[key] for key in keep
|
|
309
|
+
safe = {key: source[key] for key in keep}
|
|
358
310
|
safe["model"] = (
|
|
359
|
-
source
|
|
360
|
-
or source.get("mimo_video_model")
|
|
361
|
-
or source.get("mimo_model")
|
|
362
|
-
or "mimo-v2.5"
|
|
311
|
+
source["mimo_qc_model"] or source["mimo_video_model"] or source["mimo_model"]
|
|
363
312
|
)
|
|
364
313
|
key_present = bool(
|
|
365
314
|
source.get("mimo_video_api_key")
|
|
366
315
|
or source.get("mimo_api_key")
|
|
367
316
|
or source.get("api_key")
|
|
368
317
|
)
|
|
369
|
-
safe =
|
|
318
|
+
safe = qc_contract.redact_secrets(safe)
|
|
370
319
|
safe["key_present"] = key_present
|
|
371
320
|
return safe
|
|
@@ -2,18 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
import json
|
|
7
|
-
|
|
8
|
-
|
|
9
6
|
import re
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from typing import Any, Mapping
|
|
7
|
+
from typing import Any
|
|
8
|
+
from collections.abc import Mapping
|
|
13
9
|
|
|
14
10
|
import qc_contract
|
|
15
|
-
|
|
16
|
-
from mimo_qc_evidence import _fingerprint_value, _redact, _summarize, safe_mimo_config
|
|
11
|
+
from mimo_qc_evidence import _fingerprint_value, _summarize, safe_mimo_config
|
|
17
12
|
from mimo_qc_payload import _strip_json_fence
|
|
18
13
|
from mimo_qc_contract import (
|
|
19
14
|
ARTIFACT_NAME,
|
|
@@ -24,11 +19,11 @@ from mimo_qc_contract import (
|
|
|
24
19
|
|
|
25
20
|
|
|
26
21
|
def _extract_observations(model_output: Any) -> list[Mapping[str, Any]]:
|
|
27
|
-
|
|
22
|
+
"""Bound whatever shape the model (or an offline fixture) returned to observation dicts."""
|
|
28
23
|
if isinstance(model_output, str):
|
|
29
24
|
try:
|
|
30
25
|
model_output = json.loads(_strip_json_fence(model_output))
|
|
31
|
-
except
|
|
26
|
+
except ValueError:
|
|
32
27
|
return [
|
|
33
28
|
{
|
|
34
29
|
"code": "freeform_observation",
|
|
@@ -47,10 +42,9 @@ def _extract_observations(model_output: Any) -> list[Mapping[str, Any]]:
|
|
|
47
42
|
return [item for item in model_output[key] if isinstance(item, Mapping)][
|
|
48
43
|
:MAX_OBSERVATIONS
|
|
49
44
|
]
|
|
50
|
-
|
|
45
|
+
if "choices" in model_output: # a raw chat-completion response saved as a fixture
|
|
51
46
|
return _extract_observations(model_output["choices"][0]["message"]["content"])
|
|
52
|
-
|
|
53
|
-
return [model_output] if model_output else []
|
|
47
|
+
return [model_output] if model_output else []
|
|
54
48
|
|
|
55
49
|
|
|
56
50
|
def _norm_choice(raw: Any, allowed: set[str], default: str) -> str:
|
|
@@ -59,15 +53,10 @@ def _norm_choice(raw: Any, allowed: set[str], default: str) -> str:
|
|
|
59
53
|
|
|
60
54
|
|
|
61
55
|
def _caption_match_text(value: Any) -> str:
|
|
62
|
-
return re.sub(r"[^0-9A-Za-z
|
|
56
|
+
return re.sub(r"[^0-9A-Za-z㐀-鿿]+", "", str(value or "")).lower()
|
|
63
57
|
|
|
64
58
|
|
|
65
59
|
def _generated_subtitle_corpus(payload: Mapping[str, Any]) -> str:
|
|
66
|
-
group = (
|
|
67
|
-
(payload.get("evidence") or {}).get("generated_subtitles")
|
|
68
|
-
if isinstance(payload.get("evidence"), Mapping)
|
|
69
|
-
else None
|
|
70
|
-
)
|
|
71
60
|
strings = []
|
|
72
61
|
|
|
73
62
|
def visit(value: Any) -> None:
|
|
@@ -80,7 +69,7 @@ def _generated_subtitle_corpus(payload: Mapping[str, Any]) -> str:
|
|
|
80
69
|
for child in value:
|
|
81
70
|
visit(child)
|
|
82
71
|
|
|
83
|
-
visit(
|
|
72
|
+
visit(payload["evidence"]["generated_subtitles"])
|
|
84
73
|
return _caption_match_text(" ".join(strings))
|
|
85
74
|
|
|
86
75
|
|
|
@@ -117,21 +106,18 @@ def _misclassified_generated_caption(
|
|
|
117
106
|
def normalize_observations(
|
|
118
107
|
model_output: Any,
|
|
119
108
|
*,
|
|
109
|
+
payload: Mapping[str, Any],
|
|
120
110
|
stage: str = DEFAULT_STAGE,
|
|
121
|
-
payload: Mapping[str, Any] | None = None,
|
|
122
111
|
model_config: Mapping[str, Any] | None = None,
|
|
123
112
|
) -> list[dict[str, Any]]:
|
|
124
113
|
"""Normalize bounded model output into permanently non-blocking findings."""
|
|
125
|
-
payload = _redact(payload or {})
|
|
126
114
|
cfg = safe_mimo_config(model_config)
|
|
127
|
-
model_used =
|
|
115
|
+
model_used = cfg["model"]
|
|
128
116
|
findings = []
|
|
129
117
|
for index, observation in enumerate(_extract_observations(model_output), start=1):
|
|
130
118
|
if _misclassified_generated_caption(observation, payload, stage):
|
|
131
119
|
continue
|
|
132
120
|
obs = _summarize(observation, max_items=10, max_string=MAX_MESSAGE_CHARS)
|
|
133
|
-
if not isinstance(obs, Mapping):
|
|
134
|
-
continue
|
|
135
121
|
category_hint = str(
|
|
136
122
|
obs.get("category") or obs.get("type") or "semantic"
|
|
137
123
|
).lower()
|
|
@@ -156,18 +142,16 @@ def normalize_observations(
|
|
|
156
142
|
"aesthetic" if category == "mimo_aesthetic" else "semantic",
|
|
157
143
|
)
|
|
158
144
|
raw_evidence = obs.get("evidence")
|
|
159
|
-
supplied_evidence: Mapping[str, Any] = (
|
|
160
|
-
raw_evidence if isinstance(raw_evidence, Mapping) else {}
|
|
161
|
-
)
|
|
162
145
|
evidence = {
|
|
163
|
-
**
|
|
146
|
+
**(raw_evidence if isinstance(raw_evidence, Mapping) else {}),
|
|
164
147
|
"model": model_used,
|
|
165
148
|
"config": cfg,
|
|
166
149
|
"fingerprint": {
|
|
167
|
-
"evidence": payload
|
|
150
|
+
"evidence": payload["evidence_fingerprint"],
|
|
168
151
|
"observation": _fingerprint_value(observation),
|
|
169
152
|
},
|
|
170
153
|
}
|
|
154
|
+
location = obs.get("location")
|
|
171
155
|
findings.append(
|
|
172
156
|
qc_contract.build_finding(
|
|
173
157
|
finding_id=str(
|
|
@@ -185,14 +169,12 @@ def normalize_observations(
|
|
|
185
169
|
deterministic=False,
|
|
186
170
|
blocking=False,
|
|
187
171
|
source={"artifact": ARTIFACT_NAME, "adapter": "mimo_qc.py"},
|
|
188
|
-
location=
|
|
189
|
-
|
|
190
|
-
else {},
|
|
191
|
-
evidence=_redact(evidence),
|
|
172
|
+
location=location if isinstance(location, Mapping) else {},
|
|
173
|
+
evidence=evidence,
|
|
192
174
|
model_used=model_used,
|
|
193
175
|
artifact_fingerprints={
|
|
194
|
-
"payload":
|
|
195
|
-
"evidence":
|
|
176
|
+
"payload": payload["payload_fingerprint"],
|
|
177
|
+
"evidence": payload["evidence_fingerprint"],
|
|
196
178
|
},
|
|
197
179
|
next_action="human_review",
|
|
198
180
|
decision_reason=message,
|
|
@@ -2,19 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
7
8
|
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from mimo_qc_evidence import (
|
|
12
|
-
_cache_evidence,
|
|
13
|
-
_fingerprint_value,
|
|
14
|
-
_redact,
|
|
15
|
-
safe_mimo_config,
|
|
16
|
-
)
|
|
17
|
-
from mimo_qc_contract import ARTIFACT_NAME, DEFAULT_STAGE, MAX_FRAMES
|
|
9
|
+
from mimo_qc_evidence import _fingerprint_value, safe_mimo_config
|
|
10
|
+
from mimo_qc_contract import ARTIFACT_NAME, DEFAULT_STAGE
|
|
18
11
|
|
|
19
12
|
|
|
20
13
|
def _semantic_evidence(evidence: Mapping[str, Any], *, stage: str) -> dict[str, Any]:
|
|
@@ -83,22 +76,16 @@ def _semantic_evidence(evidence: Mapping[str, Any], *, stage: str) -> dict[str,
|
|
|
83
76
|
semantic["evidence_roles"].pop("post_render_frame_limits", None)
|
|
84
77
|
semantic["evidence_roles"].pop("actual_audio_timing", None)
|
|
85
78
|
else:
|
|
86
|
-
final_output = semantic.get("final_output")
|
|
87
|
-
candidates = (
|
|
88
|
-
final_output.get("candidates", [])
|
|
89
|
-
if isinstance(final_output, Mapping)
|
|
90
|
-
else []
|
|
91
|
-
)
|
|
92
79
|
semantic["final_output"] = {
|
|
93
80
|
"candidates": [
|
|
94
81
|
dict(candidate)
|
|
95
|
-
for candidate in candidates
|
|
96
|
-
if
|
|
82
|
+
for candidate in semantic["final_output"]["candidates"]
|
|
83
|
+
if candidate["exists"]
|
|
97
84
|
]
|
|
98
85
|
}
|
|
99
86
|
# Artifact summaries are already bounded at collection time; summarizing them again
|
|
100
87
|
# would wrap their ``items`` arrays in another summary layer and obscure the values.
|
|
101
|
-
return
|
|
88
|
+
return semantic
|
|
102
89
|
|
|
103
90
|
|
|
104
91
|
def build_payload(
|
|
@@ -144,11 +131,10 @@ def build_payload(
|
|
|
144
131
|
'"semantic|aesthetic|sampled","evidence":{...}}]}。最多 12 条。'
|
|
145
132
|
),
|
|
146
133
|
"evidence": _semantic_evidence(evidence, stage=stage),
|
|
147
|
-
"evidence_fingerprint": evidence
|
|
148
|
-
or _fingerprint_value(_cache_evidence(evidence)),
|
|
134
|
+
"evidence_fingerprint": evidence["fingerprint"],
|
|
149
135
|
}
|
|
150
136
|
payload["payload_fingerprint"] = _fingerprint_value(payload)
|
|
151
|
-
return
|
|
137
|
+
return payload
|
|
152
138
|
|
|
153
139
|
|
|
154
140
|
def _request_payload(
|
|
@@ -167,30 +153,24 @@ def _request_payload(
|
|
|
167
153
|
),
|
|
168
154
|
}
|
|
169
155
|
]
|
|
170
|
-
for index, sample in enumerate(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
content.append(
|
|
189
|
-
{
|
|
190
|
-
"type": "text",
|
|
191
|
-
"text": f"END QC_FRAME_{index}: timestamp {timestamp_label}.",
|
|
192
|
-
}
|
|
193
|
-
)
|
|
156
|
+
for index, sample in enumerate(frame_samples, start=1):
|
|
157
|
+
timestamp_label = f"{sample['timestamp']:.3f}s"
|
|
158
|
+
content.append(
|
|
159
|
+
{
|
|
160
|
+
"type": "text",
|
|
161
|
+
"text": (
|
|
162
|
+
f"BEGIN QC_FRAME_{index}: final-output timestamp {timestamp_label}. "
|
|
163
|
+
f"Judge only this image and do not transfer its content to another frame."
|
|
164
|
+
),
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
content.append({"type": "image_url", "image_url": {"url": sample["data_url"]}})
|
|
168
|
+
content.append(
|
|
169
|
+
{
|
|
170
|
+
"type": "text",
|
|
171
|
+
"text": f"END QC_FRAME_{index}: timestamp {timestamp_label}.",
|
|
172
|
+
}
|
|
173
|
+
)
|
|
194
174
|
return {
|
|
195
175
|
"model": payload["model"],
|
|
196
176
|
"messages": [{"role": "user", "content": content}],
|
|
@@ -209,6 +189,8 @@ def _strip_json_fence(text: str) -> str:
|
|
|
209
189
|
|
|
210
190
|
|
|
211
191
|
def _validated_live_output(response: Any) -> Any:
|
|
192
|
+
"""The one shape check on the third-party model response: a list of observations, or an
|
|
193
|
+
object carrying `observations`/`findings`; anything else is a failed (fail-open) request."""
|
|
212
194
|
try:
|
|
213
195
|
content = response["choices"][0]["message"]["content"]
|
|
214
196
|
except (KeyError, IndexError, TypeError):
|