@modelprofile.com/mcp-crossharness 5.1.0 → 5.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.codexappserverclient.d.ts +71 -0
- package/dist_ts/classes.codexappserverclient.js +298 -0
- package/dist_ts/classes.connection.codex.d.ts +1 -9
- package/dist_ts/classes.connection.codex.js +22 -246
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/package.json +2 -2
- package/readme.md +47 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.codexappserverclient.ts +331 -0
- package/ts/classes.connection.codex.ts +22 -256
- package/ts/index.ts +1 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import * as helpers from './helpers.js';
|
|
3
3
|
import { commitinfo } from './00_commitinfo_data.js';
|
|
4
|
+
import { CodexAppServerClient } from './classes.codexappserverclient.js';
|
|
4
5
|
import type * as interfaces from './interfaces.js';
|
|
5
6
|
|
|
6
|
-
const appServerVersion = '0.146.0';
|
|
7
|
-
const maxSocketMessageBytes = 4 * 1024 * 1024;
|
|
8
|
-
const maxSocketBufferedBytes = 4 * 1024 * 1024;
|
|
9
7
|
const maxTurnTextChars = 1024 * 1024;
|
|
10
8
|
const maxTurnDeltaChunks = 4096;
|
|
11
9
|
const maxEarlyNotifications = 100;
|
|
@@ -28,14 +26,6 @@ interface ICodexNotification {
|
|
|
28
26
|
params: Record<string, unknown>;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
interface IPendingRequest {
|
|
32
|
-
resolve: (valueArg: unknown) => void;
|
|
33
|
-
reject: (errorArg: Error) => void;
|
|
34
|
-
timer: ReturnType<typeof setTimeout>;
|
|
35
|
-
signal?: AbortSignal;
|
|
36
|
-
abortHandler?: () => void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
29
|
interface IPendingTurn {
|
|
40
30
|
threadId: string;
|
|
41
31
|
turnId?: string;
|
|
@@ -70,9 +60,7 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
70
60
|
public state: interfaces.TConnectionState = 'connecting';
|
|
71
61
|
private connectedAt = new Date().toISOString();
|
|
72
62
|
private serverVersion: string | undefined;
|
|
73
|
-
private
|
|
74
|
-
private requestCounter = 0;
|
|
75
|
-
private pendingRequests = new Map<number, IPendingRequest>();
|
|
63
|
+
private client?: CodexAppServerClient;
|
|
76
64
|
private pendingTurns = new Set<IPendingTurn>();
|
|
77
65
|
private threadQueues = new Map<string, IThreadQueueEntry[]>();
|
|
78
66
|
private activeThreads = new Set<string>();
|
|
@@ -93,79 +81,20 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
93
81
|
throw new Error(`Codex connection ${this.connectionId} has already been started`);
|
|
94
82
|
}
|
|
95
83
|
this.connectStarted = true;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
followRedirects: false,
|
|
103
|
-
maxPayload: maxSocketMessageBytes,
|
|
104
|
-
});
|
|
105
|
-
this.socket = socket;
|
|
106
|
-
socket.on('message', (data) => this.handleMessage(data.toString()));
|
|
107
|
-
socket.on('error', (error) => this.handleSocketFailure(error));
|
|
108
|
-
socket.on('close', () => this.handleSocketClose());
|
|
109
|
-
await new Promise<void>((resolve, reject) => {
|
|
110
|
-
const cleanup = () => {
|
|
111
|
-
clearTimeout(timer);
|
|
112
|
-
socket.off('open', handleOpen);
|
|
113
|
-
socket.off('error', handleError);
|
|
114
|
-
socket.off('close', handleClose);
|
|
115
|
-
};
|
|
116
|
-
const handleOpen = () => {
|
|
117
|
-
cleanup();
|
|
118
|
-
resolve();
|
|
119
|
-
};
|
|
120
|
-
const handleError = (errorArg: Error) => {
|
|
121
|
-
cleanup();
|
|
122
|
-
reject(errorArg);
|
|
123
|
-
};
|
|
124
|
-
const handleClose = () => {
|
|
125
|
-
cleanup();
|
|
126
|
-
reject(new Error('Codex server WebSocket closed during handshake'));
|
|
127
|
-
};
|
|
128
|
-
const timer = setTimeout(() => {
|
|
129
|
-
cleanup();
|
|
130
|
-
socket.terminate();
|
|
131
|
-
reject(new Error('Codex server WebSocket handshake timed out'));
|
|
132
|
-
}, 5000);
|
|
133
|
-
socket.once('open', handleOpen);
|
|
134
|
-
socket.once('error', handleError);
|
|
135
|
-
socket.once('close', handleClose);
|
|
84
|
+
const client = new CodexAppServerClient({
|
|
85
|
+
transport: { type: 'websocket', url: this.serverUrl, token: process.env.CODEX_REMOTE_TOKEN },
|
|
86
|
+
clientInfo: { name: 'mcp-crossharness', title: 'mcp-crossharness', version: commitinfo.version },
|
|
87
|
+
onNotification: ({ method, params }) => this.handleNotification(method, params),
|
|
88
|
+
onServerRequest: (request) => this.respondToServerRequest(request),
|
|
89
|
+
onClose: (error) => this.handleSocketFailure(error),
|
|
136
90
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
clientInfo: {
|
|
142
|
-
name: 'mcp-crossharness',
|
|
143
|
-
title: 'mcp-crossharness',
|
|
144
|
-
version: commitinfo.version,
|
|
145
|
-
},
|
|
146
|
-
capabilities: {
|
|
147
|
-
experimentalApi: false,
|
|
148
|
-
requestAttestation: false,
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
5000,
|
|
152
|
-
true,
|
|
153
|
-
);
|
|
154
|
-
if (!helpers.isRecord(initialized) || typeof initialized.userAgent !== 'string') {
|
|
155
|
-
throw new Error(
|
|
156
|
-
`Codex server returned an invalid initialize response (expected app-server ${appServerVersion})`,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
this.serverVersion = initialized.userAgent.match(/\/(\d+\.\d+\.\d+[^\s]*)/)?.[1];
|
|
160
|
-
if (this.state !== 'connecting' || socket.readyState !== plugins.WsWebSocket.OPEN) {
|
|
161
|
-
throw new Error('Codex server connection closed during initialization');
|
|
162
|
-
}
|
|
163
|
-
this.sendNotification('initialized');
|
|
164
|
-
this.state = 'connected';
|
|
165
|
-
} catch (error) {
|
|
166
|
-
socket.terminate();
|
|
167
|
-
throw error;
|
|
91
|
+
this.client = client;
|
|
92
|
+
await client.connect();
|
|
93
|
+
if (this.state !== 'connecting' || client.state !== 'connected') {
|
|
94
|
+
throw new Error('Codex server connection closed during initialization');
|
|
168
95
|
}
|
|
96
|
+
this.serverVersion = client.serverVersion;
|
|
97
|
+
this.state = 'connected';
|
|
169
98
|
}
|
|
170
99
|
|
|
171
100
|
public getInfo(): interfaces.IConnectionInfo {
|
|
@@ -418,7 +347,7 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
418
347
|
this.onClosing?.();
|
|
419
348
|
this.onClosing = undefined;
|
|
420
349
|
}
|
|
421
|
-
|
|
350
|
+
this.client?.close(reasonArg);
|
|
422
351
|
this.finishClosed();
|
|
423
352
|
}
|
|
424
353
|
|
|
@@ -476,118 +405,13 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
476
405
|
methodArg: string,
|
|
477
406
|
paramsArg: unknown,
|
|
478
407
|
timeoutMsArg = 30000,
|
|
479
|
-
|
|
408
|
+
_allowConnectingArg = false,
|
|
480
409
|
signalArg?: AbortSignal,
|
|
481
410
|
): Promise<unknown> {
|
|
482
|
-
if (
|
|
411
|
+
if (this.state !== 'connected' || !this.client) {
|
|
483
412
|
return Promise.reject(new Error(`Codex connection ${this.connectionId} is ${this.state}`));
|
|
484
413
|
}
|
|
485
|
-
|
|
486
|
-
if (!socket || socket.readyState !== plugins.WsWebSocket.OPEN) {
|
|
487
|
-
return Promise.reject(new Error('Codex server WebSocket is not open'));
|
|
488
|
-
}
|
|
489
|
-
if (this.pendingRequests.size >= 256) {
|
|
490
|
-
return Promise.reject(new Error('Codex connection has too many active requests'));
|
|
491
|
-
}
|
|
492
|
-
if (signalArg?.aborted) return Promise.reject(this.abortError(signalArg.reason));
|
|
493
|
-
return new Promise((resolve, reject) => {
|
|
494
|
-
const id = ++this.requestCounter;
|
|
495
|
-
const timer = setTimeout(() => {
|
|
496
|
-
const pending = this.pendingRequests.get(id);
|
|
497
|
-
if (!pending) return;
|
|
498
|
-
this.pendingRequests.delete(id);
|
|
499
|
-
this.clearPendingRequestResources(pending);
|
|
500
|
-
reject(new Error(`Codex request timed out: ${methodArg}`));
|
|
501
|
-
}, timeoutMsArg);
|
|
502
|
-
const pending: IPendingRequest = { resolve, reject, timer, signal: signalArg };
|
|
503
|
-
if (signalArg) {
|
|
504
|
-
pending.abortHandler = () => {
|
|
505
|
-
if (!this.pendingRequests.delete(id)) return;
|
|
506
|
-
this.clearPendingRequestResources(pending);
|
|
507
|
-
reject(this.abortError(signalArg.reason));
|
|
508
|
-
};
|
|
509
|
-
signalArg.addEventListener('abort', pending.abortHandler, { once: true });
|
|
510
|
-
}
|
|
511
|
-
this.pendingRequests.set(id, pending);
|
|
512
|
-
const failSend = (errorArg: Error) => {
|
|
513
|
-
const current = this.pendingRequests.get(id);
|
|
514
|
-
if (!current) return;
|
|
515
|
-
this.pendingRequests.delete(id);
|
|
516
|
-
this.clearPendingRequestResources(current);
|
|
517
|
-
current.reject(errorArg);
|
|
518
|
-
};
|
|
519
|
-
try {
|
|
520
|
-
this.sendFrame({ id, method: methodArg, params: paramsArg });
|
|
521
|
-
} catch (error) {
|
|
522
|
-
failSend(error instanceof Error ? error : new Error(String(error)));
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
private sendNotification(methodArg: string, paramsArg?: unknown): void {
|
|
528
|
-
this.sendFrame(
|
|
529
|
-
paramsArg === undefined
|
|
530
|
-
? { method: methodArg }
|
|
531
|
-
: { method: methodArg, params: paramsArg },
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
private sendFrame(frameArg: Record<string, unknown>): void {
|
|
536
|
-
const socket = this.socket;
|
|
537
|
-
const payload = JSON.stringify(frameArg);
|
|
538
|
-
const payloadBytes = Buffer.byteLength(payload, 'utf8');
|
|
539
|
-
if (!socket || socket.readyState !== plugins.WsWebSocket.OPEN) {
|
|
540
|
-
const error = new Error('Codex server WebSocket is not open');
|
|
541
|
-
this.handleSocketFailure(error);
|
|
542
|
-
throw error;
|
|
543
|
-
}
|
|
544
|
-
if (socket.bufferedAmount + payloadBytes > maxSocketBufferedBytes) {
|
|
545
|
-
const error = new Error('Codex WebSocket send buffer exceeded the 4 MiB limit');
|
|
546
|
-
this.handleSocketFailure(error);
|
|
547
|
-
throw error;
|
|
548
|
-
}
|
|
549
|
-
try {
|
|
550
|
-
socket.send(payload, (error) => {
|
|
551
|
-
if (error) this.handleSocketFailure(error);
|
|
552
|
-
});
|
|
553
|
-
} catch (error) {
|
|
554
|
-
const sendError = error instanceof Error ? error : new Error(String(error));
|
|
555
|
-
this.handleSocketFailure(sendError);
|
|
556
|
-
throw sendError;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
private handleMessage(rawArg: string): void {
|
|
561
|
-
const message = helpers.parseJson(rawArg);
|
|
562
|
-
if (!helpers.isRecord(message)) return;
|
|
563
|
-
if (message.id !== undefined && typeof message.method === 'string') {
|
|
564
|
-
this.respondToServerRequest(message);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
if (typeof message.id === 'number' && this.pendingRequests.has(message.id)) {
|
|
568
|
-
const pending = this.pendingRequests.get(message.id)!;
|
|
569
|
-
this.pendingRequests.delete(message.id);
|
|
570
|
-
this.clearPendingRequestResources(pending);
|
|
571
|
-
if (message.error !== undefined) {
|
|
572
|
-
pending.reject(
|
|
573
|
-
new Error(
|
|
574
|
-
`Codex server rejected request: ${JSON.stringify(message.error).slice(0, 4000)}`,
|
|
575
|
-
),
|
|
576
|
-
);
|
|
577
|
-
} else {
|
|
578
|
-
pending.resolve(message.result);
|
|
579
|
-
}
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
if (typeof message.method === 'string') {
|
|
583
|
-
if (!helpers.isRecord(message.params)) {
|
|
584
|
-
if (message.method === 'turn/completed') {
|
|
585
|
-
this.handleSocketFailure(new Error('Codex returned malformed turn/completed params'));
|
|
586
|
-
}
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
this.handleNotification(message.method, message.params);
|
|
590
|
-
}
|
|
414
|
+
return this.client.request(methodArg, paramsArg, timeoutMsArg, signalArg);
|
|
591
415
|
}
|
|
592
416
|
|
|
593
417
|
private handleNotification(methodArg: string, paramsArg: Record<string, unknown>): void {
|
|
@@ -898,13 +722,6 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
898
722
|
}
|
|
899
723
|
}
|
|
900
724
|
|
|
901
|
-
private clearPendingRequestResources(pendingArg: IPendingRequest): void {
|
|
902
|
-
clearTimeout(pendingArg.timer);
|
|
903
|
-
if (pendingArg.abortHandler) {
|
|
904
|
-
pendingArg.signal?.removeEventListener('abort', pendingArg.abortHandler);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
725
|
private abortError(reasonArg: unknown): Error {
|
|
909
726
|
return reasonArg instanceof Error ? reasonArg : new Error('operation cancelled');
|
|
910
727
|
}
|
|
@@ -1002,7 +819,7 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
1002
819
|
return true;
|
|
1003
820
|
}
|
|
1004
821
|
|
|
1005
|
-
private respondToServerRequest(messageArg: Record<string, unknown>): void {
|
|
822
|
+
private respondToServerRequest(messageArg: { id: string | number; method: string; params: Record<string, unknown> }): void {
|
|
1006
823
|
const method = String(messageArg.method);
|
|
1007
824
|
let result: unknown;
|
|
1008
825
|
switch (method) {
|
|
@@ -1025,15 +842,12 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
1025
842
|
break;
|
|
1026
843
|
default:
|
|
1027
844
|
try {
|
|
1028
|
-
this.
|
|
1029
|
-
id: messageArg.id,
|
|
1030
|
-
error: { code: -32601, message: `Crossharness does not implement ${method}` },
|
|
1031
|
-
});
|
|
845
|
+
this.client?.rejectRequest(messageArg.id, -32601, `Crossharness does not implement ${method}`);
|
|
1032
846
|
} catch {}
|
|
1033
847
|
return;
|
|
1034
848
|
}
|
|
1035
849
|
try {
|
|
1036
|
-
this.
|
|
850
|
+
this.client?.respond(messageArg.id, result);
|
|
1037
851
|
} catch {}
|
|
1038
852
|
}
|
|
1039
853
|
|
|
@@ -1045,21 +859,7 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
1045
859
|
this.onClosing?.();
|
|
1046
860
|
this.onClosing = undefined;
|
|
1047
861
|
}
|
|
1048
|
-
|
|
1049
|
-
if (socket && socket.readyState !== plugins.WsWebSocket.CLOSED) {
|
|
1050
|
-
socket.terminate();
|
|
1051
|
-
} else {
|
|
1052
|
-
this.finishClosed();
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
private handleSocketClose(): void {
|
|
1057
|
-
if (this.state !== 'closing' && this.state !== 'closed') {
|
|
1058
|
-
this.state = 'closing';
|
|
1059
|
-
this.rejectAll(new Error('Codex server connection closed'));
|
|
1060
|
-
this.onClosing?.();
|
|
1061
|
-
this.onClosing = undefined;
|
|
1062
|
-
}
|
|
862
|
+
this.client?.close(errorArg.message);
|
|
1063
863
|
this.finishClosed();
|
|
1064
864
|
}
|
|
1065
865
|
|
|
@@ -1072,11 +872,6 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
1072
872
|
}
|
|
1073
873
|
|
|
1074
874
|
private rejectAll(errorArg: Error): void {
|
|
1075
|
-
for (const pending of this.pendingRequests.values()) {
|
|
1076
|
-
this.clearPendingRequestResources(pending);
|
|
1077
|
-
pending.reject(errorArg);
|
|
1078
|
-
}
|
|
1079
|
-
this.pendingRequests.clear();
|
|
1080
875
|
for (const turn of [...this.pendingTurns]) this.failTurn(turn, errorArg);
|
|
1081
876
|
for (const [threadId, queue] of this.threadQueues) {
|
|
1082
877
|
const activeOffset = this.activeThreads.has(threadId) ? 1 : 0;
|
|
@@ -1088,33 +883,4 @@ export class CodexConnection implements interfaces.IHarnessConnection {
|
|
|
1088
883
|
this.threadQueues.clear();
|
|
1089
884
|
}
|
|
1090
885
|
|
|
1091
|
-
private async closeSocket(): Promise<void> {
|
|
1092
|
-
const socket = this.socket;
|
|
1093
|
-
if (!socket || socket.readyState === plugins.WsWebSocket.CLOSED) return;
|
|
1094
|
-
await new Promise<void>((resolve) => {
|
|
1095
|
-
let settled = false;
|
|
1096
|
-
const finish = () => {
|
|
1097
|
-
if (settled) return;
|
|
1098
|
-
settled = true;
|
|
1099
|
-
clearTimeout(timer);
|
|
1100
|
-
socket.off('close', finish);
|
|
1101
|
-
resolve();
|
|
1102
|
-
};
|
|
1103
|
-
const timer = setTimeout(() => {
|
|
1104
|
-
socket.terminate();
|
|
1105
|
-
finish();
|
|
1106
|
-
}, 1000);
|
|
1107
|
-
socket.once('close', finish);
|
|
1108
|
-
try {
|
|
1109
|
-
if (socket.readyState === plugins.WsWebSocket.CONNECTING) {
|
|
1110
|
-
socket.terminate();
|
|
1111
|
-
} else {
|
|
1112
|
-
socket.close();
|
|
1113
|
-
}
|
|
1114
|
-
} catch {
|
|
1115
|
-
socket.terminate();
|
|
1116
|
-
finish();
|
|
1117
|
-
}
|
|
1118
|
-
});
|
|
1119
|
-
}
|
|
1120
886
|
}
|