@x12i/memorix-service 3.1.0 → 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.
Files changed (90) hide show
  1. package/dist/ai-services/associate-prep.d.ts +37 -0
  2. package/dist/ai-services/associate-prep.d.ts.map +1 -0
  3. package/dist/ai-services/associate-prep.js +208 -0
  4. package/dist/ai-services/associate-prep.js.map +1 -0
  5. package/dist/ai-services/chunk-markdown.d.ts +22 -0
  6. package/dist/ai-services/chunk-markdown.d.ts.map +1 -0
  7. package/dist/ai-services/chunk-markdown.js +159 -0
  8. package/dist/ai-services/chunk-markdown.js.map +1 -0
  9. package/dist/ai-services/client.d.ts +1 -1
  10. package/dist/ai-services/client.d.ts.map +1 -1
  11. package/dist/ai-services/client.js +2 -2
  12. package/dist/ai-services/client.js.map +1 -1
  13. package/dist/ai-services/normalize-output.d.ts +7 -1
  14. package/dist/ai-services/normalize-output.d.ts.map +1 -1
  15. package/dist/ai-services/normalize-output.js +340 -60
  16. package/dist/ai-services/normalize-output.js.map +1 -1
  17. package/dist/ai-services/skill-execution.d.ts +3 -2
  18. package/dist/ai-services/skill-execution.d.ts.map +1 -1
  19. package/dist/ai-services/skill-execution.js +24 -4
  20. package/dist/ai-services/skill-execution.js.map +1 -1
  21. package/dist/app.d.ts.map +1 -1
  22. package/dist/app.js +4 -0
  23. package/dist/app.js.map +1 -1
  24. package/dist/connector-catalog.d.ts +68 -0
  25. package/dist/connector-catalog.d.ts.map +1 -0
  26. package/dist/connector-catalog.js +258 -0
  27. package/dist/connector-catalog.js.map +1 -0
  28. package/dist/connector-role.d.ts +22 -0
  29. package/dist/connector-role.d.ts.map +1 -0
  30. package/dist/connector-role.js +71 -0
  31. package/dist/connector-role.js.map +1 -0
  32. package/dist/destination-runtime.d.ts +8 -0
  33. package/dist/destination-runtime.d.ts.map +1 -0
  34. package/dist/destination-runtime.js +11 -0
  35. package/dist/destination-runtime.js.map +1 -0
  36. package/dist/execution-target.d.ts +141 -0
  37. package/dist/execution-target.d.ts.map +1 -0
  38. package/dist/execution-target.js +853 -0
  39. package/dist/execution-target.js.map +1 -0
  40. package/dist/generic-connectors.d.ts +41 -0
  41. package/dist/generic-connectors.d.ts.map +1 -0
  42. package/dist/generic-connectors.js +158 -0
  43. package/dist/generic-connectors.js.map +1 -0
  44. package/dist/jobs-scope.d.ts +21 -0
  45. package/dist/jobs-scope.d.ts.map +1 -0
  46. package/dist/jobs-scope.js +83 -0
  47. package/dist/jobs-scope.js.map +1 -0
  48. package/dist/live.d.ts +11 -0
  49. package/dist/live.d.ts.map +1 -1
  50. package/dist/live.js +700 -73
  51. package/dist/live.js.map +1 -1
  52. package/dist/openapi/openapi.json +3445 -791
  53. package/dist/pipeline-compile.d.ts +35 -1
  54. package/dist/pipeline-compile.d.ts.map +1 -1
  55. package/dist/pipeline-compile.js +408 -87
  56. package/dist/pipeline-compile.js.map +1 -1
  57. package/dist/pipeline-invoke.d.ts +63 -0
  58. package/dist/pipeline-invoke.d.ts.map +1 -0
  59. package/dist/pipeline-invoke.js +114 -0
  60. package/dist/pipeline-invoke.js.map +1 -0
  61. package/dist/pipeline-ports.d.ts +13 -0
  62. package/dist/pipeline-ports.d.ts.map +1 -0
  63. package/dist/pipeline-ports.js +104 -0
  64. package/dist/pipeline-ports.js.map +1 -0
  65. package/dist/platform.d.ts +30 -1
  66. package/dist/platform.d.ts.map +1 -1
  67. package/dist/platform.js +228 -6
  68. package/dist/platform.js.map +1 -1
  69. package/dist/routes/connector-invocations.d.ts.map +1 -1
  70. package/dist/routes/connector-invocations.js +18 -21
  71. package/dist/routes/connector-invocations.js.map +1 -1
  72. package/dist/routes/jobs.d.ts +4 -0
  73. package/dist/routes/jobs.d.ts.map +1 -0
  74. package/dist/routes/jobs.js +888 -0
  75. package/dist/routes/jobs.js.map +1 -0
  76. package/dist/routes/memory.d.ts.map +1 -1
  77. package/dist/routes/memory.js +143 -79
  78. package/dist/routes/memory.js.map +1 -1
  79. package/dist/routes/pipelines.d.ts.map +1 -1
  80. package/dist/routes/pipelines.js +98 -22
  81. package/dist/routes/pipelines.js.map +1 -1
  82. package/dist/routes/relationships.d.ts.map +1 -1
  83. package/dist/routes/relationships.js +80 -2
  84. package/dist/routes/relationships.js.map +1 -1
  85. package/dist/runx/client.d.ts +18 -0
  86. package/dist/runx/client.d.ts.map +1 -0
  87. package/dist/runx/client.js +59 -0
  88. package/dist/runx/client.js.map +1 -0
  89. package/openapi/openapi.json +3445 -791
  90. package/package.json +10 -8
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Authoring pack pipeline → runtime v2 shape (pipelineId / stepId / stepKind / portConfig).
3
+ * Supports unified step `target` (service | ai-service | connector | pipeline | runx).
3
4
  */
