codex-copilot-dx 0.6.10 → 0.6.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
4
4
  "description": "Use Codex Desktop, Claude Code, and Claude App with GitHub Copilot",
5
5
  "author": "Dale Xiao",
6
6
  "type": "module",
@@ -6,6 +6,7 @@ import {
6
6
  rememberResponseHistoryNode,
7
7
  responseHistoryRootId,
8
8
  } from "./response-history.mjs";
9
+ import { isResponsesToolOutputItem, readResponsesToolOutputParts } from "./responses-content.mjs";
9
10
  import { enforceResponsesImageLimit } from "./responses-image-limit.mjs";
10
11
  import { status } from "./status.mjs";
11
12
 
@@ -69,27 +70,39 @@ function isEncryptedContentPart(value) {
69
70
  return value && typeof value === "object" && value.type === "encrypted_content";
70
71
  }
71
72
 
73
+ const OMIT_ENCRYPTED_CONTENT = Symbol("omit-encrypted-content");
74
+ const ENCRYPTED_HISTORY_REBASE = Symbol("encrypted-history-rebase");
75
+ const ENCRYPTED_TOOL_OUTPUT_MARKER = "[CCDX: encrypted tool output omitted because upstream could not decrypt it.]";
76
+
72
77
  function stripEncryptedReasoningValue(value, state) {
78
+ if (isEncryptedContentPart(value)) {
79
+ state.changed = true;
80
+ return OMIT_ENCRYPTED_CONTENT;
81
+ }
82
+
73
83
  if (Array.isArray(value)) {
74
84
  const out = [];
75
85
  for (const item of value) {
76
- if (isEncryptedContentPart(item)) {
77
- state.changed = true;
78
- continue;
79
- }
80
- out.push(stripEncryptedReasoningValue(item, state));
86
+ const stripped = stripEncryptedReasoningValue(item, state);
87
+ if (stripped !== OMIT_ENCRYPTED_CONTENT) out.push(stripped);
81
88
  }
82
89
  return out;
83
90
  }
84
91
 
85
92
  if (value && typeof value === "object") {
93
+ const keys = Object.keys(value);
94
+ if (keys.length === 1 && keys[0] === "encrypted_content") {
95
+ state.changed = true;
96
+ return OMIT_ENCRYPTED_CONTENT;
97
+ }
86
98
  const out = {};
87
99
  for (const [key, child] of Object.entries(value)) {
88
100
  if (key === "encrypted_content") {
89
101
  state.changed = true;
90
102
  continue;
91
103
  }
92
- out[key] = stripEncryptedReasoningValue(child, state);
104
+ const stripped = stripEncryptedReasoningValue(child, state);
105
+ if (stripped !== OMIT_ENCRYPTED_CONTENT) out[key] = stripped;
93
106
  }
94
107
  return out;
95
108
  }
@@ -97,28 +110,82 @@ function stripEncryptedReasoningValue(value, state) {
97
110
  return value;
98
111
  }
99
112
 
113
+ function stripEncryptedReasoningInputValue(item, state) {
114
+ if (!isResponsesToolOutputItem(item)) {
115
+ const out = stripEncryptedReasoningValue(item, state);
116
+ if (item?.type === "message"
117
+ && Array.isArray(item.content)
118
+ && item.content.length > 0
119
+ && Array.isArray(out?.content)
120
+ && out.content.length === 0) {
121
+ return OMIT_ENCRYPTED_CONTENT;
122
+ }
123
+ return out;
124
+ }
125
+ const hasOutput = Object.prototype.hasOwnProperty.call(item, "output");
126
+ const withoutOutput = { ...item };
127
+ delete withoutOutput.output;
128
+ const out = stripEncryptedReasoningValue(withoutOutput, state);
129
+ if (!hasOutput) return out;
130
+ if (isEncryptedContentPart(item.output)) {
131
+ state.changed = true;
132
+ out.output = ENCRYPTED_TOOL_OUTPUT_MARKER;
133
+ return out;
134
+ }
135
+ out.output = typeof item.output === "string" ? item.output : cloneJson(item.output);
136
+ if (typeof item.output === "string" && !item.output.includes("encrypted_content")) return out;
137
+ const parsed = readResponsesToolOutputParts(out);
138
+ if (parsed) {
139
+ const outputState = { changed: false };
140
+ const parts = stripEncryptedReasoningValue(parsed.parts, outputState);
141
+ if (!outputState.changed) return out;
142
+ state.changed = true;
143
+ if (!parts.length) {
144
+ out.output = ENCRYPTED_TOOL_OUTPUT_MARKER;
145
+ return out;
146
+ }
147
+ parsed.parts.splice(0, parsed.parts.length, ...parts);
148
+ parsed.commit?.();
149
+ return out;
150
+ }
151
+ const output = stripEncryptedReasoningValue(out.output, state);
152
+ out.output = output === OMIT_ENCRYPTED_CONTENT ? ENCRYPTED_TOOL_OUTPUT_MARKER : output;
153
+ return out;
154
+ }
155
+
100
156
  function isEncryptedReasoningInputItem(item) {
101
- return item && typeof item === "object"
102
- && (item.type === "reasoning"
103
- || item.type === "encrypted_content"
104
- || Object.prototype.hasOwnProperty.call(item, "encrypted_content"));
157
+ if (!item || typeof item !== "object") return false;
158
+ if (["reasoning", "encrypted_content", "compaction"].includes(item.type)) return true;
159
+ const keys = Object.keys(item);
160
+ return keys.length === 1 && keys[0] === "encrypted_content";
105
161
  }
106
162
 
107
163
  export function sanitizeEncryptedReasoningRequest(reqContext) {
108
164
  const state = { changed: false };
109
165
  let body = cloneJson(reqContext.body);
110
166
  let currentInputStart = reqContext.currentInputStart;
167
+ let historicalInputChanged = false;
111
168
  if (Array.isArray(body.input)) {
112
169
  const input = [];
113
170
  let retainedBeforeCurrent = 0;
114
171
  for (let index = 0; index < body.input.length; index += 1) {
115
172
  const item = body.input[index];
173
+ const historical = Number.isFinite(currentInputStart) && index < currentInputStart;
116
174
  if (isEncryptedReasoningInputItem(item)) {
117
175
  state.changed = true;
176
+ if (historical) historicalInputChanged = true;
118
177
  continue;
119
178
  }
120
- input.push(stripEncryptedReasoningValue(item, state));
121
- if (Number.isFinite(currentInputStart) && index < currentInputStart) retainedBeforeCurrent += 1;
179
+ const itemState = { changed: false };
180
+ const stripped = stripEncryptedReasoningInputValue(item, itemState);
181
+ if (stripped !== OMIT_ENCRYPTED_CONTENT) input.push(stripped);
182
+ if (itemState.changed) {
183
+ state.changed = true;
184
+ if (historical) historicalInputChanged = true;
185
+ }
186
+ if (stripped !== OMIT_ENCRYPTED_CONTENT
187
+ && Number.isFinite(currentInputStart)
188
+ && index < currentInputStart) retainedBeforeCurrent += 1;
122
189
  }
123
190
  body.input = input;
124
191
  if (Number.isFinite(currentInputStart)) currentInputStart = retainedBeforeCurrent;
@@ -127,12 +194,19 @@ export function sanitizeEncryptedReasoningRequest(reqContext) {
127
194
  }
128
195
  if (!state.changed) return null;
129
196
  const historyInputItems = Array.isArray(reqContext.historyInputItems)
130
- ? reqContext.historyInputItems
131
- .filter((item) => !isEncryptedReasoningInputItem(item))
132
- .map((item) => stripEncryptedReasoningValue(item, { changed: false }))
197
+ ? reqContext.historyInputItems.flatMap((item) => {
198
+ if (isEncryptedReasoningInputItem(item)) return [];
199
+ const stripped = stripEncryptedReasoningInputValue(item, { changed: false });
200
+ return stripped === OMIT_ENCRYPTED_CONTENT ? [] : [stripped];
201
+ })
133
202
  : reqContext.historyInputItems;
203
+ const shouldRebaseHistory = historicalInputChanged
204
+ && Number.isFinite(reqContext.currentInputStart)
205
+ && reqContext.historyParentId !== undefined
206
+ && reqContext.historyParentId !== null;
134
207
  return {
135
208
  ...reqContext,
209
+ ...(shouldRebaseHistory ? { [ENCRYPTED_HISTORY_REBASE]: true } : {}),
136
210
  body,
137
211
  currentInputStart,
138
212
  inputItems: Array.isArray(body.input) ? body.input : reqContext.inputItems,
@@ -140,12 +214,30 @@ export function sanitizeEncryptedReasoningRequest(reqContext) {
140
214
  };
141
215
  }
142
216
 
217
+ function finalizeEncryptedHistoryRebase(reqContext) {
218
+ if (!reqContext[ENCRYPTED_HISTORY_REBASE]) return reqContext;
219
+ const finalized = {
220
+ ...reqContext,
221
+ historyParentId: null,
222
+ historyRootId: null,
223
+ historyInputItems: reqContext.body.input,
224
+ inputItems: reqContext.body.input,
225
+ };
226
+ delete finalized[ENCRYPTED_HISTORY_REBASE];
227
+ return finalized;
228
+ }
229
+
143
230
  export function isEncryptedContentVerificationError(statusCode, text) {
144
231
  if (statusCode < 400 || !text) return false;
145
232
  const lower = String(text).toLowerCase();
146
- return lower.includes("encrypted content")
233
+ const reasoningFailure = lower.includes("encrypted content")
147
234
  && lower.includes("could not be verified")
148
235
  && (lower.includes("could not be decrypted") || lower.includes("could not be parsed"));
236
+ const functionOutputFailure = lower.includes("encrypted function output content")
237
+ && lower.includes("could not be decrypted or decoded");
238
+ const missingEncryptedContent = statusCode < 500
239
+ && /missing required parameter:\s*(['"]?)input\[\d+\](?:\.[a-z0-9_]+|\[\d+\])*\.encrypted_content\1(?=\.?(?:\s|$|["},\]]))/.test(lower);
240
+ return reasoningFailure || functionOutputFailure || missingEncryptedContent;
149
241
  }
150
242
 
151
243
  export function isImageNamespaceCollisionError(statusCode, text) {
@@ -192,7 +284,10 @@ export async function openCopilotResponse(reqContext, upstream = copilotResponse
192
284
  });
193
285
  options.assertPrepareActive?.();
194
286
  payloadPrepared = true;
195
- if (resp.ok) return { resp, reqContext };
287
+ if (resp.ok) {
288
+ reqContext = finalizeEncryptedHistoryRebase(reqContext);
289
+ return { resp, reqContext };
290
+ }
196
291
 
197
292
  const errorText = await resp.text();
198
293
  options.assertPrepareActive?.();
@@ -210,7 +305,7 @@ export async function openCopilotResponse(reqContext, upstream = copilotResponse
210
305
  if (retryContext) {
211
306
  encryptedRetried = true;
212
307
  reqContext = retryContext;
213
- console.warn(status("warn", "encrypted reasoning rejected by upstream; retrying without encrypted reasoning"));
308
+ console.warn(status("warn", "encrypted replay content rejected by upstream; retrying without unavailable encrypted content"));
214
309
  continue;
215
310
  }
216
311
  }