@narumitw/pi-codex-compact 0.51.3 → 0.53.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/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  // @generated by scripts/build-runtime.mjs; do not edit.
2
2
  // @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
3
3
  import {
4
- usesCodexResponsesApi
5
- } from "./chunks/chunk-6TZ2L2BM.js";
4
+ resolveCompactionRoute,
5
+ terminalText
6
+ } from "./chunks/chunk-7H7JJ7ZV.js";
6
7
 
7
8
  // src/codex-compact.ts
8
9
  import {
@@ -17,6 +18,7 @@ import { createHash, randomUUID } from "node:crypto";
17
18
 
18
19
  // src/protocol.ts
19
20
  var MAX_SSE_BYTES = 8 * 1024 * 1024;
21
+ var MAX_COMPACT_JSON_BYTES = 8 * 1024 * 1024;
20
22
  var MAX_COMPACTION_ITEM_BYTES = 2 * 1024 * 1024;
21
23
  var CodexCompactionProtocolError = class extends Error {
22
24
  constructor(message) {
@@ -35,9 +37,7 @@ function isCompactionItem(value) {
35
37
  }
36
38
  function validateCompactionItem(value, maxBytes = MAX_COMPACTION_ITEM_BYTES) {
37
39
  if (!isCompactionItem(value)) {
38
- throw new CodexCompactionProtocolError(
39
- "Remote response did not contain a valid compaction item"
40
- );
40
+ throw new CodexCompactionProtocolError("Remote response did not contain a valid compaction item");
41
41
  }
42
42
  if (byteLength(value) > maxBytes) {
43
43
  throw new CodexCompactionProtocolError("Remote compaction item exceeded the size limit");
@@ -130,9 +130,7 @@ async function collectCompactionSse(stream, options = {}) {
130
130
  reader.releaseLock();
131
131
  }
132
132
  if (!completedResponse) {
133
- throw new CodexCompactionProtocolError(
134
- "Remote compaction stream ended without response.completed"
135
- );
133
+ throw new CodexCompactionProtocolError("Remote compaction stream ended without response.completed");
136
134
  }
137
135
  if (items.size !== 1) {
138
136
  throw new CodexCompactionProtocolError(
@@ -141,6 +139,107 @@ async function collectCompactionSse(stream, options = {}) {
141
139
  }
142
140
  return { item: [...items.values()][0], completedResponse };
143
141
  }
142
+ function isRetainedCompactContent(value) {
143
+ if (!isObject(value)) return false;
144
+ if (value.type === "input_text") return typeof value.text === "string";
145
+ if (value.type !== "input_image") return false;
146
+ if (value.detail !== void 0 && value.detail !== null && value.detail !== "auto" && value.detail !== "low" && value.detail !== "high" && value.detail !== "original") {
147
+ return false;
148
+ }
149
+ if (value.file_id !== void 0 && value.file_id !== null && typeof value.file_id !== "string" || value.image_url !== void 0 && value.image_url !== null && typeof value.image_url !== "string") {
150
+ return false;
151
+ }
152
+ return typeof value.file_id === "string" && value.file_id.length > 0 || typeof value.image_url === "string" && value.image_url.length > 0;
153
+ }
154
+ function isRetainedCompactMessage(value) {
155
+ return isObject(value) && value.role === "user" && (value.type === void 0 || value.type === "message") && Array.isArray(value.content) && value.content.length > 0 && value.content.every(isRetainedCompactContent);
156
+ }
157
+ function validateCompactedResponse(value, options = {}) {
158
+ if (!isObject(value) || !Array.isArray(value.output)) {
159
+ throw new CodexCompactionProtocolError("Responses Compact returned an invalid response object");
160
+ }
161
+ const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
162
+ const maxItemBytes = options.maxItemBytes ?? MAX_COMPACTION_ITEM_BYTES;
163
+ if (byteLength(value) > maxBytes) {
164
+ throw new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
165
+ }
166
+ if (value.output.length === 0) {
167
+ throw new CodexCompactionProtocolError("Responses Compact returned no output items");
168
+ }
169
+ const output = value.output.map((item2) => {
170
+ if (!isObject(item2)) {
171
+ throw new CodexCompactionProtocolError("Responses Compact returned a non-object output item");
172
+ }
173
+ if (byteLength(item2) > maxItemBytes) {
174
+ throw new CodexCompactionProtocolError("Responses Compact output item exceeded the size limit");
175
+ }
176
+ return structuredClone(item2);
177
+ });
178
+ const compactionItems = output.filter((item2) => item2.type === "compaction");
179
+ if (compactionItems.length !== 1 || output.at(-1)?.type !== "compaction") {
180
+ throw new CodexCompactionProtocolError(
181
+ "Responses Compact must return retained messages followed by one compaction item"
182
+ );
183
+ }
184
+ for (const item2 of output.slice(0, -1)) {
185
+ if (!isRetainedCompactMessage(item2)) {
186
+ throw new CodexCompactionProtocolError("Responses Compact returned an unsupported retained output item");
187
+ }
188
+ }
189
+ const item = validateCompactionItem(output.at(-1), maxItemBytes);
190
+ return { item, output: [...output.slice(0, -1), item], response: structuredClone(value) };
191
+ }
192
+ async function collectCompactResponse(response, options = {}) {
193
+ const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
194
+ const declaredLength = Number(response.headers.get("content-length"));
195
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
196
+ const error = new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
197
+ await response.body?.cancel(error).catch(() => void 0);
198
+ throw error;
199
+ }
200
+ if (!response.body) {
201
+ throw new CodexCompactionProtocolError("Responses Compact response did not contain a body");
202
+ }
203
+ const reader = response.body.getReader();
204
+ const chunks = [];
205
+ let bytes = 0;
206
+ const onAbort = () => {
207
+ void reader.cancel(new DOMException("Compaction aborted", "AbortError")).catch(() => void 0);
208
+ };
209
+ options.signal?.addEventListener("abort", onAbort, { once: true });
210
+ try {
211
+ while (true) {
212
+ if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
213
+ const { done, value } = await reader.read();
214
+ if (done) break;
215
+ bytes += value.byteLength;
216
+ if (bytes > maxBytes) {
217
+ throw new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
218
+ }
219
+ chunks.push(value);
220
+ }
221
+ if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
222
+ } catch (error) {
223
+ await reader.cancel(error).catch(() => void 0);
224
+ throw error;
225
+ } finally {
226
+ options.signal?.removeEventListener("abort", onAbort);
227
+ reader.releaseLock();
228
+ }
229
+ const body = new Uint8Array(bytes);
230
+ let offset = 0;
231
+ for (const chunk of chunks) {
232
+ body.set(chunk, offset);
233
+ offset += chunk.byteLength;
234
+ }
235
+ let parsed;
236
+ try {
237
+ parsed = JSON.parse(new TextDecoder().decode(body));
238
+ } catch {
239
+ throw new CodexCompactionProtocolError("Responses Compact returned malformed JSON");
240
+ }
241
+ return validateCompactedResponse(parsed, options);
242
+ }
144
243
  function markerTextFromItem(item) {
145
244
  if (!isObject(item) || item.role !== "user" || !Array.isArray(item.content)) return void 0;
146
245
  if (item.content.length !== 1) return void 0;
@@ -175,15 +274,21 @@ function appendCompactionTrigger(payload) {
175
274
  throw new CodexCompactionProtocolError("Codex Responses payload is missing an input array");
176
275
  }
177
276
  if (payload.input.some((item) => isObject(item) && item.type === "compaction_trigger")) {
178
- throw new CodexCompactionProtocolError(
179
- "Provider payload already contains a compaction trigger"
180
- );
277
+ throw new CodexCompactionProtocolError("Provider payload already contains a compaction trigger");
181
278
  }
182
279
  return { ...payload, input: [...payload.input, { type: "compaction_trigger" }] };
183
280
  }
281
+ function expandRemoteCompactionPayload(payload, checkpoint) {
282
+ if (checkpoint) {
283
+ return rewriteCheckpointMarker(payload, checkpoint.marker, checkpoint.replacementHistory);
284
+ }
285
+ if (!isObject(payload) || !Array.isArray(payload.input)) {
286
+ throw new CodexCompactionProtocolError("Responses payload is missing an input array");
287
+ }
288
+ return structuredClone(payload);
289
+ }
184
290
  function prepareRemoteCompactionPayload(payload, checkpoint) {
185
- const expanded = checkpoint ? rewriteCheckpointMarker(payload, checkpoint.marker, checkpoint.replacementHistory) : payload;
186
- return appendCompactionTrigger(expanded);
291
+ return appendCompactionTrigger(expandRemoteCompactionPayload(payload, checkpoint));
187
292
  }
188
293
  function hasCheckpointMarker(payload, marker) {
189
294
  return isObject(payload) && Array.isArray(payload.input) && payload.input.some((item) => markerTextFromItem(item) === marker);
@@ -191,11 +296,16 @@ function hasCheckpointMarker(payload, marker) {
191
296
 
192
297
  // src/checkpoint.ts
193
298
  var CHECKPOINT_KIND = "pi-codex-remote-compaction";
194
- var CHECKPOINT_VERSION = 1;
299
+ var CHECKPOINT_VERSION = 3;
195
300
  var REPLACEMENT_TOKEN_BUDGET = 64e3;
196
301
  var REPLACEMENT_BYTE_BUDGET = 8 * 1024 * 1024;
197
302
  var MAX_MEDIA_ITEM_BYTES = 2 * 1024 * 1024;
303
+ var MAX_CHECKPOINT_DETAILS_BYTES = 10 * 1024 * 1024;
304
+ var MAX_CHECKPOINT_ID_LENGTH = 128;
198
305
  var MAX_PROVIDER_ID_LENGTH = 256;
306
+ var MAX_API_ID_LENGTH = 256;
307
+ var MAX_MODEL_ID_LENGTH = 512;
308
+ var MAX_KEPT_FINGERPRINTS = 1e5;
199
309
  function isObject2(value) {
200
310
  return typeof value === "object" && value !== null && !Array.isArray(value);
201
311
  }
@@ -216,13 +326,13 @@ function checkpointMarker(checkpointId) {
216
326
  return [
217
327
  `[PI_CODEX_REMOTE_CHECKPOINT:${checkpointId}]`,
218
328
  "Opaque checkpoint injection failed. Do not infer missing history; tell the user to re-enable",
219
- "@narumitw/pi-codex-compact with a model using the openai-codex-responses API."
329
+ "@narumitw/pi-codex-compact with the same model and Responses API."
220
330
  ].join(" ");
221
331
  }
222
332
  function fallbackSummary(checkpointId) {
223
333
  return [
224
- `OpenAI Codex Remote Compaction V2 checkpoint ${checkpointId} stores the older history opaquely.`,
225
- "Full replay requires @narumitw/pi-codex-compact and the same model through a compatible Codex Responses provider.",
334
+ `Responses compaction checkpoint ${checkpointId} stores the older history opaquely.`,
335
+ "Full replay requires @narumitw/pi-codex-compact and the same model through a compatible Responses provider.",
226
336
  "Without them, only Pi's retained recent messages remain available."
227
337
  ].join(" ");
228
338
  }
@@ -235,7 +345,20 @@ function markerMessage(checkpointId, timestamp) {
235
345
  }
236
346
  function parseCheckpointDetails(value) {
237
347
  if (!isObject2(value)) return void 0;
238
- if (value.kind !== CHECKPOINT_KIND || value.version !== CHECKPOINT_VERSION || typeof value.checkpointId !== "string" || value.checkpointId.length < 8 || typeof value.provider !== "string" || value.provider.length === 0 || value.provider.length > MAX_PROVIDER_ID_LENGTH || value.api !== "openai-codex-responses" || typeof value.modelId !== "string" || value.protocol !== "remote-compaction-v2" || !Array.isArray(value.replacementHistory) || !Array.isArray(value.keptMessageFingerprints) || typeof value.createdAt !== "string") {
348
+ try {
349
+ if (serializedBytes(value) > MAX_CHECKPOINT_DETAILS_BYTES) return void 0;
350
+ } catch {
351
+ return void 0;
352
+ }
353
+ const isVersionOne = value.version === 1 && value.api === "openai-codex-responses" && value.protocol === "remote-compaction-v2";
354
+ const isVersionTwo = value.version === 2 && (value.api === "openai-codex-responses" || value.api === "openai-responses" || value.api === "azure-openai-responses") && (value.protocol === "remote-v2" || value.protocol === "responses-compact");
355
+ const isVersionThree = value.version === CHECKPOINT_VERSION && typeof value.api === "string" && value.api.length > 0 && value.api.length <= MAX_API_ID_LENGTH && (value.profile === "codex-responses-v1" || value.profile === "openai-responses-v1") && (value.protocol === "remote-v2" || value.protocol === "responses-compact");
356
+ if (value.kind !== CHECKPOINT_KIND || !isVersionOne && !isVersionTwo && !isVersionThree || typeof value.checkpointId !== "string" || value.checkpointId.length < 8 || value.checkpointId.length > MAX_CHECKPOINT_ID_LENGTH || typeof value.provider !== "string" || value.provider.length === 0 || value.provider.length > MAX_PROVIDER_ID_LENGTH || typeof value.modelId !== "string" || value.modelId.length === 0 || value.modelId.length > MAX_MODEL_ID_LENGTH || !Array.isArray(value.replacementHistory) || !Array.isArray(value.keptMessageFingerprints) || value.keptMessageFingerprints.length > MAX_KEPT_FINGERPRINTS || typeof value.createdAt !== "string" || value.createdAt.length > 64) {
357
+ return void 0;
358
+ }
359
+ const api = value.api;
360
+ const profile = api === "openai-codex-responses" ? "codex-responses-v1" : api === "openai-responses" || api === "azure-openai-responses" ? "openai-responses-v1" : value.profile;
361
+ if (profile !== "codex-responses-v1" && profile !== "openai-responses-v1" || api !== "openai-codex-responses" && api !== "openai-responses" && api !== "azure-openai-responses" && profile !== "codex-responses-v1" || isVersionThree && value.profile !== profile) {
239
362
  return void 0;
240
363
  }
241
364
  if (value.replacementHistory.length === 0 || !value.replacementHistory.every(isObject2) || !value.keptMessageFingerprints.every(
@@ -249,7 +372,19 @@ function parseCheckpointDetails(value) {
249
372
  } catch {
250
373
  return void 0;
251
374
  }
252
- return structuredClone(value);
375
+ return {
376
+ kind: CHECKPOINT_KIND,
377
+ version: CHECKPOINT_VERSION,
378
+ checkpointId: value.checkpointId,
379
+ provider: value.provider,
380
+ api,
381
+ profile,
382
+ modelId: value.modelId,
383
+ protocol: isVersionOne ? "remote-v2" : value.protocol,
384
+ replacementHistory: structuredClone(value.replacementHistory),
385
+ keptMessageFingerprints: [...value.keptMessageFingerprints],
386
+ createdAt: value.createdAt
387
+ };
253
388
  }
254
389
  function latestCheckpoint(entries) {
255
390
  for (let index = entries.length - 1; index >= 0; index--) {
@@ -324,8 +459,7 @@ function buildReplacementHistory(input, compactionItem, options = {}) {
324
459
  const opaque = validateCompactionItem(compactionItem);
325
460
  let remainingBytes = byteBudget - serializedBytes(opaque);
326
461
  let remainingChars = tokenBudget * 4;
327
- if (remainingBytes <= 0)
328
- throw new Error("Opaque compaction item exceeds replacement history budget");
462
+ if (remainingBytes <= 0) throw new Error("Opaque compaction item exceeds replacement history budget");
329
463
  const retainedNewestFirst = [];
330
464
  const candidates = input.filter(
331
465
  (item) => isObject2(item) && item.role === "user" && item.type !== "compaction_trigger"
@@ -362,9 +496,10 @@ function createCheckpointDetails(input) {
362
496
  version: CHECKPOINT_VERSION,
363
497
  checkpointId: input.checkpointId ?? randomUUID(),
364
498
  provider: input.provider,
365
- api: "openai-codex-responses",
499
+ api: input.api,
500
+ profile: input.profile,
366
501
  modelId: input.modelId,
367
- protocol: "remote-compaction-v2",
502
+ protocol: input.protocol,
368
503
  replacementHistory: structuredClone(input.replacementHistory),
369
504
  keptMessageFingerprints: input.keptMessages.map(fingerprintMessage),
370
505
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -374,22 +509,280 @@ function createCheckpointDetails(input) {
374
509
  return parsed;
375
510
  }
376
511
 
377
- // src/remote.ts
378
- var EMPTY_USAGE = {
379
- input: 0,
380
- output: 0,
381
- cacheRead: 0,
382
- cacheWrite: 0,
383
- totalTokens: 0,
384
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
385
- };
386
- function isObject3(value) {
512
+ // src/remote-types.ts
513
+ function isJsonObject(value) {
387
514
  return typeof value === "object" && value !== null && !Array.isArray(value);
388
515
  }
389
516
  function abortError() {
390
517
  return new DOMException("Compaction aborted", "AbortError");
391
518
  }
392
- async function requestRemoteCompaction(request) {
519
+ function assertPreparedInput(payload) {
520
+ if (!Array.isArray(payload.input) || !payload.input.every(isJsonObject)) {
521
+ throw new Error("Prepared compaction payload has invalid input items");
522
+ }
523
+ return structuredClone(payload.input);
524
+ }
525
+
526
+ // src/remote-shared.ts
527
+ async function collectProviderUsage(stream, signal) {
528
+ let usage;
529
+ for await (const event of stream) {
530
+ if (signal.aborted) throw abortError();
531
+ if (event.type === "error") {
532
+ throw new Error(event.error.errorMessage ?? "Responses compaction request failed");
533
+ }
534
+ if (event.type === "done") usage = event.message.usage;
535
+ }
536
+ if (signal.aborted) throw abortError();
537
+ if (!usage) throw new Error("Responses provider stream ended without completion usage");
538
+ return usage;
539
+ }
540
+
541
+ // src/remote-compact.ts
542
+ var OFFICIAL_COMPACT_FIELDS = [
543
+ "model",
544
+ "input",
545
+ "instructions",
546
+ "previous_response_id",
547
+ "prompt_cache_key",
548
+ "prompt_cache_retention",
549
+ "service_tier"
550
+ ];
551
+ var CODEX_COMPACT_FIELDS = [
552
+ "model",
553
+ "input",
554
+ "instructions",
555
+ "tools",
556
+ "parallel_tool_calls",
557
+ "reasoning",
558
+ "service_tier",
559
+ "prompt_cache_key",
560
+ "text",
561
+ "access_programs"
562
+ ];
563
+ function compactPayload(payload, profile) {
564
+ const fields = profile === "codex-responses-v1" ? CODEX_COMPACT_FIELDS : OFFICIAL_COMPACT_FIELDS;
565
+ const result = {};
566
+ for (const field of fields) {
567
+ if (Object.hasOwn(payload, field) && payload[field] !== void 0) {
568
+ result[field] = structuredClone(payload[field]);
569
+ }
570
+ }
571
+ if (typeof result.model !== "string" || result.model.length === 0) {
572
+ throw new CodexCompactionProtocolError("Responses payload is missing a model");
573
+ }
574
+ assertPreparedInput(result);
575
+ return result;
576
+ }
577
+ function requestUrl(input) {
578
+ return new URL(input instanceof Request ? input.url : String(input));
579
+ }
580
+ function responsesCompactUrl(input) {
581
+ const original = requestUrl(input);
582
+ if (!original.pathname.endsWith("/responses")) {
583
+ throw new CodexCompactionProtocolError("Provider request URL does not end with the Responses endpoint");
584
+ }
585
+ const compact = new URL(original);
586
+ compact.pathname = `${compact.pathname}/compact`;
587
+ if (compact.origin !== original.origin) {
588
+ throw new CodexCompactionProtocolError("Responses Compact URL changed origin");
589
+ }
590
+ return compact;
591
+ }
592
+ function mergedHeaders(input, init) {
593
+ const headers = new Headers(input instanceof Request ? input.headers : void 0);
594
+ new Headers(init?.headers).forEach((value, name) => {
595
+ headers.set(name, value);
596
+ });
597
+ headers.delete("content-encoding");
598
+ headers.delete("content-length");
599
+ headers.set("accept", "application/json");
600
+ headers.set("content-type", "application/json");
601
+ return headers;
602
+ }
603
+ function mergedSignal(input, init, ownerSignal) {
604
+ const signals = [ownerSignal];
605
+ if (input instanceof Request) signals.push(input.signal);
606
+ if (init?.signal) signals.push(init.signal);
607
+ return signals.length === 1 ? ownerSignal : AbortSignal.any(signals);
608
+ }
609
+ function nonRetryableBridgeFailure(error) {
610
+ const message = error instanceof Error ? error.message : String(error);
611
+ return Response.json(
612
+ {
613
+ error: {
614
+ message,
615
+ type: "invalid_request_error",
616
+ code: "invalid_compact_response"
617
+ }
618
+ },
619
+ { status: 400 }
620
+ );
621
+ }
622
+ function nonNegativeInteger(value) {
623
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
624
+ }
625
+ function optionalUsageDetail(details, field) {
626
+ if (details === void 0 || details === null) return 0;
627
+ if (!isJsonObject(details)) {
628
+ throw new CodexCompactionProtocolError("Responses Compact response has invalid usage details");
629
+ }
630
+ const value = details[field];
631
+ if (value === void 0 || value === null) return 0;
632
+ if (!nonNegativeInteger(value)) {
633
+ throw new CodexCompactionProtocolError(`Responses Compact response has invalid usage detail ${field}`);
634
+ }
635
+ return value;
636
+ }
637
+ function validatedUsage(response) {
638
+ const usage = response.usage;
639
+ if (!isJsonObject(usage)) {
640
+ throw new CodexCompactionProtocolError("Responses Compact response is missing usage");
641
+ }
642
+ for (const field of ["input_tokens", "output_tokens", "total_tokens"]) {
643
+ if (!nonNegativeInteger(usage[field])) {
644
+ throw new CodexCompactionProtocolError(`Responses Compact response has invalid ${field}`);
645
+ }
646
+ }
647
+ const inputTokens = usage.input_tokens;
648
+ const outputTokens = usage.output_tokens;
649
+ const totalTokens = usage.total_tokens;
650
+ const cachedTokens = optionalUsageDetail(usage.input_tokens_details, "cached_tokens");
651
+ const cacheWriteTokens = optionalUsageDetail(usage.input_tokens_details, "cache_write_tokens");
652
+ const reasoningTokens = optionalUsageDetail(usage.output_tokens_details, "reasoning_tokens");
653
+ if (cachedTokens + cacheWriteTokens > inputTokens || reasoningTokens > outputTokens) {
654
+ throw new CodexCompactionProtocolError("Responses Compact response has inconsistent usage details");
655
+ }
656
+ if (totalTokens !== inputTokens + outputTokens) {
657
+ throw new CodexCompactionProtocolError("Responses Compact response has inconsistent total usage");
658
+ }
659
+ return structuredClone(usage);
660
+ }
661
+ function syntheticCompletion(result, payload) {
662
+ const completed = {
663
+ id: typeof result.response.id === "string" ? result.response.id : "resp_pi_compact_bridge",
664
+ object: "response",
665
+ created_at: typeof result.response.created_at === "number" ? result.response.created_at : Math.floor(Date.now() / 1e3),
666
+ status: "completed",
667
+ model: payload.model,
668
+ output: [],
669
+ parallel_tool_calls: false,
670
+ tool_choice: "auto",
671
+ tools: [],
672
+ usage: validatedUsage(result.response)
673
+ };
674
+ const events = [
675
+ { type: "response.created", response: { ...completed, status: "in_progress" } },
676
+ { type: "response.completed", response: completed }
677
+ ];
678
+ return new Response(events.map((event) => `data: ${JSON.stringify(event)}
679
+
680
+ `).join(""), {
681
+ status: 200,
682
+ headers: { "content-type": "text/event-stream" }
683
+ });
684
+ }
685
+ async function requestResponsesCompact(request) {
686
+ if (request.signal.aborted) throw abortError();
687
+ let preparedPayload;
688
+ let sentInput;
689
+ let compactResult;
690
+ let bridgeError;
691
+ let dispatchInFlight = false;
692
+ let successfulResponses = 0;
693
+ const baseFetch = request.fetch ?? globalThis.fetch;
694
+ const bridgeFetch = async (input, init) => {
695
+ if (request.signal.aborted) throw abortError();
696
+ if (successfulResponses > 0) {
697
+ bridgeError = new CodexCompactionProtocolError("Provider dispatched again after Responses Compact succeeded");
698
+ return nonRetryableBridgeFailure(bridgeError);
699
+ }
700
+ if (dispatchInFlight) {
701
+ bridgeError = new CodexCompactionProtocolError("Provider dispatched overlapping Responses Compact requests");
702
+ return nonRetryableBridgeFailure(bridgeError);
703
+ }
704
+ if (!preparedPayload) {
705
+ bridgeError = new CodexCompactionProtocolError("Provider dispatched before exposing its request payload");
706
+ return nonRetryableBridgeFailure(bridgeError);
707
+ }
708
+ let compactUrl;
709
+ try {
710
+ compactUrl = responsesCompactUrl(input);
711
+ } catch (error) {
712
+ bridgeError = error;
713
+ return nonRetryableBridgeFailure(error);
714
+ }
715
+ const signal = mergedSignal(input, init, request.signal);
716
+ dispatchInFlight = true;
717
+ try {
718
+ const response = await baseFetch(compactUrl, {
719
+ ...init,
720
+ method: "POST",
721
+ headers: mergedHeaders(input, init),
722
+ body: JSON.stringify(preparedPayload),
723
+ signal
724
+ });
725
+ if (!response.ok) return response;
726
+ try {
727
+ const result = await collectCompactResponse(response, { signal });
728
+ successfulResponses += 1;
729
+ if (successfulResponses !== 1) {
730
+ throw new CodexCompactionProtocolError(
731
+ "Provider returned more than one successful Responses Compact response"
732
+ );
733
+ }
734
+ compactResult = result;
735
+ return syntheticCompletion(result, preparedPayload);
736
+ } catch (error) {
737
+ bridgeError = error;
738
+ return nonRetryableBridgeFailure(error);
739
+ }
740
+ } finally {
741
+ dispatchInFlight = false;
742
+ }
743
+ };
744
+ const stream = request.provider.stream(request.model, request.context, {
745
+ apiKey: request.apiKey,
746
+ headers: request.headers,
747
+ env: request.env,
748
+ signal: request.signal,
749
+ transport: "sse",
750
+ cacheRetention: "none",
751
+ timeoutMs: request.requestTimeoutMs ?? 5 * 60 * 1e3,
752
+ maxRetries: request.maxRetries ?? 2,
753
+ fetch: bridgeFetch,
754
+ onPayload: (payload) => {
755
+ if (preparedPayload) {
756
+ throw new CodexCompactionProtocolError("Provider exposed more than one compaction request payload");
757
+ }
758
+ const expanded = expandRemoteCompactionPayload(payload, request.priorCheckpoint);
759
+ preparedPayload = compactPayload(expanded, request.profile);
760
+ sentInput = assertPreparedInput(preparedPayload);
761
+ return expanded;
762
+ }
763
+ });
764
+ let usage;
765
+ try {
766
+ usage = await collectProviderUsage(stream, request.signal);
767
+ } catch (error) {
768
+ if (bridgeError) throw bridgeError;
769
+ throw error;
770
+ }
771
+ if (request.signal.aborted) throw abortError();
772
+ if (bridgeError) throw bridgeError;
773
+ if (!preparedPayload || !sentInput || !compactResult || successfulResponses !== 1) {
774
+ throw new CodexCompactionProtocolError("Provider did not complete exactly one Responses Compact request");
775
+ }
776
+ return {
777
+ item: compactResult.item,
778
+ promptInput: sentInput,
779
+ compactedOutput: compactResult.output,
780
+ usage
781
+ };
782
+ }
783
+
784
+ // src/remote-v2.ts
785
+ async function requestRemoteCompactionV2(request) {
393
786
  if (request.signal.aborted) throw abortError();
394
787
  let sentInput;
395
788
  const inspections = [];
@@ -421,35 +814,30 @@ async function requestRemoteCompaction(request) {
421
814
  fetch: inspectedFetch,
422
815
  onPayload: (payload) => {
423
816
  const prepared = prepareRemoteCompactionPayload(payload, request.priorCheckpoint);
424
- if (!Array.isArray(prepared.input) || !prepared.input.every(isObject3)) {
425
- throw new CodexCompactionProtocolError(
426
- "Prepared compaction payload has invalid input items"
427
- );
428
- }
429
- sentInput = structuredClone(prepared.input.slice(0, -1));
817
+ sentInput = assertPreparedInput(prepared).slice(0, -1);
430
818
  return prepared;
431
819
  }
432
820
  });
433
- let usage = EMPTY_USAGE;
434
- for await (const event of stream) {
435
- if (request.signal.aborted) throw abortError();
436
- if (event.type === "error") {
437
- throw new Error(event.error.errorMessage ?? "Codex remote compaction request failed");
438
- }
439
- if (event.type === "done") usage = event.message.usage;
440
- }
441
- if (request.signal.aborted) throw abortError();
442
- if (!sentInput)
821
+ const usage = await collectProviderUsage(stream, request.signal);
822
+ if (!sentInput) {
443
823
  throw new CodexCompactionProtocolError("Provider did not expose a request payload");
444
- if (inspections.length === 0) {
445
- throw new CodexCompactionProtocolError("Provider response did not expose an SSE body");
446
824
  }
447
- const inspection = await inspections.at(-1);
825
+ if (inspections.length !== 1) {
826
+ throw new CodexCompactionProtocolError(
827
+ `Provider exposed ${inspections.length} successful SSE responses; expected exactly one`
828
+ );
829
+ }
830
+ const inspection = await inspections[0];
448
831
  if (request.signal.aborted) throw abortError();
449
- if (!inspection?.ok) throw inspection?.error ?? new Error("Remote compaction inspection failed");
832
+ if (!inspection.ok) throw inspection.error;
450
833
  return { item: inspection.value.item, promptInput: sentInput, usage };
451
834
  }
452
835
 
836
+ // src/remote.ts
837
+ function requestRemoteCompaction(request) {
838
+ return request.protocol === "responses-compact" ? requestResponsesCompact(request) : requestRemoteCompactionV2(request);
839
+ }
840
+
453
841
  // src/settings.ts
454
842
  import { randomUUID as randomUUID2 } from "node:crypto";
455
843
  import { constants } from "node:fs";
@@ -460,6 +848,8 @@ var CODEX_COMPACT_SETTINGS_FILE = "pi-codex-compact.json";
460
848
  var MAX_SETTINGS_BYTES = 64 * 1024;
461
849
  var DEFAULT_CODEX_COMPACT_SETTINGS = Object.freeze({
462
850
  enabled: true,
851
+ protocol: "auto",
852
+ apiProfiles: {},
463
853
  requestTimeoutMs: 3e5,
464
854
  maxRetries: 2,
465
855
  replacementTokenBudget: 64e3,
@@ -470,18 +860,54 @@ var LIMITS = Object.freeze({
470
860
  maxRetries: { minimum: 0, maximum: 2 },
471
861
  replacementTokenBudget: { minimum: 8e3, maximum: 128e3 }
472
862
  });
863
+ var BUILT_IN_APIS = /* @__PURE__ */ new Set([
864
+ "openai-completions",
865
+ "mistral-conversations",
866
+ "openai-codex-responses",
867
+ "openai-responses",
868
+ "azure-openai-responses",
869
+ "anthropic-messages",
870
+ "bedrock-converse-stream",
871
+ "google-generative-ai",
872
+ "google-vertex",
873
+ "pi-messages"
874
+ ]);
875
+ var MAX_API_PROFILE_ID_LENGTH = 256;
473
876
  function isRecord(value) {
474
877
  return typeof value === "object" && value !== null && !Array.isArray(value);
475
878
  }
476
879
  function validInteger(value, minimum, maximum) {
477
880
  return typeof value === "number" && Number.isSafeInteger(value) && value >= minimum && value <= maximum;
478
881
  }
882
+ function hasWhitespaceOrControl(value) {
883
+ return [...value].some((character) => {
884
+ const code = character.codePointAt(0) ?? 0;
885
+ return code <= 32 || code === 127;
886
+ });
887
+ }
888
+ function normalizeApiProfiles(value) {
889
+ if (!isRecord(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
890
+ return void 0;
891
+ }
892
+ const profiles = {};
893
+ for (const [api, profile] of Object.entries(value)) {
894
+ if (api.length === 0 || api.length > MAX_API_PROFILE_ID_LENGTH || api.trim() !== api || hasWhitespaceOrControl(api) || BUILT_IN_APIS.has(api) || api === "__proto__" || api === "constructor" || api === "prototype" || profile !== "codex-responses-v1") {
895
+ return void 0;
896
+ }
897
+ profiles[api] = profile;
898
+ }
899
+ return profiles;
900
+ }
479
901
  function normalizeCodexCompactSettings(value) {
480
902
  if (!isRecord(value)) return void 0;
481
903
  if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") return void 0;
904
+ if (Object.hasOwn(value, "protocol") && value.protocol !== "auto" && value.protocol !== "remote-v2" && value.protocol !== "responses-compact") {
905
+ return void 0;
906
+ }
482
907
  if (Object.hasOwn(value, "notifyOnFallback") && typeof value.notifyOnFallback !== "boolean") {
483
908
  return void 0;
484
909
  }
910
+ if (Object.hasOwn(value, "apiProfiles") && normalizeApiProfiles(value.apiProfiles) === void 0) return void 0;
485
911
  for (const [field, limits] of Object.entries(LIMITS)) {
486
912
  if (Object.hasOwn(value, field) && !validInteger(value[field], limits.minimum, limits.maximum)) {
487
913
  return void 0;
@@ -489,6 +915,8 @@ function normalizeCodexCompactSettings(value) {
489
915
  }
490
916
  return {
491
917
  enabled: typeof value.enabled === "boolean" ? value.enabled : DEFAULT_CODEX_COMPACT_SETTINGS.enabled,
918
+ protocol: value.protocol === "remote-v2" || value.protocol === "responses-compact" ? value.protocol : DEFAULT_CODEX_COMPACT_SETTINGS.protocol,
919
+ apiProfiles: normalizeApiProfiles(value.apiProfiles) ?? structuredClone(DEFAULT_CODEX_COMPACT_SETTINGS.apiProfiles),
492
920
  requestTimeoutMs: typeof value.requestTimeoutMs === "number" ? value.requestTimeoutMs : DEFAULT_CODEX_COMPACT_SETTINGS.requestTimeoutMs,
493
921
  maxRetries: typeof value.maxRetries === "number" ? value.maxRetries : DEFAULT_CODEX_COMPACT_SETTINGS.maxRetries,
494
922
  replacementTokenBudget: typeof value.replacementTokenBudget === "number" ? value.replacementTokenBudget : DEFAULT_CODEX_COMPACT_SETTINGS.replacementTokenBudget,
@@ -541,9 +969,7 @@ async function loadCodexCompactSettings(path = codexCompactSettingsPath(), signa
541
969
  async function savePatch(path, patch, signal) {
542
970
  const latest = await loadCodexCompactSettings(path, signal);
543
971
  if (latest.kind === "invalid") {
544
- throw new Error(
545
- "Cannot overwrite an invalid pi-codex-compact.json; repair it and reload first"
546
- );
972
+ throw new Error("Cannot overwrite an invalid pi-codex-compact.json; repair it and reload first");
547
973
  }
548
974
  const document = { ...latest.document, ...patch };
549
975
  const settings = normalizeCodexCompactSettings(document);
@@ -607,35 +1033,39 @@ var STATUS_KEY = "codex-compact";
607
1033
  function activeCheckpoint(ctx) {
608
1034
  return latestCheckpoint(ctx.sessionManager.getBranch());
609
1035
  }
610
- function isCheckpointCompatible(details, model) {
611
- return usesCodexResponsesApi(model) && model.id === details.modelId;
1036
+ function isCheckpointCompatible(details, model, settings) {
1037
+ const route = resolveCompactionRoute(model, settings);
1038
+ return route.kind === "remote" && model !== void 0 && route.api === details.api && route.profile === details.profile && model.id === details.modelId;
612
1039
  }
613
1040
  function keptMessages(event) {
614
1041
  const leafId = event.branchEntries.at(-1)?.id ?? null;
615
1042
  const contextEntries = buildContextEntries(event.branchEntries, leafId);
616
- const keptIndex = contextEntries.findIndex(
617
- (entry) => entry.id === event.preparation.firstKeptEntryId
618
- );
1043
+ const keptIndex = contextEntries.findIndex((entry) => entry.id === event.preparation.firstKeptEntryId);
619
1044
  if (keptIndex < 0) {
620
1045
  throw new Error("Pi compaction cut point is not present in the active context");
621
1046
  }
622
1047
  return contextEntries.slice(keptIndex).flatMap(sessionEntryToContextMessages);
623
1048
  }
624
1049
  function activeTools(pi) {
625
- const enabled = new Set(pi.getActiveTools());
626
- return pi.getAllTools().filter((tool) => enabled.has(tool.name)).map((tool) => ({
627
- name: tool.name,
628
- description: tool.description,
629
- parameters: tool.parameters
630
- }));
631
- }
632
- function projectedCurrentMessages(event, model) {
1050
+ const available = new Map(pi.getAllTools().map((tool) => [tool.name, tool]));
1051
+ return pi.getActiveTools().flatMap((name) => {
1052
+ const tool = available.get(name);
1053
+ return tool ? [
1054
+ {
1055
+ name: tool.name,
1056
+ description: tool.description,
1057
+ parameters: tool.parameters
1058
+ }
1059
+ ] : [];
1060
+ });
1061
+ }
1062
+ function projectedCurrentMessages(event, model, route) {
633
1063
  const leafId = event.branchEntries.at(-1)?.id ?? null;
634
1064
  const session = buildSessionContext(event.branchEntries, leafId);
635
1065
  const prior = latestCheckpoint(event.branchEntries);
636
1066
  if (!prior) return { messages: session.messages };
637
- if (prior.details.modelId !== model.id) {
638
- throw new Error("The active opaque checkpoint belongs to a different Codex model");
1067
+ if (prior.details.api !== route.api || prior.details.profile !== route.profile || prior.details.modelId !== model.id) {
1068
+ throw new Error("The active opaque checkpoint belongs to a different Responses model");
639
1069
  }
640
1070
  const projected = projectCheckpointContext(session.messages, prior.details, prior.entry.summary);
641
1071
  if (!projected) {
@@ -645,24 +1075,27 @@ function projectedCurrentMessages(event, model) {
645
1075
  }
646
1076
  function notifyFailure(ctx, error, settings) {
647
1077
  if (!ctx.hasUI || !settings.notifyOnFallback) return;
648
- const message = error instanceof Error ? error.message : String(error);
649
- ctx.ui.notify(`Codex remote compaction failed; using Pi compaction. ${message}`, "warning");
1078
+ const message = terminalText(error instanceof Error ? error.message : String(error));
1079
+ ctx.ui.notify(`Responses compaction failed; using Pi compaction. ${message}`, "warning");
650
1080
  }
651
1081
  function sessionStillOwned(ctx, sessionId, signal) {
652
1082
  return !signal.aborted && ctx.sessionManager.getSessionId() === sessionId;
653
1083
  }
654
- async function compactRemotely(pi, event, ctx, settings, fetch) {
1084
+ async function compactRemotely(pi, event, ctx, settings, ownerSignal, fetch) {
655
1085
  const model = ctx.model;
656
- if (!settings.enabled || !usesCodexResponsesApi(model)) return void 0;
1086
+ const route = resolveCompactionRoute(model, settings);
1087
+ if (route.kind === "native" || !model) return void 0;
1088
+ const signal = AbortSignal.any([event.signal, ownerSignal]);
1089
+ if (signal.aborted) return { cancel: true };
657
1090
  const sessionId = ctx.sessionManager.getSessionId();
658
- ctx.ui.setStatus(STATUS_KEY, "Codex remote compaction\u2026");
1091
+ ctx.ui.setStatus(STATUS_KEY, route.protocol === "remote-v2" ? "Responses Remote V2\u2026" : "Responses Compact API\u2026");
659
1092
  try {
660
1093
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
661
- if (!sessionStillOwned(ctx, sessionId, event.signal)) return { cancel: true };
1094
+ if (!sessionStillOwned(ctx, sessionId, signal)) return { cancel: true };
662
1095
  if (!auth.ok) throw new Error(auth.error);
663
1096
  const provider = ctx.modelRegistry.getProvider(model.provider);
664
- if (!provider) throw new Error("The active Codex Responses provider is unavailable");
665
- const current = projectedCurrentMessages(event, model);
1097
+ if (!provider) throw new Error("The active Responses provider is unavailable");
1098
+ const current = projectedCurrentMessages(event, model, route);
666
1099
  const context = {
667
1100
  systemPrompt: ctx.getSystemPrompt(),
668
1101
  messages: convertToLlm(current.messages),
@@ -672,10 +1105,12 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
672
1105
  provider,
673
1106
  model,
674
1107
  context,
1108
+ protocol: route.protocol,
1109
+ profile: route.profile,
675
1110
  apiKey: auth.apiKey,
676
1111
  headers: auth.headers,
677
1112
  env: auth.env,
678
- signal: event.signal,
1113
+ signal,
679
1114
  priorCheckpoint: current.prior ? {
680
1115
  marker: checkpointMarker(current.prior.checkpointId),
681
1116
  replacementHistory: current.prior.replacementHistory
@@ -684,13 +1119,18 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
684
1119
  maxRetries: settings.maxRetries,
685
1120
  fetch
686
1121
  });
687
- if (!sessionStillOwned(ctx, sessionId, event.signal)) return { cancel: true };
688
- const replacementHistory = buildReplacementHistory(response.promptInput, response.item, {
689
- tokenBudget: settings.replacementTokenBudget
690
- });
1122
+ if (!sessionStillOwned(ctx, sessionId, signal)) return { cancel: true };
1123
+ const replacementHistory = buildReplacementHistory(
1124
+ response.compactedOutput?.slice(0, -1) ?? response.promptInput,
1125
+ response.item,
1126
+ { tokenBudget: settings.replacementTokenBudget }
1127
+ );
691
1128
  const details = createCheckpointDetails({
692
1129
  provider: model.provider,
1130
+ api: route.api,
1131
+ profile: route.profile,
693
1132
  modelId: model.id,
1133
+ protocol: route.protocol,
694
1134
  replacementHistory,
695
1135
  keptMessages: keptMessages(event)
696
1136
  });
@@ -704,7 +1144,7 @@ async function compactRemotely(pi, event, ctx, settings, fetch) {
704
1144
  }
705
1145
  };
706
1146
  } catch (error) {
707
- if (event.signal.aborted || ctx.sessionManager.getSessionId() !== sessionId) {
1147
+ if (signal.aborted || ctx.sessionManager.getSessionId() !== sessionId) {
708
1148
  return { cancel: true };
709
1149
  }
710
1150
  notifyFailure(ctx, error, settings);
@@ -720,11 +1160,12 @@ function createCodexCompactExtension(options = {}) {
720
1160
  let sessionController = new AbortController();
721
1161
  let generation = 0;
722
1162
  pi.registerCommand("codex-compact", {
723
- description: "Compact now or configure Codex Remote Compaction V2",
724
- handler: async (_args, ctx) => {
1163
+ description: "Compact now or configure Responses compaction",
1164
+ handler: async (args, ctx) => {
1165
+ if (args.trim()) throw new Error("Usage: /codex-compact");
725
1166
  const ownerGeneration = generation;
726
1167
  const controller = sessionController;
727
- const { showCodexCompactMenu } = await import("./chunks/settings-menu-PEDLURUH.js");
1168
+ const { showCodexCompactMenu } = await import("./chunks/settings-menu-TVWWDPBF.js");
728
1169
  if (ownerGeneration !== generation || controller.signal.aborted) return;
729
1170
  await showCodexCompactMenu(settingsRuntime, ctx, {
730
1171
  signal: controller.signal,
@@ -746,7 +1187,7 @@ function createCodexCompactExtension(options = {}) {
746
1187
  if (sessionController.signal.aborted || ownerGeneration !== generation) return;
747
1188
  if (ctx.hasUI) {
748
1189
  ctx.ui.notify(
749
- `Could not load pi-codex-compact.json; using defaults. ${error instanceof Error ? error.message : String(error)}`,
1190
+ `Could not load pi-codex-compact.json; using defaults. ${terminalText(error instanceof Error ? error.message : String(error))}`,
750
1191
  "warning"
751
1192
  );
752
1193
  }
@@ -757,44 +1198,43 @@ function createCodexCompactExtension(options = {}) {
757
1198
  }
758
1199
  if (ctx.hasUI && state.kind === "invalid") {
759
1200
  ctx.ui.notify(
760
- `Invalid pi-codex-compact.json; using defaults without overwriting it. ${state.issue}`,
1201
+ `Invalid pi-codex-compact.json; using defaults without overwriting it. ${terminalText(state.issue ?? "unknown validation error")}`,
761
1202
  "warning"
762
1203
  );
763
1204
  }
764
1205
  });
765
1206
  pi.on(
766
1207
  "session_before_compact",
767
- (event, ctx) => compactRemotely(pi, event, ctx, settingsRuntime.get().settings, options.fetch)
1208
+ (event, ctx) => compactRemotely(pi, event, ctx, settingsRuntime.get().settings, sessionController.signal, options.fetch)
768
1209
  );
769
1210
  pi.on("context", (event, ctx) => {
770
1211
  if (!settingsRuntime.get().settings.enabled) return void 0;
1212
+ const settings = settingsRuntime.get().settings;
771
1213
  const checkpoint = activeCheckpoint(ctx);
772
- if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model)) return void 0;
773
- const messages = projectCheckpointContext(
774
- event.messages,
775
- checkpoint.details,
776
- checkpoint.entry.summary
777
- );
1214
+ if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model, settings)) return void 0;
1215
+ const messages = projectCheckpointContext(event.messages, checkpoint.details, checkpoint.entry.summary);
778
1216
  return messages ? { messages } : void 0;
779
1217
  });
780
1218
  pi.on("before_provider_request", (event, ctx) => {
781
1219
  if (!settingsRuntime.get().settings.enabled) return void 0;
1220
+ const settings = settingsRuntime.get().settings;
782
1221
  const checkpoint = activeCheckpoint(ctx);
783
- if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model)) return void 0;
1222
+ if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model, settings)) return void 0;
784
1223
  const marker = checkpointMarker(checkpoint.details.checkpointId);
785
1224
  if (!hasCheckpointMarker(event.payload, marker)) return void 0;
786
1225
  return rewriteCheckpointMarker(event.payload, marker, checkpoint.details.replacementHistory);
787
1226
  });
788
1227
  pi.on("model_select", (event, ctx) => {
789
1228
  if (!settingsRuntime.get().settings.enabled) return;
1229
+ const settings = settingsRuntime.get().settings;
790
1230
  const checkpoint = activeCheckpoint(ctx);
791
- if (!checkpoint || isCheckpointCompatible(checkpoint.details, event.model)) return;
1231
+ if (!checkpoint || isCheckpointCompatible(checkpoint.details, event.model, settings)) return;
792
1232
  const key = `${ctx.sessionManager.getSessionId()}:${event.model.provider}:${event.model.id}`;
793
1233
  if (providerWarnings.has(key)) return;
794
1234
  providerWarnings.add(key);
795
1235
  if (ctx.hasUI) {
796
1236
  ctx.ui.notify(
797
- "The active Codex checkpoint cannot replay on this model; Pi will expose only its fallback marker and retained recent messages.",
1237
+ "The active Responses checkpoint cannot replay on this model; Pi will expose only its fallback marker and retained recent messages.",
798
1238
  "warning"
799
1239
  );
800
1240
  }