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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README-zh.md +416 -0
  3. package/README.md +414 -0
  4. package/ROADMAP.md +60 -0
  5. package/dist/adapter/complete-tool.d.ts +3 -0
  6. package/dist/adapter/complete-tool.js +53 -0
  7. package/dist/adapter/debug-log.d.ts +38 -0
  8. package/dist/adapter/debug-log.js +151 -0
  9. package/dist/adapter/extension.d.ts +2 -0
  10. package/dist/adapter/extension.js +11 -0
  11. package/dist/adapter/graph-execution-host.d.ts +73 -0
  12. package/dist/adapter/graph-execution-host.js +181 -0
  13. package/dist/adapter/isolated-graph-session.d.ts +75 -0
  14. package/dist/adapter/isolated-graph-session.js +313 -0
  15. package/dist/adapter/loop-graph-extension.d.ts +96 -0
  16. package/dist/adapter/loop-graph-extension.js +487 -0
  17. package/dist/adapter/mechanism-runtime.d.ts +97 -0
  18. package/dist/adapter/mechanism-runtime.js +670 -0
  19. package/dist/adapter/model-messages.d.ts +17 -0
  20. package/dist/adapter/model-messages.js +11 -0
  21. package/dist/adapter/observability.d.ts +88 -0
  22. package/dist/adapter/observability.js +31 -0
  23. package/dist/adapter/output-contract.d.ts +12 -0
  24. package/dist/adapter/output-contract.js +87 -0
  25. package/dist/adapter/pi-node-context.d.ts +132 -0
  26. package/dist/adapter/pi-node-context.js +619 -0
  27. package/dist/adapter/projection.d.ts +121 -0
  28. package/dist/adapter/projection.js +169 -0
  29. package/dist/adapter/skill-content.d.ts +16 -0
  30. package/dist/adapter/skill-content.js +16 -0
  31. package/dist/advanced.d.ts +32 -0
  32. package/dist/advanced.js +17 -0
  33. package/dist/builders/graph.d.ts +27 -0
  34. package/dist/builders/graph.js +39 -0
  35. package/dist/builders/node.d.ts +8 -0
  36. package/dist/builders/node.js +9 -0
  37. package/dist/builders/refs.d.ts +5 -0
  38. package/dist/builders/refs.js +10 -0
  39. package/dist/builders/route.d.ts +11 -0
  40. package/dist/builders/route.js +18 -0
  41. package/dist/core/context.d.ts +73 -0
  42. package/dist/core/context.js +229 -0
  43. package/dist/core/graph.d.ts +172 -0
  44. package/dist/core/graph.js +57 -0
  45. package/dist/core/json.d.ts +8 -0
  46. package/dist/core/json.js +46 -0
  47. package/dist/core/limits.d.ts +7 -0
  48. package/dist/core/limits.js +14 -0
  49. package/dist/core/mechanism.d.ts +88 -0
  50. package/dist/core/mechanism.js +5 -0
  51. package/dist/core/result.d.ts +41 -0
  52. package/dist/core/result.js +1 -0
  53. package/dist/core/schema.d.ts +8 -0
  54. package/dist/core/schema.js +23 -0
  55. package/dist/core/skill.d.ts +12 -0
  56. package/dist/core/skill.js +1 -0
  57. package/dist/host/baseline.d.ts +11 -0
  58. package/dist/host/baseline.js +4 -0
  59. package/dist/host/graph-catalog.d.ts +8 -0
  60. package/dist/host/graph-catalog.js +24 -0
  61. package/dist/host/graph-host.d.ts +53 -0
  62. package/dist/host/graph-host.js +181 -0
  63. package/dist/host/preflight.d.ts +17 -0
  64. package/dist/host/preflight.js +81 -0
  65. package/dist/host/skill-catalog.d.ts +24 -0
  66. package/dist/host/skill-catalog.js +92 -0
  67. package/dist/host/tool-catalog.d.ts +27 -0
  68. package/dist/host/tool-catalog.js +33 -0
  69. package/dist/index.d.ts +21 -0
  70. package/dist/index.js +10 -0
  71. package/dist/replay/checkpoint.d.ts +40 -0
  72. package/dist/replay/checkpoint.js +57 -0
  73. package/dist/replay/events.d.ts +40 -0
  74. package/dist/replay/events.js +1 -0
  75. package/dist/replay/finalizer.d.ts +26 -0
  76. package/dist/replay/finalizer.js +117 -0
  77. package/dist/replay/html.d.ts +3 -0
  78. package/dist/replay/html.js +270 -0
  79. package/dist/replay/index.d.ts +13 -0
  80. package/dist/replay/index.js +7 -0
  81. package/dist/replay/model.d.ts +81 -0
  82. package/dist/replay/model.js +1 -0
  83. package/dist/replay/parser.d.ts +3 -0
  84. package/dist/replay/parser.js +332 -0
  85. package/dist/replay/recorder.d.ts +30 -0
  86. package/dist/replay/recorder.js +195 -0
  87. package/dist/replay/store.d.ts +41 -0
  88. package/dist/replay/store.js +94 -0
  89. package/dist/router.d.ts +4 -0
  90. package/dist/router.js +61 -0
  91. package/dist/runtime/event-bus.d.ts +101 -0
  92. package/dist/runtime/event-bus.js +18 -0
  93. package/dist/runtime/graph-runtime.d.ts +173 -0
  94. package/dist/runtime/graph-runtime.js +1293 -0
  95. package/dist/runtime/invocation-budget.d.ts +22 -0
  96. package/dist/runtime/invocation-budget.js +52 -0
  97. package/dist/runtime/mechanism-runtime.d.ts +92 -0
  98. package/dist/runtime/mechanism-runtime.js +387 -0
  99. package/dist/runtime.d.ts +91 -0
  100. package/dist/runtime.js +258 -0
  101. package/dist/tools-resolve.d.ts +20 -0
  102. package/dist/tools-resolve.js +52 -0
  103. package/dist/type.d.ts +593 -0
  104. package/dist/type.js +30 -0
  105. package/dist/validate.d.ts +25 -0
  106. package/dist/validate.js +203 -0
  107. package/package.json +69 -0
