fix(web): converge session search boundaries (round 9)

This commit is contained in:
Hypatia May
2026-07-27 14:46:08 +08:00
parent ba2925c704
commit 0aa7f8c5cf
29 changed files with 629 additions and 157 deletions
+14 -3
View File
@@ -674,6 +674,7 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
const seenCursors = new Set<SessionSearchCursor>()
let cursor: SessionSearchCursor | undefined
let providerCallCount = 0
let providerPageLimit = SESSION_SEARCH_LIMIT
while (authorized.length <= SESSION_SEARCH_LIMIT) {
if (isAborted(signal)) return cancelled()
if (providerCallCount >= SESSION_SEARCH_PROVIDER_CALL_LIMIT) {
@@ -683,6 +684,7 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
}
providerCallCount++
const requestedCursor = cursor
const requestedPageLimit = providerPageLimit
let page
try {
page = await sessionQuery.searchSessions({
@@ -691,11 +693,20 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
{ kind: 'type', values: ['user/message', 'assistant/message', 'steering/message'] },
{ kind: 'surface', values: ['current'] },
],
limit: SESSION_SEARCH_LIMIT,
limit: requestedPageLimit,
...requestedCursor === undefined ? {} : { cursor: requestedCursor },
}, { signal })
} catch (error: unknown) {
if (isAborted(signal)) return cancelled()
if (
requestedCursor === undefined
&& error instanceof SessionQueryError
&& error.code === 'SESSION_QUERY_INVALID_LIMIT'
&& requestedPageLimit > 1
) {
providerPageLimit = Math.max(1, Math.floor(requestedPageLimit / 2))
continue
}
if (
requestedCursor !== undefined
&& error instanceof SessionQueryError
@@ -711,9 +722,9 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
}
if (isAborted(signal)) return cancelled()
const providerItemCount = page.items.length
if (providerItemCount > SESSION_SEARCH_LIMIT) {
if (providerItemCount > requestedPageLimit) {
throw new Error(
`session search provider returned ${providerItemCount} items; maximum is ${SESSION_SEARCH_LIMIT}`,
`session search provider returned ${providerItemCount} items; maximum is ${requestedPageLimit}`,
)
}
// Host visibility is the authorization boundary. Consume the
@@ -58,6 +58,26 @@ export const sessionListValueSchema = z.object({
const SESSION_SEARCH_QUERY_MAX_CHARS = 500
/** Product response bound validated independently by every client carrier. */
const SESSION_SEARCH_RESULT_LIMIT = 20
/** Maximum response snippet length in Unicode code points. */
const SESSION_SEARCH_SNIPPET_MAX_CODE_POINTS = 240
/** Early-exit Unicode code-point bound without materializing an iterator result. */
function hasAtMostCodePoints(value: string, maximum: number): boolean {
let count = 0
let offset = 0
while (offset < value.length) {
if (count === maximum) return false
const first = value.charCodeAt(offset)
const paired = first >= 0xD800
&& first <= 0xDBFF
&& offset + 1 < value.length
&& value.charCodeAt(offset + 1) >= 0xDC00
&& value.charCodeAt(offset + 1) <= 0xDFFF
offset += paired ? 2 : 1
count++
}
return true
}
/** session.search request payload. */
export const sessionSearchRequestSchema = z.object({
@@ -68,7 +88,10 @@ export const sessionSearchRequestSchema = z.object({
/** One session.search result. */
export const sessionSearchItemSchema = z.object({
sessionId: sessionIdSchema,
snippet: z.string(),
snippet: z.string().refine(
snippet => hasAtMostCodePoints(snippet, SESSION_SEARCH_SNIPPET_MAX_CODE_POINTS),
{ message: `search snippet must contain at most ${SESSION_SEARCH_SNIPPET_MAX_CODE_POINTS} Unicode code points` },
),
}) satisfies z.ZodType<Wire<SessionSearchItem>>
/** session.search response value. */