@jskit-ai/assistant-core 0.1.164 → 0.1.166

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.
Files changed (63) hide show
  1. package/examples/conversation/App.vue +104 -0
  2. package/examples/conversation/README.md +42 -0
  3. package/examples/conversation/dist/assets/index-CSTXm6q3.css +1 -0
  4. package/examples/conversation/dist/assets/index-DGgOLzoo.js +30 -0
  5. package/examples/conversation/dist/index.html +4 -0
  6. package/examples/conversation/index.html +2 -0
  7. package/examples/conversation/main.js +9 -0
  8. package/examples/conversation/node_modules/.vite/deps/@mdi_js.js +7450 -0
  9. package/examples/conversation/node_modules/.vite/deps/@mdi_js.js.map +1 -0
  10. package/examples/conversation/node_modules/.vite/deps/VTooltip-jIxO6ULs.js +2311 -0
  11. package/examples/conversation/node_modules/.vite/deps/VTooltip-jIxO6ULs.js.map +1 -0
  12. package/examples/conversation/node_modules/.vite/deps/_metadata.json +70 -0
  13. package/examples/conversation/node_modules/.vite/deps/defineComponent-DK6LSjDW.js +710 -0
  14. package/examples/conversation/node_modules/.vite/deps/defineComponent-DK6LSjDW.js.map +1 -0
  15. package/examples/conversation/node_modules/.vite/deps/hotkey-parsing-DnoZxtbW.js +1214 -0
  16. package/examples/conversation/node_modules/.vite/deps/hotkey-parsing-DnoZxtbW.js.map +1 -0
  17. package/examples/conversation/node_modules/.vite/deps/icons-ze2hmg7j.js +98 -0
  18. package/examples/conversation/node_modules/.vite/deps/icons-ze2hmg7j.js.map +1 -0
  19. package/examples/conversation/node_modules/.vite/deps/package.json +3 -0
  20. package/examples/conversation/node_modules/.vite/deps/theme-D78LVcJg.js +1301 -0
  21. package/examples/conversation/node_modules/.vite/deps/theme-D78LVcJg.js.map +1 -0
  22. package/examples/conversation/node_modules/.vite/deps/vue.js +2 -0
  23. package/examples/conversation/node_modules/.vite/deps/vue.runtime.esm-bundler-YcakWsDn.js +8538 -0
  24. package/examples/conversation/node_modules/.vite/deps/vue.runtime.esm-bundler-YcakWsDn.js.map +1 -0
  25. package/examples/conversation/node_modules/.vite/deps/vuetify.js +413 -0
  26. package/examples/conversation/node_modules/.vite/deps/vuetify.js.map +1 -0
  27. package/examples/conversation/node_modules/.vite/deps/vuetify_components.js +25370 -0
  28. package/examples/conversation/node_modules/.vite/deps/vuetify_components.js.map +1 -0
  29. package/examples/conversation/node_modules/.vite/deps/vuetify_directives.js +132 -0
  30. package/examples/conversation/node_modules/.vite/deps/vuetify_directives.js.map +1 -0
  31. package/examples/conversation/node_modules/.vite/deps/vuetify_iconsets_mdi-svg.js +70 -0
  32. package/examples/conversation/node_modules/.vite/deps/vuetify_iconsets_mdi-svg.js.map +1 -0
  33. package/examples/conversation/package.json +22 -0
  34. package/examples/conversation/server.js +123 -0
  35. package/examples/conversation/vite.config.js +8 -0
  36. package/package.json +59 -7
  37. package/src/client/conversation/AssistantComposerActions.vue +43 -0
  38. package/src/client/conversation/AssistantConversationElement.vue +78 -0
  39. package/src/client/conversation/AssistantProgress.vue +88 -0
  40. package/src/client/conversation/AssistantPromptInput.vue +467 -0
  41. package/src/client/conversation/AssistantTranscript.vue +1361 -0
  42. package/src/client/conversation/LongTextInlineParts.vue +64 -0
  43. package/src/client/conversation/LongTextPreviewBlocks.vue +324 -0
  44. package/src/client/conversation/index.js +9 -0
  45. package/src/client/conversation/submitText.js +29 -0
  46. package/src/client/conversation/useScrollToBottom.js +151 -0
  47. package/src/server/conversation/codexClient.js +328 -0
  48. package/src/server/conversation/codexEvents.js +623 -0
  49. package/src/server/conversation/codexTurn.js +280 -0
  50. package/src/server/conversation/index.js +2 -0
  51. package/src/server/conversation/memoryStorage.js +76 -0
  52. package/src/server/conversation/normalize.js +7 -0
  53. package/src/server/conversation/openCodeClient.js +512 -0
  54. package/src/server/conversation/transcript.js +92 -0
  55. package/src/shared/conversation/answerChoiceSugar.js +191 -0
  56. package/src/shared/conversation/index.js +6 -0
  57. package/src/shared/conversation/longTextBlocks.js +403 -0
  58. package/src/shared/conversation/numberedQuestionSugar.js +295 -0
  59. package/src/shared/conversation/scrollFollowState.js +32 -0
  60. package/src/shared/conversation/thinkingText.js +13 -0
  61. package/src/shared/conversation/turns.js +30 -0
  62. package/src/testing/conversationStorageContract.js +35 -0
  63. package/test/conversation.test.js +57 -0
