@maintainer-pro/ai-server 0.1.6 → 0.1.8
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/package.json +3 -3
- package/src/server.mjs +127 -42
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maintainer-pro/ai-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Node HTTP server for Maintainer Pro chat. Uses @maintainer-pro/ai-cli to talk to coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"maintainer-pro",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@maintainer-pro/ai-cli": "^0.1.
|
|
34
|
-
"@maintainer-pro/ai-ui": "^0.2.
|
|
33
|
+
"@maintainer-pro/ai-cli": "^0.1.14",
|
|
34
|
+
"@maintainer-pro/ai-ui": "^0.2.8",
|
|
35
35
|
"ws": "^8.21.3"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
package/src/server.mjs
CHANGED
|
@@ -93,6 +93,35 @@ function tryResolveIife(from) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
function readJsonVersion(file) {
|
|
97
|
+
try {
|
|
98
|
+
const pkg = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
99
|
+
return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readDepPackageVersion(name) {
|
|
106
|
+
try {
|
|
107
|
+
return readJsonVersion(createRequire(import.meta.url).resolve(`${name}/package.json`));
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const PACKAGE_VERSION =
|
|
114
|
+
readJsonVersion(path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json")) ||
|
|
115
|
+
"0.0.0";
|
|
116
|
+
|
|
117
|
+
export function runtimePackageVersions() {
|
|
118
|
+
return {
|
|
119
|
+
server: PACKAGE_VERSION,
|
|
120
|
+
cli: readDepPackageVersion("@maintainer-pro/ai-cli"),
|
|
121
|
+
ui: readDepPackageVersion("@maintainer-pro/ai-ui"),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
96
125
|
export function findIife() {
|
|
97
126
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
98
127
|
const cwdPkg = path.join(process.cwd(), "package.json");
|
|
@@ -367,11 +396,18 @@ export async function startAiServer(options = {}) {
|
|
|
367
396
|
return {
|
|
368
397
|
status: 200,
|
|
369
398
|
headers: outHeaders,
|
|
370
|
-
body: Buffer.from(
|
|
399
|
+
body: Buffer.from(
|
|
400
|
+
JSON.stringify({
|
|
401
|
+
ok: true,
|
|
402
|
+
workspace: workspaceDir,
|
|
403
|
+
versions: runtimePackageVersions(),
|
|
404
|
+
})
|
|
405
|
+
),
|
|
371
406
|
};
|
|
372
407
|
}
|
|
373
408
|
if (pathname === "/embed-config.js") {
|
|
374
409
|
const aiServerUrl = resolvePublicUrl({ headers });
|
|
410
|
+
const versions = runtimePackageVersions();
|
|
375
411
|
const payload = {
|
|
376
412
|
aiServerUrl,
|
|
377
413
|
apiUrl: `${aiServerUrl}/api/chat`,
|
|
@@ -382,6 +418,7 @@ export async function startAiServer(options = {}) {
|
|
|
382
418
|
maintainerProApiKey:
|
|
383
419
|
pickEnv(env, "MAINTAINER_PRO_CLIENT_API_KEY") ||
|
|
384
420
|
pickEnv(env, "NEXT_PUBLIC_MAINTAINER_PRO_CLIENT_API_KEY"),
|
|
421
|
+
versions,
|
|
385
422
|
};
|
|
386
423
|
logger.debug({ aiServerUrl }, "embed-config");
|
|
387
424
|
outHeaders["content-type"] = "text/javascript; charset=utf-8";
|
|
@@ -484,7 +521,23 @@ export async function startAiServer(options = {}) {
|
|
|
484
521
|
return messages;
|
|
485
522
|
}
|
|
486
523
|
|
|
487
|
-
async function runChatPost(body) {
|
|
524
|
+
async function runChatPost(body, opts = {}) {
|
|
525
|
+
const userMessageId =
|
|
526
|
+
typeof body.userMessageId === "string" ? body.userMessageId : "";
|
|
527
|
+
const conversationId =
|
|
528
|
+
typeof body.conversationId === "string" ? body.conversationId : "";
|
|
529
|
+
let claimedHere = false;
|
|
530
|
+
if (userMessageId && !opts.alreadyClaimed) {
|
|
531
|
+
if (runningTurns.has(userMessageId)) {
|
|
532
|
+
logger.debug({ userMessageId }, "runChatPost duplicate");
|
|
533
|
+
return { ok: true, status: 200, data: { duplicate: true } };
|
|
534
|
+
}
|
|
535
|
+
runningTurns.add(userMessageId);
|
|
536
|
+
if (conversationId) {
|
|
537
|
+
runningByConversation.set(conversationId, userMessageId);
|
|
538
|
+
}
|
|
539
|
+
claimedHere = true;
|
|
540
|
+
}
|
|
488
541
|
logger.debug(
|
|
489
542
|
{
|
|
490
543
|
conversationId: body.conversationId,
|
|
@@ -498,25 +551,37 @@ export async function startAiServer(options = {}) {
|
|
|
498
551
|
},
|
|
499
552
|
"runChatPost"
|
|
500
553
|
);
|
|
501
|
-
const response = await handlers.POST(
|
|
502
|
-
new Request("http://127.0.0.1/api/chat", {
|
|
503
|
-
method: "POST",
|
|
504
|
-
headers: { "content-type": "application/json" },
|
|
505
|
-
body: JSON.stringify(body),
|
|
506
|
-
})
|
|
507
|
-
);
|
|
508
|
-
const text = await response.text();
|
|
509
|
-
let data = {};
|
|
510
554
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
555
|
+
const response = await handlers.POST(
|
|
556
|
+
new Request("http://127.0.0.1/api/chat", {
|
|
557
|
+
method: "POST",
|
|
558
|
+
headers: { "content-type": "application/json" },
|
|
559
|
+
body: JSON.stringify(body),
|
|
560
|
+
})
|
|
561
|
+
);
|
|
562
|
+
const text = await response.text();
|
|
563
|
+
let data = {};
|
|
564
|
+
try {
|
|
565
|
+
data = text ? JSON.parse(text) : {};
|
|
566
|
+
} catch {
|
|
567
|
+
data = { error: text || "invalid chat response" };
|
|
568
|
+
}
|
|
569
|
+
logger.debug(
|
|
570
|
+
{ conversationId: body.conversationId, ok: response.ok, status: response.status },
|
|
571
|
+
"runChatPost done"
|
|
572
|
+
);
|
|
573
|
+
return { ok: response.ok, status: response.status, data };
|
|
574
|
+
} finally {
|
|
575
|
+
if (claimedHere && userMessageId) {
|
|
576
|
+
runningTurns.delete(userMessageId);
|
|
577
|
+
if (
|
|
578
|
+
conversationId &&
|
|
579
|
+
runningByConversation.get(conversationId) === userMessageId
|
|
580
|
+
) {
|
|
581
|
+
runningByConversation.delete(conversationId);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
514
584
|
}
|
|
515
|
-
logger.debug(
|
|
516
|
-
{ conversationId: body.conversationId, ok: response.ok, status: response.status },
|
|
517
|
-
"runChatPost done"
|
|
518
|
-
);
|
|
519
|
-
return { ok: response.ok, status: response.status, data };
|
|
520
585
|
}
|
|
521
586
|
|
|
522
587
|
function publishChatResult(
|
|
@@ -607,16 +672,23 @@ export async function startAiServer(options = {}) {
|
|
|
607
672
|
userMessageId,
|
|
608
673
|
});
|
|
609
674
|
const assistantMessageId = randomUUID();
|
|
610
|
-
const result = await runChatPost(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
675
|
+
const result = await runChatPost(
|
|
676
|
+
{
|
|
677
|
+
conversationId,
|
|
678
|
+
messages,
|
|
679
|
+
userMessage: content,
|
|
680
|
+
skipPersistUser: true,
|
|
681
|
+
userMessageId,
|
|
682
|
+
assistantMessageId,
|
|
683
|
+
senderType: event.message.senderType === "client" ? "client" : undefined,
|
|
684
|
+
senderName: event.message.senderName ?? undefined,
|
|
685
|
+
context:
|
|
686
|
+
event.message.context && typeof event.message.context === "object"
|
|
687
|
+
? event.message.context
|
|
688
|
+
: undefined,
|
|
689
|
+
},
|
|
690
|
+
{ alreadyClaimed: true }
|
|
691
|
+
);
|
|
620
692
|
publishChatResult(conversationId, result, assistantMessageId, userMessageId);
|
|
621
693
|
if (!result.ok) {
|
|
622
694
|
logger.warn({ status: result.status }, "ai turn failed");
|
|
@@ -716,6 +788,7 @@ export async function startAiServer(options = {}) {
|
|
|
716
788
|
hasMessages: Array.isArray(msg.messages),
|
|
717
789
|
messageCount: Array.isArray(msg.messages) ? msg.messages.length : 0,
|
|
718
790
|
skipPersistUser: msg.skipPersistUser,
|
|
791
|
+
hasContext: Boolean(msg.context && typeof msg.context === "object"),
|
|
719
792
|
},
|
|
720
793
|
"ws chat.run"
|
|
721
794
|
);
|
|
@@ -736,6 +809,10 @@ export async function startAiServer(options = {}) {
|
|
|
736
809
|
typeof msg.senderType === "string" ? msg.senderType : "client",
|
|
737
810
|
senderName:
|
|
738
811
|
typeof msg.senderName === "string" ? msg.senderName : null,
|
|
812
|
+
context:
|
|
813
|
+
msg.context && typeof msg.context === "object"
|
|
814
|
+
? msg.context
|
|
815
|
+
: undefined,
|
|
739
816
|
},
|
|
740
817
|
});
|
|
741
818
|
logger.info(
|
|
@@ -790,16 +867,21 @@ export async function startAiServer(options = {}) {
|
|
|
790
867
|
conversationId,
|
|
791
868
|
userMessageId,
|
|
792
869
|
});
|
|
793
|
-
const result = await runChatPost(
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
870
|
+
const result = await runChatPost(
|
|
871
|
+
{
|
|
872
|
+
conversationId,
|
|
873
|
+
messages,
|
|
874
|
+
userMessage,
|
|
875
|
+
skipPersistUser: msg.skipPersistUser !== false,
|
|
876
|
+
userMessageId,
|
|
877
|
+
assistantMessageId,
|
|
878
|
+
senderType: typeof msg.senderType === "string" ? msg.senderType : undefined,
|
|
879
|
+
senderName: typeof msg.senderName === "string" ? msg.senderName : undefined,
|
|
880
|
+
context:
|
|
881
|
+
msg.context && typeof msg.context === "object" ? msg.context : undefined,
|
|
882
|
+
},
|
|
883
|
+
{ alreadyClaimed: true }
|
|
884
|
+
);
|
|
803
885
|
publishChatResult(conversationId, result, assistantMessageId, userMessageId);
|
|
804
886
|
if (!result.ok) {
|
|
805
887
|
throw new Error(result.data?.error ?? `chat failed (${result.status})`);
|
|
@@ -882,13 +964,13 @@ export async function startAiServer(options = {}) {
|
|
|
882
964
|
(m) =>
|
|
883
965
|
m.role === "user" &&
|
|
884
966
|
m.queueStatus === "working" &&
|
|
885
|
-
m.
|
|
967
|
+
m.provider !== "developer"
|
|
886
968
|
);
|
|
887
969
|
const hasQueued = msgs.some(
|
|
888
970
|
(m) =>
|
|
889
971
|
m.role === "user" &&
|
|
890
972
|
m.queueStatus === "queued" &&
|
|
891
|
-
m.
|
|
973
|
+
m.provider !== "developer"
|
|
892
974
|
);
|
|
893
975
|
if (!hasWorking && !hasQueued) continue;
|
|
894
976
|
const result = await db.recoverQueue(conv.id);
|
|
@@ -958,7 +1040,10 @@ export async function startAiServer(options = {}) {
|
|
|
958
1040
|
/\/$/,
|
|
959
1041
|
""
|
|
960
1042
|
) || `http://localhost:${port}`;
|
|
961
|
-
logger.info(
|
|
1043
|
+
logger.info(
|
|
1044
|
+
{ versions: runtimePackageVersions() },
|
|
1045
|
+
`chat → ${base}/api/chat`
|
|
1046
|
+
);
|
|
962
1047
|
logger.info(`ws → ${toHttpWsUrl(base)}/api/ws`);
|
|
963
1048
|
logger.info(`ui → ${base}/`);
|
|
964
1049
|
logger.info(`embed-config → ${base}/embed-config.js`);
|