mixdog 0.9.33 → 0.9.34

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 (44) hide show
  1. package/package.json +4 -2
  2. package/scripts/compact-trigger-migration-smoke.mjs +37 -31
  3. package/scripts/provider-toolcall-test.mjs +535 -36
  4. package/src/agents/heavy-worker/AGENT.md +3 -4
  5. package/src/agents/worker/AGENT.md +2 -3
  6. package/src/repl.mjs +9 -1
  7. package/src/rules/shared/01-tool.md +4 -2
  8. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +22 -0
  9. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +21 -2
  10. package/src/runtime/agent/orchestrator/providers/gemini.mjs +2 -1
  11. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +15 -9
  12. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
  13. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +130 -18
  14. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +22 -12
  15. package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +131 -0
  16. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +102 -26
  17. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +194 -9
  18. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +13 -2
  19. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -1
  20. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -0
  21. package/src/runtime/agent/orchestrator/session/compact.mjs +3 -0
  22. package/src/runtime/agent/orchestrator/session/context-utils.mjs +39 -6
  23. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +15 -23
  24. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +28 -0
  25. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +5 -0
  26. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +25 -0
  27. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +16 -8
  28. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +50 -4
  29. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +53 -1
  30. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +12 -2
  31. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +270 -47
  32. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -2
  33. package/src/runtime/channels/lib/output-forwarder.mjs +7 -1
  34. package/src/runtime/channels/lib/owned-runtime.mjs +5 -2
  35. package/src/runtime/memory/lib/embedding-worker.mjs +18 -3
  36. package/src/runtime/memory/lib/memory-embed.mjs +89 -8
  37. package/src/session-runtime/context-status.mjs +7 -6
  38. package/src/session-runtime/runtime-core.mjs +0 -10
  39. package/src/tui/app/use-transcript-window.mjs +13 -1
  40. package/src/tui/components/StatusLine.jsx +5 -5
  41. package/src/tui/dist/index.mjs +5 -3
  42. package/src/ui/statusline.mjs +4 -10
  43. package/src/vendor/statusline/bin/statusline-route.mjs +4 -5
  44. package/src/vendor/statusline/src/gateway/route-meta.mjs +3 -2
@@ -12,11 +12,15 @@ import test from 'node:test';
12
12
  import assert from 'node:assert/strict';
13
13
 
14
14
  import {
15
+ OpenAICompatProvider,
15
16
  _toResponsesToolsForTest,
16
17
  _toXaiResponsesInputForTest,
17
18
  parseToolCalls as compatParseToolCalls,
18
19
  parseResponsesToolCalls as compatParseResponsesToolCalls,
19
20
  } from '../src/runtime/agent/orchestrator/providers/openai-compat.mjs';
