feat: add URL-driven workspace state and streamed Codex research

This commit is contained in:
2026-09-28 20:08:28 +09:00
parent 85d23a328b
commit 8085ad2f90
207 changed files with 15287 additions and 16026 deletions
+199 -226
View File
@@ -1,33 +1,32 @@
import { createServer } from 'node:http'
import { tweetLegacyFixture } from './fixtures/twitter.ts'
import { createServer } from "node:http";
import { tweetLegacyFixture } from "./fixtures/twitter.ts";
const portValue = process.env.TWITTER_LITE_MOCK_RELAY_PORT
const port = Number(portValue)
const portValue = process.env.TWITTER_LITE_MOCK_RELAY_PORT;
const port = Number(portValue);
if (!portValue || !Number.isInteger(port) || port < 1) {
throw new Error('TWITTER_LITE_MOCK_RELAY_PORT must be a valid port')
throw new Error("TWITTER_LITE_MOCK_RELAY_PORT must be a valid port");
}
const failedRetryQueries = new Set()
const failedRetryQueries = new Set();
const rootByTarget = new Map([
['1001', '1000'],
['2001', '2000'],
['3001', '3000'],
])
["1001", "1000"],
["2001", "2000"],
["3001", "3000"],
]);
const targetByRoot = new Map([
['1000', '1001'],
['2000', '2001'],
['3000', '3001'],
])
const failedThreadRoots = new Set()
["1000", "1001"],
["2000", "2001"],
["3000", "3001"],
]);
const failedThreadRoots = new Set();
const isRecord = (value) =>
value !== null && typeof value === 'object' && !Array.isArray(value)
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
const tweetResult = (
id,
text,
username = 'yuta',
username = "yuta",
{ conversationId = id, inReplyTo, replyCount = 1 } = {},
) => ({
rest_id: id,
@@ -39,14 +38,14 @@ const tweetResult = (
core: {
user_results: {
result: {
rest_id: '42',
legacy: { screen_name: username, name: 'Yuta' },
rest_id: "42",
legacy: { screen_name: username, name: "Yuta" },
},
},
},
})
});
const tweet = (id, text, username = 'yuta', options) => ({
const tweet = (id, text, username = "yuta", options) => ({
entryId: `tweet-${id}`,
content: {
itemContent: {
@@ -55,149 +54,140 @@ const tweet = (id, text, username = 'yuta', options) => ({
},
},
},
})
});
const cursor = (value) => ({
entryId: 'cursor-bottom',
content: { cursorType: 'Bottom', value },
})
entryId: "cursor-bottom",
content: { cursorType: "Bottom", value },
});
const send = (response, payload, status = 200, headers = {}) => {
response.writeHead(status, {
'content-type': 'application/json; charset=utf-8',
"content-type": "application/json; charset=utf-8",
...headers,
})
response.end(JSON.stringify(payload))
}
});
response.end(JSON.stringify(payload));
};
const fail = (response, status, message, headers) =>
send(response, { errors: [{ message }] }, status, headers)
send(response, { errors: [{ message }] }, status, headers);
const variablesFrom = (url) => {
const encoded = url.searchParams.get('variables')
if (!encoded) throw new Error('variables are required')
const variables = JSON.parse(encoded)
if (!isRecord(variables)) throw new Error('variables must be an object')
return variables
}
const encoded = url.searchParams.get("variables");
if (!encoded) throw new Error("variables are required");
const variables = JSON.parse(encoded);
if (!isRecord(variables)) throw new Error("variables must be an object");
return variables;
};
const jsonBodyFrom = async (request) => {
let body = ''
let body = "";
for await (const chunk of request) {
body += chunk
if (body.length > 1_000_000) throw new Error('request body is too large')
body += chunk;
if (body.length > 1_000_000) throw new Error("request body is too large");
}
const parsed = JSON.parse(body)
if (!isRecord(parsed)) throw new Error('JSON body must be an object')
return parsed
}
const parsed = JSON.parse(body);
if (!isRecord(parsed)) throw new Error("JSON body must be an object");
return parsed;
};
const timeline = (entries) => ({ instructions: [{ entries }] })
const timeline = (entries) => ({ instructions: [{ entries }] });
const server = createServer(async (request, response) => {
const url = new URL(request.url ?? '/', `http://127.0.0.1:${port}`)
if (request.method === 'GET' && url.pathname === '/profiles') {
send(response, { profiles: ['e2e', 'alternate'] })
return
const url = new URL(request.url ?? "/", `http://127.0.0.1:${port}`);
if (request.method === "GET" && url.pathname === "/profiles") {
send(response, { profiles: ["e2e", "alternate"] });
return;
}
const operation = [
'UserByScreenName',
'UserTweets',
'TweetDetail',
'SearchTimeline',
'ListsManagementPageTimeline',
'ListLatestTweetsTimeline',
].find((name) => url.pathname.endsWith(`/${name}`))
"UserByScreenName",
"UserTweets",
"TweetDetail",
"SearchTimeline",
"ListsManagementPageTimeline",
"ListLatestTweetsTimeline",
].find((name) => url.pathname.endsWith(`/${name}`));
if (!operation) {
fail(response, 501, 'unsupported read operation')
return
fail(response, 501, "unsupported read operation");
return;
}
const expectedMethod = operation === 'SearchTimeline' ? 'POST' : 'GET'
const expectedMethod = operation === "SearchTimeline" ? "POST" : "GET";
if (request.method !== expectedMethod) {
fail(response, 405, `expected ${expectedMethod}`, {
allow: expectedMethod,
})
return
});
return;
}
const profileName = request.headers['x-profile-name']
if (profileName !== 'e2e' && profileName !== 'alternate') {
fail(response, 403, 'invalid profile')
return
const profileName = request.headers["x-profile-name"];
if (profileName !== "e2e" && profileName !== "alternate") {
fail(response, 403, "invalid profile");
return;
}
let variables
let variables;
try {
variables = variablesFrom(url)
variables = variablesFrom(url);
} catch {
fail(response, 400, 'malformed variables')
return
fail(response, 400, "malformed variables");
return;
}
if (operation === 'UserByScreenName') {
if (
typeof variables.screen_name !== 'string' ||
variables.screen_name.length === 0
) {
fail(response, 400, 'screen_name is required')
return
if (operation === "UserByScreenName") {
if (typeof variables.screen_name !== "string" || variables.screen_name.length === 0) {
fail(response, 400, "screen_name is required");
return;
}
send(response, {
data: {
user: {
result: {
rest_id: '42',
legacy: { screen_name: variables.screen_name, name: 'Yuta' },
rest_id: "42",
legacy: { screen_name: variables.screen_name, name: "Yuta" },
},
},
},
})
return
});
return;
}
if (operation === 'UserTweets') {
const expectedCursor =
variables.cursor === undefined || variables.cursor === 'user-next'
if (
variables.userId !== '42' ||
variables.count !== 20 ||
!expectedCursor
) {
fail(response, 400, 'invalid user timeline variables')
return
if (operation === "UserTweets") {
const expectedCursor = variables.cursor === undefined || variables.cursor === "user-next";
if (variables.userId !== "42" || variables.count !== 20 || !expectedCursor) {
fail(response, 400, "invalid user timeline variables");
return;
}
const secondPage = variables.cursor === 'user-next'
const firstPageText =
profileName === 'alternate' ? 'alternate profile page 1' : 'user page 1'
const secondPage = variables.cursor === "user-next";
const firstPageText = profileName === "alternate" ? "alternate profile page 1" : "user page 1";
const entries = secondPage
? [tweet('1010', 'user page 2')]
? [tweet("1010", "user page 2")]
: [
tweet('1001', firstPageText, 'yuta', {
conversationId: '1000',
tweet("1001", firstPageText, "yuta", {
conversationId: "1000",
}),
cursor('user-next'),
]
cursor("user-next"),
];
send(response, {
data: {
user: { result: { timeline: { timeline: timeline(entries) } } },
},
})
return
});
return;
}
if (operation === 'ListsManagementPageTimeline') {
if (operation === "ListsManagementPageTimeline") {
if (variables.count !== 100) {
fail(response, 400, 'invalid lists management variables')
return
fail(response, 400, "invalid lists management variables");
return;
}
const features = url.searchParams.get('features')
const features = url.searchParams.get("features");
try {
if (!features || !isRecord(JSON.parse(features))) throw new Error()
if (!features || !isRecord(JSON.parse(features))) throw new Error();
} catch {
fail(response, 400, 'invalid lists management feature lock')
return
fail(response, 400, "invalid lists management feature lock");
return;
}
send(response, {
data: {
@@ -210,28 +200,26 @@ const server = createServer(async (request, response) => {
{
content: {
clientEventInfo: {
component: 'suggest_owned_subscribed_list',
component: "suggest_owned_subscribed_list",
},
items: [
{
item: {
itemContent: {
list: {
id_str: '1234567890',
id_str: "1234567890",
name:
profileName === 'alternate'
? 'Alternate List'
: 'My E2E List',
description: 'Accounts selected for testing',
profileName === "alternate" ? "Alternate List" : "My E2E List",
description: "Accounts selected for testing",
member_count: 10,
mode: 'Public',
mode: "Public",
created_at: 1_704_067_200_000,
user_results: {
result: {
rest_id: '42',
rest_id: "42",
core: {
screen_name: 'yuta',
name: 'Yuta',
screen_name: "yuta",
name: "Yuta",
},
},
},
@@ -249,66 +237,65 @@ const server = createServer(async (request, response) => {
},
},
},
errors: [{ message: 'optional banner field failed' }],
})
return
errors: [{ message: "optional banner field failed" }],
});
return;
}
if (operation === 'ListLatestTweetsTimeline') {
const expectedCursor =
variables.cursor === undefined || variables.cursor === 'list-next'
if (operation === "ListLatestTweetsTimeline") {
const expectedCursor = variables.cursor === undefined || variables.cursor === "list-next";
if (
typeof variables.listId !== 'string' ||
typeof variables.listId !== "string" ||
!/^\d+$/.test(variables.listId) ||
variables.count !== 20 ||
!expectedCursor
) {
fail(response, 400, 'invalid list timeline variables')
return
fail(response, 400, "invalid list timeline variables");
return;
}
const secondPage = variables.cursor === 'list-next'
const secondPage = variables.cursor === "list-next";
const entries = secondPage
? [tweet('3010', 'list page 2', 'listed')]
: [tweet('3001', 'list page 1', 'listed'), cursor('list-next')]
? [tweet("3010", "list page 2", "listed")]
: [tweet("3001", "list page 1", "listed"), cursor("list-next")];
send(response, {
data: {
list: { tweets_timeline: { timeline: timeline(entries) } },
},
})
return
});
return;
}
if (operation === 'TweetDetail') {
const focalTweetId = variables.focalTweetId
if (typeof focalTweetId !== 'string' || !/^\d+$/.test(focalTweetId)) {
fail(response, 400, 'invalid focalTweetId')
return
if (operation === "TweetDetail") {
const focalTweetId = variables.focalTweetId;
if (typeof focalTweetId !== "string" || !/^\d+$/.test(focalTweetId)) {
fail(response, 400, "invalid focalTweetId");
return;
}
const features = url.searchParams.get('features')
const fieldToggles = url.searchParams.get('fieldToggles')
const features = url.searchParams.get("features");
const fieldToggles = url.searchParams.get("fieldToggles");
try {
if (!features || !isRecord(JSON.parse(features))) throw new Error()
if (!features || !isRecord(JSON.parse(features))) throw new Error();
if (!fieldToggles || !isRecord(JSON.parse(fieldToggles))) {
throw new Error()
throw new Error();
}
} catch {
fail(response, 400, 'invalid TweetDetail feature locks')
return
fail(response, 400, "invalid TweetDetail feature locks");
return;
}
const targetRoot = rootByTarget.get(focalTweetId)
const targetRoot = rootByTarget.get(focalTweetId);
if (targetRoot && variables.cursor === undefined) {
const noReplies = focalTweetId === '3001'
const noReplies = focalTweetId === "3001";
const text = noReplies
? 'no replies'
: focalTweetId === '1001'
? 'user page 1'
: 'retry focal'
const result = tweetResult(focalTweetId, text, 'focus', {
? "no replies"
: focalTweetId === "1001"
? "user page 1"
: "retry focal";
const result = tweetResult(focalTweetId, text, "focus", {
conversationId: targetRoot,
inReplyTo: targetRoot,
replyCount: noReplies ? 0 : 1,
})
});
send(response, {
data: {
tweetResult: { result },
@@ -316,7 +303,7 @@ const server = createServer(async (request, response) => {
instructions: [
{
entries: [
tweet(focalTweetId, text, 'focus', {
tweet(focalTweetId, text, "focus", {
conversationId: targetRoot,
inReplyTo: targetRoot,
replyCount: noReplies ? 0 : 1,
@@ -326,47 +313,43 @@ const server = createServer(async (request, response) => {
],
},
},
})
return
});
return;
}
const targetId = targetByRoot.get(focalTweetId)
const targetId = targetByRoot.get(focalTweetId);
if (!targetId) {
fail(response, 400, 'unsupported TweetDetail fixture')
return
fail(response, 400, "unsupported TweetDetail fixture");
return;
}
const expectedCursor = `thread-next:${focalTweetId}`
const expectedCursor = `thread-next:${focalTweetId}`;
if (variables.cursor !== undefined && variables.cursor !== expectedCursor) {
fail(response, 400, 'invalid thread cursor')
return
fail(response, 400, "invalid thread cursor");
return;
}
if (variables.cursor && focalTweetId === '3000') {
fail(response, 500, 'zero-reply thread must not request a continuation')
return
if (variables.cursor && focalTweetId === "3000") {
fail(response, 500, "zero-reply thread must not request a continuation");
return;
}
if (
variables.cursor &&
focalTweetId === '2000' &&
!failedThreadRoots.has(focalTweetId)
) {
failedThreadRoots.add(focalTweetId)
fail(response, 503, 'transient thread fixture failure')
return
if (variables.cursor && focalTweetId === "2000" && !failedThreadRoots.has(focalTweetId)) {
failedThreadRoots.add(focalTweetId);
fail(response, 503, "transient thread fixture failure");
return;
}
const entries = variables.cursor
? [
tweet(`${focalTweetId}3`, 'thread page 2', 'reply2', {
tweet(`${focalTweetId}3`, "thread page 2", "reply2", {
conversationId: focalTweetId,
inReplyTo: targetId,
}),
]
: focalTweetId === '3000'
: focalTweetId === "3000"
? [
tweet(focalTweetId, 'thread root', 'root', {
tweet(focalTweetId, "thread root", "root", {
conversationId: focalTweetId,
}),
tweet(targetId, 'no replies', 'focus', {
tweet(targetId, "no replies", "focus", {
conversationId: focalTweetId,
inReplyTo: focalTweetId,
replyCount: 0,
@@ -374,28 +357,23 @@ const server = createServer(async (request, response) => {
cursor(expectedCursor),
]
: [
tweet(focalTweetId, 'thread root', 'root', {
tweet(focalTweetId, "thread root", "root", {
conversationId: focalTweetId,
}),
tweet(
targetId,
focalTweetId === '1000' ? 'user page 1' : 'retry focal',
'focus',
{
conversationId: focalTweetId,
inReplyTo: focalTweetId,
},
),
tweet(`${focalTweetId}2`, 'thread page 1', 'reply1', {
tweet(targetId, focalTweetId === "1000" ? "user page 1" : "retry focal", "focus", {
conversationId: focalTweetId,
inReplyTo: focalTweetId,
}),
tweet(`${focalTweetId}2`, "thread page 1", "reply1", {
conversationId: focalTweetId,
inReplyTo: targetId,
}),
cursor(expectedCursor),
]
];
send(response, {
data: {
tweetResult: {
result: tweetResult(focalTweetId, 'thread root', 'root', {
result: tweetResult(focalTweetId, "thread root", "root", {
conversationId: focalTweetId,
}),
},
@@ -403,67 +381,62 @@ const server = createServer(async (request, response) => {
instructions: [{ entries }],
},
},
})
if (variables.cursor && focalTweetId === '2000') {
failedThreadRoots.delete(focalTweetId)
});
if (variables.cursor && focalTweetId === "2000") {
failedThreadRoots.delete(focalTweetId);
}
return
return;
}
const contentType = request.headers['content-type'] ?? ''
const contentType = request.headers["content-type"] ?? "";
if (!/^application\/json(?:\s*;|$)/i.test(contentType)) {
fail(response, 400, 'SearchTimeline requires JSON')
return
fail(response, 400, "SearchTimeline requires JSON");
return;
}
let body
let body;
try {
body = await jsonBodyFrom(request)
body = await jsonBodyFrom(request);
} catch {
fail(response, 400, 'malformed JSON body')
return
fail(response, 400, "malformed JSON body");
return;
}
const rawQuery = variables.rawQuery
const rawQuery = variables.rawQuery;
const expectedCursor =
variables.cursor === undefined ||
variables.cursor === `search-next:${rawQuery}`
variables.cursor === undefined || variables.cursor === `search-next:${rawQuery}`;
if (
typeof rawQuery !== 'string' ||
typeof rawQuery !== "string" ||
rawQuery.length === 0 ||
variables.count !== 20 ||
variables.querySource !== 'typed_query' ||
!['Top', 'Latest'].includes(variables.product) ||
variables.querySource !== "typed_query" ||
!["Top", "Latest"].includes(variables.product) ||
!expectedCursor ||
!isRecord(body.features) ||
typeof body.queryId !== 'string' ||
typeof body.queryId !== "string" ||
body.queryId.length === 0 ||
Object.hasOwn(body, 'variables')
Object.hasOwn(body, "variables")
) {
fail(response, 400, 'invalid search request')
return
fail(response, 400, "invalid search request");
return;
}
const secondPage = variables.cursor !== undefined
if (
secondPage &&
rawQuery.startsWith('retry-') &&
!failedRetryQueries.has(rawQuery)
) {
failedRetryQueries.add(rawQuery)
fail(response, 503, 'transient fixture failure')
return
const secondPage = variables.cursor !== undefined;
if (secondPage && rawQuery.startsWith("retry-") && !failedRetryQueries.has(rawQuery)) {
failedRetryQueries.add(rawQuery);
fail(response, 503, "transient fixture failure");
return;
}
if (secondPage && rawQuery.startsWith('slow-')) {
await new Promise((resolve) => setTimeout(resolve, 1_200))
if (secondPage && rawQuery.startsWith("slow-")) {
await new Promise((resolve) => setTimeout(resolve, 1_200));
}
const follows = rawQuery.includes('filter:follows') ? 'follows' : 'all'
const label = `${profileName === 'alternate' ? 'alternate · ' : ''}${variables.product} · ${follows}`
const id = encodeURIComponent(rawQuery)
const follows = rawQuery.includes("filter:follows") ? "follows" : "all";
const label = `${profileName === "alternate" ? "alternate · " : ""}${variables.product} · ${follows}`;
const id = encodeURIComponent(rawQuery);
const entries = secondPage
? [tweet(`s-${id}-2`, `${label} page 2`)]
: [tweet(`s-${id}-1`, `${label} page 1`), cursor(`search-next:${rawQuery}`)]
: [tweet(`s-${id}-1`, `${label} page 1`), cursor(`search-next:${rawQuery}`)];
send(response, {
data: {
@@ -471,7 +444,7 @@ const server = createServer(async (request, response) => {
search_timeline: { timeline: timeline(entries) },
},
},
})
})
});
});
server.listen(port, '127.0.0.1')
server.listen(port, "127.0.0.1");