Skip to content

Commit 98c0298

Browse files
committed
fix: use SDK SessionEvent type for forward compat with copilot-sdk 0.3.0
The auditor's session.on() handler used an inline type annotation that is incompatible with the SessionEvent discriminated union in @github/copilot-sdk 0.3.0. Import and use the SDK's SessionEvent type instead — works with both 0.2.x and 0.3.x. Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent 7deaef6 commit 98c0298

1 file changed

Lines changed: 118 additions & 118 deletions

File tree

src/plugin-system/auditor.ts

Lines changed: 118 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import { createWriteStream, mkdirSync, type WriteStream } from "node:fs";
1515
import { createRequire } from "node:module";
1616
import { join } from "node:path";
1717
import { tmpdir } from "node:os";
18-
import { approveAll, type CopilotClient } from "@github/copilot-sdk";
18+
import {
19+
approveAll,
20+
type CopilotClient,
21+
type SessionEvent,
22+
} from "@github/copilot-sdk";
1923
import type {
2024
PluginManifest,
2125
AuditResult,
@@ -655,130 +659,126 @@ export async function deepAudit(
655659
}
656660
signal?.addEventListener("abort", onAbort, { once: true });
657661

658-
const unsubscribe = auditSession.on(
659-
(event: { type: string; data?: Record<string, unknown> }) => {
660-
// Trace every event type so we can see what's happening
661-
trace(
662-
`event: ${event.type} ${JSON.stringify(event.data ?? {}).slice(0, 300)}`,
663-
);
664-
665-
// Only reset inactivity timer on CONTENT-BEARING events.
666-
// Usage events and other housekeeping must NOT reset the
667-
// timer — otherwise the audit can hang indefinitely getting
668-
// usage events with no actual content.
669-
const isContentEvent =
670-
event.type === "assistant.message" ||
671-
event.type === "assistant.message_delta" ||
672-
event.type === "assistant.reasoning_delta" ||
673-
event.type === "assistant.turn_start" ||
674-
event.type === "assistant.turn_end";
675-
676-
if (isContentEvent) {
677-
clearTimeout(timeoutId);
678-
timeoutId = setTimeout(() => {
679-
unsubscribe();
680-
signal?.removeEventListener("abort", onAbort);
681-
reject(
682-
new Error(
683-
`Audit timed out — no events for ${AUDIT_INACTIVITY_MS / 1000}s`,
684-
),
685-
);
686-
}, AUDIT_INACTIVITY_MS);
687-
}
662+
const unsubscribe = auditSession.on((event: SessionEvent) => {
663+
// Trace every event type so we can see what's happening
664+
trace(
665+
`event: ${event.type} ${JSON.stringify(event.data ?? {}).slice(0, 300)}`,
666+
);
688667

689-
if (event.type === "assistant.message") {
690-
// Final complete message — extract content
691-
const content = (event.data as { content?: string })?.content;
692-
if (content) text = content;
693-
} else if (event.type === "assistant.reasoning_delta") {
694-
// Model is reasoning — forward to progress callback.
695-
// The progress callback handles display logic (e.g.
696-
// suppressing the preview after streaming starts).
697-
const delta = (event.data as { deltaContent?: string })
698-
?.deltaContent;
699-
if (delta) {
700-
progress("reasoning", delta);
701-
}
702-
} else if (event.type === "assistant.message_delta") {
703-
// Streaming delta — accumulate text for the final response.
704-
// Skip whitespace-only deltas for progress display — the
705-
// model can emit "\n\n" before reasoning starts (undocumented
706-
// behaviour). We still accumulate the text but don't trigger
707-
// the "Receiving audit report" UI for whitespace.
708-
const delta = (event.data as { deltaContent?: string })
709-
?.deltaContent;
710-
if (delta) {
711-
text += delta;
712-
if (text.trim().length > 0) {
713-
progress(
714-
"streaming",
715-
`Receiving audit report (${text.length.toLocaleString()} chars)...`,
716-
);
717-
}
718-
}
719-
} else if (
720-
event.type === "assistant.turn_start" ||
721-
event.type === "assistant.turn_end"
722-
) {
723-
// Turn lifecycle events — the CLI server fires these during
724-
// multi-call continuation loops. They're the ONLY reliable
725-
// life sign during dead zones where no message/reasoning
726-
// deltas arrive (known SDK bug — see github/copilot-sdk#524).
727-
// Forward to progress for spinner feedback.
728-
const turnId = (event.data as { turnId?: string })?.turnId;
729-
progress(
730-
"turn",
731-
`${event.type === "assistant.turn_start" ? "Turn" : "Turn complete"} ${turnId ?? ""}`.trim(),
732-
);
733-
} else if (event.type === "assistant.usage") {
734-
// Accumulate usage stats — don't print each one.
735-
// We emit a single aggregate summary at completion.
736-
const d = event.data as {
737-
inputTokens?: number;
738-
outputTokens?: number;
739-
cacheReadTokens?: number;
740-
cost?: number;
741-
duration?: number;
742-
};
743-
totalInputTokens += d.inputTokens ?? 0;
744-
totalOutputTokens += d.outputTokens ?? 0;
745-
totalCacheReadTokens += d.cacheReadTokens ?? 0;
746-
totalCost += d.cost ?? 0;
747-
totalDurationMs += d.duration ?? 0;
748-
usageEventCount++;
749-
// Update spinner so user knows it's still alive
750-
progress(
751-
"usage-tick",
752-
`Analysis in progress (${usageEventCount} API call${usageEventCount === 1 ? "" : "s"})...`,
753-
);
754-
} else if (event.type === "session.idle") {
755-
clearTimeout(timeoutId);
668+
// Only reset inactivity timer on CONTENT-BEARING events.
669+
// Usage events and other housekeeping must NOT reset the
670+
// timer — otherwise the audit can hang indefinitely getting
671+
// usage events with no actual content.
672+
const isContentEvent =
673+
event.type === "assistant.message" ||
674+
event.type === "assistant.message_delta" ||
675+
event.type === "assistant.reasoning_delta" ||
676+
event.type === "assistant.turn_start" ||
677+
event.type === "assistant.turn_end";
678+
679+
if (isContentEvent) {
680+
clearTimeout(timeoutId);
681+
timeoutId = setTimeout(() => {
756682
unsubscribe();
757683
signal?.removeEventListener("abort", onAbort);
758-
// Emit aggregated usage summary
759-
if (usageEventCount > 0) {
684+
reject(
685+
new Error(
686+
`Audit timed out — no events for ${AUDIT_INACTIVITY_MS / 1000}s`,
687+
),
688+
);
689+
}, AUDIT_INACTIVITY_MS);
690+
}
691+
692+
if (event.type === "assistant.message") {
693+
// Final complete message — extract content
694+
const content = (event.data as { content?: string })?.content;
695+
if (content) text = content;
696+
} else if (event.type === "assistant.reasoning_delta") {
697+
// Model is reasoning — forward to progress callback.
698+
// The progress callback handles display logic (e.g.
699+
// suppressing the preview after streaming starts).
700+
const delta = (event.data as { deltaContent?: string })?.deltaContent;
701+
if (delta) {
702+
progress("reasoning", delta);
703+
}
704+
} else if (event.type === "assistant.message_delta") {
705+
// Streaming delta — accumulate text for the final response.
706+
// Skip whitespace-only deltas for progress display — the
707+
// model can emit "\n\n" before reasoning starts (undocumented
708+
// behaviour). We still accumulate the text but don't trigger
709+
// the "Receiving audit report" UI for whitespace.
710+
const delta = (event.data as { deltaContent?: string })?.deltaContent;
711+
if (delta) {
712+
text += delta;
713+
if (text.trim().length > 0) {
760714
progress(
761-
"usage",
762-
JSON.stringify({
763-
inputTokens: totalInputTokens,
764-
outputTokens: totalOutputTokens,
765-
cacheReadTokens: totalCacheReadTokens,
766-
cost: totalCost,
767-
duration: totalDurationMs,
768-
}),
715+
"streaming",
716+
`Receiving audit report (${text.length.toLocaleString()} chars)...`,
769717
);
770718
}
771-
resolve(text);
772-
} else if (event.type === "session.error") {
773-
clearTimeout(timeoutId);
774-
unsubscribe();
775-
signal?.removeEventListener("abort", onAbort);
776-
const msg =
777-
(event.data as { message?: string })?.message ?? "unknown error";
778-
reject(new Error(msg));
779719
}
780-
},
781-
);
720+
} else if (
721+
event.type === "assistant.turn_start" ||
722+
event.type === "assistant.turn_end"
723+
) {
724+
// Turn lifecycle events — the CLI server fires these during
725+
// multi-call continuation loops. They're the ONLY reliable
726+
// life sign during dead zones where no message/reasoning
727+
// deltas arrive (known SDK bug — see github/copilot-sdk#524).
728+
// Forward to progress for spinner feedback.
729+
const turnId = (event.data as { turnId?: string })?.turnId;
730+
progress(
731+
"turn",
732+
`${event.type === "assistant.turn_start" ? "Turn" : "Turn complete"} ${turnId ?? ""}`.trim(),
733+
);
734+
} else if (event.type === "assistant.usage") {
735+
// Accumulate usage stats — don't print each one.
736+
// We emit a single aggregate summary at completion.
737+
const d = event.data as {
738+
inputTokens?: number;
739+
outputTokens?: number;
740+
cacheReadTokens?: number;
741+
cost?: number;
742+
duration?: number;
743+
};
744+
totalInputTokens += d.inputTokens ?? 0;
745+
totalOutputTokens += d.outputTokens ?? 0;
746+
totalCacheReadTokens += d.cacheReadTokens ?? 0;
747+
totalCost += d.cost ?? 0;
748+
totalDurationMs += d.duration ?? 0;
749+
usageEventCount++;
750+
// Update spinner so user knows it's still alive
751+
progress(
752+
"usage-tick",
753+
`Analysis in progress (${usageEventCount} API call${usageEventCount === 1 ? "" : "s"})...`,
754+
);
755+
} else if (event.type === "session.idle") {
756+
clearTimeout(timeoutId);
757+
unsubscribe();
758+
signal?.removeEventListener("abort", onAbort);
759+
// Emit aggregated usage summary
760+
if (usageEventCount > 0) {
761+
progress(
762+
"usage",
763+
JSON.stringify({
764+
inputTokens: totalInputTokens,
765+
outputTokens: totalOutputTokens,
766+
cacheReadTokens: totalCacheReadTokens,
767+
cost: totalCost,
768+
duration: totalDurationMs,
769+
}),
770+
);
771+
}
772+
resolve(text);
773+
} else if (event.type === "session.error") {
774+
clearTimeout(timeoutId);
775+
unsubscribe();
776+
signal?.removeEventListener("abort", onAbort);
777+
const msg =
778+
(event.data as { message?: string })?.message ?? "unknown error";
779+
reject(new Error(msg));
780+
}
781+
});
782782

783783
// Start inactivity timer
784784
timeoutId = setTimeout(() => {

0 commit comments

Comments
 (0)