@actalk/inkos-core 1.5.0 → 1.6.1
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/dist/agent/agent-session.d.ts +4 -0
- package/dist/agent/agent-session.d.ts.map +1 -1
- package/dist/agent/agent-session.js +78 -5
- package/dist/agent/agent-session.js.map +1 -1
- package/dist/agent/agent-system-prompt.d.ts +2 -0
- package/dist/agent/agent-system-prompt.d.ts.map +1 -1
- package/dist/agent/agent-system-prompt.js +181 -16
- package/dist/agent/agent-system-prompt.js.map +1 -1
- package/dist/agent/agent-tools.d.ts +97 -1
- package/dist/agent/agent-tools.d.ts.map +1 -1
- package/dist/agent/agent-tools.js +434 -3
- package/dist/agent/agent-tools.js.map +1 -1
- package/dist/agent/film-authoring-tools.d.ts +89 -0
- package/dist/agent/film-authoring-tools.d.ts.map +1 -0
- package/dist/agent/film-authoring-tools.js +336 -0
- package/dist/agent/film-authoring-tools.js.map +1 -0
- package/dist/agent/index.d.ts +2 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +2 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/llm-stub.d.ts +19 -0
- package/dist/agent/llm-stub.d.ts.map +1 -0
- package/dist/agent/llm-stub.js +134 -0
- package/dist/agent/llm-stub.js.map +1 -0
- package/dist/agents/composer.d.ts.map +1 -1
- package/dist/agents/composer.js +24 -12
- package/dist/agents/composer.js.map +1 -1
- package/dist/agents/researcher.d.ts +38 -0
- package/dist/agents/researcher.d.ts.map +1 -0
- package/dist/agents/researcher.js +164 -0
- package/dist/agents/researcher.js.map +1 -0
- package/dist/agents/script-storyboard.d.ts +51 -0
- package/dist/agents/script-storyboard.d.ts.map +1 -0
- package/dist/agents/script-storyboard.js +368 -0
- package/dist/agents/script-storyboard.js.map +1 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/interaction/action-envelope.d.ts +669 -1
- package/dist/interaction/action-envelope.d.ts.map +1 -1
- package/dist/interaction/action-envelope.js +87 -0
- package/dist/interaction/action-envelope.js.map +1 -1
- package/dist/interaction/edit-controller.d.ts.map +1 -1
- package/dist/interaction/edit-controller.js +91 -1
- package/dist/interaction/edit-controller.js.map +1 -1
- package/dist/interaction/intents.d.ts +2 -2
- package/dist/interaction/project-control.d.ts +1 -1
- package/dist/interaction/session-transcript-schema.d.ts +22 -22
- package/dist/interaction/session.d.ts +14 -14
- package/dist/interaction/session.d.ts.map +1 -1
- package/dist/interaction/session.js +1 -1
- package/dist/interaction/session.js.map +1 -1
- package/dist/interactive-film/authoring-generate.d.ts +4 -0
- package/dist/interactive-film/authoring-generate.d.ts.map +1 -0
- package/dist/interactive-film/authoring-generate.js +16 -0
- package/dist/interactive-film/authoring-generate.js.map +1 -0
- package/dist/interactive-film/authoring-store.d.ts +35 -0
- package/dist/interactive-film/authoring-store.d.ts.map +1 -0
- package/dist/interactive-film/authoring-store.js +114 -0
- package/dist/interactive-film/authoring-store.js.map +1 -0
- package/dist/interactive-film/authoring-tools.d.ts +9 -0
- package/dist/interactive-film/authoring-tools.d.ts.map +1 -0
- package/dist/interactive-film/authoring-tools.js +19 -0
- package/dist/interactive-film/authoring-tools.js.map +1 -0
- package/dist/interactive-film/delta.d.ts +591 -0
- package/dist/interactive-film/delta.d.ts.map +1 -0
- package/dist/interactive-film/delta.js +54 -0
- package/dist/interactive-film/delta.js.map +1 -0
- package/dist/interactive-film/emotion.d.ts +20 -0
- package/dist/interactive-film/emotion.d.ts.map +1 -0
- package/dist/interactive-film/emotion.js +57 -0
- package/dist/interactive-film/emotion.js.map +1 -0
- package/dist/interactive-film/evaluator.d.ts +7 -0
- package/dist/interactive-film/evaluator.d.ts.map +1 -0
- package/dist/interactive-film/evaluator.js +40 -0
- package/dist/interactive-film/evaluator.js.map +1 -0
- package/dist/interactive-film/export-html.d.ts +6 -0
- package/dist/interactive-film/export-html.d.ts.map +1 -0
- package/dist/interactive-film/export-html.js +84 -0
- package/dist/interactive-film/export-html.js.map +1 -0
- package/dist/interactive-film/export-ink.d.ts +3 -0
- package/dist/interactive-film/export-ink.d.ts.map +1 -0
- package/dist/interactive-film/export-ink.js +51 -0
- package/dist/interactive-film/export-ink.js.map +1 -0
- package/dist/interactive-film/film-context.d.ts +4 -0
- package/dist/interactive-film/film-context.d.ts.map +1 -0
- package/dist/interactive-film/film-context.js +30 -0
- package/dist/interactive-film/film-context.js.map +1 -0
- package/dist/interactive-film/generate.d.ts +13 -0
- package/dist/interactive-film/generate.d.ts.map +1 -0
- package/dist/interactive-film/generate.js +32 -0
- package/dist/interactive-film/generate.js.map +1 -0
- package/dist/interactive-film/graph-schema.d.ts +819 -0
- package/dist/interactive-film/graph-schema.d.ts.map +1 -0
- package/dist/interactive-film/graph-schema.js +84 -0
- package/dist/interactive-film/graph-schema.js.map +1 -0
- package/dist/interactive-film/graph-store.d.ts +5 -0
- package/dist/interactive-film/graph-store.d.ts.map +1 -0
- package/dist/interactive-film/graph-store.js +24 -0
- package/dist/interactive-film/graph-store.js.map +1 -0
- package/dist/interactive-film/memory-link.d.ts +9 -0
- package/dist/interactive-film/memory-link.d.ts.map +1 -0
- package/dist/interactive-film/memory-link.js +15 -0
- package/dist/interactive-film/memory-link.js.map +1 -0
- package/dist/interactive-film/node-image.d.ts +29 -0
- package/dist/interactive-film/node-image.d.ts.map +1 -0
- package/dist/interactive-film/node-image.js +52 -0
- package/dist/interactive-film/node-image.js.map +1 -0
- package/dist/interactive-film/paths.d.ts +14 -0
- package/dist/interactive-film/paths.d.ts.map +1 -0
- package/dist/interactive-film/paths.js +60 -0
- package/dist/interactive-film/paths.js.map +1 -0
- package/dist/interactive-film/validation.d.ts +14 -0
- package/dist/interactive-film/validation.d.ts.map +1 -0
- package/dist/interactive-film/validation.js +292 -0
- package/dist/interactive-film/validation.js.map +1 -0
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +53 -16
- package/dist/llm/provider.js.map +1 -1
- package/dist/models/book.d.ts +13 -0
- package/dist/models/book.d.ts.map +1 -1
- package/dist/models/book.js +3 -0
- package/dist/models/book.js.map +1 -1
- package/dist/models/input-governance.d.ts +14 -5
- package/dist/models/input-governance.d.ts.map +1 -1
- package/dist/models/input-governance.js +3 -0
- package/dist/models/input-governance.js.map +1 -1
- package/dist/models/project.d.ts +63 -10
- package/dist/models/project.d.ts.map +1 -1
- package/dist/models/project.js +11 -0
- package/dist/models/project.js.map +1 -1
- package/dist/pipeline/runner.js +1 -1
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pipeline/script-storyboard-runner.d.ts +127 -0
- package/dist/pipeline/script-storyboard-runner.d.ts.map +1 -0
- package/dist/pipeline/script-storyboard-runner.js +446 -0
- package/dist/pipeline/script-storyboard-runner.js.map +1 -0
- package/dist/play/play-agents.d.ts.map +1 -1
- package/dist/play/play-agents.js +5 -2
- package/dist/play/play-agents.js.map +1 -1
- package/dist/play/play-store.d.ts +2 -2
- package/dist/skills/builtin-prompts.d.ts +10 -0
- package/dist/skills/builtin-prompts.d.ts.map +1 -0
- package/dist/skills/builtin-prompts.js +152 -0
- package/dist/skills/builtin-prompts.js.map +1 -0
- package/dist/skills/builtin.d.ts +3 -0
- package/dist/skills/builtin.d.ts.map +1 -0
- package/dist/skills/builtin.js +252 -0
- package/dist/skills/builtin.js.map +1 -0
- package/dist/skills/context-planner.d.ts +17 -0
- package/dist/skills/context-planner.d.ts.map +1 -0
- package/dist/skills/context-planner.js +48 -0
- package/dist/skills/context-planner.js.map +1 -0
- package/dist/skills/external-loader.d.ts +20 -0
- package/dist/skills/external-loader.d.ts.map +1 -0
- package/dist/skills/external-loader.js +126 -0
- package/dist/skills/external-loader.js.map +1 -0
- package/dist/skills/index.d.ts +8 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +8 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/prompt-pack.d.ts +33 -0
- package/dist/skills/prompt-pack.d.ts.map +1 -0
- package/dist/skills/prompt-pack.js +82 -0
- package/dist/skills/prompt-pack.js.map +1 -0
- package/dist/skills/registry.d.ts +6 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/registry.js +101 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/skills/types.d.ts +146 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +32 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/state/manager.d.ts +8 -2
- package/dist/state/manager.d.ts.map +1 -1
- package/dist/state/manager.js +58 -5
- package/dist/state/manager.js.map +1 -1
- package/dist/utils/context-assembly.d.ts +3 -0
- package/dist/utils/context-assembly.d.ts.map +1 -1
- package/dist/utils/context-assembly.js +3 -0
- package/dist/utils/context-assembly.js.map +1 -1
- package/dist/utils/web-search.d.ts +6 -1
- package/dist/utils/web-search.d.ts.map +1 -1
- package/dist/utils/web-search.js +8 -4
- package/dist/utils/web-search.js.map +1 -1
- package/package.json +14 -3
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { enumerateRuntimePaths } from "./paths.js";
|
|
2
|
+
function label(node) {
|
|
3
|
+
return node.title || node.id;
|
|
4
|
+
}
|
|
5
|
+
export function validateStoryGraph(graph) {
|
|
6
|
+
const issues = [];
|
|
7
|
+
const ids = new Set(graph.nodes.map((n) => n.id));
|
|
8
|
+
const nodeMap = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
9
|
+
// BROKEN_LINK
|
|
10
|
+
for (const node of graph.nodes) {
|
|
11
|
+
for (const c of node.choices) {
|
|
12
|
+
if (!ids.has(c.targetNodeId)) {
|
|
13
|
+
issues.push({
|
|
14
|
+
code: "BROKEN_LINK",
|
|
15
|
+
level: "error",
|
|
16
|
+
message: `节点「${label(node)}」的选项「${c.text}」指向不存在的节点 ${c.targetNodeId}`,
|
|
17
|
+
nodeIds: [node.id],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// DEAD_END:非结局节点没有任何指向存在节点的出口
|
|
23
|
+
for (const node of graph.nodes) {
|
|
24
|
+
if (node.type === "ending")
|
|
25
|
+
continue;
|
|
26
|
+
const hasExit = node.choices.some((c) => ids.has(c.targetNodeId));
|
|
27
|
+
if (!hasExit) {
|
|
28
|
+
issues.push({
|
|
29
|
+
code: "DEAD_END",
|
|
30
|
+
level: "error",
|
|
31
|
+
message: `节点「${label(node)}」是死路:没有任何有效出口`,
|
|
32
|
+
nodeIds: [node.id],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// 可达性 BFS
|
|
37
|
+
const start = graph.nodes.find((n) => n.type === "start") ?? graph.nodes[0];
|
|
38
|
+
const reachable = new Set();
|
|
39
|
+
if (start) {
|
|
40
|
+
const queue = [start.id];
|
|
41
|
+
while (queue.length > 0) {
|
|
42
|
+
const cur = queue.shift();
|
|
43
|
+
if (reachable.has(cur))
|
|
44
|
+
continue;
|
|
45
|
+
reachable.add(cur);
|
|
46
|
+
const n = nodeMap.get(cur);
|
|
47
|
+
if (!n)
|
|
48
|
+
continue;
|
|
49
|
+
for (const c of n.choices) {
|
|
50
|
+
if (ids.has(c.targetNodeId) && !reachable.has(c.targetNodeId))
|
|
51
|
+
queue.push(c.targetNodeId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// UNREACHABLE
|
|
56
|
+
for (const node of graph.nodes) {
|
|
57
|
+
if (graph.nodes.length > 1 && !reachable.has(node.id)) {
|
|
58
|
+
issues.push({
|
|
59
|
+
code: "UNREACHABLE",
|
|
60
|
+
level: "warning",
|
|
61
|
+
message: `节点「${label(node)}」从开场无法到达`,
|
|
62
|
+
nodeIds: [node.id],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// NO_PATH_TO_ENDING
|
|
67
|
+
if (start) {
|
|
68
|
+
let canEnd = false;
|
|
69
|
+
for (const id of reachable) {
|
|
70
|
+
if (nodeMap.get(id)?.type === "ending") {
|
|
71
|
+
canEnd = true;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!canEnd) {
|
|
76
|
+
issues.push({
|
|
77
|
+
code: "NO_PATH_TO_ENDING",
|
|
78
|
+
level: "error",
|
|
79
|
+
message: `从开场节点「${label(start)}」出发无法到达任何结局`,
|
|
80
|
+
nodeIds: [start.id],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { ok: issues.every((i) => i.level !== "error"), issues };
|
|
85
|
+
}
|
|
86
|
+
export function reviewStoryGraph(graph) {
|
|
87
|
+
const issues = [...validateStoryGraph(graph).issues];
|
|
88
|
+
const reads = new Set();
|
|
89
|
+
const writes = new Set();
|
|
90
|
+
for (const node of graph.nodes) {
|
|
91
|
+
for (const choice of node.choices) {
|
|
92
|
+
if (choice.condition)
|
|
93
|
+
reads.add(choice.condition.var);
|
|
94
|
+
for (const effect of choice.effects)
|
|
95
|
+
writes.add(effect.var);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const v of reads) {
|
|
99
|
+
if (!writes.has(v)) {
|
|
100
|
+
issues.push({
|
|
101
|
+
code: "VARIABLE_UNWRITTEN",
|
|
102
|
+
level: "warning",
|
|
103
|
+
message: `变量「${v}」被选项条件读取,但没有任何选项写入它——该条件门除默认值外永远不会改变`,
|
|
104
|
+
nodeIds: [],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const v of graph.variables.map((vv) => vv.name)) {
|
|
109
|
+
if (!reads.has(v) && !writes.has(v)) {
|
|
110
|
+
issues.push({
|
|
111
|
+
code: "VARIABLE_UNUSED",
|
|
112
|
+
level: "info",
|
|
113
|
+
message: `变量「${v}」声明了但没有任何选项写入、也没有任何条件读取它——这是个多余的声明`,
|
|
114
|
+
nodeIds: [],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (graph.endings.length >= 2) {
|
|
119
|
+
const types = new Set(graph.endings.map((e) => e.type));
|
|
120
|
+
if (types.size === 1) {
|
|
121
|
+
issues.push({
|
|
122
|
+
code: "ENDING_VARIETY",
|
|
123
|
+
level: "info",
|
|
124
|
+
message: `${graph.endings.length} 个结局都是同一类型(${[...types][0]}),重玩价值低——考虑设计不同基调的结局`,
|
|
125
|
+
nodeIds: graph.endings.map((e) => e.nodeId),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const node of graph.nodes) {
|
|
130
|
+
if (node.type !== "ending" && !node.imageSlot?.assetRef) {
|
|
131
|
+
issues.push({
|
|
132
|
+
code: "IMAGE_MISSING",
|
|
133
|
+
level: "info",
|
|
134
|
+
message: `节点「${node.title || node.id}」还没有配图`,
|
|
135
|
+
nodeIds: [node.id],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// --- P6 new rules ---
|
|
140
|
+
const { paths, truncated } = enumerateRuntimePaths(graph);
|
|
141
|
+
const reachedNodeIds = new Set();
|
|
142
|
+
for (const p of paths) {
|
|
143
|
+
for (const id of p.nodeIds)
|
|
144
|
+
reachedNodeIds.add(id);
|
|
145
|
+
}
|
|
146
|
+
const edgeReachable = computeEdgeReachable(graph);
|
|
147
|
+
// GATED_UNREACHABLE and ENDING_UNREACHABLE rely on a complete path enumeration.
|
|
148
|
+
// When enumeration is truncated (>200 paths), the reached-node set is incomplete,
|
|
149
|
+
// so asserting unreachability would be unsound. Skip both loops in that case.
|
|
150
|
+
if (!truncated) {
|
|
151
|
+
// GATED_UNREACHABLE: edge-reachable but not runtime-reachable (endings are reported by ENDING_UNREACHABLE)
|
|
152
|
+
for (const node of graph.nodes) {
|
|
153
|
+
if (node.type === "start")
|
|
154
|
+
continue;
|
|
155
|
+
if (node.type === "ending")
|
|
156
|
+
continue;
|
|
157
|
+
if (edgeReachable.has(node.id) && !reachedNodeIds.has(node.id)) {
|
|
158
|
+
issues.push({
|
|
159
|
+
code: "GATED_UNREACHABLE",
|
|
160
|
+
level: "warning",
|
|
161
|
+
message: `节点「${node.title || node.id}」连边可达,但没有任何满足变量条件的路径能到达——它被一个永远不成立的条件挡住了`,
|
|
162
|
+
nodeIds: [node.id],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// ENDING_UNREACHABLE: check by ending.nodeId presence in reached nodes (handles multiple endings sharing the same node)
|
|
167
|
+
for (const ending of graph.endings) {
|
|
168
|
+
if (!reachedNodeIds.has(ending.nodeId)) {
|
|
169
|
+
issues.push({
|
|
170
|
+
code: "ENDING_UNREACHABLE",
|
|
171
|
+
level: "warning",
|
|
172
|
+
message: `结局「${ending.title}」没有任何真实路径能到达`,
|
|
173
|
+
nodeIds: [ending.nodeId],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// LINEAR_GRAPH: only meaningful when there are intermediate normal nodes (a pure start→ending stub is not a drama)
|
|
179
|
+
const hasBranch = graph.nodes.some((n) => n.choices.length >= 2);
|
|
180
|
+
const hasNormalNode = graph.nodes.some((n) => n.type === "normal");
|
|
181
|
+
if (graph.nodes.some((n) => n.type === "start") && graph.endings.length > 0 && hasNormalNode && !hasBranch) {
|
|
182
|
+
issues.push({
|
|
183
|
+
code: "LINEAR_GRAPH",
|
|
184
|
+
level: "info",
|
|
185
|
+
message: `整个故事没有任何分叉选择——更像线性剧本而非互动影游,考虑加入分支`,
|
|
186
|
+
nodeIds: [],
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// ISOLATED_NODE (endings are reported by ENDING_UNREACHABLE, not as generic isolated nodes)
|
|
190
|
+
// Skip nodes that already carry an UNREACHABLE issue from validateStoryGraph — that report
|
|
191
|
+
// is more informative (BFS-based, not just topology), so the duplicate is noise.
|
|
192
|
+
const incoming = new Set();
|
|
193
|
+
for (const n of graph.nodes)
|
|
194
|
+
for (const c of n.choices)
|
|
195
|
+
incoming.add(c.targetNodeId);
|
|
196
|
+
for (const node of graph.nodes) {
|
|
197
|
+
if (node.type !== "start" && node.type !== "ending" && !incoming.has(node.id)) {
|
|
198
|
+
if (issues.some((i) => i.code === "UNREACHABLE" && i.nodeIds.includes(node.id)))
|
|
199
|
+
continue;
|
|
200
|
+
issues.push({
|
|
201
|
+
code: "ISOLATED_NODE",
|
|
202
|
+
level: "info",
|
|
203
|
+
message: `节点「${node.title || node.id}」没有任何选项指向它——孤立节点`,
|
|
204
|
+
nodeIds: [node.id],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ILLUSORY_BRANCH: a branch where all choices share the same target and have no effects
|
|
209
|
+
for (const node of graph.nodes) {
|
|
210
|
+
if (node.choices.length >= 2) {
|
|
211
|
+
const targets = new Set(node.choices.map((c) => c.targetNodeId));
|
|
212
|
+
const allNoEffect = node.choices.every((c) => (c.effects?.length ?? 0) === 0);
|
|
213
|
+
if (targets.size === 1 && allNoEffect) {
|
|
214
|
+
issues.push({
|
|
215
|
+
code: "ILLUSORY_BRANCH",
|
|
216
|
+
level: "info",
|
|
217
|
+
message: `节点「${node.title || node.id}」的所有选项都通向同一个节点且没有不同效果——这是个假分支`,
|
|
218
|
+
nodeIds: [node.id],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// LONG_LINEAR_CHAIN: >=5 consecutive single-choice normal nodes
|
|
224
|
+
const CHAIN_THRESHOLD = 5;
|
|
225
|
+
const longChainHeads = findLongLinearChainHeads(graph, CHAIN_THRESHOLD);
|
|
226
|
+
for (const id of longChainHeads) {
|
|
227
|
+
issues.push({
|
|
228
|
+
code: "LONG_LINEAR_CHAIN",
|
|
229
|
+
level: "info",
|
|
230
|
+
message: `从节点「${id}」开始有一段较长的无分支直链(≥${CHAIN_THRESHOLD} 个单选项节点)——节奏可能偏拖,考虑插入分支或事件`,
|
|
231
|
+
nodeIds: [id],
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return { ok: issues.every((i) => i.level !== "error"), issues };
|
|
235
|
+
}
|
|
236
|
+
function computeEdgeReachable(graph) {
|
|
237
|
+
const start = graph.nodes.find((n) => n.type === "start");
|
|
238
|
+
const reachable = new Set();
|
|
239
|
+
if (!start)
|
|
240
|
+
return reachable;
|
|
241
|
+
const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
242
|
+
const queue = [start.id];
|
|
243
|
+
while (queue.length > 0) {
|
|
244
|
+
const cur = queue.shift();
|
|
245
|
+
if (reachable.has(cur))
|
|
246
|
+
continue;
|
|
247
|
+
reachable.add(cur);
|
|
248
|
+
const node = nodeById.get(cur);
|
|
249
|
+
if (!node)
|
|
250
|
+
continue;
|
|
251
|
+
for (const choice of node.choices) {
|
|
252
|
+
if (!reachable.has(choice.targetNodeId))
|
|
253
|
+
queue.push(choice.targetNodeId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return reachable;
|
|
257
|
+
}
|
|
258
|
+
function findLongLinearChainHeads(graph, threshold) {
|
|
259
|
+
const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
260
|
+
const isChainNode = (id) => {
|
|
261
|
+
const n = nodeById.get(id);
|
|
262
|
+
return n !== undefined && n.type === "normal" && n.choices.length === 1;
|
|
263
|
+
};
|
|
264
|
+
const incomingIsChainNode = new Map();
|
|
265
|
+
for (const n of graph.nodes) {
|
|
266
|
+
for (const c of n.choices) {
|
|
267
|
+
if (isChainNode(n.id)) {
|
|
268
|
+
incomingIsChainNode.set(c.targetNodeId, true);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const heads = [];
|
|
273
|
+
for (const node of graph.nodes) {
|
|
274
|
+
if (!isChainNode(node.id))
|
|
275
|
+
continue;
|
|
276
|
+
if (incomingIsChainNode.get(node.id))
|
|
277
|
+
continue;
|
|
278
|
+
let length = 0;
|
|
279
|
+
let cur = node.id;
|
|
280
|
+
const visited = new Set();
|
|
281
|
+
while (cur !== undefined && isChainNode(cur) && !visited.has(cur)) {
|
|
282
|
+
visited.add(cur);
|
|
283
|
+
length++;
|
|
284
|
+
const chainNode = nodeById.get(cur);
|
|
285
|
+
cur = chainNode.choices[0].targetNodeId;
|
|
286
|
+
}
|
|
287
|
+
if (length >= threshold)
|
|
288
|
+
heads.push(node.id);
|
|
289
|
+
}
|
|
290
|
+
return heads;
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/interactive-film/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/llm/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKtD,OAAO,KAAK,EACV,GAAG,IAAI,KAAK,EACZ,KAAK,IAAI,OAAO,EAChB,OAAO,IAAI,SAAS,EAErB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/llm/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/llm/provider.js
CHANGED
|
@@ -4,6 +4,7 @@ import { getEndpoint } from "./providers/index.js";
|
|
|
4
4
|
import { lookupModel } from "./providers/lookup.js";
|
|
5
5
|
import { fetchWithProxy } from "../utils/proxy-fetch.js";
|
|
6
6
|
import { isApiKeyOptionalForEndpoint } from "../utils/llm-endpoint-auth.js";
|
|
7
|
+
import { isLlmStubEnabled, stubChatCompletion } from "../agent/llm-stub.js";
|
|
7
8
|
const INKOS_USER_AGENT = "InkOS/1.3.5";
|
|
8
9
|
const UNKNOWN_MODEL_FALLBACK_MAX_TOKENS = 8192 * 3;
|
|
9
10
|
const TRANSIENT_LLM_RETRIES = 2;
|
|
@@ -170,7 +171,10 @@ function parseEnvHeaders() {
|
|
|
170
171
|
}
|
|
171
172
|
return undefined;
|
|
172
173
|
}
|
|
173
|
-
// === Partial Response
|
|
174
|
+
// === Partial Response(流式生成中途被掐断)===
|
|
175
|
+
// 语义:内容不完整、不可信。由 withTransientLLMRetry 整体重新生成;
|
|
176
|
+
// 重试耗尽后如实抛错。绝不把半截内容当成功返回(那会产出写到一半就
|
|
177
|
+
// 结束的章节/设定文件)。partialContent 仅用于错误诊断。
|
|
174
178
|
export class PartialResponseError extends Error {
|
|
175
179
|
partialContent;
|
|
176
180
|
constructor(partialContent, cause) {
|
|
@@ -179,8 +183,6 @@ export class PartialResponseError extends Error {
|
|
|
179
183
|
this.partialContent = partialContent;
|
|
180
184
|
}
|
|
181
185
|
}
|
|
182
|
-
/** Minimum chars to consider a partial response salvageable (Chinese ~2 chars/word → 500 chars ≈ 250 words) */
|
|
183
|
-
const MIN_SALVAGEABLE_CHARS = 500;
|
|
184
186
|
export class ContextWindowExceededError extends Error {
|
|
185
187
|
estimatedInputTokens;
|
|
186
188
|
reservedOutputTokens;
|
|
@@ -461,7 +463,11 @@ export function isTransientLLMHttpError(error) {
|
|
|
461
463
|
return statusHit || phraseHit;
|
|
462
464
|
}
|
|
463
465
|
function isRetryableLLMError(error) {
|
|
464
|
-
|
|
466
|
+
// PartialResponseError = 流在生成中途被掐断(网关切长连接等)。重试会完整
|
|
467
|
+
// 重新生成一次,比把半截内容当成功交付(截断的章节/设定文件)要正确。
|
|
468
|
+
return error instanceof PartialResponseError
|
|
469
|
+
|| isTransientLLMTransportError(error)
|
|
470
|
+
|| isTransientLLMHttpError(error);
|
|
465
471
|
}
|
|
466
472
|
async function withTransientLLMRetry(run, options) {
|
|
467
473
|
const enabled = options?.enabled ?? true;
|
|
@@ -474,7 +480,6 @@ async function withTransientLLMRetry(run, options) {
|
|
|
474
480
|
lastError = error;
|
|
475
481
|
if (!enabled
|
|
476
482
|
|| attempt >= TRANSIENT_LLM_RETRIES
|
|
477
|
-
|| error instanceof PartialResponseError
|
|
478
483
|
|| !isRetryableLLMError(error)) {
|
|
479
484
|
throw error;
|
|
480
485
|
}
|
|
@@ -719,6 +724,7 @@ async function chatCompletionViaCustomAnthropicCompatible(client, model, message
|
|
|
719
724
|
let buffer = "";
|
|
720
725
|
let content = "";
|
|
721
726
|
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
727
|
+
let sawMessageStop = false;
|
|
722
728
|
const monitor = createStreamMonitor(onStreamProgress);
|
|
723
729
|
try {
|
|
724
730
|
while (true) {
|
|
@@ -744,6 +750,7 @@ async function chatCompletionViaCustomAnthropicCompatible(client, model, message
|
|
|
744
750
|
usage.completionTokens = json.usage.output_tokens ?? usage.completionTokens;
|
|
745
751
|
}
|
|
746
752
|
if (json.type === "message_stop") {
|
|
753
|
+
sawMessageStop = true;
|
|
747
754
|
usage.totalTokens = usage.promptTokens + usage.completionTokens;
|
|
748
755
|
}
|
|
749
756
|
}
|
|
@@ -755,6 +762,10 @@ async function chatCompletionViaCustomAnthropicCompatible(client, model, message
|
|
|
755
762
|
if (!content) {
|
|
756
763
|
throw wrapLLMError(new Error("LLM returned empty response from stream"), errorCtx);
|
|
757
764
|
}
|
|
765
|
+
if (!sawMessageStop) {
|
|
766
|
+
// Anthropic 协议的正常结束必须有 message_stop;没有就是流被中途掐断
|
|
767
|
+
throw new PartialResponseError(content, new Error("stream closed without message_stop"));
|
|
768
|
+
}
|
|
758
769
|
if (!usage.totalTokens) {
|
|
759
770
|
usage.totalTokens = usage.promptTokens + usage.completionTokens;
|
|
760
771
|
}
|
|
@@ -811,6 +822,7 @@ async function chatCompletionViaCustomOpenAICompatible(client, model, messages,
|
|
|
811
822
|
let buffer = "";
|
|
812
823
|
let content = "";
|
|
813
824
|
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
825
|
+
let sawResponseTerminal = false;
|
|
814
826
|
const monitor = createStreamMonitor(onStreamProgress);
|
|
815
827
|
try {
|
|
816
828
|
while (true) {
|
|
@@ -829,7 +841,8 @@ async function chatCompletionViaCustomOpenAICompatible(client, model, messages,
|
|
|
829
841
|
monitor.onChunk(json.delta);
|
|
830
842
|
onTextDelta?.(json.delta);
|
|
831
843
|
}
|
|
832
|
-
if (json.type === "response.completed") {
|
|
844
|
+
if (json.type === "response.completed" || json.type === "response.incomplete") {
|
|
845
|
+
sawResponseTerminal = true;
|
|
833
846
|
usage = {
|
|
834
847
|
promptTokens: json.response?.usage?.input_tokens ?? 0,
|
|
835
848
|
completionTokens: json.response?.usage?.output_tokens ?? 0,
|
|
@@ -848,6 +861,10 @@ async function chatCompletionViaCustomOpenAICompatible(client, model, messages,
|
|
|
848
861
|
if (!content) {
|
|
849
862
|
throw wrapLLMError(new Error("LLM returned empty response from stream"), errorCtx);
|
|
850
863
|
}
|
|
864
|
+
if (!sawResponseTerminal) {
|
|
865
|
+
// Responses 协议的正常结束必须有 response.completed/incomplete 终止事件
|
|
866
|
+
throw new PartialResponseError(content, new Error("stream closed without response.completed"));
|
|
867
|
+
}
|
|
851
868
|
return { content, usage };
|
|
852
869
|
}
|
|
853
870
|
const payload = {
|
|
@@ -901,6 +918,9 @@ async function chatCompletionViaCustomOpenAICompatible(client, model, messages,
|
|
|
901
918
|
let content = "";
|
|
902
919
|
let reasoningContent = "";
|
|
903
920
|
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
921
|
+
// OpenAI 协议的正常结束必须出现 [DONE] 哨兵或带 finish_reason 的 chunk。
|
|
922
|
+
// 网关掐断长连接时流会"干净地"关闭但没有任何终止信号——那是截断,不是完成。
|
|
923
|
+
let sawTerminal = false;
|
|
904
924
|
const monitor = createStreamMonitor(onStreamProgress);
|
|
905
925
|
try {
|
|
906
926
|
while (true) {
|
|
@@ -911,9 +931,16 @@ async function chatCompletionViaCustomOpenAICompatible(client, model, messages,
|
|
|
911
931
|
const parsed = parseSseEvents(buffer);
|
|
912
932
|
buffer = parsed.rest;
|
|
913
933
|
for (const event of parsed.events) {
|
|
914
|
-
if (!event.data
|
|
934
|
+
if (!event.data)
|
|
915
935
|
continue;
|
|
936
|
+
if (event.data === "[DONE]") {
|
|
937
|
+
sawTerminal = true;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
916
940
|
const json = JSON.parse(event.data);
|
|
941
|
+
if (json?.choices?.[0]?.finish_reason) {
|
|
942
|
+
sawTerminal = true;
|
|
943
|
+
}
|
|
917
944
|
const delta = extractChatDeltaContent(json);
|
|
918
945
|
if (delta) {
|
|
919
946
|
content += delta;
|
|
@@ -944,10 +971,15 @@ async function chatCompletionViaCustomOpenAICompatible(client, model, messages,
|
|
|
944
971
|
if (!finalContent) {
|
|
945
972
|
throw wrapLLMError(new Error("LLM returned empty response from stream"), errorCtx);
|
|
946
973
|
}
|
|
974
|
+
if (!sawTerminal) {
|
|
975
|
+
throw new PartialResponseError(finalContent, new Error("stream closed without [DONE]/finish_reason"));
|
|
976
|
+
}
|
|
947
977
|
return { content: finalContent, usage };
|
|
948
978
|
}
|
|
949
979
|
// === Simple Chat (used by all agents via BaseAgent.chat()) ===
|
|
950
980
|
export async function chatCompletion(client, model, messages, options) {
|
|
981
|
+
if (isLlmStubEnabled())
|
|
982
|
+
return Promise.resolve(stubChatCompletion(messages, model));
|
|
951
983
|
// C1 (v2.0.0):删除 maxTokensCap 机制。per-call 显式传的 maxTokens 永远不被裁剪。
|
|
952
984
|
const resolved = {
|
|
953
985
|
temperature: clampTemperatureForModel(client.service, model, options?.temperature ?? client.defaults.temperature),
|
|
@@ -975,13 +1007,9 @@ export async function chatCompletion(client, model, messages, options) {
|
|
|
975
1007
|
{ enabled: (options?.retry ?? true) && !onTextDelta });
|
|
976
1008
|
}
|
|
977
1009
|
catch (error) {
|
|
978
|
-
//
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
content: error.partialContent,
|
|
982
|
-
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
983
|
-
};
|
|
984
|
-
}
|
|
1010
|
+
// 注意:中断的流(PartialResponseError)不再"打捞"半截内容当成功返回——
|
|
1011
|
+
// 那会产出写到一半就结束的章节/设定文件。重试由 withTransientLLMRetry
|
|
1012
|
+
// 负责(完整重新生成);重试耗尽后如实抛错。
|
|
985
1013
|
throw wrapLLMError(error, errorCtx);
|
|
986
1014
|
}
|
|
987
1015
|
}
|
|
@@ -1059,6 +1087,7 @@ async function chatCompletionViaPiAi(client, model, messages, resolved, onStream
|
|
|
1059
1087
|
const monitor = createStreamMonitor(onStreamProgress);
|
|
1060
1088
|
let inputTokens = 0;
|
|
1061
1089
|
let outputTokens = 0;
|
|
1090
|
+
let sawDone = false;
|
|
1062
1091
|
try {
|
|
1063
1092
|
for await (const event of eventStream) {
|
|
1064
1093
|
if (event.type === "text_delta") {
|
|
@@ -1070,9 +1099,12 @@ async function chatCompletionViaPiAi(client, model, messages, resolved, onStream
|
|
|
1070
1099
|
const msg = event.type === "done" ? event.message : event.error;
|
|
1071
1100
|
inputTokens = msg.usage.input;
|
|
1072
1101
|
outputTokens = msg.usage.output;
|
|
1102
|
+
if (event.type === "done") {
|
|
1103
|
+
sawDone = true;
|
|
1104
|
+
}
|
|
1073
1105
|
if (event.type === "error" && msg.errorMessage) {
|
|
1074
1106
|
const partial = chunks.join("");
|
|
1075
|
-
if (partial
|
|
1107
|
+
if (partial) {
|
|
1076
1108
|
throw new PartialResponseError(partial, new Error(msg.errorMessage));
|
|
1077
1109
|
}
|
|
1078
1110
|
throw new Error(msg.errorMessage);
|
|
@@ -1085,7 +1117,8 @@ async function chatCompletionViaPiAi(client, model, messages, resolved, onStream
|
|
|
1085
1117
|
if (streamError instanceof PartialResponseError)
|
|
1086
1118
|
throw streamError;
|
|
1087
1119
|
const partial = chunks.join("");
|
|
1088
|
-
if (partial
|
|
1120
|
+
if (partial) {
|
|
1121
|
+
// 带着已收到的部分内容抛 PartialResponseError,让瞬时重试整体重新生成
|
|
1089
1122
|
throw new PartialResponseError(partial, streamError);
|
|
1090
1123
|
}
|
|
1091
1124
|
throw streamError;
|
|
@@ -1099,6 +1132,10 @@ async function chatCompletionViaPiAi(client, model, messages, resolved, onStream
|
|
|
1099
1132
|
console.warn(`[inkos] LLM 流式响应无文本内容 (${diag})`);
|
|
1100
1133
|
throw new Error(`LLM returned empty response from stream (${diag})`);
|
|
1101
1134
|
}
|
|
1135
|
+
if (!sawDone) {
|
|
1136
|
+
// 事件流没有以 done 收尾就结束 = 上游把流掐断了,内容不可信
|
|
1137
|
+
throw new PartialResponseError(content, new Error("stream ended without done event"));
|
|
1138
|
+
}
|
|
1102
1139
|
return {
|
|
1103
1140
|
content,
|
|
1104
1141
|
usage: {
|