skynet-graph 1.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/CONTRIBUTING.md +22 -0
- package/LICENSE +661 -0
- package/README.md +245 -0
- package/bin/sg +20 -0
- package/concepts/README.md +23 -0
- package/concepts/_substrate/Claim/Confidence/Trusted.json +6 -0
- package/concepts/_substrate/Claim/Confidence.json +10 -0
- package/concepts/_substrate/Claim/Freshness/Stale.json +7 -0
- package/concepts/_substrate/Claim/Freshness.json +5 -0
- package/concepts/_substrate/Claim/Verification/Refuted.json +6 -0
- package/concepts/_substrate/Claim/Verification.json +6 -0
- package/concepts/_substrate/Claim.json +4 -0
- package/concepts/_substrate/Frontier/InBeam.json +6 -0
- package/concepts/_substrate/Frontier/Stuck.json +6 -0
- package/concepts/_substrate/Frontier.json +5 -0
- package/concepts/_substrate/Intake/ToTask.json +22 -0
- package/concepts/_substrate/Intake/Typed.json +10 -0
- package/concepts/_substrate/Intake.json +27 -0
- package/concepts/_substrate/Task/Answer.json +12 -0
- package/concepts/_substrate/Task/Complexity/Atomic.json +5 -0
- package/concepts/_substrate/Task/Complexity/Compound/Expansion.json +7 -0
- package/concepts/_substrate/Task/Complexity/Compound.json +6 -0
- package/concepts/_substrate/Task/Complexity.json +6 -0
- package/concepts/_substrate/Task/EvalComplexity.json +13 -0
- package/concepts/_substrate/Task/ReportUp.json +6 -0
- package/concepts/_substrate/Task/Rollup.json +10 -0
- package/concepts/_substrate/Task.json +5 -0
- package/concepts/clinical/Diagnosis/Medication.json +6 -0
- package/concepts/clinical/Diagnosis.json +15 -0
- package/concepts/clinical/Observation/LabValue/OutOfRange.json +6 -0
- package/concepts/clinical/Observation/LabValue.json +5 -0
- package/concepts/clinical/Observation.json +6 -0
- package/concepts/common/Document/pathBasket.json +6 -0
- package/concepts/common/Document.json +3 -0
- package/concepts/common/Edge/Distance.json +13 -0
- package/concepts/common/Edge/Stay/LongStay.json +8 -0
- package/concepts/common/Edge/Stay.json +8 -0
- package/concepts/common/Edge/Travel/LongTravel.json +16 -0
- package/concepts/common/Edge/Travel/ShortTravel.json +11 -0
- package/concepts/common/Edge/Travel/targetNode.json +7 -0
- package/concepts/common/Edge/Travel.json +7 -0
- package/concepts/common/Edge.json +3 -0
- package/concepts/common/Vertice.json +3 -0
- package/concepts/supply/Fulfillment.json +10 -0
- package/concepts/supply/Inventory/Reorder.json +7 -0
- package/concepts/supply/Inventory.json +5 -0
- package/concepts/supply/Procurement/SupplierConfirm.json +6 -0
- package/concepts/supply/Procurement.json +8 -0
- package/concepts/supply/Transport.json +5 -0
- package/doc/API.md +556 -0
- package/doc/CAPABILITIES.md +373 -0
- package/doc/MODELISATION.md +511 -0
- package/doc/architecture.md +267 -0
- package/doc/concept-as-graph.md +379 -0
- package/doc/concept-learning.md +168 -0
- package/doc/creative-loop-map.md +202 -0
- package/doc/original-2016-doc.md +148 -0
- package/doc/usage.md +501 -0
- package/lib/authoring/abstract.js +401 -0
- package/lib/authoring/abstraction.js +190 -0
- package/lib/authoring/adapt.js +393 -0
- package/lib/authoring/ancestry.js +216 -0
- package/lib/authoring/author.js +147 -0
- package/lib/authoring/bounded-merge.js +57 -0
- package/lib/authoring/canon.js +158 -0
- package/lib/authoring/clock.js +123 -0
- package/lib/authoring/combinator.js +92 -0
- package/lib/authoring/compete.js +76 -0
- package/lib/authoring/compose-hotspot.js +314 -0
- package/lib/authoring/compress.js +279 -0
- package/lib/authoring/concept-net.js +254 -0
- package/lib/authoring/concepts.js +66 -0
- package/lib/authoring/context-project.js +277 -0
- package/lib/authoring/contract.js +476 -0
- package/lib/authoring/corpus-pack.js +125 -0
- package/lib/authoring/cost-probe.js +86 -0
- package/lib/authoring/crystallize.js +555 -0
- package/lib/authoring/dag-decompose.js +256 -0
- package/lib/authoring/dataset-adapter.js +253 -0
- package/lib/authoring/debug-provider.js +92 -0
- package/lib/authoring/decompose.js +343 -0
- package/lib/authoring/emittability.js +236 -0
- package/lib/authoring/equilibrium.js +160 -0
- package/lib/authoring/extract.js +153 -0
- package/lib/authoring/forest.js +55 -0
- package/lib/authoring/forge-fallback.js +116 -0
- package/lib/authoring/givens.js +121 -0
- package/lib/authoring/glossary.js +177 -0
- package/lib/authoring/grammar-graph.js +191 -0
- package/lib/authoring/granularity.js +80 -0
- package/lib/authoring/graph-net.js +108 -0
- package/lib/authoring/ground.js +148 -0
- package/lib/authoring/higher-order.js +85 -0
- package/lib/authoring/hotspot.js +132 -0
- package/lib/authoring/hysteresis.js +85 -0
- package/lib/authoring/index.js +106 -0
- package/lib/authoring/lattice-morphism.js +61 -0
- package/lib/authoring/lattice-pack.js +148 -0
- package/lib/authoring/leaf-io.js +77 -0
- package/lib/authoring/library.js +156 -0
- package/lib/authoring/lifecycle.js +92 -0
- package/lib/authoring/loop.js +193 -0
- package/lib/authoring/master-loop.js +132 -0
- package/lib/authoring/mdl.js +209 -0
- package/lib/authoring/memo-stability.js +122 -0
- package/lib/authoring/method-explorer.js +138 -0
- package/lib/authoring/method-pack.js +181 -0
- package/lib/authoring/method.js +202 -0
- package/lib/authoring/mine.js +510 -0
- package/lib/authoring/mount.js +119 -0
- package/lib/authoring/negotiate.js +100 -0
- package/lib/authoring/parametric.js +139 -0
- package/lib/authoring/reaggregate.js +75 -0
- package/lib/authoring/rebalance.js +157 -0
- package/lib/authoring/recall.js +135 -0
- package/lib/authoring/registry.js +350 -0
- package/lib/authoring/relearn.js +147 -0
- package/lib/authoring/retention.js +89 -0
- package/lib/authoring/segment-proxy.js +114 -0
- package/lib/authoring/serve-leaf.js +64 -0
- package/lib/authoring/slot-aware-serve.js +77 -0
- package/lib/authoring/sound-invoke.js +59 -0
- package/lib/authoring/split-serve.js +74 -0
- package/lib/authoring/ste.js +239 -0
- package/lib/authoring/stock.js +101 -0
- package/lib/authoring/store.js +94 -0
- package/lib/authoring/supervise.js +138 -0
- package/lib/authoring/support.js +131 -0
- package/lib/authoring/task-mirror.js +51 -0
- package/lib/authoring/typed-loop.js +160 -0
- package/lib/authoring/validate.js +569 -0
- package/lib/authoring/widen.js +88 -0
- package/lib/combos/appliance.js +189 -0
- package/lib/combos/critique.js +377 -0
- package/lib/combos/defaults.js +86 -0
- package/lib/combos/durable-runner.js +115 -0
- package/lib/combos/forge.js +209 -0
- package/lib/combos/index.js +86 -0
- package/lib/combos/learning-library.js +253 -0
- package/lib/combos/mixture-serve.js +193 -0
- package/lib/combos/plan-loop.js +192 -0
- package/lib/combos/proxy-cache.js +272 -0
- package/lib/combos/self-mod.js +94 -0
- package/lib/durable/audit.js +102 -0
- package/lib/durable/checkpoint-store.js +550 -0
- package/lib/durable/fold.js +62 -0
- package/lib/durable/interpreter.js +242 -0
- package/lib/durable/xlate.js +185 -0
- package/lib/graph/Graph.js +2486 -0
- package/lib/graph/expr.js +307 -0
- package/lib/graph/index.js +26 -0
- package/lib/graph/log.js +134 -0
- package/lib/graph/objects/Concept.js +382 -0
- package/lib/graph/objects/Entity.js +503 -0
- package/lib/graph/objects/Node.js +57 -0
- package/lib/graph/objects/PathMap.js +557 -0
- package/lib/graph/objects/Segment.js +100 -0
- package/lib/graph/tasks/stabilize.js +59 -0
- package/lib/graph/tasks/taskflow.js +247 -0
- package/lib/index.js +122 -0
- package/lib/load.js +132 -0
- package/lib/providers/backends.js +117 -0
- package/lib/providers/borderline.js +129 -0
- package/lib/providers/cache.js +226 -0
- package/lib/providers/canonicalize.js +198 -0
- package/lib/providers/constat.js +107 -0
- package/lib/providers/geo.js +53 -0
- package/lib/providers/index.js +116 -0
- package/lib/providers/intake.js +263 -0
- package/lib/providers/llm-local.js +88 -0
- package/lib/providers/llm.js +236 -0
- package/lib/providers/local-host.js +197 -0
- package/lib/providers/merge-consistency.js +162 -0
- package/lib/providers/nogood.js +149 -0
- package/lib/providers/reason-loop.js +223 -0
- package/lib/providers/semiring.js +285 -0
- package/lib/providers/solver-fork.js +163 -0
- package/lib/providers/stats.js +228 -0
- package/lib/providers/verify.js +120 -0
- package/lib/runtime/index.js +135 -0
- package/lib/runtime/invoke-pool.js +92 -0
- package/lib/runtime/protocol.js +210 -0
- package/lib/runtime/transport-socket.js +77 -0
- package/lib/runtime/worker-entry.js +31 -0
- package/lib/sg/cli.js +601 -0
- package/lib/sg/log-sinks.js +214 -0
- package/lib/sg/mcp.js +395 -0
- package/lib/sg/proxy-run.js +66 -0
- package/lib/sg/serve-demo.js +185 -0
- package/lib/sg/serve.js +205 -0
- package/lib/sg/trace.js +118 -0
- package/lib/studio/protocol.js +45 -0
- package/lib/studio/public/app.js +310 -0
- package/lib/studio/public/components/ConceptEditor.js +43 -0
- package/lib/studio/public/components/ConceptTree.js +43 -0
- package/lib/studio/public/components/CorpusPanel.js +68 -0
- package/lib/studio/public/components/ForkTree.js +36 -0
- package/lib/studio/public/components/GrammarGraph.js +99 -0
- package/lib/studio/public/components/GraphCanvas.js +156 -0
- package/lib/studio/public/components/Inspector.js +48 -0
- package/lib/studio/public/components/LearningPanel.js +85 -0
- package/lib/studio/public/components/PromptConsole.js +46 -0
- package/lib/studio/public/components/ProviderTrace.js +39 -0
- package/lib/studio/public/components/SessionSplit.js +47 -0
- package/lib/studio/public/components/TilingOverlay.js +40 -0
- package/lib/studio/public/components/Timeline.js +57 -0
- package/lib/studio/public/index.html +26 -0
- package/lib/studio/public/styles.css +185 -0
- package/lib/studio/public/ws.js +58 -0
- package/lib/studio/server.js +130 -0
- package/lib/studio/session.js +397 -0
- package/lib/studio/studio.js +149 -0
- package/package.json +42 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Promise / semaphore manager & task sequencer.
|
|
20
|
+
*
|
|
21
|
+
* Vendored, zero-dependency drop-in for the `taskflows` package (originally by
|
|
22
|
+
* Nathanael Braun). Behaviour-preserving: the engine's stabilize loop relies on
|
|
23
|
+
* the exact lock/release + followers semantics (see lib/graph/Graph.js `_loopTF`,
|
|
24
|
+
* `pushMutation`, and the #11.a re-entrancy guard). The only change vs the
|
|
25
|
+
* published lib is that the unused `require()`s (isfunction/isnumber/merge/
|
|
26
|
+
* slice/splice) are dropped and `isArray`/`isString` are inlined.
|
|
27
|
+
*
|
|
28
|
+
* Mental model: `locks` is a semaphore. Each task run takes a lock; release() drops
|
|
29
|
+
* one. While locks > 0 new tasks queue (doAfter/todo); when locks hit 0 the flow is
|
|
30
|
+
* COMPLETE and its one-shot `_followers` fire — that follower is how stabilize.js
|
|
31
|
+
* hands control back to Graph._loopTF once a whole stabilization pass has drained.
|
|
32
|
+
*
|
|
33
|
+
* @class TaskFlow
|
|
34
|
+
*/
|
|
35
|
+
"use strict";
|
|
36
|
+
|
|
37
|
+
var isArray = Array.isArray,
|
|
38
|
+
isString = function ( s ) { return typeof s === 'string'; };
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* TaskFlow
|
|
42
|
+
* @param todo {fn|fn[]} task(s) to run (string => method name on scope)
|
|
43
|
+
* @param scope {object} execution context passed to tasks
|
|
44
|
+
* @param followers {fn|fn[]} one-shot callback(s) fired when locks reach 0
|
|
45
|
+
* @param name {string}
|
|
46
|
+
* @constructor
|
|
47
|
+
*/
|
|
48
|
+
var TaskFlow = function ( todo, scope, followers, name ) {
|
|
49
|
+
this.scope = scope || {};
|
|
50
|
+
this.todo = isArray(todo) ? todo : todo && [todo] || [];
|
|
51
|
+
this.doAfter = [];
|
|
52
|
+
this.locks = 0;
|
|
53
|
+
this.fails = 0;
|
|
54
|
+
this._complete = false;
|
|
55
|
+
this._followers = followers instanceof Array ? followers : [followers];
|
|
56
|
+
this._onfail = null;
|
|
57
|
+
this.displayName = name;
|
|
58
|
+
|
|
59
|
+
this.release = this.success = this.release.bind(this);
|
|
60
|
+
this.asyncFail = this.fail.bind(this);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
module.exports = TaskFlow;
|
|
64
|
+
|
|
65
|
+
TaskFlow.prototype = {
|
|
66
|
+
kill : function () {
|
|
67
|
+
this.doAfter = this.scope = this._onfail = this._followers = 0;
|
|
68
|
+
this.dead = true;
|
|
69
|
+
},
|
|
70
|
+
reset : function () {
|
|
71
|
+
if ( this.dead ) return;
|
|
72
|
+
this._complete = false;
|
|
73
|
+
this._pos = 0;
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* Add one-shot success callback / TaskFlow.
|
|
77
|
+
* A passed TaskFlow is released (so it starts running if it holds no other locks).
|
|
78
|
+
* @param cb {function|TaskFlow}
|
|
79
|
+
* @returns {TaskFlow}
|
|
80
|
+
*/
|
|
81
|
+
then : function () {
|
|
82
|
+
if ( this.dead ) return;
|
|
83
|
+
this._followers = (this._followers instanceof Array) && this._followers || [];
|
|
84
|
+
for ( var i = 0, ln = arguments.length ; i < ln ; i++ ) {
|
|
85
|
+
if ( arguments[i] instanceof Array ) this.then.apply(this, arguments[i]);
|
|
86
|
+
else {
|
|
87
|
+
this._followers.push(arguments[i]);
|
|
88
|
+
if ( arguments[i] instanceof TaskFlow )
|
|
89
|
+
arguments[i].locks++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if ( this._complete ) {
|
|
93
|
+
this.locks++;
|
|
94
|
+
this.release();
|
|
95
|
+
}
|
|
96
|
+
return this;
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* Add one-shot fail callback.
|
|
100
|
+
* @param cb {function|TaskFlow|array}
|
|
101
|
+
* @returns {TaskFlow}
|
|
102
|
+
*/
|
|
103
|
+
catchFail : function () {
|
|
104
|
+
if ( this.dead ) return;
|
|
105
|
+
var done = this.fails && this._complete;
|
|
106
|
+
this._onfail = this._onfail || [];
|
|
107
|
+
for ( var i = 0, ln = arguments.length ; i < ln ; i++ ) {
|
|
108
|
+
if ( arguments[i] instanceof Array ) this.catchFail.apply(this, arguments[i]);
|
|
109
|
+
else {
|
|
110
|
+
if ( !done ) this._onfail.push(arguments[i]);
|
|
111
|
+
else arguments[i]();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return this;
|
|
115
|
+
},
|
|
116
|
+
/**
|
|
117
|
+
* Make this flow fall into failure, triggering one-shot callbacks.
|
|
118
|
+
* @param cause
|
|
119
|
+
*/
|
|
120
|
+
fail : function ( cause ) {
|
|
121
|
+
if ( this.dead ) return;
|
|
122
|
+
var tmp, i = 0;
|
|
123
|
+
this._fail = cause;
|
|
124
|
+
if ( this._onfail )
|
|
125
|
+
while ( i < this._onfail.length ) {
|
|
126
|
+
tmp = this._onfail[i++];
|
|
127
|
+
if ( tmp instanceof Function ) tmp(this.scope, cause, this);
|
|
128
|
+
else if ( tmp instanceof TaskFlow ) tmp.release();
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* Push a task & (re)start running the flow if no remaining locks.
|
|
133
|
+
* @returns {TaskFlow}
|
|
134
|
+
*/
|
|
135
|
+
push : function () {
|
|
136
|
+
if ( this.dead ) return;
|
|
137
|
+
this.locks++;
|
|
138
|
+
this._complete = false;
|
|
139
|
+
this.todo.push.apply(this.todo, arguments);
|
|
140
|
+
this.release();
|
|
141
|
+
return this;
|
|
142
|
+
},
|
|
143
|
+
/**
|
|
144
|
+
* Add a lock (optionally waiting on a previous flow).
|
|
145
|
+
* @param previous {TaskFlow|TaskFlow[]} optional flow(s) to wait on
|
|
146
|
+
* @returns {TaskFlow}
|
|
147
|
+
*/
|
|
148
|
+
wait : function ( previous ) {
|
|
149
|
+
if ( this.dead ) return;
|
|
150
|
+
if ( isArray(previous) )
|
|
151
|
+
return previous.map(this.wait.bind(this));
|
|
152
|
+
if ( previous ) previous.then(this);
|
|
153
|
+
else this.locks++;
|
|
154
|
+
return this;
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Decrease locks; at 0, run pending tasks else fire `then` followers.
|
|
158
|
+
* @param desync
|
|
159
|
+
* @returns {*}
|
|
160
|
+
*/
|
|
161
|
+
release : function ( desync ) {
|
|
162
|
+
if ( this.dead ) return;
|
|
163
|
+
if ( desync && this.locks > 0 ) return setTimeout(this.success) && this;
|
|
164
|
+
var tmp;
|
|
165
|
+
|
|
166
|
+
if ( !--this.locks ) {
|
|
167
|
+
if ( this.doAfter.length || this.todo.length > this._pos ) {
|
|
168
|
+
this.run(tmp);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this._complete = true;
|
|
172
|
+
this.running = false;
|
|
173
|
+
|
|
174
|
+
if ( this._followers instanceof Array ) {
|
|
175
|
+
while ( this._followers.length ) {
|
|
176
|
+
tmp = this._followers.shift();
|
|
177
|
+
if ( tmp instanceof Function ) tmp(this.scope, this);
|
|
178
|
+
else if ( tmp instanceof TaskFlow ) tmp.release();
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
tmp = this._followers;
|
|
182
|
+
if ( tmp instanceof Function ) tmp(this.scope, this);
|
|
183
|
+
else if ( tmp instanceof TaskFlow ) tmp.release();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return this;
|
|
187
|
+
},
|
|
188
|
+
_pos : 0,
|
|
189
|
+
_nextTask : 0,
|
|
190
|
+
pushSubTask : function ( task ) {
|
|
191
|
+
if ( this.dead ) return;
|
|
192
|
+
this.doAfter.push(task);
|
|
193
|
+
},
|
|
194
|
+
/**
|
|
195
|
+
* Execute one task, then trampoline to the next. `run` calls itself (line ~end)
|
|
196
|
+
* rather than looping so a long task chain never grows the call stack. A task may
|
|
197
|
+
* be: a fn (called with (scope, this) — if it RETURNS a value, that becomes the
|
|
198
|
+
* next step, chaining work); a method name (resolved on scope); a nested TaskFlow
|
|
199
|
+
* (this flow waits on it via then); or an ARRAY (a parallel pool — every entry is
|
|
200
|
+
* scheduled via setTimeout and gets its own lock, so they run concurrently and the
|
|
201
|
+
* flow only completes once all have released). Each step takes a lock; the trailing
|
|
202
|
+
* release() balances it so `_followers` fire exactly at quiescence.
|
|
203
|
+
* @returns {*}
|
|
204
|
+
*/
|
|
205
|
+
run : function ( step, force, releaseAfter ) {
|
|
206
|
+
if ( this.dead ) return;
|
|
207
|
+
if ( !step && !this.locks && !this.doAfter.length && (this._pos >= this.todo.length) ) {
|
|
208
|
+
this.locks++;
|
|
209
|
+
this.running = false;
|
|
210
|
+
this.release();
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
if ( !force && this.locks ) return step && this.pushSubTask(step);
|
|
214
|
+
|
|
215
|
+
this.running = true;
|
|
216
|
+
|
|
217
|
+
step = step || this.doAfter.length && this.doAfter.shift() || this.todo[this._pos++];
|
|
218
|
+
|
|
219
|
+
this.locks++;
|
|
220
|
+
if ( isString(step) ) {
|
|
221
|
+
if ( this.scope[step] instanceof Function )
|
|
222
|
+
step = this.scope[step](null, this.scope, this);
|
|
223
|
+
else step = this.scope[step];
|
|
224
|
+
} else if ( step instanceof Function ) {
|
|
225
|
+
this._succesfull = true;
|
|
226
|
+
step = step(this.scope, this);
|
|
227
|
+
this._succesfull = false;
|
|
228
|
+
} else if ( step instanceof TaskFlow ) { // sync wf
|
|
229
|
+
step.then(this);
|
|
230
|
+
step = null;
|
|
231
|
+
} else if ( step instanceof Array ) { // async pool
|
|
232
|
+
this.locks++;
|
|
233
|
+
for ( var i = 0 ; i < step.length ; i++, this.locks++ )
|
|
234
|
+
setTimeout(this.run.bind(this, step[i], true, true));
|
|
235
|
+
setTimeout(this.success);
|
|
236
|
+
step = null;
|
|
237
|
+
} else {
|
|
238
|
+
step = null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
!step && releaseAfter && this.release();
|
|
242
|
+
this.run(step, step && force, step && releaseAfter);
|
|
243
|
+
this.release();
|
|
244
|
+
|
|
245
|
+
return this;
|
|
246
|
+
}
|
|
247
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
'use strict';
|
|
19
|
+
/**
|
|
20
|
+
* Package facade. `require('skynet-graph')` returns the Graph constructor, with
|
|
21
|
+
* standalone-boot helpers attached as statics so a host can spin a graph up from
|
|
22
|
+
* plain directories without hand-wiring the concept tree + providers:
|
|
23
|
+
*
|
|
24
|
+
* const Graph = require('skynet-graph');
|
|
25
|
+
* const g = Graph.fromDirs({ concepts: './concepts', providers: './providers' });
|
|
26
|
+
*
|
|
27
|
+
* The engine core (lib/graph) stays filesystem-free; fromDirs composes the edge
|
|
28
|
+
* loaders (lib/load.js) with the provider registry (lib/providers).
|
|
29
|
+
*/
|
|
30
|
+
const Graph = require('./graph');
|
|
31
|
+
const providers = require('./providers');
|
|
32
|
+
const { loadConceptMap, loadProviders } = require('./load.js');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Boot a Graph from on-disk concept sets + provider modules.
|
|
36
|
+
*
|
|
37
|
+
* @param {object} opts
|
|
38
|
+
* @param {string|string[]} [opts.concepts] concept-set dir(s) (see loadConceptMap)
|
|
39
|
+
* @param {string|object|Array} [opts.providers] provider module dir(s)/file(s) or fragment(s)
|
|
40
|
+
* @param {object} [opts.providerCtx] ctx passed to provider factory modules
|
|
41
|
+
* @param {boolean|Array} [opts.builtins] register packaged providers (true = geo + default llm)
|
|
42
|
+
* @param {object|string} [opts.seed] serialized record, or a path to a JSON snapshot
|
|
43
|
+
* @param {object} [opts.conf] Graph cfg overrides (conceptSets defaults to all loaded)
|
|
44
|
+
* @param {object} [opts.conceptMap] pre-built conceptMap (skips loadConceptMap)
|
|
45
|
+
* @returns {Graph}
|
|
46
|
+
*/
|
|
47
|
+
function fromDirs( opts = {} ) {
|
|
48
|
+
const conceptMap = opts.conceptMap
|
|
49
|
+
|| (opts.concepts ? loadConceptMap(opts.concepts) : {});
|
|
50
|
+
|
|
51
|
+
const fragments = [];
|
|
52
|
+
if ( opts.providers ) fragments.push(...loadProviders(opts.providers, opts.providerCtx || {}));
|
|
53
|
+
if ( fragments.length ) providers.register(Graph, fragments);
|
|
54
|
+
// builtins: true -> packaged defaults (geo + default llm client); array -> explicit fragments
|
|
55
|
+
if ( opts.builtins ) providers.register(Graph, opts.builtins === true ? undefined : opts.builtins);
|
|
56
|
+
|
|
57
|
+
let seed = opts.seed;
|
|
58
|
+
if ( typeof seed === 'string' ) seed = JSON.parse(require('fs').readFileSync(require('path').resolve(seed), 'utf8'));
|
|
59
|
+
|
|
60
|
+
const conf = { ...(opts.conf || {}) };
|
|
61
|
+
if ( !conf.conceptSets ) conf.conceptSets = Object.keys(conceptMap);
|
|
62
|
+
|
|
63
|
+
return new Graph(seed || {}, conf, conceptMap);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Attach standalone helpers as statics (keeps `require('skynet-graph')` === Graph).
|
|
67
|
+
Graph.fromDirs = fromDirs;
|
|
68
|
+
Graph.loadConceptMap = loadConceptMap;
|
|
69
|
+
Graph.loadProviders = loadProviders;
|
|
70
|
+
Graph.register = function ( fragments ) { return providers.register(Graph, fragments); };
|
|
71
|
+
Graph.providers = providers;
|
|
72
|
+
|
|
73
|
+
// R0 backends — the preset/config layer over the ask-makers (a provider NAME + key -> a chat `ask`). The
|
|
74
|
+
// shared foundation the serving surfaces / Studio backend panel / client routing consume; every resolved
|
|
75
|
+
// backend carries an `egress` flag (local=false) — the seed a no-egress policy reads.
|
|
76
|
+
Graph.backends = require('./providers/backends');
|
|
77
|
+
|
|
78
|
+
// Authoring & R&D toolkit, namespaced (concepts/validate/author/contract/abstract/method/mount/crystallize/…).
|
|
79
|
+
// Exposed as a static so the substrate tools are usable without deep-path requires. Lazy-required (like
|
|
80
|
+
// createStudioServer): an engine-only / providers-only host pays no load cost, and the shelved numerical
|
|
81
|
+
// modules (concept-net/equilibrium/ste) load only when authoring is actually touched.
|
|
82
|
+
Object.defineProperty(Graph, 'authoring', { get: function () { return require('./authoring'); } });
|
|
83
|
+
|
|
84
|
+
// Combos — thin, delivered assemblies over the bricks (createAppliance/…). Lazy like `authoring`:
|
|
85
|
+
// an engine-only host pays nothing; the substrate/providers load only when a combo is touched.
|
|
86
|
+
Object.defineProperty(Graph, 'combos', { get: function () { return require('./combos'); } });
|
|
87
|
+
|
|
88
|
+
// The first-class SETTLE verb (Controller-P0): a promise that resolves on the next stabilization (or
|
|
89
|
+
// immediately if already quiescent). Lifts the de-facto `supervise.js#nextStable` so a host can
|
|
90
|
+
// `await Graph.settle(g)` after a mutation instead of wiring an onStabilize callback. Lazy-required.
|
|
91
|
+
Graph.settle = function ( graph ) { return require('./authoring/supervise.js').nextStable(graph); };
|
|
92
|
+
|
|
93
|
+
// Distributed runtime: dispatch sub-graphs to a worker (worker_threads) or a cross-instance socket runtime.
|
|
94
|
+
const runtime = require('./runtime');
|
|
95
|
+
Graph.createGraphWorker = runtime.createGraphWorker;
|
|
96
|
+
Graph.spawnGraph = runtime.spawnGraph;
|
|
97
|
+
Graph.invokeGraph = runtime.invokeGraph; // P1: one-shot BOUNDED invoke (only Σ_sep crosses back)
|
|
98
|
+
Graph.createInvokePool = runtime.createInvokePool; // P3: warm-worker pool keyed by contract (N cases → 1 instance)
|
|
99
|
+
Graph.serveGraphWorker = runtime.serveGraphWorker; // P5: stand up a socket runtime remote clients dispatch to
|
|
100
|
+
|
|
101
|
+
// Logger factory (for a host that wants to build/inject its own logger via cfg.logger).
|
|
102
|
+
Graph.createLogger = require('./graph/log').createLogger;
|
|
103
|
+
|
|
104
|
+
// Durable executor — Layer A (the CheckpointStore: durable marking + content-memo + lease queue). Eager-require
|
|
105
|
+
// is safe: the module loads `node:sqlite` ONLY inside the sqlite factory (so engine-only hosts never touch it).
|
|
106
|
+
// `createCheckpointStore({file})` = the convenience default — a file → the durable SQLite backend, none → memory.
|
|
107
|
+
const durable = require('./durable/checkpoint-store.js');
|
|
108
|
+
Graph.createCheckpointStore = function ( opts ) { opts = opts || {}; return opts.file ? durable.createSqliteCheckpointStore(opts) : durable.createMemoryCheckpointStore(opts); };
|
|
109
|
+
Graph.createMemoryCheckpointStore = durable.createMemoryCheckpointStore;
|
|
110
|
+
Graph.createSqliteCheckpointStore = durable.createSqliteCheckpointStore;
|
|
111
|
+
// the full durable executor namespace: the CheckpointStore + C-xlate (compileMethod) + Layer B (runFlow) +
|
|
112
|
+
// the fold-back JOIN's monoid algebra (foldSiblings).
|
|
113
|
+
Graph.durable = Object.assign({}, durable, require('./durable/xlate.js'), require('./durable/interpreter.js'),
|
|
114
|
+
require('./durable/fold.js'), require('./durable/audit.js'));
|
|
115
|
+
|
|
116
|
+
// Studio (the web inspector/console): a host can embed the server as a library instead of
|
|
117
|
+
// only via `bin/sg studio`. Lazy-required so the fs/ws/http deps don't load for engine-only use.
|
|
118
|
+
Object.defineProperty(Graph, 'createStudioServer', {
|
|
119
|
+
get: function () { return require('./studio/server.js').createServer; }
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
module.exports = Graph;
|
package/lib/load.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
'use strict';
|
|
19
|
+
/**
|
|
20
|
+
* Directory loaders — turn on-disk concept sets and provider modules into the
|
|
21
|
+
* shapes the engine expects, so a host (or the `sg` CLI) can boot a graph from
|
|
22
|
+
* plain folders passed by path. Kept OUT of lib/graph/* on purpose: the engine
|
|
23
|
+
* core stays filesystem-free (hermetic/portable); all fs lives here at the edge.
|
|
24
|
+
*/
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const { buildConceptTree } = require('./authoring/concepts.js');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build a conceptMap ({ <setName>: tree }) from one or more directories.
|
|
31
|
+
*
|
|
32
|
+
* Each argument dir is auto-classified:
|
|
33
|
+
* - if it directly contains top-level *.json -> it IS a single set (name = its
|
|
34
|
+
* basename), e.g. loadConceptMap('./concepts/common') -> { common: tree };
|
|
35
|
+
* - else each immediate sub-directory holding *.json is a set, e.g.
|
|
36
|
+
* loadConceptMap('./concepts') -> { common: tree, ... }.
|
|
37
|
+
*
|
|
38
|
+
* @param {string|string[]} concepts dir path(s)
|
|
39
|
+
* @param {object} [opts] { validate: true } runs validateConceptTree per set and THROWS an
|
|
40
|
+
* aggregated error if any set has errors (a strict validating load).
|
|
41
|
+
* @returns {Object} conceptMap keyed by set name
|
|
42
|
+
*/
|
|
43
|
+
function loadConceptMap( concepts, opts = {} ) {
|
|
44
|
+
const dirs = Array.isArray(concepts) ? concepts : [concepts];
|
|
45
|
+
const map = {};
|
|
46
|
+
for ( const d of dirs ) {
|
|
47
|
+
const abs = path.resolve(d);
|
|
48
|
+
const entries = fs.readdirSync(abs, { withFileTypes: true });
|
|
49
|
+
const hasTopJson = entries.some(e => e.isFile() && e.name.endsWith('.json'));
|
|
50
|
+
if ( hasTopJson ) {
|
|
51
|
+
map[path.basename(abs)] = buildConceptTree(abs);
|
|
52
|
+
} else {
|
|
53
|
+
for ( const e of entries ) {
|
|
54
|
+
if ( !e.isDirectory() ) continue;
|
|
55
|
+
const setDir = path.join(abs, e.name);
|
|
56
|
+
if ( fs.readdirSync(setDir).some(f => f.endsWith('.json')) )
|
|
57
|
+
map[e.name] = buildConceptTree(setDir);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if ( opts.validate ) {
|
|
62
|
+
const { validateConceptTree } = require('./authoring/validate.js');
|
|
63
|
+
const fails = [];
|
|
64
|
+
for ( const set of Object.keys(map) )
|
|
65
|
+
for ( const err of validateConceptTree(map[set], opts.validateOpts || {}).errors )
|
|
66
|
+
fails.push(`[${set}] ${err.concept} — ${err.kind}: ${err.message}`);
|
|
67
|
+
if ( fails.length ) throw new Error('concept validation failed:\n' + fails.join('\n'));
|
|
68
|
+
}
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Keys re-derived from the file path on load (concepts.js#buildConceptTree), so they are NOT
|
|
73
|
+
// written back out — and childConcepts becomes a sibling directory, not a key.
|
|
74
|
+
const NON_FILE_KEYS = new Set(['_id', '_name', 'childConcepts']);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Write a serialized concept tree back to the on-disk JSONC layout (the inverse of
|
|
78
|
+
* buildConceptTree / loadConceptMap): one `<Name>.json` per concept, a `<Name>/` sub-directory
|
|
79
|
+
* for its children. Comments are NOT preserved (prose belongs in a typed `_description`/`note`
|
|
80
|
+
* field); the result reloads to the identical tree. fs lives here at the edge, not in the core.
|
|
81
|
+
* @param tree a `{ childConcepts: {...} }` tree (e.g. graph.exportConcepts())
|
|
82
|
+
* @param dir target directory (created if missing)
|
|
83
|
+
* @returns dir
|
|
84
|
+
*/
|
|
85
|
+
function exportConceptsToDir( tree, dir ) {
|
|
86
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
87
|
+
const kids = (tree && tree.childConcepts) || {};
|
|
88
|
+
for ( const name of Object.keys(kids) ) {
|
|
89
|
+
const node = kids[name];
|
|
90
|
+
const schema = {};
|
|
91
|
+
for ( const k of Object.keys(node) ) if ( !NON_FILE_KEYS.has(k) ) schema[k] = node[k];
|
|
92
|
+
fs.writeFileSync(path.join(dir, name + '.json'), JSON.stringify(schema, null, 2) + '\n');
|
|
93
|
+
if ( node.childConcepts && Object.keys(node.childConcepts).length )
|
|
94
|
+
exportConceptsToDir(node, path.join(dir, name));
|
|
95
|
+
}
|
|
96
|
+
return dir;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Collect provider-map fragments from module file(s)/dir(s) (or pass-through
|
|
101
|
+
* fragment objects). A provider module exports either:
|
|
102
|
+
* - a fragment map `{ Namespace: { fn } }`,
|
|
103
|
+
* - `{ default: fragment }`, or
|
|
104
|
+
* - a factory `(ctx) => fragment` (so it can self-configure from `ctx`, e.g. an
|
|
105
|
+
* LLM `ask` backend or env). `ctx` is whatever the caller passes.
|
|
106
|
+
*
|
|
107
|
+
* Returns an array suitable for `register(Graph, fragments)`.
|
|
108
|
+
*
|
|
109
|
+
* @param {string|object|Array} providers dir/file path(s) and/or fragment object(s)
|
|
110
|
+
* @param {object} [ctx] passed to any factory module
|
|
111
|
+
* @returns {object[]} fragments
|
|
112
|
+
*/
|
|
113
|
+
function loadProviders( providers, ctx = {} ) {
|
|
114
|
+
const items = Array.isArray(providers) ? providers : [providers];
|
|
115
|
+
const fragments = [];
|
|
116
|
+
for ( const item of items ) {
|
|
117
|
+
if ( item && typeof item === 'object' ) { fragments.push(item); continue; } // already a fragment
|
|
118
|
+
const abs = path.resolve(item);
|
|
119
|
+
const files = fs.statSync(abs).isDirectory()
|
|
120
|
+
? fs.readdirSync(abs).filter(f => f.endsWith('.js')).sort().map(f => path.join(abs, f))
|
|
121
|
+
: [abs];
|
|
122
|
+
for ( const f of files ) {
|
|
123
|
+
let mod = require(f);
|
|
124
|
+
if ( typeof mod === 'function' ) mod = mod(ctx); // factory(ctx)
|
|
125
|
+
else if ( mod && typeof mod.default !== 'undefined' ) mod = mod.default; // esm-interop default
|
|
126
|
+
if ( mod && typeof mod === 'object' ) fragments.push(mod);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return fragments;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { loadConceptMap, loadProviders, exportConceptsToDir };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Nathanael Braun
|
|
3
|
+
* @author : Nathanael BRAUN <pp9ping@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
'use strict';
|
|
19
|
+
/**
|
|
20
|
+
* backends — the PRESET / CONFIG layer over the ask-makers (`llm.js`, `llm-local.js`). One thin,
|
|
21
|
+
* data-driven place to answer "given a provider NAME + a key, hand me a chat `ask`". It invents no
|
|
22
|
+
* protocol and no router: every hosted provider worth targeting is OpenAI-compatible except Anthropic
|
|
23
|
+
* (its own `/v1/messages`) and the embedded local model (`node-llama-cpp`) — so the table is small and
|
|
24
|
+
* a new provider is one row (base + flavour + key-env).
|
|
25
|
+
*
|
|
26
|
+
* It is the SHARED foundation the serving surfaces, the Studio backend panel, and the client's N-tier
|
|
27
|
+
* routing all consume: the public preset table here = the free config brick; the client wraps it with a
|
|
28
|
+
* policy (order + `dataPolicy:'no-egress'`); the pro adds per-task routing. The `egress` flag carried on
|
|
29
|
+
* every resolved backend (local=false, remote=true) is the SEED that a no-egress policy reads to decide
|
|
30
|
+
* what a query is allowed to leave the machine for.
|
|
31
|
+
*
|
|
32
|
+
* const ask = makeBackend({ preset: 'deepseek' }); // key from $DEEPSEEK_API_KEY
|
|
33
|
+
* const ask = makeBackend({ preset: 'custom', base: 'http://localhost:8000', key: 'sk-x' }); // any vLLM
|
|
34
|
+
* const ask = makeBackend({ preset: 'local', modelPath: '/models/qwen.gguf' }); // embedded
|
|
35
|
+
*
|
|
36
|
+
* The returned `ask` is the standard chat seam: `async ({system,user,maxTokens,temperature}) -> text`.
|
|
37
|
+
*/
|
|
38
|
+
const llm = require('./llm');
|
|
39
|
+
|
|
40
|
+
// The preset table. `base` is the API ROOT (the OpenAI maker appends `/v1/chat/completions`, the
|
|
41
|
+
// Anthropic maker `/v1/messages`) — so bases here carry NO `/v1`. `defaultModel` is only a convenience
|
|
42
|
+
// default (always overridable via spec.model or $LLM_MODEL), kept minimal so the table does not rot.
|
|
43
|
+
const BACKENDS = {
|
|
44
|
+
local: { api: 'local', label: 'Local (embedded GGUF)', egress: false, needsKey: false },
|
|
45
|
+
openai: { api: 'openai', label: 'OpenAI', base: 'https://api.openai.com', egress: true, needsKey: true, keyEnv: 'OPENAI_API_KEY', defaultModel: 'gpt-4o-mini' },
|
|
46
|
+
anthropic: { api: 'anthropic', label: 'Anthropic (Claude)', base: 'https://api.anthropic.com', egress: true, needsKey: true, keyEnv: 'ANTHROPIC_API_KEY', defaultModel: 'claude-3-5-sonnet-latest' },
|
|
47
|
+
huggingface: { api: 'openai', label: 'Hugging Face', base: 'https://router.huggingface.co', egress: true, needsKey: true, keyEnv: 'HF_TOKEN' },
|
|
48
|
+
deepseek: { api: 'openai', label: 'DeepSeek', base: 'https://api.deepseek.com', egress: true, needsKey: true, keyEnv: 'DEEPSEEK_API_KEY', defaultModel: 'deepseek-chat' },
|
|
49
|
+
openrouter: { api: 'openai', label: 'OpenRouter', base: 'https://openrouter.ai/api', egress: true, needsKey: true, keyEnv: 'OPENROUTER_API_KEY' },
|
|
50
|
+
// `custom` = any OpenAI-compatible endpoint you point at (a local vLLM / llama-server / LM Studio, a
|
|
51
|
+
// LAN model, a self-host). No key required by default; set `api:'anthropic'` for a Claude-shaped one.
|
|
52
|
+
custom: { api: 'openai', label: 'Custom (bring your own base)', egress: true, needsKey: false }
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const ALIASES = { claude: 'anthropic', hf: 'huggingface', 'gguf': 'local' };
|
|
56
|
+
|
|
57
|
+
function err( message, code ) { return Object.assign(new Error(message), { code: code }); }
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Normalize a backend spec into a descriptor (PURE — no maker call, no secret logged). Precedence per
|
|
61
|
+
* field: `spec > environment > preset default`. Default preset is `custom` (a bare OpenAI-compatible
|
|
62
|
+
* endpoint), so `resolveBackend({ base, key })` just works for a self-hosted model.
|
|
63
|
+
* @param spec { preset?, base?, model?, key?, modelPath?, api?, egress? }
|
|
64
|
+
* @returns { name, api, base, model, key, modelPath, egress, needsKey, keyEnv, label }
|
|
65
|
+
*/
|
|
66
|
+
function resolveBackend( spec ) {
|
|
67
|
+
spec = spec || {};
|
|
68
|
+
const asked = spec.preset || spec.backend || spec.name || 'custom';
|
|
69
|
+
const name = ALIASES[asked] || asked;
|
|
70
|
+
const preset = BACKENDS[name];
|
|
71
|
+
if ( !preset ) throw err('unknown backend preset "' + asked + '" — known: ' + Object.keys(BACKENDS).join(', '), 'UNKNOWN_BACKEND');
|
|
72
|
+
|
|
73
|
+
const api = spec.api || preset.api;
|
|
74
|
+
const base = spec.base || preset.base || process.env.LLM_BASE || null;
|
|
75
|
+
const model = spec.model || process.env.LLM_MODEL || preset.defaultModel || null;
|
|
76
|
+
const key = spec.key || (preset.keyEnv && process.env[preset.keyEnv]) || process.env.LLM_KEY || null;
|
|
77
|
+
const modelPath = api === 'local' ? (spec.modelPath || process.env.LOCAL_MODEL || null) : null;
|
|
78
|
+
const egress = spec.egress != null ? !!spec.egress : !!preset.egress;
|
|
79
|
+
|
|
80
|
+
return { name: name, api: api, base: base, model: model, key: key, modelPath: modelPath,
|
|
81
|
+
egress: egress, needsKey: !!preset.needsKey, keyEnv: preset.keyEnv || null, label: preset.label };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build a chat `ask` for a backend spec. Dispatches on the resolved flavour; a needs-key preset with no
|
|
86
|
+
* resolvable key is a typed `NO_API_KEY` error UP FRONT (a clear message beats an opaque 401 mid-call).
|
|
87
|
+
* @returns async ({system,user,maxTokens,temperature}) -> text
|
|
88
|
+
*/
|
|
89
|
+
function makeBackend( spec ) {
|
|
90
|
+
spec = spec || {};
|
|
91
|
+
const d = resolveBackend(spec);
|
|
92
|
+
if ( d.needsKey && !d.key ) throw err('backend "' + d.name + '" needs an API key — set ' + (d.keyEnv || 'a key') + ' or pass { key }', 'NO_API_KEY');
|
|
93
|
+
|
|
94
|
+
if ( d.api === 'local' ) {
|
|
95
|
+
// lazy: only a host that actually uses the embedded model pays for the optional native dep.
|
|
96
|
+
const makeLocalAsk = require('./llm-local').makeLocalAsk;
|
|
97
|
+
return makeLocalAsk(Object.assign({}, spec.localOpts, { modelPath: d.modelPath, reasoningBudget: spec.reasoningBudget }));
|
|
98
|
+
}
|
|
99
|
+
const opts = { api: d.api, base: d.base, model: d.model, key: d.key };
|
|
100
|
+
if ( spec.extraBody ) opts.extraBody = spec.extraBody;
|
|
101
|
+
if ( spec.assistantPrefill != null ) opts.assistantPrefill = spec.assistantPrefill;
|
|
102
|
+
return llm.makeAsk(opts); // dispatches openai/anthropic on opts.api
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The presets as a SECRET-FREE list (for a Studio dropdown / `sg` help): name, label, flavour, whether a
|
|
107
|
+
* key is needed and from which env, and the egress class. Never returns a resolved key.
|
|
108
|
+
*/
|
|
109
|
+
function listBackends() {
|
|
110
|
+
return Object.keys(BACKENDS).map(function ( name ) {
|
|
111
|
+
const p = BACKENDS[name];
|
|
112
|
+
return { name: name, label: p.label, api: p.api, base: p.base || null,
|
|
113
|
+
egress: !!p.egress, needsKey: !!p.needsKey, keyEnv: p.keyEnv || null };
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { BACKENDS: BACKENDS, ALIASES: ALIASES, resolveBackend: resolveBackend, makeBackend: makeBackend, listBackends: listBackends };
|