21
+ import {
22
+ GrokOAuthProvider,
23
+ } from '../src/runtime/agent/orchestrator/providers/grok-oauth.mjs';
20
24
  import {
21
25
  consumeCompatResponsesStream,
22
26
  isInvalidToolArgsMarker,
@@ -26,14 +30,26 @@ import {
26
30
  } from '../src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs';
27
31
  import {
28
32
  _computeDelta,
33
+ _buildResponseCreateFrame,
34
+ _sansInput,
35
+ _stableStringify,
29
36
  } from '../src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs';
30
37
  import {
31
38
  _cacheObservationForTest,
32
39
  } from '../src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs';
40
+ import {
41
+ _withCodexWsClientMetadata,
42
+ } from '../src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs';
43
+ import {
44
+ _captureTurnStateFromEvent,
45
+ } from '../src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs';
33
46
  import {
34
47
  createGeminiTextLeakGuard,
35
48
  parseToolCalls as geminiParseToolCalls,
36
49
  } from '../src/runtime/agent/orchestrator/providers/gemini.mjs';
50
+ import {
51
+ _resolveGeminiCacheUsage,
52
+ } from '../src/runtime/agent/orchestrator/providers/gemini-cache.mjs';
37
53
  import { parseSSEStream as anthropicParseSSEStream } from '../src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
38
54
  import { _buildRequestBodyForCacheSmoke } from '../src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
39
55
  import {
@@ -294,6 +310,52 @@ test('gemini leak guard: unknown tool name → text flushed, no synthetic call',
294
310
  assert.ok(content.includes('nonexistent_tool'));
295
311
  });
296
312
 
313
+ test('gemini cache usage: official cached token fields are subsets of prompt tokens', () => {
314
+ const direct = _resolveGeminiCacheUsage({
315
+ usageMetadata: { promptTokenCount: 1000, cachedContentTokenCount: 400 },
316
+ });
317
+ assert.deepEqual({
318
+ inputTokens: direct.inputTokens,
319
+ reportedCachedTokens: direct.reportedCachedTokens,
320
+ cachedTokens: direct.cachedTokens,
321
+ cacheTokenSource: direct.cacheTokenSource,
322
+ }, {
323
+ inputTokens: 1000,
324
+ reportedCachedTokens: 400,
325
+ cachedTokens: 400,
326
+ cacheTokenSource: 'usage_metadata',
327
+ });
328
+
329
+ const sdkAlias = _resolveGeminiCacheUsage({
330
+ usageMetadata: { prompt_token_count: 1200, total_cached_tokens: 500 },
331
+ });
332
+ assert.equal(sdkAlias.inputTokens, 1200);
333
+ assert.equal(sdkAlias.reportedCachedTokens, 500);
334
+ assert.equal(sdkAlias.cachedTokens, 500);
335
+ });
336
+
337
+ test('gemini cache usage: clamps over-reported cache and falls back only for attached cachedContent', () => {
338
+ const clamped = _resolveGeminiCacheUsage({
339
+ usageMetadata: { promptTokenCount: 100, cachedContentTokenCount: 150 },
340
+ });
341
+ assert.equal(clamped.cachedTokens, 100);
342
+
343
+ const fallback = _resolveGeminiCacheUsage({
344
+ usageMetadata: { promptTokenCount: 1000 },
345
+ cachedContent: 'cachedContents/abc',
346
+ providerState: { gemini: { cacheTokenSize: 250 } },
347
+ });
348
+ assert.equal(fallback.cachedTokens, 250);
349
+ assert.equal(fallback.cacheTokenSource, 'cache_create_fallback');
350
+
351
+ const noFallbackWithoutAttachment = _resolveGeminiCacheUsage({
352
+ usageMetadata: { promptTokenCount: 1000 },
353
+ providerState: { gemini: { cacheTokenSize: 250 } },
354
+ });
355
+ assert.equal(noFallbackWithoutAttachment.cachedTokens, 0);
356
+ assert.equal(noFallbackWithoutAttachment.cacheTokenSource, 'none');
357
+ });
358
+
297
359
  // === 3. anthropic / anthropic-oauth ========================================
298
360
  // tool_use block parser lives in anthropic-oauth.mjs:936 parseSSEStream
299
361
  // (content_block_start/delta/stop → toolCalls.push). anthropic.mjs has NO
@@ -564,6 +626,19 @@ test('anthropic(-oauth) leak guard: benign prose <function> mention preserved, n
564
626
 
565
627
  import { customToolCallFromResponseItem } from '../src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs';
566
628
  import { parseToolSearchArgs } from '../src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs';
629
+ import { _warmupContinuityTraceForTest } from '../src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs';
630
+
631
+ test('openai-oauth-ws (warmup continuity): warmup id anchors first real; first-3 misses counted', () => {
632
+ const t = _warmupContinuityTraceForTest({
633
+ warmupUsed: true, warmupResponseId: 'resp_w', priorEntryResponseId: 'resp_w',
634
+ sentPrevResponseId: null, earlyCacheMisses: [false, 'warm_session_zero_cached_tokens', false, true],
635
+ });
636
+ assert.equal(t.warmup_chain_continuous, true);
637
+ assert.equal(t.warmup_first_real_prev_id, 'resp_w');
638
+ assert.deepEqual(t.early_cache_misses, [false, 'warm_session_zero_cached_tokens', false]);
639
+ assert.equal(t.early_cache_miss_count, 1);
640
+ assert.equal(_warmupContinuityTraceForTest({ warmupUsed: true, warmupResponseId: 'a', priorEntryResponseId: 'b' }).warmup_chain_continuous, false);
641
+ });
567
642
 
568
643
  test('openai-oauth (shared custom-tool-wire): custom_tool_call item → canonical call', () => {
569
644
  const item = { type: 'custom_tool_call', call_id: 'ctc_1', name: 'apply_patch', input: '*** patch ***' };
@@ -1061,32 +1136,26 @@ test('anthropic effort: explicit thinkingBudgetTokens wins over effort', () => {
1061
1136
  });
1062
1137
  // === 9. OpenAI OAuth WS cache tracing ======================================
1063
1138
 
1064
- test('openai oauth ws delta: default full frame is an expected mode, not a cache break', () => {
1065
- const previous = process.env.MIXDOG_OAI_WS_DELTA;
1066
- try {
1067
- delete process.env.MIXDOG_OAI_WS_DELTA;
1068
- const body = { model: 'gpt-5.5', input: [{ type: 'message', role: 'user', content: 'hi' }] };
1069
- const delta = _computeDelta({
1070
- entry: {
1071
- lastRequestSansInput: '{}',
1072
- lastResponseId: 'resp_prev',
1073
- lastRequestInput: body.input,
1074
- lastResponseItems: [],
1075
- },
1076
- body,
1077
- });
1078
- assert.equal(delta.mode, 'full');
1079
- assert.equal(delta.reason, 'full_default');
1080
- assert.equal(delta.frame.previous_response_id, undefined);
1081
- } finally {
1082
- if (previous == null) delete process.env.MIXDOG_OAI_WS_DELTA;
1083
- else process.env.MIXDOG_OAI_WS_DELTA = previous;
1084
- }
1139
+ test('openai oauth ws delta: default delta safely falls back on request-property mismatch', () => {
1140
+ const body = { model: 'gpt-5.5', input: [{ type: 'message', role: 'user', content: 'hi' }] };
1141
+ const delta = _computeDelta({
1142
+ entry: {
1143
+ lastRequestSansInput: '{}',
1144
+ lastResponseId: 'resp_prev',
1145
+ lastRequestInput: body.input,
1146
+ lastResponseItems: [],
1147
+ },
1148
+ body,
1149
+ });
1150
+ assert.equal(delta.mode, 'full');
1151
+ assert.equal(delta.reason, 'request_properties_changed');
1152
+ assert.equal(delta.frame.previous_response_id, undefined);
1085
1153
  });
1086
1154
 
1087
- test('openai oauth ws delta: opt-in still falls back without turn-state unless forced', () => {
1088
- const previous = process.env.MIXDOG_OAI_WS_DELTA;
1155
+ test('openai oauth ws delta: ws-delta mode uses previous_response_id without turn-state, keeps safe fallback', () => {
1156
+ const prevTransport = process.env.MIXDOG_OAI_TRANSPORT;
1089
1157
  try {
1158
+ process.env.MIXDOG_OAI_TRANSPORT = 'ws-delta';
1090
1159
  const body = {
1091
1160
  model: 'gpt-5.5',
1092
1161
  input: [
@@ -1099,24 +1168,124 @@ test('openai oauth ws delta: opt-in still falls back without turn-state unless f
1099
1168
  lastResponseId: 'resp_prev',
1100
1169
  lastRequestInput: [body.input[0]],
1101
1170
  lastResponseItems: [],
1171
+ // NOTE: no turnState — ws-delta (refs) mode must still emit a delta.
1102
1172
  };
1103
1173
 
1104
- process.env.MIXDOG_OAI_WS_DELTA = '1';
1105
- const safe = _computeDelta({ entry, body });
1106
- assert.equal(safe.mode, 'full');
1107
- assert.equal(safe.reason, 'delta_missing_turn_state');
1108
- assert.equal(safe.frame.previous_response_id, undefined);
1109
-
1110
- process.env.MIXDOG_OAI_WS_DELTA = 'force';
1111
- const forced = _computeDelta({ entry, body });
1112
- assert.equal(forced.mode, 'delta');
1113
- assert.equal(forced.frame.previous_response_id, 'resp_prev');
1114
- assert.deepEqual(forced.frame.input, [body.input[1]]);
1174
+ const refs = _computeDelta({ entry, body });
1175
+ assert.equal(refs.mode, 'delta');
1176
+ assert.equal(refs.frame.previous_response_id, 'resp_prev');
1177
+ assert.deepEqual(refs.frame.input, [body.input[1]]);
1178
+
1179
+ // Safe fallback preserved: a changed request property breaks the prefix
1180
+ // and retreats to a full frame even in ws-delta mode.
1181
+ const changed = _computeDelta({ entry, body: { ...body, model: 'gpt-5.6' } });
1182
+ assert.equal(changed.mode, 'full');
1183
+ assert.equal(changed.reason, 'request_properties_changed');
1184
+ assert.equal(changed.frame.previous_response_id, undefined);
1185
+
1186
+ const noAnchor = _computeDelta({ entry: { ...entry, lastResponseId: null }, body });
1187
+ assert.equal(noAnchor.mode, 'full');
1188
+ assert.equal(noAnchor.reason, 'no_anchor');
1189
+ assert.equal(noAnchor.frame.previous_response_id, undefined);
1190
+
1191
+ const prefixMismatch = _computeDelta({
1192
+ entry: { ...entry, lastRequestInput: [{ type: 'message', role: 'user', content: 'other' }] },
1193
+ body,
1194
+ });
1195
+ assert.equal(prefixMismatch.mode, 'full');
1196
+ assert.equal(prefixMismatch.reason, 'input_prefix_mismatch');
1197
+ assert.equal(prefixMismatch.frame.previous_response_id, undefined);
1198
+
1199
+ const responseMismatch = _computeDelta({
1200
+ entry: {
1201
+ ...entry,
1202
+ lastRequestInput: [body.input[0]],
1203
+ lastResponseItems: [{ type: 'function_call', call_id: 'call_1', name: 'tool', arguments: '{}' }],
1204
+ },
1205
+ body: {
1206
+ ...body,
1207
+ input: [
1208
+ body.input[0],
1209
+ { type: 'function_call', call_id: 'call_other', name: 'tool', arguments: '{}' },
1210
+ body.input[1],
1211
+ ],
1212
+ },
1213
+ });
1214
+ assert.equal(responseMismatch.mode, 'full');
1215
+ assert.equal(responseMismatch.reason, 'response_output_mismatch:function_call');
1216
+ assert.equal(responseMismatch.frame.previous_response_id, undefined);
1115
1217
  } finally {
1116
- if (previous == null) delete process.env.MIXDOG_OAI_WS_DELTA;
1117
- else process.env.MIXDOG_OAI_WS_DELTA = previous;
1218
+ if (prevTransport == null) delete process.env.MIXDOG_OAI_TRANSPORT;
1219
+ else process.env.MIXDOG_OAI_TRANSPORT = prevTransport;
1118
1220
  }
1119
1221
  });
1222
+
1223
+ test('openai oauth ws delta: warmup generate:false does not force request_properties_changed', () => {
1224
+ const prevTransport = process.env.MIXDOG_OAI_TRANSPORT;
1225
+ try {
1226
+ process.env.MIXDOG_OAI_TRANSPORT = 'ws-delta';
1227
+ const warmupBody = { model: 'gpt-5.5', generate: false, input: [{ type: 'message', role: 'user', content: 'prev' }] };
1228
+ const body = { model: 'gpt-5.5', input: [warmupBody.input[0], { type: 'message', role: 'user', content: 'next' }] };
1229
+ const entry = {
1230
+ lastRequestSansInput: _stableStringify(_sansInput(warmupBody)),
1231
+ lastResponseId: 'resp_warm',
1232
+ lastRequestInput: [warmupBody.input[0]],
1233
+ lastResponseItems: [],
1234
+ };
1235
+ assert.equal(_sansInput(warmupBody).generate, undefined); // warmup marker normalized out
1236
+ const delta = _computeDelta({ entry, body });
1237
+ assert.equal(delta.mode, 'delta');
1238
+ assert.deepEqual(delta.frame.input, [body.input[1]]);
1239
+ // Non-warmup generate difference still breaks the delta.
1240
+ const genEntry = { ...entry, lastRequestSansInput: _stableStringify(_sansInput({ ...warmupBody, generate: true })) };
1241
+ const genChanged = _computeDelta({ entry: genEntry, body });
1242
+ assert.equal(genChanged.mode, 'full');
1243
+ assert.equal(genChanged.reason, 'request_properties_changed');
1244
+ } finally {
1245
+ if (prevTransport == null) delete process.env.MIXDOG_OAI_TRANSPORT;
1246
+ else process.env.MIXDOG_OAI_TRANSPORT = prevTransport;
1247
+ }
1248
+ });
1249
+
1250
+ test('canonical frame: full-frame builder leads with type and preserves codex body key order', () => {
1251
+ const body = {
1252
+ model: 'gpt-5.5',
1253
+ instructions: 'sys',
1254
+ input: [{ type: 'message', role: 'user', content: 'hi' }],
1255
+ tool_choice: 'auto',
1256
+ parallel_tool_calls: true,
1257
+ reasoning: { effort: 'medium' },
1258
+ store: false,
1259
+ stream: true,
1260
+ include: ['reasoning.encrypted_content'],
1261
+ prompt_cache_key: 'k',
1262
+ text: { verbosity: 'low' },
1263
+ };
1264
+ const frame = _buildResponseCreateFrame(body);
1265
+ assert.deepEqual(Object.keys(frame), ['type', ...Object.keys(body)]);
1266
+ assert.equal(frame.type, 'response.create');
1267
+ // A full-frame build is byte-identical to the legacy spread form.
1268
+ assert.equal(JSON.stringify(frame), JSON.stringify({ type: 'response.create', ...body }));
1269
+ assert.equal(frame.previous_response_id, undefined);
1270
+ });
1271
+
1272
+ test('canonical frame: delta insert keeps key order, drops empty instructions, overrides input', () => {
1273
+ const body = {
1274
+ model: 'gpt-5.5',
1275
+ instructions: 'sys',
1276
+ input: [{ a: 1 }, { b: 2 }],
1277
+ tool_choice: 'auto',
1278
+ text: { verbosity: 'low' },
1279
+ };
1280
+ const delta = _buildResponseCreateFrame(body, { previousResponseId: 'resp_prev', inputOverride: [{ b: 2 }] });
1281
+ assert.deepEqual(Object.keys(delta), ['type', 'model', 'instructions', 'previous_response_id', 'input', 'tool_choice', 'text']);
1282
+ assert.equal(delta.previous_response_id, 'resp_prev');
1283
+ assert.deepEqual(delta.input, [{ b: 2 }]);
1284
+ // Empty instructions is dropped in delta mode (server resolves via prev id).
1285
+ const noInstr = _buildResponseCreateFrame({ ...body, instructions: '' }, { previousResponseId: 'resp_prev', inputOverride: [] });
1286
+ assert.deepEqual(Object.keys(noInstr), ['type', 'model', 'previous_response_id', 'input', 'tool_choice', 'text']);
1287
+ });
1288
+
1120
1289
  test('openai oauth ws cache observation detects warm zero and partial retreats', () => {
1121
1290
  const zero = _cacheObservationForTest({
1122
1291
  entry: { promptCacheMaxCachedTokens: 47_616 },
@@ -1141,3 +1310,333 @@ test('openai oauth ws cache observation detects warm zero and partial retreats',
1141
1310
  assert.equal(healthy.actualMiss, false);
1142
1311
  assert.equal(healthy.missReason, null);
1143
1312
  });
1313
+
1314
+ // === 10. OpenAI transport-policy switch (MIXDOG_OAI_TRANSPORT) ==============
1315
+ // One clean knob selects among ws-full | ws-delta | http-sse | auto. The
1316
+ // resolver is a pure function over an injected env, and the delta gate
1317
+ // (_computeDelta) + transport dispatch both read it, so these unit tests pin
1318
+ // the resolution and the delta branching without any network.
1319
+ import {
1320
+ resolveOpenAiTransportPolicy,
1321
+ _normalizeTransportMode,
1322
+ } from '../src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs';
1323
+ import {
1324
+ resolveResponsesTransportPolicy,
1325
+ RESPONSES_TRANSPORT_CAPABILITIES,
1326
+ _gateTransportMode,
1327
+ FULL_RESPONSES_TRANSPORT_CAPS,
1328
+ } from '../src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs';
1329
+
1330
+ test('transport policy: default (no env) is ws-delta / refs continuation ON', () => {
1331
+ const p = resolveOpenAiTransportPolicy({});
1332
+ assert.equal(p.mode, 'ws-delta');
1333
+ assert.equal(p.transport, 'ws');
1334
+ assert.equal(p.allowHttpFallback, false);
1335
+ assert.deepEqual(p.delta, { force: false, refs: true, optIn: true });
1336
+ });
1337
+
1338
+ test('transport policy: default ignores the legacy MIXDOG_OAI_WS_DELTA env', () => {
1339
+ // Legacy compatibility removed: delta is selected solely via ws-delta mode.
1340
+ assert.deepEqual(resolveOpenAiTransportPolicy({ MIXDOG_OAI_WS_DELTA: '1' }).delta, { force: false, refs: true, optIn: true });
1341
+ assert.deepEqual(resolveOpenAiTransportPolicy({ MIXDOG_OAI_WS_DELTA: 'force' }).delta, { force: false, refs: true, optIn: true });
1342
+ assert.deepEqual(resolveOpenAiTransportPolicy({ MIXDOG_OAI_WS_DELTA: 'refs' }).delta, { force: false, refs: true, optIn: true });
1343
+ });
1344
+
1345
+ test('transport policy: ws-full forces full frames', () => {
1346
+ const p = resolveOpenAiTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'ws-full' });
1347
+ assert.equal(p.transport, 'ws');
1348
+ assert.equal(p.allowHttpFallback, false);
1349
+ assert.equal(p.delta.optIn, false);
1350
+ });
1351
+
1352
+ test('transport policy: ws-delta selects refs-compatible delta (no turn-state demand)', () => {
1353
+ const p = resolveOpenAiTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'ws-delta' });
1354
+ assert.equal(p.transport, 'ws');
1355
+ assert.equal(p.allowHttpFallback, false);
1356
+ assert.deepEqual(p.delta, { force: false, refs: true, optIn: true });
1357
+ });
1358
+
1359
+ test('transport policy: http-sse forces the HTTP transport with delta OFF', () => {
1360
+ const p = resolveOpenAiTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'http-sse' });
1361
+ assert.equal(p.transport, 'http');
1362
+ assert.equal(p.allowHttpFallback, false);
1363
+ assert.equal(p.delta.optIn, false);
1364
+ });
1365
+
1366
+ test('transport policy: unknown MIXDOG_OAI_TRANSPORT falls back to default ws-delta', () => {
1367
+ const p = resolveOpenAiTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'quantum' });
1368
+ assert.equal(p.mode, 'ws-delta');
1369
+ assert.equal(p.transport, 'ws');
1370
+ });
1371
+
1372
+ test('response frame builder can omit transport-only stream/background for codex warmup parity', () => {
1373
+ const body = { model: 'gpt-5.5', input: [{ a: 1 }, { b: 2 }], stream: true, background: false, text: { verbosity: 'low' } };
1374
+ const full = _buildResponseCreateFrame(body, { omitTransportFields: true });
1375
+ assert.equal('stream' in full || 'background' in full, false);
1376
+ const d = _buildResponseCreateFrame(body, { previousResponseId: 'resp_prev', inputOverride: [{ b: 2 }], omitTransportFields: true });
1377
+ assert.deepEqual([d.previous_response_id, d.input, 'stream' in d], ['resp_prev', [{ b: 2 }], false]);
1378
+ });
1379
+
1380
+ test('transport policy: mode token aliases normalize', () => {
1381
+ assert.equal(_normalizeTransportMode('WS_FULL'), 'ws-full');
1382
+ assert.equal(_normalizeTransportMode(' ws delta '), 'ws-delta');
1383
+ assert.equal(_normalizeTransportMode('http/sse'), 'http-sse');
1384
+ assert.equal(_normalizeTransportMode('sse'), 'http-sse');
1385
+ assert.equal(_normalizeTransportMode('auto'), 'ws-delta');
1386
+ assert.equal(_normalizeTransportMode('official'), null);
1387
+ assert.equal(_normalizeTransportMode(''), null);
1388
+ assert.equal(_normalizeTransportMode('bogus'), null);
1389
+ });
1390
+
1391
+ test('transport policy: ws-delta drives _computeDelta to emit a delta frame', () => {
1392
+ const prevTransport = process.env.MIXDOG_OAI_TRANSPORT;
1393
+ try {
1394
+ process.env.MIXDOG_OAI_TRANSPORT = 'ws-delta';
1395
+ const body = {
1396
+ model: 'gpt-5.5',
1397
+ input: [
1398
+ { type: 'message', role: 'user', content: 'prev' },
1399
+ { type: 'message', role: 'user', content: 'next' },
1400
+ ],
1401
+ };
1402
+ const entry = {
1403
+ lastRequestSansInput: '{"model":"gpt-5.5"}',
1404
+ lastResponseId: 'resp_prev',
1405
+ lastRequestInput: [body.input[0]],
1406
+ lastResponseItems: [],
1407
+ // no turnState — refs mode must still delta
1408
+ };
1409
+ const delta = _computeDelta({ entry, body });
1410
+ assert.equal(delta.mode, 'delta');
1411
+ assert.equal(delta.frame.previous_response_id, 'resp_prev');
1412
+ assert.deepEqual(delta.frame.input, [body.input[1]]);
1413
+ } finally {
1414
+ if (prevTransport == null) delete process.env.MIXDOG_OAI_TRANSPORT;
1415
+ else process.env.MIXDOG_OAI_TRANSPORT = prevTransport;
1416
+ }
1417
+ });
1418
+
1419
+ test('transport policy: ws-full forces _computeDelta to full (delta OFF)', () => {
1420
+ const prevTransport = process.env.MIXDOG_OAI_TRANSPORT;
1421
+ try {
1422
+ process.env.MIXDOG_OAI_TRANSPORT = 'ws-full';
1423
+ const body = { model: 'gpt-5.5', input: [{ type: 'message', role: 'user', content: 'hi' }] };
1424
+ const delta = _computeDelta({
1425
+ entry: {
1426
+ lastRequestSansInput: '{"model":"gpt-5.5"}',
1427
+ lastResponseId: 'resp_prev',
1428
+ lastRequestInput: body.input,
1429
+ lastResponseItems: [],
1430
+ },
1431
+ body,
1432
+ });
1433
+ assert.equal(delta.mode, 'full');
1434
+ assert.equal(delta.reason, 'full_default');
1435
+ assert.equal(delta.frame.previous_response_id, undefined);
1436
+ } finally {
1437
+ if (prevTransport == null) delete process.env.MIXDOG_OAI_TRANSPORT;
1438
+ else process.env.MIXDOG_OAI_TRANSPORT = prevTransport;
1439
+ }
1440
+ });
1441
+
1442
+ // === 10b. Shared Responses transport policy (capability gating) ============
1443
+ // resolveResponsesTransportPolicy generalizes the OpenAI switch across every
1444
+ // Responses backend. Full-capability providers (OpenAI OAuth/direct) resolve
1445
+ // byte-identically to resolveOpenAiTransportPolicy; xAI/Grok also carry WS
1446
+ // delta capability, so default/ws-delta drive the OFFICIAL xAI continuation
1447
+ // (previous_response_id + incremental input) rather than collapsing to
1448
+ // 'ws-full'.
1449
+
1450
+ test('responses transport policy: full caps === legacy OpenAI resolver (byte-identical)', () => {
1451
+ for (const env of [
1452
+ {},
1453
+ { MIXDOG_OAI_TRANSPORT: 'ws-delta' },
1454
+ { MIXDOG_OAI_TRANSPORT: 'ws-full' },
1455
+ { MIXDOG_OAI_TRANSPORT: 'http-sse' },
1456
+ { MIXDOG_OAI_TRANSPORT: 'quantum' },
1457
+ ]) {
1458
+ const legacy = resolveOpenAiTransportPolicy(env);
1459
+ const shared = resolveResponsesTransportPolicy(env, RESPONSES_TRANSPORT_CAPABILITIES['openai-oauth']);
1460
+ assert.equal(shared.mode, legacy.mode);
1461
+ assert.equal(shared.transport, legacy.transport);
1462
+ assert.equal(shared.allowHttpFallback, legacy.allowHttpFallback);
1463
+ assert.deepEqual(shared.delta, legacy.delta);
1464
+ }
1465
+ });
1466
+
1467
+ test('responses transport policy: openai direct caps match oauth (both full)', () => {
1468
+ const env = { MIXDOG_OAI_TRANSPORT: 'ws-delta' };
1469
+ const direct = resolveResponsesTransportPolicy(env, RESPONSES_TRANSPORT_CAPABILITIES.openai);
1470
+ assert.equal(direct.transport, 'ws');
1471
+ assert.deepEqual(direct.delta, { force: false, refs: true, optIn: true });
1472
+ });
1473
+
1474
+ test('responses transport policy: xai auto defers (transport=auto, delta OFF, no http fallback)', () => {
1475
+ const p = resolveResponsesTransportPolicy({}, RESPONSES_TRANSPORT_CAPABILITIES.xai);
1476
+ assert.equal(p.mode, 'ws-delta');
1477
+ assert.equal(p.transport, 'ws');
1478
+ assert.equal(p.allowHttpFallback, false);
1479
+ assert.deepEqual(p.delta, { force: false, refs: true, optIn: true });
1480
+ });
1481
+
1482
+ test('responses transport policy: explicit auto is a ws-delta compatibility spelling', () => {
1483
+ const p = resolveResponsesTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'auto' }, RESPONSES_TRANSPORT_CAPABILITIES.xai);
1484
+ assert.equal(p.transport, 'ws');
1485
+ assert.deepEqual(p.delta, { force: false, refs: true, optIn: true });
1486
+ });
1487
+
1488
+ test('responses transport policy: xai ws-delta drives official continuation (refs delta ON)', () => {
1489
+ const p = resolveResponsesTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'ws-delta' }, RESPONSES_TRANSPORT_CAPABILITIES.xai);
1490
+ assert.equal(p.requestedMode, 'ws-delta');
1491
+ assert.equal(p.mode, 'ws-delta');
1492
+ assert.equal(p.transport, 'ws');
1493
+ assert.equal(p.allowHttpFallback, false);
1494
+ // Official xAI continuation: previous_response_id + incremental input, no
1495
+ // Codex turn-state — refs delta ON.
1496
+ assert.deepEqual(p.delta, { force: false, refs: true, optIn: true });
1497
+ });
1498
+
1499
+ test('responses transport policy: xai ws-full → WS, http-sse → HTTP', () => {
1500
+ const wsFull = resolveResponsesTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'ws-full' }, RESPONSES_TRANSPORT_CAPABILITIES.xai);
1501
+ assert.equal(wsFull.transport, 'ws');
1502
+ assert.equal(wsFull.allowHttpFallback, false);
1503
+ const http = resolveResponsesTransportPolicy({ MIXDOG_OAI_TRANSPORT: 'http-sse' }, RESPONSES_TRANSPORT_CAPABILITIES.xai);
1504
+ assert.equal(http.transport, 'http');
1505
+ assert.equal(http.allowHttpFallback, false);
1506
+ });
1507
+
1508
+ test('openai-compat/xai: provider-local HTTP pin beats global OAI ws-delta', async () => {
1509
+ const prevTransport = process.env.MIXDOG_OAI_TRANSPORT;
1510
+ try {
1511
+ process.env.MIXDOG_OAI_TRANSPORT = 'ws-delta';
1512
+ const provider = new OpenAICompatProvider('xai', {
1513
+ apiKey: 'xai-test',
1514
+ responsesTransport: 'http',
1515
+ });
1516
+ let httpCalled = false;
1517
+ provider._doSendXaiResponses = async () => {
1518
+ httpCalled = true;
1519
+ return { content: 'ok' };
1520
+ };
1521
+ provider._doSendXaiResponsesWebSocket = async () => {
1522
+ throw new Error('explicit HTTP transport should not use WS');
1523
+ };
1524
+ const result = await provider._doSend([{ role: 'user', content: 'hi' }], 'grok-build', [], {});
1525
+ assert.equal(result.content, 'ok');
1526
+ assert.equal(httpCalled, true);
1527
+ } finally {
1528
+ if (prevTransport == null) delete process.env.MIXDOG_OAI_TRANSPORT;
1529
+ else process.env.MIXDOG_OAI_TRANSPORT = prevTransport;
1530
+ }
1531
+ });
1532
+
1533
+ test('grok-oauth: all Grok models inherit global transport; explicit setting is the escape hatch', () => {
1534
+ const prevOaiTransport = process.env.MIXDOG_OAI_TRANSPORT;
1535
+ const prevResponsesTransport = process.env.MIXDOG_GROK_OAUTH_RESPONSES_TRANSPORT;
1536
+ const prevGrokTransport = process.env.MIXDOG_GROK_OAUTH_TRANSPORT;
1537
+ try {
1538
+ process.env.MIXDOG_OAI_TRANSPORT = 'ws-delta';
1539
+ delete process.env.MIXDOG_GROK_OAUTH_RESPONSES_TRANSPORT;
1540
+ delete process.env.MIXDOG_GROK_OAUTH_TRANSPORT;
1541
+ const provider = new GrokOAuthProvider({});
1542
+
1543
+ const apiInner = provider._ensureInner('tok', 'grok-build-0.1');
1544
+ assert.equal(apiInner.config.responsesTransport, undefined);
1545
+
1546
+ // No Grok-specific override: proxy-only grok-build now inherits the
1547
+ // shared switch too (WS→api.x.ai), no implicit HTTP pin.
1548
+ const proxyInner = provider._ensureInner('tok', 'grok-build');
1549
+ assert.equal(proxyInner.config.responsesTransport, undefined);
1550
+
1551
+ // Escape hatch: an explicit Grok-specific http setting pins proxy-only
1552
+ // models back onto the Grok CLI proxy over HTTP.
1553
+ process.env.MIXDOG_GROK_OAUTH_RESPONSES_TRANSPORT = 'http';
1554
+ const pinnedProvider = new GrokOAuthProvider({});
1555
+ const pinnedProxy = pinnedProvider._ensureInner('tok', 'grok-build');
1556
+ assert.equal(pinnedProxy.config.responsesTransport, 'http');
1557
+ const pinnedApi = pinnedProvider._ensureInner('tok2', 'grok-build-0.1');
1558
+ assert.equal(pinnedApi.config.responsesTransport, 'http');
1559
+ } finally {
1560
+ if (prevOaiTransport == null) delete process.env.MIXDOG_OAI_TRANSPORT;
1561
+ else process.env.MIXDOG_OAI_TRANSPORT = prevOaiTransport;
1562
+ if (prevResponsesTransport == null) delete process.env.MIXDOG_GROK_OAUTH_RESPONSES_TRANSPORT;
1563
+ else process.env.MIXDOG_GROK_OAUTH_RESPONSES_TRANSPORT = prevResponsesTransport;
1564
+ if (prevGrokTransport == null) delete process.env.MIXDOG_GROK_OAUTH_TRANSPORT;
1565
+ else process.env.MIXDOG_GROK_OAUTH_TRANSPORT = prevGrokTransport;
1566
+ }
1567
+ });
1568
+
1569
+ test('responses transport policy: _gateTransportMode down-shifts per capability', () => {
1570
+ // delta unsupported → ws-delta collapses to ws-full; others pass through.
1571
+ const noDelta = { ws: true, http: true, delta: false };
1572
+ assert.equal(_gateTransportMode('ws-delta', noDelta), 'ws-full');
1573
+ assert.equal(_gateTransportMode('ws-full', noDelta), 'ws-full');
1574
+ assert.equal(_gateTransportMode('http-sse', noDelta), 'http-sse');
1575
+ // WS unsupported → WS modes prefer HTTP.
1576
+ const httpOnly = { ws: false, http: true, delta: false };
1577
+ assert.equal(_gateTransportMode('ws-full', httpOnly), 'http-sse');
1578
+ assert.equal(_gateTransportMode('ws-delta', httpOnly), 'http-sse');
1579
+ // HTTP unsupported → http-sse prefers full-frame WS.
1580
+ const wsOnly = { ws: true, http: false, delta: true };
1581
+ assert.equal(_gateTransportMode('http-sse', wsOnly), 'ws-full');
1582
+ // full caps pass everything through unchanged.
1583
+ for (const m of ['auto', 'ws-full', 'ws-delta', 'http-sse']) {
1584
+ assert.equal(_gateTransportMode(m, FULL_RESPONSES_TRANSPORT_CAPS), m);
1585
+ }
1586
+ });
1587
+
1588
+ // === 11. Codex x-codex-turn-state parity =================================
1589
+ // Server-issued sticky-routing token. codex-rs stores it in a per-turn
1590
+ // `OnceLock<String>` (client.rs:277-285): captured ONCE at turn start from the
1591
+ // `x-codex-turn-state` RESPONSE header, replayed unchanged on every request
1592
+ // within that turn, never fabricated, and dropped between turns (a fresh
1593
+ // ModelClientSession/OnceLock per turn). These tests pin that exact contract
1594
+ // against our pooled-socket emulation (capture + first-use/turn-id attribution
1595
+ // + per-turn drop guard).
1596
+ test('codex turn-state: captures server response header once, never synthesizes', () => {
1597
+ const entry = {};
1598
+ // No header on the event → nothing captured (never fabricated).
1599
+ _captureTurnStateFromEvent(entry, { type: 'response.created', headers: {} });
1600
+ assert.equal(entry.turnState, undefined);
1601
+ // Server issues the token on a response header → captured.
1602
+ _captureTurnStateFromEvent(entry, { type: 'response.created', headers: { 'x-codex-turn-state': 'tok-1' } });
1603
+ assert.equal(entry.turnState, 'tok-1');
1604
+ // OnceLock semantics: a later server token in the same turn does NOT overwrite.
1605
+ _captureTurnStateFromEvent(entry, { headers: { 'x-codex-turn-state': 'tok-2' } });
1606
+ assert.equal(entry.turnState, 'tok-1');
1607
+ });
1608
+
1609
+ test('codex turn-state: captures from nested response/metadata header shapes', () => {
1610
+ const fromResponse = {};
1611
+ _captureTurnStateFromEvent(fromResponse, { response: { headers: { 'x-codex-turn-state': 'tok-r' } } });
1612
+ assert.equal(fromResponse.turnState, 'tok-r');
1613
+ const fromMeta = {};
1614
+ _captureTurnStateFromEvent(fromMeta, { response: { metadata: { headers: { 'x-codex-turn-state': 'tok-m' } } } });
1615
+ assert.equal(fromMeta.turnState, 'tok-m');
1616
+ });
1617
+
1618
+ test('codex turn-state: echoed within a turn, dropped across turns, never fabricated', () => {
1619
+ const ctxA = { sendOpts: { turnId: 'turn-A', codexSessionId: 'sess', threadId: 'thread' } };
1620
+ // A server-captured token with unknown owner (handshake/prewarm capture).
1621
+ const entry = { turnState: 'tok-A' };
1622
+ const f1 = _withCodexWsClientMetadata({}, entry, true, ctxA);
1623
+ assert.equal(f1.client_metadata['x-codex-turn-state'], 'tok-A');
1624
+ // First use attributes the token to the turn now on the wire.
1625
+ assert.equal(entry.turnStateTurnId, 'turn-A');
1626
+ // Subsequent request in the SAME turn replays the same token.
1627
+ const f2 = _withCodexWsClientMetadata({}, entry, true, ctxA);
1628
+ assert.equal(f2.client_metadata['x-codex-turn-state'], 'tok-A');
1629
+ // Next turn: the token must be dropped, never replayed or fabricated.
1630
+ const ctxB = { sendOpts: { turnId: 'turn-B', codexSessionId: 'sess', threadId: 'thread' } };
1631
+ const f3 = _withCodexWsClientMetadata({}, entry, true, ctxB);
1632
+ assert.equal('x-codex-turn-state' in f3.client_metadata, false);
1633
+ assert.equal(entry.turnState, null);
1634
+ });
1635
+
1636
+ test('codex turn-state: parity disabled leaves the frame untouched (no metadata, no echo)', () => {
1637
+ const entry = { turnState: 'tok-A' };
1638
+ const frame = { input: [] };
1639
+ const out = _withCodexWsClientMetadata(frame, entry, false, { sendOpts: { turnId: 'turn-A' } });
1640
+ assert.equal(out, frame);
1641
+ assert.equal(out.client_metadata, undefined);
1642
+ });
@@ -8,10 +8,9 @@ Broad implementation agent.
8
8
  Bounded slices; smallest coherent change, not rewrite. Stop when scope is
