@spendgraph/graph 0.6.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.
Files changed (71) hide show
  1. package/README.md +2 -0
  2. package/dist/compile/ceiling.d.ts +3 -0
  3. package/dist/compile/ceiling.js +1 -0
  4. package/dist/compile/compile.d.ts +5 -14
  5. package/dist/compile/compile.js +1 -1
  6. package/dist/compile/covered.d.ts +3 -0
  7. package/dist/compile/covered.js +1 -0
  8. package/dist/compile/edges.d.ts +5 -0
  9. package/dist/compile/edges.js +1 -0
  10. package/dist/compile/errors.d.ts +14 -0
  11. package/dist/compile/errors.js +1 -0
  12. package/dist/compile/index.d.ts +2 -0
  13. package/dist/compile/index.js +1 -1
  14. package/dist/compile/nodes.d.ts +4 -0
  15. package/dist/compile/nodes.js +1 -0
  16. package/dist/compile/reachable.d.ts +9 -0
  17. package/dist/compile/reachable.js +1 -0
  18. package/dist/execute/context.d.ts +4 -0
  19. package/dist/execute/context.js +1 -0
  20. package/dist/execute/errors.d.ts +6 -0
  21. package/dist/execute/errors.js +1 -0
  22. package/dist/execute/index.d.ts +3 -3
  23. package/dist/execute/index.js +1 -1
  24. package/dist/execute/outcome.d.ts +4 -25
  25. package/dist/execute/outcome.js +1 -1
  26. package/dist/execute/resume.d.ts +16 -0
  27. package/dist/execute/resume.js +1 -0
  28. package/dist/execute/run.d.ts +33 -0
  29. package/dist/execute/run.js +1 -0
  30. package/dist/execute/steps.d.ts +7 -0
  31. package/dist/execute/steps.js +1 -0
  32. package/dist/execute/visit.d.ts +10 -0
  33. package/dist/execute/visit.js +1 -0
  34. package/dist/execute/walk.d.ts +4 -0
  35. package/dist/execute/walk.js +1 -0
  36. package/dist/index.d.ts +4 -3
  37. package/dist/index.js +1 -1
  38. package/dist/node/edge.d.ts +1 -6
  39. package/dist/node/node.d.ts +1 -7
  40. package/dist/node/node.js +1 -1
  41. package/dist/stream/events.d.ts +22 -0
  42. package/dist/stream/events.js +1 -0
  43. package/dist/stream/index.d.ts +2 -0
  44. package/dist/stream/index.js +1 -0
  45. package/dist/stream/stream.d.ts +9 -0
  46. package/dist/stream/stream.js +1 -0
  47. package/dist/types/args.d.ts +2 -15
  48. package/dist/types/context.d.ts +39 -14
  49. package/dist/types/edge.d.ts +1 -4
  50. package/dist/types/event.d.ts +9 -11
  51. package/dist/types/index.d.ts +1 -1
  52. package/dist/types/node.d.ts +2 -10
  53. package/dist/types/result.d.ts +6 -46
  54. package/dist/types/spec.d.ts +13 -4
  55. package/dist/types/wait.d.ts +9 -17
  56. package/dist/types/wait.js +1 -1
  57. package/docs/nodes.mdx +59 -0
  58. package/docs/overview.mdx +96 -0
  59. package/docs/pausing.mdx +109 -0
  60. package/docs/running.mdx +71 -0
  61. package/docs/streaming.mdx +62 -0
  62. package/docs/wiring.mdx +76 -0
  63. package/package.json +10 -8
  64. package/dist/compile/validate.d.ts +0 -49
  65. package/dist/compile/validate.js +0 -1
  66. package/dist/execute/execute.d.ts +0 -16
  67. package/dist/execute/execute.js +0 -1
  68. package/dist/execute/step.d.ts +0 -37
  69. package/dist/execute/step.js +0 -1
  70. package/dist/execute/stream.d.ts +0 -25
  71. package/dist/execute/stream.js +0 -1
