@world-engines/project-host 0.1.0-alpha.0

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.
Files changed (218) hide show
  1. package/LICENSE +46 -0
  2. package/assets/gui/WELogo-title.svg +11 -0
  3. package/assets/gui/app.js +1326 -0
  4. package/assets/gui/index.html +118 -0
  5. package/assets/gui/local-preview-asset-urls.js +61 -0
  6. package/assets/gui/local-preview-bridge.js +393 -0
  7. package/assets/gui/spatial-template-panel.js +233 -0
  8. package/assets/gui/styles.css +115 -0
  9. package/assets/gui/terrain-render.js +120 -0
  10. package/assets/gui/workspace-status.css +17 -0
  11. package/assets/gui/workspace-status.js +255 -0
  12. package/assets/gui/world-map-globe.js +187 -0
  13. package/assets/gui/world-map-worker.js +62 -0
  14. package/assets/gui/world-map.css +30 -0
  15. package/assets/gui/world-map.js +2466 -0
  16. package/dist/account-model-proxy.d.ts +21 -0
  17. package/dist/account-model-proxy.js +470 -0
  18. package/dist/authoring-browser-runtime.d.ts +7 -0
  19. package/dist/authoring-browser-runtime.js +110 -0
  20. package/dist/authoring-ingestion.d.ts +38 -0
  21. package/dist/authoring-ingestion.js +410 -0
  22. package/dist/authoring-model-connection.d.ts +166 -0
  23. package/dist/authoring-model-connection.js +757 -0
  24. package/dist/chinese-lexical.d.ts +152 -0
  25. package/dist/chinese-lexical.js +555 -0
  26. package/dist/chromium-preview-browser-driver.d.ts +43 -0
  27. package/dist/chromium-preview-browser-driver.js +575 -0
  28. package/dist/cli/download.d.ts +12 -0
  29. package/dist/cli/download.js +50 -0
  30. package/dist/cli/dump.d.ts +32 -0
  31. package/dist/cli/dump.js +91 -0
  32. package/dist/cli/ingest.d.ts +49 -0
  33. package/dist/cli/ingest.js +225 -0
  34. package/dist/cli/review.d.ts +10 -0
  35. package/dist/cli/review.js +44 -0
  36. package/dist/cli/upload.d.ts +57 -0
  37. package/dist/cli/upload.js +112 -0
  38. package/dist/cli-command.d.ts +60 -0
  39. package/dist/cli-command.js +613 -0
  40. package/dist/cli.d.ts +2 -0
  41. package/dist/cli.js +182 -0
  42. package/dist/desktop-authoring-runtime.d.ts +88 -0
  43. package/dist/desktop-authoring-runtime.js +1602 -0
  44. package/dist/desktop-sidecar.d.ts +11 -0
  45. package/dist/desktop-sidecar.js +143 -0
  46. package/dist/document-dump.d.ts +113 -0
  47. package/dist/document-dump.js +431 -0
  48. package/dist/document-graph-extractor.d.ts +125 -0
  49. package/dist/document-graph-extractor.js +588 -0
  50. package/dist/document-ingestion-journal.d.ts +68 -0
  51. package/dist/document-ingestion-journal.js +471 -0
  52. package/dist/document-ingestion.d.ts +183 -0
  53. package/dist/document-ingestion.js +976 -0
  54. package/dist/document-query-path-proof.d.ts +30 -0
  55. package/dist/document-query-path-proof.js +34 -0
  56. package/dist/document-query-path-renderer.d.ts +50 -0
  57. package/dist/document-query-path-renderer.js +75 -0
  58. package/dist/document-query-proof-paths.d.ts +22 -0
  59. package/dist/document-query-proof-paths.js +71 -0
  60. package/dist/document-query.d.ts +97 -0
  61. package/dist/document-query.js +1106 -0
  62. package/dist/document-relation-role.d.ts +14 -0
  63. package/dist/document-relation-role.js +86 -0
  64. package/dist/document-source.d.ts +107 -0
  65. package/dist/document-source.js +255 -0
  66. package/dist/embedding-cache.d.ts +35 -0
  67. package/dist/embedding-cache.js +317 -0
  68. package/dist/frozen-review-local-pipeline.d.ts +88 -0
  69. package/dist/frozen-review-local-pipeline.js +609 -0
  70. package/dist/frozen-review-source-capture.d.ts +15 -0
  71. package/dist/frozen-review-source-capture.js +551 -0
  72. package/dist/frozen-review-wasm-encryptor.d.ts +16 -0
  73. package/dist/frozen-review-wasm-encryptor.js +72 -0
  74. package/dist/frozen-review.d.ts +131 -0
  75. package/dist/frozen-review.js +802 -0
  76. package/dist/graph-traversal.d.ts +23 -0
  77. package/dist/graph-traversal.js +100 -0
  78. package/dist/gui/graph-layout-worker.js +1 -0
  79. package/dist/gui/graph-layout.wasm +0 -0
  80. package/dist/gui/graph.css +1 -0
  81. package/dist/gui/graph.js +76 -0
  82. package/dist/gui/plane-generation.js +112 -0
  83. package/dist/gui/plane-generator.js +310 -0
  84. package/dist/gui/plane-generator_bg.wasm +0 -0
  85. package/dist/gui/procedural-city.js +595 -0
  86. package/dist/gui/road-generation.js +129 -0
  87. package/dist/gui/road-parcels.js +1584 -0
  88. package/dist/gui/score.css +1 -0
  89. package/dist/gui/score.js +54 -0
  90. package/dist/gui/spatial-runtime.js +1231 -0
  91. package/dist/gui/spatial-templates.js +200 -0
  92. package/dist/gui/trigger.css +1 -0
  93. package/dist/gui/trigger.js +142 -0
  94. package/dist/harness-callback-transport.d.ts +17 -0
  95. package/dist/harness-callback-transport.js +128 -0
  96. package/dist/harness-document-ingestion.d.ts +77 -0
  97. package/dist/harness-document-ingestion.js +343 -0
  98. package/dist/index.d.ts +43 -0
  99. package/dist/index.js +43 -0
  100. package/dist/ingestion-source-reconciliation.d.ts +88 -0
  101. package/dist/ingestion-source-reconciliation.js +253 -0
  102. package/dist/ladybug-runtime.d.ts +117 -0
  103. package/dist/ladybug-runtime.js +886 -0
  104. package/dist/local-account-embedding.d.ts +34 -0
  105. package/dist/local-account-embedding.js +294 -0
  106. package/dist/local-gallery.d.ts +35 -0
  107. package/dist/local-gallery.js +227 -0
  108. package/dist/local-gui-auth.d.ts +96 -0
  109. package/dist/local-gui-auth.js +703 -0
  110. package/dist/local-gui-contract.d.ts +53 -0
  111. package/dist/local-gui-contract.js +5 -0
  112. package/dist/local-gui-image-assets.d.ts +17 -0
  113. package/dist/local-gui-image-assets.js +96 -0
  114. package/dist/local-gui-preview-client.d.ts +3 -0
  115. package/dist/local-gui-preview-client.js +111 -0
  116. package/dist/local-gui-score.d.ts +72 -0
  117. package/dist/local-gui-score.js +184 -0
  118. package/dist/local-gui-server.d.ts +10 -0
  119. package/dist/local-gui-server.js +987 -0
  120. package/dist/local-gui-version.d.ts +17 -0
  121. package/dist/local-gui-version.js +147 -0
  122. package/dist/local-gui-workspace.d.ts +14 -0
  123. package/dist/local-gui-workspace.js +171 -0
  124. package/dist/local-model-settings.d.ts +28 -0
  125. package/dist/local-model-settings.js +124 -0
  126. package/dist/local-preview-assets.d.ts +28 -0
  127. package/dist/local-preview-assets.js +124 -0
  128. package/dist/local-preview-bound-resources.d.ts +12 -0
  129. package/dist/local-preview-bound-resources.js +80 -0
  130. package/dist/local-preview-message-store.d.ts +63 -0
  131. package/dist/local-preview-message-store.js +214 -0
  132. package/dist/local-preview-player-read-models.d.ts +47 -0
  133. package/dist/local-preview-player-read-models.js +90 -0
  134. package/dist/local-preview-projections.d.ts +61 -0
  135. package/dist/local-preview-projections.js +117 -0
  136. package/dist/local-preview-runtime.d.ts +105 -0
  137. package/dist/local-preview-runtime.js +553 -0
  138. package/dist/local-preview-session.d.ts +73 -0
  139. package/dist/local-preview-session.js +395 -0
  140. package/dist/local-preview-source.d.ts +40 -0
  141. package/dist/local-preview-source.js +192 -0
  142. package/dist/local-preview-view-events.d.ts +101 -0
  143. package/dist/local-preview-view-events.js +179 -0
  144. package/dist/local-preview-view-state-sqlite.d.ts +24 -0
  145. package/dist/local-preview-view-state-sqlite.js +263 -0
  146. package/dist/local-preview-view-state.d.ts +93 -0
  147. package/dist/local-preview-view-state.js +108 -0
  148. package/dist/local-preview-vite-config.d.ts +6 -0
  149. package/dist/local-preview-vite-config.js +122 -0
  150. package/dist/local-preview-world-effects.d.ts +9 -0
  151. package/dist/local-preview-world-effects.js +55 -0
  152. package/dist/local-preview-worldline-store.d.ts +47 -0
  153. package/dist/local-preview-worldline-store.js +282 -0
  154. package/dist/local-preview-worldlines.d.ts +95 -0
  155. package/dist/local-preview-worldlines.js +136 -0
  156. package/dist/mcp-async-dispatcher.d.ts +3 -0
  157. package/dist/mcp-async-dispatcher.js +129 -0
  158. package/dist/mcp-line-queue.d.ts +14 -0
  159. package/dist/mcp-line-queue.js +184 -0
  160. package/dist/mcp-pipe-internal.d.ts +6 -0
  161. package/dist/mcp-pipe-internal.js +151 -0
  162. package/dist/mcp-pipe-response.d.ts +14 -0
  163. package/dist/mcp-pipe-response.js +50 -0
  164. package/dist/mcp-stdio-response.d.ts +12 -0
  165. package/dist/mcp-stdio-response.js +27 -0
  166. package/dist/mcp.d.ts +27 -0
  167. package/dist/mcp.js +194 -0
  168. package/dist/prepared-embedding-cache.d.ts +38 -0
  169. package/dist/prepared-embedding-cache.js +238 -0
  170. package/dist/preview-browser-adapter.d.ts +53 -0
  171. package/dist/preview-browser-adapter.js +171 -0
  172. package/dist/preview-runtime.d.ts +67 -0
  173. package/dist/preview-runtime.js +460 -0
  174. package/dist/project-auth-transport.d.ts +27 -0
  175. package/dist/project-auth-transport.js +121 -0
  176. package/dist/project-auth.d.ts +51 -0
  177. package/dist/project-auth.js +108 -0
  178. package/dist/project-git-capture-recovery.d.ts +16 -0
  179. package/dist/project-git-capture-recovery.js +347 -0
  180. package/dist/project-git-cli.d.ts +5 -0
  181. package/dist/project-git-cli.js +40 -0
  182. package/dist/project-git-scenario-source.d.ts +10 -0
  183. package/dist/project-git-scenario-source.js +240 -0
  184. package/dist/project-git-view-source.d.ts +13 -0
  185. package/dist/project-git-view-source.js +134 -0
  186. package/dist/project-git.d.ts +97 -0
  187. package/dist/project-git.js +656 -0
  188. package/dist/project-host.d.ts +59 -0
  189. package/dist/project-host.js +471 -0
  190. package/dist/project-writer-lease.d.ts +18 -0
  191. package/dist/project-writer-lease.js +430 -0
  192. package/dist/public-error.d.ts +7 -0
  193. package/dist/public-error.js +61 -0
  194. package/dist/retrieval-benchmark.d.ts +46 -0
  195. package/dist/retrieval-benchmark.js +58 -0
  196. package/dist/safe-project-path.d.ts +8 -0
  197. package/dist/safe-project-path.js +93 -0
  198. package/dist/scenario-writer.d.ts +32 -0
  199. package/dist/scenario-writer.js +191 -0
  200. package/dist/transfer/download.d.ts +159 -0
  201. package/dist/transfer/download.js +598 -0
  202. package/dist/transfer/transport.d.ts +26 -0
  203. package/dist/transfer/transport.js +1 -0
  204. package/dist/transfer/upload.d.ts +162 -0
  205. package/dist/transfer/upload.js +354 -0
  206. package/dist/transfer-types.d.ts +95 -0
  207. package/dist/transfer-types.js +1 -0
  208. package/dist/transfer.d.ts +18 -0
  209. package/dist/transfer.js +273 -0
  210. package/dist/trigger-runtime.d.ts +121 -0
  211. package/dist/trigger-runtime.js +153 -0
  212. package/dist/vendor/wasm-core/asset-hashes.json +5 -0
  213. package/dist/vendor/wasm-core/wasm_core.d.ts +490 -0
  214. package/dist/vendor/wasm-core/wasm_core.js +1 -0
  215. package/dist/vendor/wasm-core/wasm_core_bg.wasm +0 -0
  216. package/dist/view-watcher.d.ts +9 -0
  217. package/dist/view-watcher.js +34 -0
  218. package/package.json +110 -0
