@jskit-ai/assistant-core 0.1.240 → 0.1.241

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 (101) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +5 -5
  3. package/src/client/conversation/AssistantTranscript.vue +26 -29
  4. package/src/client/conversation/LongTextPreviewBlocks.vue +11 -0
  5. package/src/client/conversation/messageDelivery.js +8 -2
  6. package/src/client/conversation/useAssistantAttachments.js +1 -1
  7. package/src/server/conversation/agentRun.js +12 -198
  8. package/src/server/conversation/attachments.js +6 -5
  9. package/src/server/conversation/boundedToolLoop.js +160 -0
  10. package/src/server/conversation/claudeHistory.js +20 -16
  11. package/src/server/conversation/claudeProcess.js +2 -2
  12. package/src/server/conversation/claudeTurn.js +41 -38
  13. package/src/server/conversation/codexAuthPreflight.js +104 -0
  14. package/src/server/conversation/codexClient.js +11 -16
  15. package/src/server/conversation/codexConfiguration.js +112 -98
  16. package/src/server/conversation/codexConversationStore.js +189 -0
  17. package/src/server/conversation/codexConversations.js +47 -44
  18. package/src/server/conversation/codexDetachedTurn.js +12 -11
  19. package/src/server/conversation/codexEvents.js +95 -99
  20. package/src/server/conversation/codexHelperExecution.js +3 -13
  21. package/src/server/conversation/codexHelperThreadLedger.js +7 -13
  22. package/src/server/conversation/codexHelperThreadRecovery.js +2 -16
  23. package/src/server/conversation/codexHelperThreads.js +5 -6
  24. package/src/server/conversation/codexHistoryAdapter.js +32 -26
  25. package/src/server/conversation/codexMessageCommands.js +22 -22
  26. package/src/server/conversation/codexMessageDelivery.js +12 -7
  27. package/src/server/conversation/codexModelCatalog.js +97 -0
  28. package/src/server/conversation/codexNotificationObserver.js +24 -40
  29. package/src/server/conversation/codexNotificationQueue.js +2 -3
  30. package/src/server/conversation/codexOutput.js +20 -27
  31. package/src/server/conversation/codexProcess.js +123 -127
  32. package/src/server/conversation/codexProcessLeader.js +3 -3
  33. package/src/server/conversation/codexProvider.js +31 -614
  34. package/src/server/conversation/codexProviderOwner.js +2 -2
  35. package/src/server/conversation/codexProviderSelection.js +38 -40
  36. package/src/server/conversation/codexRenewalCommands.js +13 -10
  37. package/src/server/conversation/codexRenewalThreads.js +412 -0
  38. package/src/server/conversation/codexRunJournal.js +24 -42
  39. package/src/server/conversation/codexRuntimeLifecycle.js +19 -15
  40. package/src/server/conversation/codexSessionCleanup.js +11 -10
  41. package/src/server/conversation/codexSessionReconciliation.js +6 -6
  42. package/src/server/conversation/codexThreadReadiness.js +19 -33
  43. package/src/server/conversation/codexThreadStatus.js +3 -3
  44. package/src/server/conversation/codexTurn.js +124 -297
  45. package/src/server/conversation/codexTurnRecovery.js +49 -28
  46. package/src/server/conversation/codexTurnState.js +34 -13
  47. package/src/server/conversation/commandWrapper.js +3 -0
  48. package/src/server/conversation/deliveryTiming.js +14 -5
  49. package/src/server/conversation/index.js +1 -0
  50. package/src/server/conversation/localExecution.js +13 -17
  51. package/src/server/conversation/normalize.js +8 -3
  52. package/src/server/conversation/openCodeClient.js +9 -10
  53. package/src/server/conversation/openCodeProcess.js +9 -5
  54. package/src/server/conversation/openCodeRuntime.js +10 -11
  55. package/src/server/conversation/openCodeTurn.js +43 -29
  56. package/src/server/conversation/providers/claudeDriver.js +8 -16
  57. package/src/server/conversation/providers/codexDriver.js +25 -41
  58. package/src/server/conversation/providers/opencodeDriver.js +17 -42
  59. package/src/server/conversation/providers/scoped.js +48 -4
  60. package/src/server/conversation/runtime.js +50 -49
  61. package/src/server/conversation/runtimeStateUpgrade.js +34 -22
  62. package/src/server/conversation/transcript.js +10 -10
  63. package/src/server/hosts/codexConfiguration.js +1 -0
  64. package/src/server/hosts/codexProvider.js +8 -6
  65. package/src/server/index.js +2 -1
  66. package/src/server/lib/assistantToolLoop.js +1 -153
  67. package/src/server/lib/serviceToolCatalog.js +17 -42
  68. package/src/shared/assistantResource.js +4 -2
  69. package/src/shared/conversation/attachments.js +4 -0
  70. package/src/shared/conversation/longTextBlocks.js +54 -19
  71. package/src/shared/conversation/numberedQuestionSugar.js +17 -10
  72. package/src/shared/conversation/pagination.js +4 -6
  73. package/src/shared/conversation/patches.js +1 -4
  74. package/src/shared/conversation/streaming.js +3 -11
  75. package/src/shared/conversation/turns.js +11 -0
  76. package/src/shared/index.js +1 -0
  77. package/test/assistantBoundedToolLoop.test.js +2 -2
  78. package/test/assistantResource.test.js +4 -4
  79. package/test/assistantSettingsResource.test.js +2 -2
  80. package/test/claudeConversationSession.test.js +11 -1
  81. package/test/claudeTurn.test.js +27 -7
  82. package/test/codexClient.test.js +20 -0
  83. package/test/codexConversationSession.test.js +2 -2
  84. package/test/codexConversationsCheckpoint.test.js +51 -0
  85. package/test/codexModelCatalog.test.js +1 -1
  86. package/test/codexProviderHome.test.js +51 -0
  87. package/test/codexProviderSelection.test.js +71 -0
  88. package/test/codexTurn.test.js +222 -6
  89. package/test/conversationAgentRun.test.js +2 -1
  90. package/test/conversationAttachments.test.js +27 -2
  91. package/test/conversationDeliveryTiming.test.js +21 -0
  92. package/test/conversationNormalize.test.js +11 -0
  93. package/test/fixtures/nestedListQuestionMessage.md +37 -0
  94. package/test/frameworkActions.test.js +2 -2
  95. package/test/longTextBlocks.test.js +102 -0
  96. package/test/messageDelivery.test.js +7 -1
  97. package/test/openCodeClient.test.js +20 -0
  98. package/test/openCodeRuntime.test.js +1 -1
  99. package/test/openCodeTurn.test.js +90 -0
  100. package/test/opencodeConversationSession.test.js +1 -1
  101. package/test/serviceToolCatalog.test.js +27 -0
