first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import type { SseErrorPayload } from '../errors';
|
||||
import type { SseTransportEvent } from './common';
|
||||
|
||||
export const CHAT_SSE_PROTOCOL_VERSION = 1;
|
||||
|
||||
export interface ChatSseStartPayload {
|
||||
runId?: string;
|
||||
agentId?: string;
|
||||
bin: string;
|
||||
protocolVersion?: typeof CHAT_SSE_PROTOCOL_VERSION;
|
||||
/** Legacy daemon-internal absolute cwd. Kept for compatibility during W2 adoption. */
|
||||
cwd?: string | null;
|
||||
projectId?: string | null;
|
||||
model?: string | null;
|
||||
reasoning?: string | null;
|
||||
}
|
||||
|
||||
export interface ChatSseChunkPayload {
|
||||
chunk: string;
|
||||
}
|
||||
|
||||
export interface ChatSseEndPayload {
|
||||
code: number | null;
|
||||
signal?: string | null;
|
||||
status?: 'succeeded' | 'failed' | 'canceled';
|
||||
}
|
||||
|
||||
export type DaemonAgentPayload =
|
||||
| { type: 'status'; label: string; model?: string; ttftMs?: number; detail?: string }
|
||||
| { type: 'text_delta'; delta: string }
|
||||
| { type: 'thinking_delta'; delta: string }
|
||||
| { type: 'thinking_start' }
|
||||
| { type: 'tool_use'; id: string; name: string; input: unknown }
|
||||
| { type: 'tool_result'; toolUseId: string; content: string; isError?: boolean }
|
||||
| { type: 'usage'; usage?: { input_tokens?: number; output_tokens?: number }; costUsd?: number; durationMs?: number }
|
||||
| { type: 'raw'; line: string };
|
||||
|
||||
export type ChatSseEvent =
|
||||
| SseTransportEvent<'start', ChatSseStartPayload>
|
||||
| SseTransportEvent<'agent', DaemonAgentPayload>
|
||||
| SseTransportEvent<'stdout', ChatSseChunkPayload>
|
||||
| SseTransportEvent<'stderr', ChatSseChunkPayload>
|
||||
| SseTransportEvent<'error', SseErrorPayload>
|
||||
| SseTransportEvent<'end', ChatSseEndPayload>;
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface SseTransportEvent<Name extends string, Payload> {
|
||||
id?: string;
|
||||
event: Name;
|
||||
data: Payload;
|
||||
}
|
||||
|
||||
export type SseEventName<Event> = Event extends SseTransportEvent<infer Name, unknown> ? Name : never;
|
||||
|
||||
export type SseEventPayload<Event, Name extends string> = Event extends SseTransportEvent<Name, infer Payload>
|
||||
? Payload
|
||||
: never;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { PanelEvent } from '../critique';
|
||||
import {
|
||||
panelEventToSse,
|
||||
type CritiqueSseEvent,
|
||||
CRITIQUE_SSE_EVENT_NAMES,
|
||||
} from './critique';
|
||||
|
||||
describe('CritiqueSseEvent', () => {
|
||||
it('panelEventToSse maps PanelEvent.type "run_started" to event "critique.run_started"', () => {
|
||||
const e: PanelEvent = {
|
||||
type: 'run_started', runId: 'r1', protocolVersion: 1,
|
||||
cast: ['designer','critic','brand','a11y','copy'],
|
||||
maxRounds: 3, threshold: 8, scale: 10,
|
||||
};
|
||||
const sse = panelEventToSse(e);
|
||||
expect(sse.event).toBe('critique.run_started');
|
||||
expect(sse.data).toMatchObject({
|
||||
runId: 'r1', protocolVersion: 1, maxRounds: 3, threshold: 8, scale: 10,
|
||||
});
|
||||
// No 'type' field on the SSE payload.
|
||||
expect((sse.data as Record<string, unknown>).type).toBeUndefined();
|
||||
});
|
||||
|
||||
it('panelEventToSse round-trips every PanelEvent type', () => {
|
||||
const samples: PanelEvent[] = [
|
||||
{ type: 'run_started', runId: 'r', protocolVersion: 1, cast: ['critic'], maxRounds: 3, threshold: 8, scale: 10 },
|
||||
{ type: 'panelist_open', runId: 'r', round: 1, role: 'designer' },
|
||||
{ type: 'panelist_dim', runId: 'r', round: 1, role: 'critic', dimName: 'contrast', dimScore: 4, dimNote: '' },
|
||||
{ type: 'panelist_must_fix', runId: 'r', round: 1, role: 'a11y', text: '' },
|
||||
{ type: 'panelist_close', runId: 'r', round: 1, role: 'critic', score: 6 },
|
||||
{ type: 'round_end', runId: 'r', round: 1, composite: 6, mustFix: 7, decision: 'continue', reason: '' },
|
||||
{ type: 'ship', runId: 'r', round: 3, composite: 8.6, status: 'shipped', artifactRef: { projectId: 'p', artifactId: 'a' }, summary: '' },
|
||||
{ type: 'degraded', runId: 'r', reason: 'malformed_block', adapter: 'pi-rpc' },
|
||||
{ type: 'interrupted', runId: 'r', bestRound: 2, composite: 7.86 },
|
||||
{ type: 'failed', runId: 'r', cause: 'cli_exit_nonzero' },
|
||||
{ type: 'parser_warning', runId: 'r', kind: 'weak_debate', position: 0 },
|
||||
];
|
||||
for (const e of samples) {
|
||||
const sse = panelEventToSse(e);
|
||||
expect(sse.event).toBe(`critique.${e.type}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('CRITIQUE_SSE_EVENT_NAMES contains all 11 critique.* names', () => {
|
||||
expect(CRITIQUE_SSE_EVENT_NAMES).toContain('critique.run_started');
|
||||
expect(CRITIQUE_SSE_EVENT_NAMES).toContain('critique.parser_warning');
|
||||
expect(CRITIQUE_SSE_EVENT_NAMES.length).toBe(11);
|
||||
// Each name has the 'critique.' prefix.
|
||||
for (const name of CRITIQUE_SSE_EVENT_NAMES) {
|
||||
expect(name.startsWith('critique.')).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { PanelEvent } from '../critique';
|
||||
import type { SseTransportEvent } from './common';
|
||||
|
||||
type PayloadOf<T extends PanelEvent['type']> = Omit<Extract<PanelEvent, { type: T }>, 'type'>;
|
||||
|
||||
export type CritiqueSseEvent =
|
||||
| SseTransportEvent<'critique.run_started', PayloadOf<'run_started'>>
|
||||
| SseTransportEvent<'critique.panelist_open', PayloadOf<'panelist_open'>>
|
||||
| SseTransportEvent<'critique.panelist_dim', PayloadOf<'panelist_dim'>>
|
||||
| SseTransportEvent<'critique.panelist_must_fix', PayloadOf<'panelist_must_fix'>>
|
||||
| SseTransportEvent<'critique.panelist_close', PayloadOf<'panelist_close'>>
|
||||
| SseTransportEvent<'critique.round_end', PayloadOf<'round_end'>>
|
||||
| SseTransportEvent<'critique.ship', PayloadOf<'ship'>>
|
||||
| SseTransportEvent<'critique.degraded', PayloadOf<'degraded'>>
|
||||
| SseTransportEvent<'critique.interrupted', PayloadOf<'interrupted'>>
|
||||
| SseTransportEvent<'critique.failed', PayloadOf<'failed'>>
|
||||
| SseTransportEvent<'critique.parser_warning', PayloadOf<'parser_warning'>>;
|
||||
|
||||
export const CRITIQUE_SSE_EVENT_NAMES = [
|
||||
'critique.run_started',
|
||||
'critique.panelist_open',
|
||||
'critique.panelist_dim',
|
||||
'critique.panelist_must_fix',
|
||||
'critique.panelist_close',
|
||||
'critique.round_end',
|
||||
'critique.ship',
|
||||
'critique.degraded',
|
||||
'critique.interrupted',
|
||||
'critique.failed',
|
||||
'critique.parser_warning',
|
||||
] as const satisfies readonly CritiqueSseEvent['event'][];
|
||||
|
||||
export type CritiqueSseEventName = typeof CRITIQUE_SSE_EVENT_NAMES[number];
|
||||
|
||||
export function panelEventToSse(e: PanelEvent): CritiqueSseEvent {
|
||||
const { type, ...payload } = e;
|
||||
// The cast is safe: each PanelEvent variant maps 1:1 to a CritiqueSseEvent variant
|
||||
// by prefixing the type with 'critique.' and moving every other field into data.
|
||||
return { event: `critique.${type}`, data: payload } as CritiqueSseEvent;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { ProxyStreamDeltaPayload, ProxyStreamEndPayload, ProxyStreamStartPayload } from '../api/proxy';
|
||||
import type { SseErrorPayload } from '../errors';
|
||||
import type { SseTransportEvent } from './common';
|
||||
|
||||
export const PROXY_SSE_PROTOCOL_VERSION = 1;
|
||||
|
||||
export type ProxySseEvent =
|
||||
| SseTransportEvent<'start', ProxyStreamStartPayload>
|
||||
| SseTransportEvent<'delta', ProxyStreamDeltaPayload>
|
||||
| SseTransportEvent<'error', SseErrorPayload>
|
||||
| SseTransportEvent<'end', ProxyStreamEndPayload>;
|
||||
Reference in New Issue
Block a user