@@ -0,0 +1,588 @@
1
+ import { AuthoringModelConnectionError } from "./authoring-model-connection.js";
2
+ export class DocumentGraphExtractionError extends Error {
3
+ code;
4
+ diagnostics;
5
+ name = "DocumentGraphExtractionError";
6
+ constructor(code, diagnostics) {
7
+ super(code);
8
+ this.code = code;
9
+ this.diagnostics = diagnostics;
10
+ }
11
+ }
12
+ const DEFAULT_EXTRACTION_MAX_OUTPUT_TOKENS = 32_768;
13
+ const MAX_EXTRACTION_MAX_OUTPUT_TOKENS = 65_536;
14
+ const NODE_KINDS = Object.freeze(["person", "place", "object", "organization", "event", "concept"]);
15
+ const LOCAL_ID = /^[a-z][a-z0-9_]{0,127}$/u;
16
+ const PREDICATE = /^[a-z][a-z0-9_]{0,127}$/u;
17
+ const PREDICATE_ALIASES = Object.freeze({
18
+ based_in: "located_in", in: "located_in", located_at: "located_in",
19
+ father_of: "parent_of", mother_of: "parent_of", parent: "parent_of",
20
+ master_of: "teacher_of", teacher: "teacher_of",
21
+ owner_of: "owns", owned_by: "owned_by",
22
+ belongs_to: "member_of", member: "member_of",
23
+ participant_in: "participates_in", took_part_in: "participates_in",
24
+ occurred_at: "happened_at", occurs_at: "happened_at",
25
+ });
26
+ const EXTRACTION_SCHEMA = Object.freeze({
27
+ type: "object",
28
+ additionalProperties: false,
29
+ required: ["segments", "nodes", "edges"],
30
+ properties: {
31
+ segments: { type: "array", minItems: 1, items: { type: "object", additionalProperties: false,
32
+ required: ["start_unit_id", "end_unit_id", "title"], properties: {
33
+ start_unit_id: { type: "string", minLength: 1 }, end_unit_id: { type: "string", minLength: 1 }, title: { type: "string", minLength: 1 },
34
+ } } },
35
+ nodes: { type: "array", items: { type: "object", additionalProperties: false,
36
+ required: ["local_id", "label", "kind", "aliases", "statements"], properties: {
37
+ local_id: { type: "string", pattern: "^[a-z][a-z0-9_]{0,127}$" },
38
+ label: { type: "string", minLength: 1 }, kind: { type: "string", enum: NODE_KINDS },
39
+ aliases: { type: "array", items: { type: "string", minLength: 1 } },
40
+ statements: { type: "array", items: { type: "object", additionalProperties: false,
41
+ required: ["predicate", "assertion", "value_json", "qualifiers_json", "evidence_unit_ids"], properties: {
42
+ predicate: { type: "string", pattern: "^[a-z][a-z0-9_]{0,127}$" }, assertion: { type: "string", minLength: 1 },
43
+ value_json: { type: "string", minLength: 1 }, qualifiers_json: { type: "string", minLength: 2 },
44
+ evidence_unit_ids: { type: "array", minItems: 1, items: { type: "string", minLength: 1 } },
45
+ } },
46
+ },
47
+ } } },
48
+ edges: { type: "array", items: { type: "object", additionalProperties: false,
49
+ required: ["source_local_id", "target_local_id", "predicate", "assertion", "qualifiers_json", "evidence_unit_ids"], properties: {
50
+ source_local_id: { type: "string", minLength: 1 }, target_local_id: { type: "string", minLength: 1 },
51
+ predicate: { type: "string", pattern: "^[a-z][a-z0-9_]{0,127}$" }, assertion: { type: "string", minLength: 1 },
52
+ qualifiers_json: { type: "string", minLength: 2 }, evidence_unit_ids: { type: "array", minItems: 1, items: { type: "string", minLength: 1 } },
53
+ } } },
54
+ },
55
+ });
56
+ const SYSTEM_PROMPT = [
57
+ "你是 D4F 文档结构与知识图谱抽取器。只输出符合 schema 的单个裸 JSON 对象;禁止 Markdown 代码围栏、前言、解释和后记。",
58
+ "segments 必须按原顺序完整且恰好覆盖全部 core units,不得覆盖 context units,不得 gap/overlap。目标语义段约 500-1800 字,但不得拆开原子 unit,也不为凑长度伪造内容。",
59
+ "coreCoverage.singleSegmentRequired 为 true 时只输出一个 segment:start_unit_id 必须是 coreCoverage.firstCoreUnitId,end_unit_id 必须是 coreCoverage.lastCoreUnitId。所有 layout/control unit 也必须被相邻 segment 范围覆盖;不能跳过空白 unit。",
60
+ "context units 只用于消解 core 中跨句代词和承接;node/statement/edge 的 evidence 可含 context,但必须至少含一个 core unit。不要单独抽取只存在于 context 的事实。",
61
+ "node 是人物、地点、物件、组织、事件或概念;属性和事件细节写入 node.statements。edge 只用于两个 node 之间有方向的关系,绝不能把 node 本身当 edge。",
62
+ "两个实体之间的关系只写 edge,不要再把同一关系重复写成 node statement;statement 用于标量/JSON 属性、事件摘要及无法自然建模为两个实体的可检索陈述。",
63
+ "aliases 只放同一实体的真实别名、字号、旧称或简称,不放职业、类别、关系、地点或描述。",
64
+ "knownEntities 只是当前窗口命中的别名参考,不是事实证据。不要复述名单,也不要因参考提示重复输出同一身份或同一事实;仅抽取当前 core 支持的事实。",
65
+ "predicate 必须使用英文 lower_snake_case。优先通用语义与固定方向:is_a 实例→类别;located_in 对象→容器地点;parent_of 父母→子女;teacher_of 教师→学生;owns 所有者→所有物;member_of 成员→组织;part_of 部分→整体;rules 统治者→辖地;created_by 作品→创作者;participates_in 参与者→事件;happened_at 事件→地点;happened_before 较早事件→较晚事件。只有真正对称且更具体关系不存在时才用 related_to。",
66
+ "assertion 必须用自然语言明确说出主体、关系、客体或属性值。输出前结合 evidence 和 context 检查关系方向;quote/unit 存在本身不能证明方向正确。",
67
+ "qualifiers_json 必须是 JSON object 字符串,保留时间、说话人、否定、条件、传闻/不确定性等限定;无限定用 {}。statement.value_json 必须是任意合法 JSON 值的字符串。",
68
+ "local_id 仅在当前窗口内引用,禁止生成全局/stable ID。每条 edge 必须引用本次 nodes 中存在的 local_id。不得编造缺失事实;允许 nodes/edges 为空。",
69
+ ].join("\n");
70
+ const SEMANTIC_NEGATION_V4_PROMPT = [
71
+ "否定的两个实体关系也必须写入 edge,不得只写成 node statement。",
72
+ "否定 edge 的 predicate 必须使用不带 not_ 前缀的正向 canonical predicate,并在 qualifiers_json 中显式写 negation:true;不得把否定关系输出成正向事实。",
73
+ "无法同时输出有当前 evidence 支持的两个 endpoint node 时,省略该关系;不得从 statement、代词或自然语言猜测补造 endpoint 或反转方向。",
74
+ ].join("\n");
75
+ function systemPrompt(input) {
76
+ return input.methodVersion === "semantic-negation-v4" || input.methodVersion === "harness-v1"
77
+ ? `${SYSTEM_PROMPT}\n${SEMANTIC_NEGATION_V4_PROMPT}`
78
+ : SYSTEM_PROMPT;
79
+ }
80
+ function invariant(condition, code) { if (!condition)
81
+ throw new Error(code); }
82
+ function record(value) {
83
+ if (typeof value !== "object" || value === null || Array.isArray(value))
84
+ return undefined;
85
+ const prototype = Object.getPrototypeOf(value);
86
+ return prototype === Object.prototype || prototype === null ? value : undefined;
87
+ }
88
+ function hasOnlyKeys(value, keys) {
89
+ return value !== undefined && Object.keys(value).every((key) => keys.includes(key));
90
+ }
91
+ function text(value, maximum = 32_768) {
92
+ if (typeof value !== "string")
93
+ return undefined;
94
+ const normalized = value.trim().normalize("NFC");
95
+ return normalized.length > 0 && normalized.length <= maximum && !normalized.includes("\0") ? normalized : undefined;
96
+ }
97
+ function note(diagnostics, kind, reason) {
98
+ if (kind === "segment")
99
+ diagnostics.rejectedSegmentCount += 1;
100
+ else if (kind === "node")
101
+ diagnostics.rejectedNodeCount += 1;
102
+ else if (kind === "statement")
103
+ diagnostics.rejectedStatementCount += 1;
104
+ else
105
+ diagnostics.rejectedEdgeCount += 1;
106
+ diagnostics.rejectionReasons[reason] = (diagnostics.rejectionReasons[reason] ?? 0) + 1;
107
+ }
108
+ function rejectWithReasons(diagnostics, kind, reasons) {
109
+ if (kind === "node")
110
+ diagnostics.rejectedNodeCount += 1;
111
+ else if (kind === "statement")
112
+ diagnostics.rejectedStatementCount += 1;
113
+ else
114
+ diagnostics.rejectedEdgeCount += 1;
115
+ for (const reason of reasons)
116
+ diagnostics.rejectionReasons[reason] = (diagnostics.rejectionReasons[reason] ?? 0) + 1;
117
+ }
118
+ function jsonValue(value) {
119
+ if (value === null || typeof value === "string" || typeof value === "boolean")
120
+ return true;
121
+ if (typeof value === "number")
122
+ return Number.isFinite(value);
123
+ if (Array.isArray(value))
124
+ return value.every(jsonValue);
125
+ const input = record(value);
126
+ return input !== undefined && Object.values(input).every(jsonValue);
127
+ }
128
+ function parseJsonValue(value) {
129
+ if (typeof value !== "string")
130
+ return undefined;
131
+ try {
132
+ const decoded = JSON.parse(value);
133
+ return jsonValue(decoded) ? decoded : undefined;
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ }
139
+ function parseQualifiers(value) {
140
+ const decoded = parseJsonValue(value);
141
+ return record(decoded);
142
+ }
143
+ function canonicalJson(value) {
144
+ if (Array.isArray(value))
145
+ return `[${value.map(canonicalJson).join(",")}]`;
146
+ const input = record(value);
147
+ if (input)
148
+ return `{${Object.keys(input).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(input[key])}`).join(",")}}`;
149
+ return JSON.stringify(value);
150
+ }
151
+ function normalizePredicate(value) {
152
+ const candidate = text(value, 128)?.toLowerCase();
153
+ if (!candidate || !PREDICATE.test(candidate))
154
+ return undefined;
155
+ return PREDICATE_ALIASES[candidate] ?? candidate;
156
+ }
157
+ function normalizeEdgePredicate(value, qualifiers, semanticNegation) {
158
+ const predicate = normalizePredicate(value);
159
+ if (!predicate || !semanticNegation || !predicate.startsWith("not_"))
160
+ return predicate === undefined ? Object.freeze({}) : Object.freeze({ predicate });
161
+ if (qualifiers?.negation !== true)
162
+ return Object.freeze({ reason: "edge_negation_not_explicit" });
163
+ const positive = predicate.slice(4);
164
+ if (!PREDICATE.test(positive) || positive.startsWith("not_"))
165
+ return Object.freeze({ reason: "edge_predicate_invalid" });
166
+ return Object.freeze({ predicate: PREDICATE_ALIASES[positive] ?? positive });
167
+ }
168
+ function diagnosticsValue(value) {
169
+ return Object.freeze({ ...value, rejectionReasons: Object.freeze(Object.fromEntries(Object.entries(value.rejectionReasons).sort())) });
170
+ }
171
+ function providerInvalidDetail(error) {
172
+ if (error.schemaIssue !== undefined)
173
+ return Object.freeze({
174
+ reason: `provider_schema_${error.schemaIssue.keyword}`,
175
+ repairIssue: Object.freeze({ stage: "closed_schema", validationIssues: error.validationIssues }),
176
+ });
177
+ const stage = error.validationStage === "closed_schema" ? "unknown" : error.validationStage ?? "unknown";
178
+ return Object.freeze({ reason: `provider_${stage}_invalid`, repairIssue: Object.freeze({ stage,
179
+ ...(error.parseIssue === undefined ? {} : { parseIssue: error.parseIssue }) }) });
180
+ }
181
+ function sourceUnits(input) {
182
+ const actualWindow = input.source.windows.find((candidate) => candidate.id === input.window.id);
183
+ invariant(actualWindow !== undefined, "document_graph_window_unknown");
184
+ invariant(actualWindow.coreUnitIds.join("\0") === input.window.coreUnitIds.join("\0")
185
+ && actualWindow.contextUnitIds.join("\0") === input.window.contextUnitIds.join("\0"), "document_graph_window_mismatch");
186
+ const unitById = new Map(input.source.units.map((unit) => [unit.id, unit]));
187
+ const allowedIds = new Set([...input.window.contextUnitIds, ...input.window.coreUnitIds]);
188
+ invariant(input.window.coreUnitIds.length > 0 && [...allowedIds].every((id) => unitById.has(id)), "document_graph_window_unit_unknown");
189
+ return Object.freeze({ unitById, coreIds: input.window.coreUnitIds, allowedIds, windowText: actualWindow.text });
190
+ }
191
+ function evidence(value, source) {
192
+ if (!Array.isArray(value) || value.length === 0 || !value.every((id) => typeof id === "string" && source.allowedIds.has(id)))
193
+ return undefined;
194
+ const requested = new Set(value);
195
+ if (!source.coreIds.some((id) => requested.has(id)))
196
+ return undefined;
197
+ const units = [...source.unitById.values()].filter((unit) => requested.has(unit.id));
198
+ if (units.length !== requested.size)
199
+ return undefined;
200
+ return Object.freeze({
201
+ unitIds: Object.freeze(units.map((unit) => unit.id)),
202
+ text: units.map((unit) => unit.text).join(""),
203
+ startOffset: units[0].startOffset,
204
+ endOffset: units.at(-1).endOffset,
205
+ });
206
+ }
207
+ function validateSegments(value, source, diagnostics) {
208
+ if (!Array.isArray(value) || value.length === 0) {
209
+ diagnostics.segmentationIssues = Object.freeze([{ segmentIndex: 0, kind: "missing_segments", expectedStartUnitId: source.coreIds[0] ?? null,
210
+ actualStartUnitId: null, actualEndUnitId: null }]);
211
+ note(diagnostics, "segment", "segments_required");
212
+ return undefined;
213
+ }
214
+ const indexes = new Map(source.coreIds.map((id, index) => [id, index]));
215
+ const output = [];
216
+ const issues = [];
217
+ let expectedStart = 0;
218
+ for (const [segmentIndex, candidate] of value.entries()) {
219
+ const input = record(candidate);
220
+ const start = typeof input?.start_unit_id === "string" ? indexes.get(input.start_unit_id) : undefined;
221
+ const end = typeof input?.end_unit_id === "string" ? indexes.get(input.end_unit_id) : undefined;
222
+ const title = text(input?.title, 512);
223
+ const bounds = { segmentIndex, expectedStartUnitId: source.coreIds[expectedStart] ?? null,
224
+ actualStartUnitId: start === undefined ? null : source.coreIds[start], actualEndUnitId: end === undefined ? null : source.coreIds[end] };
225
+ if (start === undefined || end === undefined || end < start) {
226
+ issues.push(Object.freeze({ ...bounds, kind: "invalid_bounds" }));
227
+ continue;
228
+ }
229
+ if (start !== expectedStart) {
230
+ issues.push(Object.freeze({ ...bounds, kind: start > expectedStart ? "gap" : "overlap",
231
+ affectedStartUnitId: source.coreIds[Math.min(start, expectedStart)],
232
+ affectedEndUnitId: source.coreIds[start > expectedStart ? start - 1 : Math.min(end, expectedStart - 1)],
233
+ }));
234
+ }
235
+ if (!title || !hasOnlyKeys(input, ["start_unit_id", "end_unit_id", "title"])) {
236
+ issues.push(Object.freeze({ ...bounds, kind: "invalid_title" }));
237
+ }
238
+ else
239
+ output.push(Object.freeze({ startUnitId: source.coreIds[start], endUnitId: source.coreIds[end], title }));
240
+ expectedStart = Math.max(expectedStart, end + 1);
241
+ }
242
+ if (expectedStart !== source.coreIds.length)
243
+ issues.push(Object.freeze({ segmentIndex: value.length, kind: "gap",
244
+ expectedStartUnitId: source.coreIds[expectedStart], actualStartUnitId: null, actualEndUnitId: null,
245
+ affectedStartUnitId: source.coreIds[expectedStart], affectedEndUnitId: source.coreIds.at(-1),
246
+ }));
247
+ if (issues.length > 0) {
248
+ diagnostics.segmentationIssues = Object.freeze(issues);
249
+ note(diagnostics, "segment", issues.length === 1 && issues[0].segmentIndex === value.length
250
+ ? "segment_coverage_incomplete" : "segment_order_or_coverage_invalid");
251
+ return undefined;
252
+ }
253
+ return Object.freeze(output);
254
+ }
255
+ function validateCandidate(value, source, diagnostics, refinedDiagnostics, semanticNegation) {
256
+ const before = diagnostics.rejectedSegmentCount + diagnostics.rejectedNodeCount + diagnostics.rejectedStatementCount + diagnostics.rejectedEdgeCount;
257
+ const root = record(value);
258
+ if (!hasOnlyKeys(root, ["segments", "nodes", "edges"]))
259
+ note(diagnostics, "segment", "response_shape_invalid");
260
+ const segments = validateSegments(root?.segments, source, diagnostics);
261
+ const rawNodes = Array.isArray(root?.nodes) ? root.nodes : [];
262
+ if (!Array.isArray(root?.nodes))
263
+ note(diagnostics, "node", "nodes_invalid");
264
+ const nodeByLocalId = new Map();
265
+ const nodeByIdentity = new Map();
266
+ for (const candidate of rawNodes) {
267
+ const input = record(candidate);
268
+ const localId = text(input?.local_id, 128);
269
+ const label = text(input?.label, 512);
270
+ const kind = NODE_KINDS.includes(input?.kind) ? input.kind : undefined;
271
+ const nodeReasons = [
272
+ ...(!localId || !LOCAL_ID.test(localId) ? ["node_local_id_invalid"] : []),
273
+ ...(localId && nodeByLocalId.has(localId) ? ["node_local_id_duplicate"] : []),
274
+ ...(!label ? ["node_label_invalid"] : []),
275
+ ...(!kind ? ["node_kind_invalid"] : []),
276
+ ...(!Array.isArray(input?.aliases) ? ["node_aliases_container_invalid"] : []),
277
+ ...(!Array.isArray(input?.statements) ? ["node_statements_container_invalid"] : []),
278
+ ...(!hasOnlyKeys(input, ["local_id", "label", "kind", "aliases", "statements"]) ? ["node_additional_property"] : []),
279
+ ];
280
+ if (nodeReasons.length > 0) {
281
+ if (refinedDiagnostics)
282
+ rejectWithReasons(diagnostics, "node", nodeReasons);
283
+ else
284
+ note(diagnostics, "node", "node_shape_invalid");
285
+ continue;
286
+ }
287
+ invariant(input !== undefined && localId !== undefined && label !== undefined && kind !== undefined
288
+ && Array.isArray(input.aliases) && Array.isArray(input.statements), "document_graph_node_validation_inconsistent");
289
+ const aliases = input.aliases.map((alias) => text(alias, 512));
290
+ if (aliases.some((alias) => !alias)) {
291
+ note(diagnostics, "node", "node_alias_invalid");
292
+ continue;
293
+ }
294
+ const identity = `${kind}\0${label.toLocaleLowerCase("zh-Hans")}`;
295
+ const canonical = nodeByIdentity.get(identity) ?? { localId, label, kind, aliases: new Set(), statements: [] };
296
+ if (!nodeByIdentity.has(identity))
297
+ nodeByIdentity.set(identity, canonical);
298
+ nodeByLocalId.set(localId, canonical.localId);
299
+ for (const alias of aliases)
300
+ if (alias !== label)
301
+ canonical.aliases.add(alias);
302
+ for (const statementValue of input.statements) {
303
+ const statement = record(statementValue);
304
+ const predicate = normalizePredicate(statement?.predicate);
305
+ const assertion = text(statement?.assertion);
306
+ const parsedValue = parseJsonValue(statement?.value_json);
307
+ const qualifiers = parseQualifiers(statement?.qualifiers_json);
308
+ const proven = evidence(statement?.evidence_unit_ids, source);
309
+ const statementReasons = [
310
+ ...(!predicate ? ["statement_predicate_invalid"] : []), ...(!assertion ? ["statement_assertion_invalid"] : []),
311
+ ...(parsedValue === undefined ? ["statement_value_json_invalid"] : []), ...(!qualifiers ? ["statement_qualifiers_invalid"] : []),
312
+ ...(!proven ? ["statement_evidence_invalid"] : []),
313
+ ...(!hasOnlyKeys(statement, ["predicate", "assertion", "value_json", "qualifiers_json", "evidence_unit_ids"])
314
+ ? ["statement_additional_property"] : []),
315
+ ];
316
+ if (statementReasons.length > 0) {
317
+ if (refinedDiagnostics)
318
+ rejectWithReasons(diagnostics, "statement", statementReasons);
319
+ else
320
+ note(diagnostics, "statement", "node_statement_invalid");
321
+ continue;
322
+ }
323
+ invariant(predicate !== undefined && assertion !== undefined && parsedValue !== undefined && qualifiers !== undefined && proven !== undefined, "document_graph_statement_validation_inconsistent");
324
+ const normalized = Object.freeze({ predicate, assertion, value: parsedValue, qualifiers: Object.freeze(qualifiers), evidence: proven });
325
+ const key = `${predicate}\0${assertion}\0${canonicalJson(parsedValue)}\0${canonicalJson(qualifiers)}`;
326
+ if (!canonical.statements.some((item) => `${item.predicate}\0${item.assertion}\0${canonicalJson(item.value)}\0${canonicalJson(item.qualifiers)}` === key))
327
+ canonical.statements.push(normalized);
328
+ }
329
+ }
330
+ const nodes = Object.freeze([...nodeByIdentity.values()].map((node) => Object.freeze({
331
+ localId: node.localId, label: node.label, kind: node.kind,
332
+ aliases: Object.freeze([...node.aliases].sort((left, right) => left.localeCompare(right, "zh-Hans"))),
333
+ statements: Object.freeze(node.statements),
334
+ })));
335
+ const rawEdges = Array.isArray(root?.edges) ? root.edges : [];
336
+ if (!Array.isArray(root?.edges))
337
+ note(diagnostics, "edge", "edges_invalid");
338
+ const edgesByIdentity = new Map();
339
+ for (const candidate of rawEdges) {
340
+ const input = record(candidate);
341
+ const sourceLocalId = typeof input?.source_local_id === "string" ? nodeByLocalId.get(input.source_local_id) : undefined;
342
+ const targetLocalId = typeof input?.target_local_id === "string" ? nodeByLocalId.get(input.target_local_id) : undefined;
343
+ const assertion = text(input?.assertion);
344
+ const qualifiers = parseQualifiers(input?.qualifiers_json);
345
+ const normalizedPredicate = normalizeEdgePredicate(input?.predicate, qualifiers, semanticNegation);
346
+ const predicate = normalizedPredicate.predicate;
347
+ const proven = evidence(input?.evidence_unit_ids, source);
348
+ const edgeReasons = [
349
+ ...(!sourceLocalId ? ["edge_source_ref_missing"] : []), ...(!targetLocalId ? ["edge_target_ref_missing"] : []),
350
+ ...(sourceLocalId !== undefined && sourceLocalId === targetLocalId ? ["edge_self_edge"] : []),
351
+ ...(normalizedPredicate.reason !== undefined ? [normalizedPredicate.reason] : !predicate ? ["edge_predicate_invalid"] : []),
352
+ ...(!assertion ? ["edge_assertion_invalid"] : []),
353
+ ...(!qualifiers ? ["edge_qualifiers_invalid"] : []), ...(!proven ? ["edge_evidence_invalid"] : []),
354
+ ...(!hasOnlyKeys(input, ["source_local_id", "target_local_id", "predicate", "assertion", "qualifiers_json", "evidence_unit_ids"])
355
+ ? ["edge_additional_property"] : []),
356
+ ];
357
+ if (edgeReasons.length > 0) {
358
+ if (refinedDiagnostics)
359
+ rejectWithReasons(diagnostics, "edge", edgeReasons);
360
+ else
361
+ note(diagnostics, "edge", !sourceLocalId || !targetLocalId ? "edge_node_ref_invalid" : "edge_invalid");
362
+ continue;
363
+ }
364
+ invariant(sourceLocalId !== undefined && targetLocalId !== undefined && predicate !== undefined && assertion !== undefined
365
+ && qualifiers !== undefined && proven !== undefined, "document_graph_edge_validation_inconsistent");
366
+ const key = `${sourceLocalId}\0${targetLocalId}\0${predicate}\0${assertion}\0${canonicalJson(qualifiers)}`;
367
+ const previous = edgesByIdentity.get(key);
368
+ if (!previous)
369
+ edgesByIdentity.set(key, Object.freeze({ sourceLocalId, targetLocalId, predicate, assertion, qualifiers: Object.freeze(qualifiers), evidence: proven }));
370
+ else {
371
+ const merged = evidence([...previous.evidence.unitIds, ...proven.unitIds], source);
372
+ edgesByIdentity.set(key, Object.freeze({ ...previous, evidence: merged }));
373
+ }
374
+ }
375
+ const after = diagnostics.rejectedSegmentCount + diagnostics.rejectedNodeCount + diagnostics.rejectedStatementCount + diagnostics.rejectedEdgeCount;
376
+ return Object.freeze({ ...(segments === undefined ? {} : { segments }), nodes, edges: Object.freeze([...edgesByIdentity.values()]), issueCount: after - before });
377
+ }
378
+ function adaptiveRepairPayload(input, source) {
379
+ const previous = input.adaptiveRepair;
380
+ if (previous === undefined)
381
+ return undefined;
382
+ invariant(input.methodVersion === "adaptive-repair-v3" || input.methodVersion === "semantic-negation-v4" || input.methodVersion === "harness-v1", "document_graph_adaptive_method_invalid");
383
+ invariant(Number.isSafeInteger(previous.attempt) && previous.attempt >= 1 && previous.attempt < 3
384
+ && /^[0-9a-f]{64}$/u.test(previous.diagnosticFingerprint), "document_graph_adaptive_attempt_invalid");
385
+ const allowed = [...source.allowedIds];
386
+ invariant(previous.allowedEvidenceUnitIds.length === allowed.length
387
+ && new Set(previous.allowedEvidenceUnitIds).size === allowed.length
388
+ && previous.allowedEvidenceUnitIds.every((id) => source.allowedIds.has(id)), "document_graph_adaptive_units_mismatch");
389
+ const reasons = [...new Set(previous.reasons)].sort();
390
+ invariant(reasons.length > 0 && reasons.length <= 64 && reasons.every((reason) => /^[A-Za-z][A-Za-z0-9_]{0,127}$/u.test(reason)), "document_graph_adaptive_reasons_invalid");
391
+ invariant(new Set(previous.validLocalNodeIds).size === previous.validLocalNodeIds.length
392
+ && previous.validLocalNodeIds.every((id) => LOCAL_ID.test(id)), "document_graph_adaptive_node_ids_invalid");
393
+ invariant((previous.segmentationIssues ?? []).every((issue) => [issue.expectedStartUnitId, issue.actualStartUnitId, issue.actualEndUnitId,
394
+ issue.affectedStartUnitId, issue.affectedEndUnitId].every((id) => id === null || id === undefined || source.allowedIds.has(id))), "document_graph_adaptive_segmentation_invalid");
395
+ const strategies = [
396
+ ...(reasons.some((reason) => reason.startsWith("provider_")) ? ["closed_schema_regeneration"] : []),
397
+ ...(reasons.some((reason) => reason === "edge_source_ref_missing" || reason === "edge_target_ref_missing" || reason === "edge_node_ref_invalid") ? ["endpoint_closure"] : []),
398
+ ...(reasons.some((reason) => reason.startsWith("statement_") || reason.startsWith("edge_predicate_") || reason.startsWith("edge_assertion_")
399
+ || reason.startsWith("edge_qualifiers_") || reason.startsWith("edge_evidence_") || reason === "edge_invalid") ? ["invalid_record_repair"] : []),
400
+ ...(reasons.some((reason) => reason.startsWith("segment_")) ? ["segment_coverage_regeneration"] : []),
401
+ ...(reasons.some((reason) => reason.startsWith("node_")) ? ["node_shape_repair"] : []),
402
+ ];
403
+ return {
404
+ attempt: previous.attempt + 1,
405
+ previousDiagnosticFingerprint: previous.diagnosticFingerprint,
406
+ repeatedDiagnosticFingerprint: previous.repeatedDiagnosticFingerprint,
407
+ reasons,
408
+ strategies: [...new Set(strategies)],
409
+ constraints: [
410
+ "只修复 diagnostics 指向的无效 records;schema 要求仍需返回完整对象。",
411
+ "previousValidLocalNodeIds 只说明上次通过结构校验的 local id,不是事实金标;所有输出仍须由本窗口 source units 独立支持。",
412
+ "edge 两端必须同时出现在本次输出 nodes;无法由当前 evidence 支持端点时删除该 edge,不得补造 node、alias、事实或反转方向。",
413
+ "字段无法修复时删除该无效单项;不得把 scalar、职业、类别、地点或关系提升为 alias。",
414
+ "不得改变、删减或扩展 core/context source units;segments 仍须精确完整覆盖 core。",
415
+ ],
416
+ legalLocalIdPattern: "^[a-z][a-z0-9_]{0,127}$",
417
+ previousValidLocalNodeIds: previous.validLocalNodeIds,
418
+ allowedEvidenceUnitIds: previous.allowedEvidenceUnitIds,
419
+ sourceSpans: previous.allowedEvidenceUnitIds.map((id) => {
420
+ const unit = source.unitById.get(id);
421
+ invariant(unit !== undefined, "document_graph_adaptive_units_mismatch");
422
+ return { unitId: id, startOffset: unit.startOffset, endOffset: unit.endOffset, core: source.coreIds.includes(id) };
423
+ }),
424
+ segmentationIssues: previous.segmentationIssues ?? [],
425
+ };
426
+ }
427
+ function promptPayload(input, source, diagnostics) {
428
+ const unit = (id) => {
429
+ const value = source.unitById.get(id);
430
+ return { id: value.id, kind: value.kind, text: value.text, ...(value.chapterId === undefined ? {} : { chapterId: value.chapterId }) };
431
+ };
432
+ // NFC 仅用于匹配;source units 与发给模型的原文均保持原字节语义,不做简繁推断。
433
+ const windowText = source.windowText.normalize("NFC");
434
+ const knownEntities = new Map();
435
+ for (const entity of input.knownEntities ?? []) {
436
+ const names = [entity.label, ...(entity.aliases ?? [])];
437
+ if (!names.some((name) => name.length > 0 && windowText.includes(name.normalize("NFC"))))
438
+ continue;
439
+ const hint = { kind: entity.kind, label: entity.label, aliases: [...(entity.aliases ?? [])].sort() };
440
+ const identity = JSON.stringify(hint);
441
+ if (!knownEntities.has(identity))
442
+ knownEntities.set(identity, hint);
443
+ }
444
+ diagnostics.catalogProvided = input.knownEntities?.length ?? 0;
445
+ diagnostics.catalogIncluded = knownEntities.size;
446
+ const adaptiveRepair = adaptiveRepairPayload(input, source);
447
+ return {
448
+ window: { id: input.window.id, chapterId: input.window.chapterId ?? null },
449
+ coreCoverage: { firstCoreUnitId: source.coreIds[0], lastCoreUnitId: source.coreIds.at(-1), coreUnitCount: source.coreIds.length,
450
+ coreUtf16: input.window.coreEndOffset - input.window.coreStartOffset,
451
+ singleSegmentRequired: input.window.coreEndOffset - input.window.coreStartOffset < 1_800 },
452
+ contextUnits: input.window.contextUnitIds.map(unit), coreUnits: input.window.coreUnitIds.map(unit),
453
+ knownEntities: [...knownEntities.values()],
454
+ ...(adaptiveRepair === undefined ? {} : { adaptiveRepair }),
455
+ };
456
+ }
457
+ function createDiagnostics(initialMaxOutputTokens) {
458
+ invariant(Number.isSafeInteger(initialMaxOutputTokens) && initialMaxOutputTokens >= 1
459
+ && initialMaxOutputTokens <= MAX_EXTRACTION_MAX_OUTPUT_TOKENS, "document_graph_output_budget_invalid");
460
+ return { modelRequests: 0, repairAttempts: 0, rejectedSegmentCount: 0, rejectedNodeCount: 0,
461
+ rejectedStatementCount: 0, rejectedEdgeCount: 0, rejectionReasons: {}, initialMaxOutputTokens,
462
+ maxOutputTokens: initialMaxOutputTokens, outputBudgetRetries: 0, catalogProvided: 0, catalogIncluded: 0 };
463
+ }
464
+ function prepareRequest(input, diagnostics) {
465
+ const source = sourceUnits(input);
466
+ const payload = promptPayload(input, source, diagnostics);
467
+ return Object.freeze({
468
+ schemaName: "document_graph_segment_v1",
469
+ jsonSchema: EXTRACTION_SCHEMA,
470
+ messages: Object.freeze([
471
+ Object.freeze({ role: "system", content: systemPrompt(input) }),
472
+ Object.freeze({ role: "user", content: JSON.stringify(payload) }),
473
+ ]),
474
+ taskMode: "extraction",
475
+ maxOutputTokens: diagnostics.maxOutputTokens,
476
+ });
477
+ }
478
+ /** 外部 harness 消费的纯请求构造器;只暴露原文 unit、同一闭合 schema 与可验证约束,不发起模型调用。 */
479
+ export function prepareDocumentGraphWindowRequest(input) {
480
+ return prepareRequest(input, createDiagnostics(input.maxOutputTokens ?? DEFAULT_EXTRACTION_MAX_OUTPUT_TOKENS));
481
+ }
482
+ /** 外部 harness 响应使用与 provider 路径相同的证据、分段、实体和关系 validator。 */
483
+ export function validateDocumentGraphWindowCandidate(input, candidate) {
484
+ const diagnostics = createDiagnostics(input.maxOutputTokens ?? DEFAULT_EXTRACTION_MAX_OUTPUT_TOKENS);
485
+ // 同时验证 adaptive repair identity,并为诊断冻结 catalog 数量。
486
+ prepareRequest(input, diagnostics);
487
+ const source = sourceUnits(input);
488
+ const refinedDiagnostics = input.methodVersion === "adaptive-repair-v3"
489
+ || input.methodVersion === "semantic-negation-v4" || input.methodVersion === "harness-v1";
490
+ const semanticNegation = input.methodVersion === "semantic-negation-v4" || input.methodVersion === "harness-v1";
491
+ const validated = validateCandidate(candidate, source, diagnostics, refinedDiagnostics, semanticNegation);
492
+ const partial = validated.issueCount > 0 || validated.segments === undefined;
493
+ return Object.freeze({
494
+ windowId: input.window.id,
495
+ status: partial ? "partial" : "complete",
496
+ segments: validated.segments ?? Object.freeze([]),
497
+ nodes: validated.nodes,
498
+ edges: validated.edges,
499
+ diagnostics: diagnosticsValue(diagnostics),
500
+ });
501
+ }
502
+ async function generate(input, messages, diagnostics) {
503
+ if (input.signal?.aborted)
504
+ throw new AuthoringModelConnectionError("aborted", false);
505
+ diagnostics.modelRequests += 1;
506
+ try {
507
+ const result = await input.modelConnection.structuredJson.generate({
508
+ schemaName: "document_graph_segment_v1", jsonSchema: EXTRACTION_SCHEMA, messages,
509
+ taskMode: "extraction", maxOutputTokens: diagnostics.maxOutputTokens, timeoutMs: 300_000,
510
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
511
+ });
512
+ if (result.framing === "single_json_fence")
513
+ diagnostics.jsonFenceUnwraps = (diagnostics.jsonFenceUnwraps ?? 0) + 1;
514
+ return result.value;
515
+ }
516
+ catch (error) {
517
+ if (!(error instanceof AuthoringModelConnectionError) || error.code !== "provider_output_truncated")
518
+ throw error;
519
+ diagnostics.rejectionReasons.provider_output_truncated = (diagnostics.rejectionReasons.provider_output_truncated ?? 0) + 1;
520
+ if (input.signal?.aborted)
521
+ throw new AuthoringModelConnectionError("aborted", false);
522
+ if (diagnostics.maxOutputTokens >= MAX_EXTRACTION_MAX_OUTPUT_TOKENS || diagnostics.outputBudgetRetries > 0) {
523
+ throw new DocumentGraphExtractionError("provider_output_truncated", diagnosticsValue(diagnostics));
524
+ }
525
+ // 全次窗口调用最多一次预算扩容;同一 messages/schema/model/source,不截删原文。
526
+ diagnostics.maxOutputTokens = MAX_EXTRACTION_MAX_OUTPUT_TOKENS;
527
+ diagnostics.outputBudgetRetries += 1;
528
+ return generate(input, messages, diagnostics);
529
+ }
530
+ }
531
+ /** D4F 分段、图抽取、证据回取、通用 predicate 归一化与一次 bounded repair 的唯一 Interface。 */
532
+ export async function extractDocumentGraphWindow(input) {
533
+ invariant(input.modelConnection.structuredJson.modelId === "deepseek-v4-flash", "document_graph_model_invalid");
534
+ const initialMaxOutputTokens = input.maxOutputTokens === undefined ? DEFAULT_EXTRACTION_MAX_OUTPUT_TOKENS : input.maxOutputTokens;
535
+ const diagnostics = createDiagnostics(initialMaxOutputTokens);
536
+ const source = sourceUnits(input);
537
+ const prepared = prepareRequest(input, diagnostics);
538
+ const payload = JSON.parse(prepared.messages[1].content);
539
+ const initialMessages = prepared.messages;
540
+ let candidate;
541
+ try {
542
+ candidate = await generate(input, initialMessages, diagnostics);
543
+ }
544
+ catch (error) {
545
+ if (!(error instanceof AuthoringModelConnectionError) || error.code !== "provider_response_invalid")
546
+ throw error;
547
+ const invalid = providerInvalidDetail(error);
548
+ if (error.validationIssues.length > 0)
549
+ diagnostics.validationIssues = error.validationIssues;
550
+ diagnostics.repairAttempts = 1;
551
+ diagnostics.rejectionReasons[invalid.reason] = 1;
552
+ try {
553
+ candidate = await generate(input, [...initialMessages, { role: "user", content: JSON.stringify({ repair: true, issue: invalid.repairIssue }) }], diagnostics);
554
+ }
555
+ catch (repairError) {
556
+ if (!(repairError instanceof AuthoringModelConnectionError) || repairError.code !== "provider_response_invalid")
557
+ throw repairError;
558
+ const repairInvalid = providerInvalidDetail(repairError);
559
+ if (repairError.validationIssues.length > 0)
560
+ diagnostics.validationIssues = repairError.validationIssues;
561
+ diagnostics.rejectionReasons[repairInvalid.reason] = (diagnostics.rejectionReasons[repairInvalid.reason] ?? 0) + 1;
562
+ throw new DocumentGraphExtractionError("document_graph_provider_output_invalid", diagnosticsValue(diagnostics));
563
+ }
564
+ }
565
+ const refinedDiagnostics = input.methodVersion === "adaptive-repair-v3" || input.methodVersion === "semantic-negation-v4"
566
+ || input.methodVersion === "harness-v1";
567
+ const semanticNegation = input.methodVersion === "semantic-negation-v4" || input.methodVersion === "harness-v1";
568
+ let validated = validateCandidate(candidate, source, diagnostics, refinedDiagnostics, semanticNegation);
569
+ if (validated.issueCount > 0 && diagnostics.repairAttempts === 0) {
570
+ diagnostics.repairAttempts = 1;
571
+ const issues = Object.keys(diagnostics.rejectionReasons).sort();
572
+ candidate = await generate(input, [
573
+ { role: "system", content: systemPrompt(input) },
574
+ { role: "user", content: JSON.stringify(payload) },
575
+ { role: "user", content: JSON.stringify({ repair: true, issues,
576
+ ...(diagnostics.segmentationIssues === undefined ? {} : { coverage: { ...record(payload.coreCoverage), issues: diagnostics.segmentationIssues } }),
577
+ invalidCandidate: candidate }) },
578
+ ], diagnostics);
579
+ validated = validateCandidate(candidate, source, diagnostics, refinedDiagnostics, semanticNegation);
580
+ }
581
+ if (!validated.segments)
582
+ throw new DocumentGraphExtractionError("document_graph_segmentation_invalid", diagnosticsValue(diagnostics));
583
+ const partial = validated.issueCount > 0;
584
+ return Object.freeze({
585
+ windowId: input.window.id, status: partial ? "partial" : "complete", segments: validated.segments,
586
+ nodes: validated.nodes, edges: validated.edges, diagnostics: diagnosticsValue(diagnostics),
587
+ });
588
+ }
@@ -0,0 +1,68 @@
1
+ type JsonValue = null | boolean | number | string | JsonValue[] | {
2
+ [key: string]: JsonValue;
3
+ };
4
+ type JournalPhase = "extracting" | "ready_to_commit" | "committing" | "committed";
5
+ export type DocumentIngestionJournalErrorCode = "invalid_input" | "busy" | "identity_mismatch" | "corrupt" | "invalid_transition" | "lease_uncertain";
6
+ export declare class DocumentIngestionJournalError extends Error {
7
+ readonly code: DocumentIngestionJournalErrorCode;
8
+ readonly name = "DocumentIngestionJournalError";
9
+ constructor(code: DocumentIngestionJournalErrorCode);
10
+ }
11
+ export interface DocumentIngestionJournalIdentity {
12
+ readonly operationId: string;
13
+ readonly sourceByteSha256: string;
14
+ readonly planSha256: string;
15
+ readonly modelId: string;
16
+ readonly expectedProjectRevision: number;
17
+ /** 旧 manifest 没有该字段;缺省必须按 legacy-v1 恢复,不能就地升级方法。 */
18
+ readonly method?: DocumentIngestionJournalMethodIdentity;
19
+ }
20
+ export interface DocumentIngestionJournalMethodIdentity {
21
+ readonly version: string;
22
+ readonly sha256: string;
23
+ readonly promptVersion: string;
24
+ readonly schemaVersion: string;
25
+ readonly mergeVersion: string;
26
+ readonly embeddingPolicy: string;
27
+ readonly validatorDiagnosticsVersion?: string;
28
+ readonly repairPolicyVersion?: string;
29
+ }
30
+ export interface DocumentIngestionJournalStatus extends DocumentIngestionJournalIdentity {
31
+ readonly phase: JournalPhase;
32
+ readonly completedSegmentIds: readonly string[];
33
+ readonly failedSegments: readonly Readonly<{
34
+ id: string;
35
+ attempts: number;
36
+ code: string;
37
+ retryable: boolean;
38
+ }>[];
39
+ readonly commitReceipt: JsonValue | null;
40
+ }
41
+ export interface DocumentIngestionJournal {
42
+ getStatus(): Promise<DocumentIngestionJournalStatus>;
43
+ readSegment(id: string): Promise<JsonValue | null>;
44
+ writeSegment(id: string, payload: JsonValue): Promise<Readonly<{
45
+ payloadSha256: string;
46
+ }>>;
47
+ clearSegmentFailure(id: string): Promise<void>;
48
+ recordSegmentFailure(id: string, failure: Readonly<{
49
+ code: string;
50
+ retryable: boolean;
51
+ attempts?: number;
52
+ }>): Promise<void>;
53
+ markPhase(phase: Exclude<JournalPhase, "committed">): Promise<void>;
54
+ recordCommitReceipt(receipt: JsonValue): Promise<void>;
55
+ close(): Promise<void>;
56
+ }
57
+ /** 只读 operational 状态;不获取 writer lease,也不修复或改写 manifest。 */
58
+ export declare function readDocumentIngestionJournalStatus(projectRoot: string, operationId: string): Promise<DocumentIngestionJournalStatus>;
59
+ export declare function openDocumentIngestionJournal(options: Readonly<{
60
+ projectRoot: string;
61
+ operationId: string;
62
+ sourceByteSha256: string;
63
+ planSha256: string;
64
+ modelId: string;
65
+ expectedProjectRevision: number;
66
+ method?: DocumentIngestionJournalMethodIdentity;
67
+ }>): Promise<DocumentIngestionJournal>;
68
+ export {};