@@ -1,49 +0,0 @@
1
- import type { Edge, GraphSpec, Node } from "../types/index.js";
2
- /**
3
- * Everything about a graph that is wrong without running it.
4
- *
5
- * The argument for a graph over a hand-written loop: the shape is data, so it
6
- * can be wrong at build time rather than on the branch nobody exercised. What
7
- * no static check catches is a loop that never exits — that is `maxSteps`.
8
- */
9
- export declare function indexNodes(spec: GraphSpec): Map<string, Node<never>>;
10
- export declare function indexEdges(edges: Edge[], byName: Map<string, Node<never>>): Map<string, Edge[]>;
11
- /** The default when a spec names no ceiling. */
12
- export declare const DEFAULT_MAX_STEPS = 25;
13
- /**
14
- * The step ceiling, refusing one that would not be a ceiling.
15
- *
16
- * `NaN` is the case worth catching: `Math.max(1, NaN)` is `NaN`, and
17
- * `steps.length >= NaN` is false forever, so the one guard against a runaway
18
- * quietly turns off. A cyclic graph then spins with no way to interrupt it —
19
- * a synchronous node starves the event loop with microtasks, so not even a
20
- * timer fires. `Number(process.env.MAX_STEPS)` on an unset variable is `NaN`,
21
- * which is how a config gets here without anyone writing it.
22
- *
23
- * `Infinity` is refused too, and deliberately. It reads as "no limit", and the
24
- * limit is the point: a graph is a DAG whose ceiling exists to stop a cycle.
25
- */
26
- export declare function ceilingFor(maxSteps: number | undefined): number;
27
- /**
28
- * Refuses a node whose way out is only half specified.
29
- *
30
- * A node with **no** edges is the end of a run — the author wrote none, and
31
- * meant it. A node with edges that are **all conditional** is a different thing:
32
- * a run arriving there with every condition false has nowhere to go, stops, and
33
- * reports `completed`. The graph skipped the rest of its work and called it
34
- * success, which is the answer nobody wants and nobody asked for.
35
- *
36
- * So the line is between saying nothing and saying half of it. Declaring where a
37
- * run goes is a decision; declaring it only for some cases is the decision left
38
- * unfinished, and this is the same argument as `assertAllReachable` one step
39
- * later — a node nothing reaches is a typo caught at build time, a node nothing
40
- * *leaves* is the same typo caught on the branch nobody exercised.
41
- */
42
- export declare function assertEveryBranchIsCovered(edgesFrom: Map<string, Edge[]>): void;
43
- /**
44
- * Refuses a node nothing reaches.
45
- *
46
- * Refused rather than warned about: the usual cause is a typo in an edge, and
47
- * the usual symptom is a branch that quietly never runs.
48
- */
49
- export declare function assertAllReachable(entry: string, byName: Map<string, Node<never>>, edgesFrom: Map<string, Edge[]>): void;
@@ -1 +0,0 @@
1
- function d(e){if(e.nodes.length===0)throw new Error("A graph needs at least one node.");const n=new Map;for(const o of e.nodes){if(n.has(o.name))throw new Error(`Two nodes are called "${o.name}".`);n.set(o.name,o)}if(!n.has(e.entry))throw new Error(`The entry node "${e.entry}" is not in this graph. Known: ${s(n)}.`);return n}function s(e){return[...e.keys()].join(", ")}function f(e,n){const o=new Map;for(const t of e){if(!n.has(t.from))throw new Error(`An edge starts at "${t.from}", which is not a node in this graph. Known: ${s(n)}.`);if(t.to!==null&&!n.has(t.to))throw new Error(`An edge from "${t.from}" points at "${t.to}", which is not a node in this graph. Known: ${s(n)}.`);const i=o.get(t.from)??[],h=i.find(r=>!r.when);if(h)throw new Error(`The edge from "${t.from}" to "${h.to??"the end"}" has no condition, so the edge to "${t.to??"the end"}" declared after it can never be taken. Put the unconditional edge last.`);i.push(t),o.set(t.from,i)}return o}const a=25;function c(e){if(e===void 0)return a;if(!Number.isFinite(e))throw new Error(`maxSteps is ${e}, which is not a ceiling \u2014 a run would never stop at it. Give it a finite number, or leave it out for the default of 25.`);return Math.max(1,Math.floor(e))}function w(e){const n=[...e.entries()].filter(([,o])=>o.length>0&&o.every(t=>t.when)).map(([o])=>o);if(n.length!==0)throw new Error(`${n.map(o=>`"${o}"`).join(", ")} ${n.length===1?"has":"have"} only conditional edges, so a run where none of them match stops there and reports success. Add an unconditional edge as the default, or end("${n[0]}") to stop there on purpose.`)}function l(e,n,o){const t=new Set([e]),i=[e];for(;i.length;)for(const r of o.get(i.shift())??[])r.to&&!t.has(r.to)&&(t.add(r.to),i.push(r.to));const h=[...n.keys()].filter(r=>!t.has(r));if(h.length)throw new Error(`Nothing reaches ${h.map(r=>`"${r}"`).join(", ")} from "${e}". Add an edge, or take the node out.`)}export{a as DEFAULT_MAX_STEPS,l as assertAllReachable,w as assertEveryBranchIsCovered,c as ceilingFor,f as indexEdges,d as indexNodes};
@@ -1,16 +0,0 @@
1
- import type { Edge, GraphEvent, GraphResult, GraphRunOptions, Node } from "../types/index.js";
2
- /**
3
- * Runs a compiled graph.
4
- *
5
- * Never throws, for the reason `invoke` never throws: an exception discards the
6
- * two steps that worked along with the evidence of where it stopped. Every exit
7
- * here is a `GraphResult` carrying the steps taken.
8
- *
9
- * A failed node halts the run. Continuing would leave every later node reading
10
- * `outputs` for something that is not there.
11
- *
12
- * `sink` is how a run narrates itself. It is called as the run proceeds and its
13
- * absence changes nothing, so the streamed and unstreamed paths stay one piece
14
- * of code rather than two that drift.
15
- */
16
- export declare function executeGraph(byName: Map<string, Node<never>>, edgesFrom: Map<string, Edge[]>, entry: string, maxSteps: number, values: Record<string, unknown>, now: () => number, sink?: (event: GraphEvent) => void, opts?: GraphRunOptions): Promise<GraphResult>;
@@ -1 +0,0 @@
1
- import{FieldValidationError as _,validateFields as j}from"@spendgraph/sdk";import{pausedInside as B}from"../types/index.js";import{nestedSteps as C,outputOf as G,tokensOf as K,totalTokens as R}from"./outcome.js";import{failedStep as F,message as m,sealed as g}from"./step.js";function w(r,t,i,u,h,n){return{type:"node_end",node:r,index:t,status:i,output:h,error:n,latencyMs:u}}function T(r,t){if(!r.args?.length)return null;const i=j(t,r.args);return i.length>0?new _(i).message:null}function V(r,t){let i;for(const u of r)if(!u.when||(i??=t(),u.when(i)))return u.to;return null}function q(r,t,i,u,h){return h({type:"paused",node:i,index:u,waitingOn:t.waitingOn,...t.runId?{runId:t.runId}:{}}),{...r,waitingOn:t.waitingOn,...t.runId?{waitingFor:t.runId}:{}}}async function L(r,t,i,u,h,n,c=()=>{},x={}){const S=n(),v=(x.steps??[]).map(g),a=[...v],I={...x.outputs},E=e=>({values:h,outputs:I,steps:[...a],emit:o=>c({type:"token",node:e,text:o})}),l=(e,o)=>({status:e,output:e==="completed"?a.at(-1)?.output??"":"",error:o,steps:a,outputs:I,latencyMs:n()-S,...R(a)}),O=x.entry??i;if(!r.has(O))return l("failed",`No node is called "${O}", so there is nowhere to start. \`entry\` names where a resumed run picks up. Known: ${[...r.keys()].join(", ")}.`);let $=O;for(;$;){if(a.length-v.length>=u)return l("failed",`Ran ${u} steps without reaching an end, stopping at "${$}". Either a conditional edge never turns false, or maxSteps is too low for this graph.`);const e=r.get($),o=n(),A=E($),s=a.length;let p;try{p=e.input?e.input(A):h}catch(d){return a.push(g(F(s,e.name,{},n()-o,m(d)))),c(w(e.name,s,"failed",n()-o,void 0,m(d))),l("failed",`Building the input for "${e.name}" threw: ${m(d)}`)}c({type:"node_start",node:e.name,index:s,input:p});const y=T(e,p);if(y)return a.push(g(F(s,e.name,p,n()-o,y))),c(w(e.name,s,"failed",n()-o,void 0,y)),l("failed",`"${e.name}" was given ${y}`);let f;try{f=await e.run(p,A)}catch(d){return a.push(g(F(s,e.name,p,n()-o,m(d)))),c(w(e.name,s,"failed",n()-o,void 0,m(d))),l("failed",`"${e.name}" failed: ${m(d)}`)}I[e.name]=f,a.push(g({index:s,source:e.name,input:p,output:G(f),status:"completed",latencyMs:n()-o,...K(f)})),a.push(...C(f,a.length,e.name).map(g)),c(w(e.name,s,"completed",n()-o,f));const M=B(f);if(M)return q(l("completed"),M,e.name,s,c);try{$=V(t.get(e.name)??[],()=>E(e.name))}catch(d){return l("failed",`Choosing what runs after "${e.name}" threw: ${m(d)}`)}}return l("completed")}export{L as executeGraph};
@@ -1,37 +0,0 @@
1
- import type { RolloutStep } from "@spendgraph/sdk";
2
- /**
3
- * Same convention as a tool result: a string is itself, anything else is JSON.
4
- *
5
- * `JSON.stringify` throws on a circular object, on a `BigInt`, and on anything
6
- * whose own `toJSON` throws — and recording a step happens outside the guard
7
- * around the node, so that exception escaped `executeGraph`, which is
8
- * documented never to throw. It took the steps that had worked with it, which
9
- * is the loss that guard exists to prevent.
10
- *
11
- * A circular object is not exotic: any value with a parent backlink is one.
12
- *
13
- * What comes back when it cannot be written is a note saying so, never `""` —
14
- * an empty output reads as a node that produced nothing, and this is a node
15
- * that produced something unrecordable. The same distinction as a token count
16
- * that is absent rather than nought.
17
- */
18
- export declare function stringify(value: unknown): string;
19
- export declare function failedStep(index: number, source: string, input: Record<string, unknown>, latencyMs: number, error: string): RolloutStep;
20
- export declare function message(err: unknown): string;
21
- /**
22
- * A step, sealed against the nodes that will be handed it.
23
- *
24
- * `GraphContext.steps` is documented as "a copy; writing to it does nothing",
25
- * and the copy is shallow — so pushing onto the array was ignored while editing
26
- * a step *object* went straight into the run's record. A node could rewrite an
27
- * earlier step's output, or its token counts, and the run's totals with them.
28
- *
29
- * Frozen once here rather than deep-copied per context: the copy is already
30
- * O(n) on every node, and deep-copying would make that worse to buy the same
31
- * property. In an ES module a write to a frozen object throws, so an accident
32
- * announces itself rather than quietly rewriting history.
33
- *
34
- * Nothing edits a step in place — harness replaces one with `{ ...step, … }` —
35
- * so sealing costs nothing that is being used.
36
- */
37
- export declare function sealed(step: RolloutStep): RolloutStep;
@@ -1 +0,0 @@
1
- function i(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: ${n instanceof Error?n.message:String(n)}]`}}function f(t,n,r,e,o){return{index:t,source:n,input:r,output:"",status:"failed",error:o,latencyMs:e}}function s(t){return t instanceof Error?t.message:String(t)}function c(t){return Object.freeze(t)}export{f as failedStep,s as message,c as sealed,i as stringify};
@@ -1,25 +0,0 @@
1
- import type { Edge, GraphEvent, GraphResult, GraphRunOptions, Node } from "../types/index.js";
2
- /**
3
- * A run as it happens, and the same run once it is over.
4
- *
5
- * One reader. Events are handed out and dropped, so a second iterator takes
6
- * half of them rather than seeing the same run twice — fan out downstream of
7
- * this, not by iterating it twice.
8
- */
9
- export interface GraphStream extends AsyncIterable<GraphEvent> {
10
- /**
11
- * The finished run.
12
- *
13
- * Resolves whether or not anyone iterated, so a caller can pipe the events to
14
- * a client and still record the rollout from the same run.
15
- */
16
- result: Promise<GraphResult>;
17
- }
18
- /**
19
- * Runs a compiled graph, narrating it as it goes.
20
- *
21
- * The run starts immediately rather than on the first read: a caller who only
22
- * wants the result should not have to iterate to get one, and a client that
23
- * connects late should see what already happened.
24
- */
25
- export declare function streamGraph(byName: Map<string, Node<never>>, edgesFrom: Map<string, Edge[]>, entry: string, maxSteps: number, values: Record<string, unknown>, now: () => number, opts?: GraphRunOptions): GraphStream;
@@ -1 +0,0 @@
1
- import{executeGraph as u}from"./execute.js";class w{waiting=[];wake=null;closed=!1;push(e){this.waiting.push(e),this.release()}close(){this.closed=!0,this.release()}release(){const e=this.wake;this.wake=null,e?.()}async*drain(){for(;;){for(;this.waiting.length>0;)yield this.waiting.shift();if(this.closed)return;await new Promise(e=>{this.wake=e})}}}function d(s,e,n,r,l,h,c={}){const t=new w,i=(async()=>{try{const a=await u(s,e,n,r,l,h,o=>t.push(o),c);return t.push({type:"result",result:a}),a}finally{t.close()}})();return i.catch(()=>{}),{result:i,[Symbol.asyncIterator]:()=>t.drain()}}export{d as streamGraph};