mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-11 20:07:00 -07:00
!feat(core): surface loop detection as a warning
This commit is contained in:
@@ -395,15 +395,18 @@ describe('translateEvent', () => {
|
||||
});
|
||||
|
||||
describe('LoopDetected events', () => {
|
||||
it('emits a custom loop_detected event', () => {
|
||||
it('emits a non-fatal warning error event', () => {
|
||||
state.streamStartEmitted = true;
|
||||
const event: ServerGeminiStreamEvent = {
|
||||
type: GeminiEventType.LoopDetected,
|
||||
};
|
||||
const result = translateEvent(event, state);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]?.type).toBe('custom');
|
||||
expect((result[0] as AgentEvent<'custom'>).kind).toBe('loop_detected');
|
||||
expect(result[0]?.type).toBe('error');
|
||||
const loopWarning = result[0] as AgentEvent<'error'>;
|
||||
expect(loopWarning.fatal).toBe(false);
|
||||
expect(loopWarning.message).toBe('Loop detected, stopping execution');
|
||||
expect(loopWarning._meta?.['code']).toBe('LOOP_DETECTED');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -676,6 +679,10 @@ describe('mapError', () => {
|
||||
expect(result.status).toBe('RESOURCE_EXHAUSTED');
|
||||
expect(result.message).toBe('Rate limit');
|
||||
expect(result.fatal).toBe(true);
|
||||
expect(result._meta?.['rawError']).toEqual({
|
||||
message: 'Rate limit',
|
||||
status: 429,
|
||||
});
|
||||
});
|
||||
|
||||
it('maps Error instances', () => {
|
||||
|
||||
@@ -59,13 +59,12 @@ export function createTranslationState(streamId?: string): TranslationState {
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function makeEvent(
|
||||
type: AgentEventType,
|
||||
function makeEvent<T extends AgentEventType>(
|
||||
type: T,
|
||||
state: TranslationState,
|
||||
payload: Partial<AgentEvent>,
|
||||
payload: Partial<AgentEvent<T>>,
|
||||
): AgentEvent {
|
||||
const id = `${state.streamId}-${state.eventCounter++}`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- constructing AgentEvent from common fields + payload
|
||||
return {
|
||||
...payload,
|
||||
id,
|
||||
@@ -169,12 +168,13 @@ export function translateEvent(
|
||||
case GeminiEventType.LoopDetected:
|
||||
ensureStreamStart(state, out);
|
||||
out.push(
|
||||
makeEvent('custom', state, {
|
||||
kind: 'loop_detected',
|
||||
makeEvent('error', state, {
|
||||
status: 'INTERNAL',
|
||||
message: 'Loop detected, stopping execution',
|
||||
fatal: false,
|
||||
_meta: { code: 'LOOP_DETECTED' },
|
||||
}),
|
||||
);
|
||||
// No agent_end — the stream continues. Consumer decides how to handle:
|
||||
// non-interactive emits a warning, interactive shows a confirmation dialog.
|
||||
break;
|
||||
|
||||
case GeminiEventType.ContextWindowWillOverflow:
|
||||
@@ -380,7 +380,8 @@ export function mapHttpToGrpcStatus(
|
||||
|
||||
/**
|
||||
* Maps a StructuredError (or unknown error value) to an ErrorData payload.
|
||||
* Preserves error metadata (name, code, stack) in _meta.
|
||||
* Preserves selected error metadata in _meta and includes raw structured
|
||||
* errors for lossless debugging.
|
||||
*/
|
||||
export function mapError(
|
||||
error: unknown,
|
||||
@@ -397,14 +398,13 @@ export function mapError(
|
||||
}
|
||||
}
|
||||
|
||||
const hasMeta = Object.keys(meta).length > 0;
|
||||
|
||||
if (isStructuredError(error)) {
|
||||
const structuredMeta = { ...meta, rawError: error };
|
||||
return {
|
||||
status: mapHttpToGrpcStatus(error.status),
|
||||
message: error.message,
|
||||
fatal: true,
|
||||
...(hasMeta ? { _meta: meta } : {}),
|
||||
_meta: structuredMeta,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -413,7 +413,7 @@ export function mapError(
|
||||
status: 'INTERNAL',
|
||||
message: error.message,
|
||||
fatal: true,
|
||||
...(hasMeta ? { _meta: meta } : {}),
|
||||
...(Object.keys(meta).length > 0 ? { _meta: meta } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user