@maintainer-pro/ai-server 0.1.7 → 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 +122 -48
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,20 +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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
+
);
|
|
624
692
|
publishChatResult(conversationId, result, assistantMessageId, userMessageId);
|
|
625
693
|
if (!result.ok) {
|
|
626
694
|
logger.warn({ status: result.status }, "ai turn failed");
|
|
@@ -799,18 +867,21 @@ export async function startAiServer(options = {}) {
|
|
|
799
867
|
conversationId,
|
|
800
868
|
userMessageId,
|
|
801
869
|
});
|
|
802
|
-
const result = await runChatPost(
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
+
);
|
|
814
885
|
publishChatResult(conversationId, result, assistantMessageId, userMessageId);
|
|
815
886
|
if (!result.ok) {
|
|
816
887
|
throw new Error(result.data?.error ?? `chat failed (${result.status})`);
|
|
@@ -893,13 +964,13 @@ export async function startAiServer(options = {}) {
|
|
|
893
964
|
(m) =>
|
|
894
965
|
m.role === "user" &&
|
|
895
966
|
m.queueStatus === "working" &&
|
|
896
|
-
m.
|
|
967
|
+
m.provider !== "developer"
|
|
897
968
|
);
|
|
898
969
|
const hasQueued = msgs.some(
|
|
899
970
|
(m) =>
|
|
900
971
|
m.role === "user" &&
|
|
901
972
|
m.queueStatus === "queued" &&
|
|
902
|
-
m.
|
|
973
|
+
m.provider !== "developer"
|
|
903
974
|
);
|
|
904
975
|
if (!hasWorking && !hasQueued) continue;
|
|
905
976
|
const result = await db.recoverQueue(conv.id);
|
|
@@ -969,7 +1040,10 @@ export async function startAiServer(options = {}) {
|
|
|
969
1040
|
/\/$/,
|
|
970
1041
|
""
|
|
971
1042
|
) || `http://localhost:${port}`;
|
|
972
|
-
logger.info(
|
|
1043
|
+
logger.info(
|
|
1044
|
+
{ versions: runtimePackageVersions() },
|
|
1045
|
+
`chat → ${base}/api/chat`
|
|
1046
|
+
);
|
|
973
1047
|
logger.info(`ws → ${toHttpWsUrl(base)}/api/ws`);
|
|
974
1048
|
logger.info(`ui → ${base}/`);
|
|
975
1049
|
logger.info(`embed-config → ${base}/embed-config.js`);
|