@@ -0,0 +1,88 @@
1
+ import type { CompletionValidationStage, NodeCompletion } from "../type.js";
2
+ export interface AgentRunLifecycleContext {
3
+ timestamp: number;
4
+ graphRunId: string;
5
+ graphId: string;
6
+ nodeId: string;
7
+ scopeId: string;
8
+ agentRunId: number;
9
+ }
10
+ export type LoopGraphLifecycleEvent = Readonly<{
11
+ type: "graph_start";
12
+ timestamp: number;
13
+ graphId: string;
14
+ boundary: string;
15
+ invocationKind: string;
16
+ } | {
17
+ type: "graph_end";
18
+ timestamp: number;
19
+ graphId: string;
20
+ status: string;
21
+ steps: number;
22
+ } | {
23
+ type: "graph_error";
24
+ timestamp: number;
25
+ graphId: string;
26
+ error: string;
27
+ } | {
28
+ type: "node_enter";
29
+ timestamp: number;
30
+ graphId: string;
31
+ nodeId: string;
32
+ scopeId: string;
33
+ depth: number;
34
+ } | {
35
+ type: "node_exit";
36
+ timestamp: number;
37
+ graphId: string;
38
+ nodeId: string;
39
+ scopeId: string;
40
+ status: string;
41
+ depth: number;
42
+ } | {
43
+ type: "compaction";
44
+ timestamp: number;
45
+ graphId: string;
46
+ nodeId: string;
47
+ scopeId: string;
48
+ generation: number;
49
+ reason?: unknown;
50
+ } | (AgentRunLifecycleContext & {
51
+ type: "output_contract.prepared";
52
+ schemaFingerprint: string;
53
+ schemaBytes: number;
54
+ }) | (AgentRunLifecycleContext & {
55
+ type: "completion.submitted";
56
+ schemaFingerprint?: string;
57
+ }) | (AgentRunLifecycleContext & {
58
+ type: "completion.validation_started";
59
+ validatorStage: CompletionValidationStage;
60
+ schemaFingerprint?: string;
61
+ }) | (AgentRunLifecycleContext & {
62
+ type: "completion.accepted";
63
+ completionStatus: NodeCompletion["status"];
64
+ schemaFingerprint?: string;
65
+ durationMs: number;
66
+ }) | (AgentRunLifecycleContext & {
67
+ type: "completion.rejected";
68
+ reason: string;
69
+ validatorStage?: CompletionValidationStage;
70
+ schemaFingerprint?: string;
71
+ durationMs: number;
72
+ }) | (AgentRunLifecycleContext & {
73
+ type: "completion.failed";
74
+ scope: "node" | "graph";
75
+ reason: string;
76
+ validatorStage?: CompletionValidationStage;
77
+ schemaFingerprint?: string;
78
+ durationMs: number;
79
+ })>;
80
+ export type LoopGraphTraceSink = (event: LoopGraphLifecycleEvent) => void | Promise<void>;
81
+ export interface LoopGraphLogger {
82
+ debug?(message: string, event?: LoopGraphLifecycleEvent): void;
83
+ error?(message: string, event?: LoopGraphLifecycleEvent): void;
84
+ }
85
+ /** 观测失败不得改变图控制流。 */
86
+ export declare function emitLifecycleEvent(event: LoopGraphLifecycleEvent, traceSink?: LoopGraphTraceSink, logger?: LoopGraphLogger): void;
87
+ /** debug 模式使用的 JSONL sink;创建时不会清空已有文件。 */
88
+ export declare function createJsonlTraceSink(filePath?: string): LoopGraphTraceSink;
@@ -0,0 +1,31 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ /** 观测失败不得改变图控制流。 */
4
+ export function emitLifecycleEvent(event, traceSink, logger) {
5
+ try {
6
+ const pending = traceSink?.(event);
7
+ if (pending && typeof pending.catch === "function") {
8
+ void pending.catch(() => { });
9
+ }
10
+ }
11
+ catch {
12
+ // observability is best-effort
13
+ }
14
+ try {
15
+ const message = `[loop-graph] ${event.type}`;
16
+ if (event.type === "graph_error")
17
+ logger?.error?.(message, event);
18
+ else
19
+ logger?.debug?.(message, event);
20
+ }
21
+ catch {
22
+ // logger failures are isolated from execution
23
+ }
24
+ }
25
+ /** debug 模式使用的 JSONL sink;创建时不会清空已有文件。 */
26
+ export function createJsonlTraceSink(filePath = path.resolve("loop-graph-debug.log")) {
27
+ const resolved = path.resolve(filePath);
28
+ return (event) => {
29
+ fs.appendFileSync(resolved, `${JSON.stringify(event)}\n`, "utf8");
30
+ };
31
+ }
@@ -0,0 +1,12 @@
1
+ import type { CompletionValidationResult, JsonSchema } from "../type.js";
2
+ export declare const DEFAULT_OUTPUT_CONTRACT_MAX_BYTES: number;
3
+ export declare const OUTPUT_CONTRACT_MESSAGE_TYPE = "loop_graph_output_contract";
4
+ export interface PreparedOutputContract {
5
+ readonly schema: JsonSchema;
6
+ readonly serialized: string;
7
+ readonly modelText: string;
8
+ readonly fingerprint: string;
9
+ readonly byteSize: number;
10
+ validate(result: Record<string, unknown>): CompletionValidationResult;
11
+ }
12
+ export declare function prepareOutputContract(schema: JsonSchema | undefined, maxBytes?: number): PreparedOutputContract | null;
@@ -0,0 +1,87 @@
1
+ import { createHash } from "node:crypto";
2
+ import Schema from "typebox/schema";
3
+ export const DEFAULT_OUTPUT_CONTRACT_MAX_BYTES = 64 * 1024;
4
+ export const OUTPUT_CONTRACT_MESSAGE_TYPE = "loop_graph_output_contract";
5
+ export function prepareOutputContract(schema, maxBytes = DEFAULT_OUTPUT_CONTRACT_MAX_BYTES) {
6
+ if (schema == null)
7
+ return null;
8
+ if (!Number.isInteger(maxBytes) || maxBytes <= 0) {
9
+ throw new TypeError(`outputContractMaxBytes 必须是正整数,收到: ${String(maxBytes)}`);
10
+ }
11
+ const normalized = normalizeJsonValue(schema, "$", new Set());
12
+ const serialized = JSON.stringify(normalized, null, 2);
13
+ const bytes = Buffer.byteLength(serialized, "utf8");
14
+ if (bytes > maxBytes) {
15
+ throw new Error(`outputSchema 序列化后为 ${bytes} bytes,超过 outputContractMaxBytes (${maxBytes})`);
16
+ }
17
+ const validator = Schema.Compile(normalized);
18
+ const fingerprint = createHash("sha256").update(serialized).digest("hex");
19
+ return Object.freeze({
20
+ schema: deepFreeze(normalized),
21
+ serialized,
22
+ fingerprint,
23
+ byteSize: bytes,
24
+ modelText: [
25
+ "=== OUTPUT CONTRACT ===",
26
+ "提交到 __graph_complete__.result 的值必须严格符合以下 JSON Schema:",
27
+ serialized,
28
+ "=== END OUTPUT CONTRACT ===",
29
+ ].join("\n"),
30
+ validate(result) {
31
+ const [isValid, errors] = validator.Errors(result);
32
+ if (isValid)
33
+ return { isValid: true };
34
+ const summary = errors.slice(0, 5).map((error) => {
35
+ const path = error.instancePath || "$";
36
+ return `${path} ${error.message}`;
37
+ }).join("; ");
38
+ return { isValid: false, reason: `输出不符合 outputSchema: ${summary}` };
39
+ },
40
+ });
41
+ }
42
+ function normalizeJsonValue(value, path, ancestors) {
43
+ if (value === null || typeof value === "string" || typeof value === "boolean")
44
+ return value;
45
+ if (typeof value === "number") {
46
+ if (!Number.isFinite(value))
47
+ throw new TypeError(`${path} 包含非有限数字`);
48
+ return value;
49
+ }
50
+ if (typeof value !== "object") {
51
+ throw new TypeError(`${path} 包含不可序列化的 ${typeof value}`);
52
+ }
53
+ if (ancestors.has(value))
54
+ throw new TypeError(`${path} 包含循环引用`);
55
+ ancestors.add(value);
56
+ try {
57
+ if (Array.isArray(value)) {
58
+ if (Object.keys(value).length !== value.length) {
59
+ throw new TypeError(`${path} 包含稀疏数组或非索引属性`);
60
+ }
61
+ return value.map((entry, index) => normalizeJsonValue(entry, `${path}[${index}]`, ancestors));
62
+ }
63
+ const prototype = Object.getPrototypeOf(value);
64
+ if (prototype !== Object.prototype && prototype !== null) {
65
+ throw new TypeError(`${path} 必须是普通 JSON 对象`);
66
+ }
67
+ if (Object.getOwnPropertySymbols(value).length > 0) {
68
+ throw new TypeError(`${path} 包含 JSON 无法表示的 symbol key`);
69
+ }
70
+ const output = {};
71
+ for (const key of Object.keys(value).sort()) {
72
+ output[key] = normalizeJsonValue(value[key], `${path}.${key}`, ancestors);
73
+ }
74
+ return output;
75
+ }
76
+ finally {
77
+ ancestors.delete(value);
78
+ }
79
+ }
80
+ function deepFreeze(value) {
81
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
82
+ Object.freeze(value);
83
+ for (const child of Object.values(value))
84
+ deepFreeze(child);
85
+ }
86
+ return value;
87
+ }
@@ -0,0 +1,132 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { ContextSnapshot } from "../core/context.js";
3
+ import type { ReplayEventScope } from "../replay/events.js";
4
+ import type { CompletionSubmissionDecision, CompletionValidationStage, NodeCompletion, NodeContext } from "../type.js";
5
+ import type { AgentRunRequest } from "../type.js";
6
+ import { type ModelMessageFormatter } from "./model-messages.js";
7
+ export interface AgentRunMechanismLifecycle {
8
+ beforeAgentRun(agentRunId: number, request: AgentRunRequest): Promise<{
9
+ blocked: boolean;
10
+ reason?: string;
11
+ }>;
12
+ validateCompletion(agentRunId: number, completion: NodeCompletion): Promise<{
13
+ action: "allow";
14
+ verifiedResult?: NodeCompletion["verifiedResult"];
15
+ } | {
16
+ action: "reject" | "fail-node" | "fail-graph";
17
+ reason: string;
18
+ }>;
19
+ afterAgentRun(agentRunId: number): void;
20
+ }
21
+ export type AgentRunTelemetryEvent = {
22
+ type: "output_contract.prepared";
23
+ agentRunId: number;
24
+ schemaFingerprint: string;
25
+ schemaBytes: number;
26
+ } | {
27
+ type: "completion.submitted";
28
+ agentRunId: number;
29
+ schemaFingerprint?: string;
30
+ } | {
31
+ type: "completion.validation_started";
32
+ agentRunId: number;
33
+ validatorStage: CompletionValidationStage;
34
+ schemaFingerprint?: string;
35
+ } | {
36
+ type: "completion.accepted";
37
+ agentRunId: number;
38
+ completionStatus: NodeCompletion["status"];
39
+ schemaFingerprint?: string;
40
+ durationMs: number;
41
+ } | {
42
+ type: "completion.rejected";
43
+ agentRunId: number;
44
+ reason: string;
45
+ validatorStage?: CompletionValidationStage;
46
+ schemaFingerprint?: string;
47
+ durationMs: number;
48
+ } | {
49
+ type: "completion.failed";
50
+ agentRunId: number;
51
+ scope: "node" | "graph";
52
+ reason: string;
53
+ validatorStage?: CompletionValidationStage;
54
+ schemaFingerprint?: string;
55
+ durationMs: number;
56
+ };
57
+ type CompletionState = "submitted" | "validating" | "accepted" | "rejected" | "failed";
58
+ export declare const CONTEXT_SNAPSHOT_MESSAGE_TYPE = "loop_graph_context";
59
+ export declare class PiNodeContext implements NodeContext {
60
+ private readonly outputContractMaxBytes;
61
+ private readonly telemetry?;
62
+ readonly signal: AbortSignal;
63
+ private pi;
64
+ private currentNodeId;
65
+ /** __graph_complete__ 捕获的 completion 列表(同节点内可能调多次) */
66
+ private pendingCompletions;
67
+ private readonly completionFingerprints;
68
+ /** 活跃 run 的 resolve */
69
+ private activeResolve;
70
+ private activeRunId;
71
+ private nextRunId;
72
+ private readonly agentRunTimeoutMs;
73
+ private readonly messageFormatter;
74
+ private readonly completionValidationTimeoutMs;
75
+ private nodeValidateFn;
76
+ private routeValidateFn;
77
+ private postMechanismValidateFn;
78
+ private mechanismLifecycle;
79
+ private validationInFlight;
80
+ private activeOutputContract;
81
+ private activeOutputContractMessage;
82
+ private activeContextSnapshot;
83
+ private contextProjectionCount;
84
+ private foldableContextCompacted;
85
+ private submissionQueue;
86
+ private rejectionCount;
87
+ private completionState;
88
+ get completionSubmissionState(): CompletionState;
89
+ constructor(pi: ExtensionAPI, agentRunTimeoutMs?: number, messageFormatter?: ModelMessageFormatter, completionValidationTimeoutMs?: number, outputContractMaxBytes?: number, telemetry?: ((event: AgentRunTelemetryEvent) => void) | undefined);
90
+ private runValidateFn;
91
+ runAgent(request: AgentRunRequest): Promise<NodeCompletion>;
92
+ /**
93
+ * 直接执行 pi 平台上的工具。当前占用位,未实现。
94
+ *
95
+ * 纯代码节点不需要此方法——你可以在 execute 里直接
96
+ * import 并使用任何 Node.js 或第三方库:
97
+ *
98
+ * ```typescript
99
+ * execute: async (instance, input, ctx) => {
100
+ * const data = fs.readFileSync(input.data.path, "utf-8");
101
+ * const result = await fetch("https://api.example.com", {...});
102
+ * return { nodeId: "parse", status: "ok", result: { data, result } };
103
+ * }//讨论在有纯代码节点的前提下该功能是否必要
104
+ * ```
105
+ */
106
+ callTool(_name: string, _input: Record<string, unknown>): Promise<unknown>;
107
+ /** 当前节点内调用 __graph_complete__ 的次数 */
108
+ get completeCount(): number;
109
+ getActiveOutputContractMessage(): Readonly<Record<string, unknown>> | null;
110
+ setContextSnapshot(snapshot: ContextSnapshot | null): void;
111
+ markContextCompacted(): void;
112
+ getContextSnapshotMessage(): Readonly<Record<string, unknown>> | null;
113
+ getReplayScope(): ReplayEventScope | null;
114
+ submitCompletion(params: {
115
+ result: Record<string, unknown>;
116
+ }): Promise<CompletionSubmissionDecision>;
117
+ onAgentEnd(): Promise<void>;
118
+ private processAgentEnd;
119
+ private processCompletionSubmission;
120
+ private rejectOrExhaust;
121
+ setCurrentNodeId(nodeId: string): void;
122
+ setNodeCompletionValidator(validate: AgentRunRequest["validateCompletion"]): void;
123
+ setRouteCompletionValidator(validate: AgentRunRequest["validateCompletion"]): void;
124
+ setPostMechanismCompletionValidator(validate: AgentRunRequest["validateCompletion"]): void;
125
+ setMechanismLifecycle(lifecycle: AgentRunMechanismLifecycle | null): void;
126
+ private runValidationStage;
127
+ private emitValidationStarted;
128
+ private emitTelemetry;
129
+ private clearAgentRunArtifacts;
130
+ reset(): void;
131
+ }
132
+ export {};