@exellix/graph-composer 2.0.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/.env.example +66 -0
- package/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/aiTaskProfile.d.ts +66 -0
- package/dist/aiTaskProfile.d.ts.map +1 -0
- package/dist/aiTaskProfile.js +179 -0
- package/dist/canonicalGraphDocument.d.ts +8 -0
- package/dist/canonicalGraphDocument.d.ts.map +1 -0
- package/dist/canonicalGraphDocument.js +344 -0
- package/dist/canonicalGraphWarnings.d.ts +6 -0
- package/dist/canonicalGraphWarnings.d.ts.map +1 -0
- package/dist/canonicalGraphWarnings.js +140 -0
- package/dist/catalogMatchAssist.d.ts +20 -0
- package/dist/catalogMatchAssist.d.ts.map +1 -0
- package/dist/catalogMatchAssist.js +203 -0
- package/dist/cataloxCatalogBridge.d.ts +103 -0
- package/dist/cataloxCatalogBridge.d.ts.map +1 -0
- package/dist/cataloxCatalogBridge.js +222 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +43 -0
- package/dist/composeInstructions.d.ts +11 -0
- package/dist/composeInstructions.d.ts.map +1 -0
- package/dist/composeInstructions.js +39 -0
- package/dist/defaultUtilitySkills.d.ts +4 -0
- package/dist/defaultUtilitySkills.d.ts.map +1 -0
- package/dist/defaultUtilitySkills.js +5 -0
- package/dist/exampleGeneration.d.ts +15 -0
- package/dist/exampleGeneration.d.ts.map +1 -0
- package/dist/exampleGeneration.js +72 -0
- package/dist/exampleInputs.d.ts +12 -0
- package/dist/exampleInputs.d.ts.map +1 -0
- package/dist/exampleInputs.js +181 -0
- package/dist/graphComposerActions.d.ts +22 -0
- package/dist/graphComposerActions.d.ts.map +1 -0
- package/dist/graphComposerActions.js +168 -0
- package/dist/graphComposerAgent.d.ts +26 -0
- package/dist/graphComposerAgent.d.ts.map +1 -0
- package/dist/graphComposerAgent.js +175 -0
- package/dist/graphComposerOutputValidation.d.ts +23 -0
- package/dist/graphComposerOutputValidation.d.ts.map +1 -0
- package/dist/graphComposerOutputValidation.js +709 -0
- package/dist/graphConceptPatchMerge.d.ts +10 -0
- package/dist/graphConceptPatchMerge.d.ts.map +1 -0
- package/dist/graphConceptPatchMerge.js +40 -0
- package/dist/graphEngineBridge.d.ts +7 -0
- package/dist/graphEngineBridge.d.ts.map +1 -0
- package/dist/graphEngineBridge.js +5 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/openRouterConnectTimeout.d.ts +3 -0
- package/dist/openRouterConnectTimeout.d.ts.map +1 -0
- package/dist/openRouterConnectTimeout.js +48 -0
- package/dist/packDir.d.ts +7 -0
- package/dist/packDir.d.ts.map +1 -0
- package/dist/packDir.js +23 -0
- package/dist/parseGraphConceptStory.d.ts +21 -0
- package/dist/parseGraphConceptStory.d.ts.map +1 -0
- package/dist/parseGraphConceptStory.js +105 -0
- package/dist/redactForLog.d.ts +2 -0
- package/dist/redactForLog.d.ts.map +1 -0
- package/dist/redactForLog.js +37 -0
- package/dist/runGraphComposer.d.ts +54 -0
- package/dist/runGraphComposer.d.ts.map +1 -0
- package/dist/runGraphComposer.js +444 -0
- package/dist/scopingCatalogHostTypes.d.ts +28 -0
- package/dist/scopingCatalogHostTypes.d.ts.map +1 -0
- package/dist/scopingCatalogHostTypes.js +6 -0
- package/dist/scopingNeedMatchAssist.d.ts +14 -0
- package/dist/scopingNeedMatchAssist.d.ts.map +1 -0
- package/dist/scopingNeedMatchAssist.js +58 -0
- package/dist/taskNodeTaskVariable.d.ts +44 -0
- package/dist/taskNodeTaskVariable.d.ts.map +1 -0
- package/dist/taskNodeTaskVariable.js +347 -0
- package/dist/types.d.ts +174 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/examples/network-vuln-subnet-triage.v2.json +389 -0
- package/functions/graph-composer/meta.json +607 -0
- package/functions/graph-composer/prompts/README.md +46 -0
- package/functions/graph-composer/prompts/action-create.md +51 -0
- package/functions/graph-composer/prompts/action-explain.md +26 -0
- package/functions/graph-composer/prompts/action-modify.md +32 -0
- package/functions/graph-composer/prompts/action-review-concept.md +97 -0
- package/functions/graph-composer/prompts/action-suggest-catalog-creations.md +31 -0
- package/functions/graph-composer/prompts/action-suggest-catalog-resolution.md +42 -0
- package/functions/graph-composer/prompts/action-suggest-concept-objective.md +38 -0
- package/functions/graph-composer/prompts/action-suggest-scoping-map-creation.md +31 -0
- package/functions/graph-composer/prompts/action-suggest-scoping-need-match.md +25 -0
- package/functions/graph-composer/prompts/default-utility-skills.json +22 -0
- package/functions/graph-composer/prompts/judge-rules.md +30 -0
- package/functions/graph-composer/prompts/orchestrator-system.md +21 -0
- package/functions/graph-composer/prompts/shared/graph-format.md +124 -0
- package/functions/graph-composer/prompts/shared/request-context.md +12 -0
- package/functions/graph-composer/prompts/shared/skill-selection.md +6 -0
- package/functions/graph-composer/prompts/shared/structural-validation.md +19 -0
- package/functions/graph-composer/prompts/skill-catalog-ai-header.md +3 -0
- package/functions/graph-composer/prompts/skill-catalog-utility-header.md +3 -0
- package/functions/graph-composer/prompts/skill-mode-extensible.md +7 -0
- package/functions/graph-composer/prompts/skill-mode-locked.md +7 -0
- package/functions/graph-composer/test-cases.json +52 -0
- package/package.json +86 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { initConfig } from "@x12i/env";
|
|
4
|
+
import { createLogxer } from "@x12i/logxer";
|
|
5
|
+
import { createClient } from "@x12i/funcx";
|
|
6
|
+
import { buildRequestPrompt, executeFuncx as invokeFuncxPack, } from "@x12i/funcx/functions";
|
|
7
|
+
import { composeWorkerInstructions } from "./composeInstructions.js";
|
|
8
|
+
import { buildCatalogMatchHints } from "./catalogMatchAssist.js";
|
|
9
|
+
import { buildScopingNeedMatchHints } from "./scopingNeedMatchAssist.js";
|
|
10
|
+
import { DEFAULT_UTILITY_SKILLS } from "./defaultUtilitySkills.js";
|
|
11
|
+
import { getActionDefinition } from "./graphComposerActions.js";
|
|
12
|
+
import { extractFirstFencedJsonBlock, graphComposerPackRoot, } from "./packDir.js";
|
|
13
|
+
import { isGraphConceptStoryDescription, parseGraphConceptStory, } from "./parseGraphConceptStory.js";
|
|
14
|
+
import { applyGraphConceptPatchOnlyIfEmpty } from "./graphConceptPatchMerge.js";
|
|
15
|
+
import { redactSecretsForLog } from "./redactForLog.js";
|
|
16
|
+
import { validateReviewConceptOutputAgainstInput } from "./graphComposerOutputValidation.js";
|
|
17
|
+
export { redactSecretsForLog };
|
|
18
|
+
export { applyGraphConceptPatchOnlyIfEmpty } from "./graphConceptPatchMerge.js";
|
|
19
|
+
function processSuggestConceptObjectiveOutput(shaped, intent, existingGraph) {
|
|
20
|
+
const patchRaw = shaped.graphConceptPatch;
|
|
21
|
+
if (patchRaw === null ||
|
|
22
|
+
typeof patchRaw !== "object" ||
|
|
23
|
+
Array.isArray(patchRaw)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const patch = patchRaw;
|
|
27
|
+
const { patch: next, skipped } = applyGraphConceptPatchOnlyIfEmpty(patch, intent.onlyIfEmpty, existingGraph);
|
|
28
|
+
shaped.graphConceptPatch = next;
|
|
29
|
+
if (skipped.length > 0) {
|
|
30
|
+
const prefix = `Skipped fields already set in metadata.graphConcept: ${skipped.join(", ")}.`;
|
|
31
|
+
const r = shaped.rationale;
|
|
32
|
+
shaped.rationale =
|
|
33
|
+
typeof r === "string" && r.trim().length > 0 ? `${prefix} ${r}` : prefix;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Load .env into process.env before Logxer reads MONGO_URI (and related vars) at construction.
|
|
37
|
+
initConfig({}, { dotenvPath: ".env", throwOnMissing: false });
|
|
38
|
+
const logger = createLogxer({
|
|
39
|
+
packageName: "graph-composer",
|
|
40
|
+
envPrefix: "GRAPH_COMPOSER",
|
|
41
|
+
});
|
|
42
|
+
let cachedRules = null;
|
|
43
|
+
function loadRules() {
|
|
44
|
+
if (cachedRules !== null)
|
|
45
|
+
return cachedRules;
|
|
46
|
+
const root = graphComposerPackRoot();
|
|
47
|
+
const judgeMd = path.join(root, "prompts", "judge-rules.md");
|
|
48
|
+
const legacyJson = path.join(root, "rules.json");
|
|
49
|
+
const legacyRules = path.join(root, "rules");
|
|
50
|
+
let raw;
|
|
51
|
+
if (existsSync(judgeMd)) {
|
|
52
|
+
raw = extractFirstFencedJsonBlock(readFileSync(judgeMd, "utf-8"));
|
|
53
|
+
}
|
|
54
|
+
else if (existsSync(legacyRules)) {
|
|
55
|
+
raw = readFileSync(legacyRules, "utf-8");
|
|
56
|
+
}
|
|
57
|
+
else if (existsSync(legacyJson)) {
|
|
58
|
+
raw = readFileSync(legacyJson, "utf-8");
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
throw new Error(`Missing judge rules: expected ${judgeMd} or legacy rules.json`);
|
|
62
|
+
}
|
|
63
|
+
const parsed = JSON.parse(raw);
|
|
64
|
+
if (!Array.isArray(parsed)) {
|
|
65
|
+
cachedRules = [];
|
|
66
|
+
return cachedRules;
|
|
67
|
+
}
|
|
68
|
+
cachedRules = parsed.filter((r) => r !== null &&
|
|
69
|
+
typeof r === "object" &&
|
|
70
|
+
"rule" in r &&
|
|
71
|
+
typeof r.rule === "string" &&
|
|
72
|
+
"weight" in r &&
|
|
73
|
+
typeof r.weight === "number");
|
|
74
|
+
return cachedRules;
|
|
75
|
+
}
|
|
76
|
+
function normalizeInput(input) {
|
|
77
|
+
const skillMode = input.skillMode ?? "locked";
|
|
78
|
+
const aiSkills = input.aiSkills ?? [];
|
|
79
|
+
const utilitySkills = input.utilitySkills === undefined
|
|
80
|
+
? [...DEFAULT_UTILITY_SKILLS]
|
|
81
|
+
: input.utilitySkills;
|
|
82
|
+
return {
|
|
83
|
+
...input,
|
|
84
|
+
skillMode,
|
|
85
|
+
aiSkills,
|
|
86
|
+
utilitySkills,
|
|
87
|
+
catalogCandidates: input.catalogCandidates,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function assertInputShape(input) {
|
|
91
|
+
const def = getActionDefinition(input.intent.action);
|
|
92
|
+
if (def.requiresExistingGraph) {
|
|
93
|
+
if (input.existingGraph === undefined ||
|
|
94
|
+
input.existingGraph === null ||
|
|
95
|
+
typeof input.existingGraph !== "object") {
|
|
96
|
+
throw new Error(`existingGraph is required when intent.action is "${def.action}"`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (input.intent.action === "suggestScopingNeedMatch") {
|
|
100
|
+
const maps = input.catalogCandidates?.scopingMaps;
|
|
101
|
+
if (!Array.isArray(maps) || maps.length === 0) {
|
|
102
|
+
throw new Error('catalogCandidates.scopingMaps must be a non-empty array when intent.action is "suggestScopingNeedMatch"');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Maps `suggestedCatalogArtifacts` to `catalogProposals` when the latter is empty
|
|
108
|
+
* or missing (consumer contract alias). No-op for other actions.
|
|
109
|
+
*/
|
|
110
|
+
export function normalizeReviewConceptComposerOutput(out) {
|
|
111
|
+
const cur = out.catalogProposals;
|
|
112
|
+
const hasProposals = Array.isArray(cur) && cur.length > 0;
|
|
113
|
+
if (hasProposals)
|
|
114
|
+
return;
|
|
115
|
+
const alt = out.suggestedCatalogArtifacts;
|
|
116
|
+
if (Array.isArray(alt) && alt.length > 0) {
|
|
117
|
+
out.catalogProposals = alt;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Models sometimes omit `action` or nest the payload; normalize before validation. */
|
|
121
|
+
function unwrapComposerPayload(result) {
|
|
122
|
+
if (result === null || typeof result !== "object")
|
|
123
|
+
return result;
|
|
124
|
+
const o = result;
|
|
125
|
+
if (typeof o.action === "string")
|
|
126
|
+
return result;
|
|
127
|
+
for (const k of ["result", "output", "data", "response"]) {
|
|
128
|
+
const inner = o[k];
|
|
129
|
+
if (inner !== null && typeof inner === "object") {
|
|
130
|
+
const io = inner;
|
|
131
|
+
if (typeof io.action === "string" ||
|
|
132
|
+
io.graph !== undefined ||
|
|
133
|
+
io.explanation !== undefined ||
|
|
134
|
+
io.changelog !== undefined ||
|
|
135
|
+
io.graphConceptPatch !== undefined ||
|
|
136
|
+
io.suggestedConceptPatch !== undefined ||
|
|
137
|
+
io.skillResolution !== undefined ||
|
|
138
|
+
io.catalogRequestProposals !== undefined ||
|
|
139
|
+
io.hasSuitableMatch !== undefined ||
|
|
140
|
+
io.scopingMapProposal !== undefined ||
|
|
141
|
+
io.verdict !== undefined ||
|
|
142
|
+
io.findings !== undefined ||
|
|
143
|
+
io.catalogProposals !== undefined ||
|
|
144
|
+
io.suggestedCatalogArtifacts !== undefined ||
|
|
145
|
+
io.requirementOptions !== undefined) {
|
|
146
|
+
return inner;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
const EXPLAIN_TOP_LEVEL_KEYS = [
|
|
153
|
+
"summary",
|
|
154
|
+
"executionOrder",
|
|
155
|
+
"nodeDescriptions",
|
|
156
|
+
"dataFlow",
|
|
157
|
+
"conditionalPaths",
|
|
158
|
+
];
|
|
159
|
+
const EXPLAIN_NODE_DESC_ALIASES = ["perNodeDescriptions", "perNode"];
|
|
160
|
+
function explainTopLevelCandidatePresent(o) {
|
|
161
|
+
for (const k of EXPLAIN_TOP_LEVEL_KEYS) {
|
|
162
|
+
if (k in o && o[k] !== undefined)
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
for (const k of EXPLAIN_NODE_DESC_ALIASES) {
|
|
166
|
+
if (k in o && o[k] !== undefined)
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* If `action` is `explain` and known fields are flattened at the top level, moves them under `explanation` (see `functions/graph-composer/meta.json`). Maps `perNode` / `perNodeDescriptions` to `nodeDescriptions`. No-op for other actions or when `explanation` is already an object.
|
|
173
|
+
*/
|
|
174
|
+
export function normalizeGraphComposerExplainOutput(result) {
|
|
175
|
+
if (result === null || typeof result !== "object")
|
|
176
|
+
return result;
|
|
177
|
+
const o = result;
|
|
178
|
+
if (o.action !== "explain")
|
|
179
|
+
return result;
|
|
180
|
+
return normalizeExplainFlattenedShallow(o);
|
|
181
|
+
}
|
|
182
|
+
function normalizeExplainFlattenedShallow(o) {
|
|
183
|
+
const expl = o.explanation;
|
|
184
|
+
if (expl !== null &&
|
|
185
|
+
expl !== undefined &&
|
|
186
|
+
typeof expl === "object" &&
|
|
187
|
+
!Array.isArray(expl)) {
|
|
188
|
+
return { ...o };
|
|
189
|
+
}
|
|
190
|
+
if (!explainTopLevelCandidatePresent(o)) {
|
|
191
|
+
return { ...o };
|
|
192
|
+
}
|
|
193
|
+
const explanation = {};
|
|
194
|
+
for (const k of EXPLAIN_TOP_LEVEL_KEYS) {
|
|
195
|
+
if (k in o && o[k] !== undefined) {
|
|
196
|
+
explanation[k] = o[k];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (explanation.nodeDescriptions === undefined) {
|
|
200
|
+
if ("perNodeDescriptions" in o &&
|
|
201
|
+
o.perNodeDescriptions !== undefined) {
|
|
202
|
+
explanation.nodeDescriptions = o.perNodeDescriptions;
|
|
203
|
+
}
|
|
204
|
+
else if ("perNode" in o && o.perNode !== undefined) {
|
|
205
|
+
explanation.nodeDescriptions = o.perNode;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const omit = new Set([
|
|
209
|
+
...EXPLAIN_TOP_LEVEL_KEYS,
|
|
210
|
+
...EXPLAIN_NODE_DESC_ALIASES,
|
|
211
|
+
]);
|
|
212
|
+
const rest = {};
|
|
213
|
+
for (const [k, v] of Object.entries(o)) {
|
|
214
|
+
if (!omit.has(k))
|
|
215
|
+
rest[k] = v;
|
|
216
|
+
}
|
|
217
|
+
return { ...rest, explanation };
|
|
218
|
+
}
|
|
219
|
+
function ensureComposerOutputShape(intentAction, result) {
|
|
220
|
+
const unwrapped = unwrapComposerPayload(result);
|
|
221
|
+
if (unwrapped === null || typeof unwrapped !== "object")
|
|
222
|
+
return unwrapped;
|
|
223
|
+
const raw = unwrapped;
|
|
224
|
+
const o = typeof raw.action === "string"
|
|
225
|
+
? { ...raw }
|
|
226
|
+
: { ...raw, action: intentAction };
|
|
227
|
+
if (o.action === "explain") {
|
|
228
|
+
return normalizeExplainFlattenedShallow(o);
|
|
229
|
+
}
|
|
230
|
+
return o;
|
|
231
|
+
}
|
|
232
|
+
function validateGraphComposerOutput(intentAction, out, input) {
|
|
233
|
+
if (out.action !== intentAction) {
|
|
234
|
+
throw new Error(`Output action "${String(out.action)}" does not match intent.action "${intentAction}"`);
|
|
235
|
+
}
|
|
236
|
+
getActionDefinition(intentAction).validateOutput(out, { input });
|
|
237
|
+
}
|
|
238
|
+
const REVIEW_TRACE_MAX_JSON_CHARS = 500_000;
|
|
239
|
+
function safeJsonStringifyForLog(value) {
|
|
240
|
+
try {
|
|
241
|
+
const s = JSON.stringify(value);
|
|
242
|
+
if (s.length > REVIEW_TRACE_MAX_JSON_CHARS) {
|
|
243
|
+
return `${s.slice(0, REVIEW_TRACE_MAX_JSON_CHARS)}\n...[truncated]`;
|
|
244
|
+
}
|
|
245
|
+
return s;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return "[non-serializable]";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function logReviewConceptIo(phase, data, traceReviewConceptIo) {
|
|
252
|
+
const redacted = redactSecretsForLog(data);
|
|
253
|
+
const payload = safeJsonStringifyForLog(redacted);
|
|
254
|
+
const msg = `graph-composer reviewConcept ${phase}`;
|
|
255
|
+
const meta = {
|
|
256
|
+
payloadCharLength: payload.length,
|
|
257
|
+
payload,
|
|
258
|
+
};
|
|
259
|
+
if (traceReviewConceptIo) {
|
|
260
|
+
logger.info(msg, meta);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
logger.debug(msg, meta);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function withAskTimeout(client, timeoutMs) {
|
|
267
|
+
const ask = client.ask.bind(client);
|
|
268
|
+
return {
|
|
269
|
+
...client,
|
|
270
|
+
ask(instruction, opts) {
|
|
271
|
+
return ask(instruction, {
|
|
272
|
+
...opts,
|
|
273
|
+
timeoutMs: opts?.timeoutMs ?? timeoutMs,
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
export function resolveGraphComposerClient(options) {
|
|
279
|
+
const needsBuiltClient = options.client === undefined &&
|
|
280
|
+
(options.connectTimeoutMs !== undefined ||
|
|
281
|
+
options.askTimeoutMs !== undefined);
|
|
282
|
+
if (!needsBuiltClient && options.askTimeoutMs === undefined) {
|
|
283
|
+
return options.client;
|
|
284
|
+
}
|
|
285
|
+
if (options.client !== undefined) {
|
|
286
|
+
if (options.askTimeoutMs === undefined) {
|
|
287
|
+
return options.client;
|
|
288
|
+
}
|
|
289
|
+
return withAskTimeout(options.client, options.askTimeoutMs);
|
|
290
|
+
}
|
|
291
|
+
const base = createClient({
|
|
292
|
+
backend: "openrouter",
|
|
293
|
+
...(options.connectTimeoutMs !== undefined
|
|
294
|
+
? { openrouter: { connectTimeoutMs: options.connectTimeoutMs } }
|
|
295
|
+
: {}),
|
|
296
|
+
});
|
|
297
|
+
if (options.askTimeoutMs === undefined) {
|
|
298
|
+
return base;
|
|
299
|
+
}
|
|
300
|
+
return withAskTimeout(base, options.askTimeoutMs);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Returns a concrete OpenRouter client for flows that require `client.ask` (e.g. orchestrator).
|
|
304
|
+
* Unlike `resolveGraphComposerClient`, this always returns a `Client` when OpenRouter is configured.
|
|
305
|
+
*/
|
|
306
|
+
export function getGraphComposerLlmClient(options = {}) {
|
|
307
|
+
const resolved = resolveGraphComposerClient(options);
|
|
308
|
+
if (resolved !== undefined)
|
|
309
|
+
return resolved;
|
|
310
|
+
const base = createClient({
|
|
311
|
+
backend: "openrouter",
|
|
312
|
+
...(options.connectTimeoutMs !== undefined
|
|
313
|
+
? { openrouter: { connectTimeoutMs: options.connectTimeoutMs } }
|
|
314
|
+
: {}),
|
|
315
|
+
});
|
|
316
|
+
return options.askTimeoutMs !== undefined
|
|
317
|
+
? withAskTimeout(base, options.askTimeoutMs)
|
|
318
|
+
: base;
|
|
319
|
+
}
|
|
320
|
+
function buildGraphComposerPrompt(request) {
|
|
321
|
+
return `# graph-composer\n\n${buildRequestPrompt(request)}`;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Run a single graph-composer worker (`intent.action`) with the per-action system prompt.
|
|
325
|
+
* Shared by `runGraphComposer` and `runGraphComposerAgent`.
|
|
326
|
+
*/
|
|
327
|
+
export async function runGraphWorker(input, options = {}) {
|
|
328
|
+
assertInputShape(input);
|
|
329
|
+
const normalized = normalizeInput(input);
|
|
330
|
+
const action = normalized.intent.action;
|
|
331
|
+
getActionDefinition(action);
|
|
332
|
+
const rules = loadRules();
|
|
333
|
+
const system = composeWorkerInstructions(action, normalized.aiSkills, normalized.utilitySkills, normalized.skillMode);
|
|
334
|
+
/** Per-mode system text for aifunctions-js (same pack for all tiers unless you add ultra-specific prompts). */
|
|
335
|
+
const instructions = {
|
|
336
|
+
weak: system,
|
|
337
|
+
normal: system,
|
|
338
|
+
strong: system,
|
|
339
|
+
ultra: system,
|
|
340
|
+
};
|
|
341
|
+
const client = resolveGraphComposerClient(options);
|
|
342
|
+
let requestForPrompt = normalized;
|
|
343
|
+
const assist = options.catalogMatchListsAssist !== false &&
|
|
344
|
+
(action === "suggestCatalogResolution" ||
|
|
345
|
+
action === "suggestCatalogCreations") &&
|
|
346
|
+
client !== undefined &&
|
|
347
|
+
normalized.catalogCandidates !== undefined;
|
|
348
|
+
if (assist) {
|
|
349
|
+
const hints = await buildCatalogMatchHints(normalized, {
|
|
350
|
+
client,
|
|
351
|
+
mode: options.mode,
|
|
352
|
+
model: options.model,
|
|
353
|
+
});
|
|
354
|
+
if (hints !== undefined) {
|
|
355
|
+
requestForPrompt = { ...normalized, catalogMatchHints: hints };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const scopingAssist = options.catalogMatchListsAssist !== false &&
|
|
359
|
+
action === "suggestScopingNeedMatch" &&
|
|
360
|
+
client !== undefined;
|
|
361
|
+
if (scopingAssist) {
|
|
362
|
+
const sh = await buildScopingNeedMatchHints(normalized, {
|
|
363
|
+
client,
|
|
364
|
+
mode: options.mode,
|
|
365
|
+
model: options.model,
|
|
366
|
+
});
|
|
367
|
+
if (sh !== undefined) {
|
|
368
|
+
requestForPrompt = { ...requestForPrompt, scopingNeedMatchHints: sh };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const focusEmpty = normalized.intent.focusNodeIds === undefined ||
|
|
372
|
+
normalized.intent.focusNodeIds.length === 0;
|
|
373
|
+
if ((action === "create" ||
|
|
374
|
+
action === "modify" ||
|
|
375
|
+
action === "reviewConcept") &&
|
|
376
|
+
focusEmpty &&
|
|
377
|
+
isGraphConceptStoryDescription(normalized.intent.description)) {
|
|
378
|
+
requestForPrompt = {
|
|
379
|
+
...requestForPrompt,
|
|
380
|
+
graphConceptFromStory: {
|
|
381
|
+
parsed: parseGraphConceptStory(normalized.intent.description),
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (action === "reviewConcept") {
|
|
386
|
+
logReviewConceptIo("request", requestForPrompt, options.traceReviewConceptIo);
|
|
387
|
+
}
|
|
388
|
+
logger.info("graph-composer worker start", {
|
|
389
|
+
action,
|
|
390
|
+
skillMode: normalized.skillMode,
|
|
391
|
+
aiSkillCount: normalized.aiSkills.length,
|
|
392
|
+
utilitySkillCount: normalized.utilitySkills.length,
|
|
393
|
+
composedInstructionChars: system.length,
|
|
394
|
+
});
|
|
395
|
+
const mode = options.mode ?? "strong";
|
|
396
|
+
try {
|
|
397
|
+
// One @x12i/funcx function-pack LLM call for this intent.action (not a DAG node skill).
|
|
398
|
+
const result = await invokeFuncxPack({
|
|
399
|
+
request: requestForPrompt,
|
|
400
|
+
buildPrompt: buildGraphComposerPrompt,
|
|
401
|
+
instructions,
|
|
402
|
+
rules,
|
|
403
|
+
client,
|
|
404
|
+
mode,
|
|
405
|
+
model: options.model,
|
|
406
|
+
temperature: options.temperature,
|
|
407
|
+
maxTokens: options.maxTokens,
|
|
408
|
+
});
|
|
409
|
+
if (action === "reviewConcept") {
|
|
410
|
+
logReviewConceptIo("rawResult", result, options.traceReviewConceptIo);
|
|
411
|
+
}
|
|
412
|
+
const shaped = ensureComposerOutputShape(action, result);
|
|
413
|
+
if (shaped !== null && typeof shaped === "object") {
|
|
414
|
+
const o = shaped;
|
|
415
|
+
if (action === "reviewConcept") {
|
|
416
|
+
normalizeReviewConceptComposerOutput(o);
|
|
417
|
+
}
|
|
418
|
+
if (action === "suggestConceptObjective") {
|
|
419
|
+
processSuggestConceptObjectiveOutput(o, normalized.intent, normalized.existingGraph);
|
|
420
|
+
}
|
|
421
|
+
validateGraphComposerOutput(action, shaped, normalized);
|
|
422
|
+
if (action === "reviewConcept") {
|
|
423
|
+
validateReviewConceptOutputAgainstInput(shaped, {
|
|
424
|
+
existingGraph: normalized.existingGraph,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
logger.info("graph-composer worker complete", { action });
|
|
429
|
+
return shaped;
|
|
430
|
+
}
|
|
431
|
+
catch (e) {
|
|
432
|
+
logger.error("graph-composer worker failed", {
|
|
433
|
+
action,
|
|
434
|
+
message: e instanceof Error ? e.message : String(e),
|
|
435
|
+
});
|
|
436
|
+
throw e;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
export async function runGraphComposer(input, options = {}) {
|
|
440
|
+
return runGraphWorker(input, options);
|
|
441
|
+
}
|
|
442
|
+
export function getPackDir() {
|
|
443
|
+
return graphComposerPackRoot();
|
|
444
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side handoff types for creating scoping catalog rows (e.g. Catalox native
|
|
3
|
+
* catalog + governance, or your persistence layer). **Verify** field names against
|
|
4
|
+
* your catalog schema; graph-composer does not perform CRUD.
|
|
5
|
+
*/
|
|
6
|
+
/** Payload your service might send to register a new scoping map document shape. */
|
|
7
|
+
export type WoroxScopingMapCatalogCreatePayload = {
|
|
8
|
+
/** Canonical id, e.g. xmemory:scopemap:v1:... */
|
|
9
|
+
scopingMapId: string;
|
|
10
|
+
title: string;
|
|
11
|
+
purpose: string;
|
|
12
|
+
/** Optional: default question framing for scoped Q&A docs */
|
|
13
|
+
questionTitle?: string;
|
|
14
|
+
/** Tags for list APIs / UI */
|
|
15
|
+
tags?: string[];
|
|
16
|
+
};
|
|
17
|
+
/** Example Mongo-oriented create body for xmemory scoped data layout (illustrative). */
|
|
18
|
+
export type WoroxScopedDataDocumentShape = {
|
|
19
|
+
_meta: {
|
|
20
|
+
scopingMapId: string;
|
|
21
|
+
entityId: string;
|
|
22
|
+
entityType?: string;
|
|
23
|
+
questionTitle?: string;
|
|
24
|
+
scopedAt?: string;
|
|
25
|
+
};
|
|
26
|
+
answer: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=scopingCatalogHostTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopingCatalogHostTypes.d.ts","sourceRoot":"","sources":["../src/scopingCatalogHostTypes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oFAAoF;AACpF,MAAM,MAAM,mCAAmC,GAAG;IAChD,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,wFAAwF;AACxF,MAAM,MAAM,4BAA4B,GAAG;IACzC,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Client } from "@x12i/funcx";
|
|
2
|
+
import type { LlmMode } from "@x12i/funcx/functions";
|
|
3
|
+
import type { GraphComposerInput } from "./types.js";
|
|
4
|
+
export type ScopingNeedMatchAssistOptions = {
|
|
5
|
+
client: Client;
|
|
6
|
+
mode?: LlmMode;
|
|
7
|
+
model?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* `matchLists` pre-pass: map the stated need (and optional graph hint) to `scopingMaps` candidates.
|
|
11
|
+
* Injected as `scopingNeedMatchHints` on the worker request payload.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildScopingNeedMatchHints(input: GraphComposerInput, opts: ScopingNeedMatchAssistOptions): Promise<Record<string, unknown> | undefined>;
|
|
14
|
+
//# sourceMappingURL=scopingNeedMatchAssist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopingNeedMatchAssist.d.ts","sourceRoot":"","sources":["../src/scopingNeedMatchAssist.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AA0BpB,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,kBAAkB,EACzB,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAqC9C"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { matchLists } from "@x12i/funcx/functions";
|
|
2
|
+
function scopingCatalogItems(maps) {
|
|
3
|
+
return maps.map((m) => ({
|
|
4
|
+
scopingMapId: m.scopingMapId,
|
|
5
|
+
questionId: m.questionId,
|
|
6
|
+
title: m.title,
|
|
7
|
+
description: m.description,
|
|
8
|
+
tags: m.tags ?? [],
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
function graphOneLiner(graph) {
|
|
12
|
+
if (graph === undefined || graph === null || typeof graph !== "object") {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const g = graph;
|
|
16
|
+
const id = typeof g.id === "string" ? g.id : "";
|
|
17
|
+
const nodes = g.nodes;
|
|
18
|
+
const n = Array.isArray(nodes) ? nodes.length : 0;
|
|
19
|
+
if (!id && n === 0)
|
|
20
|
+
return undefined;
|
|
21
|
+
return [id ? `graphId=${id}` : null, `taskNodeCount~${n}`]
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.join(" ");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `matchLists` pre-pass: map the stated need (and optional graph hint) to `scopingMaps` candidates.
|
|
27
|
+
* Injected as `scopingNeedMatchHints` on the worker request payload.
|
|
28
|
+
*/
|
|
29
|
+
export async function buildScopingNeedMatchHints(input, opts) {
|
|
30
|
+
if (input.intent.action !== "suggestScopingNeedMatch") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const maps = input.catalogCandidates?.scopingMaps;
|
|
34
|
+
if (!Array.isArray(maps) || maps.length === 0) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const need = input.intent.description?.trim() ?? "";
|
|
38
|
+
if (need === "") {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const hint = graphOneLiner(input.existingGraph);
|
|
42
|
+
const list1 = [
|
|
43
|
+
{
|
|
44
|
+
need,
|
|
45
|
+
...(hint !== undefined ? { graphHint: hint } : {}),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const { client, mode, model } = opts;
|
|
49
|
+
const r = await matchLists({
|
|
50
|
+
list1,
|
|
51
|
+
list2: scopingCatalogItems(maps),
|
|
52
|
+
guidance: "Match the data need to the single best scoping map or questionId candidate. Prefer precise title/description fit over generic maps. Use reason briefly.",
|
|
53
|
+
mode,
|
|
54
|
+
client,
|
|
55
|
+
model,
|
|
56
|
+
}, { rules: [] });
|
|
57
|
+
return { matches: r.matches, unmatched: r.unmatched };
|
|
58
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
* Task nodes (GraphModelObject / TaskNode): runtime payload bindings in
|
|
4
|
+
|
|
5
|
+
* `inputsConfig`; dynamic task config in `taskVariable`; results in
|
|
6
|
+
|
|
7
|
+
* `executionMapping`. See `docs/formats-documentations/`.
|
|
8
|
+
|
|
9
|
+
*/
|
|
10
|
+
export type TaskNodeInputsLayoutIssueCode = "variables_in_inputs" | "question_in_inputs" | "deprecated_inputs_field" | "deprecated_inputs_and_inputsConfig" | "deprecated_output_mapping" | "metadata_ai_task_profile" | "metadata_narrix" | "metadata_execution_key";
|
|
11
|
+
export type TaskNodeInputsLayoutIssue = {
|
|
12
|
+
nodeId: string;
|
|
13
|
+
code: TaskNodeInputsLayoutIssueCode;
|
|
14
|
+
keys?: string[];
|
|
15
|
+
metadataKey?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function isFinalizerNode(node: Record<string, unknown>): boolean;
|
|
18
|
+
export declare function isTaskNode(node: Record<string, unknown>): boolean;
|
|
19
|
+
/** True when the value is a `$path` ref to a template variable bucket. */
|
|
20
|
+
export declare function isTaskVariablePathRef(v: unknown): boolean;
|
|
21
|
+
/** Dual-read: canonical `inputsConfig`, then legacy `inputs`. */
|
|
22
|
+
export declare function resolveTaskNodeInputsConfig(node: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
23
|
+
/** @deprecated Use `resolveTaskNodeInputsConfig`. */
|
|
24
|
+
export declare function resolveTaskNodeInputs(node: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
25
|
+
export declare function resolveTaskNodeTaskVariable(node: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
26
|
+
export declare function reportTaskNodeInputsLayoutIssues(graph: object): TaskNodeInputsLayoutIssue[];
|
|
27
|
+
/**
|
|
28
|
+
|
|
29
|
+
* Canonicalizes task nodes toward `docs/formats-documentations`:
|
|
30
|
+
|
|
31
|
+
* - splits dynamic config into `taskVariable`
|
|
32
|
+
|
|
33
|
+
* - renames `inputs` → `inputsConfig`, `outputMapping` → `executionMapping`
|
|
34
|
+
|
|
35
|
+
* - moves execution keys from `metadata` → `taskConfiguration`
|
|
36
|
+
|
|
37
|
+
* - preserves `variables.*` / `jobVariables.*` / `taskVariables.*` path refs as authored
|
|
38
|
+
|
|
39
|
+
*/
|
|
40
|
+
export declare function canonicalizeGraphTaskNodeTaskVariable(graph: object): object;
|
|
41
|
+
/** Full graph model canonicalization for create/modify output. */
|
|
42
|
+
export declare function canonicalizeGraphModel(graph: object): object;
|
|
43
|
+
export declare function taskNodeInputsLayoutWarningMessage(issues: TaskNodeInputsLayoutIssue[]): string;
|
|
44
|
+
//# sourceMappingURL=taskNodeTaskVariable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taskNodeTaskVariable.d.ts","sourceRoot":"","sources":["../src/taskNodeTaskVariable.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,MAAM,MAAM,6BAA6B,GAErC,qBAAqB,GAErB,oBAAoB,GAEpB,yBAAyB,GAEzB,oCAAoC,GAEpC,2BAA2B,GAE3B,0BAA0B,GAE1B,iBAAiB,GAEjB,wBAAwB,CAAC;AAI7B,MAAM,MAAM,yBAAyB,GAAG;IAEtC,MAAM,EAAE,MAAM,CAAC;IAEf,IAAI,EAAE,6BAA6B,CAAC;IAEpC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB,WAAW,CAAC,EAAE,MAAM,CAAC;CAEtB,CAAC;AAsDF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAItE;AAID,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAQjE;AAkBD,0EAA0E;AAE1E,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAgBzD;AAID,iEAAiE;AAEjE,wBAAgB,2BAA2B,CAEzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAE5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAMrC;AAID,qDAAqD;AAErD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAErC;AAID,wBAAgB,2BAA2B,CAEzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAE5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAMrC;AAgCD,wBAAgB,gCAAgC,CAE9C,KAAK,EAAE,MAAM,GAEZ,yBAAyB,EAAE,CAwH7B;AAoOD;;;;;;;;;;;;GAYG;AAEH,wBAAgB,qCAAqC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3E;AAID,kEAAkE;AAElE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAoD5D;AAID,wBAAgB,kCAAkC,CAEhD,MAAM,EAAE,yBAAyB,EAAE,GAElC,MAAM,CA8HR"}
|