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,313 @@
1
+ import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
2
+ import { createLoopGraphExtension, } from "./loop-graph-extension.js";
3
+ import { IsolatedSessionGraphHost } from "./graph-execution-host.js";
4
+ import { createGraphHost } from "../host/graph-host.js";
5
+ import { GraphCatalog } from "../host/graph-catalog.js";
6
+ import { FileRunStore } from "../replay/store.js";
7
+ /**
8
+ * 使用 pi 官方 in-memory AgentSession 创建隔离图执行环境。
9
+ *
10
+ * 子会话通过 inline extension factory 安装同一套 LoopGraph Runtime,避免维护
11
+ * 第二套 graph loop。runtimeOnly 模式只保留运行钩子,不注册对外命令或资源通知。
12
+ */
13
+ export function createIsolatedGraphSessionFactory(options) {
14
+ const legacyOptions = options;
15
+ return async (_request) => {
16
+ const cwd = options.cwd ?? process.cwd();
17
+ const agentDir = options.agentDir ?? getAgentDir();
18
+ const settingsManager = SettingsManager.inMemory(options.compaction ? { compaction: options.compaction } : undefined);
19
+ let loop = null;
20
+ // A child AgentSession owns a fresh extension/tool registry. Keep a stable
21
+ // scope for all inline-factory loads belonging to this one Session.
22
+ const protocolToolRegistrationScope = {};
23
+ const resourceLoader = new DefaultResourceLoader({
24
+ cwd,
25
+ agentDir,
26
+ settingsManager,
27
+ noExtensions: true,
28
+ noSkills: true,
29
+ noPromptTemplates: true,
30
+ noThemes: true,
31
+ noContextFiles: true,
32
+ extensionFactories: [
33
+ (pi) => {
34
+ loop = createLoopGraphExtension(pi, {
35
+ runtimeOnly: true,
36
+ protocolToolRegistrationScope,
37
+ defaultTools: legacyOptions.defaultTools,
38
+ toolCatalog: options.toolCatalog,
39
+ skillCatalog: options.skillCatalog,
40
+ unsafeToolResolver: options.unsafeToolResolver,
41
+ baseline: options.baseline,
42
+ skillBasePath: options.skillBasePath,
43
+ frameFormatter: options.frameFormatter,
44
+ createDelegateHost: options.createDelegateHost,
45
+ delegateTools: options.customTools,
46
+ delegateCompaction: options.compaction,
47
+ limits: options.limits,
48
+ contextRenderer: options.contextRenderer,
49
+ modelMessageFormatter: options.modelMessageFormatter,
50
+ completionFeedbackFormatter: options.completionFeedbackFormatter,
51
+ outputContractMaxBytes: options.outputContractMaxBytes,
52
+ skillProvider: options.skillProvider,
53
+ skillRenderer: options.skillRenderer,
54
+ skillFailure: options.skillFailure,
55
+ contextRenderers: options.contextRenderers,
56
+ toolResolver: options.toolResolver,
57
+ traceSink: options.traceSink,
58
+ logger: options.logger,
59
+ debug: options.debug,
60
+ debugLogPath: options.debugLogPath,
61
+ recording: options.recording,
62
+ recordingRequired: options.recordingRequired,
63
+ runStore: options.runStore,
64
+ artifactThresholdBytes: options.artifactThresholdBytes,
65
+ pricingResolver: options.pricingResolver,
66
+ createInvocationAgentHost: (_request, recorder) => createPiInvocationAgentHost(options, recorder),
67
+ });
68
+ },
69
+ ],
70
+ });
71
+ await resourceLoader.reload();
72
+ const customToolNames = (options.customTools ?? []).map((tool) => tool.name);
73
+ const catalogToolNames = options.toolCatalog?.names ?? [];
74
+ const activeTools = [
75
+ "read",
76
+ ...(legacyOptions.defaultTools ?? []),
77
+ ...customToolNames,
78
+ ...catalogToolNames,
79
+ "__graph_complete__",
80
+ ];
81
+ const { session } = await createAgentSession({
82
+ cwd,
83
+ agentDir,
84
+ authStorage: options.authStorage,
85
+ modelRegistry: options.modelRegistry,
86
+ model: options.model,
87
+ thinkingLevel: options.thinkingLevel ?? "off",
88
+ sessionManager: SessionManager.inMemory(cwd),
89
+ settingsManager,
90
+ resourceLoader,
91
+ customTools: options.customTools,
92
+ tools: [...new Set(activeTools)],
93
+ });
94
+ if (!loop) {
95
+ session.dispose();
96
+ throw new Error("runtime-only LoopGraph extension 初始化失败");
97
+ }
98
+ const runtime = loop;
99
+ // Resource loading may instantiate the inline extension more than once. Register
100
+ // only after createAgentSession has selected the final instance that run() uses.
101
+ for (const graph of options.graphs ?? [])
102
+ runtime.registerGraph(graph);
103
+ return {
104
+ run(graph, request) {
105
+ return runtime.executeGraph(graph, {
106
+ source: "tool",
107
+ params: request.background,
108
+ }, {
109
+ signal: request.signal,
110
+ limits: request.limits,
111
+ maxSteps: request.maxSteps,
112
+ recording: request.recording,
113
+ recordingRequired: request.recordingRequired,
114
+ });
115
+ },
116
+ abort() {
117
+ return session.abort();
118
+ },
119
+ dispose() {
120
+ session.dispose();
121
+ },
122
+ };
123
+ };
124
+ }
125
+ /**
126
+ * 构造可递归 delegate 的一次性 host factory。每次调用创建新 host/session,
127
+ * 子 session 内的 delegate graph-node 继续复用同一份认证、模型与真实工具实现。
128
+ */
129
+ export function createIsolatedDelegateHostFactory(options) {
130
+ let createSession;
131
+ const createHost = async () => new IsolatedSessionGraphHost({ createSession });
132
+ createSession = createIsolatedGraphSessionFactory({
133
+ ...options,
134
+ createDelegateHost: createHost,
135
+ });
136
+ return createHost;
137
+ }
138
+ /** Creates one Pi Session that only executes Agent Runs for a Core Graph Invocation. */
139
+ export async function createPiInvocationAgentHost(options, recorder = null) {
140
+ const cwd = options.cwd ?? process.cwd();
141
+ const agentDir = options.agentDir ?? getAgentDir();
142
+ const settingsManager = SettingsManager.inMemory(options.compaction ? { compaction: options.compaction } : undefined);
143
+ let loop = null;
144
+ // Invocation hosts are also isolated Sessions and therefore need their own
145
+ // completion-tool registration scope.
146
+ const protocolToolRegistrationScope = {};
147
+ const resourceLoader = new DefaultResourceLoader({
148
+ cwd,
149
+ agentDir,
150
+ settingsManager,
151
+ noExtensions: true,
152
+ noSkills: true,
153
+ noPromptTemplates: true,
154
+ noThemes: true,
155
+ noContextFiles: true,
156
+ extensionFactories: [
157
+ (pi) => {
158
+ loop = createLoopGraphExtension(pi, {
159
+ runtimeOnly: true,
160
+ protocolToolRegistrationScope,
161
+ toolCatalog: options.toolCatalog,
162
+ skillCatalog: options.skillCatalog,
163
+ unsafeToolResolver: options.unsafeToolResolver,
164
+ baseline: options.baseline,
165
+ limits: options.limits,
166
+ modelMessageFormatter: options.modelMessageFormatter,
167
+ completionFeedbackFormatter: options.completionFeedbackFormatter,
168
+ outputContractMaxBytes: options.outputContractMaxBytes,
169
+ recording: "off",
170
+ createInvocationAgentHost: (_request, nestedRecorder) => createPiInvocationAgentHost(options, nestedRecorder),
171
+ });
172
+ },
173
+ ],
174
+ });
175
+ await resourceLoader.reload();
176
+ const customToolNames = (options.customTools ?? []).map((tool) => tool.name);
177
+ const catalogToolNames = options.toolCatalog?.names ?? [];
178
+ const { session } = await createAgentSession({
179
+ cwd,
180
+ agentDir,
181
+ authStorage: options.authStorage,
182
+ modelRegistry: options.modelRegistry,
183
+ model: options.model,
184
+ thinkingLevel: options.thinkingLevel ?? "off",
185
+ sessionManager: SessionManager.inMemory(cwd),
186
+ settingsManager,
187
+ resourceLoader,
188
+ customTools: options.customTools,
189
+ tools: [...new Set(["read", ...customToolNames, ...catalogToolNames, "__graph_complete__"])],
190
+ });
191
+ if (!loop) {
192
+ session.dispose();
193
+ throw new Error("invocation-scoped Pi Agent Host 初始化失败");
194
+ }
195
+ const runtime = loop;
196
+ for (const graph of options.graphs ?? [])
197
+ runtime.registerGraph(graph);
198
+ const agentHost = runtime.createAgentHost(recorder);
199
+ let disposed = false;
200
+ return {
201
+ runAgent: agentHost.runAgent,
202
+ runAgentFromCode: agentHost.runAgentFromCode,
203
+ async dispose() {
204
+ if (disposed)
205
+ return;
206
+ disposed = true;
207
+ try {
208
+ await session.abort();
209
+ }
210
+ finally {
211
+ await agentHost.dispose();
212
+ session.dispose();
213
+ }
214
+ },
215
+ };
216
+ }
217
+ /** Creates a Core GraphHost backed by one isolated Pi AgentSession. */
218
+ export async function createPiGraphHost(options) {
219
+ const effectiveOptions = { ...options, runStore: options.runStore ?? new FileRunStore() };
220
+ const createSession = createIsolatedGraphSessionFactory(effectiveOptions);
221
+ const session = await createSession({
222
+ background: {},
223
+ invocationKind: "api",
224
+ boundary: "call",
225
+ });
226
+ let disposed = false;
227
+ let running = false;
228
+ return {
229
+ async execute(graph, input, runOptions = {}) {
230
+ if (disposed)
231
+ throw new Error("GraphHost 已释放");
232
+ if (running)
233
+ throw new Error("GraphHost 已有 Root Run 正在执行;并发运行必须创建独立 Host");
234
+ if (runOptions.signal?.aborted)
235
+ throw abortError();
236
+ running = true;
237
+ const onAbort = () => { void session.abort().catch(() => undefined); };
238
+ runOptions.signal?.addEventListener("abort", onAbort, { once: true });
239
+ try {
240
+ return await session.run(graph, {
241
+ background: input,
242
+ invocationKind: "api",
243
+ boundary: "call",
244
+ signal: runOptions.signal,
245
+ limits: runOptions.limits,
246
+ maxSteps: runOptions.maxSteps,
247
+ recording: runOptions.recording,
248
+ recordingRequired: runOptions.recordingRequired,
249
+ });
250
+ }
251
+ finally {
252
+ runOptions.signal?.removeEventListener("abort", onAbort);
253
+ running = false;
254
+ }
255
+ },
256
+ async dispose() {
257
+ if (disposed)
258
+ return;
259
+ disposed = true;
260
+ try {
261
+ await session.abort();
262
+ }
263
+ finally {
264
+ session.dispose();
265
+ }
266
+ },
267
+ async resume(graph, resumeOptions) {
268
+ if (disposed)
269
+ throw new Error("GraphHost 已释放");
270
+ if (running)
271
+ throw new Error("GraphHost 已有 Root Run 正在执行;并发运行必须创建独立 Host");
272
+ running = true;
273
+ const catalog = new GraphCatalog();
274
+ for (const registered of effectiveOptions.graphs ?? [])
275
+ catalog.register(registered);
276
+ if (!catalog.has({ id: graph.id, version: graph.version }))
277
+ catalog.register(graph);
278
+ const agentHost = await createPiInvocationAgentHost(effectiveOptions, null);
279
+ const resumeHost = createGraphHost({
280
+ runtime: {
281
+ catalog,
282
+ toolCatalog: effectiveOptions.toolCatalog,
283
+ skillCatalog: effectiveOptions.skillCatalog,
284
+ unsafeToolResolver: effectiveOptions.unsafeToolResolver,
285
+ baseline: effectiveOptions.baseline,
286
+ runAgent: agentHost.runAgent,
287
+ runAgentFromCode: agentHost.runAgentFromCode,
288
+ createInvocationAgentHost: (request) => createPiInvocationAgentHost(effectiveOptions, null),
289
+ delegateGraph: (request) => request.execute(),
290
+ },
291
+ runStore: effectiveOptions.runStore,
292
+ checkpointStore: effectiveOptions.runStore,
293
+ recording: effectiveOptions.recording,
294
+ recordingRequired: effectiveOptions.recordingRequired,
295
+ artifactThresholdBytes: effectiveOptions.artifactThresholdBytes,
296
+ pricingResolver: effectiveOptions.pricingResolver,
297
+ });
298
+ try {
299
+ return await resumeHost.resume(graph, resumeOptions);
300
+ }
301
+ finally {
302
+ running = false;
303
+ await resumeHost.dispose();
304
+ await agentHost.dispose();
305
+ }
306
+ },
307
+ };
308
+ }
309
+ function abortError() {
310
+ const error = new Error("Graph execution aborted");
311
+ error.name = "AbortError";
312
+ return error;
313
+ }
@@ -0,0 +1,96 @@
1
+ import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ import type { CompletionSubmissionDecision } from "../type.js";
3
+ import type { Graph as CoreGraph } from "../core/graph.js";
4
+ import type { JsonValue } from "../core/json.js";
5
+ import type { Mechanism } from "../core/mechanism.js";
6
+ import type { GraphRunResult as CoreGraphRunResult } from "../core/result.js";
7
+ import type { InvocationLimits } from "../core/limits.js";
8
+ import type { RecordingMode } from "../core/result.js";
9
+ import type { HostBaseline } from "../host/baseline.js";
10
+ import type { SkillCatalog } from "../host/skill-catalog.js";
11
+ import { ToolCatalog, type UnsafeToolResolver } from "../host/tool-catalog.js";
12
+ import { type ModelMessageFormatter } from "./model-messages.js";
13
+ import type { NodeContextRenderer } from "./projection.js";
14
+ import type { GraphRef } from "../core/graph.js";
15
+ import { type RunStore } from "../replay/store.js";
16
+ import { Recorder } from "../replay/recorder.js";
17
+ import type { PricingResolver } from "../replay/events.js";
18
+ import type { InvocationAgentHost, InvocationAgentHostRequest } from "../runtime/graph-runtime.js";
19
+ export interface LoopGraphLimits {
20
+ readonly rootMaxSteps?: number;
21
+ readonly childMaxSteps?: number;
22
+ readonly agentRunTimeoutMs?: number;
23
+ readonly completionValidationTimeoutMs?: number;
24
+ }
25
+ export interface ContextRendererRegistry {
26
+ readonly graphs?: Readonly<Record<string, NodeContextRenderer>>;
27
+ readonly nodes?: Readonly<Record<string, Readonly<Record<string, NodeContextRenderer>>>>;
28
+ }
29
+ export interface LoopGraphExecutionOptions {
30
+ readonly contextRenderer?: NodeContextRenderer;
31
+ readonly signal?: AbortSignal;
32
+ readonly limits?: Partial<InvocationLimits>;
33
+ readonly maxSteps?: number;
34
+ readonly recording?: RecordingMode;
35
+ readonly recordingRequired?: boolean;
36
+ }
37
+ export interface CompletionFeedbackInput {
38
+ readonly nodeId: string;
39
+ readonly decision: CompletionSubmissionDecision;
40
+ }
41
+ export type CompletionFeedbackFormatter = (input: CompletionFeedbackInput) => string;
42
+ export declare const defaultCompletionFeedbackFormatter: CompletionFeedbackFormatter;
43
+ export interface LoopGraphExtensionOptions {
44
+ readonly runtimeOnly?: boolean;
45
+ /**
46
+ * @internal Identifies one isolated Pi resource-loading lifecycle. Completion
47
+ * tools must be registered once per such lifecycle, not once per process.
48
+ */
49
+ readonly protocolToolRegistrationScope?: object;
50
+ readonly toolCatalog?: ToolCatalog;
51
+ readonly skillCatalog?: SkillCatalog;
52
+ readonly unsafeToolResolver?: UnsafeToolResolver;
53
+ readonly baseline?: HostBaseline;
54
+ readonly limits?: LoopGraphLimits;
55
+ readonly outputContractMaxBytes?: number;
56
+ readonly contextMaxBytes?: number;
57
+ readonly mechanisms?: readonly Mechanism[];
58
+ readonly modelMessageFormatter?: Partial<ModelMessageFormatter>;
59
+ readonly completionFeedbackFormatter?: CompletionFeedbackFormatter;
60
+ readonly recording?: RecordingMode;
61
+ readonly recordingRequired?: boolean;
62
+ readonly runStore?: RunStore;
63
+ readonly artifactThresholdBytes?: number;
64
+ readonly pricingResolver?: PricingResolver;
65
+ readonly createInvocationAgentHost?: (request: InvocationAgentHostRequest, recorder: Recorder | null) => Promise<InvocationAgentHost>;
66
+ }
67
+ export interface LoopGraphExtension {
68
+ registerGraph(graph: CoreGraph): void;
69
+ exposeGraph(ref: GraphRef, exposure: GraphExposure): void;
70
+ executeGraph(graph: CoreGraph, trigger: {
71
+ readonly source: "command";
72
+ readonly args?: string;
73
+ readonly params?: Record<string, unknown>;
74
+ } | {
75
+ readonly source: "tool";
76
+ readonly params?: Record<string, unknown>;
77
+ }, options?: LoopGraphExecutionOptions): Promise<CoreGraphRunResult>;
78
+ /** @internal Creates an Agent-only lane; it never owns a GraphRuntime. */
79
+ createAgentHost(recorder?: Recorder | null): InvocationAgentHost;
80
+ }
81
+ export type GraphExposure = {
82
+ readonly kind: "command";
83
+ readonly name: string;
84
+ readonly description?: string;
85
+ readonly execution?: "isolated" | "current-session";
86
+ readonly parseInput?: (args: string) => JsonValue;
87
+ } | {
88
+ readonly kind: "tool";
89
+ readonly name: string;
90
+ readonly description?: string;
91
+ readonly execution?: "isolated" | "current-session";
92
+ readonly parameters?: ToolDefinition["parameters"];
93
+ readonly parseInput?: (params: unknown) => JsonValue;
94
+ readonly formatResult?: (result: CoreGraphRunResult) => unknown;
95
+ };
96
+ export declare function createLoopGraphExtension(pi: ExtensionAPI, options?: LoopGraphExtensionOptions): LoopGraphExtension;