4
- import { type PipelineDefinition } from "@x12i/memorix-pipeline/v2";
5
+ import { type PipelineDefinition, type StepKind } from "@x12i/memorix-pipeline/v2";
5
6
  export type ServiceDefSlice = {
6
7
  id?: string;
7
8
  serviceId?: string;
9
+ capabilities?: string[];
8
10
  memorixAccess?: unknown;
9
11
  /** AI-services/v1 binding (type + item); when set, compile stamps FR invoke config. */
10
12
  aiService?: {
@@ -22,6 +24,25 @@ export type ServiceDefSlice = {
22
24
  itemId?: string;
23
25
  };
24
26
  };
27
+ /** Authoring step target — same surface as Jobs execution targets + runx. */
28
+ export type AuthoringStepTarget = {
29
+ kind: "service";
30
+ serviceId: string;
31
+ } | {
32
+ kind: "ai-service";
33
+ aiServiceType: string;
34
+ itemId: string;
35
+ } | {
36
+ kind: "connector";
37
+ connectorId: string;
38
+ sourceId: string;
39
+ } | {
40
+ kind: "pipeline";
41
+ pipelineId: string;
42
+ } | {
43
+ kind: "runx";
44
+ capabilityId: string;
45
+ };
25
46
  export declare function resolveKnowxWorkerBaseUrl(): string;
26
47
  /**
27
48
  * Explicit test-only switch for the APS AI Services Simulator.
@@ -43,9 +64,22 @@ export declare function resolveKnowxServiceEndpoint(serviceId: string, opts?: {
43
64
  workerBaseUrl?: string;
44
65
  endpointEnv?: string;
45
66
  }): string;
67
+ /** Prefer behavioral step kind from service capabilities. */
68
+ export declare function preferredStepKindFromCapabilities(caps: string[] | undefined): StepKind;
69
+ /**
70
+ * Build a directed call graph of pipelines that invoke other pipelines via
71
+ * `target.pipeline` / `run-pipeline` steps. Detects cycles when ids are known.
72
+ */
73
+ export declare function detectPipelineCallCycles(pipelines: Array<{
74
+ id?: string;
75
+ pipelineId?: string;
76
+ steps?: Array<Record<string, unknown>>;
77
+ }>): string[];
46
78
  export declare function compileAuthoringPipeline(input: unknown, opts?: {
47
79
  knownServiceIds?: Set<string>;
48
80
  workerBaseUrl?: string;
49
81
  serviceDefs?: ServiceDefSlice[];
82
+ /** When set, validate nested pipeline targets exist. */
83
+ knownPipelineIds?: Set<string>;
50
84
  }): PipelineDefinition;
51
85
  //# sourceMappingURL=pipeline-compile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline-compile.d.ts","sourceRoot":"","sources":["../src/pipeline-compile.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAGL,KAAK,kBAAkB,EAExB,MAAM,2BAA2B,CAAC;AAmCnC,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uFAAuF;IACvF,SAAS,CAAC,EAAE;QACV,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,CAAC,EAAE;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,0FAA0F;QAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,+CAA+C;QAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF,wBAAgB,yBAAyB,IAAI,MAAM,CAMlD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAID;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,GAAG,SAAS,CAMjE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IACL,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACA,MAAM,CAiBR;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE;IACL,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;CACjC,GACA,kBAAkB,CAiJpB"}
1
+ {"version":3,"file":"pipeline-compile.d.ts","sourceRoot":"","sources":["../src/pipeline-compile.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,QAAQ,EACd,MAAM,2BAA2B,CAAC;AA+CnC,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uFAAuF;IACvF,SAAS,CAAC,EAAE;QACV,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,CAAC,EAAE;QACX,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,0FAA0F;QAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,+CAA+C;QAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3C,wBAAgB,yBAAyB,IAAI,MAAM,CAMlD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAID;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,GAAG,SAAS,CAMjE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IACL,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACA,MAAM,CAiBR;AAQD,6DAA6D;AAC7D,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,GACzB,QAAQ,CAOV;AA2ND;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACxC,CAAC,GACD,MAAM,EAAE,CAoDV;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE;IACL,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAChC,GACA,kBAAkB,CAmNpB"}
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Authoring pack pipeline → runtime v2 shape (pipelineId / stepId / stepKind / portConfig).
3
+ * Supports unified step `target` (service | ai-service | connector | pipeline | runx).
3
4
  */
4
5
  import { validatePipelineDefinition, STEP_KINDS, } from "@x12i/memorix-pipeline/v2";
5
6
  import { defaultEndpointEnvForType, resolveAiServiceOriginUrl, } from "./ai-services/config.js";
@@ -22,6 +23,10 @@ const STEP_ALIASES = {
22
23
  validate: "validate",
23
24
  "map-normalize": "map-normalize",
24
25
  "identity-dedupe": "identity-dedupe",
26
+ "run-pipeline": "run-pipeline",
27
+ pipeline: "run-pipeline",
28
+ "call-pipeline": "run-pipeline",
29
+ runx: "runx",
25
30
  };
26
31
  const MUTATING = new Set([
27
32
  "persist-raw",
@@ -29,6 +34,13 @@ const MUTATING = new Set([
29
34
  "relationship-write",
30
35
  "workflow",
31
36
  "narrative",
37
+ "run-pipeline",
38
+ ]);
39
+ const BEHAVIORAL = new Set([
40
+ "enrich",
41
+ "analyze",
42
+ "decide",
43
+ "narrative",
32
44
  ]);
33
45
  export function resolveKnowxWorkerBaseUrl() {
34
46
  return (process.env.KNOWX_WORKER_URL?.trim() ||
@@ -79,6 +91,245 @@ export function resolveKnowxServiceEndpoint(serviceId, opts) {
79
91
  }
80
92
  return workerDefault;
81
93
  }
94
+ function asRecord(value) {
95
+ return value && typeof value === "object" && !Array.isArray(value)
96
+ ? value
97
+ : null;
98
+ }
99
+ /** Prefer behavioral step kind from service capabilities. */
100
+ export function preferredStepKindFromCapabilities(caps) {
101
+ if (!caps?.length)
102
+ return "analyze";
103
+ if (caps.includes("enrich"))
104
+ return "enrich";
105
+ if (caps.includes("decide"))
106
+ return "decide";
107
+ if (caps.includes("narrative"))
108
+ return "narrative";
109
+ if (caps.includes("analyze"))
110
+ return "analyze";
111
+ return "analyze";
112
+ }
113
+ function parseStepTarget(step, rawKind) {
114
+ const target = asRecord(step.target);
115
+ if (target) {
116
+ const kind = String(target.kind ?? "").trim();
117
+ if (kind === "service") {
118
+ const serviceId = String(target.serviceId ?? "").trim();
119
+ if (serviceId)
120
+ return { kind: "service", serviceId };
121
+ }
122
+ if (kind === "ai-service" || kind === "ai") {
123
+ const aiServiceType = String(target.aiServiceType ?? "").trim();
124
+ const itemId = String(target.itemId ?? "").trim();
125
+ if (aiServiceType && itemId) {
126
+ return { kind: "ai-service", aiServiceType, itemId };
127
+ }
128
+ }
129
+ if (kind === "connector") {
130
+ const connectorId = String(target.connectorId ?? "").trim();
131
+ const sourceId = String(target.sourceId ?? "").trim();
132
+ if (connectorId && sourceId) {
133
+ return { kind: "connector", connectorId, sourceId };
134
+ }
135
+ }
136
+ if (kind === "pipeline") {
137
+ const pipelineId = String(target.pipelineId ?? "").trim();
138
+ if (pipelineId)
139
+ return { kind: "pipeline", pipelineId };
140
+ }
141
+ if (kind === "runx") {
142
+ const capabilityId = String(target.capabilityId ?? "").trim();
143
+ if (capabilityId)
144
+ return { kind: "runx", capabilityId };
145
+ }
146
+ }
147
+ // Shorthand fields on the step.
148
+ const pipelineId = String(step.pipelineId ?? "").trim();
149
+ if (pipelineId &&
150
+ (rawKind === "run-pipeline" ||
151
+ rawKind === "pipeline" ||
152
+ rawKind === "call-pipeline" ||
153
+ rawKind === "")) {
154
+ return { kind: "pipeline", pipelineId };
155
+ }
156
+ const capabilityId = String(step.capabilityId ??
157
+ asRecord(step.portConfig)?.capabilityId ??
158
+ "").trim();
159
+ if (capabilityId && (rawKind === "runx" || rawKind === "")) {
160
+ return { kind: "runx", capabilityId };
161
+ }
162
+ const sourceId = String(step.sourceId ?? "").trim();
163
+ const connectorId = String(step.connectorId ??
164
+ asRecord(step.portConfig)?.connectorId ??
165
+ "").trim();
166
+ if (sourceId && connectorId) {
167
+ return { kind: "connector", connectorId, sourceId };
168
+ }
169
+ if (sourceId && (rawKind === "memory-pull" || rawKind === "pull" || rawKind === "collect")) {
170
+ return {
171
+ kind: "connector",
172
+ connectorId: connectorId || "default",
173
+ sourceId,
174
+ };
175
+ }
176
+ const stepAi = asRecord(step.aiService);
177
+ if (stepAi) {
178
+ const aiServiceType = String(stepAi.aiServiceType ?? "").trim();
179
+ const itemId = String(stepAi.itemId ?? "").trim();
180
+ if (aiServiceType && itemId) {
181
+ return { kind: "ai-service", aiServiceType, itemId };
182
+ }
183
+ }
184
+ const serviceId = String(step.serviceId ?? "").trim();
185
+ if (serviceId) {
186
+ return { kind: "service", serviceId };
187
+ }
188
+ return null;
189
+ }
190
+ function stampAiPortConfig(step, opts) {
191
+ const endpoint = resolveAiServiceOriginUrl(opts.aiServiceType, {
192
+ endpoint: opts.endpoint || undefined,
193
+ endpointEnv: opts.endpointEnv,
194
+ }) ?? "";
195
+ if (!endpoint)
196
+ return false;
197
+ const existingPc = step.portConfig ?? {};
198
+ const existingExec = existingPc.execution && typeof existingPc.execution === "object"
199
+ ? existingPc.execution
200
+ : {};
201
+ const skillExec = opts.aiServiceType === "skill"
202
+ ? defaultSkillExecutionForItem(opts.itemId, {
203
+ stepId: String(step.stepId ?? step.id ?? ""),
204
+ })
205
+ : undefined;
206
+ step.portConfig = {
207
+ ...existingPc,
208
+ serviceId: opts.serviceId,
209
+ operation: opts.operation,
210
+ ...(opts.memorixAccess ? { memorixAccess: opts.memorixAccess } : {}),
211
+ endpoint,
212
+ aiServiceType: opts.aiServiceType,
213
+ itemId: opts.itemId,
214
+ invokeProtocol: "ai-services/1",
215
+ ...(skillExec || Object.keys(existingExec).length
216
+ ? {
217
+ execution: {
218
+ ...skillExec,
219
+ ...existingExec,
220
+ },
221
+ }
222
+ : {}),
223
+ };
224
+ return true;
225
+ }
226
+ function stampServicePortConfig(step, svc, serviceId, opts) {
227
+ const operation = (typeof svc?.invocation?.operation === "string"
228
+ ? svc.invocation.operation
229
+ : undefined) ?? serviceId;
230
+ const memorixAccess = Array.isArray(svc?.memorixAccess)
231
+ ? svc.memorixAccess
232
+ : undefined;
233
+ const stepAi = step.aiService && typeof step.aiService === "object"
234
+ ? step.aiService
235
+ : null;
236
+ const aiServiceType = String(stepAi?.aiServiceType ??
237
+ svc?.aiService?.aiServiceType ??
238
+ svc?.invocation?.aiServiceType ??
239
+ "").trim();
240
+ const itemId = String(stepAi?.itemId ?? svc?.aiService?.itemId ?? svc?.invocation?.itemId ?? "").trim();
241
+ if (aiServiceType && itemId) {
242
+ const endpointEnv = String(stepAi?.endpointEnv ??
243
+ svc?.aiService?.endpointEnv ??
244
+ svc?.invocation?.endpointEnv ??
245
+ defaultEndpointEnvForType(aiServiceType) ??
246
+ "").trim() || undefined;
247
+ const explicitEndpoint = String(stepAi?.endpoint ?? svc?.aiService?.endpoint ?? "").trim();
248
+ if (stampAiPortConfig(step, {
249
+ serviceId,
250
+ operation,
251
+ memorixAccess,
252
+ aiServiceType,
253
+ itemId,
254
+ endpointEnv,
255
+ endpoint: explicitEndpoint || undefined,
256
+ })) {
257
+ return;
258
+ }
259
+ // No companion URL — fall through to worker invocation (offline / CI).
260
+ }
261
+ const endpointEnv = typeof svc?.invocation?.endpointEnv === "string"
262
+ ? svc.invocation.endpointEnv.trim()
263
+ : serviceId === "knowx-extract"
264
+ ? "KNOWX_EXTRACT_WORKER_URL"
265
+ : undefined;
266
+ const endpoint = resolveKnowxServiceEndpoint(serviceId, {
267
+ workerBaseUrl: opts?.workerBaseUrl,
268
+ endpointEnv,
269
+ });
270
+ step.portConfig = {
271
+ ...(step.portConfig ?? {}),
272
+ serviceId,
273
+ operation,
274
+ ...(memorixAccess ? { memorixAccess } : {}),
275
+ endpoint,
276
+ };
277
+ }
278
+ /**
279
+ * Build a directed call graph of pipelines that invoke other pipelines via
280
+ * `target.pipeline` / `run-pipeline` steps. Detects cycles when ids are known.
281
+ */
282
+ export function detectPipelineCallCycles(pipelines) {
283
+ const graph = new Map();
284
+ for (const p of pipelines) {
285
+ const id = String(p.pipelineId ?? p.id ?? "").trim();
286
+ if (!id)
287
+ continue;
288
+ if (!graph.has(id))
289
+ graph.set(id, new Set());
290
+ for (const step of p.steps ?? []) {
291
+ const rawKind = String(step.stepKind ?? step.kind ?? step.type ?? "");
292
+ const target = parseStepTarget(step, rawKind);
293
+ if (target?.kind === "pipeline") {
294
+ graph.get(id).add(target.pipelineId);
295
+ }
296
+ if (rawKind === "run-pipeline" ||
297
+ rawKind === "pipeline" ||
298
+ rawKind === "call-pipeline") {
299
+ const child = String(step.pipelineId ??
300
+ asRecord(step.portConfig)?.pipelineId ??
301
+ "").trim();
302
+ if (child)
303
+ graph.get(id).add(child);
304
+ }
305
+ }
306
+ }
307
+ const issues = [];
308
+ const visiting = new Set();
309
+ const visited = new Set();
310
+ const path = [];
311
+ function dfs(node) {
312
+ if (visiting.has(node)) {
313
+ const cycleStart = path.indexOf(node);
314
+ const cycle = [...path.slice(cycleStart), node].join(" → ");
315
+ issues.push(`pipeline call cycle: ${cycle}`);
316
+ return;
317
+ }
318
+ if (visited.has(node))
319
+ return;
320
+ visiting.add(node);
321
+ path.push(node);
322
+ for (const next of graph.get(node) ?? []) {
323
+ dfs(next);
324
+ }
325
+ path.pop();
326
+ visiting.delete(node);
327
+ visited.add(node);
328
+ }
329
+ for (const id of graph.keys())
330
+ dfs(id);
331
+ return issues;
332
+ }
82
333
  export function compileAuthoringPipeline(input, opts) {
83
334
  const raw = structuredClone(input);
84
335
  if (typeof raw.pipelineId !== "string" || !raw.pipelineId.trim()) {
@@ -96,107 +347,177 @@ export function compileAuthoringPipeline(input, opts) {
96
347
  }
97
348
  const issues = [];
98
349
  for (const step of raw.steps ?? []) {
99
- const rawKind = String(step.stepKind ?? step.kind ?? step.type ?? "");
350
+ const stepLabel = String(step.stepId ?? step.id ?? "step");
351
+ if (typeof step.stepId !== "string" && typeof step.id === "string") {
352
+ step.stepId = step.id;
353
+ }
354
+ let rawKind = String(step.stepKind ?? step.kind ?? step.type ?? "").trim();
355
+ const target = parseStepTarget(step, rawKind);
356
+ // invoke-service alias: only when target/service can be resolved.
100
357
  if (rawKind === "invoke-service") {
101
- issues.push(`step ${String(step.stepId ?? step.id)} uses unsupported invoke-service`);
102
- continue;
358
+ if (!target) {
359
+ issues.push(`step ${stepLabel} uses unsupported invoke-service without target/serviceId`);
360
+ continue;
361
+ }
362
+ rawKind = "";
103
363
  }
104
- const canonical = STEP_ALIASES[rawKind] ??
105
- (STEP_KINDS.includes(rawKind) ? rawKind : null);
106
- if (!canonical) {
107
- issues.push(`step ${String(step.stepId ?? step.id)} has unknown stepKind "${rawKind}"`);
108
- continue;
364
+ // Target-driven kind selection.
365
+ if (target?.kind === "pipeline") {
366
+ step.stepKind = "run-pipeline";
367
+ step.portConfig = {
368
+ ...(step.portConfig ?? {}),
369
+ pipelineId: target.pipelineId,
370
+ wait: true,
371
+ };
372
+ if (opts?.knownPipelineIds && !opts.knownPipelineIds.has(target.pipelineId)) {
373
+ issues.push(`step ${stepLabel} references unknown pipeline "${target.pipelineId}"`);
374
+ }
109
375
  }
110
- step.stepKind = canonical;
111
- if (typeof step.stepId !== "string" && typeof step.id === "string") {
112
- step.stepId = step.id;
376
+ else if (target?.kind === "runx") {
377
+ step.stepKind = "runx";
378
+ step.portConfig = {
379
+ ...(step.portConfig ?? {}),
380
+ capabilityId: target.capabilityId,
381
+ invokeProtocol: "runx/1",
382
+ };
113
383
  }
114
- if (MUTATING.has(canonical) && typeof step.idempotencyKeyExpr !== "string") {
115
- step.idempotencyKeyExpr = `{{runId}}:${String(step.stepId ?? "step")}`;
384
+ else if (target?.kind === "connector") {
385
+ step.stepKind = "memory-pull";
386
+ if (typeof step.sourceId !== "string")
387
+ step.sourceId = target.sourceId;
388
+ step.portConfig = {
389
+ ...(step.portConfig ?? {}),
390
+ sourceId: target.sourceId,
391
+ connectorId: target.connectorId,
392
+ mode: asRecord(step.portConfig)?.mode ?? "knowledge",
393
+ trigger: asRecord(step.portConfig)?.trigger ??
394
+ "on-demand",
395
+ };
116
396
  }
117
- const serviceId = typeof step.serviceId === "string" ? step.serviceId.trim() : "";
118
- if (serviceId) {
119
- const svc = serviceById.get(serviceId);
120
- const operation = (typeof svc?.invocation?.operation === "string"
121
- ? svc.invocation.operation
122
- : undefined) ?? serviceId;
123
- const memorixAccess = Array.isArray(svc?.memorixAccess)
124
- ? svc.memorixAccess
125
- : undefined;
126
- const stepAi = step.aiService && typeof step.aiService === "object"
127
- ? step.aiService
128
- : null;
129
- const aiServiceType = String(stepAi?.aiServiceType ??
130
- svc?.aiService?.aiServiceType ??
131
- svc?.invocation?.aiServiceType ??
132
- "").trim();
133
- const itemId = String(stepAi?.itemId ?? svc?.aiService?.itemId ?? svc?.invocation?.itemId ?? "").trim();
134
- if (aiServiceType && itemId) {
135
- const endpointEnv = String(stepAi?.endpointEnv ??
136
- svc?.aiService?.endpointEnv ??
137
- svc?.invocation?.endpointEnv ??
138
- defaultEndpointEnvForType(aiServiceType) ??
139
- "").trim() || undefined;
140
- const explicitEndpoint = String(stepAi?.endpoint ?? svc?.aiService?.endpoint ?? "").trim();
141
- const endpoint = resolveAiServiceOriginUrl(aiServiceType, {
142
- endpoint: explicitEndpoint || undefined,
143
- endpointEnv,
144
- }) ?? "";
145
- if (endpoint) {
146
- const existingPc = step.portConfig ?? {};
147
- const existingExec = existingPc.execution && typeof existingPc.execution === "object"
148
- ? existingPc.execution
149
- : {};
150
- const skillExec = aiServiceType === "skill"
151
- ? defaultSkillExecutionForItem(itemId, {
152
- stepId: String(step.stepId ?? step.id ?? ""),
153
- })
154
- : undefined;
397
+ else if (target?.kind === "ai-service") {
398
+ step.stepKind = "analyze";
399
+ const serviceId = String(step.serviceId ?? "").trim() ||
400
+ `ai:${target.aiServiceType}:${target.itemId}`;
401
+ step.serviceId = serviceId;
402
+ const stamped = stampAiPortConfig(step, {
403
+ serviceId,
404
+ operation: serviceId,
405
+ aiServiceType: target.aiServiceType,
406
+ itemId: target.itemId,
407
+ endpointEnv: defaultEndpointEnvForType(target.aiServiceType),
408
+ });
409
+ if (!stamped) {
410
+ step.portConfig = {
411
+ ...(step.portConfig ?? {}),
412
+ serviceId,
413
+ aiServiceType: target.aiServiceType,
414
+ itemId: target.itemId,
415
+ invokeProtocol: "ai-services/1",
416
+ };
417
+ }
418
+ }
419
+ else if (target?.kind === "service") {
420
+ const svc = serviceById.get(target.serviceId);
421
+ const preferred = preferredStepKindFromCapabilities(svc?.capabilities);
422
+ let canonical = null;
423
+ if (rawKind && STEP_ALIASES[rawKind]) {
424
+ canonical = STEP_ALIASES[rawKind];
425
+ }
426
+ else if (rawKind && STEP_KINDS.includes(rawKind)) {
427
+ canonical = rawKind;
428
+ }
429
+ else if (!rawKind || rawKind === "invoke-service") {
430
+ canonical = preferred;
431
+ }
432
+ else {
433
+ // Unknown kind with serviceId → prefer capabilities.
434
+ canonical = preferred;
435
+ }
436
+ // If author picked a non-behavioral kind with a service, keep it only
437
+ // when it's an existing non-target kind; otherwise use preferred.
438
+ if (canonical && !BEHAVIORAL.has(canonical) && rawKind && STEP_ALIASES[rawKind]) {
439
+ // e.g. write-record + serviceId is invalid remote; still allow pass-through kinds.
440
+ }
441
+ else if (!canonical || (rawKind === "" && preferred)) {
442
+ canonical = preferred;
443
+ }
444
+ if (!BEHAVIORAL.has(canonical) && target.kind === "service" && (!rawKind || rawKind === "invoke-service")) {
445
+ canonical = preferred;
446
+ }
447
+ step.stepKind = canonical ?? preferred;
448
+ step.serviceId = target.serviceId;
449
+ stampServicePortConfig(step, svc, target.serviceId, {
450
+ workerBaseUrl: opts?.workerBaseUrl,
451
+ });
452
+ }
453
+ else {
454
+ // Legacy: stepKind only
455
+ const canonical = STEP_ALIASES[rawKind] ??
456
+ (STEP_KINDS.includes(rawKind) ? rawKind : null);
457
+ if (!canonical) {
458
+ issues.push(`step ${stepLabel} has unknown stepKind "${rawKind}"`);
459
+ continue;
460
+ }
461
+ step.stepKind = canonical;
462
+ // memory-pull with sourceId only
463
+ const sourceId = String(step.sourceId ?? "").trim();
464
+ if (canonical === "memory-pull" && sourceId) {
465
+ step.portConfig = {
466
+ ...(step.portConfig ?? {}),
467
+ sourceId,
468
+ mode: asRecord(step.portConfig)?.mode ?? "knowledge",
469
+ trigger: asRecord(step.portConfig)?.trigger ??
470
+ "on-demand",
471
+ };
472
+ }
473
+ // run-pipeline with pipelineId on step
474
+ if (canonical === "run-pipeline") {
475
+ const childId = String(step.pipelineId ??
476
+ asRecord(step.portConfig)?.pipelineId ??
477
+ "").trim();
478
+ if (childId) {
155
479
  step.portConfig = {
156
- ...existingPc,
157
- serviceId,
158
- operation,
159
- ...(memorixAccess ? { memorixAccess } : {}),
160
- endpoint,
161
- aiServiceType,
162
- itemId,
163
- invokeProtocol: "ai-services/1",
164
- ...(skillExec || Object.keys(existingExec).length
165
- ? {
166
- execution: {
167
- ...skillExec,
168
- ...existingExec,
169
- },
170
- }
171
- : {}),
480
+ ...(step.portConfig ?? {}),
481
+ pipelineId: childId,
482
+ wait: true,
172
483
  };
173
- continue;
174
484
  }
175
- // No companion URL — fall through to worker invocation (offline / CI).
176
- }
177
- const endpointEnv = typeof svc?.invocation?.endpointEnv === "string"
178
- ? svc.invocation.endpointEnv.trim()
179
- : serviceId === "knowx-extract"
180
- ? "KNOWX_EXTRACT_WORKER_URL"
181
- : undefined;
182
- const endpoint = resolveKnowxServiceEndpoint(serviceId, {
183
- workerBaseUrl: opts?.workerBaseUrl,
184
- endpointEnv,
185
- });
186
- step.portConfig = {
187
- ...(step.portConfig ?? {}),
188
- serviceId,
189
- operation,
190
- ...(memorixAccess ? { memorixAccess } : {}),
191
- endpoint,
192
- };
485
+ }
486
+ // runx with capabilityId on step
487
+ if (canonical === "runx") {
488
+ const capId = String(step.capabilityId ??
489
+ asRecord(step.portConfig)?.capabilityId ??
490
+ "").trim();
491
+ if (capId) {
492
+ step.portConfig = {
493
+ ...(step.portConfig ?? {}),
494
+ capabilityId: capId,
495
+ invokeProtocol: "runx/1",
496
+ };
497
+ }
498
+ }
499
+ const serviceId = typeof step.serviceId === "string" ? step.serviceId.trim() : "";
500
+ if (serviceId) {
501
+ stampServicePortConfig(step, serviceById.get(serviceId), serviceId, { workerBaseUrl: opts?.workerBaseUrl });
502
+ }
503
+ }
504
+ const finalKind = step.stepKind;
505
+ if (MUTATING.has(finalKind) && typeof step.idempotencyKeyExpr !== "string") {
506
+ step.idempotencyKeyExpr = `{{runId}}:${String(step.stepId ?? "step")}`;
193
507
  }
194
508
  }
195
509
  if (issues.length) {
196
510
  throw new Error(`compilePipeline failed: ${issues.join("; ")}`);
197
511
  }
512
+ // Allow synthetic AI ids and runx through service validation.
513
+ const known = new Set(opts?.knownServiceIds ?? []);
514
+ for (const step of raw.steps ?? []) {
515
+ const sid = String(step.serviceId ?? "").trim();
516
+ if (sid)
517
+ known.add(sid);
518
+ }
198
519
  validatePipelineDefinition(raw, {
199
- knownServiceIds: opts?.knownServiceIds,
520
+ knownServiceIds: known,
200
521
  });
201
522
  return raw;
202
523
  }