@spendgraph/graph 0.5.0 → 0.7.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 +202 -0
- package/README.md +25 -134
- package/dist/compile/ceiling.d.ts +3 -0
- package/dist/compile/ceiling.js +1 -0
- package/dist/compile/compile.d.ts +6 -15
- package/dist/compile/compile.js +1 -17
- package/dist/compile/covered.d.ts +3 -0
- package/dist/compile/covered.js +1 -0
- package/dist/compile/edges.d.ts +5 -0
- package/dist/compile/edges.js +1 -0
- package/dist/compile/errors.d.ts +14 -0
- package/dist/compile/errors.js +1 -0
- package/dist/compile/index.d.ts +2 -0
- package/dist/compile/index.js +1 -1
- package/dist/compile/nodes.d.ts +4 -0
- package/dist/compile/nodes.js +1 -0
- package/dist/compile/reachable.d.ts +9 -0
- package/dist/compile/reachable.js +1 -0
- package/dist/execute/context.d.ts +4 -0
- package/dist/execute/context.js +1 -0
- package/dist/execute/errors.d.ts +6 -0
- package/dist/execute/errors.js +1 -0
- package/dist/execute/index.d.ts +3 -3
- package/dist/execute/index.js +1 -2
- package/dist/execute/outcome.d.ts +5 -30
- package/dist/execute/outcome.js +1 -44
- package/dist/execute/resume.d.ts +16 -0
- package/dist/execute/resume.js +1 -0
- package/dist/execute/run.d.ts +33 -0
- package/dist/execute/run.js +1 -0
- package/dist/execute/steps.d.ts +7 -0
- package/dist/execute/steps.js +1 -0
- package/dist/execute/visit.d.ts +10 -0
- package/dist/execute/visit.js +1 -0
- package/dist/execute/walk.d.ts +4 -0
- package/dist/execute/walk.js +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -2
- package/dist/node/edge.d.ts +1 -6
- package/dist/node/edge.js +1 -6
- package/dist/node/index.js +1 -2
- package/dist/node/node.d.ts +1 -7
- package/dist/node/node.js +1 -25
- package/dist/stream/events.d.ts +22 -0
- package/dist/stream/events.js +1 -0
- package/dist/stream/index.d.ts +2 -0
- package/dist/stream/index.js +1 -0
- package/dist/stream/stream.d.ts +9 -0
- package/dist/stream/stream.js +1 -0
- package/dist/types/args.d.ts +2 -15
- package/dist/types/args.js +0 -1
- package/dist/types/context.d.ts +39 -14
- package/dist/types/context.js +0 -1
- package/dist/types/edge.d.ts +1 -4
- package/dist/types/edge.js +0 -1
- package/dist/types/event.d.ts +17 -7
- package/dist/types/event.js +0 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.js +1 -1
- package/dist/types/node.d.ts +2 -10
- package/dist/types/node.js +0 -1
- package/dist/types/result.d.ts +15 -6
- package/dist/types/result.js +0 -1
- package/dist/types/spec.d.ts +14 -5
- package/dist/types/spec.js +0 -1
- package/dist/types/wait.d.ts +24 -0
- package/dist/types/wait.js +1 -0
- package/docs/nodes.mdx +59 -0
- package/docs/overview.mdx +96 -0
- package/docs/pausing.mdx +109 -0
- package/docs/running.mdx +71 -0
- package/docs/streaming.mdx +62 -0
- package/docs/wiring.mdx +76 -0
- package/package.json +11 -8
- package/dist/compile/validate.d.ts +0 -17
- package/dist/compile/validate.js +0 -51
- package/dist/execute/execute.d.ts +0 -16
- package/dist/execute/execute.js +0 -92
- package/dist/execute/step.d.ts +0 -5
- package/dist/execute/step.js +0 -19
- package/dist/execute/stream.d.ts +0 -19
- package/dist/execute/stream.js +0 -49
package/dist/execute/outcome.js
CHANGED
|
@@ -1,44 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
function isOutcome(value) {
|
|
3
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4
|
-
return false;
|
|
5
|
-
const v = value;
|
|
6
|
-
return (typeof v.output === "string" ||
|
|
7
|
-
typeof v.inputTokens === "number" ||
|
|
8
|
-
typeof v.outputTokens === "number" ||
|
|
9
|
-
Array.isArray(v.steps));
|
|
10
|
-
}
|
|
11
|
-
export function outputOf(value) {
|
|
12
|
-
const outcome = isOutcome(value) ? value : undefined;
|
|
13
|
-
return outcome && typeof outcome.output === "string" ? outcome.output : stringify(value);
|
|
14
|
-
}
|
|
15
|
-
export function tokensOf(value) {
|
|
16
|
-
if (!isOutcome(value))
|
|
17
|
-
return {};
|
|
18
|
-
const nested = Array.isArray(value.steps) && value.steps.length > 0;
|
|
19
|
-
return {
|
|
20
|
-
...(!nested && typeof value.inputTokens === "number" ? { inputTokens: value.inputTokens } : {}),
|
|
21
|
-
...(!nested && typeof value.outputTokens === "number"
|
|
22
|
-
? { outputTokens: value.outputTokens }
|
|
23
|
-
: {}),
|
|
24
|
-
...(value.model ? { model: value.model } : {}),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export function nestedSteps(value, from, source) {
|
|
28
|
-
if (!isOutcome(value) || !Array.isArray(value.steps))
|
|
29
|
-
return [];
|
|
30
|
-
return value.steps.map((step, at) => ({
|
|
31
|
-
...step,
|
|
32
|
-
index: from + at,
|
|
33
|
-
source: `${source}.${step.source}`,
|
|
34
|
-
}));
|
|
35
|
-
}
|
|
36
|
-
export function totalTokens(steps) {
|
|
37
|
-
let inputTokens = 0;
|
|
38
|
-
let outputTokens = 0;
|
|
39
|
-
for (const step of steps) {
|
|
40
|
-
inputTokens += step.inputTokens ?? 0;
|
|
41
|
-
outputTokens += step.outputTokens ?? 0;
|
|
42
|
-
}
|
|
43
|
-
return { inputTokens, outputTokens };
|
|
44
|
-
}
|
|
1
|
+
import{sealed as u,stringify as f}from"./steps.js";function r(t){if(!t||typeof t!="object"||Array.isArray(t))return!1;const n=t;return typeof n.output=="string"||typeof n.inputTokens=="number"||typeof n.outputTokens=="number"||Array.isArray(n.steps)}function k(t){return r(t)&&typeof t.output=="string"?t.output:f(t)}function s(t){if(!(typeof t!="number"||!Number.isFinite(t)||t<0))return Math.trunc(t)}const p=["cacheReadTokens","cacheWriteTokens","citationTokens","reasoningTokens"];function c(t){const n={};for(const o of p)t[o]!==void 0&&(n[o]=s(t[o])??0);return n}function m(t){if(!r(t))return{};const n=Array.isArray(t.steps)&&t.steps.length>0,o=n?void 0:s(t.inputTokens),e=n?void 0:s(t.outputTokens);return{...o===void 0?{}:{inputTokens:o},...e===void 0?{}:{outputTokens:e},...n?{}:c(t),...t.model?{model:t.model}:{}}}const d=t=>!!t&&typeof t=="object"&&!Array.isArray(t);function A(t,n,o){return!r(t)||!Array.isArray(t.steps)?[]:t.steps.filter(d).map((e,i)=>u({...e,index:n+i,source:`${o}.${e.source??"step"}`}))}import{totalTokens as g}from"@spendgraph/sdk";export{A as nestedSteps,k as outputOf,m as tokensOf,g as totalTokens};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GraphResult, GraphRunOptions } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* How to re-enter a graph at the node that stopped to ask.
|
|
4
|
+
*
|
|
5
|
+
* The steps it hands back stop short of that node: a nested run resumed by its
|
|
6
|
+
* own id comes back carrying its whole history, the half before the pause
|
|
7
|
+
* included, so seeding that half again would record it twice. What the outer
|
|
8
|
+
* run owns is everything before the node; what the node did is the node's to
|
|
9
|
+
* report.
|
|
10
|
+
*
|
|
11
|
+
* The **last** time the node ran, not the first. A cycle visits it more than
|
|
12
|
+
* once, and a run that went round three times before stopping paused on the
|
|
13
|
+
* third — cutting at the first would throw away every round it had already
|
|
14
|
+
* paid for.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resumingAt(result: GraphResult, node: string): GraphRunOptions;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function n(t,e){for(let s=t.steps.length-1;s>=0;s--)if(t.steps[s].source===e)return s;return-1}function p(t,e){const s=n(t,e);return{entry:e,steps:s<0?t.steps:t.steps.slice(0,s),outputs:t.outputs}}export{p as resumingAt};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
+
import type { Edge, GraphEvent, GraphResult, GraphRunOptions, Node, Paused } from "../types/index.js";
|
|
3
|
+
export type Narrator = (event: GraphEvent) => void;
|
|
4
|
+
/** A graph as compiled: what `execute` and `stream` walk. */
|
|
5
|
+
export interface Compiled {
|
|
6
|
+
entry: string;
|
|
7
|
+
maxSteps: number;
|
|
8
|
+
nodes: Map<string, Node<never>>;
|
|
9
|
+
edgesFrom: Map<string, Edge[]>;
|
|
10
|
+
onFailure?: string;
|
|
11
|
+
now: () => number;
|
|
12
|
+
}
|
|
13
|
+
export interface Run {
|
|
14
|
+
compiled: Compiled;
|
|
15
|
+
values: Record<string, unknown>;
|
|
16
|
+
outputs: Record<string, unknown>;
|
|
17
|
+
steps: RolloutStep[];
|
|
18
|
+
visited: number;
|
|
19
|
+
/** The node the walk arrived from, kept on the run so every context reads the same one. */
|
|
20
|
+
cameFrom: string | null;
|
|
21
|
+
/** What sent the walk to the failure node, or null while nothing has fallen over. */
|
|
22
|
+
failure: {
|
|
23
|
+
node: string;
|
|
24
|
+
error: string;
|
|
25
|
+
} | null;
|
|
26
|
+
lastOutput: string;
|
|
27
|
+
startedAt: number;
|
|
28
|
+
narrate: Narrator;
|
|
29
|
+
}
|
|
30
|
+
export declare function beginRun(compiled: Compiled, values: Record<string, unknown>, opts: GraphRunOptions, narrate: Narrator): Run;
|
|
31
|
+
export declare function finished(run: Run, status: "completed" | "failed", error?: string): GraphResult;
|
|
32
|
+
/** A run stopped at a node that asked something: said once for the stream, once on the result. */
|
|
33
|
+
export declare function parked(run: Run, asked: Paused, node: string, index: number): GraphResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{totalTokens as p}from"./outcome.js";import{sealed as i}from"./steps.js";function l(t,n,e,o){const u=(e.steps??[]).map(i);return{compiled:t,values:n,outputs:{...e.outputs},steps:u,visited:0,cameFrom:null,failure:null,lastOutput:"",startedAt:t.now(),narrate:o}}function r(t,n,e){return{status:n,output:n==="completed"?t.lastOutput:"",error:e,steps:t.steps,outputs:t.outputs,latencyMs:t.compiled.now()-t.startedAt,...p(t.steps)}}function d(t,n,e,o){return t.narrate({type:"paused",node:e,index:o,waitingOn:n.waitingOn,...n.runId?{runId:n.runId}:{}}),{...r(t,"completed"),waitingOn:n.waitingOn,...n.runId?{waitingFor:n.runId}:{}}}export{l as beginRun,r as finished,d as parked};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
+
/** A string is itself, anything else is JSON; what cannot be written says so rather than reading as nothing. */
|
|
3
|
+
export declare function stringify(value: unknown): string;
|
|
4
|
+
export declare function reasonOf(err: unknown): string;
|
|
5
|
+
export declare function failedStep(index: number, source: string, input: Record<string, unknown>, latencyMs: number, error: string): RolloutStep;
|
|
6
|
+
/** Frozen, so a node handed the record cannot rewrite an earlier step's output or cost. */
|
|
7
|
+
export declare function sealed(step: RolloutStep): RolloutStep;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function f(t){if(t==null)return"";if(typeof t=="string")return t;try{const n=JSON.stringify(t);return n===void 0?`[not recordable: ${typeof t}]`:n}catch(n){return`[not recordable: ${i(n)}]`}}function i(t){return t instanceof Error?t.message:String(t)}function u(t,n,r,e,o){return{index:t,source:n,input:r,output:"",status:"failed",error:o,latencyMs:e}}function c(t){return Object.freeze(t)}export{u as failedStep,i as reasonOf,c as sealed,f as stringify};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Node } from "../types/index.js";
|
|
2
|
+
import type { Run } from "./run.js";
|
|
3
|
+
export type Visited = {
|
|
4
|
+
value: unknown;
|
|
5
|
+
} | {
|
|
6
|
+
error: string;
|
|
7
|
+
why: string;
|
|
8
|
+
};
|
|
9
|
+
/** Runs one node: builds its input, checks its args, runs it, and records a step whichever way it went. */
|
|
10
|
+
export declare function visit(run: Run, node: Node<never>): Promise<Visited>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{FieldValidationError as l,validateFields as m}from"@spendgraph/sdk";import{contextFor as f}from"./context.js";import{givenBadArgs as g,inputThrew as x,nodeFailed as h}from"./errors.js";import{nestedSteps as y,outputOf as O,tokensOf as A}from"./outcome.js";import{failedStep as F,reasonOf as u,sealed as d}from"./steps.js";function M(e,r){if(!e.args?.length)return null;const t=m(r,e.args);return t.length>0?new l(t).message:null}const c=e=>e.run.compiled.now()-e.startedAt;function p(e,r,t,s){const{run:n,node:o,index:a}=e,i=c(e);return n.steps.push(d(F(a,o.name,r,i,t))),n.narrate({type:"node_end",node:o.name,index:a,status:"failed",error:t,latencyMs:i}),{error:s(o.name,t),why:t}}function _(e,r,t){const{run:s,node:n,index:o}=e,a=c(e);s.outputs[n.name]=t,s.lastOutput=O(t),s.steps.push(d({index:o,source:n.name,input:r,output:s.lastOutput,status:"completed",latencyMs:a,...A(t)}));for(const i of y(t,s.steps.length,n.name))s.steps.push(i);return s.narrate({type:"node_end",node:n.name,index:o,status:"completed",output:t,latencyMs:a}),{value:t}}async function B(e,r){e.visited+=1;const t={run:e,node:r,index:e.steps.length,startedAt:e.compiled.now()},s=f(e,r.name);let n;try{n=r.input?r.input(s):e.values}catch(a){return p(t,{},u(a),x)}e.narrate({type:"node_start",node:r.name,index:t.index,input:n});const o=M(r,n);if(o)return p(t,n,o,g);try{return _(t,n,await r.run(n,s))}catch(a){return p(t,n,u(a),h)}}export{B as visit};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GraphResult, GraphRunOptions } from "../types/index.js";
|
|
2
|
+
import { type Compiled, type Narrator } from "./run.js";
|
|
3
|
+
/** Walks a compiled graph. Never throws: every exit is a result carrying the steps taken. */
|
|
4
|
+
export declare function executeGraph(compiled: Compiled, values: Record<string, unknown>, opts?: GraphRunOptions, narrate?: Narrator): Promise<GraphResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{pausedInside as x}from"../types/index.js";import{contextFor as g}from"./context.js";import{choosingThrew as p,hitTheCeiling as w,nowhereToStart as y}from"./errors.js";import{beginRun as F,finished as a,parked as v}from"./run.js";import{reasonOf as k}from"./steps.js";import{visit as S}from"./visit.js";function T(e,t){let n;for(const o of e)if(!o.when||(n??=t(),o.when(n)))return o.to;return null}function A(e,t){try{const n=e.compiled.edgesFrom.get(t.name)??[];return{next:T(n,()=>g(e,t.name))}}catch(n){return{error:p(t.name,k(n))}}}async function j(e,t,n={},o=()=>{}){const r=F(e,t,n,o),u=n.entry??e.entry;if(!e.nodes.has(u))return a(r,"failed",y(u,[...e.nodes.keys()]));let i=u,c=null;for(;i;){if(r.visited>=e.maxSteps)return a(r,"failed",w(e.maxSteps,i));const s=e.nodes.get(i),l=r.steps.length;r.cameFrom=c;const f=await S(r,s);if("error"in f){const h=e.onFailure;if(!h||s.name===h)return a(r,"failed",f.error);r.failure={node:s.name,error:f.why},c=s.name,i=h;continue}const d=x(f.value);if(d)return v(r,d,s.name,l);const m=A(r,s);if("error"in m)return a(r,"failed",m.error);c=i,i=m.next}return a(r,"completed")}export{j as executeGraph};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export { type Graph, type GraphOptions, graph } from "./compile/index.js";
|
|
2
|
-
export
|
|
1
|
+
export { type Graph, type GraphOptions, GraphSpecError, graph } from "./compile/index.js";
|
|
2
|
+
export { resumingAt } from "./execute/index.js";
|
|
3
3
|
export type { NodeSpec } from "./node/index.js";
|
|
4
4
|
export { edge, end, node } from "./node/index.js";
|
|
5
|
-
export type {
|
|
5
|
+
export type { GraphStream } from "./stream/index.js";
|
|
6
|
+
export type { ArgSpec, ArgsOf, ArgValue, Edge, Emit, GraphContext, GraphEvent, GraphResult, GraphRunOptions, GraphSpec, Node, Note, Paused, Wait, } from "./types/index.js";
|
|
7
|
+
export { pausedInside } from "./types/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { edge, end, node } from "./node/index.js";
|
|
1
|
+
import{GraphSpecError as o,graph as p}from"./compile/index.js";import{resumingAt as m}from"./execute/index.js";import{edge as f,end as n,node as x}from"./node/index.js";import{pausedInside as g}from"./types/index.js";export{o as GraphSpecError,f as edge,n as end,p as graph,x as node,g as pausedInside,m as resumingAt};
|
package/dist/node/edge.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { Edge } from "../types/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* An edge from one node to the next, or to the end of the run.
|
|
4
|
-
*
|
|
5
|
-
* A helper so the argument order is fixed by something other than memory:
|
|
6
|
-
* `edge("a", "b")` reads in the direction the run travels.
|
|
7
|
-
*/
|
|
2
|
+
/** An edge from one node to the next, or to the end of the run. */
|
|
8
3
|
export declare function edge(from: string, to: string | null, when?: Edge["when"]): Edge;
|
|
9
4
|
/** Ends the run after `from`. The same as `edge(from, null)`, said out loud. */
|
|
10
5
|
export declare function end(from: string, when?: Edge["when"]): Edge;
|
package/dist/node/edge.js
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
|
|
2
|
-
return when ? { from, to, when } : { from, to };
|
|
3
|
-
}
|
|
4
|
-
export function end(from, when) {
|
|
5
|
-
return edge(from, null, when);
|
|
6
|
-
}
|
|
1
|
+
function t(n,e,r){return r?{from:n,to:e,when:r}:{from:n,to:e}}function u(n,e){return t(n,null,e)}export{t as edge,u as end};
|
package/dist/node/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { node } from "./node.js";
|
|
1
|
+
import{edge as r,end as d}from"./edge.js";import{node as m}from"./node.js";export{r as edge,d as end,m as node};
|
package/dist/node/node.d.ts
CHANGED
|
@@ -5,11 +5,5 @@ export interface NodeSpec<T extends readonly ArgSpec[]> {
|
|
|
5
5
|
input?: (ctx: GraphContext) => Record<string, unknown>;
|
|
6
6
|
run(input: ArgsOf<T>, ctx: GraphContext): Promise<unknown> | unknown;
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
9
|
-
* Declares a node, checking what a graph cannot check later.
|
|
10
|
-
*
|
|
11
|
-
* The same argument as `tool()`: a duplicated argument silently loses one, and a
|
|
12
|
-
* hyphenated name reads badly in the step record that is the only trace of a
|
|
13
|
-
* failed run.
|
|
14
|
-
*/
|
|
8
|
+
/** Declares a node, checking what a graph cannot check later. */
|
|
15
9
|
export declare function node<const T extends readonly ArgSpec[]>(spec: NodeSpec<T>): Node<ArgsOf<T>>;
|
package/dist/node/node.js
CHANGED
|
@@ -1,25 +1 @@
|
|
|
1
|
-
const
|
|
2
|
-
export function node(spec) {
|
|
3
|
-
if (!VALID_NAME.test(spec.name)) {
|
|
4
|
-
throw new Error(`Node name "${spec.name}" must be letters, digits and underscores, starting with a letter.`);
|
|
5
|
-
}
|
|
6
|
-
const seen = new Set();
|
|
7
|
-
for (const arg of spec.args ?? []) {
|
|
8
|
-
if (!VALID_NAME.test(arg.name)) {
|
|
9
|
-
throw new Error(`Node "${spec.name}" argument "${arg.name}" is not a usable name.`);
|
|
10
|
-
}
|
|
11
|
-
if (seen.has(arg.name)) {
|
|
12
|
-
throw new Error(`Node "${spec.name}" declares "${arg.name}" twice.`);
|
|
13
|
-
}
|
|
14
|
-
seen.add(arg.name);
|
|
15
|
-
if (arg.type === "enum" && !arg.options?.length) {
|
|
16
|
-
throw new Error(`Node "${spec.name}" argument "${arg.name}" is an enum with no options, so nothing can satisfy it.`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
name: spec.name,
|
|
21
|
-
args: spec.args?.map((a) => ({ ...a, options: a.options ? [...a.options] : undefined })),
|
|
22
|
-
input: spec.input,
|
|
23
|
-
run: spec.run,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
1
|
+
const o=/^[A-Za-z_][A-Za-z0-9_]*$/;function a(e){if(!o.test(e.name))throw new Error(`Node name "${e.name}" must be letters, digits and underscores, starting with a letter.`);const t=new Set;for(const n of e.args??[]){if(!o.test(n.name))throw new Error(`Node "${e.name}" argument "${n.name}" is not a usable name.`);if(t.has(n.name))throw new Error(`Node "${e.name}" declares "${n.name}" twice.`);if(t.add(n.name),n.type==="enum"&&!n.options?.length)throw new Error(`Node "${e.name}" argument "${n.name}" is an enum with no options, so nothing can satisfy it.`)}return{name:e.name,args:e.args?.map(n=>({...n,options:n.options?[...n.options]:void 0})),input:e.input,run:e.run}}export{a as node};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GraphEvent } from "../types/index.js";
|
|
2
|
+
/** Events queued between the run and its one reader; a run never waits on a slow reader. */
|
|
3
|
+
export declare class Events {
|
|
4
|
+
private queued;
|
|
5
|
+
private head;
|
|
6
|
+
private wake;
|
|
7
|
+
private closed;
|
|
8
|
+
private draining;
|
|
9
|
+
push(event: GraphEvent): void;
|
|
10
|
+
close(): void;
|
|
11
|
+
private release;
|
|
12
|
+
private takeNext;
|
|
13
|
+
/**
|
|
14
|
+
* The events, once.
|
|
15
|
+
*
|
|
16
|
+
* A second reader is refused rather than served: both take from the same
|
|
17
|
+
* queue, so each would see roughly half the run — which reads as a graph that
|
|
18
|
+
* dropped tokens rather than as the mistake it is. Two watchers want one
|
|
19
|
+
* reader writing into something they can both read, not two readers.
|
|
20
|
+
*/
|
|
21
|
+
drain(): AsyncGenerator<GraphEvent>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class s{queued=[];head=0;wake=null;closed=!1;draining=!1;push(e){this.queued.push(e),this.release()}close(){this.closed=!0,this.release()}release(){const e=this.wake;this.wake=null,e?.()}takeNext(){const e=this.queued[this.head++];return this.head===this.queued.length&&(this.queued=[],this.head=0),e}async*drain(){if(this.draining)throw new Error("This stream is already being read. Iterate it once, or await `result` instead \u2014 to show a run to more than one watcher, keep the events somewhere they can both read.");for(this.draining=!0;;){for(;this.head<this.queued.length;)yield this.takeNext();if(this.closed)return;await new Promise(e=>{this.wake=e})}}}export{s as Events};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{streamGraph as e}from"./stream.js";export{e as streamGraph};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Compiled } from "../execute/index.js";
|
|
2
|
+
import type { GraphEvent, GraphResult, GraphRunOptions } from "../types/index.js";
|
|
3
|
+
/** A run as it happens, and the same run once it is over. Iterable once. */
|
|
4
|
+
export interface GraphStream extends AsyncIterable<GraphEvent> {
|
|
5
|
+
/** Resolves whether or not anyone iterated. */
|
|
6
|
+
result: Promise<GraphResult>;
|
|
7
|
+
}
|
|
8
|
+
/** Runs a compiled graph, narrating it; the run starts on the call, not on the first read. */
|
|
9
|
+
export declare function streamGraph(compiled: Compiled, values: Record<string, unknown>, opts?: GraphRunOptions): GraphStream;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{executeGraph as c}from"../execute/index.js";import{Events as u}from"./events.js";function l(n,o,s={}){const t=new u,r=(async()=>{try{const e=await c(n,o,s,a=>t.push(a));return t.push({type:"result",result:e}),e}finally{t.close()}})();return r.catch(()=>{}),{result:r,[Symbol.asyncIterator]:()=>t.drain()}}export{l as streamGraph};
|
package/dist/types/args.d.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { FieldType } from "@spendgraph/sdk";
|
|
2
|
-
/**
|
|
3
|
-
* A declared argument, as written.
|
|
4
|
-
*
|
|
5
|
-
* Structurally a `FieldSpec` with every field readonly, because that is what an
|
|
6
|
-
* `as const` array produces — and `FieldSpec.options` being a mutable `string[]`
|
|
7
|
-
* is enough to make the whole tuple fail the constraint and inference fall back
|
|
8
|
-
* to nothing, silently.
|
|
9
|
-
*/
|
|
2
|
+
/** A declared argument as written: a readonly `FieldSpec`, which is what an `as const` array produces. */
|
|
10
3
|
export interface ArgSpec {
|
|
11
4
|
readonly name: string;
|
|
12
5
|
readonly type: FieldType;
|
|
@@ -41,13 +34,7 @@ type Required<T> = T extends {
|
|
|
41
34
|
type Optional<T> = T extends {
|
|
42
35
|
required: true;
|
|
43
36
|
} ? never : T;
|
|
44
|
-
/**
|
|
45
|
-
* The object `run` is handed, derived from the args you declared.
|
|
46
|
-
*
|
|
47
|
-
* Declare `args` with `as const` and the handler's parameter types follow, so a
|
|
48
|
-
* renamed argument is a compile error rather than an `undefined` discovered
|
|
49
|
-
* halfway through a graph.
|
|
50
|
-
*/
|
|
37
|
+
/** The object `run` is handed, derived from the args declared `as const`. */
|
|
51
38
|
export type ArgsOf<T extends readonly ArgSpec[]> = {
|
|
52
39
|
[F in Required<T[number]> as F["name"]]: ArgValue<F>;
|
|
53
40
|
} & {
|
package/dist/types/args.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/context.d.ts
CHANGED
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
-
import type { Emit } from "./event.js";
|
|
3
|
-
/**
|
|
4
|
-
* What a node can see while it decides and while it runs.
|
|
5
|
-
*
|
|
6
|
-
* `outputs` is the only channel between nodes. A shared mutable bag would let
|
|
7
|
-
* node four depend on a key node two happens to set — a dependency the graph
|
|
8
|
-
* does not declare and compilation cannot check.
|
|
9
|
-
*/
|
|
2
|
+
import type { Emit, Note } from "./event.js";
|
|
3
|
+
/** What a node can see while it decides and while it runs; `outputs` is the only channel between nodes. */
|
|
10
4
|
export interface GraphContext {
|
|
11
5
|
/** The object `execute` was called with. */
|
|
12
6
|
values: Record<string, unknown>;
|
|
13
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* What each node that has finished returned, by name.
|
|
9
|
+
*
|
|
10
|
+
* By name, so a node a cycle comes back to overwrites what it returned last
|
|
11
|
+
* time — a retry reads the attempt it just made, not the first one. Keeping
|
|
12
|
+
* every pass would make `outputs` a different shape for a cyclic graph than a
|
|
13
|
+
* straight one, and every reader would owe a check it usually does not need.
|
|
14
|
+
*/
|
|
14
15
|
outputs: Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* The node that routed here, or null on the entry node.
|
|
18
|
+
*
|
|
19
|
+
* What a node reached from more than one place otherwise has to guess at, by
|
|
20
|
+
* probing `outputs` for whichever branch's node happens to have run. That
|
|
21
|
+
* guess is wrong in a graph with a cycle: `outputs` keeps the last value a
|
|
22
|
+
* node returned for the rest of the run, so a join reached a second time
|
|
23
|
+
* reads a branch it did not come through this pass.
|
|
24
|
+
*/
|
|
25
|
+
from: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* What failed on the way here, for a node reached by `onFailure`.
|
|
28
|
+
*
|
|
29
|
+
* Null everywhere else, and on the failure node itself when the run reached
|
|
30
|
+
* it through an ordinary edge — which is how a compensating node tells "I am
|
|
31
|
+
* cleaning up after a crash" from "I was walked through in the normal way".
|
|
32
|
+
*/
|
|
33
|
+
failure: {
|
|
34
|
+
node: string;
|
|
35
|
+
error: string;
|
|
36
|
+
} | null;
|
|
15
37
|
/** The steps recorded so far, in order. A copy; writing to it does nothing. */
|
|
16
38
|
steps: RolloutStep[];
|
|
39
|
+
/** Sends a token out of the run, tagged with this node. A no-op unless someone is streaming. */
|
|
40
|
+
emit: Emit;
|
|
17
41
|
/**
|
|
18
|
-
* Sends
|
|
42
|
+
* Sends structured progress out of the run, tagged with this node.
|
|
19
43
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
44
|
+
* `emit` is for what the model is saying; this is for what the run is doing —
|
|
45
|
+
* which step of how many, what it found, how far along. A node reaches it
|
|
46
|
+
* through the context rather than closing over the caller's, so a step defined
|
|
47
|
+
* in another module can report progress without being handed a channel.
|
|
23
48
|
*/
|
|
24
|
-
|
|
49
|
+
note: Note;
|
|
25
50
|
}
|
package/dist/types/context.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/edge.d.ts
CHANGED
|
@@ -4,9 +4,6 @@ export interface Edge {
|
|
|
4
4
|
from: string;
|
|
5
5
|
/** The next node, or null to end the run here. */
|
|
6
6
|
to: string | null;
|
|
7
|
-
/**
|
|
8
|
-
* Taken only when true. Tried in declaration order, first match wins — so an
|
|
9
|
-
* unconditional edge is the default branch and belongs last.
|
|
10
|
-
*/
|
|
7
|
+
/** Taken only when true. */
|
|
11
8
|
when?: (ctx: GraphContext) => boolean;
|
|
12
9
|
}
|
package/dist/types/edge.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/event.d.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import type { GraphResult } from "./result.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Every event names the node it came from, because a stream carrying tokens
|
|
6
|
-
* from four nodes is unreadable without it — and which nodes are worth showing
|
|
7
|
-
* is the consumer's decision, not the graph's.
|
|
8
|
-
*/
|
|
2
|
+
import type { Wait } from "./wait.js";
|
|
3
|
+
/** What a run says about itself while it is still running; every event names its node. */
|
|
9
4
|
export type GraphEvent = {
|
|
10
5
|
type: "node_start";
|
|
11
6
|
node: string;
|
|
@@ -16,6 +11,11 @@ export type GraphEvent = {
|
|
|
16
11
|
type: "token";
|
|
17
12
|
node: string;
|
|
18
13
|
text: string;
|
|
14
|
+
} | {
|
|
15
|
+
/** Whatever a node chose to say about its own progress, as data. */
|
|
16
|
+
type: "note";
|
|
17
|
+
node: string;
|
|
18
|
+
data: unknown;
|
|
19
19
|
} | {
|
|
20
20
|
type: "node_end";
|
|
21
21
|
node: string;
|
|
@@ -24,9 +24,19 @@ export type GraphEvent = {
|
|
|
24
24
|
output?: unknown;
|
|
25
25
|
error?: string;
|
|
26
26
|
latencyMs: number;
|
|
27
|
+
} | {
|
|
28
|
+
/** A node returned a run that stopped to ask; the walk stops here. */
|
|
29
|
+
type: "paused";
|
|
30
|
+
node: string;
|
|
31
|
+
index: number;
|
|
32
|
+
waitingOn: Wait;
|
|
33
|
+
/** The nested run to resume, where it named one. */
|
|
34
|
+
runId?: string;
|
|
27
35
|
} | {
|
|
28
36
|
type: "result";
|
|
29
37
|
result: GraphResult;
|
|
30
38
|
};
|
|
31
39
|
/** Where a node's tokens go. A run that nobody is streaming drops them. */
|
|
32
40
|
export type Emit = (text: string) => void;
|
|
41
|
+
/** Where a node's structured progress goes. Dropped the same way when nobody is listening. */
|
|
42
|
+
export type Note = (data: unknown) => void;
|
package/dist/types/event.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export type { ArgSpec, ArgsOf, ArgValue } from "./args.js";
|
|
2
2
|
export type { GraphContext } from "./context.js";
|
|
3
3
|
export type { Edge } from "./edge.js";
|
|
4
|
-
export type { Emit, GraphEvent } from "./event.js";
|
|
4
|
+
export type { Emit, GraphEvent, Note } from "./event.js";
|
|
5
5
|
export type { Node } from "./node.js";
|
|
6
|
-
export type { GraphResult } from "./result.js";
|
|
6
|
+
export type { GraphResult, GraphRunOptions } from "./result.js";
|
|
7
7
|
export type { GraphSpec } from "./spec.js";
|
|
8
|
+
export { type Paused, pausedInside, type Wait } from "./wait.js";
|
package/dist/types/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
import{pausedInside as o}from"./wait.js";export{o as pausedInside};
|
package/dist/types/node.d.ts
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import type { FieldSpec } from "@spendgraph/sdk";
|
|
2
2
|
import type { GraphContext } from "./context.js";
|
|
3
|
-
/**
|
|
4
|
-
* One unit of work in a graph.
|
|
5
|
-
*
|
|
6
|
-
* `input` is what makes a node reusable: the node says what it needs, the graph
|
|
7
|
-
* says where it comes from. Without it every node has to know the whole run.
|
|
8
|
-
*/
|
|
3
|
+
/** One unit of work in a graph. */
|
|
9
4
|
export interface Node<Args = Record<string, unknown>> {
|
|
10
5
|
/** Unique in the graph, and the key its output is stored under. */
|
|
11
6
|
name: string;
|
|
12
|
-
/**
|
|
13
|
-
* What this node needs, validated before `run` does any work. Halfway through
|
|
14
|
-
* a graph is the most expensive place to discover a typo.
|
|
15
|
-
*/
|
|
7
|
+
/** What this node needs, validated before `run` does any work. */
|
|
16
8
|
args?: FieldSpec[];
|
|
17
9
|
/** The edge's data half: an edge says what runs next, this says on what. */
|
|
18
10
|
input?: (ctx: GraphContext) => Record<string, unknown>;
|
package/dist/types/node.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/result.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* A graph run is a rollout with several steps, so this hands straight to
|
|
6
|
-
* `report()` rather than being translated first.
|
|
7
|
-
*/
|
|
2
|
+
import type { Wait } from "./wait.js";
|
|
3
|
+
/** What one run produced, in the shape of a rollout. */
|
|
8
4
|
export interface GraphResult {
|
|
9
5
|
status: "completed" | "failed";
|
|
10
6
|
/** The last node's return, stringified the way a tool result is. */
|
|
@@ -17,4 +13,17 @@ export interface GraphResult {
|
|
|
17
13
|
/** Summed across every step, nested ones included. */
|
|
18
14
|
inputTokens: number;
|
|
19
15
|
outputTokens: number;
|
|
16
|
+
/** The question a node stopped to ask, where one did; `status` still says the graph itself ran. */
|
|
17
|
+
waitingOn?: Wait;
|
|
18
|
+
/** The nested run to resume, where it named one. */
|
|
19
|
+
waitingFor?: string;
|
|
20
|
+
}
|
|
21
|
+
/** What a run may be given beyond its values, mostly for picking up where an earlier run stopped. */
|
|
22
|
+
export interface GraphRunOptions {
|
|
23
|
+
/** What nodes that already ran returned; seeded into `outputs`, not re-run, no steps of their own. */
|
|
24
|
+
outputs?: Record<string, unknown>;
|
|
25
|
+
/** Steps an earlier run already took; `index` carries on from them, `maxSteps` does not count them. */
|
|
26
|
+
steps?: RolloutStep[];
|
|
27
|
+
/** Where the walk starts instead of the compiled entry; only has to exist, reachability was checked at build. */
|
|
28
|
+
entry?: string;
|
|
20
29
|
}
|
package/dist/types/result.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types/spec.d.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import type { Edge } from "./edge.js";
|
|
2
2
|
import type { Node } from "./node.js";
|
|
3
3
|
export interface GraphSpec {
|
|
4
|
-
/** Where a run starts. */
|
|
4
|
+
/** Where a run starts, unless `GraphRunOptions.entry` names somewhere else. */
|
|
5
5
|
entry: string;
|
|
6
6
|
nodes: Node<never>[];
|
|
7
7
|
edges?: Edge[];
|
|
8
|
+
/** How many nodes a run may visit before it is called a loop; a nested run's steps do not count. */
|
|
9
|
+
maxSteps?: number;
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
11
|
+
* Where the walk goes when a node fails, instead of ending the run.
|
|
12
|
+
*
|
|
13
|
+
* A node that throws otherwise ends the walk where it stands, which is the
|
|
14
|
+
* wrong ending for a run that has already done something outside itself: the
|
|
15
|
+
* seat is held, the card is charged, and nothing is left to put them back.
|
|
16
|
+
* Naming a node here gives those steps somewhere to be undone, and the
|
|
17
|
+
* handler reads `ctx.failure` for what fell over and why.
|
|
10
18
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
19
|
+
* It does not catch its own failure — a handler that throws ends the run, or
|
|
20
|
+
* a graph could compensate forever — and it does not catch an edge condition
|
|
21
|
+
* throwing, since choosing where to go next is the part that is broken there.
|
|
13
22
|
*/
|
|
14
|
-
|
|
23
|
+
onFailure?: string;
|
|
15
24
|
}
|
package/dist/types/spec.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** A run that stopped to ask something. Structural, so `harness`'s own `Wait` satisfies it without an import. */
|
|
2
|
+
export interface Wait {
|
|
3
|
+
question: string;
|
|
4
|
+
/** Whatever answering it needs — options to pick from, a diff, an id. */
|
|
5
|
+
detail?: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** A paused run found inside a node's return. */
|
|
8
|
+
export interface Paused {
|
|
9
|
+
waitingOn: Wait;
|
|
10
|
+
/** The id the inner run is stored under — the one a caller has to resume. */
|
|
11
|
+
runId?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A node's return that is really a question, recognised by its shape rather than
|
|
15
|
+
* a marker.
|
|
16
|
+
*
|
|
17
|
+
* The id is read from `runId` or from `waitingFor`, which are the same thing
|
|
18
|
+
* under two names: a stored run calls it `runId`, and a graph that parked on one
|
|
19
|
+
* reports it as `waitingFor`. Reading only the first lost it at the second level
|
|
20
|
+
* of nesting — a graph inside a graph surfaced the question and not the run to
|
|
21
|
+
* answer it against, which is the one part of a pause a caller cannot work out
|
|
22
|
+
* for itself.
|
|
23
|
+
*/
|
|
24
|
+
export declare function pausedInside(value: unknown): Paused | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function o(r){if(!r||typeof r!="object")return null;const n=r,t=n.waitingOn;if(!t||typeof t!="object"||typeof t.question!="string")return null;const i=typeof n.runId=="string"?n.runId:n.waitingFor;return{waitingOn:t,...typeof i=="string"?{runId:i}:{}}}export{o as pausedInside};
|