Commit 15bc592
luop
fix(proxy-core): bind protocol-level sticky channel from response IDs
Three related bugs in protocol-level session stickiness shipped in
spec session-stick-routing:
P1 (binding timing): surfaces wrote sticky channels using continuation
IDs taken from the *current request* (previous_response_id /
tool_result.tool_use_id), but the next request queries with a key
built from the *next request's* incoming ID — which is the current
response's newly-minted ID, never the request-side one. Sticky
never hit. Now bind from the response payload (response.id /
tool_use.id) so the next request's lookup key matches.
Three follow-up corrections from review:
- The first cut of bindSurfaceStickyChannelFromResponse early-returned
when the request-side key was a CLI-level fallback, on the
mistaken belief that this would "steal CLI-level semantics".
CLI-level keys and protocol-level keys live in disjoint keyspaces
and never collide; the early-return broke the most common
production path (CLI client carrying a session header on round 1
with no continuation ID). The guard is removed; the helper now
binds whenever the response yields a usable continuation ID,
regardless of the request-side key.
- The four chatSurface bind sites in the Anthropic-downstream branch
forwarded raw upstream payloads to the extractor. When the upstream
was OpenAI Chat / Responses (cross-protocol fallback), the raw
payload carried tool calls under choices[].message.tool_calls or
output[].tool_calls — which the Anthropic extractor does not read.
Surfaces now feed the bind helper a NormalizedFinalResponse instead,
hitting extractor path 2 (top-level toolCalls[].id). proxyStream
was extended to expose getTerminalNormalizedFinal() for streaming
paths, and its OpenAI Chat aggregator is now always-on so claude
downstream + OpenAI Chat upstream still captures tool_call.id.
- The Anthropic-native SSE fast path in proxyStream
(consumeSseEventBlock returning handled=true) short-circuited
before applyOpenAiChatStreamEvent ran, so claude-downstream +
Anthropic-upstream traffic — the most common case — never
captured tool_use.id from content_block_start. The handled
branch now also runs transformStreamEvent + the aggregator
(discarding the produced lines because the consumer already
forwarded the original frame). New regression suite
proxyStream.test.ts exercises this path.
P2 (toggle bypass): the protocol-level branch in
buildSurfaceStickySessionKey skipped the proxyStickySessionEnabled
guard, so disabling sticky still produced a non-empty key that
reserved a session-scoped lease and could 503. Added the guard so
protocol-level matches CLI-level semantics.
P3 (failure clears protocol-level binding): clearSurfaceStickyChannel
was called from 16 surface error paths, violating spec Requirement
6.4 / 8.4 ("failure does not clear; wait for TTL or next success
to overwrite"). Added a proto-v1| prefix noop guard inside
clearSurfaceStickyChannel — call sites untouched, blast radius zero.
Implementation details:
- New extractAnthropicMessagesContinuationIdsFromResponse() in
transformers/anthropic/messages/sessionId.ts, mirroring the existing
extractResponsesTerminalResponseId() in
transformers/openai/responses/continuation.ts.
- New bindSurfaceStickyChannelFromResponse() in
proxy-core/surfaces/sharedSurface.ts that re-binds protocol-level
keys with response-side IDs after success. Always attempts the bind
when sticky is enabled and a usable continuation ID is extracted.
- proxyStream.ts (transformers/openai/chat) exposes
getTerminalNormalizedFinal(); chat aggregator state is always-on so
cross-protocol upstreams (OpenAI Chat → Anthropic downstream) and
the native Anthropic fast path both surface tool_call.id at a single
capture point.
- Wired into 5 bind call sites in openAiResponsesSurface.ts and 4
Anthropic-branch bind call sites in chatSurface.ts. count_tokens
bind site explicitly does not bind from response (no tool_use.id
in token-count payloads).
- Existing bindSurfaceStickyChannel and 16 clearSurfaceStickyChannel
call sites unchanged.
Tests:
- New regression suite sessionStick.bugCondition.test.ts (8 cases)
proves the three bugs are fixed, including the round-1-CLI-key
follow-up case for OpenAI Responses and Anthropic Messages.
- New regression suite proxyStream.test.ts (4 cases) covers the
Anthropic-native + cross-protocol + same-protocol + empty-stream
shapes; the Anthropic-native case fails on the pre-fix handled
branch (verified by reverting just that hunk).
- sessionStick.integration.test.ts rewritten: scenarios A/B now
bind from response payload; new scenarios G (P2 toggle), H (retry
override), I (P3 failure preservation), J (cross-protocol
NormalizedFinalResponse with toolCalls[]).
- 12 new parametrized cases for the Anthropic response-side
extractor (43 cases total in the file).
- Architecture test extended: extractResponsesTerminalResponseId and
the new Anthropic extractor are now in the protocol-pure boundary
matrix; proxyChannelCoordinator and channelSelection asserted free
of "proto-v1|" literals (no protocol awareness leak).
Verification:
- npm run build:server: clean
- npm run repo:drift-check: 0 new violations
- All session-stick-routing existing test suites green (236/236
across chat / anthropic / sticky surfaces)
- Cross-protocol-image-forwarding suites green1 parent 491b272 commit 15bc592
10 files changed
Lines changed: 2653 additions & 151 deletions
File tree
- src/server
- proxy-core/surfaces
- transformers
- anthropic/messages
- openai/chat
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| 60 | + | |
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
| |||
626 | 627 | | |
627 | 628 | | |
628 | 629 | | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
629 | 639 | | |
630 | 640 | | |
631 | 641 | | |
| |||
634 | 644 | | |
635 | 645 | | |
636 | 646 | | |
| 647 | + | |
637 | 648 | | |
638 | 649 | | |
639 | 650 | | |
| |||
701 | 712 | | |
702 | 713 | | |
703 | 714 | | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
704 | 740 | | |
705 | 741 | | |
706 | 742 | | |
| |||
801 | 837 | | |
802 | 838 | | |
803 | 839 | | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
804 | 865 | | |
805 | 866 | | |
806 | 867 | | |
| |||
887 | 948 | | |
888 | 949 | | |
889 | 950 | | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
890 | 975 | | |
891 | 976 | | |
892 | 977 | | |
| |||
985 | 1070 | | |
986 | 1071 | | |
987 | 1072 | | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
988 | 1094 | | |
989 | 1095 | | |
990 | 1096 | | |
| |||
1430 | 1536 | | |
1431 | 1537 | | |
1432 | 1538 | | |
| 1539 | + | |
| 1540 | + | |
| 1541 | + | |
| 1542 | + | |
1433 | 1543 | | |
1434 | 1544 | | |
1435 | 1545 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| 77 | + | |
77 | 78 | | |
78 | 79 | | |
79 | 80 | | |
| |||
924 | 925 | | |
925 | 926 | | |
926 | 927 | | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
927 | 938 | | |
928 | 939 | | |
929 | 940 | | |
| |||
939 | 950 | | |
940 | 951 | | |
941 | 952 | | |
| 953 | + | |
942 | 954 | | |
943 | 955 | | |
944 | 956 | | |
| |||
991 | 1003 | | |
992 | 1004 | | |
993 | 1005 | | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
994 | 1023 | | |
995 | 1024 | | |
996 | 1025 | | |
| |||
1083 | 1112 | | |
1084 | 1113 | | |
1085 | 1114 | | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
1086 | 1130 | | |
1087 | 1131 | | |
1088 | 1132 | | |
| |||
1125 | 1169 | | |
1126 | 1170 | | |
1127 | 1171 | | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
1128 | 1187 | | |
1129 | 1188 | | |
1130 | 1189 | | |
| |||
1239 | 1298 | | |
1240 | 1299 | | |
1241 | 1300 | | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
| 1308 | + | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
1242 | 1318 | | |
1243 | 1319 | | |
1244 | 1320 | | |
| |||
1357 | 1433 | | |
1358 | 1434 | | |
1359 | 1435 | | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
1360 | 1451 | | |
1361 | 1452 | | |
1362 | 1453 | | |
| |||
0 commit comments