mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-13 20:07:08 +01:00
fix: pre-landing review fixes
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
2d6c44b142
commit
3d372db3ef
9 files changed
+106
-41
No files matched your search
+8
-8
@@ -6,29 +6,29 @@
|
||||
"agy/gstack-plan-design-review": 110044,
|
||||
"agy/gstack-plan-devex-review": 116496,
|
||||
"agy/gstack-plan-eng-review": 144907,
|
||||
"agy/gstack-review": 103744,
|
||||
"agy/gstack-review": 103743,
|
||||
"codex/gstack-autoplan": 108553,
|
||||
"codex/gstack-land-and-deploy": 100284,
|
||||
"codex/gstack-plan-design-review": 111116,
|
||||
"codex/gstack-plan-devex-review": 118004,
|
||||
"codex/gstack-plan-eng-review": 146072,
|
||||
"codex/gstack-review": 103267,
|
||||
"codex/gstack-review": 103266,
|
||||
"copilot/gstack-autoplan": 109253,
|
||||
"copilot/gstack-plan-design-review": 110355,
|
||||
"copilot/gstack-plan-devex-review": 115958,
|
||||
"copilot/gstack-plan-eng-review": 145072,
|
||||
"copilot/gstack-review": 103325,
|
||||
"copilot/gstack-review": 103324,
|
||||
"cursor/gstack-autoplan": 108485,
|
||||
"cursor/gstack-plan-design-review": 108900,
|
||||
"cursor/gstack-plan-devex-review": 115228,
|
||||
"cursor/gstack-plan-eng-review": 143662,
|
||||
"cursor/gstack-review": 121615,
|
||||
"cursor/gstack-review": 121614,
|
||||
"factory/gstack-autoplan": 110763,
|
||||
"factory/gstack-land-and-deploy": 100105,
|
||||
"factory/gstack-plan-design-review": 111675,
|
||||
"factory/gstack-plan-devex-review": 118591,
|
||||
"factory/gstack-plan-eng-review": 146770,
|
||||
"factory/gstack-review": 125388,
|
||||
"factory/gstack-review": 125387,
|
||||
"gbrain/gstack-autoplan": 108892,
|
||||
"gbrain/gstack-plan-eng-review": 126289,
|
||||
"hermes/gstack-autoplan": 108982,
|
||||
@@ -37,18 +37,18 @@
|
||||
"kiro/gstack-plan-design-review": 108752,
|
||||
"kiro/gstack-plan-devex-review": 115080,
|
||||
"kiro/gstack-plan-eng-review": 143524,
|
||||
"kiro/gstack-review": 121439,
|
||||
"kiro/gstack-review": 121438,
|
||||
"openclaw/gstack-autoplan": 108715,
|
||||
"openclaw/gstack-plan-eng-review": 124804,
|
||||
"opencode/gstack-autoplan": 109234,
|
||||
"opencode/gstack-plan-design-review": 110187,
|
||||
"opencode/gstack-plan-devex-review": 115836,
|
||||
"opencode/gstack-plan-eng-review": 144931,
|
||||
"opencode/gstack-review": 122237,
|
||||
"opencode/gstack-review": 122236,
|
||||
"slate/gstack-autoplan": 108372,
|
||||
"slate/gstack-plan-design-review": 108826,
|
||||
"slate/gstack-plan-devex-review": 115154,
|
||||
"slate/gstack-plan-eng-review": 143593,
|
||||
"slate/gstack-review": 121527
|
||||
"slate/gstack-review": 121526
|
||||
}
|
||||
}
|
||||
+23
-11
@@ -135,7 +135,21 @@ export async function callJudge<T>(
|
||||
// could be exhausted before a frontier judge emitted any JSON.
|
||||
const resolvedModel = resolveEvalModel('judge', model);
|
||||
const maxTokens = opts?.max_tokens ?? DEFAULT_JUDGE_MAX_TOKENS;
|
||||
const client = new Anthropic();
|
||||
// What a cancelled request had received, so a judge deadline names rate-limit
|
||||
// waits, SDK retries, a stream with no events, or a long thinking phase. Counts
|
||||
// only, never content; an observer that throws never changes the judge outcome.
|
||||
const started = performance.now();
|
||||
const elapsedMs = () => Math.round(performance.now() - started);
|
||||
const progress = { httpRequests: 0, httpStatuses: [] as number[], retryWaitsMs: [] as number[], connectedMs: null as number | null,
|
||||
firstEventMs: null as number | null, lastEventMs: null as number | null, block: null as string | null, thinkingChars: 0,
|
||||
textChars: 0, messageId: null as string | null, requestId: null as string | null };
|
||||
const observe = (record: () => void) => { try { record(); } catch {} };
|
||||
const client = new Anthropic({ fetch: async (...args: Parameters<typeof fetch>) => {
|
||||
progress.httpRequests += 1;
|
||||
const response = await globalThis.fetch(...args);
|
||||
progress.httpStatuses.push(response.status);
|
||||
return response;
|
||||
} });
|
||||
|
||||
const request = {
|
||||
model: resolvedModel,
|
||||
@@ -146,16 +160,14 @@ export async function callJudge<T>(
|
||||
...(opts?.effort === undefined ? {} : { effort: opts.effort }) } }),
|
||||
messages: [{ role: 'user' as const, content: prompt }],
|
||||
};
|
||||
// What a cancelled request had received, so a judge deadline names rate-limit
|
||||
// waits, a stream with no events, or a long thinking phase. Counts only, never content.
|
||||
const started = performance.now();
|
||||
const elapsedMs = () => Math.round(performance.now() - started);
|
||||
const progress = { retryWaitsMs: [] as number[], firstEventMs: null as number | null, lastEventMs: null as number | null,
|
||||
block: null as string | null, thinkingChars: 0, textChars: 0, messageId: null as string | null, requestId: null as string | null };
|
||||
const makeRequest = () => {
|
||||
if (!opts?.stream) return client.messages.create(request, signal ? { signal } : undefined);
|
||||
const stream = client.messages.stream(request, signal ? { signal } : undefined);
|
||||
stream.on('streamEvent', event => {
|
||||
stream.on('connect', () => observe(() => {
|
||||
progress.connectedMs ??= elapsedMs();
|
||||
progress.requestId ??= stream.request_id ?? null;
|
||||
}));
|
||||
stream.on('streamEvent', event => observe(() => {
|
||||
progress.firstEventMs ??= elapsedMs();
|
||||
progress.lastEventMs = elapsedMs();
|
||||
progress.requestId ??= stream.request_id ?? null;
|
||||
@@ -163,7 +175,7 @@ export async function callJudge<T>(
|
||||
if (event.type === 'content_block_start') progress.block = event.content_block.type;
|
||||
if (event.type === 'content_block_delta' && event.delta.type === 'thinking_delta') progress.thinkingChars += event.delta.thinking.length;
|
||||
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') progress.textChars += event.delta.text.length;
|
||||
});
|
||||
}));
|
||||
return stream.finalMessage();
|
||||
};
|
||||
|
||||
@@ -199,8 +211,8 @@ export async function callJudge<T>(
|
||||
if (signal?.aborted) {
|
||||
console.error(JSON.stringify({
|
||||
type: 'llm-judge-aborted', model: resolvedModel, stream: opts?.stream === true, elapsedMs: elapsedMs(),
|
||||
requests: attempt + 1, ...progress,
|
||||
reason: signal.reason instanceof Error ? signal.reason.message : String(signal.reason),
|
||||
attempts: attempt + 1, ...progress,
|
||||
reason: (signal.reason instanceof Error ? signal.reason.message : String(signal.reason)).slice(0, 200),
|
||||
}));
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('callJudge cancellation', () => {
|
||||
await expect(pending).rejects.toThrow('Expired during rate-limit backoff.');
|
||||
expect(diagnostics).toHaveBeenCalledTimes(1);
|
||||
expect(JSON.parse(diagnostics.mock.calls[0]![0])).toMatchObject({ type: 'llm-judge-aborted', model: 'test-judge-model',
|
||||
stream: false, requests: 1, retryWaitsMs: [30000], firstEventMs: null, reason: 'Expired during rate-limit backoff.' });
|
||||
stream: false, attempts: 1, httpRequests: 0, retryWaitsMs: [30000], firstEventMs: null, reason: 'Expired during rate-limit backoff.' });
|
||||
create.mockResolvedValue(response as any);
|
||||
diagnostics.mockClear();
|
||||
expect(await callJudge('Judge this.', undefined, { signal: new AbortController().signal })).toEqual({ passed: true });
|
||||
|
||||
@@ -139,8 +139,8 @@ test('a judge aborted mid-stream retains what it had received, as counts without
|
||||
const diagnostic = diagnostics.mock.calls[0]![0];
|
||||
expect(diagnostic).not.toContain('PRIVATE_');
|
||||
const event = JSON.parse(diagnostic);
|
||||
expect(event).toMatchObject({ type: 'llm-judge-aborted', model: 'claude-fable-5-1', stream: true, requests: 1,
|
||||
retryWaitsMs: [], block: 'thinking', thinkingChars: 'PRIVATE_THINKING'.length, textChars: 0,
|
||||
expect(event).toMatchObject({ type: 'llm-judge-aborted', model: 'claude-fable-5-1', stream: true, attempts: 1,
|
||||
httpRequests: 1, httpStatuses: [200], retryWaitsMs: [], block: 'thinking', thinkingChars: 'PRIVATE_THINKING'.length, textChars: 0,
|
||||
messageId: 'msg_stalled', requestId: 'req_stalled', reason: failure.message });
|
||||
expect(event.firstEventMs).toBeGreaterThanOrEqual(0);
|
||||
expect(event.lastEventMs).toBeGreaterThanOrEqual(event.firstEventMs); // gstack-timing-ok: ordering of two recorded offsets, not an elapsed-time bound
|
||||
@@ -159,8 +159,8 @@ test('a stream that never sent an event is retained as such, and provider errors
|
||||
await requested;
|
||||
controller.abort(new Error('deadline'));
|
||||
await rejected;
|
||||
expect(JSON.parse(diagnostics.mock.calls[0]![0])).toMatchObject({ type: 'llm-judge-aborted', firstEventMs: null,
|
||||
lastEventMs: null, block: null, thinkingChars: 0, messageId: null, requestId: null, reason: 'deadline' });
|
||||
expect(JSON.parse(diagnostics.mock.calls[0]![0])).toMatchObject({ type: 'llm-judge-aborted', httpRequests: 1,
|
||||
httpStatuses: [], connectedMs: null, firstEventMs: null, lastEventMs: null, block: null, thinkingChars: 0, messageId: null, requestId: null, reason: 'deadline' });
|
||||
|
||||
diagnostics.mockClear();
|
||||
transport.mockResolvedValue(new Response(JSON.stringify({ type: 'error', error: { type: 'api_error', message: 'boom' } }),
|
||||
@@ -168,3 +168,58 @@ test('a stream that never sent an event is retained as such, and provider errors
|
||||
await expect(callJudge('score', 'claude-fable-5-1', { max_tokens: 65_536, stream: true, signal: new AbortController().signal })).rejects.toThrow();
|
||||
expect(diagnostics).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('a deadline after headers but before any event retains the connection and its request id', async () => {
|
||||
let reading!: () => void;
|
||||
const connected = new Promise<void>(resolve => { reading = resolve; });
|
||||
transport.mockImplementation(async (_url: string, init: RequestInit) => new Response(new ReadableStream({
|
||||
start(stream) { init.signal!.addEventListener('abort', () => stream.error(init.signal!.reason), { once: true }); },
|
||||
pull() { reading(); return new Promise<void>(() => {}); },
|
||||
}, { highWaterMark: 0 }), { headers: { 'content-type': 'text/event-stream', 'request-id': 'req_quiet' } }));
|
||||
const controller = new AbortController();
|
||||
const rejected = callJudge('score', 'claude-fable-5-1', { max_tokens: 65_536, stream: true, signal: controller.signal }).catch(error => error);
|
||||
await connected;
|
||||
controller.abort(new Error('deadline'));
|
||||
await rejected;
|
||||
const event = JSON.parse(diagnostics.mock.calls[0]![0]);
|
||||
expect(event).toMatchObject({ httpRequests: 1, httpStatuses: [200], requestId: 'req_quiet', firstEventMs: null, block: null });
|
||||
expect(event.connectedMs).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('SDK-internal 429 retries are counted, so throttling never reads as a silent stream', async () => {
|
||||
let resent!: () => void;
|
||||
const retried = new Promise<void>(resolve => { resent = resolve; });
|
||||
transport.mockResolvedValueOnce(new Response(JSON.stringify({ type: 'error', error: { type: 'rate_limit_error', message: 'slow down' } }),
|
||||
{ status: 429, headers: { 'content-type': 'application/json', 'retry-after-ms': '1' } }));
|
||||
transport.mockImplementationOnce((_url: string, init: RequestInit) => new Promise((_resolve, reject) => {
|
||||
init.signal!.addEventListener('abort', () => reject(init.signal!.reason), { once: true });
|
||||
resent();
|
||||
}));
|
||||
const controller = new AbortController();
|
||||
const rejected = callJudge('score', 'claude-fable-5-1', { max_tokens: 65_536, stream: true, signal: controller.signal }).catch(error => error);
|
||||
await retried;
|
||||
controller.abort(new Error('deadline'));
|
||||
await rejected;
|
||||
expect(JSON.parse(diagnostics.mock.calls[0]![0])).toMatchObject({ attempts: 1, httpRequests: 2, httpStatuses: [429],
|
||||
retryWaitsMs: [], connectedMs: null, firstEventMs: null });
|
||||
});
|
||||
|
||||
test('an event the diagnostic cannot read never changes the judge result', async () => {
|
||||
const unreadable = response();
|
||||
const body = (await unreadable.text()).replace('"thinking":"PRIVATE_THINKING"', '"thinking":null');
|
||||
transport.mockResolvedValue(new Response(body, { headers: { 'content-type': 'text/event-stream' } }));
|
||||
expect(await callJudge('score the whole bundle', 'claude-fable-5-1', {
|
||||
max_tokens: 65_536, stream: true, jsonSchema: WORKFLOW_JUDGE_RESPONSE_SCHEMA,
|
||||
})).toEqual(scores);
|
||||
});
|
||||
|
||||
test('an abort reason is retained at most 200 characters long', async () => {
|
||||
transport.mockImplementation((_url: string, init: RequestInit) => new Promise((_resolve, reject) => {
|
||||
init.signal!.addEventListener('abort', () => reject(init.signal!.reason), { once: true });
|
||||
}));
|
||||
const controller = new AbortController();
|
||||
const rejected = callJudge('score', 'claude-fable-5-1', { max_tokens: 65_536, stream: true, signal: controller.signal }).catch(error => error);
|
||||
controller.abort(new Error('x'.repeat(500)));
|
||||
await rejected;
|
||||
expect(JSON.parse(diagnostics.mock.calls[0]![0]).reason).toBe('x'.repeat(200));
|
||||
});
|
||||
Reference in new issue
Block a user