pi-loop-graph-sdk 0.2.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.
- package/LICENSE +21 -0
- package/README-zh.md +416 -0
- package/README.md +414 -0
- package/ROADMAP.md +60 -0
- package/dist/adapter/complete-tool.d.ts +3 -0
- package/dist/adapter/complete-tool.js +53 -0
- package/dist/adapter/debug-log.d.ts +38 -0
- package/dist/adapter/debug-log.js +151 -0
- package/dist/adapter/extension.d.ts +2 -0
- package/dist/adapter/extension.js +11 -0
- package/dist/adapter/graph-execution-host.d.ts +73 -0
- package/dist/adapter/graph-execution-host.js +181 -0
- package/dist/adapter/isolated-graph-session.d.ts +75 -0
- package/dist/adapter/isolated-graph-session.js +313 -0
- package/dist/adapter/loop-graph-extension.d.ts +96 -0
- package/dist/adapter/loop-graph-extension.js +487 -0
- package/dist/adapter/mechanism-runtime.d.ts +97 -0
- package/dist/adapter/mechanism-runtime.js +670 -0
- package/dist/adapter/model-messages.d.ts +17 -0
- package/dist/adapter/model-messages.js +11 -0
- package/dist/adapter/observability.d.ts +88 -0
- package/dist/adapter/observability.js +31 -0
- package/dist/adapter/output-contract.d.ts +12 -0
- package/dist/adapter/output-contract.js +87 -0
- package/dist/adapter/pi-node-context.d.ts +132 -0
- package/dist/adapter/pi-node-context.js +619 -0
- package/dist/adapter/projection.d.ts +121 -0
- package/dist/adapter/projection.js +169 -0
- package/dist/adapter/skill-content.d.ts +16 -0
- package/dist/adapter/skill-content.js +16 -0
- package/dist/advanced.d.ts +32 -0
- package/dist/advanced.js +17 -0
- package/dist/builders/graph.d.ts +27 -0
- package/dist/builders/graph.js +39 -0
- package/dist/builders/node.d.ts +8 -0
- package/dist/builders/node.js +9 -0
- package/dist/builders/refs.d.ts +5 -0
- package/dist/builders/refs.js +10 -0
- package/dist/builders/route.d.ts +11 -0
- package/dist/builders/route.js +18 -0
- package/dist/core/context.d.ts +73 -0
- package/dist/core/context.js +229 -0
- package/dist/core/graph.d.ts +172 -0
- package/dist/core/graph.js +57 -0
- package/dist/core/json.d.ts +8 -0
- package/dist/core/json.js +46 -0
- package/dist/core/limits.d.ts +7 -0
- package/dist/core/limits.js +14 -0
- package/dist/core/mechanism.d.ts +88 -0
- package/dist/core/mechanism.js +5 -0
- package/dist/core/result.d.ts +41 -0
- package/dist/core/result.js +1 -0
- package/dist/core/schema.d.ts +8 -0
- package/dist/core/schema.js +23 -0
- package/dist/core/skill.d.ts +12 -0
- package/dist/core/skill.js +1 -0
- package/dist/host/baseline.d.ts +11 -0
- package/dist/host/baseline.js +4 -0
- package/dist/host/graph-catalog.d.ts +8 -0
- package/dist/host/graph-catalog.js +24 -0
- package/dist/host/graph-host.d.ts +53 -0
- package/dist/host/graph-host.js +181 -0
- package/dist/host/preflight.d.ts +17 -0
- package/dist/host/preflight.js +81 -0
- package/dist/host/skill-catalog.d.ts +24 -0
- package/dist/host/skill-catalog.js +92 -0
- package/dist/host/tool-catalog.d.ts +27 -0
- package/dist/host/tool-catalog.js +33 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +10 -0
- package/dist/replay/checkpoint.d.ts +40 -0
- package/dist/replay/checkpoint.js +57 -0
- package/dist/replay/events.d.ts +40 -0
- package/dist/replay/events.js +1 -0
- package/dist/replay/finalizer.d.ts +26 -0
- package/dist/replay/finalizer.js +117 -0
- package/dist/replay/html.d.ts +3 -0
- package/dist/replay/html.js +270 -0
- package/dist/replay/index.d.ts +13 -0
- package/dist/replay/index.js +7 -0
- package/dist/replay/model.d.ts +81 -0
- package/dist/replay/model.js +1 -0
- package/dist/replay/parser.d.ts +3 -0
- package/dist/replay/parser.js +332 -0
- package/dist/replay/recorder.d.ts +30 -0
- package/dist/replay/recorder.js +195 -0
- package/dist/replay/store.d.ts +41 -0
- package/dist/replay/store.js +94 -0
- package/dist/router.d.ts +4 -0
- package/dist/router.js +61 -0
- package/dist/runtime/event-bus.d.ts +101 -0
- package/dist/runtime/event-bus.js +18 -0
- package/dist/runtime/graph-runtime.d.ts +173 -0
- package/dist/runtime/graph-runtime.js +1293 -0
- package/dist/runtime/invocation-budget.d.ts +22 -0
- package/dist/runtime/invocation-budget.js +52 -0
- package/dist/runtime/mechanism-runtime.d.ts +92 -0
- package/dist/runtime/mechanism-runtime.js +387 -0
- package/dist/runtime.d.ts +91 -0
- package/dist/runtime.js +258 -0
- package/dist/tools-resolve.d.ts +20 -0
- package/dist/tools-resolve.js +52 -0
- package/dist/type.d.ts +593 -0
- package/dist/type.js +30 -0
- package/dist/validate.d.ts +25 -0
- package/dist/validate.js +203 -0
- package/package.json +69 -0
package/dist/validate.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 图校验
|
|
3
|
+
// ============================================================
|
|
4
|
+
import { END } from "./type.js";
|
|
5
|
+
import { validateGraphDefinition } from "./core/graph.js";
|
|
6
|
+
export function validateGraph(graph, options = {}) {
|
|
7
|
+
if ("stages" in graph) {
|
|
8
|
+
try {
|
|
9
|
+
validateGraphDefinition(graph);
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return [{
|
|
14
|
+
code: "ENTRY_TARGET_MISSING",
|
|
15
|
+
message: error instanceof Error ? error.message : String(error),
|
|
16
|
+
path: "graph",
|
|
17
|
+
}];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const issues = [];
|
|
21
|
+
detectGraphReferenceCycles(graph, issues);
|
|
22
|
+
validateGraphInvocationBoundaries(graph, options, issues);
|
|
23
|
+
// 必须有入口
|
|
24
|
+
if (!graph.entries || graph.entries.length === 0) {
|
|
25
|
+
issues.push({
|
|
26
|
+
code: "NO_ENTRY",
|
|
27
|
+
message: "图没有任何 Entry",
|
|
28
|
+
path: "entries",
|
|
29
|
+
});
|
|
30
|
+
return issues;
|
|
31
|
+
}
|
|
32
|
+
for (const entry of graph.entries) {
|
|
33
|
+
if (!(entry.startNodeId in graph.nodes)) {
|
|
34
|
+
issues.push({
|
|
35
|
+
code: "ENTRY_TARGET_MISSING",
|
|
36
|
+
message: `Entry "${entry.id}" 的 startNodeId "${entry.startNodeId}" 不存在`,
|
|
37
|
+
path: `entries[${entry.id}]`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const [nodeId, node] of Object.entries(graph.nodes)) {
|
|
42
|
+
// 每个节点必须有路由
|
|
43
|
+
if (!(nodeId in graph.routing)) {
|
|
44
|
+
issues.push({
|
|
45
|
+
code: "NODE_ROUTING_MISSING",
|
|
46
|
+
message: `节点 "${nodeId}" 没有路由配置`,
|
|
47
|
+
path: `nodes.${nodeId}`,
|
|
48
|
+
});
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const routing = graph.routing[nodeId];
|
|
52
|
+
// 路由的 nodeId 必须一致
|
|
53
|
+
if (routing.nodeId !== nodeId) {
|
|
54
|
+
issues.push({
|
|
55
|
+
code: "ROUTING_NODE_MISSING",
|
|
56
|
+
message: `路由 nodeId "${routing.nodeId}" 与节点 "${nodeId}" 不匹配`,
|
|
57
|
+
path: `routing.${nodeId}`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// agent-choice 路由:边 description 必填校验
|
|
61
|
+
const isAgentChoice = routing.router.kind === "agent-choice";
|
|
62
|
+
for (const edge of routing.edges) {
|
|
63
|
+
// edge.from 必须等于 routing 的 nodeId
|
|
64
|
+
if (edge.from !== nodeId) {
|
|
65
|
+
issues.push({
|
|
66
|
+
code: "EDGE_FROM_MISMATCH",
|
|
67
|
+
message: `边 "${edge.id}" 的 from "${edge.from}" 与路由节点 "${nodeId}" 不匹配`,
|
|
68
|
+
path: `routing.${nodeId}.edges[${edge.id}]`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// 非 END 边的 to 必须存在
|
|
72
|
+
if (edge.to !== END && !(edge.to in graph.nodes)) {
|
|
73
|
+
issues.push({
|
|
74
|
+
code: "EDGE_TARGET_MISSING",
|
|
75
|
+
message: `边 "${edge.id}" 的 to "${String(edge.to)}" 不存在`,
|
|
76
|
+
path: `routing.${nodeId}.edges[${edge.id}]`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// agent-choice 路由下每条边必须有非空 description
|
|
80
|
+
if (isAgentChoice && (!edge.description || edge.description.trim().length === 0)) {
|
|
81
|
+
issues.push({
|
|
82
|
+
code: "AGENT_CHOICE_EDGE_MISSING_DESCRIPTION",
|
|
83
|
+
message: `节点 "${nodeId}" 使用 agent-choice 路由,边 "${edge.id}" 缺少 description。请为每条边声明可读描述,供 agent 决策时参考。`,
|
|
84
|
+
path: `routing.${nodeId}.edges[${edge.id}].description`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return issues;
|
|
90
|
+
}
|
|
91
|
+
export function assertValidGraph(graph, options = {}) {
|
|
92
|
+
const issues = validateGraph(graph, options);
|
|
93
|
+
if (issues.length > 0) {
|
|
94
|
+
throw new Error(issues.map((i) => `${i.path}: ${i.message}`).join("\n"));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function detectGraphReferenceCycles(root, issues) {
|
|
98
|
+
const ancestors = new Set();
|
|
99
|
+
const visit = (graph, path) => {
|
|
100
|
+
ancestors.add(graph);
|
|
101
|
+
for (const [nodeId, node] of Object.entries(graph.nodes)) {
|
|
102
|
+
if (node.kind !== "graph")
|
|
103
|
+
continue;
|
|
104
|
+
const childPath = `${path}.nodes.${nodeId}.graph`;
|
|
105
|
+
if (ancestors.has(node.graph)) {
|
|
106
|
+
issues.push({
|
|
107
|
+
code: "GRAPH_REFERENCE_CYCLE",
|
|
108
|
+
message: `检测到嵌套 Graph 循环引用: "${graph.id}" → "${node.graph.id}"。`,
|
|
109
|
+
path: childPath,
|
|
110
|
+
});
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
visit(node.graph, childPath);
|
|
114
|
+
}
|
|
115
|
+
ancestors.delete(graph);
|
|
116
|
+
};
|
|
117
|
+
visit(root, `graph.${root.id}`);
|
|
118
|
+
}
|
|
119
|
+
function validateGraphInvocationBoundaries(root, options, issues) {
|
|
120
|
+
const visited = new Set();
|
|
121
|
+
const visit = (graph, path) => {
|
|
122
|
+
if (visited.has(graph))
|
|
123
|
+
return;
|
|
124
|
+
visited.add(graph);
|
|
125
|
+
for (const [nodeId, node] of Object.entries(graph.nodes)) {
|
|
126
|
+
if (node.kind !== "graph")
|
|
127
|
+
continue;
|
|
128
|
+
const nodePath = `${path}.nodes.${nodeId}`;
|
|
129
|
+
const boundary = node.boundary ?? "call";
|
|
130
|
+
if ((boundary === "call" || boundary === "delegate") && node.fold) {
|
|
131
|
+
issues.push({
|
|
132
|
+
code: "INVALID_BOUNDARY_FOLD",
|
|
133
|
+
message: `graph 节点 "${nodeId}" 的 boundary 为 "${boundary}",不能配置 fold。fold 仅对 compose 边界有效。`,
|
|
134
|
+
path: `${nodePath}.boundary`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (options.supportedBoundaries && !options.supportedBoundaries.includes(boundary)) {
|
|
138
|
+
issues.push({
|
|
139
|
+
code: "UNSUPPORTED_GRAPH_BOUNDARY",
|
|
140
|
+
message: `graph 节点 "${nodeId}" 使用尚未由当前执行载体支持的 boundary: "${boundary}"。`,
|
|
141
|
+
path: `${nodePath}.boundary`,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else if (boundary === "delegate" && options.delegateHostAvailable === false) {
|
|
145
|
+
issues.push({
|
|
146
|
+
code: "DELEGATE_HOST_UNAVAILABLE",
|
|
147
|
+
message: `graph 节点 "${nodeId}" 使用 delegate,但当前环境未提供 delegate host。`,
|
|
148
|
+
path: `${nodePath}.boundary`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
visit(node.graph, `${nodePath}.graph`);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
visit(root, `graph.${root.id}`);
|
|
155
|
+
}
|
|
156
|
+
// ── 工具校验 ──
|
|
157
|
+
/**
|
|
158
|
+
* 校验图中所有节点的工具配置。
|
|
159
|
+
*
|
|
160
|
+
* - 同一节点 tools 数组内有重复名 → 报错
|
|
161
|
+
* - 如果提供 registeredNames,检查所有引用的工具是否已注册 → 报错
|
|
162
|
+
*
|
|
163
|
+
* defaultTools 与 node.tools 之间的重叠不做报错(那是故意注入),
|
|
164
|
+
* 在 resolveNodeTools 中去重即可。
|
|
165
|
+
*/
|
|
166
|
+
export function validateGraphTools(graph, defaultTools, registeredNames, resolveTools) {
|
|
167
|
+
const issues = [];
|
|
168
|
+
for (const [nodeId, node] of Object.entries(graph.nodes)) {
|
|
169
|
+
if (node.kind !== "code")
|
|
170
|
+
continue;
|
|
171
|
+
const nodeTools = node.tools ?? [];
|
|
172
|
+
// 节点内去重检查
|
|
173
|
+
const seen = new Set();
|
|
174
|
+
for (const t of nodeTools) {
|
|
175
|
+
if (seen.has(t)) {
|
|
176
|
+
issues.push({
|
|
177
|
+
code: "DUPLICATE_TOOL_IN_NODE",
|
|
178
|
+
message: `节点 "${nodeId}" 的 tools 中有重复的工具名: "${t}"`,
|
|
179
|
+
path: `nodes.${nodeId}.tools`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
seen.add(t);
|
|
183
|
+
}
|
|
184
|
+
// 工具存在性检查
|
|
185
|
+
if (registeredNames) {
|
|
186
|
+
const allTools = new Set(resolveTools
|
|
187
|
+
? resolveTools(nodeId, nodeTools)
|
|
188
|
+
: ["read", "__graph_complete__", ...defaultTools, ...nodeTools]);
|
|
189
|
+
for (const t of allTools) {
|
|
190
|
+
if (t === "read" || t === "__graph_complete__")
|
|
191
|
+
continue;
|
|
192
|
+
if (!registeredNames.has(t)) {
|
|
193
|
+
issues.push({
|
|
194
|
+
code: "TOOL_NOT_REGISTERED",
|
|
195
|
+
message: `图 "${graph.id}" 节点 "${nodeId}" 引用了未注册的工具: "${t}"`,
|
|
196
|
+
path: `nodes.${nodeId}.tools`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return issues;
|
|
203
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-loop-graph-sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Turn skill-constrained workflows into monitored loop graphs — typed stages, mechanism constraints, full replay. Comprehensive AI-friendly docs",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "0liveriaaawa",
|
|
7
|
+
"keywords": ["pi-package", "pi-extension", "agent-workflow", "graph", "loop-graph", "pi"],
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./extension": {
|
|
17
|
+
"types": "./dist/adapter/extension.d.ts",
|
|
18
|
+
"import": "./dist/adapter/extension.js"
|
|
19
|
+
},
|
|
20
|
+
"./replay": {
|
|
21
|
+
"types": "./dist/replay/index.d.ts",
|
|
22
|
+
"import": "./dist/replay/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./advanced": {
|
|
25
|
+
"types": "./dist/advanced.d.ts",
|
|
26
|
+
"import": "./dist/advanced.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist/",
|
|
31
|
+
"README.md",
|
|
32
|
+
"README-zh.md",
|
|
33
|
+
"ROADMAP.md"
|
|
34
|
+
],
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/0liveiraaa/pi-loop-graph-sdk.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/0liveiraaa/pi-loop-graph-sdk#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/0liveiraaa/pi-loop-graph-sdk/issues"
|
|
42
|
+
},
|
|
43
|
+
"pi": {
|
|
44
|
+
"extensions": ["./dist/adapter/extension.js"]
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "node scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
|
|
48
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json && npm run test:types:target && tsc -p tests/types/legacy-removals/phase1.tsconfig.json && tsc -p tests/types/legacy-removals/phase3.tsconfig.json",
|
|
49
|
+
"test:types:target": "tsc -p tests/types/target/tsconfig.json",
|
|
50
|
+
"test:types:legacy-removals": "tsc -p tests/types/legacy-removals/tsconfig.json",
|
|
51
|
+
"test:package-consumer": "node tests/package-consumer/run.mjs",
|
|
52
|
+
"test": "npm run build && vitest run",
|
|
53
|
+
"verify:phase0": "npm run typecheck && npm test && npm run test:package-consumer",
|
|
54
|
+
"prepare": "npm run build"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@earendil-works/pi-coding-agent": "0.80.3",
|
|
58
|
+
"@earendil-works/pi-tui": "^0.80.3",
|
|
59
|
+
"typescript": "^5.5.0",
|
|
60
|
+
"vitest": "^2.0.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
64
|
+
"@earendil-works/pi-tui": "*"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"typebox": "^1.3.5"
|
|
68
|
+
}
|
|
69
|
+
}
|