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.
- package/package.json +4 -2
- package/scripts/compact-trigger-migration-smoke.mjs +37 -31
- package/scripts/provider-toolcall-test.mjs +535 -36
- package/src/agents/heavy-worker/AGENT.md +3 -4
- package/src/agents/worker/AGENT.md +2 -3
- package/src/repl.mjs +9 -1
- package/src/rules/shared/01-tool.md +4 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +21 -2
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +130 -18
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +22 -12
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +131 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +102 -26
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +194 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +39 -6
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +15 -23
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +25 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +16 -8
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +50 -4
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +53 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +270 -47
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -2
- package/src/runtime/channels/lib/output-forwarder.mjs +7 -1
- package/src/runtime/channels/lib/owned-runtime.mjs +5 -2
- package/src/runtime/memory/lib/embedding-worker.mjs +18 -3
- package/src/runtime/memory/lib/memory-embed.mjs +89 -8
- package/src/session-runtime/context-status.mjs +7 -6
- package/src/session-runtime/runtime-core.mjs +0 -10
- package/src/tui/app/use-transcript-window.mjs +13 -1
- package/src/tui/components/StatusLine.jsx +5 -5
- package/src/tui/dist/index.mjs +5 -3
- package/src/ui/statusline.mjs +4 -10
- package/src/vendor/statusline/bin/statusline-route.mjs +4 -5
- 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
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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:
|
|
1088
|
-
const
|
|
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
|
-
|
|
1105
|
-
|
|
1106
|
-
assert.equal(
|
|
1107
|
-
assert.
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
const
|
|
1112
|
-
assert.equal(
|
|
1113
|
-
assert.equal(
|
|
1114
|
-
assert.
|
|
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 (
|
|
1117
|
-
else process.env.
|
|
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.
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
12
|
-
|
|
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
|
|