assistant-cloud 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/AssistantCloud.d.ts +15 -17
- package/dist/AssistantCloud.d.ts.map +1 -1
- package/dist/AssistantCloud.js +7 -2
- package/dist/AssistantCloud.js.map +1 -1
- package/dist/AssistantCloudAPI.d.ts +69 -66
- package/dist/AssistantCloudAPI.d.ts.map +1 -1
- package/dist/AssistantCloudAPI.js.map +1 -1
- package/dist/AssistantCloudAuthStrategy.d.ts +23 -25
- package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
- package/dist/AssistantCloudAuthTokens.d.ts +6 -7
- package/dist/AssistantCloudAuthTokens.d.ts.map +1 -1
- package/dist/AssistantCloudEvents.d.ts +32 -25
- package/dist/AssistantCloudEvents.d.ts.map +1 -1
- package/dist/AssistantCloudEvents.js +65 -16
- package/dist/AssistantCloudEvents.js.map +1 -1
- package/dist/AssistantCloudFiles.d.ts +25 -26
- package/dist/AssistantCloudFiles.d.ts.map +1 -1
- package/dist/AssistantCloudProjectThreadMessages.d.ts +10 -11
- package/dist/AssistantCloudProjectThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudProjectThreads.d.ts +11 -12
- package/dist/AssistantCloudProjectThreads.d.ts.map +1 -1
- package/dist/AssistantCloudProjects.d.ts +3 -5
- package/dist/AssistantCloudProjects.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.d.ts +65 -53
- package/dist/AssistantCloudRuns.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.js +9 -4
- package/dist/AssistantCloudRuns.js.map +1 -1
- package/dist/AssistantCloudScores.d.ts +17 -19
- package/dist/AssistantCloudScores.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.d.ts +33 -31
- package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.js +9 -2
- package/dist/AssistantCloudThreadMessages.js.map +1 -1
- package/dist/AssistantCloudThreads.d.ts +37 -38
- package/dist/AssistantCloudThreads.d.ts.map +1 -1
- package/dist/CloudEngagementReporter.d.ts +53 -54
- package/dist/CloudEngagementReporter.d.ts.map +1 -1
- package/dist/CloudEngagementReporter.js +4 -1
- package/dist/CloudEngagementReporter.js.map +1 -1
- package/dist/CloudMessagePersistence.d.ts +55 -56
- package/dist/CloudMessagePersistence.d.ts.map +1 -1
- package/dist/CloudMessagePersistence.js +3 -0
- package/dist/CloudMessagePersistence.js.map +1 -1
- package/dist/CloudRunReporter.d.ts +6 -8
- package/dist/CloudRunReporter.d.ts.map +1 -1
- package/dist/FormattedCloudPersistence.d.ts +34 -36
- package/dist/FormattedCloudPersistence.d.ts.map +1 -1
- package/dist/ai-sdk/index.d.ts +10 -12
- package/dist/ai-sdk/index.d.ts.map +1 -1
- package/dist/cloudResponse.d.ts +2 -4
- package/dist/cloudResponse.d.ts.map +1 -1
- package/dist/generateThreadTitle.d.ts +8 -10
- package/dist/generateThreadTitle.d.ts.map +1 -1
- package/dist/index.d.ts +18 -17
- package/dist/index.d.ts.map +1 -0
- package/dist/instrumentMcpSampling.d.ts +30 -32
- package/dist/instrumentMcpSampling.d.ts.map +1 -1
- package/dist/runTelemetry.d.ts +86 -78
- package/dist/runTelemetry.d.ts.map +1 -1
- package/dist/runTelemetry.js +57 -15
- package/dist/runTelemetry.js.map +1 -1
- package/dist/telemetry/index.d.ts +13 -15
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/AssistantCloud.ts +17 -2
- package/src/AssistantCloudAPI.ts +4 -0
- package/src/AssistantCloudEvents.test.ts +116 -0
- package/src/AssistantCloudEvents.ts +81 -20
- package/src/AssistantCloudRuns.ts +35 -5
- package/src/AssistantCloudThreadMessages.ts +21 -1
- package/src/BackendTranscriptCopy.test.ts +79 -0
- package/src/CloudEngagementReporter.ts +9 -3
- package/src/CloudMessagePersistence.ts +4 -0
- package/src/ai-sdk/index.test.ts +2 -2
- package/src/runTelemetry.test.ts +226 -2
- package/src/runTelemetry.ts +121 -17
- package/src/tests/AssistantCloud.test.ts +22 -0
- package/src/tests/AssistantCloudRuns.test.ts +28 -0
- package/src/tests/CloudEngagementReporter.test.ts +45 -0
- package/src/tests/CloudRunReporter.test.ts +14 -2
- package/dist/tests/setup.d.ts +0 -1
- package/dist/tests/setup.js +0 -7
- package/dist/tests/setup.js.map +0 -1
|
@@ -30,11 +30,23 @@ export type AssistantCloudEvent = {
|
|
|
30
30
|
const FLUSH_SIZE = 20;
|
|
31
31
|
const MAX_BATCH_SIZE = 50;
|
|
32
32
|
const FLUSH_DELAY_MS = 2_000;
|
|
33
|
+
const RETRY_DELAYS_MS = [250, 1_000] as const;
|
|
34
|
+
const pendingClearers = new WeakMap<AssistantCloudEvents, () => void>();
|
|
35
|
+
|
|
36
|
+
export const clearPendingAssistantCloudEvents = (
|
|
37
|
+
events: AssistantCloudEvents,
|
|
38
|
+
): void => {
|
|
39
|
+
pendingClearers.get(events)?.();
|
|
40
|
+
};
|
|
33
41
|
|
|
34
42
|
export class AssistantCloudEvents {
|
|
35
43
|
private buffer: AssistantCloudEvent[] = [];
|
|
36
44
|
private timer: ReturnType<typeof setTimeout> | undefined;
|
|
37
45
|
private flushing: Promise<void> | undefined;
|
|
46
|
+
private retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
47
|
+
private resolveRetryDelay: (() => void) | undefined;
|
|
48
|
+
private bestEffortRequested = false;
|
|
49
|
+
private generation = 0;
|
|
38
50
|
|
|
39
51
|
private readonly cloud: AssistantCloudAPI;
|
|
40
52
|
private readonly isEnabled: () => boolean;
|
|
@@ -44,6 +56,7 @@ export class AssistantCloudEvents {
|
|
|
44
56
|
constructor(cloud: AssistantCloudAPI, isEnabled: () => boolean) {
|
|
45
57
|
this.cloud = cloud;
|
|
46
58
|
this.isEnabled = isEnabled;
|
|
59
|
+
pendingClearers.set(this, () => this.clearPending());
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
public track(event: AssistantCloudEvent): void {
|
|
@@ -52,7 +65,7 @@ export class AssistantCloudEvents {
|
|
|
52
65
|
this.listen();
|
|
53
66
|
this.buffer.push(normalizeEvent(event));
|
|
54
67
|
if (this.buffer.length >= FLUSH_SIZE) {
|
|
55
|
-
void this.flush();
|
|
68
|
+
void this.flush(true);
|
|
56
69
|
return;
|
|
57
70
|
}
|
|
58
71
|
|
|
@@ -68,55 +81,70 @@ export class AssistantCloudEvents {
|
|
|
68
81
|
return;
|
|
69
82
|
}
|
|
70
83
|
this.listening = true;
|
|
71
|
-
window.addEventListener("pagehide", this.
|
|
84
|
+
window.addEventListener("pagehide", this.flushBestEffort);
|
|
72
85
|
document.addEventListener("visibilitychange", this.onVisibilityChange);
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
private unlisten(): void {
|
|
76
89
|
if (!this.listening) return;
|
|
77
90
|
this.listening = false;
|
|
78
|
-
window.removeEventListener("pagehide", this.
|
|
91
|
+
window.removeEventListener("pagehide", this.flushBestEffort);
|
|
79
92
|
document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
public dispose(): void {
|
|
83
96
|
this.unlisten();
|
|
84
97
|
this.clearFlushTimer();
|
|
85
|
-
void this.
|
|
98
|
+
void this.flushBestEffort();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private clearPending(): void {
|
|
102
|
+
this.generation++;
|
|
103
|
+
this.buffer = [];
|
|
104
|
+
this.clearFlushTimer();
|
|
105
|
+
this.interruptRetryDelay();
|
|
106
|
+
this.unlisten();
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
private onVisibilityChange = () => {
|
|
89
110
|
if (document.visibilityState === "hidden") {
|
|
90
|
-
void this.
|
|
111
|
+
void this.flushBestEffort();
|
|
91
112
|
}
|
|
92
113
|
};
|
|
93
114
|
|
|
94
|
-
private
|
|
115
|
+
private flushBestEffort = () => {
|
|
116
|
+
if (this.flushing) {
|
|
117
|
+
this.bestEffortRequested = true;
|
|
118
|
+
this.interruptRetryDelay();
|
|
119
|
+
}
|
|
120
|
+
return this.flush(false);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
private flush = async (retryFailures: boolean): Promise<void> => {
|
|
95
124
|
if (!this.isEnabled()) {
|
|
96
|
-
this.
|
|
97
|
-
this.clearFlushTimer();
|
|
98
|
-
this.unlisten();
|
|
125
|
+
this.clearPending();
|
|
99
126
|
return;
|
|
100
127
|
}
|
|
101
128
|
if (this.flushing) return this.flushing;
|
|
102
129
|
|
|
103
130
|
this.clearFlushTimer();
|
|
104
|
-
const task = this.flushPending();
|
|
131
|
+
const task = this.flushPending(retryFailures);
|
|
105
132
|
this.flushing = task;
|
|
106
133
|
void task.then(() => {
|
|
107
134
|
if (this.flushing !== task) return;
|
|
108
135
|
this.flushing = undefined;
|
|
136
|
+
this.bestEffortRequested = false;
|
|
109
137
|
if (this.buffer.length === 0) {
|
|
110
138
|
this.clearFlushTimer();
|
|
111
139
|
this.unlisten();
|
|
112
140
|
} else {
|
|
113
|
-
void this.flush();
|
|
141
|
+
void this.flush(true);
|
|
114
142
|
}
|
|
115
143
|
});
|
|
116
144
|
return task;
|
|
117
145
|
};
|
|
118
146
|
|
|
119
|
-
private async flushPending(): Promise<void> {
|
|
147
|
+
private async flushPending(retryFailures: boolean): Promise<void> {
|
|
120
148
|
while (this.buffer.length > 0) {
|
|
121
149
|
if (!this.isEnabled()) {
|
|
122
150
|
this.buffer = [];
|
|
@@ -124,21 +152,54 @@ export class AssistantCloudEvents {
|
|
|
124
152
|
}
|
|
125
153
|
|
|
126
154
|
const events = this.buffer.splice(0, MAX_BATCH_SIZE);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
155
|
+
const generation = this.generation;
|
|
156
|
+
for (let attempt = 0; ; attempt++) {
|
|
157
|
+
try {
|
|
158
|
+
await this.cloud.makeRequest("/events", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
body: { events },
|
|
161
|
+
keepalive: true,
|
|
162
|
+
});
|
|
163
|
+
if (generation !== this.generation) return;
|
|
164
|
+
break;
|
|
165
|
+
} catch {
|
|
166
|
+
if (generation !== this.generation) return;
|
|
167
|
+
const delay =
|
|
168
|
+
retryFailures && !this.bestEffortRequested
|
|
169
|
+
? RETRY_DELAYS_MS[attempt]
|
|
170
|
+
: undefined;
|
|
171
|
+
if (delay === undefined) break;
|
|
172
|
+
await this.waitForRetry(delay);
|
|
173
|
+
if (generation !== this.generation) return;
|
|
174
|
+
if (!this.isEnabled()) return this.clearPending();
|
|
175
|
+
if (this.bestEffortRequested) break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
134
178
|
}
|
|
135
179
|
}
|
|
136
180
|
|
|
181
|
+
private waitForRetry(delay: number): Promise<void> {
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
const finish = () => {
|
|
184
|
+
this.retryTimer = undefined;
|
|
185
|
+
this.resolveRetryDelay = undefined;
|
|
186
|
+
resolve();
|
|
187
|
+
};
|
|
188
|
+
this.resolveRetryDelay = finish;
|
|
189
|
+
this.retryTimer = setTimeout(finish, delay);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private interruptRetryDelay(): void {
|
|
194
|
+
if (this.retryTimer !== undefined) clearTimeout(this.retryTimer);
|
|
195
|
+
this.resolveRetryDelay?.();
|
|
196
|
+
}
|
|
197
|
+
|
|
137
198
|
private scheduleFlush() {
|
|
138
199
|
if (this.timer !== undefined) return;
|
|
139
200
|
this.timer = setTimeout(() => {
|
|
140
201
|
this.timer = undefined;
|
|
141
|
-
void this.flush();
|
|
202
|
+
void this.flush(true);
|
|
142
203
|
}, FLUSH_DELAY_MS);
|
|
143
204
|
}
|
|
144
205
|
|
|
@@ -19,7 +19,18 @@ type AssistantCloudRunsStreamBody = {
|
|
|
19
19
|
export type AssistantCloudRunReport = {
|
|
20
20
|
thread_id: string;
|
|
21
21
|
status: "completed" | "incomplete" | "error";
|
|
22
|
-
outcome_type?:
|
|
22
|
+
outcome_type?:
|
|
23
|
+
| "rate_limited"
|
|
24
|
+
| "validation_failed"
|
|
25
|
+
| "provider_error"
|
|
26
|
+
| "server_error"
|
|
27
|
+
| "budget_denied"
|
|
28
|
+
| "persistence_error"
|
|
29
|
+
| "aborted"
|
|
30
|
+
| "timeout"
|
|
31
|
+
| "disconnected"
|
|
32
|
+
| "length"
|
|
33
|
+
| "content_filter";
|
|
23
34
|
message_id?: string;
|
|
24
35
|
first_token_ms?: number;
|
|
25
36
|
release?: string;
|
|
@@ -27,6 +38,7 @@ export type AssistantCloudRunReport = {
|
|
|
27
38
|
tags?: string[];
|
|
28
39
|
provider?: string;
|
|
29
40
|
trace_id?: string;
|
|
41
|
+
root_span_id?: string;
|
|
30
42
|
error_code?: string;
|
|
31
43
|
error?: string;
|
|
32
44
|
total_steps?: number;
|
|
@@ -40,15 +52,24 @@ export type AssistantCloudRunReport = {
|
|
|
40
52
|
start_ms?: number;
|
|
41
53
|
end_ms?: number;
|
|
42
54
|
finish_reason?: string;
|
|
55
|
+
input?: string;
|
|
43
56
|
}[];
|
|
44
57
|
input_tokens?: number;
|
|
45
58
|
output_tokens?: number;
|
|
46
59
|
reasoning_tokens?: number;
|
|
47
60
|
cached_input_tokens?: number;
|
|
61
|
+
cost_usd?: number;
|
|
62
|
+
cost_details?: {
|
|
63
|
+
input?: number;
|
|
64
|
+
input_cached_tokens?: number;
|
|
65
|
+
output?: number;
|
|
66
|
+
total?: number;
|
|
67
|
+
};
|
|
48
68
|
model_id?: string;
|
|
49
69
|
provider_type?: string;
|
|
50
70
|
duration_ms?: number;
|
|
51
71
|
output_text?: string;
|
|
72
|
+
attributes?: Record<string, unknown>;
|
|
52
73
|
metadata?: Record<string, unknown>;
|
|
53
74
|
};
|
|
54
75
|
|
|
@@ -62,6 +83,7 @@ export class AssistantCloudRuns {
|
|
|
62
83
|
public __internal_getAssistantOptions(assistantId: string) {
|
|
63
84
|
return {
|
|
64
85
|
api: `${this.cloud._baseUrl}/v1/runs/stream`,
|
|
86
|
+
protocol: "ui-message-stream" as const,
|
|
65
87
|
headers: async () => {
|
|
66
88
|
const headers = await this.cloud._auth.getAuthHeaders();
|
|
67
89
|
if (!headers) throw new Error("Authorization failed");
|
|
@@ -71,10 +93,18 @@ export class AssistantCloudRuns {
|
|
|
71
93
|
"Aui-Sdk": this.cloud.sdkHeader(),
|
|
72
94
|
};
|
|
73
95
|
},
|
|
74
|
-
body: {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
body: async (options?: { threadId?: string }) => {
|
|
97
|
+
const threadId = options?.threadId;
|
|
98
|
+
if (threadId === undefined) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Assistant Cloud runs need a thread; the thread list adapter has not assigned a remote id to this thread yet.",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
assistant_id: assistantId,
|
|
105
|
+
response_format: "vercel-ai-data-stream/v1",
|
|
106
|
+
thread_id: threadId,
|
|
107
|
+
};
|
|
78
108
|
},
|
|
79
109
|
};
|
|
80
110
|
}
|
|
@@ -19,6 +19,7 @@ export type CloudMessage = {
|
|
|
19
19
|
updated_at: Date;
|
|
20
20
|
format: "aui/v0" | string;
|
|
21
21
|
content: ReadonlyJSONObject;
|
|
22
|
+
external_id?: string | null | undefined;
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
type AssistantCloudThreadMessageListQuery = {
|
|
@@ -35,6 +36,8 @@ type AssistantCloudThreadMessageCreateBody = {
|
|
|
35
36
|
parent_id: string | null;
|
|
36
37
|
format: "aui/v0" | string;
|
|
37
38
|
content: ReadonlyJSONObject;
|
|
39
|
+
external_id?: string | undefined;
|
|
40
|
+
parent_external_id?: string | undefined;
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
type AssistantCloudMessageCreateResponse = {
|
|
@@ -71,6 +74,10 @@ export const decodeCloudMessage = (
|
|
|
71
74
|
updated_at: readCloudTimestamp(message.updated_at, `${field}.updated_at`),
|
|
72
75
|
format: readCloudString(message.format, `${field}.format`),
|
|
73
76
|
content: readCloudJSONObject(message.content, `${field}.content`),
|
|
77
|
+
external_id: readCloudNullableString(
|
|
78
|
+
message.external_id ?? null,
|
|
79
|
+
`${field}.external_id`,
|
|
80
|
+
),
|
|
74
81
|
};
|
|
75
82
|
};
|
|
76
83
|
|
|
@@ -108,7 +115,20 @@ export class AssistantCloudThreadMessages {
|
|
|
108
115
|
const response = readCloudRecord(
|
|
109
116
|
await this.cloud.makeRequest(
|
|
110
117
|
`/threads/${encodeURIComponent(threadId)}/messages`,
|
|
111
|
-
{
|
|
118
|
+
{
|
|
119
|
+
method: "POST",
|
|
120
|
+
body: {
|
|
121
|
+
parent_id: body.parent_id,
|
|
122
|
+
format: body.format,
|
|
123
|
+
content: body.content,
|
|
124
|
+
...(body.external_id !== undefined
|
|
125
|
+
? { external_id: body.external_id }
|
|
126
|
+
: undefined),
|
|
127
|
+
...(body.parent_external_id !== undefined
|
|
128
|
+
? { parent_external_id: body.parent_external_id }
|
|
129
|
+
: undefined),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
112
132
|
),
|
|
113
133
|
"thread message create response",
|
|
114
134
|
);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { AssistantCloud } from "./AssistantCloud";
|
|
3
|
+
import type { AssistantCloudAPI } from "./AssistantCloudAPI";
|
|
4
|
+
import { AssistantCloudThreadMessages } from "./AssistantCloudThreadMessages";
|
|
5
|
+
import { CloudMessagePersistence } from "./CloudMessagePersistence";
|
|
6
|
+
|
|
7
|
+
describe("backend transcript cloud client", () => {
|
|
8
|
+
it("sends external ids only when supplied", async () => {
|
|
9
|
+
const makeRequest = vi.fn().mockResolvedValue({ message_id: "cloud-1" });
|
|
10
|
+
const messages = new AssistantCloudThreadMessages({
|
|
11
|
+
makeRequest,
|
|
12
|
+
} as unknown as AssistantCloudAPI);
|
|
13
|
+
const body = { parent_id: null, format: "aui/v0", content: {} };
|
|
14
|
+
|
|
15
|
+
await messages.create("thread-1", body);
|
|
16
|
+
expect(makeRequest).toHaveBeenLastCalledWith("/threads/thread-1/messages", {
|
|
17
|
+
method: "POST",
|
|
18
|
+
body,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await messages.create("thread-1", {
|
|
22
|
+
...body,
|
|
23
|
+
external_id: "backend-1",
|
|
24
|
+
parent_external_id: "backend-0",
|
|
25
|
+
});
|
|
26
|
+
expect(makeRequest).toHaveBeenLastCalledWith("/threads/thread-1/messages", {
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: {
|
|
29
|
+
...body,
|
|
30
|
+
external_id: "backend-1",
|
|
31
|
+
parent_external_id: "backend-0",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("decodes external ids and treats an absent field as null", async () => {
|
|
37
|
+
const makeRequest = vi.fn().mockResolvedValue({
|
|
38
|
+
messages: [
|
|
39
|
+
{
|
|
40
|
+
id: "cloud-1",
|
|
41
|
+
parent_id: null,
|
|
42
|
+
height: 0,
|
|
43
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
44
|
+
updated_at: "2026-01-01T00:00:00.000Z",
|
|
45
|
+
format: "aui/v0",
|
|
46
|
+
content: {},
|
|
47
|
+
external_id: "backend-1",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "cloud-2",
|
|
51
|
+
parent_id: "cloud-1",
|
|
52
|
+
height: 1,
|
|
53
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
54
|
+
updated_at: "2026-01-01T00:00:00.000Z",
|
|
55
|
+
format: "aui/v0",
|
|
56
|
+
content: {},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
const messages = new AssistantCloudThreadMessages({
|
|
61
|
+
makeRequest,
|
|
62
|
+
} as unknown as AssistantCloudAPI);
|
|
63
|
+
|
|
64
|
+
const result = await messages.list("thread-1");
|
|
65
|
+
expect(result.messages.map((message) => message.external_id)).toEqual([
|
|
66
|
+
"backend-1",
|
|
67
|
+
null,
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("records a resolved local to cloud message mapping", async () => {
|
|
72
|
+
const persistence = new CloudMessagePersistence({} as AssistantCloud);
|
|
73
|
+
persistence.record("backend-1", "cloud-1");
|
|
74
|
+
|
|
75
|
+
expect(persistence.isPersisted("backend-1")).toBe(true);
|
|
76
|
+
expect(await persistence.getRemoteId("backend-1")).toBe("cloud-1");
|
|
77
|
+
expect(persistence.getResolvedRemoteId("backend-1")).toBe("cloud-1");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -13,13 +13,14 @@ export type EngagementEventIds = Pick<
|
|
|
13
13
|
* Turns the ids an integration knows (its own thread and message ids) into the
|
|
14
14
|
* ids the cloud stores. A send is the one event that may create the remote
|
|
15
15
|
* thread, so it asks for `awaitThread`; every other event reads the ids that
|
|
16
|
-
* already exist.
|
|
16
|
+
* already exist. Returning nothing declines the event: the thread is not one
|
|
17
|
+
* the integration knows, so there is nothing to attribute it to.
|
|
17
18
|
*/
|
|
18
19
|
export type EngagementIdResolver = (
|
|
19
20
|
threadId: string,
|
|
20
21
|
messageId: string | undefined,
|
|
21
22
|
options: { awaitThread: boolean },
|
|
22
|
-
) => EngagementEventIds | Promise<EngagementEventIds>;
|
|
23
|
+
) => EngagementEventIds | undefined | Promise<EngagementEventIds | undefined>;
|
|
23
24
|
|
|
24
25
|
type EngagementEventInit = Pick<AssistantCloudEvent, "value" | "props">;
|
|
25
26
|
|
|
@@ -86,9 +87,13 @@ export class CloudEngagementReporter {
|
|
|
86
87
|
public runStopped(threadId: string): void {
|
|
87
88
|
const startedAt = this.runStartedAt.get(threadId);
|
|
88
89
|
if (startedAt === undefined) return;
|
|
90
|
+
const stoppedAt = Date.now();
|
|
89
91
|
this.runStartedAt.delete(threadId);
|
|
92
|
+
// A stopped run is a run that ended, so the next send measures its idle
|
|
93
|
+
// time from here rather than from the last run allowed to finish.
|
|
94
|
+
remember(this.runEndedAt, threadId, stoppedAt);
|
|
90
95
|
this.track("run_stopped", threadId, undefined, {
|
|
91
|
-
value: Math.max(0,
|
|
96
|
+
value: Math.max(0, stoppedAt - startedAt),
|
|
92
97
|
});
|
|
93
98
|
}
|
|
94
99
|
|
|
@@ -223,6 +228,7 @@ export class CloudEngagementReporter {
|
|
|
223
228
|
void Promise.resolve()
|
|
224
229
|
.then(() => this.resolveIds(threadId, messageId, options))
|
|
225
230
|
.then((ids) => {
|
|
231
|
+
if (!ids) return;
|
|
226
232
|
if (kind === "thread_switched" && !ids.thread_id) return;
|
|
227
233
|
this.getCloud().events.track({ kind, ...init, ...ids });
|
|
228
234
|
})
|
|
@@ -111,6 +111,10 @@ export class CloudMessagePersistence {
|
|
|
111
111
|
return typeof entry === "string" ? entry : undefined;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
record(localId: string, remoteId: string): void {
|
|
115
|
+
this.idMapping.set(localId, remoteId);
|
|
116
|
+
}
|
|
117
|
+
|
|
114
118
|
/**
|
|
115
119
|
* Load messages from the cloud and populate the ID mapping.
|
|
116
120
|
*
|
package/src/ai-sdk/index.test.ts
CHANGED
|
@@ -79,7 +79,7 @@ describe("extractAISDKRunTelemetry", () => {
|
|
|
79
79
|
{},
|
|
80
80
|
],
|
|
81
81
|
samplingCalls: { call_2: [{ duration_ms: 20 }] },
|
|
82
|
-
modelId: "gpt-
|
|
82
|
+
modelId: "gpt-6-luna",
|
|
83
83
|
},
|
|
84
84
|
);
|
|
85
85
|
|
|
@@ -119,7 +119,7 @@ describe("extractAISDKRunTelemetry", () => {
|
|
|
119
119
|
totalSteps: 3,
|
|
120
120
|
outputText: "Cold.",
|
|
121
121
|
usage: { inputTokens: 10, outputTokens: 5 },
|
|
122
|
-
modelId: "gpt-
|
|
122
|
+
modelId: "gpt-6-luna",
|
|
123
123
|
metadata: message.metadata,
|
|
124
124
|
});
|
|
125
125
|
});
|