9
9
  unclear or the blast radius grows.
10
10
 
11
- EDIT-FIRST DISCIPLINE. Survey the slice with one batched retrieval round,
12
- then patch the first bounded piece edit incrementally, don't read
13
- exhaustively. Repeated read-only turns without an edit = stalling; on a
14
- runtime reminder, patch the piece you understand or report blocked.
11
+ EDIT-FIRST DISCIPLINE. Patch the first bounded piece edit incrementally,
12
+ don't read exhaustively. Repeated read-only turns without an edit = stalling;
13
+ on a runtime reminder, patch the piece you understand or report blocked.
15
14
 
16
15
  Self-verify edits with shell (targeted test/build).
17
16
 
@@ -8,9 +8,8 @@ Scoped implementation agent.
8
8
  Smallest scoped change; no drive-by cleanup. Stop when done/blocked.
9
9
 
10
10
  EDIT-FIRST DISCIPLINE. Brief anchors (`file:line`) are pre-verified — trust
11
- and patch. No anchor: one locating batch, then `apply_patch`. Repeated
12
- read-only turns without an edit = stalling; on a runtime reminder, patch now
13
- or return blocked. Threshold is "plausible", not "proven".
11
+ and patch. Repeated read-only turns without an edit = stalling; on a runtime
12
+ reminder, patch now or return blocked. Threshold is "plausible", not "proven".
14
13
 
15
14
  Self-verify edits with shell (node --check/test).
16
15