mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-10-19 05:20:05 +00:00
Restores ChatGPT Web on a clean-room browser transport, merged on the operator's explicit decision. Worth stating precisely, because this touches a provenance decision: the PR does not revert #11754. It narrows RETIRED_COMMON_CHATGPT_WEB_PROVIDER_IDS to the single GPL-derived alias cgpt-web and registers chatgpt-web as a separate clean-room id. The old implementation stays retired and blocked; the retirement machinery, its error code and its 410 contract are untouched. All four retirement suites agree with that distinction and pass unchanged. The 44 protected agent-instruction surfaces this PR touches (AGENTS.md, llm.txt and its 42 mirrors, README) were verified rather than trusted: masking digits and comparing the removed and added line sets gives 264 lines on each side, identical — every change is a provider-count substitution, with no sentence added, removed or reworded. Reconciled on merge: clean against the tip, with the two chat chokepoints this PR grows (src/sse/handlers/chat.ts +40, open-sse/handlers/chatCore.ts +30) recorded in the file-size baseline under an annotation. Verified that exactly those two caps move and nothing else, so the #12411 ratchet holds. The rebaseline is carried on this branch rather than left in a validation worktree — the propagation mistake that put the 2026-09-02 merge waves base-red in #12434. Verified: 504/504 across the PR's 44 test files plus all four chatgpt-web retirement suites, check:provider-consistency OK (272 REGISTRY entries, 355 canonical providers), check-file-size OK, and every changed TypeScript file parses. Thanks @backryun — separating the clean-room id from the retired alias, instead of reopening the old one, is what made this reviewable.
167 lines
5.2 KiB
TypeScript
167 lines
5.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, test } from "node:test";
|
|
|
|
import {
|
|
buildChatGptWebSubscribeCommand,
|
|
ChatGptWebHandshakeState,
|
|
ChatGptWebTopicStream,
|
|
parseChatGptWebConversationHandoff,
|
|
} from "../../open-sse/utils/chatgptWebTransport.ts";
|
|
|
|
const SENTINEL = {
|
|
chatRequirementsToken: "sentinel-final-token",
|
|
proofToken: "proof-answer",
|
|
turnstileToken: "turnstile-answer",
|
|
expiresAtMs: 20_000,
|
|
};
|
|
|
|
function streamMessage(
|
|
topicId: string,
|
|
streamItemId: string,
|
|
encodedItem: string,
|
|
parentStreamItemId: string | null = null
|
|
) {
|
|
return {
|
|
type: "message",
|
|
topic_id: topicId,
|
|
offset: "offset-redacted",
|
|
payload: {
|
|
type: "conversation-turn-stream",
|
|
metadata: null,
|
|
payload: {
|
|
type: "stream-item",
|
|
stream_item_id: streamItemId,
|
|
parent_stream_item_id: parentStreamItemId,
|
|
encoded_item: encodedItem,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("ChatGPT Web clean-room handshake state", () => {
|
|
test("binds the finalized Sentinel answers and latest conduit token to one dispatch", () => {
|
|
const state = new ChatGptWebHandshakeState();
|
|
state.setSentinel(SENTINEL);
|
|
state.setConduit("conduit-old");
|
|
state.setConduit("conduit-current");
|
|
|
|
assert.deepEqual(state.consumeConversationHeaders("turn-trace", 10_000), {
|
|
"openai-sentinel-chat-requirements-token": "sentinel-final-token",
|
|
"openai-sentinel-proof-token": "proof-answer",
|
|
"openai-sentinel-turnstile-token": "turnstile-answer",
|
|
"x-conduit-token": "conduit-current",
|
|
"x-oai-turn-trace-id": "turn-trace",
|
|
});
|
|
assert.throws(() => state.consumeConversationHeaders("replayed-turn", 10_001), /incomplete/);
|
|
});
|
|
|
|
test("fails closed when Sentinel artifacts are expired or empty", () => {
|
|
const state = new ChatGptWebHandshakeState();
|
|
assert.throws(() => state.setSentinel({ ...SENTINEL, proofToken: "" }), /non-empty proofToken/);
|
|
|
|
state.setSentinel(SENTINEL);
|
|
state.setConduit("conduit-token");
|
|
assert.throws(() => state.consumeConversationHeaders("turn-trace", 20_000), /expired/);
|
|
assert.throws(() => state.consumeConversationHeaders("turn-trace", 19_000), /incomplete/);
|
|
});
|
|
});
|
|
|
|
describe("ChatGPT Web clean-room SSE to WebSocket handoff", () => {
|
|
test("parses the resume token and common topic from the handoff SSE", () => {
|
|
const handoff = parseChatGptWebConversationHandoff(
|
|
'data: {"type":"resume_conversation_token","kind":"topic",' +
|
|
'"token":"resume-token","conversation_id":"conversation"}\n\n' +
|
|
'data: {"type":"stream_handoff","conversation_id":"conversation",' +
|
|
'"turn_exchange_id":"turn","options":[' +
|
|
'{"type":"resume_sse_endpoint","topic_id":"topic"},' +
|
|
'{"type":"subscribe_ws_topic","topic_id":"topic"}]}\n\n' +
|
|
"data: [DONE]\n\n"
|
|
);
|
|
|
|
assert.deepEqual(handoff, {
|
|
conversationId: "conversation",
|
|
turnExchangeId: "turn",
|
|
topicId: "topic",
|
|
resumeToken: "resume-token",
|
|
});
|
|
});
|
|
|
|
test("rejects incomplete or internally inconsistent handoffs", () => {
|
|
assert.throws(
|
|
() =>
|
|
parseChatGptWebConversationHandoff(
|
|
'data: {"type":"stream_handoff","conversation_id":"conversation",' +
|
|
'"turn_exchange_id":"turn","options":[' +
|
|
'{"type":"resume_sse_endpoint","topic_id":"topic-a"},' +
|
|
'{"type":"subscribe_ws_topic","topic_id":"topic-b"}]}\n\n'
|
|
),
|
|
/topic mismatch/
|
|
);
|
|
});
|
|
|
|
test("builds the observed array-framed subscribe command", () => {
|
|
assert.equal(
|
|
buildChatGptWebSubscribeCommand(7, "topic", "offset"),
|
|
'[{"id":7,"command":{"type":"subscribe","topic_id":"topic","offset":"offset"}}]'
|
|
);
|
|
});
|
|
|
|
test("deduplicates catch-up/live overlap and ignores unrelated topics", () => {
|
|
const stream = new ChatGptWebTopicStream("topic");
|
|
const first = stream.ingestFrame(
|
|
JSON.stringify([
|
|
{
|
|
type: "reply",
|
|
id: 7,
|
|
reply: {
|
|
type: "subscribe",
|
|
topic_id: "topic",
|
|
recovered: true,
|
|
catchups: [streamMessage("topic", "item-1", "event: delta\\n")],
|
|
},
|
|
},
|
|
])
|
|
);
|
|
assert.deepEqual(first, {
|
|
encodedItems: ["event: delta\\n"],
|
|
lifecycleTypes: [],
|
|
done: false,
|
|
});
|
|
|
|
const second = stream.ingestFrame(
|
|
JSON.stringify([
|
|
streamMessage("topic", "item-1", "duplicate"),
|
|
streamMessage("other-topic", "item-other", "ignored"),
|
|
streamMessage("topic", "item-2", "data: [DONE]\n\n", "item-1"),
|
|
])
|
|
);
|
|
assert.deepEqual(second, {
|
|
encodedItems: ["data: [DONE]\n\n"],
|
|
lifecycleTypes: [],
|
|
done: false,
|
|
});
|
|
|
|
const third = stream.ingestFrame(
|
|
JSON.stringify([
|
|
{
|
|
type: "message",
|
|
topic_id: "topic",
|
|
payload: {
|
|
type: "conversation-turn-stream",
|
|
payload: { type: "done", conversation_id: "redacted" },
|
|
},
|
|
},
|
|
{
|
|
type: "message",
|
|
topic_id: "topic",
|
|
payload: { type: "conversation-turn-complete", payload: {} },
|
|
},
|
|
])
|
|
);
|
|
assert.deepEqual(third, {
|
|
encodedItems: [],
|
|
lifecycleTypes: ["conversation-turn-complete"],
|
|
done: true,
|
|
});
|
|
});
|
|
});
|