@x12i/memorix-service 3.0.6 → 3.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/README.md +8 -1
- package/dist/ai-services/associate-prep.d.ts +37 -0
- package/dist/ai-services/associate-prep.d.ts.map +1 -0
- package/dist/ai-services/associate-prep.js +208 -0
- package/dist/ai-services/associate-prep.js.map +1 -0
- package/dist/ai-services/chunk-markdown.d.ts +22 -0
- package/dist/ai-services/chunk-markdown.d.ts.map +1 -0
- package/dist/ai-services/chunk-markdown.js +159 -0
- package/dist/ai-services/chunk-markdown.js.map +1 -0
- package/dist/ai-services/client.d.ts +31 -0
- package/dist/ai-services/client.d.ts.map +1 -0
- package/dist/ai-services/client.js +104 -0
- package/dist/ai-services/client.js.map +1 -0
- package/dist/ai-services/config.d.ts +17 -0
- package/dist/ai-services/config.d.ts.map +1 -0
- package/dist/ai-services/config.js +44 -0
- package/dist/ai-services/config.js.map +1 -0
- package/dist/ai-services/normalize-output.d.ts +29 -0
- package/dist/ai-services/normalize-output.d.ts.map +1 -0
- package/dist/ai-services/normalize-output.js +428 -0
- package/dist/ai-services/normalize-output.js.map +1 -0
- package/dist/ai-services/skill-execution.d.ts +24 -0
- package/dist/ai-services/skill-execution.d.ts.map +1 -0
- package/dist/ai-services/skill-execution.js +74 -0
- package/dist/ai-services/skill-execution.js.map +1 -0
- package/dist/ai-services/types.d.ts +62 -0
- package/dist/ai-services/types.d.ts.map +1 -0
- package/dist/ai-services/types.js +3 -0
- package/dist/ai-services/types.js.map +1 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +41 -6
- package/dist/app.js.map +1 -1
- package/dist/assist/ask.d.ts +35 -0
- package/dist/assist/ask.d.ts.map +1 -0
- package/dist/assist/ask.js +346 -0
- package/dist/assist/ask.js.map +1 -0
- package/dist/assist/facts.d.ts +5 -0
- package/dist/assist/facts.d.ts.map +1 -0
- package/dist/assist/facts.js +237 -0
- package/dist/assist/facts.js.map +1 -0
- package/dist/assist/merge-ask-proposal.d.ts +9 -0
- package/dist/assist/merge-ask-proposal.d.ts.map +1 -0
- package/dist/assist/merge-ask-proposal.js +57 -0
- package/dist/assist/merge-ask-proposal.js.map +1 -0
- package/dist/assist/types.d.ts +60 -0
- package/dist/assist/types.d.ts.map +1 -0
- package/dist/assist/types.js +57 -0
- package/dist/assist/types.js.map +1 -0
- package/dist/assist/validate.d.ts +4 -0
- package/dist/assist/validate.d.ts.map +1 -0
- package/dist/assist/validate.js +319 -0
- package/dist/assist/validate.js.map +1 -0
- package/dist/cli.js +29 -6
- package/dist/cli.js.map +1 -1
- package/dist/connector-catalog.d.ts +68 -0
- package/dist/connector-catalog.d.ts.map +1 -0
- package/dist/connector-catalog.js +258 -0
- package/dist/connector-catalog.js.map +1 -0
- package/dist/connector-role.d.ts +22 -0
- package/dist/connector-role.d.ts.map +1 -0
- package/dist/connector-role.js +71 -0
- package/dist/connector-role.js.map +1 -0
- package/dist/destination-runtime.d.ts +8 -0
- package/dist/destination-runtime.d.ts.map +1 -0
- package/dist/destination-runtime.js +11 -0
- package/dist/destination-runtime.js.map +1 -0
- package/dist/execution-target.d.ts +141 -0
- package/dist/execution-target.d.ts.map +1 -0
- package/dist/execution-target.js +853 -0
- package/dist/execution-target.js.map +1 -0
- package/dist/generic-connectors.d.ts +41 -0
- package/dist/generic-connectors.d.ts.map +1 -0
- package/dist/generic-connectors.js +158 -0
- package/dist/generic-connectors.js.map +1 -0
- package/dist/isolation.d.ts +1 -1
- package/dist/isolation.d.ts.map +1 -1
- package/dist/jobs-scope.d.ts +21 -0
- package/dist/jobs-scope.d.ts.map +1 -0
- package/dist/jobs-scope.js +83 -0
- package/dist/jobs-scope.js.map +1 -0
- package/dist/journey/collect.js +2 -2
- package/dist/journey/collect.js.map +1 -1
- package/dist/journey/coverage.js +3 -3
- package/dist/journey/coverage.js.map +1 -1
- package/dist/known-packs.d.ts +1 -1
- package/dist/known-packs.d.ts.map +1 -1
- package/dist/known-packs.js +1 -1
- package/dist/known-packs.js.map +1 -1
- package/dist/live.d.ts +13 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +866 -2
- package/dist/live.js.map +1 -1
- package/dist/magit/magit-pack-service.d.ts +112 -0
- package/dist/magit/magit-pack-service.d.ts.map +1 -1
- package/dist/magit/magit-pack-service.js +494 -12
- package/dist/magit/magit-pack-service.js.map +1 -1
- package/dist/magit/pack-to-patchset.d.ts +20 -1
- package/dist/magit/pack-to-patchset.d.ts.map +1 -1
- package/dist/magit/pack-to-patchset.js +82 -9
- package/dist/magit/pack-to-patchset.js.map +1 -1
- package/dist/magit/package-catalog-bridge.d.ts +58 -0
- package/dist/magit/package-catalog-bridge.d.ts.map +1 -0
- package/dist/magit/package-catalog-bridge.js +220 -0
- package/dist/magit/package-catalog-bridge.js.map +1 -0
- package/dist/magit/proposal-to-pack.d.ts +22 -0
- package/dist/magit/proposal-to-pack.d.ts.map +1 -0
- package/dist/magit/proposal-to-pack.js +68 -0
- package/dist/magit/proposal-to-pack.js.map +1 -0
- package/dist/mongo-metadata-store.d.ts.map +1 -1
- package/dist/mongo-metadata-store.js +9 -3
- package/dist/mongo-metadata-store.js.map +1 -1
- package/dist/openapi/openapi.json +6262 -3139
- package/dist/org-sources.d.ts +14 -0
- package/dist/org-sources.d.ts.map +1 -0
- package/dist/org-sources.js +31 -0
- package/dist/org-sources.js.map +1 -0
- package/dist/pipeline-compile.d.ts +67 -1
- package/dist/pipeline-compile.d.ts.map +1 -1
- package/dist/pipeline-compile.js +455 -29
- package/dist/pipeline-compile.js.map +1 -1
- package/dist/pipeline-invoke.d.ts +63 -0
- package/dist/pipeline-invoke.d.ts.map +1 -0
- package/dist/pipeline-invoke.js +114 -0
- package/dist/pipeline-invoke.js.map +1 -0
- package/dist/pipeline-ports.d.ts +13 -0
- package/dist/pipeline-ports.d.ts.map +1 -0
- package/dist/pipeline-ports.js +104 -0
- package/dist/pipeline-ports.js.map +1 -0
- package/dist/platform.d.ts +47 -3
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +309 -26
- package/dist/platform.js.map +1 -1
- package/dist/routes/ai-services.d.ts +4 -0
- package/dist/routes/ai-services.d.ts.map +1 -0
- package/dist/routes/ai-services.js +239 -0
- package/dist/routes/ai-services.js.map +1 -0
- package/dist/routes/assist.d.ts +4 -0
- package/dist/routes/assist.d.ts.map +1 -0
- package/dist/routes/assist.js +75 -0
- package/dist/routes/assist.js.map +1 -0
- package/dist/routes/connector-invocations.d.ts.map +1 -1
- package/dist/routes/connector-invocations.js +18 -21
- package/dist/routes/connector-invocations.js.map +1 -1
- package/dist/routes/data-journey.js +11 -11
- package/dist/routes/data-journey.js.map +1 -1
- package/dist/routes/intelligence.d.ts.map +1 -1
- package/dist/routes/intelligence.js +19 -1
- package/dist/routes/intelligence.js.map +1 -1
- package/dist/routes/jobs.d.ts +4 -0
- package/dist/routes/jobs.d.ts.map +1 -0
- package/dist/routes/jobs.js +888 -0
- package/dist/routes/jobs.js.map +1 -0
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +278 -80
- package/dist/routes/memory.js.map +1 -1
- package/dist/routes/metadata.d.ts.map +1 -1
- package/dist/routes/metadata.js +770 -21
- package/dist/routes/metadata.js.map +1 -1
- package/dist/routes/pipelines.d.ts.map +1 -1
- package/dist/routes/pipelines.js +98 -22
- package/dist/routes/pipelines.js.map +1 -1
- package/dist/routes/relationships.d.ts.map +1 -1
- package/dist/routes/relationships.js +109 -2
- package/dist/routes/relationships.js.map +1 -1
- package/dist/routes/simulation.d.ts +7 -0
- package/dist/routes/simulation.d.ts.map +1 -0
- package/dist/routes/simulation.js +22 -0
- package/dist/routes/simulation.js.map +1 -0
- package/dist/runx/client.d.ts +18 -0
- package/dist/runx/client.d.ts.map +1 -0
- package/dist/runx/client.js +59 -0
- package/dist/runx/client.js.map +1 -0
- package/dist/simulation.d.ts +26 -0
- package/dist/simulation.d.ts.map +1 -0
- package/dist/simulation.js +46 -0
- package/dist/simulation.js.map +1 -0
- package/openapi/openapi.json +6262 -3139
- package/package.json +18 -8
package/dist/live.js
CHANGED
|
@@ -2,10 +2,47 @@
|
|
|
2
2
|
* Shared in-memory live-view ring for memorix-service (no DB).
|
|
3
3
|
* Inbound: Fastify hooks via @x12i/core-service / api-live-view.
|
|
4
4
|
* Worker/outbound: Memory HTTP pulls + createServiceInvokePorts remoteFetch.
|
|
5
|
+
* AI-services/v1 invoke when portConfig.invokeProtocol === "ai-services/1".
|
|
5
6
|
*/
|
|
6
7
|
import { createTrafficRing, createInstrumentedFetch, } from "@x12i/api-live-view";
|
|
7
8
|
import { createServiceInvokePorts, } from "@x12i/memorix-pipeline/v2";
|
|
8
9
|
import { issueCallbackGrant } from "./callback-grant.js";
|
|
10
|
+
import { createAiServicesClient } from "./ai-services/client.js";
|
|
11
|
+
import { chunkMarkdown, markdownApproxTokens, } from "./ai-services/chunk-markdown.js";
|
|
12
|
+
import { normalizeAiServiceStepOutput } from "./ai-services/normalize-output.js";
|
|
13
|
+
import { defaultSkillExecutionForItem } from "./ai-services/skill-execution.js";
|
|
14
|
+
import { associateDraftChunkSize, associateExistingCap, chunkArray, draftKey as associateDraftKey, mergeAssociateOutputs, prepareExistingKnowxForAssociate, slimDraftsForAssociate, slimKnowxForAssociate, } from "./ai-services/associate-prep.js";
|
|
15
|
+
import { deliverViaPlatform } from "./destination-runtime.js";
|
|
16
|
+
const DEFAULT_MAX_DOC_TOKENS = 200_000;
|
|
17
|
+
/** Serial by default — OpenRouter cheap/default often 408s under parallel load. */
|
|
18
|
+
const DEFAULT_CHUNK_CONCURRENCY = 1;
|
|
19
|
+
const DEFAULT_CHUNK_RETRIES = 4;
|
|
20
|
+
const DEFAULT_CHUNK_TARGET_TOKENS = 1500;
|
|
21
|
+
/** Wired by platform so chunked AI extract can patch the running step attempt. */
|
|
22
|
+
let stepProgressReporter = null;
|
|
23
|
+
export function setStepProgressReporter(reporter) {
|
|
24
|
+
stepProgressReporter = reporter;
|
|
25
|
+
}
|
|
26
|
+
async function reportStepProgress(req, progress) {
|
|
27
|
+
const attemptId = req.attemptId?.trim();
|
|
28
|
+
if (!attemptId || !stepProgressReporter)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
await stepProgressReporter(req.scope.orgId, attemptId, {
|
|
32
|
+
...progress,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* progress is best-effort */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function envInt(name, fallback) {
|
|
40
|
+
const raw = process.env[name]?.trim();
|
|
41
|
+
if (!raw)
|
|
42
|
+
return fallback;
|
|
43
|
+
const n = Number(raw);
|
|
44
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
45
|
+
}
|
|
9
46
|
export const memorixLiveRing = createTrafficRing();
|
|
10
47
|
/** Outbound Memory connector pulls (opx-source, REST fixtures). */
|
|
11
48
|
export const memoryPullFetch = createInstrumentedFetch(memorixLiveRing, {
|
|
@@ -28,8 +65,692 @@ function resolveAccess(raw) {
|
|
|
28
65
|
return [];
|
|
29
66
|
return raw.filter((a) => a === "explore" || a === "checkpoints" || a === "raw");
|
|
30
67
|
}
|
|
31
|
-
|
|
32
|
-
|
|
68
|
+
const aiClient = createAiServicesClient({ fetchImpl: pipelineWorkerFetch });
|
|
69
|
+
/**
|
|
70
|
+
* Paginate + collect knowx catalog for associate.
|
|
71
|
+
* Slim/cap applied later via prepareExistingKnowxForAssociate (same knobs as
|
|
72
|
+
* memorix-knowx-pack associate-ai.ts).
|
|
73
|
+
*/
|
|
74
|
+
async function fetchExistingKnowxForAssociate(memorix) {
|
|
75
|
+
const objectTypes = ["content", "people", "work-items"];
|
|
76
|
+
const out = [];
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
const headers = {
|
|
79
|
+
"x-memorix-org-id": memorix.orgId,
|
|
80
|
+
"x-memorix-agent-ids": memorix.agentIds.join(","),
|
|
81
|
+
};
|
|
82
|
+
if (memorix.grant)
|
|
83
|
+
headers["x-memorix-invocation-grant"] = memorix.grant;
|
|
84
|
+
// Soft upper bound before slim/cap — avoid unbounded Explore walks.
|
|
85
|
+
const hardCap = Math.max(associateExistingCap() * 3, 360);
|
|
86
|
+
for (const objectType of objectTypes) {
|
|
87
|
+
let cursor;
|
|
88
|
+
for (let page = 0; page < 20 && out.length < hardCap; page++) {
|
|
89
|
+
try {
|
|
90
|
+
const q = new URLSearchParams({
|
|
91
|
+
objectType,
|
|
92
|
+
contentType: "knowx",
|
|
93
|
+
limit: "200",
|
|
94
|
+
});
|
|
95
|
+
if (cursor)
|
|
96
|
+
q.set("cursor", cursor);
|
|
97
|
+
const url = `${memorix.baseUrl}/api/data/content?${q}`;
|
|
98
|
+
const res = await pipelineWorkerFetch(url, { headers });
|
|
99
|
+
if (!res.ok)
|
|
100
|
+
break;
|
|
101
|
+
const body = (await res.json());
|
|
102
|
+
for (const raw of body.records ?? []) {
|
|
103
|
+
const slim = slimKnowxForAssociate(raw);
|
|
104
|
+
const key = associateDraftKey(slim);
|
|
105
|
+
if (!key || seen.has(key))
|
|
106
|
+
continue;
|
|
107
|
+
seen.add(key);
|
|
108
|
+
out.push(slim ?? raw);
|
|
109
|
+
if (out.length >= hardCap)
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
cursor = body.pageInfo?.nextCursor ?? undefined;
|
|
113
|
+
if (!cursor)
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/** Collect knowxDrafts from top-level or nested pipeline step envelopes. */
|
|
124
|
+
function collectKnowxDrafts(inputs) {
|
|
125
|
+
const bags = [inputs.knowxDrafts, inputs.drafts];
|
|
126
|
+
for (const value of Object.values(inputs)) {
|
|
127
|
+
if (!value || typeof value !== "object")
|
|
128
|
+
continue;
|
|
129
|
+
const o = value;
|
|
130
|
+
bags.push(o.knowxDrafts, o.drafts);
|
|
131
|
+
for (const key of ["value", "body", "data", "output", "result"]) {
|
|
132
|
+
const nested = o[key];
|
|
133
|
+
if (nested && typeof nested === "object") {
|
|
134
|
+
const n = nested;
|
|
135
|
+
bags.push(n.knowxDrafts, n.drafts);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const out = [];
|
|
140
|
+
const seen = new Set();
|
|
141
|
+
for (const bag of bags) {
|
|
142
|
+
if (!Array.isArray(bag))
|
|
143
|
+
continue;
|
|
144
|
+
for (const raw of bag) {
|
|
145
|
+
if (!raw || typeof raw !== "object")
|
|
146
|
+
continue;
|
|
147
|
+
const id = String(raw.recordId ?? "").trim();
|
|
148
|
+
if (id && seen.has(id))
|
|
149
|
+
continue;
|
|
150
|
+
if (id)
|
|
151
|
+
seen.add(id);
|
|
152
|
+
out.push(raw);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Shape KnowX FR `input` for skill invoke.
|
|
159
|
+
* ai-skills@0.3+ maps FR input to root `{{input}}` — do not stamp templateTokens.input.
|
|
160
|
+
*/
|
|
161
|
+
function prepareKnowxSkillInvoke(itemId, serviceId, inputs) {
|
|
162
|
+
const isExtract = serviceId === "knowx-extract" || itemId === "knowx.extract";
|
|
163
|
+
const isAssociate = serviceId === "knowx-associate-knowledge" ||
|
|
164
|
+
itemId === "knowx.associate-knowledge";
|
|
165
|
+
if (isAssociate) {
|
|
166
|
+
const collected = collectKnowxDrafts(inputs);
|
|
167
|
+
const drafts = slimDraftsForAssociate(collected.length
|
|
168
|
+
? collected
|
|
169
|
+
: Array.isArray(inputs.knowxDrafts)
|
|
170
|
+
? inputs.knowxDrafts
|
|
171
|
+
: []);
|
|
172
|
+
const draftKeys = new Set(drafts
|
|
173
|
+
.map((d) => associateDraftKey(d))
|
|
174
|
+
.filter((k) => Boolean(k)));
|
|
175
|
+
const existingRaw = Array.isArray(inputs.existingKnowx)
|
|
176
|
+
? inputs.existingKnowx
|
|
177
|
+
: [];
|
|
178
|
+
const existingKnowx = prepareExistingKnowxForAssociate({
|
|
179
|
+
existing: existingRaw,
|
|
180
|
+
draftKeys,
|
|
181
|
+
cap: associateExistingCap(),
|
|
182
|
+
});
|
|
183
|
+
return { knowxDrafts: drafts, existingKnowx };
|
|
184
|
+
}
|
|
185
|
+
if (isExtract) {
|
|
186
|
+
// Chunk fan-out builds a per-chunk bag; keep legacy fields for short docs / fixtures.
|
|
187
|
+
return {
|
|
188
|
+
text: inputs.text,
|
|
189
|
+
knowledgeSource: inputs.knowledgeSource,
|
|
190
|
+
contentId: inputs.contentId,
|
|
191
|
+
contentIds: inputs.contentIds,
|
|
192
|
+
contentDrafts: inputs.contentDrafts,
|
|
193
|
+
chunkText: inputs.chunkText,
|
|
194
|
+
chunkIndex: inputs.chunkIndex,
|
|
195
|
+
chunkCount: inputs.chunkCount,
|
|
196
|
+
contentHash: inputs.contentHash,
|
|
197
|
+
contentRevision: inputs.contentRevision,
|
|
198
|
+
pageStart: inputs.pageStart,
|
|
199
|
+
pageEnd: inputs.pageEnd,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return inputs;
|
|
203
|
+
}
|
|
204
|
+
function asRecord(v) {
|
|
205
|
+
return v && typeof v === "object" && !Array.isArray(v)
|
|
206
|
+
? v
|
|
207
|
+
: null;
|
|
208
|
+
}
|
|
209
|
+
/** Pull markdown sources for knowx.extract (contentDrafts, promote envelope, or inline text). */
|
|
210
|
+
function collectExtractSources(inputs) {
|
|
211
|
+
const out = [];
|
|
212
|
+
const drafts = [];
|
|
213
|
+
const top = inputs.contentDrafts;
|
|
214
|
+
if (Array.isArray(top))
|
|
215
|
+
drafts.push(...top);
|
|
216
|
+
for (const value of Object.values(inputs)) {
|
|
217
|
+
const o = asRecord(value);
|
|
218
|
+
if (!o)
|
|
219
|
+
continue;
|
|
220
|
+
for (const key of ["value", "body", "data", "output", "result"]) {
|
|
221
|
+
const nested = asRecord(o[key]);
|
|
222
|
+
if (nested && Array.isArray(nested.contentDrafts)) {
|
|
223
|
+
drafts.push(...nested.contentDrafts);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (Array.isArray(o.contentDrafts)) {
|
|
227
|
+
drafts.push(...o.contentDrafts);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const seen = new Set();
|
|
231
|
+
for (const d of drafts) {
|
|
232
|
+
const md = typeof d.markdown === "string" ? d.markdown.trim() : "";
|
|
233
|
+
if (!md)
|
|
234
|
+
continue;
|
|
235
|
+
const key = `${d.contentId ?? ""}:${d.contentHash ?? md.slice(0, 40)}`;
|
|
236
|
+
if (seen.has(key))
|
|
237
|
+
continue;
|
|
238
|
+
seen.add(key);
|
|
239
|
+
out.push({
|
|
240
|
+
contentId: typeof d.contentId === "string" ? d.contentId : undefined,
|
|
241
|
+
contentHash: typeof d.contentHash === "string" ? d.contentHash : undefined,
|
|
242
|
+
contentRevision: typeof d.revision === "number" ? d.revision : undefined,
|
|
243
|
+
title: typeof d.title === "string" ? d.title : undefined,
|
|
244
|
+
markdown: md,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (!out.length) {
|
|
248
|
+
const text = typeof inputs.text === "string" ? inputs.text.trim() : "";
|
|
249
|
+
const ks = asRecord(inputs.knowledgeSource);
|
|
250
|
+
const ksBytes = typeof ks?.bytes === "string" ? String(ks.bytes).trim() : "";
|
|
251
|
+
const markdown = text || ksBytes;
|
|
252
|
+
if (markdown) {
|
|
253
|
+
out.push({
|
|
254
|
+
contentId: typeof inputs.contentId === "string" ? inputs.contentId : undefined,
|
|
255
|
+
markdown,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
function draftKey(draft) {
|
|
262
|
+
if (!draft || typeof draft !== "object")
|
|
263
|
+
return "";
|
|
264
|
+
const d = draft;
|
|
265
|
+
const ot = String(d.objectType ?? "").trim();
|
|
266
|
+
const id = String(d.recordId ?? "").trim();
|
|
267
|
+
if (!ot || !id)
|
|
268
|
+
return "";
|
|
269
|
+
return `${ot}::${id}`;
|
|
270
|
+
}
|
|
271
|
+
function draftConfidence(draft) {
|
|
272
|
+
if (!draft || typeof draft !== "object")
|
|
273
|
+
return 0;
|
|
274
|
+
const data = asRecord(draft.data);
|
|
275
|
+
const epistemic = asRecord(data?.epistemic);
|
|
276
|
+
const score = Number(epistemic?.confidenceScore ?? 0);
|
|
277
|
+
return Number.isFinite(score) ? score : 0;
|
|
278
|
+
}
|
|
279
|
+
/** Merge chunk outputs: dedupe knowxDrafts by objectType::recordId (keep highest confidence). */
|
|
280
|
+
export function mergeKnowxExtractChunkOutputs(parts) {
|
|
281
|
+
const byKey = new Map();
|
|
282
|
+
const untitled = [];
|
|
283
|
+
let docHash = "";
|
|
284
|
+
for (const part of parts) {
|
|
285
|
+
if (typeof part.docHash === "string" && part.docHash && !docHash) {
|
|
286
|
+
docHash = part.docHash;
|
|
287
|
+
}
|
|
288
|
+
const drafts = Array.isArray(part.knowxDrafts) ? part.knowxDrafts : [];
|
|
289
|
+
for (const draft of drafts) {
|
|
290
|
+
const key = draftKey(draft);
|
|
291
|
+
if (!key) {
|
|
292
|
+
untitled.push(draft);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const prev = byKey.get(key);
|
|
296
|
+
if (!prev || draftConfidence(draft) >= draftConfidence(prev)) {
|
|
297
|
+
byKey.set(key, draft);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
knowxDrafts: [...byKey.values(), ...untitled],
|
|
303
|
+
...(docHash ? { docHash } : {}),
|
|
304
|
+
kind: "ai-services",
|
|
305
|
+
live: true,
|
|
306
|
+
chunkCount: parts.length,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async function mapPool(items, concurrency, fn) {
|
|
310
|
+
const out = new Array(items.length);
|
|
311
|
+
let next = 0;
|
|
312
|
+
const workers = Array.from({ length: Math.min(concurrency, Math.max(1, items.length)) }, async () => {
|
|
313
|
+
while (next < items.length) {
|
|
314
|
+
const i = next++;
|
|
315
|
+
out[i] = await fn(items[i], i);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
await Promise.all(workers);
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
function isAbortError(err) {
|
|
322
|
+
if (!err || typeof err !== "object")
|
|
323
|
+
return false;
|
|
324
|
+
const e = err;
|
|
325
|
+
return (e.name === "AbortError" ||
|
|
326
|
+
e.code === "ABORT_ERR" ||
|
|
327
|
+
/aborted|AbortError/i.test(String(e.message ?? "")));
|
|
328
|
+
}
|
|
329
|
+
async function invokeKnowxExtractChunked(req, args) {
|
|
330
|
+
const sources = collectExtractSources(args.inputs);
|
|
331
|
+
if (!sources.length) {
|
|
332
|
+
// No markdown bag — fall through to a single invoke with the prepared bag
|
|
333
|
+
// (activity-log / knowledgeSource fixtures).
|
|
334
|
+
return invokeAiServiceOnce(req, {
|
|
335
|
+
...args,
|
|
336
|
+
invokeInput: prepareKnowxSkillInvoke(args.itemId, args.serviceId, args.inputs),
|
|
337
|
+
correlationSuffix: "extract",
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
const maxDocTokens = envInt("KNOWX_AI_MAX_DOC_TOKENS", DEFAULT_MAX_DOC_TOKENS);
|
|
341
|
+
const targetTokens = envInt("KNOWX_AI_CHUNK_TOKENS", DEFAULT_CHUNK_TARGET_TOKENS);
|
|
342
|
+
const concurrency = envInt("KNOWX_AI_CHUNK_CONCURRENCY", DEFAULT_CHUNK_CONCURRENCY);
|
|
343
|
+
const work = [];
|
|
344
|
+
for (const source of sources) {
|
|
345
|
+
const tokens = markdownApproxTokens(source.markdown);
|
|
346
|
+
if (tokens > maxDocTokens) {
|
|
347
|
+
return {
|
|
348
|
+
status: "failed",
|
|
349
|
+
retryable: false,
|
|
350
|
+
error: {
|
|
351
|
+
code: "AI_DOC_TOO_LARGE",
|
|
352
|
+
message: `Document ~${tokens} tokens exceeds KNOWX_AI_MAX_DOC_TOKENS=${maxDocTokens}${source.contentId ? ` (contentId=${source.contentId})` : ""}`,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const chunks = chunkMarkdown(source.markdown, {
|
|
357
|
+
targetTokens,
|
|
358
|
+
overlapRatio: 0.1,
|
|
359
|
+
});
|
|
360
|
+
if (!chunks.length)
|
|
361
|
+
continue;
|
|
362
|
+
for (const chunk of chunks) {
|
|
363
|
+
work.push({ source, chunk });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (!work.length) {
|
|
367
|
+
return {
|
|
368
|
+
status: "failed",
|
|
369
|
+
retryable: false,
|
|
370
|
+
error: {
|
|
371
|
+
code: "AI_EXTRACT_EMPTY",
|
|
372
|
+
message: "knowx.extract found no extractable markdown chunks",
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
// Short single-chunk docs: one invoke (preserve fixture behaviour).
|
|
377
|
+
if (work.length === 1 && work[0].chunk.count === 1) {
|
|
378
|
+
const w = work[0];
|
|
379
|
+
const bag = {
|
|
380
|
+
text: w.chunk.text,
|
|
381
|
+
contentId: w.source.contentId ?? args.inputs.contentId,
|
|
382
|
+
contentIds: w.source.contentId
|
|
383
|
+
? [w.source.contentId]
|
|
384
|
+
: args.inputs.contentIds,
|
|
385
|
+
contentHash: w.source.contentHash,
|
|
386
|
+
contentRevision: w.source.contentRevision,
|
|
387
|
+
chunkText: w.chunk.text,
|
|
388
|
+
chunkIndex: 0,
|
|
389
|
+
chunkCount: 1,
|
|
390
|
+
pageStart: w.chunk.pageStart,
|
|
391
|
+
pageEnd: w.chunk.pageEnd,
|
|
392
|
+
knowledgeSource: args.inputs.knowledgeSource,
|
|
393
|
+
};
|
|
394
|
+
return invokeAiServiceOnce(req, {
|
|
395
|
+
...args,
|
|
396
|
+
invokeInput: prepareKnowxSkillInvoke(args.itemId, args.serviceId, bag),
|
|
397
|
+
correlationSuffix: "c0",
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
const retries = envInt("KNOWX_AI_CHUNK_RETRIES", DEFAULT_CHUNK_RETRIES);
|
|
401
|
+
let draftsSoFar = 0;
|
|
402
|
+
let chunksDone = 0;
|
|
403
|
+
await reportStepProgress(req, {
|
|
404
|
+
chunkIndex: 0,
|
|
405
|
+
chunkCount: work.length,
|
|
406
|
+
draftsSoFar: 0,
|
|
407
|
+
message: `Extracting chunk 0/${work.length}`,
|
|
408
|
+
});
|
|
409
|
+
const results = await mapPool(work, concurrency, async (w, i) => {
|
|
410
|
+
const bag = {
|
|
411
|
+
chunkText: w.chunk.text,
|
|
412
|
+
chunkIndex: w.chunk.index,
|
|
413
|
+
chunkCount: w.chunk.count,
|
|
414
|
+
contentId: w.source.contentId,
|
|
415
|
+
contentHash: w.source.contentHash,
|
|
416
|
+
contentRevision: w.source.contentRevision,
|
|
417
|
+
pageStart: w.chunk.pageStart,
|
|
418
|
+
pageEnd: w.chunk.pageEnd,
|
|
419
|
+
title: w.source.title,
|
|
420
|
+
// Prefer chunkText in the prompt; keep text as the chunk for {{input}} templates.
|
|
421
|
+
text: w.chunk.text,
|
|
422
|
+
};
|
|
423
|
+
let last;
|
|
424
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
425
|
+
last = await invokeAiServiceOnce(req, {
|
|
426
|
+
...args,
|
|
427
|
+
invokeInput: prepareKnowxSkillInvoke(args.itemId, args.serviceId, bag),
|
|
428
|
+
correlationSuffix: `c${w.chunk.index}-${i}:a${attempt}`,
|
|
429
|
+
timeoutMs: 180_000,
|
|
430
|
+
});
|
|
431
|
+
if (last.status === "succeeded") {
|
|
432
|
+
const part = (last.output ?? {});
|
|
433
|
+
const drafts = Array.isArray(part.knowxDrafts) ? part.knowxDrafts.length : 0;
|
|
434
|
+
chunksDone += 1;
|
|
435
|
+
draftsSoFar += drafts;
|
|
436
|
+
await reportStepProgress(req, {
|
|
437
|
+
chunkIndex: chunksDone,
|
|
438
|
+
chunkCount: work.length,
|
|
439
|
+
draftsSoFar,
|
|
440
|
+
message: `Extracting chunk ${chunksDone}/${work.length}`,
|
|
441
|
+
});
|
|
442
|
+
return last;
|
|
443
|
+
}
|
|
444
|
+
const msg = `${last.error?.code ?? ""} ${last.error?.message ?? ""}`;
|
|
445
|
+
const retryable = last.retryable || /timeout|408|429|REMOTE_UNREACHABLE|INVOKE_FAILED/i.test(msg);
|
|
446
|
+
if (!retryable || attempt >= retries)
|
|
447
|
+
return last;
|
|
448
|
+
await new Promise((r) => setTimeout(r, 1500 * (attempt + 1)));
|
|
449
|
+
}
|
|
450
|
+
return last;
|
|
451
|
+
});
|
|
452
|
+
const succeeded = [];
|
|
453
|
+
for (const r of results) {
|
|
454
|
+
if (r.status !== "succeeded" || !r.output) {
|
|
455
|
+
return {
|
|
456
|
+
status: "failed",
|
|
457
|
+
retryable: r.retryable ?? false,
|
|
458
|
+
error: r.error ?? {
|
|
459
|
+
code: "AI_EXTRACT_CHUNK_FAILED",
|
|
460
|
+
message: "knowx.extract chunk invoke failed",
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
succeeded.push(r.output);
|
|
465
|
+
}
|
|
466
|
+
const merged = mergeKnowxExtractChunkOutputs(succeeded);
|
|
467
|
+
if (!Array.isArray(merged.knowxDrafts) || merged.knowxDrafts.length === 0) {
|
|
468
|
+
return {
|
|
469
|
+
status: "failed",
|
|
470
|
+
retryable: false,
|
|
471
|
+
error: {
|
|
472
|
+
code: "AI_OUTPUT_MISSING_DRAFTS",
|
|
473
|
+
message: "knowx.extract chunk fan-out produced no knowxDrafts after merge",
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
await reportStepProgress(req, {
|
|
478
|
+
chunkIndex: work.length,
|
|
479
|
+
chunkCount: work.length,
|
|
480
|
+
draftsSoFar: Array.isArray(merged.knowxDrafts)
|
|
481
|
+
? merged.knowxDrafts.length
|
|
482
|
+
: draftsSoFar,
|
|
483
|
+
message: "Extract complete",
|
|
484
|
+
});
|
|
485
|
+
return { status: "succeeded", output: merged };
|
|
486
|
+
}
|
|
487
|
+
async function invokeAiServiceOnce(req, args) {
|
|
488
|
+
const correlationId = `${req.runId}:${req.stepId}:${req.attempt}:${args.correlationSuffix}`;
|
|
489
|
+
try {
|
|
490
|
+
const { status, body } = await aiClient.invoke(args.endpoint, {
|
|
491
|
+
itemId: args.itemId,
|
|
492
|
+
input: args.invokeInput,
|
|
493
|
+
execution: Object.keys(args.execution).length
|
|
494
|
+
? args.execution
|
|
495
|
+
: undefined,
|
|
496
|
+
correlationId,
|
|
497
|
+
}, correlationId, args.timeoutMs);
|
|
498
|
+
if (status < 200 || status >= 300 || body.ok === false) {
|
|
499
|
+
return {
|
|
500
|
+
status: "failed",
|
|
501
|
+
retryable: Boolean(body.error?.retryable),
|
|
502
|
+
error: {
|
|
503
|
+
code: body.error?.code ?? "AI_SERVICE_INVOKE_FAILED",
|
|
504
|
+
message: body.error?.message ?? `HTTP ${status}`,
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
const normalized = normalizeAiServiceStepOutput(body, {
|
|
509
|
+
itemId: args.itemId,
|
|
510
|
+
serviceId: args.serviceId,
|
|
511
|
+
});
|
|
512
|
+
if (!normalized.ok) {
|
|
513
|
+
try {
|
|
514
|
+
const dumpPath = `/tmp/knowx-ai-unparseable-${args.correlationSuffix}-${Date.now()}.json`;
|
|
515
|
+
const { writeFileSync } = await import("node:fs");
|
|
516
|
+
writeFileSync(dumpPath, JSON.stringify({
|
|
517
|
+
itemId: args.itemId,
|
|
518
|
+
serviceId: args.serviceId,
|
|
519
|
+
correlationSuffix: args.correlationSuffix,
|
|
520
|
+
code: normalized.code,
|
|
521
|
+
message: normalized.message,
|
|
522
|
+
body,
|
|
523
|
+
}, null, 2));
|
|
524
|
+
console.error(`[ai-services] unparseable dump ${dumpPath}`);
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
/* ignore dump errors */
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
status: "failed",
|
|
531
|
+
retryable: normalized.code === "AI_OUTPUT_EMPTY",
|
|
532
|
+
error: { code: normalized.code, message: normalized.message },
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return { status: "succeeded", output: normalized.output };
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
if (isAbortError(err)) {
|
|
539
|
+
return {
|
|
540
|
+
status: "failed",
|
|
541
|
+
retryable: true,
|
|
542
|
+
error: {
|
|
543
|
+
code: "AI_EXTRACT_TIMEOUT",
|
|
544
|
+
message: err instanceof Error ? err.message : "AI extract timed out",
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
status: "failed",
|
|
550
|
+
retryable: true,
|
|
551
|
+
error: {
|
|
552
|
+
code: "REMOTE_UNREACHABLE",
|
|
553
|
+
message: err instanceof Error ? err.message : String(err),
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function invokeAiServiceRemote(req, memorix) {
|
|
559
|
+
const c = (req.config ?? {});
|
|
560
|
+
const endpoint = String(c.endpoint ?? "").replace(/\/+$/, "");
|
|
561
|
+
const itemId = String(c.itemId ?? "").trim();
|
|
562
|
+
const serviceId = String(c.serviceId ?? "").trim();
|
|
563
|
+
const aiServiceType = String(c.aiServiceType ?? "").trim();
|
|
564
|
+
if (!endpoint) {
|
|
565
|
+
return {
|
|
566
|
+
status: "failed",
|
|
567
|
+
retryable: false,
|
|
568
|
+
error: { code: "REMOTE_ENDPOINT_MISSING", message: "endpoint required" },
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
if (!itemId) {
|
|
572
|
+
return {
|
|
573
|
+
status: "failed",
|
|
574
|
+
retryable: false,
|
|
575
|
+
error: { code: "AI_ITEM_MISSING", message: "itemId required" },
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
const inputs = {
|
|
579
|
+
...(req.inputs && typeof req.inputs === "object"
|
|
580
|
+
? req.inputs
|
|
581
|
+
: {}),
|
|
582
|
+
};
|
|
583
|
+
// Associate skills have no memorix-capability explore callback — push catalog.
|
|
584
|
+
if ((serviceId === "knowx-associate-knowledge" ||
|
|
585
|
+
itemId === "knowx.associate-knowledge") &&
|
|
586
|
+
memorix &&
|
|
587
|
+
(inputs.existingKnowx === undefined ||
|
|
588
|
+
(Array.isArray(inputs.existingKnowx) && inputs.existingKnowx.length === 0))) {
|
|
589
|
+
const existing = await fetchExistingKnowxForAssociate(memorix);
|
|
590
|
+
if (existing.length)
|
|
591
|
+
inputs.existingKnowx = existing;
|
|
592
|
+
}
|
|
593
|
+
const configExec = c.execution && typeof c.execution === "object"
|
|
594
|
+
? c.execution
|
|
595
|
+
: {};
|
|
596
|
+
const skillDefaults = aiServiceType === "skill"
|
|
597
|
+
? defaultSkillExecutionForItem(itemId, {
|
|
598
|
+
runId: req.runId,
|
|
599
|
+
stepId: req.stepId,
|
|
600
|
+
})
|
|
601
|
+
: undefined;
|
|
602
|
+
const execution = {
|
|
603
|
+
...skillDefaults,
|
|
604
|
+
...configExec,
|
|
605
|
+
};
|
|
606
|
+
const isKnowxExtract = serviceId === "knowx-extract" || itemId === "knowx.extract";
|
|
607
|
+
const isKnowxAssociate = serviceId === "knowx-associate-knowledge" ||
|
|
608
|
+
itemId === "knowx.associate-knowledge";
|
|
609
|
+
if (aiServiceType === "skill" &&
|
|
610
|
+
isKnowxExtract &&
|
|
611
|
+
(itemId.startsWith("knowx.") || serviceId.startsWith("knowx-"))) {
|
|
612
|
+
return invokeKnowxExtractChunked(req, {
|
|
613
|
+
endpoint,
|
|
614
|
+
itemId,
|
|
615
|
+
serviceId,
|
|
616
|
+
execution,
|
|
617
|
+
inputs,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
if (aiServiceType === "skill" &&
|
|
621
|
+
isKnowxAssociate &&
|
|
622
|
+
(itemId.startsWith("knowx.") || serviceId.startsWith("knowx-"))) {
|
|
623
|
+
return invokeKnowxAssociateChunked(req, {
|
|
624
|
+
endpoint,
|
|
625
|
+
itemId,
|
|
626
|
+
serviceId,
|
|
627
|
+
execution,
|
|
628
|
+
inputs,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
let invokeInput = inputs;
|
|
632
|
+
if (aiServiceType === "skill" &&
|
|
633
|
+
(itemId.startsWith("knowx.") || serviceId.startsWith("knowx-"))) {
|
|
634
|
+
invokeInput = prepareKnowxSkillInvoke(itemId, serviceId, inputs);
|
|
635
|
+
}
|
|
636
|
+
return invokeAiServiceOnce(req, {
|
|
637
|
+
endpoint,
|
|
638
|
+
itemId,
|
|
639
|
+
serviceId,
|
|
640
|
+
execution,
|
|
641
|
+
invokeInput,
|
|
642
|
+
correlationSuffix: "once",
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Fan-out knowx.associate-knowledge when draft count exceeds chunk size.
|
|
647
|
+
* Mirrors memorix-knowx-pack associate-ai draft chunking.
|
|
648
|
+
*/
|
|
649
|
+
async function invokeKnowxAssociateChunked(req, args) {
|
|
650
|
+
const prepared = prepareKnowxSkillInvoke(args.itemId, args.serviceId, args.inputs);
|
|
651
|
+
const drafts = Array.isArray(prepared.knowxDrafts)
|
|
652
|
+
? prepared.knowxDrafts
|
|
653
|
+
: [];
|
|
654
|
+
const existingKnowx = Array.isArray(prepared.existingKnowx)
|
|
655
|
+
? prepared.existingKnowx
|
|
656
|
+
: [];
|
|
657
|
+
const chunks = chunkArray(drafts, associateDraftChunkSize());
|
|
658
|
+
const outputs = [];
|
|
659
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
660
|
+
const chunkDrafts = chunks[i];
|
|
661
|
+
await reportStepProgress(req, {
|
|
662
|
+
chunkIndex: i + 1,
|
|
663
|
+
chunkCount: chunks.length,
|
|
664
|
+
message: `Associate chunk ${i + 1}/${chunks.length}`,
|
|
665
|
+
});
|
|
666
|
+
let once = await invokeAiServiceOnce(req, {
|
|
667
|
+
endpoint: args.endpoint,
|
|
668
|
+
itemId: args.itemId,
|
|
669
|
+
serviceId: args.serviceId,
|
|
670
|
+
execution: args.execution,
|
|
671
|
+
invokeInput: {
|
|
672
|
+
knowxDrafts: chunkDrafts,
|
|
673
|
+
existingKnowx,
|
|
674
|
+
},
|
|
675
|
+
correlationSuffix: `assoc-${i + 1}-of-${chunks.length}`,
|
|
676
|
+
});
|
|
677
|
+
// Retries: empty companion responses are flaky on deepseek-v4-flash.
|
|
678
|
+
const retriable = (code) => code === "AI_OUTPUT_UNPARSEABLE" ||
|
|
679
|
+
code === "AI_OUTPUT_MISSING_ASSOCIATE" ||
|
|
680
|
+
code === "AI_OUTPUT_EMPTY";
|
|
681
|
+
for (let attempt = 0; attempt < 2 && once.status !== "succeeded"; attempt++) {
|
|
682
|
+
if (!retriable(once.error?.code))
|
|
683
|
+
break;
|
|
684
|
+
once = await invokeAiServiceOnce(req, {
|
|
685
|
+
endpoint: args.endpoint,
|
|
686
|
+
itemId: args.itemId,
|
|
687
|
+
serviceId: args.serviceId,
|
|
688
|
+
execution: args.execution,
|
|
689
|
+
invokeInput: {
|
|
690
|
+
knowxDrafts: chunkDrafts,
|
|
691
|
+
existingKnowx,
|
|
692
|
+
},
|
|
693
|
+
correlationSuffix: `assoc-${i + 1}-of-${chunks.length}-retry${attempt + 1}`,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
if (once.status !== "succeeded" && retriable(once.error?.code)) {
|
|
697
|
+
const shrunk = existingKnowx.length > 4
|
|
698
|
+
? existingKnowx.slice(0, Math.max(4, Math.floor(existingKnowx.length / 2)))
|
|
699
|
+
: existingKnowx;
|
|
700
|
+
once = await invokeAiServiceOnce(req, {
|
|
701
|
+
endpoint: args.endpoint,
|
|
702
|
+
itemId: args.itemId,
|
|
703
|
+
serviceId: args.serviceId,
|
|
704
|
+
execution: args.execution,
|
|
705
|
+
invokeInput: {
|
|
706
|
+
knowxDrafts: chunkDrafts,
|
|
707
|
+
existingKnowx: shrunk,
|
|
708
|
+
},
|
|
709
|
+
correlationSuffix: `assoc-${i + 1}-of-${chunks.length}-shrink`,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
// Extra retries with full catalog — empty responses are intermittent.
|
|
713
|
+
for (let attempt = 0; attempt < 3 && once.status !== "succeeded" && retriable(once.error?.code); attempt++) {
|
|
714
|
+
once = await invokeAiServiceOnce(req, {
|
|
715
|
+
endpoint: args.endpoint,
|
|
716
|
+
itemId: args.itemId,
|
|
717
|
+
serviceId: args.serviceId,
|
|
718
|
+
execution: args.execution,
|
|
719
|
+
invokeInput: {
|
|
720
|
+
knowxDrafts: chunkDrafts,
|
|
721
|
+
existingKnowx,
|
|
722
|
+
},
|
|
723
|
+
correlationSuffix: `assoc-${i + 1}-of-${chunks.length}-persist${attempt + 1}`,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
if (once.status !== "succeeded") {
|
|
727
|
+
return once;
|
|
728
|
+
}
|
|
729
|
+
const out = once.output && typeof once.output === "object"
|
|
730
|
+
? once.output
|
|
731
|
+
: {};
|
|
732
|
+
outputs.push(out);
|
|
733
|
+
}
|
|
734
|
+
const merged = mergeAssociateOutputs(outputs);
|
|
735
|
+
if (!asRecord(merged.classification) &&
|
|
736
|
+
!Array.isArray(merged.relationshipPlans)) {
|
|
737
|
+
return {
|
|
738
|
+
status: "failed",
|
|
739
|
+
retryable: false,
|
|
740
|
+
error: {
|
|
741
|
+
code: "AI_OUTPUT_MISSING_ASSOCIATE",
|
|
742
|
+
message: "knowx.associate-knowledge output missing classification / relationshipPlans",
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
await reportStepProgress(req, {
|
|
747
|
+
chunkIndex: chunks.length,
|
|
748
|
+
chunkCount: chunks.length,
|
|
749
|
+
message: "Associate complete",
|
|
750
|
+
});
|
|
751
|
+
return { status: "succeeded", output: merged };
|
|
752
|
+
}
|
|
753
|
+
const baseServiceInvokePorts = createServiceInvokePorts({
|
|
33
754
|
remoteFetch: pipelineWorkerFetch,
|
|
34
755
|
buildMemorixContext: (req) => {
|
|
35
756
|
const fromConfig = resolveAccess(req.config?.memorixAccess);
|
|
@@ -55,4 +776,147 @@ export const serviceInvokePorts = createServiceInvokePorts({
|
|
|
55
776
|
};
|
|
56
777
|
},
|
|
57
778
|
});
|
|
779
|
+
/** Helpers for in-process / remote service invoke with worker-lane recording. */
|
|
780
|
+
export const serviceInvokePorts = {
|
|
781
|
+
invokeInProcess: baseServiceInvokePorts.invokeInProcess,
|
|
782
|
+
invokeRemote: async (req) => {
|
|
783
|
+
const c = (req.config ?? {});
|
|
784
|
+
if (c.invokeProtocol === "memorix-destination/1") {
|
|
785
|
+
return invokeDestinationDeliver(req);
|
|
786
|
+
}
|
|
787
|
+
if (c.invokeProtocol === "ai-services/1" ||
|
|
788
|
+
(typeof c.aiServiceType === "string" &&
|
|
789
|
+
c.aiServiceType &&
|
|
790
|
+
typeof c.itemId === "string" &&
|
|
791
|
+
c.itemId)) {
|
|
792
|
+
const fromConfig = resolveAccess(c.memorixAccess);
|
|
793
|
+
const fromInputs = resolveAccess(req.inputs?.memorixAccess);
|
|
794
|
+
const access = fromConfig.length ? fromConfig : fromInputs;
|
|
795
|
+
const grant = issueCallbackGrant({
|
|
796
|
+
orgId: req.scope.orgId,
|
|
797
|
+
agentIds: [...req.scope.agentIds],
|
|
798
|
+
access,
|
|
799
|
+
runId: req.runId,
|
|
800
|
+
stepId: req.stepId,
|
|
801
|
+
});
|
|
802
|
+
const memorix = {
|
|
803
|
+
baseUrl: resolveMemorixPublicBaseUrl(),
|
|
804
|
+
orgId: req.scope.orgId,
|
|
805
|
+
agentIds: [...req.scope.agentIds],
|
|
806
|
+
runId: req.runId,
|
|
807
|
+
stepId: req.stepId,
|
|
808
|
+
correlationId: `${req.runId}:${req.stepId}:${req.attempt}`,
|
|
809
|
+
access,
|
|
810
|
+
grant,
|
|
811
|
+
};
|
|
812
|
+
return invokeAiServiceRemote(req, memorix);
|
|
813
|
+
}
|
|
814
|
+
return baseServiceInvokePorts.invokeRemote(req);
|
|
815
|
+
},
|
|
816
|
+
};
|
|
817
|
+
function inventDeliverItems(inputs) {
|
|
818
|
+
if (!inputs || typeof inputs !== "object")
|
|
819
|
+
return [];
|
|
820
|
+
const o = inputs;
|
|
821
|
+
if (Array.isArray(o.items)) {
|
|
822
|
+
return o.items.filter((x) => Boolean(x) && typeof x === "object");
|
|
823
|
+
}
|
|
824
|
+
if (Array.isArray(o.records)) {
|
|
825
|
+
return o.records
|
|
826
|
+
.filter((x) => Boolean(x) && typeof x === "object")
|
|
827
|
+
.map((r) => ({
|
|
828
|
+
recordId: typeof r.recordId === "string" ? r.recordId : typeof r.id === "string" ? r.id : undefined,
|
|
829
|
+
objectType: typeof r.objectType === "string" ? r.objectType : undefined,
|
|
830
|
+
contentType: typeof r.contentType === "string" ? r.contentType : undefined,
|
|
831
|
+
payload: (r.payload && typeof r.payload === "object"
|
|
832
|
+
? r.payload
|
|
833
|
+
: r),
|
|
834
|
+
identity: typeof r.identity === "string" ? r.identity : undefined,
|
|
835
|
+
}));
|
|
836
|
+
}
|
|
837
|
+
// Single-item envelope from work selector.
|
|
838
|
+
if (typeof o.recordId === "string" || typeof o.id === "string" || o.payload) {
|
|
839
|
+
return [
|
|
840
|
+
{
|
|
841
|
+
recordId: typeof o.recordId === "string"
|
|
842
|
+
? o.recordId
|
|
843
|
+
: typeof o.id === "string"
|
|
844
|
+
? o.id
|
|
845
|
+
: undefined,
|
|
846
|
+
objectType: typeof o.objectType === "string" ? o.objectType : undefined,
|
|
847
|
+
contentType: typeof o.contentType === "string" ? o.contentType : undefined,
|
|
848
|
+
payload: o.payload && typeof o.payload === "object"
|
|
849
|
+
? o.payload
|
|
850
|
+
: o,
|
|
851
|
+
identity: typeof o.identity === "string" ? o.identity : undefined,
|
|
852
|
+
},
|
|
853
|
+
];
|
|
854
|
+
}
|
|
855
|
+
return [];
|
|
856
|
+
}
|
|
857
|
+
async function invokeDestinationDeliver(req) {
|
|
858
|
+
const c = (req.config ?? {});
|
|
859
|
+
const destinationId = String(c.destinationId ?? "").trim();
|
|
860
|
+
if (!destinationId) {
|
|
861
|
+
return {
|
|
862
|
+
status: "failed",
|
|
863
|
+
retryable: false,
|
|
864
|
+
error: {
|
|
865
|
+
code: "DESTINATION_ID_MISSING",
|
|
866
|
+
message: "destinationId required for memorix-destination/1",
|
|
867
|
+
},
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const items = inventDeliverItems(req.inputs);
|
|
871
|
+
if (!items.length) {
|
|
872
|
+
return {
|
|
873
|
+
status: "failed",
|
|
874
|
+
retryable: false,
|
|
875
|
+
error: {
|
|
876
|
+
code: "DELIVER_ITEMS_MISSING",
|
|
877
|
+
message: "items, records, or a single item payload required for destination deliver",
|
|
878
|
+
},
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
try {
|
|
882
|
+
const receipt = await deliverViaPlatform({
|
|
883
|
+
orgId: req.scope.orgId,
|
|
884
|
+
destinationId,
|
|
885
|
+
items,
|
|
886
|
+
workRef: `${req.runId}:${req.stepId}`,
|
|
887
|
+
mode: c.mode === "context" || c.mode === "knowledge"
|
|
888
|
+
? c.mode
|
|
889
|
+
: undefined,
|
|
890
|
+
});
|
|
891
|
+
if (receipt.status === "failed") {
|
|
892
|
+
return {
|
|
893
|
+
status: "failed",
|
|
894
|
+
retryable: false,
|
|
895
|
+
error: {
|
|
896
|
+
code: receipt.failureSummary?.code ?? "MEMORY_DELIVER_FAILED",
|
|
897
|
+
message: receipt.failureSummary?.message ??
|
|
898
|
+
`deliver to ${destinationId} failed`,
|
|
899
|
+
},
|
|
900
|
+
output: receipt,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
status: "succeeded",
|
|
905
|
+
output: receipt,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
catch (err) {
|
|
909
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
910
|
+
return {
|
|
911
|
+
status: "failed",
|
|
912
|
+
retryable: false,
|
|
913
|
+
error: {
|
|
914
|
+
code: message.includes("MEMORY_DELIVERER_UNAVAILABLE")
|
|
915
|
+
? "MEMORY_DELIVERER_UNAVAILABLE"
|
|
916
|
+
: "MEMORY_DELIVER_ERROR",
|
|
917
|
+
message,
|
|
918
|
+
},
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
}
|
|
58
922
|
//# sourceMappingURL=live.js.map
|