@bpmnkit/proxy 0.0.11 → 0.0.12
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 +5 -1
- package/dist/index.js +155 -1
- package/dist/prompt.js +31 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://
|
|
2
|
+
<a href="https://bpmnkit.com"><img src="https://bpmnkit.com/favicon.svg" width="72" height="72" alt="BPMN Kit logo"></a>
|
|
3
3
|
<h1>@bpmnkit/proxy</h1>
|
|
4
4
|
<p>Local proxy server for BPMN Kit — AI bridge (SSE/MCP) and Camunda API proxy using stored CLI profiles</p>
|
|
5
5
|
|
|
@@ -87,3 +87,7 @@ curl -H "X-Profile: production" http://localhost:3033/api/v2/process-definitions
|
|
|
87
87
|
## License
|
|
88
88
|
|
|
89
89
|
[MIT](https://github.com/bpmnkit/monorepo/blob/main/LICENSE) © BPMN Kit — made by [u11g](https://u11g.com)
|
|
90
|
+
|
|
91
|
+
<div align="center">
|
|
92
|
+
<a href="https://bpmnkit.com"><img src="https://bpmnkit.com/favicon.svg" width="32" height="32" alt="BPMN Kit"></a>
|
|
93
|
+
</div>
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { getActiveName, getActiveProfile, getAuthHeader, getProfile, listProfile
|
|
|
10
10
|
import * as claude from "./adapters/claude.js";
|
|
11
11
|
import * as copilot from "./adapters/copilot.js";
|
|
12
12
|
import * as gemini from "./adapters/gemini.js";
|
|
13
|
-
import { buildIncidentSystemPrompt, buildIncidentUserMessage, buildMcpExplainPrompt, buildMcpImprovePrompt, buildMcpSystemPrompt, buildSystemPrompt, } from "./prompt.js";
|
|
13
|
+
import { buildIncidentSystemPrompt, buildIncidentUserMessage, buildMcpExplainPrompt, buildMcpImprovePrompt, buildMcpSystemPrompt, buildSearchSystemPrompt, buildSystemPrompt, } from "./prompt.js";
|
|
14
14
|
const PORT = process.env.AI_SERVER_PORT ? Number(process.env.AI_SERVER_PORT) : 3033;
|
|
15
15
|
// Resolve the compiled mcp-server entry point relative to this file.
|
|
16
16
|
// When bundled as bundle.cjs, import.meta.url ends with .cjs → use mcp-server.cjs.
|
|
@@ -531,6 +531,160 @@ const server = http.createServer(async (req, res) => {
|
|
|
531
531
|
res.end();
|
|
532
532
|
return;
|
|
533
533
|
}
|
|
534
|
+
// ── POST /operate/ai-search ────────────────────────────────────────────────
|
|
535
|
+
// Translates a plain-text query to a Camunda API filter, executes the search,
|
|
536
|
+
// and returns results as JSON. AI is only called when the quick-parser cannot
|
|
537
|
+
// resolve the query deterministically (saves tokens for simple queries).
|
|
538
|
+
if (url.pathname === "/operate/ai-search" && req.method === "POST") {
|
|
539
|
+
const body = await readBody(req);
|
|
540
|
+
let query;
|
|
541
|
+
try {
|
|
542
|
+
const parsed = JSON.parse(body);
|
|
543
|
+
query = parsed.query?.trim() ?? "";
|
|
544
|
+
if (!query)
|
|
545
|
+
throw new Error("empty");
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
549
|
+
res.end(JSON.stringify({ error: "{ query: string } required" }));
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const profileName = req.headers["x-profile"];
|
|
553
|
+
const profile = profileName ? getProfile(profileName) : getActiveProfile();
|
|
554
|
+
if (!profile?.config.baseUrl) {
|
|
555
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
556
|
+
res.end(JSON.stringify({ error: "No active profile" }));
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
let authHeader;
|
|
560
|
+
try {
|
|
561
|
+
authHeader = await getAuthHeader(profile.config);
|
|
562
|
+
}
|
|
563
|
+
catch (err) {
|
|
564
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
565
|
+
res.end(JSON.stringify({ error: `Auth failed: ${String(err)}` }));
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const baseUrl = profile.config.baseUrl.replace(/\/$/, "");
|
|
569
|
+
const apiHeaders = {
|
|
570
|
+
authorization: authHeader,
|
|
571
|
+
"content-type": "application/json",
|
|
572
|
+
accept: "application/json",
|
|
573
|
+
};
|
|
574
|
+
function tryQuickParse(q) {
|
|
575
|
+
const trimmed = q.trim();
|
|
576
|
+
// Pure numeric string → instance key lookup
|
|
577
|
+
if (/^\d+$/.test(trimmed)) {
|
|
578
|
+
return { endpoint: "instances", filter: { processInstanceKey: trimmed } };
|
|
579
|
+
}
|
|
580
|
+
// Single state keyword
|
|
581
|
+
const stateMap = {
|
|
582
|
+
active: "ACTIVE",
|
|
583
|
+
completed: "COMPLETED",
|
|
584
|
+
terminated: "TERMINATED",
|
|
585
|
+
};
|
|
586
|
+
const lower = trimmed.toLowerCase();
|
|
587
|
+
if (stateMap[lower]) {
|
|
588
|
+
return { endpoint: "instances", filter: { state: stateMap[lower] } };
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
function extractSearchSpec(text) {
|
|
593
|
+
// Try raw JSON first, then a ```json block, then any {...} substring
|
|
594
|
+
const candidates = [
|
|
595
|
+
text.trim(),
|
|
596
|
+
(/```(?:json)?\s*\n?([\s\S]*?)\n?```/.exec(text) ?? [])[1] ?? "",
|
|
597
|
+
(/(\{[\s\S]*\})/.exec(text) ?? [])[1] ?? "",
|
|
598
|
+
];
|
|
599
|
+
for (const candidate of candidates) {
|
|
600
|
+
if (!candidate)
|
|
601
|
+
continue;
|
|
602
|
+
try {
|
|
603
|
+
const parsed = JSON.parse(candidate);
|
|
604
|
+
if (typeof parsed === "object" &&
|
|
605
|
+
parsed !== null &&
|
|
606
|
+
"endpoint" in parsed &&
|
|
607
|
+
"filter" in parsed) {
|
|
608
|
+
return parsed;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
/* try next */
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
let spec = tryQuickParse(query);
|
|
618
|
+
console.log(`[server] /operate/ai-search → query: "${query}", quick-parse: ${spec ? "hit" : "miss"}`);
|
|
619
|
+
// Step 2: AI translation — only when quick-parse has no answer
|
|
620
|
+
if (!spec) {
|
|
621
|
+
const available = await detectAll();
|
|
622
|
+
const detected = available[0];
|
|
623
|
+
if (!detected) {
|
|
624
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
625
|
+
res.end(JSON.stringify({ error: "No AI adapter available" }));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
console.log(`[server] /operate/ai-search → adapter: ${detected.name}`);
|
|
629
|
+
const tokens = [];
|
|
630
|
+
try {
|
|
631
|
+
await detected.adapter.stream([{ role: "user", content: query }], buildSearchSystemPrompt(), null, (t) => tokens.push(t));
|
|
632
|
+
}
|
|
633
|
+
catch (err) {
|
|
634
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
635
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
spec = extractSearchSpec(tokens.join(""));
|
|
639
|
+
if (!spec) {
|
|
640
|
+
res.writeHead(422, { "Content-Type": "application/json" });
|
|
641
|
+
res.end(JSON.stringify({ error: "Could not interpret search query" }));
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
// Step 3: Execute search against Camunda
|
|
646
|
+
const finalSpec = spec;
|
|
647
|
+
let items = [];
|
|
648
|
+
let total = 0;
|
|
649
|
+
try {
|
|
650
|
+
if (finalSpec.endpoint === "variables") {
|
|
651
|
+
const r = await fetch(`${baseUrl}/variables/search`, {
|
|
652
|
+
method: "POST",
|
|
653
|
+
headers: apiHeaders,
|
|
654
|
+
body: JSON.stringify({ filter: finalSpec.filter, page: { limit: 100 } }),
|
|
655
|
+
});
|
|
656
|
+
if (r.ok) {
|
|
657
|
+
const result = (await r.json());
|
|
658
|
+
items = result.items ?? [];
|
|
659
|
+
total = result.page?.totalItems ?? items.length;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
const r = await fetch(`${baseUrl}/process-instances/search`, {
|
|
664
|
+
method: "POST",
|
|
665
|
+
headers: apiHeaders,
|
|
666
|
+
body: JSON.stringify({
|
|
667
|
+
filter: finalSpec.filter,
|
|
668
|
+
page: { limit: 100 },
|
|
669
|
+
sort: [{ field: "startDate", order: "DESC" }],
|
|
670
|
+
}),
|
|
671
|
+
});
|
|
672
|
+
if (r.ok) {
|
|
673
|
+
const result = (await r.json());
|
|
674
|
+
items = result.items ?? [];
|
|
675
|
+
total = result.page?.totalItems ?? items.length;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
catch (err) {
|
|
680
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
681
|
+
res.end(JSON.stringify({ error: `Search failed: ${String(err)}` }));
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
685
|
+
res.end(JSON.stringify({ endpoint: finalSpec.endpoint, filter: finalSpec.filter, items, total }));
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
534
688
|
// ── ALL /api/* — transparent Camunda API proxy ─────────────────────────────
|
|
535
689
|
if (url.pathname.startsWith("/api/")) {
|
|
536
690
|
const profileName = req.headers["x-profile"];
|
package/dist/prompt.js
CHANGED
|
@@ -171,6 +171,37 @@ export function buildIncidentUserMessage(incident, variables, processXml) {
|
|
|
171
171
|
}
|
|
172
172
|
return lines.join("\n");
|
|
173
173
|
}
|
|
174
|
+
// ── Operate AI search prompt ───────────────────────────────────────────────────
|
|
175
|
+
/**
|
|
176
|
+
* Minimal system prompt for the AI search endpoint.
|
|
177
|
+
* Instructs the model to output ONLY a JSON object (no prose) to keep token usage low.
|
|
178
|
+
*/
|
|
179
|
+
export function buildSearchSystemPrompt() {
|
|
180
|
+
return [
|
|
181
|
+
"You are a Camunda 8 search assistant.",
|
|
182
|
+
"Convert the user query into a JSON search request. Output ONLY a valid JSON object — no explanation, no markdown, no extra text.",
|
|
183
|
+
"",
|
|
184
|
+
'Schema: { "endpoint": "instances" | "variables", "filter": { ... } }',
|
|
185
|
+
"",
|
|
186
|
+
'Instance filter fields (endpoint "instances"):',
|
|
187
|
+
' state: "ACTIVE" | "COMPLETED" | "TERMINATED"',
|
|
188
|
+
" processDefinitionKey: string (numeric ID)",
|
|
189
|
+
" processDefinitionId: string (BPMN process ID, substring)",
|
|
190
|
+
" hasIncident: boolean",
|
|
191
|
+
" processInstanceKey: string (numeric key)",
|
|
192
|
+
" parentProcessInstanceKey: string",
|
|
193
|
+
"",
|
|
194
|
+
'Variable filter fields (endpoint "variables"):',
|
|
195
|
+
" name: string (exact variable name)",
|
|
196
|
+
' value: any (JSON-serialized: "hello", 42, true)',
|
|
197
|
+
" processInstanceKey: string",
|
|
198
|
+
" isTruncated: boolean",
|
|
199
|
+
" tenantId: string",
|
|
200
|
+
"",
|
|
201
|
+
'Use "instances" for process instance queries. Use "variables" for variable/value queries.',
|
|
202
|
+
"Omit filter fields that are not relevant. Output ONLY the JSON object.",
|
|
203
|
+
].join("\n");
|
|
204
|
+
}
|
|
174
205
|
// ── Fallback prompt builders (for non-MCP adapters like Gemini) ───────────────
|
|
175
206
|
/** Full system prompt for non-MCP adapters that must return a CompactDiagram JSON block. */
|
|
176
207
|
export function buildSystemPrompt(context) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bpmnkit/proxy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "Local proxy server for BPMN Kit — AI bridge (SSE/MCP) and Camunda API proxy using stored CLI profiles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"node": ">=20"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@bpmnkit/api": "0.0.
|
|
20
|
-
"@bpmnkit/core": "0.0.
|
|
21
|
-
"@bpmnkit/profiles": "0.0.
|
|
19
|
+
"@bpmnkit/api": "0.0.12",
|
|
20
|
+
"@bpmnkit/core": "0.0.12",
|
|
21
|
+
"@bpmnkit/profiles": "0.0.9"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|