-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathCodexAppServerClient.ts
More file actions
139 lines (118 loc) · 5.16 KB
/
Copy pathCodexAppServerClient.ts
File metadata and controls
139 lines (118 loc) · 5.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import type {MessageConnection} from "vscode-jsonrpc/node";
import type {
ClientRequest,
InitializeParams,
InitializeResponse,
ServerNotification, SetDefaultModelParams, SetDefaultModelResponse
} from "./app-server";
import type {
AccountLoginCompletedNotification, AccountUpdatedNotification,
GetAccountParams,
GetAccountResponse, LoginAccountParams, LoginAccountResponse, LogoutAccountResponse, ModelListParams,
ModelListResponse,
ThreadStartParams,
ThreadStartResponse,
TurnCompletedNotification,
TurnStartParams,
TurnStartResponse
} from "./app-server/v2";
/**
* A type-safe client over the Codex App Server's JSON-RPC API.
* Maps each request to its expected response and exposes clear, typed methods for supported JSON-RPC operations.
*/
export class CodexAppServerClient {
readonly connection: MessageConnection;
constructor(connection: MessageConnection) {
this.connection = connection;
this.connection.onUnhandledNotification((data) => {
const serverNotification = data as ServerNotification ?? null;
if (serverNotification) {
this.notify(serverNotification);
}
for (const callback of this.codexEventHandlers) {
callback({ eventType: "notification", ...serverNotification});
}
});
}
async initialize(params: InitializeParams): Promise<InitializeResponse> {
return await this.sendRequest({ method: "initialize", params: params });
}
async turnStart(params: TurnStartParams): Promise<TurnStartResponse> {
return await this.sendRequest({ method: "turn/start", params: params });
}
async threadStart(params: ThreadStartParams): Promise<ThreadStartResponse> {
return await this.sendRequest({ method: "thread/start", params: params });
}
async accountLogin(params: LoginAccountParams): Promise<LoginAccountResponse> {
return await this.sendRequest({ method: "account/login/start", params: params });
}
async accountLogout(): Promise<LogoutAccountResponse> {
return await this.sendRequest({ method: "account/logout", params: undefined });
}
async awaitLoginCompleted(): Promise<AccountLoginCompletedNotification> {
return await new Promise((resolve) => {
this.connection.onNotification("account/login/completed", (event: AccountLoginCompletedNotification) => {
resolve(event);
});
});
}
async awaitAccountUpdated(): Promise<AccountUpdatedNotification> {
return await new Promise((resolve) => {
this.connection.onNotification("account/updated", (event: AccountUpdatedNotification) => {
resolve(event);
});
});
}
async accountRead(params: GetAccountParams): Promise<GetAccountResponse> {
return await this.sendRequest({ method: "account/read", params: params });
}
//TODO create type-safe helper
async awaitTurnCompleted(): Promise<TurnCompletedNotification> {
return await new Promise((resolve) => {
this.connection.onNotification("turn/completed", (event: TurnCompletedNotification) => {
resolve(event);
});
});
}
async setModelRequest(params: SetDefaultModelParams): Promise<SetDefaultModelResponse> {
return await this.sendRequest({ method: "setDefaultModel", params });
}
async listModels(params: ModelListParams = {cursor: null, limit: null}): Promise<ModelListResponse> {
return await this.sendRequest({ method: "model/list", params });
}
//TODO support removal (leads to duplicated processing of follow-ups)
onServerNotification(callback: (event: ServerNotification) => void){
this.notificationHandlers.push(callback);
}
private codexEventHandlers: Array<(event: CodexConnectionEvent) => void> = [];
onClientTransportEvent(callback: (event: CodexConnectionEvent) => void){
this.codexEventHandlers.push(callback);
}
private notificationHandlers: Array<(event: ServerNotification) => void> = [];
private notify(notification: ServerNotification) {
for (const notificationHandler of this.notificationHandlers) {
notificationHandler(notification);
}
}
private async sendRequest<R>(request: CodexRequest): Promise<R> {
for (const callback of this.codexEventHandlers) {
callback({ eventType: "request", ...request});
}
let result: any;
if (request.params) {
result = await this.connection.sendRequest<R>(request.method, request.params)
}
else {
await this.connection.sendRequest<R>(request.method);
}
for (const callback of this.codexEventHandlers) {
callback({ eventType: "response", ...result});
}
return result;
}
}
export type CodexConnectionEvent = { eventType: "request" } & CodexRequest | { eventType: "response" } & unknown | { eventType: "notification" } & ServerNotification;
type CodexRequest = DistributiveOmit<ClientRequest, "id">
type DistributiveOmit<T, K extends keyof any> = T extends any
? Omit<T, K>
: never;