-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathsdkTestHelper.ts
More file actions
128 lines (117 loc) · 4.56 KB
/
Copy pathsdkTestHelper.ts
File metadata and controls
128 lines (117 loc) · 4.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
import { AssistantMessageEvent, CopilotSession, SessionEvent } from "../../../src";
export async function getFinalAssistantMessage(
session: CopilotSession,
{ alreadyIdle = false }: { alreadyIdle?: boolean } = {}
): Promise<AssistantMessageEvent> {
// Install the live subscription (via getFutureFinalResponse) before issuing the
// existing-messages RPC so we don't miss events that arrive while that RPC is in flight.
const futurePromise = getFutureFinalResponse(session);
// We may end up returning from the existing-messages path; attach a noop handler so
// the unawaited future-response rejection doesn't surface as an unhandled rejection.
futurePromise.catch(() => {});
const existing = await getExistingFinalResponse(session, alreadyIdle);
if (existing) {
return existing;
}
return futurePromise;
}
async function getExistingFinalResponse(
session: CopilotSession,
alreadyIdle: boolean = false
): Promise<AssistantMessageEvent | undefined> {
const messages = await session.getEvents();
const finalUserMessageIndex = messages.findLastIndex((m) => m.type === "user.message");
const currentTurnMessages =
finalUserMessageIndex < 0 ? messages : messages.slice(finalUserMessageIndex);
const currentTurnError = currentTurnMessages.find((m) => m.type === "session.error");
if (currentTurnError) {
const error = new Error(currentTurnError.data.message);
error.stack = currentTurnError.data.stack;
throw error;
}
const sessionIdleMessageIndex = alreadyIdle
? currentTurnMessages.length
: currentTurnMessages.findIndex((m) => m.type === "session.idle");
if (sessionIdleMessageIndex !== -1) {
return currentTurnMessages
.slice(0, sessionIdleMessageIndex)
.findLast((m) => m.type === "assistant.message") as AssistantMessageEvent | undefined;
}
return undefined;
}
function getFutureFinalResponse(session: CopilotSession): Promise<AssistantMessageEvent> {
return new Promise<AssistantMessageEvent>((resolve, reject) => {
let finalAssistantMessage: AssistantMessageEvent | undefined;
session.on((event) => {
if (event.type === "assistant.message") {
finalAssistantMessage = event;
} else if (event.type === "session.idle") {
if (!finalAssistantMessage) {
reject(
new Error("Received session.idle without a preceding assistant.message")
);
} else {
resolve(finalAssistantMessage);
}
} else if (event.type === "session.error") {
const error = new Error(event.data.message);
error.stack = event.data.stack;
reject(error);
}
});
});
}
export async function retry(
message: string,
fn: () => Promise<void>,
maxTries: number = 100,
delay: number = 100
) {
let failedAttempts = 0;
while (true) {
try {
await fn();
return;
} catch (error: unknown) {
failedAttempts++;
if (failedAttempts >= maxTries) {
throw new Error(
`Failed to ${message} after ${maxTries} attempts\n${formatError(error)}`
);
}
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
export function formatError(error: unknown): string {
if (error instanceof Error) {
return String(error);
} else if (typeof error === "object" && error !== null) {
try {
return JSON.stringify(error);
} catch {
return "[object with circular reference]";
}
} else {
return String(error);
}
}
export function getNextEventOfType(
session: CopilotSession,
eventType: SessionEvent["type"]
): Promise<SessionEvent> {
return new Promise<SessionEvent>((resolve, reject) => {
const unsubscribe = session.on((event) => {
if (event.type === eventType) {
unsubscribe();
resolve(event);
} else if (event.type === "session.error") {
unsubscribe();
reject(new Error(`${event.data.message}\n${event.data.stack}`));
}
});
});
}