assistant-cloud 0.1.16 → 0.1.18
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/dist/AssistantCloud.d.ts +2 -1
- package/dist/AssistantCloud.d.ts.map +1 -1
- package/dist/AssistantCloud.js +9 -1
- package/dist/AssistantCloud.js.map +1 -1
- package/dist/AssistantCloudAPI.d.ts +24 -1
- package/dist/AssistantCloudAPI.d.ts.map +1 -1
- package/dist/AssistantCloudAPI.js +3 -1
- package/dist/AssistantCloudAPI.js.map +1 -1
- package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
- package/dist/AssistantCloudAuthStrategy.js.map +1 -1
- package/dist/AssistantCloudAuthTokens.d.ts.map +1 -1
- package/dist/AssistantCloudAuthTokens.js.map +1 -1
- package/dist/AssistantCloudFiles.d.ts.map +1 -1
- package/dist/AssistantCloudFiles.js.map +1 -1
- package/dist/AssistantCloudRuns.d.ts +51 -0
- package/dist/AssistantCloudRuns.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.js +3 -0
- package/dist/AssistantCloudRuns.js.map +1 -1
- package/dist/AssistantCloudThreadMessages.d.ts +4 -0
- package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.js +3 -0
- package/dist/AssistantCloudThreadMessages.js.map +1 -1
- package/dist/AssistantCloudThreads.d.ts.map +1 -1
- package/dist/AssistantCloudThreads.js.map +1 -1
- package/dist/CloudMessagePersistence.d.ts +57 -0
- package/dist/CloudMessagePersistence.d.ts.map +1 -0
- package/dist/CloudMessagePersistence.js +103 -0
- package/dist/CloudMessagePersistence.js.map +1 -0
- package/dist/FormattedCloudPersistence.d.ts +56 -0
- package/dist/FormattedCloudPersistence.d.ts.map +1 -0
- package/dist/FormattedCloudPersistence.js +39 -0
- package/dist/FormattedCloudPersistence.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/instrumentMcpSampling.d.ts +75 -0
- package/dist/instrumentMcpSampling.d.ts.map +1 -0
- package/dist/instrumentMcpSampling.js +65 -0
- package/dist/instrumentMcpSampling.js.map +1 -0
- package/package.json +3 -3
- package/src/{AssistantCloud.tsx → AssistantCloud.ts} +14 -1
- package/src/{AssistantCloudAPI.tsx → AssistantCloudAPI.ts} +31 -3
- package/src/AssistantCloudRuns.ts +99 -0
- package/src/{AssistantCloudThreadMessages.tsx → AssistantCloudThreadMessages.ts} +15 -0
- package/src/CloudMessagePersistence.ts +123 -0
- package/src/FormattedCloudPersistence.ts +96 -0
- package/src/index.ts +13 -0
- package/src/instrumentMcpSampling.ts +109 -0
- package/src/tests/AssistantCloudAPI.test.ts +129 -0
- package/src/tests/CloudMessagePersistence.test.ts +152 -0
- package/src/tests/FormattedCloudPersistence.test.ts +134 -0
- package/src/AssistantCloudRuns.tsx +0 -44
- /package/src/{AssistantCloudAuthStrategy.tsx → AssistantCloudAuthStrategy.ts} +0 -0
- /package/src/{AssistantCloudAuthTokens.tsx → AssistantCloudAuthTokens.ts} +0 -0
- /package/src/{AssistantCloudFiles.tsx → AssistantCloudFiles.ts} +0 -0
- /package/src/{AssistantCloudThreads.tsx → AssistantCloudThreads.ts} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export type { CloudMessage } from "./AssistantCloudThreadMessages.js";
|
|
2
|
+
export type { AssistantCloudTelemetryConfig } from "./AssistantCloudAPI.js";
|
|
3
|
+
export type { AssistantCloudRunReport } from "./AssistantCloudRuns.js";
|
|
2
4
|
export { AssistantCloud } from "./AssistantCloud.js";
|
|
5
|
+
export { CloudMessagePersistence } from "./CloudMessagePersistence.js";
|
|
6
|
+
export { createFormattedPersistence, type MessageFormatAdapter, } from "./FormattedCloudPersistence.js";
|
|
7
|
+
export { wrapSamplingHandler, createSamplingCollector, type SamplingCallData, type McpSamplingHandler, } from "./instrumentMcpSampling.js";
|
|
3
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,0CAAuC;AACnE,OAAO,EAAE,cAAc,EAAE,4BAAyB"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,0CAAuC;AACnE,YAAY,EAAE,6BAA6B,EAAE,+BAA4B;AACzE,YAAY,EAAE,uBAAuB,EAAE,gCAA6B;AACpE,OAAO,EAAE,cAAc,EAAE,4BAAyB;AAClD,OAAO,EAAE,uBAAuB,EAAE,qCAAkC;AACpE,OAAO,EACL,0BAA0B,EAC1B,KAAK,oBAAoB,GAC1B,uCAAoC;AACrC,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,mCAAgC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { AssistantCloud } from "./AssistantCloud.js";
|
|
2
|
+
export { CloudMessagePersistence } from "./CloudMessagePersistence.js";
|
|
3
|
+
export { createFormattedPersistence, } from "./FormattedCloudPersistence.js";
|
|
4
|
+
export { wrapSamplingHandler, createSamplingCollector, } from "./instrumentMcpSampling.js";
|
|
2
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,4BAAyB;AAClD,OAAO,EAAE,uBAAuB,EAAE,qCAAkC;AACpE,OAAO,EACL,0BAA0B,GAE3B,uCAAoC;AACrC,OAAO,EACL,mBAAmB,EACnB,uBAAuB,GAGxB,mCAAgC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP sampling instrumentation utility.
|
|
3
|
+
*
|
|
4
|
+
* Wraps an MCP client's sampling handler to capture nested LLM calls
|
|
5
|
+
* (sampling/createMessage requests) made during tool execution.
|
|
6
|
+
* The captured data can be reported as child generation spans.
|
|
7
|
+
*/
|
|
8
|
+
export type SamplingCallData = {
|
|
9
|
+
model_id?: string;
|
|
10
|
+
input_tokens?: number;
|
|
11
|
+
output_tokens?: number;
|
|
12
|
+
duration_ms?: number;
|
|
13
|
+
};
|
|
14
|
+
export type McpSamplingHandler = (request: McpSamplingRequest) => Promise<McpSamplingResponse>;
|
|
15
|
+
export type McpSamplingRequest = {
|
|
16
|
+
method: "sampling/createMessage";
|
|
17
|
+
params: {
|
|
18
|
+
messages: unknown[];
|
|
19
|
+
modelPreferences?: {
|
|
20
|
+
hints?: {
|
|
21
|
+
name?: string;
|
|
22
|
+
}[];
|
|
23
|
+
};
|
|
24
|
+
maxTokens?: number;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export type McpSamplingResponse = {
|
|
29
|
+
model?: string;
|
|
30
|
+
content: unknown;
|
|
31
|
+
usage?: {
|
|
32
|
+
inputTokens?: number;
|
|
33
|
+
outputTokens?: number;
|
|
34
|
+
promptTokens?: number;
|
|
35
|
+
completionTokens?: number;
|
|
36
|
+
};
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Wraps an MCP sampling handler to intercept and measure sampling calls.
|
|
41
|
+
*
|
|
42
|
+
* @param handler - The original sampling handler from the MCP client
|
|
43
|
+
* @param onSamplingCall - Callback invoked with metrics for each sampling call
|
|
44
|
+
* @returns A wrapped handler that transparently captures sampling metrics
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const samplingCalls: SamplingCallData[] = [];
|
|
49
|
+
* const wrapped = wrapSamplingHandler(
|
|
50
|
+
* originalHandler,
|
|
51
|
+
* (data) => samplingCalls.push(data),
|
|
52
|
+
* );
|
|
53
|
+
* // Use `wrapped` as the MCP client's sampling handler
|
|
54
|
+
* // After tool execution, `samplingCalls` contains metrics for all nested LLM calls
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function wrapSamplingHandler(handler: McpSamplingHandler, onSamplingCall: (data: SamplingCallData) => void): McpSamplingHandler;
|
|
58
|
+
/**
|
|
59
|
+
* Creates a collector that accumulates sampling call data during tool execution.
|
|
60
|
+
* Use with `wrapSamplingHandler` to capture all sampling calls for a tool invocation.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const collector = createSamplingCollector();
|
|
65
|
+
* const wrappedHandler = wrapSamplingHandler(handler, collector.collect);
|
|
66
|
+
* // ... execute MCP tool ...
|
|
67
|
+
* const calls = collector.getCalls(); // SamplingCallData[]
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function createSamplingCollector(): {
|
|
71
|
+
collect: (data: SamplingCallData) => number;
|
|
72
|
+
getCalls: () => SamplingCallData[];
|
|
73
|
+
reset: () => void;
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=instrumentMcpSampling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentMcpSampling.d.ts","sourceRoot":"","sources":["../src/instrumentMcpSampling.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,kBAAkB,KACxB,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE;QACN,QAAQ,EAAE,OAAO,EAAE,CAAC;QACpB,gBAAgB,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,EAAE,CAAA;SAAE,CAAC;QACnD,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,kBAAkB,EAC3B,cAAc,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,GAC/C,kBAAkB,CAuBpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB;oBAGnB,gBAAgB;;;EAMnC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP sampling instrumentation utility.
|
|
3
|
+
*
|
|
4
|
+
* Wraps an MCP client's sampling handler to capture nested LLM calls
|
|
5
|
+
* (sampling/createMessage requests) made during tool execution.
|
|
6
|
+
* The captured data can be reported as child generation spans.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Wraps an MCP sampling handler to intercept and measure sampling calls.
|
|
10
|
+
*
|
|
11
|
+
* @param handler - The original sampling handler from the MCP client
|
|
12
|
+
* @param onSamplingCall - Callback invoked with metrics for each sampling call
|
|
13
|
+
* @returns A wrapped handler that transparently captures sampling metrics
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const samplingCalls: SamplingCallData[] = [];
|
|
18
|
+
* const wrapped = wrapSamplingHandler(
|
|
19
|
+
* originalHandler,
|
|
20
|
+
* (data) => samplingCalls.push(data),
|
|
21
|
+
* );
|
|
22
|
+
* // Use `wrapped` as the MCP client's sampling handler
|
|
23
|
+
* // After tool execution, `samplingCalls` contains metrics for all nested LLM calls
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function wrapSamplingHandler(handler, onSamplingCall) {
|
|
27
|
+
return async (request) => {
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
const response = await handler(request);
|
|
30
|
+
const durationMs = Date.now() - startTime;
|
|
31
|
+
const modelId = response.model ?? request.params.modelPreferences?.hints?.[0]?.name;
|
|
32
|
+
const inputTokens = response.usage?.inputTokens ?? response.usage?.promptTokens;
|
|
33
|
+
const outputTokens = response.usage?.outputTokens ?? response.usage?.completionTokens;
|
|
34
|
+
onSamplingCall({
|
|
35
|
+
...(modelId ? { model_id: modelId } : undefined),
|
|
36
|
+
...(inputTokens != null ? { input_tokens: inputTokens } : undefined),
|
|
37
|
+
...(outputTokens != null ? { output_tokens: outputTokens } : undefined),
|
|
38
|
+
duration_ms: durationMs,
|
|
39
|
+
});
|
|
40
|
+
return response;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a collector that accumulates sampling call data during tool execution.
|
|
45
|
+
* Use with `wrapSamplingHandler` to capture all sampling calls for a tool invocation.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const collector = createSamplingCollector();
|
|
50
|
+
* const wrappedHandler = wrapSamplingHandler(handler, collector.collect);
|
|
51
|
+
* // ... execute MCP tool ...
|
|
52
|
+
* const calls = collector.getCalls(); // SamplingCallData[]
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function createSamplingCollector() {
|
|
56
|
+
const calls = [];
|
|
57
|
+
return {
|
|
58
|
+
collect: (data) => calls.push(data),
|
|
59
|
+
getCalls: () => [...calls],
|
|
60
|
+
reset: () => {
|
|
61
|
+
calls.length = 0;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=instrumentMcpSampling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentMcpSampling.js","sourceRoot":"","sources":["../src/instrumentMcpSampling.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmCH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAA2B,EAC3B,cAAgD;IAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAE1C,MAAM,OAAO,GACX,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;QAEtE,MAAM,WAAW,GACf,QAAQ,CAAC,KAAK,EAAE,WAAW,IAAI,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;QAC9D,MAAM,YAAY,GAChB,QAAQ,CAAC,KAAK,EAAE,YAAY,IAAI,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;QAEnE,cAAc,CAAC;YACb,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YAChD,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YACpE,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YACvE,WAAW,EAAE,UAAU;SACxB,CAAC,CAAC;QAEH,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,OAAO;QACL,OAAO,EAAE,CAAC,IAAsB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACrD,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;QAC1B,KAAK,EAAE,GAAG,EAAE;YACV,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assistant-cloud",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Cloud integration for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
],
|
|
29
29
|
"sideEffects": false,
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"assistant-stream": "^0.3.
|
|
31
|
+
"assistant-stream": "^0.3.3"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^25.2.
|
|
34
|
+
"@types/node": "^25.2.1",
|
|
35
35
|
"vitest": "^4.0.18",
|
|
36
36
|
"@assistant-ui/x-buildutils": "0.0.1"
|
|
37
37
|
},
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AssistantCloudAPI,
|
|
3
|
+
AssistantCloudConfig,
|
|
4
|
+
AssistantCloudTelemetryConfig,
|
|
5
|
+
} from "./AssistantCloudAPI";
|
|
2
6
|
import { AssistantCloudAuthTokens } from "./AssistantCloudAuthTokens";
|
|
3
7
|
import { AssistantCloudRuns } from "./AssistantCloudRuns";
|
|
4
8
|
import { AssistantCloudThreads } from "./AssistantCloudThreads";
|
|
@@ -9,6 +13,7 @@ export class AssistantCloud {
|
|
|
9
13
|
public readonly auth;
|
|
10
14
|
public readonly runs;
|
|
11
15
|
public readonly files;
|
|
16
|
+
public readonly telemetry: AssistantCloudTelemetryConfig;
|
|
12
17
|
|
|
13
18
|
constructor(config: AssistantCloudConfig) {
|
|
14
19
|
const api = new AssistantCloudAPI(config);
|
|
@@ -18,5 +23,13 @@ export class AssistantCloud {
|
|
|
18
23
|
};
|
|
19
24
|
this.runs = new AssistantCloudRuns(api);
|
|
20
25
|
this.files = new AssistantCloudFiles(api);
|
|
26
|
+
|
|
27
|
+
const t = config.telemetry;
|
|
28
|
+
this.telemetry =
|
|
29
|
+
t === false
|
|
30
|
+
? { enabled: false }
|
|
31
|
+
: t === true || t === undefined
|
|
32
|
+
? { enabled: true }
|
|
33
|
+
: { enabled: t.enabled !== false, ...t };
|
|
21
34
|
}
|
|
22
35
|
}
|
|
@@ -4,8 +4,21 @@ import {
|
|
|
4
4
|
AssistantCloudAPIKeyAuthStrategy,
|
|
5
5
|
AssistantCloudAnonymousAuthStrategy,
|
|
6
6
|
} from "./AssistantCloudAuthStrategy";
|
|
7
|
+
import type { AssistantCloudRunReport } from "./AssistantCloudRuns";
|
|
7
8
|
|
|
8
|
-
export type
|
|
9
|
+
export type AssistantCloudTelemetryConfig = {
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Called before each telemetry report is sent.
|
|
13
|
+
* Return a modified report to enrich it (e.g. add `model_id`),
|
|
14
|
+
* or return `null` to skip the report.
|
|
15
|
+
*/
|
|
16
|
+
beforeReport?: (
|
|
17
|
+
report: AssistantCloudRunReport,
|
|
18
|
+
) => AssistantCloudRunReport | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type AssistantCloudConfig = (
|
|
9
22
|
| {
|
|
10
23
|
baseUrl: string;
|
|
11
24
|
authToken: () => Promise<string | null>;
|
|
@@ -18,7 +31,21 @@ export type AssistantCloudConfig =
|
|
|
18
31
|
| {
|
|
19
32
|
baseUrl: string;
|
|
20
33
|
anonymous: true;
|
|
21
|
-
}
|
|
34
|
+
}
|
|
35
|
+
) & {
|
|
36
|
+
/**
|
|
37
|
+
* Client-side run telemetry reporting. Default: `true`.
|
|
38
|
+
*
|
|
39
|
+
* When enabled, the SDK automatically reports run metadata (status, step
|
|
40
|
+
* count, tool calls, and token usage) to Assistant Cloud after each
|
|
41
|
+
* assistant message is saved. No message content is sent.
|
|
42
|
+
*
|
|
43
|
+
* - `true` / `undefined` — enabled with defaults
|
|
44
|
+
* - `false` — disabled
|
|
45
|
+
* - `{ beforeReport }` — enabled with a hook to enrich or filter reports
|
|
46
|
+
*/
|
|
47
|
+
telemetry?: boolean | AssistantCloudTelemetryConfig;
|
|
48
|
+
};
|
|
22
49
|
|
|
23
50
|
class CloudAPIError extends Error {
|
|
24
51
|
constructor(message: string) {
|
|
@@ -104,7 +131,8 @@ export class AssistantCloudAPI {
|
|
|
104
131
|
try {
|
|
105
132
|
const body = JSON.parse(text);
|
|
106
133
|
throw new CloudAPIError(body.message);
|
|
107
|
-
} catch {
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error instanceof CloudAPIError) throw error;
|
|
108
136
|
throw new Error(
|
|
109
137
|
`Request failed with status ${response.status}, ${text}`,
|
|
110
138
|
);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { AssistantCloudAPI } from "./AssistantCloudAPI";
|
|
2
|
+
import { AssistantStream, PlainTextDecoder } from "assistant-stream";
|
|
3
|
+
|
|
4
|
+
type AssistantCloudRunsStreamBody = {
|
|
5
|
+
thread_id: string;
|
|
6
|
+
assistant_id: "system/thread_title";
|
|
7
|
+
messages: readonly unknown[]; // TODO type
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type AssistantCloudRunReport = {
|
|
11
|
+
thread_id: string;
|
|
12
|
+
status: "completed" | "incomplete" | "error";
|
|
13
|
+
total_steps?: number;
|
|
14
|
+
tool_calls?: {
|
|
15
|
+
tool_name: string;
|
|
16
|
+
tool_call_id: string;
|
|
17
|
+
tool_args?: string;
|
|
18
|
+
tool_result?: string;
|
|
19
|
+
tool_source?: "mcp" | "frontend" | "backend";
|
|
20
|
+
start_ms?: number;
|
|
21
|
+
end_ms?: number;
|
|
22
|
+
sampling_calls?: {
|
|
23
|
+
model_id?: string;
|
|
24
|
+
input_tokens?: number;
|
|
25
|
+
output_tokens?: number;
|
|
26
|
+
duration_ms?: number;
|
|
27
|
+
}[];
|
|
28
|
+
}[];
|
|
29
|
+
steps?: {
|
|
30
|
+
input_tokens?: number;
|
|
31
|
+
output_tokens?: number;
|
|
32
|
+
tool_calls?: {
|
|
33
|
+
tool_name: string;
|
|
34
|
+
tool_call_id: string;
|
|
35
|
+
tool_args?: string;
|
|
36
|
+
tool_result?: string;
|
|
37
|
+
tool_source?: "mcp" | "frontend" | "backend";
|
|
38
|
+
start_ms?: number;
|
|
39
|
+
end_ms?: number;
|
|
40
|
+
sampling_calls?: {
|
|
41
|
+
model_id?: string;
|
|
42
|
+
input_tokens?: number;
|
|
43
|
+
output_tokens?: number;
|
|
44
|
+
duration_ms?: number;
|
|
45
|
+
}[];
|
|
46
|
+
}[];
|
|
47
|
+
start_ms?: number;
|
|
48
|
+
end_ms?: number;
|
|
49
|
+
}[];
|
|
50
|
+
input_tokens?: number;
|
|
51
|
+
output_tokens?: number;
|
|
52
|
+
model_id?: string;
|
|
53
|
+
provider_type?: string;
|
|
54
|
+
duration_ms?: number;
|
|
55
|
+
output_text?: string;
|
|
56
|
+
metadata?: Record<string, unknown>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export class AssistantCloudRuns {
|
|
60
|
+
constructor(private cloud: AssistantCloudAPI) {}
|
|
61
|
+
|
|
62
|
+
public __internal_getAssistantOptions(assistantId: string) {
|
|
63
|
+
return {
|
|
64
|
+
api: `${this.cloud._baseUrl}/v1/runs/stream`,
|
|
65
|
+
headers: async () => {
|
|
66
|
+
const headers = await this.cloud._auth.getAuthHeaders();
|
|
67
|
+
if (!headers) throw new Error("Authorization failed");
|
|
68
|
+
return {
|
|
69
|
+
...headers,
|
|
70
|
+
Accept: "text/plain",
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
body: {
|
|
74
|
+
assistant_id: assistantId,
|
|
75
|
+
response_format: "vercel-ai-data-stream/v1",
|
|
76
|
+
thread_id: "unstable_todo",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public async stream(
|
|
82
|
+
body: AssistantCloudRunsStreamBody,
|
|
83
|
+
): Promise<AssistantStream> {
|
|
84
|
+
const response = await this.cloud.makeRawRequest("/runs/stream", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
Accept: "text/plain",
|
|
88
|
+
},
|
|
89
|
+
body,
|
|
90
|
+
});
|
|
91
|
+
return AssistantStream.fromResponse(response, new PlainTextDecoder());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public async report(
|
|
95
|
+
body: AssistantCloudRunReport,
|
|
96
|
+
): Promise<{ run_id: string }> {
|
|
97
|
+
return this.cloud.makeRequest("/runs", { method: "POST", body });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -29,6 +29,10 @@ type AssistantCloudMessageCreateResponse = {
|
|
|
29
29
|
message_id: string;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
type AssistantCloudThreadMessageUpdateBody = {
|
|
33
|
+
content: ReadonlyJSONObject;
|
|
34
|
+
};
|
|
35
|
+
|
|
32
36
|
export class AssistantCloudThreadMessages {
|
|
33
37
|
constructor(private cloud: AssistantCloudAPI) {}
|
|
34
38
|
|
|
@@ -51,4 +55,15 @@ export class AssistantCloudThreadMessages {
|
|
|
51
55
|
{ method: "POST", body },
|
|
52
56
|
);
|
|
53
57
|
}
|
|
58
|
+
|
|
59
|
+
public async update(
|
|
60
|
+
threadId: string,
|
|
61
|
+
messageId: string,
|
|
62
|
+
body: AssistantCloudThreadMessageUpdateBody,
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
return this.cloud.makeRequest(
|
|
65
|
+
`/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}`,
|
|
66
|
+
{ method: "PUT", body },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
54
69
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
2
|
+
import type { AssistantCloud } from "./AssistantCloud";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared persistence logic for cloud message storage.
|
|
6
|
+
*
|
|
7
|
+
* Handles ID mapping (local → remote) and parent_id chaining for both:
|
|
8
|
+
* - AssistantCloudThreadHistoryAdapter (assistant-ui runtime)
|
|
9
|
+
* - useCloudChat (standalone AI SDK hook)
|
|
10
|
+
*
|
|
11
|
+
* The promise-based ID resolution handles concurrent appends — if message B's
|
|
12
|
+
* parent is message A, and A is still being created, we await A's promise
|
|
13
|
+
* to get its remote ID before creating B.
|
|
14
|
+
*/
|
|
15
|
+
export class CloudMessagePersistence {
|
|
16
|
+
private idMapping: Record<string, string | Promise<string>> = {};
|
|
17
|
+
|
|
18
|
+
constructor(private cloud: AssistantCloud) {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Persist a message to the cloud.
|
|
22
|
+
*
|
|
23
|
+
* @param threadId - Remote thread ID
|
|
24
|
+
* @param messageId - Local message ID (used for tracking)
|
|
25
|
+
* @param parentId - Local parent message ID (or null for first message)
|
|
26
|
+
* @param format - Message format (e.g., "aui/v0", "ai-sdk/v6")
|
|
27
|
+
* @param content - Message content (format-specific)
|
|
28
|
+
*/
|
|
29
|
+
async append(
|
|
30
|
+
threadId: string,
|
|
31
|
+
messageId: string,
|
|
32
|
+
parentId: string | null,
|
|
33
|
+
format: string,
|
|
34
|
+
content: ReadonlyJSONObject,
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
// Resolve parent's remote ID if it exists (may be a promise if concurrent)
|
|
37
|
+
const resolvedParentId = parentId
|
|
38
|
+
? ((await this.idMapping[parentId]) ?? parentId)
|
|
39
|
+
: null;
|
|
40
|
+
|
|
41
|
+
const task = this.cloud.threads.messages
|
|
42
|
+
.create(threadId, {
|
|
43
|
+
parent_id: resolvedParentId,
|
|
44
|
+
format,
|
|
45
|
+
content,
|
|
46
|
+
})
|
|
47
|
+
.then(({ message_id }) => {
|
|
48
|
+
this.idMapping[messageId] = message_id;
|
|
49
|
+
return message_id;
|
|
50
|
+
})
|
|
51
|
+
.catch((err) => {
|
|
52
|
+
// Only delete if we're still the active task (avoids clobbering a retry)
|
|
53
|
+
if (this.idMapping[messageId] === task) {
|
|
54
|
+
delete this.idMapping[messageId];
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Store the promise immediately so concurrent appends can await it
|
|
60
|
+
this.idMapping[messageId] = task;
|
|
61
|
+
return task.then(() => {});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Update an already-persisted message in the cloud.
|
|
66
|
+
*/
|
|
67
|
+
async update(
|
|
68
|
+
threadId: string,
|
|
69
|
+
messageId: string,
|
|
70
|
+
_format: string,
|
|
71
|
+
content: ReadonlyJSONObject,
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
const remoteId = await this.getRemoteId(messageId);
|
|
74
|
+
if (!remoteId) return; // not persisted yet, skip
|
|
75
|
+
await this.cloud.threads.messages.update(threadId, remoteId, { content });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if a message has been persisted (or is currently being persisted).
|
|
80
|
+
*/
|
|
81
|
+
isPersisted(messageId: string): boolean {
|
|
82
|
+
return messageId in this.idMapping;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the remote ID for a local message ID (resolved).
|
|
87
|
+
* Returns undefined if not persisted.
|
|
88
|
+
*/
|
|
89
|
+
async getRemoteId(messageId: string): Promise<string | undefined> {
|
|
90
|
+
const entry = this.idMapping[messageId];
|
|
91
|
+
if (!entry) return undefined;
|
|
92
|
+
return entry;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Load messages from the cloud and populate the ID mapping.
|
|
97
|
+
*
|
|
98
|
+
* The ID mapping is populated so that `isPersisted()` returns true for
|
|
99
|
+
* loaded messages, preventing re-persistence of already-stored messages.
|
|
100
|
+
*
|
|
101
|
+
* @param threadId - Remote thread ID
|
|
102
|
+
* @param format - Optional format filter
|
|
103
|
+
* @returns Array of cloud messages
|
|
104
|
+
*/
|
|
105
|
+
async load(threadId: string, format?: string) {
|
|
106
|
+
const { messages } = await this.cloud.threads.messages.list(
|
|
107
|
+
threadId,
|
|
108
|
+
format ? { format } : undefined,
|
|
109
|
+
);
|
|
110
|
+
// Populate ID mapping so isPersisted() recognizes loaded messages
|
|
111
|
+
for (const m of messages) {
|
|
112
|
+
this.idMapping[m.id] = m.id;
|
|
113
|
+
}
|
|
114
|
+
return messages;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Reset the ID mapping (call when switching threads).
|
|
119
|
+
*/
|
|
120
|
+
reset() {
|
|
121
|
+
this.idMapping = {};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ReadonlyJSONObject } from "assistant-stream/utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format adapter shape — structurally identical to the MessageFormatAdapter
|
|
5
|
+
* in @assistant-ui/react, but defined here to avoid cross-package type moves.
|
|
6
|
+
* TypeScript's structural typing ensures these are interchangeable.
|
|
7
|
+
*/
|
|
8
|
+
export type MessageFormatAdapter<TMessage, TStorageFormat> = {
|
|
9
|
+
format: string;
|
|
10
|
+
encode(item: { parentId: string | null; message: TMessage }): TStorageFormat;
|
|
11
|
+
decode(stored: {
|
|
12
|
+
id: string;
|
|
13
|
+
parent_id: string | null;
|
|
14
|
+
format: string;
|
|
15
|
+
content: TStorageFormat;
|
|
16
|
+
}): { parentId: string | null; message: TMessage };
|
|
17
|
+
getId(message: TMessage): string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Wraps a CloudMessagePersistence instance with format-aware encode/decode.
|
|
22
|
+
*
|
|
23
|
+
* This centralizes the pattern used by both:
|
|
24
|
+
* - useCloudChat (standalone AI SDK hook)
|
|
25
|
+
* - AssistantCloudThreadHistoryAdapter.withFormat() (assistant-ui runtime)
|
|
26
|
+
*
|
|
27
|
+
* The persistence parameter is typed structurally (not by class) so callers
|
|
28
|
+
* don't need to import CloudMessagePersistence directly.
|
|
29
|
+
*/
|
|
30
|
+
export const createFormattedPersistence = <TMessage, TStorageFormat>(
|
|
31
|
+
persistence: {
|
|
32
|
+
append: (
|
|
33
|
+
threadId: string,
|
|
34
|
+
messageId: string,
|
|
35
|
+
parentId: string | null,
|
|
36
|
+
format: string,
|
|
37
|
+
content: ReadonlyJSONObject,
|
|
38
|
+
) => Promise<void>;
|
|
39
|
+
load: (threadId: string, format?: string) => Promise<any[]>;
|
|
40
|
+
isPersisted: (messageId: string) => boolean;
|
|
41
|
+
update?: (
|
|
42
|
+
threadId: string,
|
|
43
|
+
messageId: string,
|
|
44
|
+
format: string,
|
|
45
|
+
content: ReadonlyJSONObject,
|
|
46
|
+
) => Promise<void>;
|
|
47
|
+
},
|
|
48
|
+
adapter: MessageFormatAdapter<TMessage, TStorageFormat>,
|
|
49
|
+
) => ({
|
|
50
|
+
append: async (
|
|
51
|
+
threadId: string,
|
|
52
|
+
item: { parentId: string | null; message: TMessage },
|
|
53
|
+
): Promise<void> => {
|
|
54
|
+
const messageId = adapter.getId(item.message);
|
|
55
|
+
const encoded = adapter.encode(item);
|
|
56
|
+
return persistence.append(
|
|
57
|
+
threadId,
|
|
58
|
+
messageId,
|
|
59
|
+
item.parentId,
|
|
60
|
+
adapter.format,
|
|
61
|
+
encoded as ReadonlyJSONObject,
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
update: persistence.update
|
|
65
|
+
? async (
|
|
66
|
+
threadId: string,
|
|
67
|
+
item: { parentId: string | null; message: TMessage },
|
|
68
|
+
messageId: string,
|
|
69
|
+
): Promise<void> => {
|
|
70
|
+
const encoded = adapter.encode(item);
|
|
71
|
+
return persistence.update!(
|
|
72
|
+
threadId,
|
|
73
|
+
messageId,
|
|
74
|
+
adapter.format,
|
|
75
|
+
encoded as ReadonlyJSONObject,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
: undefined,
|
|
79
|
+
load: async (threadId: string) => {
|
|
80
|
+
const messages = await persistence.load(threadId, adapter.format);
|
|
81
|
+
return {
|
|
82
|
+
messages: messages
|
|
83
|
+
.filter((m) => m.format === adapter.format)
|
|
84
|
+
.map((m) =>
|
|
85
|
+
adapter.decode({
|
|
86
|
+
id: m.id,
|
|
87
|
+
parent_id: m.parent_id,
|
|
88
|
+
format: m.format,
|
|
89
|
+
content: m.content as TStorageFormat,
|
|
90
|
+
}),
|
|
91
|
+
)
|
|
92
|
+
.reverse(),
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
isPersisted: (messageId: string) => persistence.isPersisted(messageId),
|
|
96
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
export type { CloudMessage } from "./AssistantCloudThreadMessages";
|
|
2
|
+
export type { AssistantCloudTelemetryConfig } from "./AssistantCloudAPI";
|
|
3
|
+
export type { AssistantCloudRunReport } from "./AssistantCloudRuns";
|
|
2
4
|
export { AssistantCloud } from "./AssistantCloud";
|
|
5
|
+
export { CloudMessagePersistence } from "./CloudMessagePersistence";
|
|
6
|
+
export {
|
|
7
|
+
createFormattedPersistence,
|
|
8
|
+
type MessageFormatAdapter,
|
|
9
|
+
} from "./FormattedCloudPersistence";
|
|
10
|
+
export {
|
|
11
|
+
wrapSamplingHandler,
|
|
12
|
+
createSamplingCollector,
|
|
13
|
+
type SamplingCallData,
|
|
14
|
+
type McpSamplingHandler,
|
|
15
|
+
} from "./instrumentMcpSampling";
|