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
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TSchema } from "typebox";
|
|
2
|
+
import { type JsonValue } from "./json.js";
|
|
3
|
+
export interface SchemaCheckResult {
|
|
4
|
+
readonly valid: boolean;
|
|
5
|
+
readonly value?: JsonValue;
|
|
6
|
+
readonly message?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function checkJsonSchemaValue(schema: TSchema, value: unknown): SchemaCheckResult;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Compile } from "typebox/compile";
|
|
2
|
+
import { isJsonValue } from "./json.js";
|
|
3
|
+
const validators = new WeakMap();
|
|
4
|
+
export function checkJsonSchemaValue(schema, value) {
|
|
5
|
+
if (!isJsonValue(value)) {
|
|
6
|
+
return Object.freeze({ valid: false, message: "Value is not JSON-compatible" });
|
|
7
|
+
}
|
|
8
|
+
const validator = getValidator(schema);
|
|
9
|
+
if (validator.Check(value))
|
|
10
|
+
return Object.freeze({ valid: true, value });
|
|
11
|
+
const first = validator.Errors(value)[0];
|
|
12
|
+
const location = first?.instancePath ? ` at ${first.instancePath}` : "";
|
|
13
|
+
const detail = first?.message ? `: ${first.message}` : "";
|
|
14
|
+
return Object.freeze({ valid: false, message: `Schema validation failed${location}${detail}` });
|
|
15
|
+
}
|
|
16
|
+
function getValidator(schema) {
|
|
17
|
+
const cached = validators.get(schema);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
const validator = Compile(schema);
|
|
21
|
+
validators.set(schema, validator);
|
|
22
|
+
return validator;
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface SkillRef {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly version?: string;
|
|
4
|
+
readonly required: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface ResolvedSkillView {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly version?: string;
|
|
9
|
+
readonly source: string;
|
|
10
|
+
readonly fingerprint: string;
|
|
11
|
+
readonly content: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type HostBaseline = {
|
|
2
|
+
readonly kind: "isolated";
|
|
3
|
+
} | {
|
|
4
|
+
readonly kind: "inherit";
|
|
5
|
+
readonly fingerprint?: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: "custom";
|
|
8
|
+
readonly id: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const DEFAULT_HOST_BASELINE: HostBaseline;
|
|
11
|
+
export declare function resolveHostBaseline(baseline?: HostBaseline): HostBaseline;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Graph, GraphRef } from "../core/graph.js";
|
|
2
|
+
export declare class GraphCatalog {
|
|
3
|
+
private readonly graphs;
|
|
4
|
+
register(graph: Graph): void;
|
|
5
|
+
resolve(ref: GraphRef): Graph | undefined;
|
|
6
|
+
has(ref: GraphRef): boolean;
|
|
7
|
+
get values(): readonly Graph[];
|
|
8
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class GraphCatalog {
|
|
2
|
+
graphs = new Map();
|
|
3
|
+
register(graph) {
|
|
4
|
+
const key = graphKey(graph);
|
|
5
|
+
if (this.graphs.has(key))
|
|
6
|
+
throw new Error(`Graph already registered: ${key}`);
|
|
7
|
+
this.graphs.set(key, graph);
|
|
8
|
+
}
|
|
9
|
+
resolve(ref) {
|
|
10
|
+
return this.graphs.get(refKey(ref));
|
|
11
|
+
}
|
|
12
|
+
has(ref) {
|
|
13
|
+
return this.graphs.has(refKey(ref));
|
|
14
|
+
}
|
|
15
|
+
get values() {
|
|
16
|
+
return Object.freeze([...this.graphs.values()]);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function graphKey(graph) {
|
|
20
|
+
return `${graph.id}@${graph.version}`;
|
|
21
|
+
}
|
|
22
|
+
function refKey(ref) {
|
|
23
|
+
return `${ref.id}@${ref.version}`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TSchema } from "typebox";
|
|
2
|
+
import type { Graph, SchemaValue } from "../core/graph.js";
|
|
3
|
+
import type { InvocationLimits } from "../core/limits.js";
|
|
4
|
+
import type { GraphRunResult } from "../core/result.js";
|
|
5
|
+
import type { RecordingMode } from "../core/result.js";
|
|
6
|
+
import { type GraphRuntimeHost } from "../runtime/graph-runtime.js";
|
|
7
|
+
import { type RunStore, type CheckpointStore } from "../replay/store.js";
|
|
8
|
+
import type { PricingResolver } from "../replay/events.js";
|
|
9
|
+
export interface GraphHostRunOptions {
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
11
|
+
readonly limits?: Partial<InvocationLimits>;
|
|
12
|
+
readonly maxSteps?: number;
|
|
13
|
+
readonly recording?: RecordingMode;
|
|
14
|
+
readonly recordingRequired?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface GraphHostResumeOptions {
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
readonly signal?: AbortSignal;
|
|
19
|
+
readonly maxSteps?: number;
|
|
20
|
+
readonly checkpointMigrator?: (saved: {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly version: string;
|
|
23
|
+
}) => {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly version: string;
|
|
26
|
+
};
|
|
27
|
+
readonly recording?: RecordingMode;
|
|
28
|
+
readonly recordingRequired?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface GraphHost {
|
|
31
|
+
execute<TInputSchema extends TSchema, TOutputSchema extends TSchema>(graph: Graph<TInputSchema, TOutputSchema>, input: SchemaValue<TInputSchema>, options?: GraphHostRunOptions): Promise<GraphRunResult<SchemaValue<TOutputSchema>>>;
|
|
32
|
+
resume<TInputSchema extends TSchema, TOutputSchema extends TSchema>(graph: Graph<TInputSchema, TOutputSchema>, options: GraphHostResumeOptions): Promise<GraphRunResult<SchemaValue<TOutputSchema>>>;
|
|
33
|
+
dispose(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export interface CreateGraphHostOptions {
|
|
36
|
+
readonly runtime?: GraphRuntimeHost;
|
|
37
|
+
readonly limits?: InvocationLimits;
|
|
38
|
+
readonly dispose?: () => void | Promise<void>;
|
|
39
|
+
readonly recording?: RecordingMode;
|
|
40
|
+
readonly recordingRequired?: boolean;
|
|
41
|
+
readonly runStore?: RunStore;
|
|
42
|
+
readonly checkpointStore?: CheckpointStore;
|
|
43
|
+
readonly artifactThresholdBytes?: number;
|
|
44
|
+
readonly pricingResolver?: PricingResolver;
|
|
45
|
+
}
|
|
46
|
+
/** Owns one Core Runtime execution lane. Concurrent roots require separate hosts. */
|
|
47
|
+
export declare function createGraphHost(options?: CreateGraphHostOptions): GraphHost;
|
|
48
|
+
export interface ExecuteIsolatedGraphOptions<TInput> extends GraphHostRunOptions {
|
|
49
|
+
readonly input: TInput;
|
|
50
|
+
readonly createHost: () => GraphHost | Promise<GraphHost>;
|
|
51
|
+
}
|
|
52
|
+
/** Creates, runs and always disposes a one-shot host. */
|
|
53
|
+
export declare function executeIsolatedGraph<TInputSchema extends TSchema, TOutputSchema extends TSchema>(graph: Graph<TInputSchema, TOutputSchema>, options: ExecuteIsolatedGraphOptions<SchemaValue<TInputSchema>>): Promise<GraphRunResult<SchemaValue<TOutputSchema>>>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { GraphRuntime } from "../runtime/graph-runtime.js";
|
|
2
|
+
import { RuntimeEventBus } from "../runtime/event-bus.js";
|
|
3
|
+
import { FileRunStore } from "../replay/store.js";
|
|
4
|
+
import { Recorder } from "../replay/recorder.js";
|
|
5
|
+
/** Owns one Core Runtime execution lane. Concurrent roots require separate hosts. */
|
|
6
|
+
export function createGraphHost(options = {}) {
|
|
7
|
+
const eventBus = options.runtime?.eventBus ?? new RuntimeEventBus();
|
|
8
|
+
const runStore = options.runStore ?? new FileRunStore();
|
|
9
|
+
const checkpointStore = options.checkpointStore ?? runStore;
|
|
10
|
+
const runtime = new GraphRuntime({ ...options.runtime, eventBus, checkpointStore, limits: options.limits ?? options.runtime?.limits });
|
|
11
|
+
let running = false;
|
|
12
|
+
let activeRun;
|
|
13
|
+
let activeController;
|
|
14
|
+
let disposed = false;
|
|
15
|
+
let disposing;
|
|
16
|
+
return {
|
|
17
|
+
async execute(graph, input, runOptions = {}) {
|
|
18
|
+
if (disposed)
|
|
19
|
+
throw new Error("GraphHost 已释放");
|
|
20
|
+
if (running)
|
|
21
|
+
throw new Error("GraphHost 已有 Root Run 正在执行;并发运行必须创建独立 Host");
|
|
22
|
+
running = true;
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
activeController = controller;
|
|
25
|
+
const onAbort = () => controller.abort(runOptions.signal?.reason);
|
|
26
|
+
if (runOptions.signal?.aborted)
|
|
27
|
+
onAbort();
|
|
28
|
+
else
|
|
29
|
+
runOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
30
|
+
const recording = runOptions.recording ?? options.recording ?? "replay";
|
|
31
|
+
const recordingRequired = runOptions.recordingRequired ?? options.recordingRequired ?? false;
|
|
32
|
+
const recorder = recording === "off" ? undefined : new Recorder({
|
|
33
|
+
mode: recording,
|
|
34
|
+
store: runStore,
|
|
35
|
+
artifactThresholdBytes: options.artifactThresholdBytes,
|
|
36
|
+
pricingResolver: options.pricingResolver,
|
|
37
|
+
});
|
|
38
|
+
recorder?.attach(eventBus);
|
|
39
|
+
const execution = (async () => {
|
|
40
|
+
try {
|
|
41
|
+
const result = await runtime.execute(graph, input, { ...runOptions, signal: controller.signal });
|
|
42
|
+
if (!recorder)
|
|
43
|
+
return { ...result, replay: Object.freeze({ mode: "off", status: "off" }) };
|
|
44
|
+
const finalized = await recorder.finalize(result);
|
|
45
|
+
if (recordingRequired && finalized.replay.status !== "complete") {
|
|
46
|
+
return {
|
|
47
|
+
rootRunId: result.rootRunId,
|
|
48
|
+
graphId: result.graphId,
|
|
49
|
+
graphVersion: result.graphVersion,
|
|
50
|
+
steps: result.steps,
|
|
51
|
+
durationMs: result.durationMs,
|
|
52
|
+
replay: finalized.replay,
|
|
53
|
+
status: "failed",
|
|
54
|
+
failure: {
|
|
55
|
+
code: "persistence-failed",
|
|
56
|
+
phase: "host",
|
|
57
|
+
message: finalized.replay.issues?.join("; ") ?? "Replay recording failed",
|
|
58
|
+
retryable: true,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return { ...result, replay: finalized.replay };
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
runOptions.signal?.removeEventListener("abort", onAbort);
|
|
66
|
+
running = false;
|
|
67
|
+
activeController = undefined;
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
activeRun = execution;
|
|
71
|
+
try {
|
|
72
|
+
return await execution;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
if (activeRun === execution)
|
|
76
|
+
activeRun = undefined;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
async resume(graph, resumeOptions = { runId: "" }) {
|
|
80
|
+
if (disposed)
|
|
81
|
+
throw new Error("GraphHost 已释放");
|
|
82
|
+
if (running)
|
|
83
|
+
throw new Error("GraphHost 已有 Root Run 正在执行;并发运行必须创建独立 Host");
|
|
84
|
+
running = true;
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
activeController = controller;
|
|
87
|
+
const onAbort = () => controller.abort(resumeOptions.signal?.reason);
|
|
88
|
+
if (resumeOptions.signal?.aborted)
|
|
89
|
+
onAbort();
|
|
90
|
+
else
|
|
91
|
+
resumeOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
92
|
+
const recording = resumeOptions.recording ?? options.recording ?? "replay";
|
|
93
|
+
const recordingRequired = resumeOptions.recordingRequired ?? options.recordingRequired ?? false;
|
|
94
|
+
const recorder = recording === "off" ? undefined : new Recorder({
|
|
95
|
+
mode: recording,
|
|
96
|
+
store: runStore,
|
|
97
|
+
artifactThresholdBytes: options.artifactThresholdBytes,
|
|
98
|
+
pricingResolver: options.pricingResolver,
|
|
99
|
+
});
|
|
100
|
+
recorder?.attach(eventBus);
|
|
101
|
+
const execution = (async () => {
|
|
102
|
+
try {
|
|
103
|
+
const result = await runtime.resume(graph, {
|
|
104
|
+
runId: resumeOptions.runId,
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
maxSteps: resumeOptions.maxSteps,
|
|
107
|
+
checkpointMigrator: resumeOptions.checkpointMigrator,
|
|
108
|
+
});
|
|
109
|
+
if (!recorder)
|
|
110
|
+
return { ...result, replay: Object.freeze({ mode: "off", status: "off" }) };
|
|
111
|
+
const finalized = await recorder.finalize(result);
|
|
112
|
+
if (recordingRequired && finalized.replay.status !== "complete") {
|
|
113
|
+
return {
|
|
114
|
+
rootRunId: result.rootRunId,
|
|
115
|
+
graphId: result.graphId,
|
|
116
|
+
graphVersion: result.graphVersion,
|
|
117
|
+
steps: result.steps,
|
|
118
|
+
durationMs: result.durationMs,
|
|
119
|
+
replay: finalized.replay,
|
|
120
|
+
status: "failed",
|
|
121
|
+
failure: {
|
|
122
|
+
code: "persistence-failed",
|
|
123
|
+
phase: "host",
|
|
124
|
+
message: finalized.replay.issues?.join("; ") ?? "Replay recording failed",
|
|
125
|
+
retryable: true,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { ...result, replay: finalized.replay };
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
resumeOptions.signal?.removeEventListener("abort", onAbort);
|
|
133
|
+
running = false;
|
|
134
|
+
activeController = undefined;
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
activeRun = execution;
|
|
138
|
+
try {
|
|
139
|
+
return await execution;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
if (activeRun === execution)
|
|
143
|
+
activeRun = undefined;
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
async dispose() {
|
|
147
|
+
if (disposing)
|
|
148
|
+
return disposing;
|
|
149
|
+
disposed = true;
|
|
150
|
+
activeController?.abort(new Error("GraphHost disposed"));
|
|
151
|
+
disposing = (async () => {
|
|
152
|
+
await activeRun?.catch(() => undefined);
|
|
153
|
+
await options.dispose?.();
|
|
154
|
+
})();
|
|
155
|
+
return disposing;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Creates, runs and always disposes a one-shot host. */
|
|
160
|
+
export async function executeIsolatedGraph(graph, options) {
|
|
161
|
+
const host = await options.createHost();
|
|
162
|
+
let runError;
|
|
163
|
+
try {
|
|
164
|
+
return await host.execute(graph, options.input, options);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
runError = error;
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
try {
|
|
172
|
+
await host.dispose();
|
|
173
|
+
}
|
|
174
|
+
catch (disposeError) {
|
|
175
|
+
if (runError && typeof runError === "object")
|
|
176
|
+
runError.suppressed = disposeError;
|
|
177
|
+
else
|
|
178
|
+
throw disposeError;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Graph, NodeDefinition } from "../core/graph.js";
|
|
2
|
+
import type { SkillCatalog } from "./skill-catalog.js";
|
|
3
|
+
import { type ToolCatalog, type UnsafeToolResolver } from "./tool-catalog.js";
|
|
4
|
+
export type CapabilityPreflightCode = "invalid-graph" | "tool-unavailable" | "host-unavailable";
|
|
5
|
+
export declare class CapabilityPreflightError extends Error {
|
|
6
|
+
readonly code: CapabilityPreflightCode;
|
|
7
|
+
readonly phase: "graph" | "host";
|
|
8
|
+
readonly stageId?: string | undefined;
|
|
9
|
+
constructor(code: CapabilityPreflightCode, phase: "graph" | "host", message: string, stageId?: string | undefined);
|
|
10
|
+
}
|
|
11
|
+
export interface CapabilityPreflightHost {
|
|
12
|
+
readonly toolCatalog?: ToolCatalog;
|
|
13
|
+
readonly skillCatalog?: SkillCatalog;
|
|
14
|
+
readonly unsafeToolResolver?: UnsafeToolResolver;
|
|
15
|
+
}
|
|
16
|
+
export declare function preflightGraphCapabilities(graph: Graph, host: CapabilityPreflightHost): void;
|
|
17
|
+
export declare function resolveNodeToolNames(graph: Graph, stageId: string, node: NodeDefinition, host: Pick<CapabilityPreflightHost, "toolCatalog" | "unsafeToolResolver">): readonly string[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { RUNTIME_PROTOCOL_TOOL_NAME, selectNodeToolNames, } from "./tool-catalog.js";
|
|
2
|
+
export class CapabilityPreflightError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
phase;
|
|
5
|
+
stageId;
|
|
6
|
+
constructor(code, phase, message, stageId) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.phase = phase;
|
|
10
|
+
this.stageId = stageId;
|
|
11
|
+
this.name = "CapabilityPreflightError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function preflightGraphCapabilities(graph, host) {
|
|
15
|
+
const policy = graph.tools ?? [];
|
|
16
|
+
if (new Set(policy).size !== policy.length) {
|
|
17
|
+
throw new CapabilityPreflightError("invalid-graph", "graph", `Graph Tool Policy contains duplicate names: ${graph.id}@${graph.version}`);
|
|
18
|
+
}
|
|
19
|
+
if (policy.includes(RUNTIME_PROTOCOL_TOOL_NAME)) {
|
|
20
|
+
throw new CapabilityPreflightError("invalid-graph", "graph", `Graph Tool Policy cannot declare Runtime protocol tool: ${RUNTIME_PROTOCOL_TOOL_NAME}`);
|
|
21
|
+
}
|
|
22
|
+
if (policy.length > 0 && !host.toolCatalog) {
|
|
23
|
+
throw new CapabilityPreflightError("tool-unavailable", "host", `Graph requires business tools but Host has no Tool Catalog: ${graph.id}@${graph.version}`);
|
|
24
|
+
}
|
|
25
|
+
for (const name of policy)
|
|
26
|
+
assertToolAvailable(host.toolCatalog, name);
|
|
27
|
+
assertSkillsAvailable(graph.skills, host.skillCatalog);
|
|
28
|
+
for (const [stageId, stage] of Object.entries(graph.stages)) {
|
|
29
|
+
const selected = resolveNodeToolNames(graph, stageId, stage.node, host);
|
|
30
|
+
for (const name of selected) {
|
|
31
|
+
if (name !== RUNTIME_PROTOCOL_TOOL_NAME)
|
|
32
|
+
assertToolAvailable(host.toolCatalog, name, stageId);
|
|
33
|
+
}
|
|
34
|
+
assertSkillsAvailable(stage.node.skills, host.skillCatalog, stageId);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function resolveNodeToolNames(graph, stageId, node, host) {
|
|
38
|
+
const policy = graph.tools ?? [];
|
|
39
|
+
const nodeTools = node.kind === "graph" ? undefined : node.tools;
|
|
40
|
+
const requested = nodeTools === "all" ? policy : (nodeTools ?? []);
|
|
41
|
+
if (!host.unsafeToolResolver) {
|
|
42
|
+
try {
|
|
43
|
+
return selectNodeToolNames(graph, node);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new CapabilityPreflightError("invalid-graph", "graph", error instanceof Error ? error.message : String(error), stageId);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const names = host.unsafeToolResolver(Object.freeze({
|
|
50
|
+
graph,
|
|
51
|
+
stageId,
|
|
52
|
+
node,
|
|
53
|
+
selected: Object.freeze([...requested]),
|
|
54
|
+
hostTools: host.toolCatalog?.names ?? [],
|
|
55
|
+
}));
|
|
56
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string" || name.length === 0)) {
|
|
57
|
+
throw new CapabilityPreflightError("invalid-graph", "host", "UnsafeToolResolver must return non-empty tool names", stageId);
|
|
58
|
+
}
|
|
59
|
+
return Object.freeze([...new Set(names)]);
|
|
60
|
+
}
|
|
61
|
+
function assertToolAvailable(catalog, name, stageId) {
|
|
62
|
+
if (!catalog?.has(name)) {
|
|
63
|
+
throw new CapabilityPreflightError("tool-unavailable", "host", `Host tool unavailable: ${name}`, stageId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function assertSkillsAvailable(refs, catalog, stageId) {
|
|
67
|
+
for (const ref of refs ?? []) {
|
|
68
|
+
let resolved;
|
|
69
|
+
try {
|
|
70
|
+
resolved = catalog?.resolve(ref);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (!ref.required)
|
|
74
|
+
continue;
|
|
75
|
+
throw new CapabilityPreflightError("host-unavailable", "host", `Required Skill resolution failed: ${ref.name}${ref.version ? `@${ref.version}` : ""}: ${error instanceof Error ? error.message : String(error)}`, stageId);
|
|
76
|
+
}
|
|
77
|
+
if (!ref.required || resolved)
|
|
78
|
+
continue;
|
|
79
|
+
throw new CapabilityPreflightError("host-unavailable", "host", `Required Skill unavailable: ${ref.name}${ref.version ? `@${ref.version}` : ""}`, stageId);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ResolvedSkillView, SkillRef } from "../core/skill.js";
|
|
2
|
+
export interface SkillRegistration {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly version?: string;
|
|
5
|
+
readonly source: string;
|
|
6
|
+
readonly content: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SkillResolver {
|
|
9
|
+
resolve(ref: SkillRef): SkillRegistration | undefined;
|
|
10
|
+
}
|
|
11
|
+
export type SkillResolverFunction = (ref: SkillRef) => SkillRegistration | undefined;
|
|
12
|
+
export interface SkillCatalogOptions {
|
|
13
|
+
readonly resolver?: SkillResolver | SkillResolverFunction;
|
|
14
|
+
}
|
|
15
|
+
export declare class SkillCatalog implements SkillResolver {
|
|
16
|
+
private readonly options;
|
|
17
|
+
private readonly skills;
|
|
18
|
+
private readonly misses;
|
|
19
|
+
constructor(options?: SkillCatalogOptions);
|
|
20
|
+
register(skill: SkillRegistration): void;
|
|
21
|
+
resolve(ref: SkillRef): ResolvedSkillView | undefined;
|
|
22
|
+
loadPaths(paths: readonly string[]): Promise<void>;
|
|
23
|
+
private loadPath;
|
|
24
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export class SkillCatalog {
|
|
5
|
+
options;
|
|
6
|
+
skills = new Map();
|
|
7
|
+
misses = new Set();
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
register(skill) {
|
|
12
|
+
const key = skillKey(skill.name, skill.version);
|
|
13
|
+
if (this.skills.has(key))
|
|
14
|
+
throw new Error(`Skill already registered: ${key}`);
|
|
15
|
+
this.misses.delete(key);
|
|
16
|
+
this.skills.set(key, resolveRegistration(skill));
|
|
17
|
+
}
|
|
18
|
+
resolve(ref) {
|
|
19
|
+
const key = skillKey(ref.name, ref.version);
|
|
20
|
+
const registered = this.skills.get(key);
|
|
21
|
+
if (registered)
|
|
22
|
+
return registered;
|
|
23
|
+
if (this.misses.has(key))
|
|
24
|
+
return undefined;
|
|
25
|
+
const resolver = this.options.resolver;
|
|
26
|
+
if (!resolver)
|
|
27
|
+
return undefined;
|
|
28
|
+
const candidate = typeof resolver === "function" ? resolver(ref) : resolver.resolve(ref);
|
|
29
|
+
if (!candidate) {
|
|
30
|
+
this.misses.add(key);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (candidate.name !== ref.name || candidate.version !== ref.version) {
|
|
34
|
+
throw new Error(`Skill resolver returned ${skillKey(candidate.name, candidate.version)} for ${key}`);
|
|
35
|
+
}
|
|
36
|
+
const resolved = resolveRegistration(candidate);
|
|
37
|
+
this.skills.set(key, resolved);
|
|
38
|
+
return resolved;
|
|
39
|
+
}
|
|
40
|
+
async loadPaths(paths) {
|
|
41
|
+
for (const basePath of paths)
|
|
42
|
+
await this.loadPath(basePath);
|
|
43
|
+
}
|
|
44
|
+
async loadPath(basePath) {
|
|
45
|
+
for (const entry of await safeDirectories(basePath)) {
|
|
46
|
+
const skillRoot = join(basePath, entry);
|
|
47
|
+
const direct = await safeRead(join(skillRoot, "SKILL.md"));
|
|
48
|
+
if (direct != null) {
|
|
49
|
+
this.register({ name: entry, source: join(skillRoot, "SKILL.md"), content: direct });
|
|
50
|
+
}
|
|
51
|
+
for (const version of await safeDirectories(skillRoot)) {
|
|
52
|
+
const versionPath = join(skillRoot, version, "SKILL.md");
|
|
53
|
+
const content = await safeRead(versionPath);
|
|
54
|
+
if (content != null)
|
|
55
|
+
this.register({ name: entry, version, source: versionPath, content });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function safeDirectories(path) {
|
|
61
|
+
try {
|
|
62
|
+
return (await readdir(path, { withFileTypes: true }))
|
|
63
|
+
.filter((entry) => entry.isDirectory())
|
|
64
|
+
.map((entry) => entry.name);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code === "ENOENT")
|
|
68
|
+
return [];
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function safeRead(path) {
|
|
73
|
+
try {
|
|
74
|
+
return await readFile(path, "utf8");
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.code === "ENOENT")
|
|
78
|
+
return null;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function skillKey(name, version) {
|
|
83
|
+
return `${name}@${version ?? ""}`;
|
|
84
|
+
}
|
|
85
|
+
function resolveRegistration(skill) {
|
|
86
|
+
if (!skill.name || !skill.source)
|
|
87
|
+
throw new Error("Skill registration requires name and source");
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
...skill,
|
|
90
|
+
fingerprint: createHash("sha256").update(skill.content).digest("hex"),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Graph, NodeDefinition } from "../core/graph.js";
|
|
2
|
+
import type { TSchema } from "typebox";
|
|
3
|
+
export declare const RUNTIME_PROTOCOL_TOOL_NAME: "__graph_complete__";
|
|
4
|
+
export interface ToolImplementation {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly label?: string;
|
|
7
|
+
readonly description?: string;
|
|
8
|
+
readonly parameters?: TSchema;
|
|
9
|
+
readonly execute?: (...args: readonly unknown[]) => unknown | Promise<unknown>;
|
|
10
|
+
readonly protocol?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface UnsafeToolResolverInput {
|
|
13
|
+
readonly graph: Graph;
|
|
14
|
+
readonly stageId: string;
|
|
15
|
+
readonly node: NodeDefinition;
|
|
16
|
+
readonly selected: readonly string[];
|
|
17
|
+
readonly hostTools: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
export type UnsafeToolResolver = (input: UnsafeToolResolverInput) => readonly string[];
|
|
20
|
+
export declare class ToolCatalog {
|
|
21
|
+
private readonly tools;
|
|
22
|
+
register(tool: ToolImplementation): void;
|
|
23
|
+
has(name: string): boolean;
|
|
24
|
+
resolve(name: string): ToolImplementation | undefined;
|
|
25
|
+
get names(): readonly string[];
|
|
26
|
+
}
|
|
27
|
+
export declare function selectNodeToolNames(graph: Graph, node: NodeDefinition): readonly string[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const RUNTIME_PROTOCOL_TOOL_NAME = "__graph_complete__";
|
|
2
|
+
export class ToolCatalog {
|
|
3
|
+
tools = new Map();
|
|
4
|
+
register(tool) {
|
|
5
|
+
if (!tool.name || tool.name === RUNTIME_PROTOCOL_TOOL_NAME) {
|
|
6
|
+
throw new Error(`Invalid business tool name: ${tool.name}`);
|
|
7
|
+
}
|
|
8
|
+
if (this.tools.has(tool.name))
|
|
9
|
+
throw new Error(`Tool already registered: ${tool.name}`);
|
|
10
|
+
this.tools.set(tool.name, Object.freeze({ ...tool }));
|
|
11
|
+
}
|
|
12
|
+
has(name) {
|
|
13
|
+
return this.tools.has(name);
|
|
14
|
+
}
|
|
15
|
+
resolve(name) {
|
|
16
|
+
return this.tools.get(name);
|
|
17
|
+
}
|
|
18
|
+
get names() {
|
|
19
|
+
return Object.freeze([...this.tools.keys()]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function selectNodeToolNames(graph, node) {
|
|
23
|
+
const policy = graph.tools ?? [];
|
|
24
|
+
const nodeTools = node.kind === "graph" ? undefined : node.tools;
|
|
25
|
+
const selected = nodeTools === "all" ? policy : (nodeTools ?? []);
|
|
26
|
+
const allowed = new Set(policy);
|
|
27
|
+
for (const name of selected) {
|
|
28
|
+
if (!allowed.has(name)) {
|
|
29
|
+
throw new Error(`Node selects tool outside Graph policy: ${name}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return Object.freeze([...new Set(selected)]);
|
|
33
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { createLoopGraphExtension } from "./adapter/loop-graph-extension.js";
|
|
2
|
+
export type { LoopGraphExtension, LoopGraphExtensionOptions, LoopGraphLimits, LoopGraphExecutionOptions, GraphExposure, } from "./adapter/loop-graph-extension.js";
|
|
3
|
+
export { Type } from "typebox";
|
|
4
|
+
export type { AgentNodeDefinition, AgentRunRequest, CodeNodeDefinition, Connection, ContextContent, ContextFrame, Entry, Graph, GraphDefinition, GraphNodeDefinition, GraphRef, NodeDefinition, NodeDefinition as Node, Route, Stage, Transition, } from "./core/graph.js";
|
|
5
|
+
export type { JsonValue, JsonSchema } from "./core/json.js";
|
|
6
|
+
export type { ResolvedSkillView, SkillRef } from "./core/skill.js";
|
|
7
|
+
export { defineMechanism } from "./core/mechanism.js";
|
|
8
|
+
export type { Mechanism, MechanismContext, MechanismContextApi, MechanismDecisionTrace, MechanismExec, MechanismExecResult, MechanismFailurePolicy, MechanismInstallation, MechanismScope, MechanismCompletionDecision, } from "./core/mechanism.js";
|
|
9
|
+
export type { NodeCompletion } from "./core/graph.js";
|
|
10
|
+
export type { ToolSet } from "./builders/refs.js";
|
|
11
|
+
export type { InvocationLimits } from "./core/limits.js";
|
|
12
|
+
export { createGraphHost, executeIsolatedGraph } from "./host/graph-host.js";
|
|
13
|
+
export { createPiGraphHost } from "./adapter/isolated-graph-session.js";
|
|
14
|
+
export type { IsolatedGraphSessionFactoryOptions as CreatePiGraphHostOptions } from "./adapter/isolated-graph-session.js";
|
|
15
|
+
export type { GraphHost, GraphHostRunOptions, CreateGraphHostOptions, ExecuteIsolatedGraphOptions, } from "./host/graph-host.js";
|
|
16
|
+
export type { GraphFailure, GraphFailureCode, GraphRunResult, RecordingMode, ReplayReference, } from "./core/result.js";
|
|
17
|
+
export { defineGraph, defineLinearGraph, defineSingleAgentGraph } from "./builders/graph.js";
|
|
18
|
+
export { agentNode, codeNode, graphNode } from "./builders/node.js";
|
|
19
|
+
export { connect, defineTransition, entry, finish, firstMatch } from "./builders/route.js";
|
|
20
|
+
export { graphRef } from "./core/graph.js";
|
|
21
|
+
export { skillRef, toolSet } from "./builders/refs.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createLoopGraphExtension } from "./adapter/loop-graph-extension.js";
|
|
2
|
+
export { Type } from "typebox";
|
|
3
|
+
export { defineMechanism } from "./core/mechanism.js";
|
|
4
|
+
export { createGraphHost, executeIsolatedGraph } from "./host/graph-host.js";
|
|
5
|
+
export { createPiGraphHost } from "./adapter/isolated-graph-session.js";
|
|
6
|
+
export { defineGraph, defineLinearGraph, defineSingleAgentGraph } from "./builders/graph.js";
|
|
7
|
+
export { agentNode, codeNode, graphNode } from "./builders/node.js";
|
|
8
|
+
export { connect, defineTransition, entry, finish, firstMatch } from "./builders/route.js";
|
|
9
|
+
export { graphRef } from "./core/graph.js";
|
|
10
|
+
export { skillRef, toolSet } from "./builders/refs.js";
|