@@ -0,0 +1,328 @@
1
+ import { createConnection } from "node:net";
2
+ import WebSocket from "ws";
3
+ import { normalizeText as normalizeAgentText } from "./normalize.js";
4
+ const CODEX_APP_SERVER_REQUEST_TIMEOUT_MS = 60_000;
5
+ function normalizePositiveInteger(value, fallback) {
6
+ const number = Number(value);
7
+ return Number.isSafeInteger(number) && number > 0 ? number : fallback;
8
+ }
9
+
10
+ function socketPathFromCodexAppServerEndpoint(endpoint = "") {
11
+ const normalizedEndpoint = normalizeAgentText(endpoint);
12
+ if (!normalizedEndpoint.startsWith("unix://")) {
13
+ return "";
14
+ }
15
+ return normalizedEndpoint.slice("unix://".length);
16
+ }
17
+
18
+ function codexAppServerRequestAbortedError(method = "") {
19
+ const error = new Error(`Codex app-server request was cancelled: ${normalizeAgentText(method)}`);
20
+ error.name = "AbortError";
21
+ error.code = "ABORT_ERR";
22
+ error.method = normalizeAgentText(method);
23
+ return error;
24
+ }
25
+
26
+ function addSocketListener(socket, eventName, handler) {
27
+ if (typeof socket.addEventListener === "function") {
28
+ socket.addEventListener(eventName, handler);
29
+ return () => socket.removeEventListener?.(eventName, handler);
30
+ }
31
+ if (typeof socket.on === "function") {
32
+ socket.on(eventName, handler);
33
+ return () => socket.off?.(eventName, handler) || socket.removeListener?.(eventName, handler);
34
+ }
35
+ throw new Error("Unsupported WebSocket implementation.");
36
+ }
37
+
38
+ function socketMessageText(event, maxBytes = Number.POSITIVE_INFINITY) {
39
+ const data = event?.data ?? event;
40
+ if (typeof data === "string") {
41
+ return Buffer.byteLength(data, "utf8") <= maxBytes ? data : null;
42
+ }
43
+ if (data instanceof Buffer) {
44
+ return data.byteLength <= maxBytes ? data.toString("utf8") : null;
45
+ }
46
+ const text = String(data || "");
47
+ return Buffer.byteLength(text, "utf8") <= maxBytes ? text : null;
48
+ }
49
+
50
+ class CodexAppServerJsonRpcClient {
51
+ constructor({
52
+ endpoint = "",
53
+ maxMessageBytes = Number.POSITIVE_INFINITY,
54
+ requestTimeoutMs = CODEX_APP_SERVER_REQUEST_TIMEOUT_MS,
55
+ WebSocketImpl = WebSocket
56
+ } = {}) {
57
+ this.endpoint = normalizeAgentText(endpoint);
58
+ this.maxMessageBytes = Number.isSafeInteger(maxMessageBytes) && maxMessageBytes > 0
59
+ ? maxMessageBytes
60
+ : Number.POSITIVE_INFINITY;
61
+ this.requestTimeoutMs = normalizePositiveInteger(requestTimeoutMs, CODEX_APP_SERVER_REQUEST_TIMEOUT_MS);
62
+ this.WebSocketImpl = WebSocketImpl;
63
+ this.nextRequestId = 1;
64
+ this.notificationSubscribers = new Set();
65
+ this.pendingRequests = new Map();
66
+ this.requestHandler = null;
67
+ this.connected = false;
68
+ this.socket = null;
69
+ }
70
+
71
+ isOpen() {
72
+ return Boolean(this.socket && (this.connected || this.socket.readyState === 1));
73
+ }
74
+
75
+ async connect() {
76
+ if (!this.endpoint) {
77
+ throw new Error("Codex app-server endpoint is required.");
78
+ }
79
+ if (typeof this.WebSocketImpl !== "function") {
80
+ throw new Error("A WebSocket implementation is required for Codex app-server.");
81
+ }
82
+ if (this.isOpen()) {
83
+ return this;
84
+ }
85
+ this.close();
86
+ const unixSocketPath = socketPathFromCodexAppServerEndpoint(this.endpoint);
87
+ const socketOptions = unixSocketPath
88
+ ? {
89
+ createConnection: () => createConnection(unixSocketPath),
90
+ ...(Number.isFinite(this.maxMessageBytes) ? { maxPayload: this.maxMessageBytes } : {}),
91
+ perMessageDeflate: false
92
+ }
93
+ : {
94
+ ...(Number.isFinite(this.maxMessageBytes) ? { maxPayload: this.maxMessageBytes } : {}),
95
+ perMessageDeflate: false
96
+ };
97
+ const socket = new this.WebSocketImpl(unixSocketPath ? "ws://localhost/" : this.endpoint, socketOptions);
98
+ this.socket = socket;
99
+ await new Promise((resolve, reject) => {
100
+ const cleanup = [];
101
+ const settle = (callback, value) => {
102
+ for (const dispose of cleanup) {
103
+ dispose?.();
104
+ }
105
+ callback(value);
106
+ };
107
+ cleanup.push(addSocketListener(socket, "open", () => {
108
+ if (this.socket === socket) {
109
+ this.connected = true;
110
+ }
111
+ settle(resolve);
112
+ }));
113
+ cleanup.push(addSocketListener(socket, "error", (error) => {
114
+ if (this.socket === socket) {
115
+ this.connected = false;
116
+ this.socket = null;
117
+ }
118
+ settle(reject, error?.error || error);
119
+ }));
120
+ });
121
+ addSocketListener(socket, "message", (event) => this.handleMessage(event));
122
+ addSocketListener(socket, "close", () => {
123
+ if (this.socket === socket) {
124
+ this.connected = false;
125
+ this.socket = null;
126
+ }
127
+ this.rejectPendingRequests(new Error("Codex app-server connection closed."));
128
+ });
129
+ return this;
130
+ }
131
+
132
+ async initialize({
133
+ capabilities = {
134
+ experimentalApi: true,
135
+ requestAttestation: false
136
+ },
137
+ clientInfo = {
138
+ name: "jskit-assistant",
139
+ title: "Assistant",
140
+ version: "0.1.0"
141
+ }
142
+ } = {}) {
143
+ const result = await this.request("initialize", {
144
+ capabilities,
145
+ clientInfo
146
+ });
147
+ this.notify("initialized");
148
+ return result;
149
+ }
150
+
151
+ subscribe(callback) {
152
+ if (typeof callback !== "function") {
153
+ return () => null;
154
+ }
155
+ this.notificationSubscribers.add(callback);
156
+ return () => {
157
+ this.notificationSubscribers.delete(callback);
158
+ };
159
+ }
160
+
161
+ setRequestHandler(callback) {
162
+ this.requestHandler = typeof callback === "function" ? callback : null;
163
+ }
164
+
165
+ notify(method, params) {
166
+ this.send({
167
+ method,
168
+ ...(params === undefined ? {} : { params })
169
+ });
170
+ }
171
+
172
+ request(method, params = {}, {
173
+ signal = null
174
+ } = {}) {
175
+ const id = this.nextRequestId;
176
+ this.nextRequestId += 1;
177
+ return new Promise((resolve, reject) => {
178
+ if (signal?.aborted === true) {
179
+ reject(codexAppServerRequestAbortedError(method));
180
+ return;
181
+ }
182
+ const removeAbortListener = () => {
183
+ signal?.removeEventListener?.("abort", abort);
184
+ };
185
+ const abort = () => {
186
+ const pending = this.pendingRequests.get(id);
187
+ if (!pending) {
188
+ return;
189
+ }
190
+ clearTimeout(pending.timeout);
191
+ removeAbortListener();
192
+ this.pendingRequests.delete(id);
193
+ reject(codexAppServerRequestAbortedError(method));
194
+ };
195
+ const timeout = setTimeout(() => {
196
+ removeAbortListener();
197
+ this.pendingRequests.delete(id);
198
+ reject(new Error(`Codex app-server request timed out: ${method}`));
199
+ }, this.requestTimeoutMs);
200
+ this.pendingRequests.set(id, {
201
+ method,
202
+ removeAbortListener,
203
+ reject,
204
+ resolve,
205
+ timeout
206
+ });
207
+ signal?.addEventListener?.("abort", abort, { once: true });
208
+ try {
209
+ this.send({
210
+ id,
211
+ method,
212
+ params
213
+ });
214
+ } catch (error) {
215
+ clearTimeout(timeout);
216
+ removeAbortListener();
217
+ this.pendingRequests.delete(id);
218
+ reject(error);
219
+ }
220
+ });
221
+ }
222
+
223
+ send(payload) {
224
+ if (!this.isOpen() || typeof this.socket.send !== "function") {
225
+ throw new Error("Codex app-server connection is not open.");
226
+ }
227
+ const serialized = JSON.stringify(payload);
228
+ if (Buffer.byteLength(serialized, "utf8") > this.maxMessageBytes) {
229
+ const error = new Error("Codex app-server message exceeded the configured transport limit.");
230
+ error.code = "assistant_codex_app_server_message_too_large";
231
+ throw error;
232
+ }
233
+ this.socket.send(serialized);
234
+ }
235
+
236
+ handleMessage(event) {
237
+ let message = null;
238
+ try {
239
+ const text = socketMessageText(event, this.maxMessageBytes);
240
+ if (text === null) {
241
+ const error = new Error("Codex app-server message exceeded the configured transport limit.");
242
+ error.code = "assistant_codex_app_server_message_too_large";
243
+ this.rejectPendingRequests(error);
244
+ this.close();
245
+ return;
246
+ }
247
+ message = JSON.parse(text);
248
+ } catch {
249
+ return;
250
+ }
251
+ if (Object.hasOwn(message, "id") && message.method) {
252
+ void this.handleServerRequest(message);
253
+ return;
254
+ }
255
+ if (Object.hasOwn(message, "id")) {
256
+ const pending = this.pendingRequests.get(message.id);
257
+ if (!pending) {
258
+ return;
259
+ }
260
+ clearTimeout(pending.timeout);
261
+ pending.removeAbortListener?.();
262
+ this.pendingRequests.delete(message.id);
263
+ if (message.error) {
264
+ const error = new Error(message.error.message || `Codex app-server request failed: ${pending.method}`);
265
+ error.code = message.error.code;
266
+ error.data = message.error.data;
267
+ error.method = pending.method;
268
+ pending.reject(error);
269
+ return;
270
+ }
271
+ pending.resolve(message.result);
272
+ return;
273
+ }
274
+ for (const subscriber of this.notificationSubscribers) {
275
+ subscriber(message);
276
+ }
277
+ }
278
+
279
+ async handleServerRequest(message = {}) {
280
+ try {
281
+ if (!this.requestHandler) {
282
+ const error = new Error(`Codex app-server client does not handle server request: ${message.method || "(missing method)"}`);
283
+ error.code = -32601;
284
+ throw error;
285
+ }
286
+ const result = await this.requestHandler({
287
+ id: message.id,
288
+ method: message.method,
289
+ params: message.params
290
+ });
291
+ this.send({
292
+ id: message.id,
293
+ result
294
+ });
295
+ } catch (error) {
296
+ try {
297
+ this.send({
298
+ error: {
299
+ code: Number.isSafeInteger(error?.code) ? error.code : -32000,
300
+ message: normalizeAgentText(error?.message) || "Codex app-server client request failed."
301
+ },
302
+ id: message.id
303
+ });
304
+ } catch {
305
+ // The app-server connection closed before the response could be delivered.
306
+ }
307
+ }
308
+ }
309
+
310
+ rejectPendingRequests(error) {
311
+ for (const pending of this.pendingRequests.values()) {
312
+ clearTimeout(pending.timeout);
313
+ pending.removeAbortListener?.();
314
+ pending.reject(error);
315
+ }
316
+ this.pendingRequests.clear();
317
+ }
318
+
319
+ close() {
320
+ this.rejectPendingRequests(new Error("Codex app-server connection closed."));
321
+ const socket = this.socket;
322
+ this.connected = false;
323
+ this.socket = null;
324
+ socket?.close?.();
325
+ }
326
+ }
327
+
328
+ export { CodexAppServerJsonRpcClient, socketPathFromCodexAppServerEndpoint };