@@ -1,7 +1,5 @@
1
- import {
2
- isPlainObject,
3
- normalizeText
4
- } from "./normalize.js";
1
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
2
+ import { normalizeNullableText } from "./normalize.js";
5
3
 
6
4
  const CODEX_TOKEN_USAGE_METHOD = "thread/tokenUsage/updated";
7
5
 
@@ -14,7 +12,7 @@ function nonNegativeInteger(value) {
14
12
  }
15
13
 
16
14
  function codexContextUsageFromNotification(notification = {}) {
17
- if (normalizeText(notification?.method) !== CODEX_TOKEN_USAGE_METHOD) {
15
+ if (normalizeNullableText(notification?.method) !== CODEX_TOKEN_USAGE_METHOD) {
18
16
  return null;
19
17
  }
20
18
  const params = notification?.params && typeof notification.params === "object"
@@ -33,8 +31,8 @@ function codexContextUsageFromNotification(notification = {}) {
33
31
  const inputTokens = nonNegativeInteger(last.inputTokens);
34
32
  const cumulativeTokens = nonNegativeInteger(total.totalTokens);
35
33
  const windowTokens = nonNegativeInteger(tokenUsage.modelContextWindow);
36
- const threadId = normalizeText(params.threadId);
37
- const turnId = normalizeText(params.turnId);
34
+ const threadId = normalizeNullableText(params.threadId);
35
+ const turnId = normalizeNullableText(params.turnId);
38
36
  if (
39
37
  !threadId ||
40
38
  !turnId ||
@@ -75,7 +73,7 @@ const CODEX_APP_SERVER_CONTEXT_REFRESH_SIGNALS = new Set([
75
73
 
76
74
  function codexAppServerStatusFromValue(status = null) {
77
75
  if (typeof status === "string") {
78
- const normalized = normalizeText(status);
76
+ const normalized = normalizeNullableText(status);
79
77
  if (normalized === "active") {
80
78
  return "inProgress";
81
79
  }
@@ -87,10 +85,10 @@ function codexAppServerStatusFromValue(status = null) {
87
85
  }
88
86
  return normalized;
89
87
  }
90
- if (!isPlainObject(status)) {
88
+ if (!isRecord(status)) {
91
89
  return "";
92
90
  }
93
- const type = normalizeText(status.type);
91
+ const type = normalizeNullableText(status.type);
94
92
  if (type === "active") {
95
93
  return "inProgress";
96
94
  }
@@ -107,11 +105,11 @@ function codexAppServerStatusFromValue(status = null) {
107
105
  }
108
106
 
109
107
  function codexAppServerNotificationParams(notification = {}) {
110
- return isPlainObject(notification?.params) ? notification.params : {};
108
+ return isRecord(notification?.params) ? notification.params : {};
111
109
  }
112
110
 
113
111
  function codexAppServerNotificationEvent(notification = {}) {
114
- const method = normalizeText(notification.method);
112
+ const method = normalizeNullableText(notification.method);
115
113
  const params = codexAppServerNotificationParams(notification);
116
114
  const candidates = [
117
115
  params.event,
@@ -124,17 +122,17 @@ function codexAppServerNotificationEvent(notification = {}) {
124
122
  notification.record
125
123
  ];
126
124
  for (const candidate of candidates) {
127
- if (isPlainObject(candidate)) {
125
+ if (isRecord(candidate)) {
128
126
  return candidate;
129
127
  }
130
128
  }
131
- if (isPlainObject(params.payload) || normalizeText(params.type)) {
129
+ if (isRecord(params.payload) || normalizeNullableText(params.type)) {
132
130
  return params;
133
131
  }
134
- if (isPlainObject(notification.payload) || normalizeText(notification.type)) {
132
+ if (isRecord(notification.payload) || normalizeNullableText(notification.type)) {
135
133
  return notification;
136
134
  }
137
- if (["event_msg", "response_item", "task_complete"].includes(method) && isPlainObject(params)) {
135
+ if (["event_msg", "response_item", "task_complete"].includes(method) && isRecord(params)) {
138
136
  return params;
139
137
  }
140
138
  return null;
@@ -142,33 +140,33 @@ function codexAppServerNotificationEvent(notification = {}) {
142
140
 
143
141
  function codexAppServerNotificationEventType(notification = {}, event = null) {
144
142
  const params = codexAppServerNotificationParams(notification);
145
- return normalizeText(event?.type || params.type || notification.type || notification.method);
143
+ return normalizeNullableText(event?.type || params.type || notification.type || notification.method);
146
144
  }
147
145
 
148
146
  function codexAppServerNotificationEventPayload(notification = {}, event = null) {
149
- if (isPlainObject(event?.payload)) {
147
+ if (isRecord(event?.payload)) {
150
148
  return event.payload;
151
149
  }
152
150
  const params = codexAppServerNotificationParams(notification);
153
- if (isPlainObject(params.payload)) {
151
+ if (isRecord(params.payload)) {
154
152
  return params.payload;
155
153
  }
156
- if (isPlainObject(notification.payload)) {
154
+ if (isRecord(notification.payload)) {
157
155
  return notification.payload;
158
156
  }
159
- return isPlainObject(event) ? event : {};
157
+ return isRecord(event) ? event : {};
160
158
  }
161
159
 
162
160
  function codexAppServerNotificationItem(notification = {}) {
163
161
  const item = codexAppServerNotificationParams(notification).item;
164
- return isPlainObject(item) ? item : null;
162
+ return isRecord(item) ? item : null;
165
163
  }
166
164
 
167
165
  function codexAppServerNotificationThreadId(notification = {}) {
168
166
  const params = codexAppServerNotificationParams(notification);
169
167
  const event = codexAppServerNotificationEvent(notification);
170
168
  const payload = codexAppServerNotificationEventPayload(notification, event);
171
- return normalizeText(
169
+ return normalizeNullableText(
172
170
  params.threadId ||
173
171
  params.thread_id ||
174
172
  params.thread?.id ||
@@ -184,7 +182,7 @@ function codexAppServerNotificationTurnId(notification = {}) {
184
182
  const event = codexAppServerNotificationEvent(notification);
185
183
  const payload = codexAppServerNotificationEventPayload(notification, event);
186
184
  const item = codexAppServerNotificationItem(notification);
187
- return normalizeText(
185
+ return normalizeNullableText(
188
186
  params.turnId ||
189
187
  params.turn_id ||
190
188
  params.turn?.id ||
@@ -199,7 +197,7 @@ function codexAppServerNotificationTurnId(notification = {}) {
199
197
 
200
198
  function codexAppServerNotificationTurnStatus(notification = {}) {
201
199
  const params = codexAppServerNotificationParams(notification);
202
- const turnStatus = normalizeText(params.turn?.status);
200
+ const turnStatus = normalizeNullableText(params.turn?.status);
203
201
  return turnStatus || codexAppServerStatusFromValue(params.status);
204
202
  }
205
203
 
@@ -208,7 +206,7 @@ function codexAppServerErrorText(value = null, seen = new Set()) {
208
206
  return "";
209
207
  }
210
208
  if (typeof value === "string") {
211
- const text = normalizeText(value);
209
+ const text = normalizeNullableText(value);
212
210
  if (text.startsWith("{") || text.startsWith("[")) {
213
211
  try {
214
212
  const parsed = JSON.parse(text);
@@ -222,7 +220,7 @@ function codexAppServerErrorText(value = null, seen = new Set()) {
222
220
  }
223
221
  return text;
224
222
  }
225
- if (!isPlainObject(value)) {
223
+ if (!isRecord(value)) {
226
224
  return "";
227
225
  }
228
226
  if (seen.has(value)) {
@@ -238,16 +236,16 @@ function codexAppServerErrorText(value = null, seen = new Set()) {
238
236
  codexAppServerErrorText(value.codexErrorInfo || value.codex_error_info, seen)
239
237
  ].filter(Boolean);
240
238
  if (!messages.length) {
241
- return normalizeText(value.code);
239
+ return normalizeNullableText(value.code);
242
240
  }
243
241
  return [...new Set(messages)].join(" ");
244
242
  }
245
243
 
246
244
  function codexAppServerNotificationError(notification = {}) {
247
245
  const params = codexAppServerNotificationParams(notification);
248
- const status = isPlainObject(params.status) ? params.status : {};
249
- const turn = isPlainObject(params.turn) ? params.turn : {};
250
- return normalizeText(
246
+ const status = isRecord(params.status) ? params.status : {};
247
+ const turn = isRecord(params.turn) ? params.turn : {};
248
+ return normalizeNullableText(
251
249
  codexAppServerErrorText(params.error) ||
252
250
  params.message ||
253
251
  codexAppServerErrorText(status.error) ||
@@ -259,23 +257,23 @@ function codexAppServerNotificationError(notification = {}) {
259
257
 
260
258
  function codexAppServerNotificationUsageLimitExceeded(notification = {}) {
261
259
  const params = codexAppServerNotificationParams(notification);
262
- const status = isPlainObject(params.status) ? params.status : {};
263
- const turn = isPlainObject(params.turn) ? params.turn : {};
260
+ const status = isRecord(params.status) ? params.status : {};
261
+ const turn = isRecord(params.turn) ? params.turn : {};
264
262
  return [params.error, status.error, turn.error].some((error) => (
265
- isPlainObject(error) &&
266
- normalizeText(error.codexErrorInfo || error.codex_error_info) === "usageLimitExceeded"
263
+ isRecord(error) &&
264
+ normalizeNullableText(error.codexErrorInfo || error.codex_error_info) === "usageLimitExceeded"
267
265
  ));
268
266
  }
269
267
 
270
268
  function codexAppServerTextInputText(input = {}) {
271
- if (!isPlainObject(input) || normalizeText(input.type) !== "text") {
269
+ if (!isRecord(input) || normalizeNullableText(input.type) !== "text") {
272
270
  return "";
273
271
  }
274
- return normalizeText(input.text);
272
+ return normalizeNullableText(input.text);
275
273
  }
276
274
 
277
275
  function codexAppServerUserMessageText(item = {}) {
278
- if (!isPlainObject(item) || normalizeText(item.type) !== "userMessage") {
276
+ if (!isRecord(item) || normalizeNullableText(item.type) !== "userMessage") {
279
277
  return "";
280
278
  }
281
279
  return (Array.isArray(item.content) ? item.content : [])
@@ -291,7 +289,7 @@ function codexAppServerContentText(value = null) {
291
289
  if (Array.isArray(value)) {
292
290
  return value.map((entry) => codexAppServerContentText(entry)).filter(Boolean).join("");
293
291
  }
294
- if (!isPlainObject(value)) {
292
+ if (!isRecord(value)) {
295
293
  return "";
296
294
  }
297
295
  if (typeof value.text === "string") {
@@ -306,18 +304,18 @@ function codexAppServerContentText(value = null) {
306
304
  if (typeof value.message === "string" || Array.isArray(value.message)) {
307
305
  return codexAppServerContentText(value.message);
308
306
  }
309
- if (isPlainObject(value.message)) {
307
+ if (isRecord(value.message)) {
310
308
  return codexAppServerContentText(value.message.content || value.message.text);
311
309
  }
312
310
  return "";
313
311
  }
314
312
 
315
313
  function codexAppServerAssistantItemText(item = {}) {
316
- if (!isPlainObject(item)) {
314
+ if (!isRecord(item)) {
317
315
  return "";
318
316
  }
319
- const type = normalizeText(item.type);
320
- const role = normalizeText(item.role || item.author?.role);
317
+ const type = normalizeNullableText(item.type);
318
+ const role = normalizeNullableText(item.role || item.author?.role);
321
319
  const isAssistant = role === "assistant" ||
322
320
  type === "agentMessage" ||
323
321
  type === "assistantMessage" ||
@@ -327,7 +325,7 @@ function codexAppServerAssistantItemText(item = {}) {
327
325
  if (!isAssistant) {
328
326
  return "";
329
327
  }
330
- return normalizeText(
328
+ return normalizeNullableText(
331
329
  codexAppServerContentText(item.content) ||
332
330
  codexAppServerContentText(item.text) ||
333
331
  codexAppServerContentText(item.message)
@@ -335,11 +333,11 @@ function codexAppServerAssistantItemText(item = {}) {
335
333
  }
336
334
 
337
335
  function codexAppServerHookPromptText(item = {}) {
338
- if (!isPlainObject(item) || normalizeText(item.type) !== "hookPrompt") {
336
+ if (!isRecord(item) || normalizeNullableText(item.type) !== "hookPrompt") {
339
337
  return "";
340
338
  }
341
339
  return (Array.isArray(item.fragments) ? item.fragments : [])
342
- .map((fragment) => normalizeText(fragment?.text))
340
+ .map((fragment) => normalizeNullableText(fragment?.text))
343
341
  .filter(Boolean)
344
342
  .join("\n\n");
345
343
  }
@@ -349,7 +347,7 @@ function codexAppServerNotificationItemId(notification = {}) {
349
347
  const item = codexAppServerNotificationItem(notification);
350
348
  const event = codexAppServerNotificationEvent(notification);
351
349
  const payload = codexAppServerNotificationEventPayload(notification, event);
352
- return normalizeText(
350
+ return normalizeNullableText(
353
351
  item?.id ||
354
352
  params.itemId ||
355
353
  params.item_id ||
@@ -361,22 +359,27 @@ function codexAppServerNotificationItemId(notification = {}) {
361
359
  );
362
360
  }
363
361
 
362
+ // Text of an event_msg agent_message payload.
363
+ function codexAppServerAgentMessageText(payload = {}) {
364
+ return normalizeNullableText(
365
+ codexAppServerContentText(payload.message) ||
366
+ codexAppServerContentText(payload.text) ||
367
+ codexAppServerContentText(payload.content)
368
+ );
369
+ }
370
+
364
371
  function codexAppServerFinalEventText(notification = {}, event = null, payload = {}) {
365
372
  const eventType = codexAppServerNotificationEventType(notification, event);
366
- const payloadType = normalizeText(payload.type);
367
- const phase = normalizeText(payload.phase || event?.phase);
373
+ const payloadType = normalizeNullableText(payload.type);
374
+ const phase = normalizeNullableText(payload.phase || event?.phase);
368
375
  if (eventType === "task_complete") {
369
- return normalizeText(
376
+ return normalizeNullableText(
370
377
  codexAppServerContentText(payload.last_agent_message) ||
371
378
  codexAppServerContentText(payload.lastAgentMessage)
372
379
  );
373
380
  }
374
381
  if (eventType === "event_msg" && payloadType === "agent_message" && phase === "final_answer") {
375
- return normalizeText(
376
- codexAppServerContentText(payload.message) ||
377
- codexAppServerContentText(payload.text) ||
378
- codexAppServerContentText(payload.content)
379
- );
382
+ return codexAppServerAgentMessageText(payload);
380
383
  }
381
384
  if (eventType === "response_item" && phase === "final_answer") {
382
385
  return codexAppServerAssistantItemText(payload);
@@ -385,15 +388,15 @@ function codexAppServerFinalEventText(notification = {}, event = null, payload =
385
388
  }
386
389
 
387
390
  function classifyCodexAppServerEvent(notification = {}) {
388
- const method = normalizeText(notification.method);
391
+ const method = normalizeNullableText(notification.method);
389
392
  const event = codexAppServerNotificationEvent(notification);
390
393
  const payload = codexAppServerNotificationEventPayload(notification, event);
391
394
  const eventType = event ? codexAppServerNotificationEventType(notification, event) : "";
392
- const payloadType = normalizeText(payload.type);
395
+ const payloadType = normalizeNullableText(payload.type);
393
396
  const item = codexAppServerNotificationItem(notification);
394
- const itemType = normalizeText(item?.type);
397
+ const itemType = normalizeNullableText(item?.type);
395
398
  const itemText = codexAppServerAssistantItemText(item);
396
- const phase = normalizeText(payload.phase || event?.phase || item?.phase || item?.purpose || item?.category);
399
+ const phase = normalizeNullableText(payload.phase || event?.phase || item?.phase || item?.purpose || item?.category);
397
400
  const base = {
398
401
  itemId: codexAppServerNotificationItemId(notification),
399
402
  source: method || eventType || "notification",
@@ -437,7 +440,7 @@ function classifyCodexAppServerEvent(notification = {}) {
437
440
  ...base,
438
441
  kind: "reasoning_summary",
439
442
  delta: codexAppServerContentText(codexAppServerNotificationParams(notification).delta),
440
- text: normalizeText(codexAppServerContentText(codexAppServerNotificationParams(notification).delta))
443
+ text: normalizeNullableText(codexAppServerContentText(codexAppServerNotificationParams(notification).delta))
441
444
  };
442
445
  }
443
446
 
@@ -495,11 +498,7 @@ function classifyCodexAppServerEvent(notification = {}) {
495
498
  ...base,
496
499
  kind: "live_progress",
497
500
  source: eventType,
498
- text: normalizeText(
499
- codexAppServerContentText(payload.message) ||
500
- codexAppServerContentText(payload.text) ||
501
- codexAppServerContentText(payload.content)
502
- )
501
+ text: codexAppServerAgentMessageText(payload)
503
502
  };
504
503
  }
505
504
 
@@ -535,7 +534,7 @@ function classifyCodexAppServerEvent(notification = {}) {
535
534
  }
536
535
 
537
536
  function codexAppServerSignalName(value = "") {
538
- return normalizeText(value)
537
+ return normalizeNullableText(value)
539
538
  .toLowerCase()
540
539
  .replaceAll("-", "_")
541
540
  .replaceAll("/", "_");
@@ -551,7 +550,7 @@ function codexAppServerSignalNames(value = null) {
551
550
  if (Array.isArray(value)) {
552
551
  return value.flatMap((entry) => codexAppServerSignalNames(entry));
553
552
  }
554
- if (!isPlainObject(value)) {
553
+ if (!isRecord(value)) {
555
554
  return [];
556
555
  }
557
556
  return [
@@ -567,12 +566,12 @@ function codexAppServerSignalNames(value = null) {
567
566
  }
568
567
 
569
568
  function codexAppServerContextRefreshReason(notification = {}) {
570
- const method = normalizeText(notification.method);
569
+ const method = normalizeNullableText(notification.method);
571
570
  const item = codexAppServerNotificationItem(notification);
572
571
  const event = codexAppServerNotificationEvent(notification);
573
572
  const payload = codexAppServerNotificationEventPayload(notification, event);
574
573
  const eventType = codexAppServerNotificationEventType(notification, event);
575
- const payloadType = normalizeText(payload.type);
574
+ const payloadType = normalizeNullableText(payload.type);
576
575
  const signals = [
577
576
  method,
578
577
  item?.type,
@@ -592,32 +591,28 @@ function codexAppServerContextRefreshReason(notification = {}) {
592
591
  return "";
593
592
  }
594
593
 
594
+ // Like codexAppServerThreadRawValue, but provider results may also wrap the
595
+ // thread once more under `thread`, checked after raw and response.thread.
595
596
  function codexAppServerProviderThread(value = {}) {
596
- if (isPlainObject(value?.raw)) {
597
- return value.raw;
598
- }
599
- if (isPlainObject(value?.response?.thread)) {
600
- return value.response.thread;
601
- }
602
- if (isPlainObject(value?.thread)) {
597
+ if (!isRecord(value?.raw) && !isRecord(value?.response?.thread) && isRecord(value?.thread)) {
603
598
  return value.thread;
604
599
  }
605
- return isPlainObject(value) ? value : {};
600
+ return codexAppServerThreadRawValue(value || {});
606
601
  }
607
602
 
608
603
  function codexAppServerProviderTurnId(turn = {}) {
609
- return normalizeText(turn.id || turn.turnId || turn.turn_id || turn.turn?.id || "");
604
+ return normalizeNullableText(turn.id || turn.turnId || turn.turn_id || turn.turn?.id || "");
610
605
  }
611
606
 
612
607
  function codexAppServerProviderTurnItems(turn = {}) {
613
608
  return [
614
609
  ...(Array.isArray(turn.items) ? turn.items : []),
615
610
  ...(Array.isArray(turn.itemsView) ? turn.itemsView : [])
616
- ].filter(isPlainObject);
611
+ ].filter(isRecord);
617
612
  }
618
613
 
619
614
  function codexAppServerProviderThreadTurn(value = {}, turnId = "") {
620
- const normalizedTurnId = normalizeText(turnId);
615
+ const normalizedTurnId = normalizeNullableText(turnId);
621
616
  if (!normalizedTurnId) {
622
617
  return null;
623
618
  }
@@ -637,11 +632,11 @@ function codexAppServerProviderTurnAssistantSegments(turn = {}) {
637
632
  const active = ["inProgress", "starting"].includes(codexAppServerStatusFromValue(turn.status));
638
633
  return items
639
634
  .filter((item, index) => {
640
- const phase = normalizeText(item.phase);
635
+ const phase = normalizeNullableText(item.phase);
641
636
  return phase === "final_answer" || (!phase && !active && index >= responseStart);
642
637
  })
643
638
  .map((item) => {
644
- const itemId = normalizeText(item.id);
639
+ const itemId = normalizeNullableText(item.id);
645
640
  const text = codexAppServerAssistantItemText(item);
646
641
  if (
647
642
  !itemId ||
@@ -672,37 +667,37 @@ function codexAppServerOutputOwnerTurnId({
672
667
  trackedThreadId = "",
673
668
  trackedTurnId = ""
674
669
  } = {}) {
675
- const normalizedNotificationThreadId = normalizeText(notificationThreadId);
676
- const normalizedTrackedThreadId = normalizeText(trackedThreadId);
677
- const normalizedTrackedTurnId = normalizeText(trackedTurnId);
670
+ const normalizedNotificationThreadId = normalizeNullableText(notificationThreadId);
671
+ const normalizedTrackedThreadId = normalizeNullableText(trackedThreadId);
672
+ const normalizedTrackedTurnId = normalizeNullableText(trackedTurnId);
678
673
  const trackedTurnOwnsResult = trackedActive === true &&
679
- ["active", "finalizing"].includes(normalizeText(trackedState)) &&
674
+ ["active", "finalizing"].includes(normalizeNullableText(trackedState)) &&
680
675
  Boolean(normalizedTrackedTurnId) &&
681
676
  Boolean(normalizedNotificationThreadId) &&
682
677
  normalizedNotificationThreadId === normalizedTrackedThreadId;
683
678
  return trackedTurnOwnsResult
684
679
  ? normalizedTrackedTurnId
685
- : normalizeText(notificationTurnId);
680
+ : normalizeNullableText(notificationTurnId);
686
681
  }
687
682
 
688
683
  function codexAppServerThreadRawValue(thread = {}) {
689
- if (isPlainObject(thread.raw)) {
684
+ if (isRecord(thread.raw)) {
690
685
  return thread.raw;
691
686
  }
692
- if (isPlainObject(thread.response?.thread)) {
687
+ if (isRecord(thread.response?.thread)) {
693
688
  return thread.response.thread;
694
689
  }
695
- return isPlainObject(thread) ? thread : {};
690
+ return isRecord(thread) ? thread : {};
696
691
  }
697
692
 
698
693
  function codexAppServerThreadTurnId(thread = {}) {
699
- const observedTurnId = normalizeText(thread.observedTurn?.id || "");
694
+ const observedTurnId = normalizeNullableText(thread.observedTurn?.id || "");
700
695
  if (observedTurnId) {
701
696
  return observedTurnId;
702
697
  }
703
698
  const rawThread = codexAppServerThreadRawValue(thread);
704
- const status = isPlainObject(rawThread.status) ? rawThread.status : {};
705
- return normalizeText(
699
+ const status = isRecord(rawThread.status) ? rawThread.status : {};
700
+ return normalizeNullableText(
706
701
  thread.turnId ||
707
702
  thread.turn_id ||
708
703
  thread.turn?.id ||
@@ -726,14 +721,14 @@ function codexAppServerThreadTurnId(thread = {}) {
726
721
 
727
722
  function codexAppServerThreadError(thread = {}) {
728
723
  const rawThread = codexAppServerThreadRawValue(thread);
729
- const status = isPlainObject(rawThread.status) ? rawThread.status : {};
724
+ const status = isRecord(rawThread.status) ? rawThread.status : {};
730
725
  return codexAppServerErrorText(rawThread.error || status.error);
731
726
  }
732
727
 
733
728
  function codexAppServerProviderThreadTurns(thread = null) {
734
729
  const rawThread = codexAppServerThreadRawValue(thread || {});
735
730
  return (Array.isArray(rawThread.turns) ? rawThread.turns : [])
736
- .filter((turn) => isPlainObject(turn));
731
+ .filter((turn) => isRecord(turn));
737
732
  }
738
733
 
739
734
  function codexAppServerProviderTurnStatus(turn = {}) {
@@ -742,7 +737,7 @@ function codexAppServerProviderTurnStatus(turn = {}) {
742
737
 
743
738
  function codexAppServerProviderTurnClientIds(turn = {}) {
744
739
  return codexAppServerProviderTurnItems(turn)
745
- .map((item) => normalizeText(
740
+ .map((item) => normalizeNullableText(
746
741
  item.clientId ||
747
742
  item.client_id ||
748
743
  item.clientUserMessageId ||
@@ -756,8 +751,8 @@ function codexAppServerProviderTurnForOperation(thread = null, {
756
751
  clientMessageId = "",
757
752
  turnId = ""
758
753
  } = {}) {
759
- const normalizedTurnId = normalizeText(turnId || "");
760
- const normalizedClientMessageId = normalizeText(clientMessageId || "");
754
+ const normalizedTurnId = normalizeNullableText(turnId || "");
755
+ const normalizedClientMessageId = normalizeNullableText(clientMessageId || "");
761
756
  const turns = codexAppServerProviderThreadTurns(thread);
762
757
  if (normalizedTurnId) {
763
758
  return turns.find((turn) => (
@@ -772,7 +767,7 @@ function codexAppServerProviderTurnForOperation(thread = null, {
772
767
  )) || null;
773
768
  }
774
769
 
775
- // Native snapshot matching from the original renewal handover/seed paths.
770
+ // Native snapshot matching for renewal handover and seeding.
776
771
  // The application projects these classifications into its approval/error policy.
777
772
  function inspectCodexAppServerRenewalThread(thread = null, {
778
773
  clientMessageId = "",
@@ -818,6 +813,7 @@ export {
818
813
  inspectCodexAppServerRenewalThread,
819
814
  codexAppServerProviderTurnText,
820
815
  codexAppServerProviderTurnError,
816
+ codexAppServerAgentMessageText,
821
817
  codexAppServerAssistantItemText,
822
818
  codexAppServerContentText,
823
819
  codexAppServerContextRefreshReason,
@@ -1,13 +1,6 @@
1
- import { deleteCodexAppServerThread } from "./codexProvider.js";
1
+ import { codexAppServerDeliveredTurn, deleteCodexAppServerThread } from "./codexProvider.js";
2
2
  import { CODEX_HELPER_THREAD_LIFECYCLES } from "./codexHelperThreadLedger.js";
3
-
4
- function normalizeText(value) {
5
- return String(value || "").trim();
6
- }
7
-
8
- export function errorMessage(value, fallback = "Codex could not be prepared.") {
9
- return normalizeText(value?.error || value?.message || value) || fallback;
10
- }
3
+ import { codexAppServerErrorMessage as errorMessage, normalizeCodexRunText as normalizeText } from "./codexTurnState.js";
11
4
 
12
5
  export function createCodexHelperOwnershipError({ applicationName = "Application", errorPrefix = "" } = {}) {
13
6
  return function codexAppServerHelperOwnershipError(message = "", details = {}) {
@@ -253,10 +246,7 @@ export async function prepareDetachedCodexHelperThread(owner, scope, preparation
253
246
  const currentDelivery = await sendPreparedCodexAppServerHelperTurn(
254
247
  provider, preparation.turn(threadId), preparation.isolation
255
248
  );
256
- const currentTurnId = normalizeText(currentDelivery.turn?.id);
257
- const currentStatus = normalizeText(
258
- currentDelivery.turn?.status || currentDelivery.turn?.raw?.status
259
- );
249
+ const { turnId: currentTurnId, status: currentStatus } = codexAppServerDeliveredTurn(currentDelivery);
260
250
  helperThreadRecord = await updateCodexAppServerHelperThread(
261
251
  helperThreadRecord,
262
252
  {
@@ -1,6 +1,8 @@
1
1
  import crypto from "node:crypto";
2
2
  import { link, mkdir, open, readdir, rename, rm } from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
5
+ import { normalizeNullableText } from "./normalize.js";
4
6
 
5
7
  import { tryAcquireExclusiveFileLock } from "@jskit-ai/kernel/server/support";
6
8
 
@@ -72,16 +74,8 @@ function createCodexHelperThreadLedgerOwner({
72
74
  const codexHelperThreadLedgerMutationQueues = new Map();
73
75
  const codexHelperThreadLedgerProcessId = crypto.randomUUID();
74
76
 
75
- function normalizeText(value) {
76
- return String(value ?? "").trim();
77
- }
78
-
79
- function isRecord(value) {
80
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
81
- }
82
-
83
77
  function ledgerError(message = "", code = `${errorPrefix}ledger_invalid`) {
84
- const error = new Error(normalizeText(message) || "Codex helper ownership record is invalid.");
78
+ const error = new Error(normalizeNullableText(message) || "Codex helper ownership record is invalid.");
85
79
  error.code = code;
86
80
  error.retryable = true;
87
81
  return error;
@@ -102,7 +96,7 @@ function createCodexHelperThreadLedgerOwner({
102
96
  }
103
97
 
104
98
  function requiredText(value, label, maxLength = 4096) {
105
- const normalized = normalizeText(value);
99
+ const normalized = normalizeNullableText(value);
106
100
  if (!normalized) {
107
101
  throw ledgerError(`${label} is required.`);
108
102
  }
@@ -137,7 +131,7 @@ function createCodexHelperThreadLedgerOwner({
137
131
  }
138
132
 
139
133
  function optionalText(value, label = "value", maxLength = 4096) {
140
- const normalized = normalizeText(value);
134
+ const normalized = normalizeNullableText(value);
141
135
  if (normalized.length > maxLength) {
142
136
  throw ledgerError(`${label} exceeds ${maxLength} characters.`);
143
137
  }
@@ -635,8 +629,8 @@ function createCodexHelperThreadLedgerOwner({
635
629
  records.push(await readRecordFile(filePath));
636
630
  } catch (error) {
637
631
  failures.push(Object.freeze({
638
- code: normalizeText(error?.code),
639
- error: normalizeText(error?.message || error),
632
+ code: normalizeNullableText(error?.code),
633
+ error: normalizeNullableText(error?.message || error),
640
634
  fileName: entry.name,
641
635
  retryable: true
642
636
  }));
@@ -1,21 +1,7 @@
1
- import { stat } from "node:fs/promises";
2
1
  import { deleteCodexAppServerThread, codexAppServerRuntimeStopWasVerified } from "./codexProvider.js";
2
+ import { directoryExists } from "./codexProviderOwner.js";
3
3
  import { CODEX_HELPER_THREAD_LIFECYCLES } from "./codexHelperThreadLedger.js";
4
-
5
- export function normalizeText(value) {
6
- return String(value || "").trim();
7
- }
8
-
9
- export async function directoryExists(filePath = "") {
10
- try {
11
- return (await stat(filePath)).isDirectory();
12
- } catch (error) {
13
- if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
14
- return false;
15
- }
16
- throw error;
17
- }
18
- }
4
+ import { normalizeCodexRunText as normalizeText } from "./codexTurnState.js";
19
5
 
20
6
  // Recovery operations of the same Helper lifecycle. All maps and record mutations
21
7
  // belong to the supplied lifecycle; this module creates no owner or registry.
@@ -1,7 +1,10 @@
1
1
  import crypto from "node:crypto";
2
- import { createCodexHelperOwnershipError, errorMessage, prepareDetachedCodexHelperThread } from "./codexHelperExecution.js";
2
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { createCodexHelperOwnershipError, prepareDetachedCodexHelperThread } from "./codexHelperExecution.js";
3
4
  import { deleteCodexAppServerHelperThread } from "./codexProvider.js";
4
- import { createCodexHelperThreadRecovery, directoryExists, normalizeText } from "./codexHelperThreadRecovery.js";
5
+ import { directoryExists } from "./codexProviderOwner.js";
6
+ import { createCodexHelperThreadRecovery } from "./codexHelperThreadRecovery.js";
7
+ import { codexAppServerErrorMessage as errorMessage, normalizeCodexRunText as normalizeText } from "./codexTurnState.js";
5
8
  import {
6
9
  CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION,
7
10
  CODEX_HELPER_THREAD_LIFECYCLES
@@ -9,10 +12,6 @@ import {
9
12
 
10
13
  const CODEX_APP_SERVER_PROVIDER_ID = "codex_app_server";
11
14
 
12
- function isRecord(value) {
13
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
14
- }
15
-
16
15
  // Internal facet of the original run owner. Public supplies policy and authorized
17
16
  // scope inputs; native ownership, pending work and retirement have one owner.
18
17
  export function createCodexHelperThreadLifecycle({