assistant-cloud 0.1.40 → 0.1.42
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 +3 -3
- package/dist/AssistantCloud.js +2 -2
- package/dist/AssistantCloud.js.map +1 -1
- package/dist/AssistantCloudAPI.d.ts.map +1 -1
- package/dist/AssistantCloudAPI.js +5 -5
- package/dist/AssistantCloudAPI.js.map +1 -1
- package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
- package/dist/AssistantCloudAuthStrategy.js +35 -12
- package/dist/AssistantCloudAuthStrategy.js.map +1 -1
- package/dist/AssistantCloudAuthTokens.d.ts.map +1 -1
- package/dist/AssistantCloudAuthTokens.js +3 -1
- package/dist/AssistantCloudAuthTokens.js.map +1 -1
- package/dist/AssistantCloudFiles.d.ts.map +1 -1
- package/dist/AssistantCloudFiles.js +16 -4
- package/dist/AssistantCloudFiles.js.map +1 -1
- package/dist/AssistantCloudRuns.d.ts +3 -13
- package/dist/AssistantCloudRuns.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.js +10 -2
- package/dist/AssistantCloudRuns.js.map +1 -1
- package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.js +3 -2
- package/dist/AssistantCloudThreadMessages.js.map +1 -1
- package/dist/AssistantCloudThreads.d.ts.map +1 -1
- package/dist/AssistantCloudThreads.js +3 -2
- package/dist/AssistantCloudThreads.js.map +1 -1
- package/dist/generateThreadTitle.d.ts +15 -0
- package/dist/generateThreadTitle.d.ts.map +1 -0
- package/dist/generateThreadTitle.js +25 -0
- package/dist/generateThreadTitle.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/runTelemetry.d.ts +54 -0
- package/dist/runTelemetry.d.ts.map +1 -0
- package/dist/runTelemetry.js +79 -0
- package/dist/runTelemetry.js.map +1 -0
- package/package.json +5 -5
- package/src/AssistantCloud.ts +1 -1
- package/src/AssistantCloudAPI.ts +9 -8
- package/src/AssistantCloudAuthStrategy.ts +49 -12
- package/src/AssistantCloudAuthTokens.test.ts +30 -0
- package/src/AssistantCloudAuthTokens.ts +7 -1
- package/src/AssistantCloudFiles.test.ts +71 -0
- package/src/AssistantCloudFiles.ts +56 -10
- package/src/AssistantCloudRuns.ts +37 -15
- package/src/AssistantCloudThreadMessages.test.ts +20 -0
- package/src/AssistantCloudThreadMessages.ts +10 -3
- package/src/AssistantCloudThreads.test.ts +17 -0
- package/src/AssistantCloudThreads.ts +6 -1
- package/src/generateThreadTitle.test.ts +71 -0
- package/src/generateThreadTitle.ts +38 -0
- package/src/index.ts +10 -0
- package/src/runTelemetry.test.ts +130 -0
- package/src/runTelemetry.ts +140 -0
- package/src/tests/AssistantCloud.test.ts +39 -0
- package/src/tests/AssistantCloudAPI.test.ts +25 -0
- package/src/tests/AssistantCloudAuthStrategy.test.ts +278 -5
- package/src/tests/AssistantCloudRuns.test.ts +115 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { McpSamplingHandler, SamplingCallData, createSamplingCollector, wrapSamplingHandler } from "./instrumentMcpSampling.js";
|
|
2
|
+
import { AssistantCloudRunReportToolCall, RunTelemetryToolCallInit, RunTelemetryUsage, RunTelemetryUsageInit, createRunTelemetryToolCall, normalizeRunTelemetryUsage, truncateRunTelemetryText } from "./runTelemetry.js";
|
|
2
3
|
import { AssistantCloudRunReport } from "./AssistantCloudRuns.js";
|
|
3
4
|
import { AssistantCloudTelemetryConfig, CloudAPIError } from "./AssistantCloudAPI.js";
|
|
4
5
|
import { CloudMessage } from "./AssistantCloudThreadMessages.js";
|
|
@@ -6,4 +7,5 @@ import { AssistantCloud } from "./AssistantCloud.js";
|
|
|
6
7
|
import { CloudMessagePersistence } from "./CloudMessagePersistence.js";
|
|
7
8
|
import { MessageFormatAdapter, createFormattedPersistence } from "./FormattedCloudPersistence.js";
|
|
8
9
|
import { CloudResponseError } from "./cloudResponse.js";
|
|
9
|
-
|
|
10
|
+
import { generateThreadTitle } from "./generateThreadTitle.js";
|
|
11
|
+
export { AssistantCloud, type AssistantCloudRunReport, type AssistantCloudRunReportToolCall, type AssistantCloudTelemetryConfig, CloudAPIError, type CloudMessage, CloudMessagePersistence, CloudResponseError, type McpSamplingHandler, type MessageFormatAdapter, type RunTelemetryToolCallInit, type RunTelemetryUsage, type RunTelemetryUsageInit, type SamplingCallData, createFormattedPersistence, createRunTelemetryToolCall, createSamplingCollector, generateThreadTitle, normalizeRunTelemetryUsage, truncateRunTelemetryText, wrapSamplingHandler };
|
package/dist/index.js
CHANGED
|
@@ -3,5 +3,7 @@ import { CloudAPIError } from "./AssistantCloudAPI.js";
|
|
|
3
3
|
import { AssistantCloud } from "./AssistantCloud.js";
|
|
4
4
|
import { CloudMessagePersistence } from "./CloudMessagePersistence.js";
|
|
5
5
|
import { createFormattedPersistence } from "./FormattedCloudPersistence.js";
|
|
6
|
+
import { generateThreadTitle } from "./generateThreadTitle.js";
|
|
7
|
+
import { createRunTelemetryToolCall, normalizeRunTelemetryUsage, truncateRunTelemetryText } from "./runTelemetry.js";
|
|
6
8
|
import { createSamplingCollector, wrapSamplingHandler } from "./instrumentMcpSampling.js";
|
|
7
|
-
export { AssistantCloud, CloudAPIError, CloudMessagePersistence, CloudResponseError, createFormattedPersistence, createSamplingCollector, wrapSamplingHandler };
|
|
9
|
+
export { AssistantCloud, CloudAPIError, CloudMessagePersistence, CloudResponseError, createFormattedPersistence, createRunTelemetryToolCall, createSamplingCollector, generateThreadTitle, normalizeRunTelemetryUsage, truncateRunTelemetryText, wrapSamplingHandler };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SamplingCallData } from "./instrumentMcpSampling.js";
|
|
2
|
+
//#region src/runTelemetry.d.ts
|
|
3
|
+
type AssistantCloudRunReportToolCall = {
|
|
4
|
+
tool_name: string;
|
|
5
|
+
tool_call_id: string;
|
|
6
|
+
tool_args?: string;
|
|
7
|
+
tool_result?: string;
|
|
8
|
+
tool_source?: "mcp" | "frontend" | "backend";
|
|
9
|
+
start_ms?: number;
|
|
10
|
+
end_ms?: number;
|
|
11
|
+
sampling_calls?: SamplingCallData[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Clamps a string to the size the runs endpoint accepts for a single span
|
|
15
|
+
* field.
|
|
16
|
+
*/
|
|
17
|
+
declare function truncateRunTelemetryText(value: string): string;
|
|
18
|
+
type RunTelemetryToolCallInit = {
|
|
19
|
+
toolName: string;
|
|
20
|
+
toolCallId: string;
|
|
21
|
+
args?: unknown;
|
|
22
|
+
/**
|
|
23
|
+
* Pre-serialized arguments, used in place of serializing `args`. Values over
|
|
24
|
+
* the span size are clamped before they are included in the report.
|
|
25
|
+
*/
|
|
26
|
+
argsText?: string | undefined;
|
|
27
|
+
result?: unknown;
|
|
28
|
+
toolSource?: "mcp" | "frontend" | "backend" | undefined;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Serializes one tool call into the shape the runs endpoint accepts. An `mcp`
|
|
32
|
+
* source has its result summarized, because MCP content blocks carry inline
|
|
33
|
+
* base64 image and audio payloads that would otherwise dominate the report.
|
|
34
|
+
*/
|
|
35
|
+
declare function createRunTelemetryToolCall(init: RunTelemetryToolCallInit): AssistantCloudRunReportToolCall;
|
|
36
|
+
type RunTelemetryUsage = {
|
|
37
|
+
inputTokens?: number;
|
|
38
|
+
outputTokens?: number;
|
|
39
|
+
reasoningTokens?: number;
|
|
40
|
+
cachedInputTokens?: number;
|
|
41
|
+
};
|
|
42
|
+
type RunTelemetryUsageInit = RunTelemetryUsage & {
|
|
43
|
+
promptTokens?: number;
|
|
44
|
+
completionTokens?: number;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the token counts a provider reports under either the current or the
|
|
48
|
+
* legacy prompt/completion names. Returns undefined when no count is present,
|
|
49
|
+
* so callers can tell an empty usage object from a zeroed one.
|
|
50
|
+
*/
|
|
51
|
+
declare function normalizeRunTelemetryUsage(usage: RunTelemetryUsageInit): RunTelemetryUsage | undefined;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { AssistantCloudRunReportToolCall, RunTelemetryToolCallInit, RunTelemetryUsage, RunTelemetryUsageInit, createRunTelemetryToolCall, normalizeRunTelemetryUsage, truncateRunTelemetryText };
|
|
54
|
+
//# sourceMappingURL=runTelemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runTelemetry.d.ts","names":[],"sources":["../src/runTelemetry.ts"],"mappings":";;KAMY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,iBAAiB;;;;;;iBAOH,yBAAyB;KAwC7B;EACV;EACA;EACA;;;;;EAKA;EACA;EACA;;;;;;;iBAQc,2BACd,MAAM,2BACL;KAgBS;EACV;EACA;EACA;EACA;;KAGU,wBAAwB;EAClC;EACA;;;;;;;iBAQc,2BACd,OAAO,wBACN"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//#region src/runTelemetry.ts
|
|
2
|
+
const MAX_TELEMETRY_TEXT_LENGTH = 5e4;
|
|
3
|
+
const BASE64_PATTERN = /^[A-Za-z0-9+/]{100,}={0,2}$/;
|
|
4
|
+
/**
|
|
5
|
+
* Clamps a string to the size the runs endpoint accepts for a single span
|
|
6
|
+
* field.
|
|
7
|
+
*/
|
|
8
|
+
function truncateRunTelemetryText(value) {
|
|
9
|
+
if (value.length <= MAX_TELEMETRY_TEXT_LENGTH) return value;
|
|
10
|
+
return value.slice(0, MAX_TELEMETRY_TEXT_LENGTH);
|
|
11
|
+
}
|
|
12
|
+
function safeStringify(value) {
|
|
13
|
+
if (value == null) return void 0;
|
|
14
|
+
try {
|
|
15
|
+
return truncateRunTelemetryText(JSON.stringify(value));
|
|
16
|
+
} catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function summarizeMcpResult(value) {
|
|
21
|
+
if (value == null) return void 0;
|
|
22
|
+
try {
|
|
23
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
24
|
+
if (Array.isArray(parsed)) {
|
|
25
|
+
const summarized = parsed.map((item) => {
|
|
26
|
+
if (item && typeof item === "object" && item.type) {
|
|
27
|
+
if ((item.type === "image" || item.type === "audio") && typeof item.data === "string" && BASE64_PATTERN.test(item.data.slice(0, 200))) {
|
|
28
|
+
const sizeKB = (item.data.length * 3 / 4 / 1024).toFixed(1);
|
|
29
|
+
return {
|
|
30
|
+
...item,
|
|
31
|
+
data: `[${item.type}: ${sizeKB}KB]`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return item;
|
|
36
|
+
});
|
|
37
|
+
return truncateRunTelemetryText(JSON.stringify(summarized));
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
40
|
+
return safeStringify(value);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Serializes one tool call into the shape the runs endpoint accepts. An `mcp`
|
|
44
|
+
* source has its result summarized, because MCP content blocks carry inline
|
|
45
|
+
* base64 image and audio payloads that would otherwise dominate the report.
|
|
46
|
+
*/
|
|
47
|
+
function createRunTelemetryToolCall(init) {
|
|
48
|
+
const { toolName, toolCallId, args, argsText, result, toolSource } = init;
|
|
49
|
+
const call = {
|
|
50
|
+
tool_name: toolName,
|
|
51
|
+
tool_call_id: toolCallId
|
|
52
|
+
};
|
|
53
|
+
const toolArgs = argsText != null ? truncateRunTelemetryText(argsText) : safeStringify(args);
|
|
54
|
+
if (toolArgs !== void 0) call.tool_args = toolArgs;
|
|
55
|
+
const toolResult = toolSource === "mcp" ? summarizeMcpResult(result) : safeStringify(result);
|
|
56
|
+
if (toolResult !== void 0) call.tool_result = toolResult;
|
|
57
|
+
if (toolSource) call.tool_source = toolSource;
|
|
58
|
+
return call;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolves the token counts a provider reports under either the current or the
|
|
62
|
+
* legacy prompt/completion names. Returns undefined when no count is present,
|
|
63
|
+
* so callers can tell an empty usage object from a zeroed one.
|
|
64
|
+
*/
|
|
65
|
+
function normalizeRunTelemetryUsage(usage) {
|
|
66
|
+
const inputTokens = usage.inputTokens ?? usage.promptTokens;
|
|
67
|
+
const outputTokens = usage.outputTokens ?? usage.completionTokens;
|
|
68
|
+
if (inputTokens == null && outputTokens == null && usage.reasoningTokens == null && usage.cachedInputTokens == null) return;
|
|
69
|
+
return {
|
|
70
|
+
...inputTokens != null ? { inputTokens } : void 0,
|
|
71
|
+
...outputTokens != null ? { outputTokens } : void 0,
|
|
72
|
+
...usage.reasoningTokens != null ? { reasoningTokens: usage.reasoningTokens } : void 0,
|
|
73
|
+
...usage.cachedInputTokens != null ? { cachedInputTokens: usage.cachedInputTokens } : void 0
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { createRunTelemetryToolCall, normalizeRunTelemetryUsage, truncateRunTelemetryText };
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=runTelemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runTelemetry.js","names":[],"sources":["../src/runTelemetry.ts"],"sourcesContent":["import type { SamplingCallData } from \"./instrumentMcpSampling\";\n\nconst MAX_TELEMETRY_TEXT_LENGTH = 50_000;\n\nconst BASE64_PATTERN = /^[A-Za-z0-9+/]{100,}={0,2}$/;\n\nexport type AssistantCloudRunReportToolCall = {\n tool_name: string;\n tool_call_id: string;\n tool_args?: string;\n tool_result?: string;\n tool_source?: \"mcp\" | \"frontend\" | \"backend\";\n start_ms?: number;\n end_ms?: number;\n sampling_calls?: SamplingCallData[];\n};\n\n/**\n * Clamps a string to the size the runs endpoint accepts for a single span\n * field.\n */\nexport function truncateRunTelemetryText(value: string): string {\n if (value.length <= MAX_TELEMETRY_TEXT_LENGTH) return value;\n return value.slice(0, MAX_TELEMETRY_TEXT_LENGTH);\n}\n\nfunction safeStringify(value: unknown): string | undefined {\n if (value == null) return undefined;\n try {\n return truncateRunTelemetryText(JSON.stringify(value));\n } catch {\n return undefined;\n }\n}\n\nfunction summarizeMcpResult(value: unknown): string | undefined {\n if (value == null) return undefined;\n try {\n const parsed = typeof value === \"string\" ? JSON.parse(value) : value;\n if (Array.isArray(parsed)) {\n const summarized = parsed.map((item) => {\n if (item && typeof item === \"object\" && item.type) {\n if (\n (item.type === \"image\" || item.type === \"audio\") &&\n typeof item.data === \"string\" &&\n BASE64_PATTERN.test(item.data.slice(0, 200))\n ) {\n const sizeKB = ((item.data.length * 3) / 4 / 1024).toFixed(1);\n return { ...item, data: `[${item.type}: ${sizeKB}KB]` };\n }\n }\n return item;\n });\n return truncateRunTelemetryText(JSON.stringify(summarized));\n }\n } catch {\n // not JSON array, fall through\n }\n return safeStringify(value);\n}\n\nexport type RunTelemetryToolCallInit = {\n toolName: string;\n toolCallId: string;\n args?: unknown;\n /**\n * Pre-serialized arguments, used in place of serializing `args`. Values over\n * the span size are clamped before they are included in the report.\n */\n argsText?: string | undefined;\n result?: unknown;\n toolSource?: \"mcp\" | \"frontend\" | \"backend\" | undefined;\n};\n\n/**\n * Serializes one tool call into the shape the runs endpoint accepts. An `mcp`\n * source has its result summarized, because MCP content blocks carry inline\n * base64 image and audio payloads that would otherwise dominate the report.\n */\nexport function createRunTelemetryToolCall(\n init: RunTelemetryToolCallInit,\n): AssistantCloudRunReportToolCall {\n const { toolName, toolCallId, args, argsText, result, toolSource } = init;\n const call: AssistantCloudRunReportToolCall = {\n tool_name: toolName,\n tool_call_id: toolCallId,\n };\n const toolArgs =\n argsText != null ? truncateRunTelemetryText(argsText) : safeStringify(args);\n if (toolArgs !== undefined) call.tool_args = toolArgs;\n const toolResult =\n toolSource === \"mcp\" ? summarizeMcpResult(result) : safeStringify(result);\n if (toolResult !== undefined) call.tool_result = toolResult;\n if (toolSource) call.tool_source = toolSource;\n return call;\n}\n\nexport type RunTelemetryUsage = {\n inputTokens?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n};\n\nexport type RunTelemetryUsageInit = RunTelemetryUsage & {\n promptTokens?: number;\n completionTokens?: number;\n};\n\n/**\n * Resolves the token counts a provider reports under either the current or the\n * legacy prompt/completion names. Returns undefined when no count is present,\n * so callers can tell an empty usage object from a zeroed one.\n */\nexport function normalizeRunTelemetryUsage(\n usage: RunTelemetryUsageInit,\n): RunTelemetryUsage | undefined {\n const inputTokens = usage.inputTokens ?? usage.promptTokens;\n const outputTokens = usage.outputTokens ?? usage.completionTokens;\n\n if (\n inputTokens == null &&\n outputTokens == null &&\n usage.reasoningTokens == null &&\n usage.cachedInputTokens == null\n ) {\n return undefined;\n }\n\n return {\n ...(inputTokens != null ? { inputTokens } : undefined),\n ...(outputTokens != null ? { outputTokens } : undefined),\n ...(usage.reasoningTokens != null\n ? { reasoningTokens: usage.reasoningTokens }\n : undefined),\n ...(usage.cachedInputTokens != null\n ? { cachedInputTokens: usage.cachedInputTokens }\n : undefined),\n };\n}\n"],"mappings":";AAEA,MAAM,4BAA4B;AAElC,MAAM,iBAAiB;;;;;AAiBvB,SAAgB,yBAAyB,OAAuB;CAC9D,IAAI,MAAM,UAAU,2BAA2B,OAAO;CACtD,OAAO,MAAM,MAAM,GAAG,yBAAyB;AACjD;AAEA,SAAS,cAAc,OAAoC;CACzD,IAAI,SAAS,MAAM,OAAO,KAAA;CAC1B,IAAI;EACF,OAAO,yBAAyB,KAAK,UAAU,KAAK,CAAC;CACvD,QAAQ;EACN;CACF;AACF;AAEA,SAAS,mBAAmB,OAAoC;CAC9D,IAAI,SAAS,MAAM,OAAO,KAAA;CAC1B,IAAI;EACF,MAAM,SAAS,OAAO,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;EAC/D,IAAI,MAAM,QAAQ,MAAM,GAAG;GACzB,MAAM,aAAa,OAAO,KAAK,SAAS;IACtC,IAAI,QAAQ,OAAO,SAAS,YAAY,KAAK,MAExC;UAAA,KAAK,SAAS,WAAW,KAAK,SAAS,YACxC,OAAO,KAAK,SAAS,YACrB,eAAe,KAAK,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,GAC3C;MACA,MAAM,UAAW,KAAK,KAAK,SAAS,IAAK,IAAI,KAAA,CAAM,QAAQ,CAAC;MAC5D,OAAO;OAAE,GAAG;OAAM,MAAM,IAAI,KAAK,KAAK,IAAI,OAAO;MAAK;KACxD;;IAEF,OAAO;GACT,CAAC;GACD,OAAO,yBAAyB,KAAK,UAAU,UAAU,CAAC;EAC5D;CACF,QAAQ,CAER;CACA,OAAO,cAAc,KAAK;AAC5B;;;;;;AAoBA,SAAgB,2BACd,MACiC;CACjC,MAAM,EAAE,UAAU,YAAY,MAAM,UAAU,QAAQ,eAAe;CACrE,MAAM,OAAwC;EAC5C,WAAW;EACX,cAAc;CAChB;CACA,MAAM,WACJ,YAAY,OAAO,yBAAyB,QAAQ,IAAI,cAAc,IAAI;CAC5E,IAAI,aAAa,KAAA,GAAW,KAAK,YAAY;CAC7C,MAAM,aACJ,eAAe,QAAQ,mBAAmB,MAAM,IAAI,cAAc,MAAM;CAC1E,IAAI,eAAe,KAAA,GAAW,KAAK,cAAc;CACjD,IAAI,YAAY,KAAK,cAAc;CACnC,OAAO;AACT;;;;;;AAmBA,SAAgB,2BACd,OAC+B;CAC/B,MAAM,cAAc,MAAM,eAAe,MAAM;CAC/C,MAAM,eAAe,MAAM,gBAAgB,MAAM;CAEjD,IACE,eAAe,QACf,gBAAgB,QAChB,MAAM,mBAAmB,QACzB,MAAM,qBAAqB,MAE3B;CAGF,OAAO;EACL,GAAI,eAAe,OAAO,EAAE,YAAY,IAAI,KAAA;EAC5C,GAAI,gBAAgB,OAAO,EAAE,aAAa,IAAI,KAAA;EAC9C,GAAI,MAAM,mBAAmB,OACzB,EAAE,iBAAiB,MAAM,gBAAgB,IACzC,KAAA;EACJ,GAAI,MAAM,qBAAqB,OAC3B,EAAE,mBAAmB,MAAM,kBAAkB,IAC7C,KAAA;CACN;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assistant-cloud",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"description": "Cloud integration for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
],
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"assistant-stream": "^0.3.
|
|
30
|
+
"assistant-stream": "^0.3.40"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"
|
|
35
|
-
"
|
|
33
|
+
"@assistant-ui/x-buildutils": "0.0.24",
|
|
34
|
+
"@types/node": "^26.2.0",
|
|
35
|
+
"vitest": "^4.1.11"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public",
|
package/src/AssistantCloud.ts
CHANGED
package/src/AssistantCloudAPI.ts
CHANGED
|
@@ -139,16 +139,17 @@ export class AssistantCloudAPI {
|
|
|
139
139
|
|
|
140
140
|
if (!response.ok) {
|
|
141
141
|
const text = await response.text();
|
|
142
|
+
let message: string | undefined;
|
|
142
143
|
try {
|
|
143
144
|
const body = JSON.parse(text);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
if (typeof body?.message === "string" && body.message.length > 0) {
|
|
146
|
+
message = body.message;
|
|
147
|
+
}
|
|
148
|
+
} catch {}
|
|
149
|
+
throw new CloudAPIError(
|
|
150
|
+
message ?? `Request failed with status ${response.status}, ${text}`,
|
|
151
|
+
response.status,
|
|
152
|
+
);
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
return response;
|
|
@@ -178,7 +178,16 @@ export class AssistantCloudAPIKeyAuthStrategy implements AssistantCloudAuthStrat
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
const
|
|
181
|
+
const LEGACY_AUI_REFRESH_TOKEN_NAME = "aui:refresh_token";
|
|
182
|
+
|
|
183
|
+
const getRefreshTokenName = (baseUrl: string): string =>
|
|
184
|
+
`${LEGACY_AUI_REFRESH_TOKEN_NAME}:${baseUrl}`;
|
|
185
|
+
|
|
186
|
+
const removeLegacyRefreshToken = (storage: Storage): void => {
|
|
187
|
+
try {
|
|
188
|
+
storage.removeItem(LEGACY_AUI_REFRESH_TOKEN_NAME);
|
|
189
|
+
} catch {}
|
|
190
|
+
};
|
|
182
191
|
|
|
183
192
|
const getLocalStorage = (): Storage | null => {
|
|
184
193
|
if (!("localStorage" in globalThis)) return null;
|
|
@@ -189,32 +198,52 @@ const getLocalStorage = (): Storage | null => {
|
|
|
189
198
|
}
|
|
190
199
|
};
|
|
191
200
|
|
|
192
|
-
const readRefreshToken = (): RefreshToken | undefined => {
|
|
201
|
+
const readRefreshToken = (baseUrl: string): RefreshToken | undefined => {
|
|
193
202
|
const storage = getLocalStorage();
|
|
194
203
|
if (!storage) return undefined;
|
|
195
204
|
try {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
const name = getRefreshTokenName(baseUrl);
|
|
206
|
+
const value = storage.getItem(name);
|
|
207
|
+
if (value) {
|
|
208
|
+
removeLegacyRefreshToken(storage);
|
|
209
|
+
return JSON.parse(value) as RefreshToken;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const legacyValue = storage.getItem(LEGACY_AUI_REFRESH_TOKEN_NAME);
|
|
213
|
+
if (!legacyValue) return undefined;
|
|
214
|
+
|
|
215
|
+
let refreshToken: RefreshToken;
|
|
216
|
+
try {
|
|
217
|
+
refreshToken = JSON.parse(legacyValue) as RefreshToken;
|
|
218
|
+
} catch {
|
|
219
|
+
removeLegacyRefreshToken(storage);
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
storage.setItem(name, legacyValue);
|
|
224
|
+
removeLegacyRefreshToken(storage);
|
|
225
|
+
return refreshToken;
|
|
200
226
|
} catch {
|
|
201
227
|
return undefined;
|
|
202
228
|
}
|
|
203
229
|
};
|
|
204
230
|
|
|
205
|
-
const writeRefreshToken = (
|
|
231
|
+
const writeRefreshToken = (
|
|
232
|
+
baseUrl: string,
|
|
233
|
+
refreshToken: RefreshToken,
|
|
234
|
+
): void => {
|
|
206
235
|
const storage = getLocalStorage();
|
|
207
236
|
if (!storage) return;
|
|
208
237
|
try {
|
|
209
|
-
storage.setItem(
|
|
238
|
+
storage.setItem(getRefreshTokenName(baseUrl), JSON.stringify(refreshToken));
|
|
210
239
|
} catch {}
|
|
211
240
|
};
|
|
212
241
|
|
|
213
|
-
const removeRefreshToken = (): void => {
|
|
242
|
+
const removeRefreshToken = (baseUrl: string): void => {
|
|
214
243
|
const storage = getLocalStorage();
|
|
215
244
|
if (!storage) return;
|
|
216
245
|
try {
|
|
217
|
-
storage.removeItem(
|
|
246
|
+
storage.removeItem(getRefreshTokenName(baseUrl));
|
|
218
247
|
} catch {}
|
|
219
248
|
};
|
|
220
249
|
|
|
@@ -228,7 +257,7 @@ export class AssistantCloudAnonymousAuthStrategy implements AssistantCloudAuthSt
|
|
|
228
257
|
this.baseUrl = baseUrl;
|
|
229
258
|
this.jwtStrategy = new AssistantCloudJWTAuthStrategy(async () => {
|
|
230
259
|
const currentTime = Date.now();
|
|
231
|
-
const storedRefreshToken = readRefreshToken();
|
|
260
|
+
const storedRefreshToken = readRefreshToken(this.baseUrl);
|
|
232
261
|
|
|
233
262
|
if (storedRefreshToken) {
|
|
234
263
|
const refreshExpiry = new Date(storedRefreshToken.expires_at).getTime();
|
|
@@ -249,6 +278,7 @@ export class AssistantCloudAnonymousAuthStrategy implements AssistantCloudAuthSt
|
|
|
249
278
|
);
|
|
250
279
|
if (data.refresh_token != null) {
|
|
251
280
|
writeRefreshToken(
|
|
281
|
+
this.baseUrl,
|
|
252
282
|
readRefreshTokenResponse(
|
|
253
283
|
data.refresh_token,
|
|
254
284
|
"refresh auth token response.refresh_token",
|
|
@@ -257,8 +287,14 @@ export class AssistantCloudAnonymousAuthStrategy implements AssistantCloudAuthSt
|
|
|
257
287
|
}
|
|
258
288
|
return accessToken;
|
|
259
289
|
}
|
|
290
|
+
|
|
291
|
+
if (response.status === 429 || response.status >= 500) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
`Assistant Cloud token refresh failed with status ${response.status}`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
260
296
|
} else {
|
|
261
|
-
removeRefreshToken();
|
|
297
|
+
removeRefreshToken(this.baseUrl);
|
|
262
298
|
}
|
|
263
299
|
}
|
|
264
300
|
|
|
@@ -275,6 +311,7 @@ export class AssistantCloudAnonymousAuthStrategy implements AssistantCloudAuthSt
|
|
|
275
311
|
);
|
|
276
312
|
|
|
277
313
|
writeRefreshToken(
|
|
314
|
+
this.baseUrl,
|
|
278
315
|
readRefreshTokenResponse(
|
|
279
316
|
data.refresh_token,
|
|
280
317
|
"anonymous auth token response.refresh_token",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { AssistantCloudAPI } from "./AssistantCloudAPI";
|
|
3
|
+
import { AssistantCloudAuthTokens } from "./AssistantCloudAuthTokens";
|
|
4
|
+
|
|
5
|
+
const createCloudAuthTokens = () => {
|
|
6
|
+
const makeRequest = vi.fn();
|
|
7
|
+
const api = { makeRequest } as unknown as AssistantCloudAPI;
|
|
8
|
+
return { tokens: new AssistantCloudAuthTokens(api), makeRequest };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe("AssistantCloudAuthTokens responses", () => {
|
|
12
|
+
it("decodes a valid token response", async () => {
|
|
13
|
+
const { tokens, makeRequest } = createCloudAuthTokens();
|
|
14
|
+
makeRequest.mockResolvedValue({ token: "token-1" });
|
|
15
|
+
|
|
16
|
+
await expect(tokens.create()).resolves.toEqual({ token: "token-1" });
|
|
17
|
+
expect(makeRequest).toHaveBeenCalledWith("/auth/tokens", {
|
|
18
|
+
method: "POST",
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("rejects a response that is missing the token", async () => {
|
|
23
|
+
const { tokens, makeRequest } = createCloudAuthTokens();
|
|
24
|
+
makeRequest.mockResolvedValue({});
|
|
25
|
+
|
|
26
|
+
await expect(tokens.create()).rejects.toThrow(
|
|
27
|
+
'Invalid Assistant Cloud response for "token": expected a string',
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssistantCloudAPI } from "./AssistantCloudAPI";
|
|
2
|
+
import { readCloudRecord, readCloudString } from "./cloudResponse";
|
|
2
3
|
|
|
3
4
|
type AssistantCloudAuthTokensCreateResponse = {
|
|
4
5
|
token: string;
|
|
@@ -12,6 +13,11 @@ export class AssistantCloudAuthTokens {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
public async create(): Promise<AssistantCloudAuthTokensCreateResponse> {
|
|
15
|
-
|
|
16
|
+
const response = readCloudRecord(
|
|
17
|
+
await this.cloud.makeRequest("/auth/tokens", { method: "POST" }),
|
|
18
|
+
"auth token response",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return { token: readCloudString(response.token, "token") };
|
|
16
22
|
}
|
|
17
23
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { AssistantCloudAPI } from "./AssistantCloudAPI";
|
|
3
|
+
import { AssistantCloudFiles } from "./AssistantCloudFiles";
|
|
4
|
+
|
|
5
|
+
const createCloudFiles = () => {
|
|
6
|
+
const makeRequest = vi.fn();
|
|
7
|
+
const api = { makeRequest } as unknown as AssistantCloudAPI;
|
|
8
|
+
return { files: new AssistantCloudFiles(api), makeRequest };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe("AssistantCloudFiles responses", () => {
|
|
12
|
+
it("decodes PDF conversion responses", async () => {
|
|
13
|
+
const { files, makeRequest } = createCloudFiles();
|
|
14
|
+
makeRequest.mockResolvedValue({
|
|
15
|
+
success: true,
|
|
16
|
+
urls: ["https://example.com/page-1.png"],
|
|
17
|
+
message: "converted",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await expect(
|
|
21
|
+
files.pdfToImages({ file_url: "https://example.com/file.pdf" }),
|
|
22
|
+
).resolves.toEqual({
|
|
23
|
+
success: true,
|
|
24
|
+
urls: ["https://example.com/page-1.png"],
|
|
25
|
+
message: "converted",
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("rejects malformed PDF conversion responses", async () => {
|
|
30
|
+
const { files, makeRequest } = createCloudFiles();
|
|
31
|
+
makeRequest.mockResolvedValue({
|
|
32
|
+
success: true,
|
|
33
|
+
urls: [42],
|
|
34
|
+
message: "converted",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await expect(files.pdfToImages({ file_blob: "data" })).rejects.toThrow(
|
|
38
|
+
'Invalid Assistant Cloud response for "PDF conversion response.urls[0]": expected a string',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("decodes presigned upload responses", async () => {
|
|
43
|
+
const { files, makeRequest } = createCloudFiles();
|
|
44
|
+
makeRequest.mockResolvedValue({
|
|
45
|
+
success: true,
|
|
46
|
+
signedUrl: "https://uploads.example.com/file",
|
|
47
|
+
expiresAt: "2026-08-16T12:00:00.000Z",
|
|
48
|
+
publicUrl: "https://cdn.example.com/file",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await expect(
|
|
52
|
+
files.generatePresignedUploadUrl({ filename: "notes.txt" }),
|
|
53
|
+
).resolves.toEqual({
|
|
54
|
+
success: true,
|
|
55
|
+
signedUrl: "https://uploads.example.com/file",
|
|
56
|
+
expiresAt: "2026-08-16T12:00:00.000Z",
|
|
57
|
+
publicUrl: "https://cdn.example.com/file",
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("rejects malformed presigned upload responses", async () => {
|
|
62
|
+
const { files, makeRequest } = createCloudFiles();
|
|
63
|
+
makeRequest.mockResolvedValue({});
|
|
64
|
+
|
|
65
|
+
await expect(
|
|
66
|
+
files.generatePresignedUploadUrl({ filename: "notes.txt" }),
|
|
67
|
+
).rejects.toThrow(
|
|
68
|
+
'Invalid Assistant Cloud response for "presigned upload response.success": expected a boolean',
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { AssistantCloudAPI } from "./AssistantCloudAPI";
|
|
2
|
+
import {
|
|
3
|
+
readCloudArray,
|
|
4
|
+
readCloudBoolean,
|
|
5
|
+
readCloudRecord,
|
|
6
|
+
readCloudString,
|
|
7
|
+
} from "./cloudResponse";
|
|
2
8
|
|
|
3
9
|
type PdfToImagesRequestBody = {
|
|
4
10
|
file_blob?: string | undefined;
|
|
@@ -32,21 +38,61 @@ export class AssistantCloudFiles {
|
|
|
32
38
|
public async pdfToImages(
|
|
33
39
|
body: PdfToImagesRequestBody,
|
|
34
40
|
): Promise<PdfToImagesResponse> {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
const response = readCloudRecord(
|
|
42
|
+
await this.cloud.makeRequest("/files/pdf-to-images", {
|
|
43
|
+
method: "POST",
|
|
44
|
+
body,
|
|
45
|
+
}),
|
|
46
|
+
"PDF conversion response",
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
success: readCloudBoolean(
|
|
51
|
+
response.success,
|
|
52
|
+
"PDF conversion response.success",
|
|
53
|
+
),
|
|
54
|
+
urls: readCloudArray(response.urls, "PDF conversion response.urls").map(
|
|
55
|
+
(url, index) =>
|
|
56
|
+
readCloudString(url, `PDF conversion response.urls[${index}]`),
|
|
57
|
+
),
|
|
58
|
+
message: readCloudString(
|
|
59
|
+
response.message,
|
|
60
|
+
"PDF conversion response.message",
|
|
61
|
+
),
|
|
62
|
+
};
|
|
39
63
|
}
|
|
40
64
|
|
|
41
65
|
public async generatePresignedUploadUrl(
|
|
42
66
|
body: GeneratePresignedUploadUrlRequestBody,
|
|
43
67
|
): Promise<GeneratePresignedUploadUrlResponse> {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
const response = readCloudRecord(
|
|
69
|
+
await this.cloud.makeRequest(
|
|
70
|
+
"/files/attachments/generate-presigned-upload-url",
|
|
71
|
+
{
|
|
72
|
+
method: "POST",
|
|
73
|
+
body,
|
|
74
|
+
},
|
|
75
|
+
),
|
|
76
|
+
"presigned upload response",
|
|
50
77
|
);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
success: readCloudBoolean(
|
|
81
|
+
response.success,
|
|
82
|
+
"presigned upload response.success",
|
|
83
|
+
),
|
|
84
|
+
signedUrl: readCloudString(
|
|
85
|
+
response.signedUrl,
|
|
86
|
+
"presigned upload response.signedUrl",
|
|
87
|
+
),
|
|
88
|
+
expiresAt: readCloudString(
|
|
89
|
+
response.expiresAt,
|
|
90
|
+
"presigned upload response.expiresAt",
|
|
91
|
+
),
|
|
92
|
+
publicUrl: readCloudString(
|
|
93
|
+
response.publicUrl,
|
|
94
|
+
"presigned upload response.publicUrl",
|
|
95
|
+
),
|
|
96
|
+
};
|
|
51
97
|
}
|
|
52
98
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { AssistantCloudAPI } from "./AssistantCloudAPI";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AssistantCloudRunReportToolCall } from "./runTelemetry";
|
|
3
3
|
import { AssistantStream, PlainTextDecoder } from "assistant-stream";
|
|
4
|
+
import {
|
|
5
|
+
CloudResponseError,
|
|
6
|
+
readCloudRecord,
|
|
7
|
+
readCloudString,
|
|
8
|
+
} from "./cloudResponse";
|
|
4
9
|
|
|
5
10
|
type AssistantCloudRunsStreamBody = {
|
|
6
11
|
thread_id: string;
|
|
@@ -8,17 +13,6 @@ type AssistantCloudRunsStreamBody = {
|
|
|
8
13
|
messages: readonly unknown[]; // TODO type
|
|
9
14
|
};
|
|
10
15
|
|
|
11
|
-
type ReportToolCall = {
|
|
12
|
-
tool_name: string;
|
|
13
|
-
tool_call_id: string;
|
|
14
|
-
tool_args?: string;
|
|
15
|
-
tool_result?: string;
|
|
16
|
-
tool_source?: "mcp" | "frontend" | "backend";
|
|
17
|
-
start_ms?: number;
|
|
18
|
-
end_ms?: number;
|
|
19
|
-
sampling_calls?: SamplingCallData[];
|
|
20
|
-
};
|
|
21
|
-
|
|
22
16
|
// NOTE: Keep this payload shape aligned with the strict runtime validator in
|
|
23
17
|
// assistant-cloud: apps/aui-cloud-api/src/endpoints/runs/create.ts
|
|
24
18
|
// (createRunSchema). New telemetry fields must be added in both repos together.
|
|
@@ -26,13 +20,13 @@ export type AssistantCloudRunReport = {
|
|
|
26
20
|
thread_id: string;
|
|
27
21
|
status: "completed" | "incomplete" | "error";
|
|
28
22
|
total_steps?: number;
|
|
29
|
-
tool_calls?:
|
|
23
|
+
tool_calls?: AssistantCloudRunReportToolCall[];
|
|
30
24
|
steps?: {
|
|
31
25
|
input_tokens?: number;
|
|
32
26
|
output_tokens?: number;
|
|
33
27
|
reasoning_tokens?: number;
|
|
34
28
|
cached_input_tokens?: number;
|
|
35
|
-
tool_calls?:
|
|
29
|
+
tool_calls?: AssistantCloudRunReportToolCall[];
|
|
36
30
|
start_ms?: number;
|
|
37
31
|
end_ms?: number;
|
|
38
32
|
}[];
|
|
@@ -83,12 +77,40 @@ export class AssistantCloudRuns {
|
|
|
83
77
|
},
|
|
84
78
|
body,
|
|
85
79
|
});
|
|
80
|
+
|
|
81
|
+
if (!response.body) {
|
|
82
|
+
throw new CloudResponseError(
|
|
83
|
+
'Invalid Assistant Cloud response for "run stream": expected a response body',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const receivedContentType = response.headers.get("content-type");
|
|
88
|
+
const contentType = receivedContentType
|
|
89
|
+
?.split(";", 1)[0]
|
|
90
|
+
?.trim()
|
|
91
|
+
.toLowerCase();
|
|
92
|
+
if (contentType !== "text/plain") {
|
|
93
|
+
await response.body.cancel().catch(() => undefined);
|
|
94
|
+
throw new CloudResponseError(
|
|
95
|
+
`Invalid Assistant Cloud response for "run stream": expected a "text/plain" content type, received ${
|
|
96
|
+
receivedContentType
|
|
97
|
+
? `"${receivedContentType}"`
|
|
98
|
+
: "no Content-Type header"
|
|
99
|
+
}`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
86
103
|
return AssistantStream.fromResponse(response, new PlainTextDecoder());
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
public async report(
|
|
90
107
|
body: AssistantCloudRunReport,
|
|
91
108
|
): Promise<{ run_id: string }> {
|
|
92
|
-
|
|
109
|
+
const response = readCloudRecord(
|
|
110
|
+
await this.cloud.makeRequest("/runs", { method: "POST", body }),
|
|
111
|
+
"run report response",
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return { run_id: readCloudString(response.run_id, "run_id") };
|
|
93
115
|
}
|
|
94
116
|
}
|