assistant-cloud 0.1.42 → 0.2.0
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 +40 -0
- package/dist/AssistantCloud.d.ts +4 -0
- package/dist/AssistantCloud.d.ts.map +1 -1
- package/dist/AssistantCloud.js +11 -5
- package/dist/AssistantCloud.js.map +1 -1
- package/dist/AssistantCloudAPI.d.ts +17 -1
- package/dist/AssistantCloudAPI.d.ts.map +1 -1
- package/dist/AssistantCloudAPI.js +20 -9
- package/dist/AssistantCloudAPI.js.map +1 -1
- package/dist/AssistantCloudAuthStrategy.d.ts +4 -1
- package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
- package/dist/AssistantCloudAuthStrategy.js +79 -18
- package/dist/AssistantCloudAuthStrategy.js.map +1 -1
- package/dist/AssistantCloudEvents.d.ts +32 -0
- package/dist/AssistantCloudEvents.d.ts.map +1 -0
- package/dist/AssistantCloudEvents.js +115 -0
- package/dist/AssistantCloudEvents.js.map +1 -0
- package/dist/AssistantCloudFiles.d.ts +12 -1
- package/dist/AssistantCloudFiles.d.ts.map +1 -1
- package/dist/AssistantCloudFiles.js +13 -1
- package/dist/AssistantCloudFiles.js.map +1 -1
- package/dist/AssistantCloudRuns.d.ts +11 -0
- package/dist/AssistantCloudRuns.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.js.map +1 -1
- package/dist/AssistantCloudScores.d.ts +27 -0
- package/dist/AssistantCloudScores.d.ts.map +1 -0
- package/dist/AssistantCloudScores.js +30 -0
- package/dist/AssistantCloudScores.js.map +1 -0
- package/dist/AssistantCloudThreadMessages.d.ts +11 -1
- package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.js +12 -1
- package/dist/AssistantCloudThreadMessages.js.map +1 -1
- package/dist/AssistantCloudThreads.d.ts +8 -0
- package/dist/AssistantCloudThreads.d.ts.map +1 -1
- package/dist/AssistantCloudThreads.js +9 -1
- package/dist/AssistantCloudThreads.js.map +1 -1
- package/dist/CloudMessagePersistence.d.ts +4 -0
- package/dist/CloudMessagePersistence.d.ts.map +1 -1
- package/dist/CloudMessagePersistence.js +53 -18
- package/dist/CloudMessagePersistence.js.map +1 -1
- package/dist/cloudResponse.d.ts +4 -1
- package/dist/cloudResponse.d.ts.map +1 -1
- package/dist/cloudResponse.js +14 -1
- package/dist/cloudResponse.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -2
- package/dist/runTelemetry.d.ts +79 -9
- package/dist/runTelemetry.d.ts.map +1 -1
- package/dist/runTelemetry.js +152 -6
- package/dist/runTelemetry.js.map +1 -1
- package/dist/telemetry/index.d.ts +32 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +62 -0
- package/dist/telemetry/index.js.map +1 -0
- package/package.json +28 -4
- package/src/AssistantCloud.ts +20 -8
- package/src/AssistantCloudAPI.ts +42 -10
- package/src/AssistantCloudAuthStrategy.ts +153 -46
- package/src/AssistantCloudEvents.test.ts +124 -0
- package/src/AssistantCloudEvents.ts +185 -0
- package/src/AssistantCloudFiles.test.ts +59 -0
- package/src/AssistantCloudFiles.ts +39 -0
- package/src/AssistantCloudRuns.ts +12 -1
- package/src/AssistantCloudScores.test.ts +113 -0
- package/src/AssistantCloudScores.ts +61 -0
- package/src/AssistantCloudThreadMessages.test.ts +51 -0
- package/src/AssistantCloudThreadMessages.ts +33 -0
- package/src/AssistantCloudThreads.test.ts +21 -0
- package/src/AssistantCloudThreads.ts +21 -0
- package/src/CloudMessagePersistence.ts +65 -29
- package/src/cloudResponse.test.ts +34 -1
- package/src/cloudResponse.ts +30 -0
- package/src/index.ts +24 -1
- package/src/runTelemetry.test.ts +366 -0
- package/src/runTelemetry.ts +271 -16
- package/src/telemetry/index.test.ts +252 -0
- package/src/telemetry/index.ts +98 -0
- package/src/tests/AssistantCloud.test.ts +22 -0
- package/src/tests/AssistantCloudAPI.test.ts +44 -4
- package/src/tests/AssistantCloudAuthStrategy.test.ts +354 -11
- package/src/tests/CloudMessagePersistence.test.ts +208 -0
package/dist/runTelemetry.js.map
CHANGED
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"file":"runTelemetry.js","names":[],"sources":["../src/runTelemetry.ts"],"sourcesContent":["import type { SamplingCallData } from \"./instrumentMcpSampling\";\nimport type { AssistantCloudRunReport } from \"./AssistantCloudRuns\";\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\nexport type RunReportOutcome =\n | \"aborted\"\n | \"disconnected\"\n | \"length\"\n | \"content_filter\";\n\n/**\n * Maps a finish event to the report status and outcome. `fallbackStatus`\n * applies when the event carries neither a finish reason nor a failure flag.\n */\nexport function deriveRunOutcome(\n input: {\n finishReason?: string | undefined;\n isAbort?: boolean | undefined;\n isDisconnect?: boolean | undefined;\n isError?: boolean | undefined;\n },\n fallbackStatus: \"completed\" | \"incomplete\" = \"completed\",\n): {\n status: \"completed\" | \"incomplete\" | \"error\";\n outcome?: RunReportOutcome;\n} {\n if (input.isError) return { status: \"error\" };\n if (input.isAbort) return { status: \"incomplete\", outcome: \"aborted\" };\n if (input.isDisconnect) {\n return { status: \"incomplete\", outcome: \"disconnected\" };\n }\n switch (input.finishReason) {\n case \"length\":\n return { status: \"incomplete\", outcome: \"length\" };\n case \"content-filter\":\n case \"content_filter\":\n return { status: \"incomplete\", outcome: \"content_filter\" };\n case \"cancelled\":\n return { status: \"incomplete\", outcome: \"aborted\" };\n case \"error\":\n return { status: \"error\" };\n default:\n return {\n status: input.finishReason === undefined ? fallbackStatus : \"completed\",\n };\n }\n}\n\nconst MAX_RUN_ERROR_CODE_LENGTH = 64;\nconst MAX_RUN_ERROR_LENGTH = 2048;\n\n/**\n * Reads the message and code the runs endpoint stores for a failed run. The\n * code is the error's `code` when it has one, else its class name.\n */\nexport function describeRunError(error: unknown): {\n error?: string;\n errorCode?: string;\n} {\n if (error == null) return {};\n const record =\n typeof error === \"object\" ? (error as Record<string, unknown>) : undefined;\n const message =\n typeof error === \"string\"\n ? error\n : typeof record?.message === \"string\"\n ? record.message\n : undefined;\n const code =\n typeof record?.code === \"string\"\n ? record.code\n : typeof record?.name === \"string\" && record.name !== \"Error\"\n ? record.name\n : undefined;\n return {\n ...(message\n ? { error: message.slice(0, MAX_RUN_ERROR_LENGTH) }\n : undefined),\n ...(code\n ? { errorCode: code.slice(0, MAX_RUN_ERROR_CODE_LENGTH) }\n : undefined),\n };\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\n/**\n * Resolves the model ID a run reports, in the order an app can supply it: an\n * explicit `modelId`, the `custom` bag, then the per-step `response.modelId`\n * that a `messageMetadata` callback copies off the AI SDK's finish-step part.\n * The AI SDK puts no model ID on a UI message part, so message metadata is the\n * only channel one arrives on.\n */\nexport function extractRunTelemetryModelId(\n metadata: Record<string, unknown> | undefined,\n): string | undefined {\n if (!metadata) return undefined;\n if (typeof metadata.modelId === \"string\") return metadata.modelId;\n const custom = metadata.custom as Record<string, unknown> | undefined;\n if (typeof custom?.modelId === \"string\") return custom.modelId;\n\n const steps: unknown = metadata.steps;\n if (!Array.isArray(steps)) return undefined;\n for (const step of steps as unknown[]) {\n if (!step || typeof step !== \"object\") continue;\n const response = (step as Record<string, unknown>).response;\n if (!response || typeof response !== \"object\") continue;\n const modelId = (response as Record<string, unknown>).modelId;\n if (typeof modelId === \"string\") return modelId;\n }\n return undefined;\n}\n\nexport type RunTelemetryUsage = {\n inputTokens?: number | undefined;\n outputTokens?: number | undefined;\n reasoningTokens?: number | undefined;\n cachedInputTokens?: number | undefined;\n};\n\nexport type RunTelemetryUsageInit = RunTelemetryUsage & {\n promptTokens?: number | undefined;\n completionTokens?: number | undefined;\n inputTokenDetails?: { cacheReadTokens?: number };\n outputTokenDetails?: { reasoningTokens?: number };\n};\n\nexport type RunReportStepInit = {\n usage?: RunTelemetryUsageInit | undefined;\n toolCalls?: AssistantCloudRunReportToolCall[] | undefined;\n startMs?: number | undefined;\n endMs?: number | undefined;\n finishReason?: string | undefined;\n};\n\nexport type RunReportInit = {\n threadId: string;\n status: AssistantCloudRunReport[\"status\"];\n outcome?: RunReportOutcome | undefined;\n errorCode?: string | undefined;\n error?: string | undefined;\n messageId?: string | undefined;\n traceId?: string | undefined;\n modelId?: string | undefined;\n provider?: string | undefined;\n usage?: RunTelemetryUsageInit | undefined;\n steps?: RunReportStepInit[] | undefined;\n totalSteps?: number | undefined;\n toolCalls?: AssistantCloudRunReportToolCall[] | undefined;\n durationMs?: number | undefined;\n firstTokenMs?: number | undefined;\n outputText?: string | undefined;\n metadata?: Record<string, unknown> | undefined;\n telemetry?: {\n environment?: string | undefined;\n release?: string | undefined;\n tags?: readonly string[] | undefined;\n };\n};\n\nfunction assignUsage(\n report: AssistantCloudRunReport,\n usage: RunTelemetryUsageInit | undefined,\n): void {\n if (!usage) return;\n const normalized = normalizeRunTelemetryUsage(usage);\n if (!normalized) return;\n if (normalized.inputTokens !== undefined) {\n report.input_tokens = normalized.inputTokens;\n }\n if (normalized.outputTokens !== undefined) {\n report.output_tokens = normalized.outputTokens;\n }\n if (normalized.reasoningTokens !== undefined) {\n report.reasoning_tokens = normalized.reasoningTokens;\n }\n if (normalized.cachedInputTokens !== undefined) {\n report.cached_input_tokens = normalized.cachedInputTokens;\n }\n}\n\nfunction createRunReportStep(\n init: RunReportStepInit,\n): NonNullable<AssistantCloudRunReport[\"steps\"]>[number] {\n const step: NonNullable<AssistantCloudRunReport[\"steps\"]>[number] = {};\n const usage = init.usage ? normalizeRunTelemetryUsage(init.usage) : undefined;\n if (usage?.inputTokens !== undefined) step.input_tokens = usage.inputTokens;\n if (usage?.outputTokens !== undefined)\n step.output_tokens = usage.outputTokens;\n if (usage?.reasoningTokens !== undefined) {\n step.reasoning_tokens = usage.reasoningTokens;\n }\n if (usage?.cachedInputTokens !== undefined) {\n step.cached_input_tokens = usage.cachedInputTokens;\n }\n if (init.toolCalls !== undefined) step.tool_calls = init.toolCalls;\n if (init.startMs !== undefined) step.start_ms = init.startMs;\n if (init.endMs !== undefined) step.end_ms = init.endMs;\n if (init.finishReason !== undefined) {\n step.finish_reason = init.finishReason.slice(0, 32);\n }\n return step;\n}\n\nfunction normalizeRunReportTags(\n tags: readonly string[] | undefined,\n): string[] | undefined {\n if (!tags) return undefined;\n const result: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n const normalized = tag.trim().slice(0, 64).trim();\n if (!normalized || seen.has(normalized)) continue;\n seen.add(normalized);\n result.push(normalized);\n if (result.length === 20) break;\n }\n return result.length > 0 ? result : undefined;\n}\n\nfunction normalizeRunReportMilliseconds(\n value: number | undefined,\n): number | undefined {\n if (value === undefined || !Number.isFinite(value)) return undefined;\n return Math.max(0, Math.round(value));\n}\n\nexport function createRunReport(init: RunReportInit): AssistantCloudRunReport {\n const report: AssistantCloudRunReport = {\n thread_id: init.threadId,\n status: init.status,\n };\n const traceId = init.traceId?.toLowerCase();\n if (traceId && /^[0-9a-f]{32}$/.test(traceId)) report.trace_id = traceId;\n if (init.outcome !== undefined) report.outcome_type = init.outcome;\n if (init.errorCode !== undefined) report.error_code = init.errorCode;\n if (init.error !== undefined) report.error = init.error;\n if (init.messageId !== undefined) report.message_id = init.messageId;\n if (init.modelId !== undefined) report.model_id = init.modelId;\n if (init.provider !== undefined) {\n report.provider = init.provider;\n report.provider_type = init.provider;\n }\n assignUsage(report, init.usage);\n if (init.steps !== undefined) {\n report.steps = init.steps.map(createRunReportStep);\n report.total_steps = init.steps.length;\n } else if (init.totalSteps !== undefined) {\n report.total_steps = init.totalSteps;\n }\n if (init.toolCalls !== undefined) report.tool_calls = init.toolCalls;\n const durationMs = normalizeRunReportMilliseconds(init.durationMs);\n if (durationMs !== undefined) report.duration_ms = durationMs;\n const firstTokenMs = normalizeRunReportMilliseconds(init.firstTokenMs);\n if (firstTokenMs !== undefined) report.first_token_ms = firstTokenMs;\n if (init.outputText !== undefined) {\n report.output_text = truncateRunTelemetryText(init.outputText);\n }\n if (init.metadata !== undefined) report.metadata = init.metadata;\n if (init.telemetry?.environment !== undefined) {\n report.environment = init.telemetry.environment;\n }\n if (init.telemetry?.release !== undefined) {\n report.release = init.telemetry.release;\n }\n const tags = normalizeRunReportTags(init.telemetry?.tags);\n if (tags !== undefined) report.tags = tags;\n return report;\n}\n\n/**\n * Resolves the token counts a provider reports under any of the names the AI\n * SDK has used: the current top-level ones, the legacy prompt/completion pair,\n * and the v7 token detail objects. 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 // AI SDK v7 moved these under token detail objects; v6 kept them top-level.\n const reasoningTokens =\n usage.reasoningTokens ?? usage.outputTokenDetails?.reasoningTokens;\n const cachedInputTokens =\n usage.cachedInputTokens ?? usage.inputTokenDetails?.cacheReadTokens;\n\n if (\n inputTokens == null &&\n outputTokens == null &&\n reasoningTokens == null &&\n cachedInputTokens == null\n ) {\n return undefined;\n }\n\n return {\n ...(inputTokens != null ? { inputTokens } : undefined),\n ...(outputTokens != null ? { outputTokens } : undefined),\n ...(reasoningTokens != null ? { reasoningTokens } : undefined),\n ...(cachedInputTokens != null ? { cachedInputTokens } : undefined),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ReadableSpan, SpanExporter, SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
//#region src/telemetry/index.d.ts
|
|
3
|
+
type AssistantCloudTraceExportOptions = {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
type AssistantCloudSpanProcessorOptions = {
|
|
9
|
+
filter?: (span: ReadableSpan) => boolean;
|
|
10
|
+
};
|
|
11
|
+
declare function assistantCloudTraceExportOptions({ apiKey, baseUrl, headers }: AssistantCloudTraceExportOptions): {
|
|
12
|
+
url: string;
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
declare function createAssistantCloudTraceExporter(options: AssistantCloudTraceExportOptions): SpanExporter;
|
|
16
|
+
declare function isAssistantCloudSpan(span: ReadableSpan): boolean;
|
|
17
|
+
declare function createAssistantCloudSpanProcessor(exporter: SpanExporter, options?: AssistantCloudSpanProcessorOptions): SpanProcessor;
|
|
18
|
+
declare function assistantCloudTraceMetadata(): {
|
|
19
|
+
traceId?: string;
|
|
20
|
+
};
|
|
21
|
+
declare function withAssistantCloudTraceMetadata<Part extends {
|
|
22
|
+
type: string;
|
|
23
|
+
} = {
|
|
24
|
+
type: string;
|
|
25
|
+
}>(messageMetadata?: (options: {
|
|
26
|
+
part: Part;
|
|
27
|
+
}) => Record<string, unknown> | undefined): (options: {
|
|
28
|
+
part: Part;
|
|
29
|
+
}) => Record<string, unknown> | undefined;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { AssistantCloudSpanProcessorOptions, AssistantCloudTraceExportOptions, assistantCloudTraceExportOptions, assistantCloudTraceMetadata, createAssistantCloudSpanProcessor, createAssistantCloudTraceExporter, isAssistantCloudSpan, withAssistantCloudTraceMetadata };
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/telemetry/index.ts"],"mappings":";;KAYY;EACV;EACA;EACA,UAAU;;KAGA;EACV,UAAU,MAAM;;iBAGF,mCACd,QACA,SACA,WACC;EACD;EACA,SAAS;;iBAmBK,kCACd,SAAS,mCACR;iBAIa,qBAAqB,MAAM;iBAO3B,kCACd,UAAU,cACV,UAAS,qCACR;iBAca;EAAiC;;iBAOjC,gCACd;EAAe;;EAAmB;GAElC,mBAAmB;EACjB,MAAM;MACF,uCACJ;EAAW,MAAM;MAAW"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { trace } from "@opentelemetry/api";
|
|
2
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
3
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
4
|
+
//#region src/telemetry/index.ts
|
|
5
|
+
const DEFAULT_BACKEND_BASE_URL = "https://backend.assistant-api.com";
|
|
6
|
+
const AI_SPAN_PREFIXES = [
|
|
7
|
+
"gen_ai.",
|
|
8
|
+
"ai.",
|
|
9
|
+
"llm."
|
|
10
|
+
];
|
|
11
|
+
function assistantCloudTraceExportOptions({ apiKey, baseUrl = DEFAULT_BACKEND_BASE_URL, headers }) {
|
|
12
|
+
if (!apiKey) throw new Error("An Assistant Cloud API key is required");
|
|
13
|
+
return {
|
|
14
|
+
url: `${trimTrailingSlashes(baseUrl)}/v1/traces`,
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${apiKey}`,
|
|
17
|
+
...headers
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function trimTrailingSlashes(url) {
|
|
22
|
+
let end = url.length;
|
|
23
|
+
while (end > 0 && url[end - 1] === "/") end--;
|
|
24
|
+
return url.slice(0, end);
|
|
25
|
+
}
|
|
26
|
+
function createAssistantCloudTraceExporter(options) {
|
|
27
|
+
return new OTLPTraceExporter(assistantCloudTraceExportOptions(options));
|
|
28
|
+
}
|
|
29
|
+
function isAssistantCloudSpan(span) {
|
|
30
|
+
return typeof span.attributes["gen_ai.operation.name"] === "string" || AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix));
|
|
31
|
+
}
|
|
32
|
+
function createAssistantCloudSpanProcessor(exporter, options = {}) {
|
|
33
|
+
const processor = new BatchSpanProcessor(exporter);
|
|
34
|
+
const filter = options.filter ?? isAssistantCloudSpan;
|
|
35
|
+
return {
|
|
36
|
+
onStart: (span, parentContext) => processor.onStart(span, parentContext),
|
|
37
|
+
onEnd: (span) => {
|
|
38
|
+
if (filter(span)) processor.onEnd(span);
|
|
39
|
+
},
|
|
40
|
+
forceFlush: () => processor.forceFlush(),
|
|
41
|
+
shutdown: () => processor.shutdown()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function assistantCloudTraceMetadata() {
|
|
45
|
+
const spanContext = trace.getActiveSpan()?.spanContext();
|
|
46
|
+
return spanContext && trace.isSpanContextValid(spanContext) ? { traceId: spanContext.traceId } : {};
|
|
47
|
+
}
|
|
48
|
+
function withAssistantCloudTraceMetadata(messageMetadata) {
|
|
49
|
+
return (options) => {
|
|
50
|
+
const metadata = messageMetadata?.(options);
|
|
51
|
+
if (options.part.type !== "start") return metadata;
|
|
52
|
+
const trace = assistantCloudTraceMetadata();
|
|
53
|
+
return trace.traceId === void 0 ? metadata : {
|
|
54
|
+
...metadata,
|
|
55
|
+
...trace
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { assistantCloudTraceExportOptions, assistantCloudTraceMetadata, createAssistantCloudSpanProcessor, createAssistantCloudTraceExporter, isAssistantCloudSpan, withAssistantCloudTraceMetadata };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/telemetry/index.ts"],"sourcesContent":["import { trace } from \"@opentelemetry/api\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n BatchSpanProcessor,\n type ReadableSpan,\n type SpanExporter,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\n\nconst DEFAULT_BACKEND_BASE_URL = \"https://backend.assistant-api.com\";\nconst AI_SPAN_PREFIXES = [\"gen_ai.\", \"ai.\", \"llm.\"];\n\nexport type AssistantCloudTraceExportOptions = {\n apiKey: string;\n baseUrl?: string;\n headers?: Record<string, string>;\n};\n\nexport type AssistantCloudSpanProcessorOptions = {\n filter?: (span: ReadableSpan) => boolean;\n};\n\nexport function assistantCloudTraceExportOptions({\n apiKey,\n baseUrl = DEFAULT_BACKEND_BASE_URL,\n headers,\n}: AssistantCloudTraceExportOptions): {\n url: string;\n headers: Record<string, string>;\n} {\n if (!apiKey) throw new Error(\"An Assistant Cloud API key is required\");\n\n return {\n url: `${trimTrailingSlashes(baseUrl)}/v1/traces`,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n ...headers,\n },\n };\n}\n\nfunction trimTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === \"/\") end--;\n return url.slice(0, end);\n}\n\nexport function createAssistantCloudTraceExporter(\n options: AssistantCloudTraceExportOptions,\n): SpanExporter {\n return new OTLPTraceExporter(assistantCloudTraceExportOptions(options));\n}\n\nexport function isAssistantCloudSpan(span: ReadableSpan): boolean {\n return (\n typeof span.attributes[\"gen_ai.operation.name\"] === \"string\" ||\n AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))\n );\n}\n\nexport function createAssistantCloudSpanProcessor(\n exporter: SpanExporter,\n options: AssistantCloudSpanProcessorOptions = {},\n): SpanProcessor {\n const processor = new BatchSpanProcessor(exporter);\n const filter = options.filter ?? isAssistantCloudSpan;\n\n return {\n onStart: (span, parentContext) => processor.onStart(span, parentContext),\n onEnd: (span) => {\n if (filter(span)) processor.onEnd(span);\n },\n forceFlush: () => processor.forceFlush(),\n shutdown: () => processor.shutdown(),\n };\n}\n\nexport function assistantCloudTraceMetadata(): { traceId?: string } {\n const spanContext = trace.getActiveSpan()?.spanContext();\n return spanContext && trace.isSpanContextValid(spanContext)\n ? { traceId: spanContext.traceId }\n : {};\n}\n\nexport function withAssistantCloudTraceMetadata<\n Part extends { type: string } = { type: string },\n>(\n messageMetadata?: (options: {\n part: Part;\n }) => Record<string, unknown> | undefined,\n): (options: { part: Part }) => Record<string, unknown> | undefined {\n return (options) => {\n const metadata = messageMetadata?.(options);\n if (options.part.type !== \"start\") return metadata;\n const trace = assistantCloudTraceMetadata();\n return trace.traceId === undefined ? metadata : { ...metadata, ...trace };\n };\n}\n"],"mappings":";;;;AASA,MAAM,2BAA2B;AACjC,MAAM,mBAAmB;CAAC;CAAW;CAAO;AAAM;AAYlD,SAAgB,iCAAiC,EAC/C,QACA,UAAU,0BACV,WAIA;CACA,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,wCAAwC;CAErE,OAAO;EACL,KAAK,GAAG,oBAAoB,OAAO,EAAE;EACrC,SAAS;GACP,eAAe,UAAU;GACzB,GAAG;EACL;CACF;AACF;AAEA,SAAS,oBAAoB,KAAqB;CAChD,IAAI,MAAM,IAAI;CACd,OAAO,MAAM,KAAK,IAAI,MAAM,OAAO,KAAK;CACxC,OAAO,IAAI,MAAM,GAAG,GAAG;AACzB;AAEA,SAAgB,kCACd,SACc;CACd,OAAO,IAAI,kBAAkB,iCAAiC,OAAO,CAAC;AACxE;AAEA,SAAgB,qBAAqB,MAA6B;CAChE,OACE,OAAO,KAAK,WAAW,6BAA6B,YACpD,iBAAiB,MAAM,WAAW,KAAK,KAAK,WAAW,MAAM,CAAC;AAElE;AAEA,SAAgB,kCACd,UACA,UAA8C,CAAC,GAChC;CACf,MAAM,YAAY,IAAI,mBAAmB,QAAQ;CACjD,MAAM,SAAS,QAAQ,UAAU;CAEjC,OAAO;EACL,UAAU,MAAM,kBAAkB,UAAU,QAAQ,MAAM,aAAa;EACvE,QAAQ,SAAS;GACf,IAAI,OAAO,IAAI,GAAG,UAAU,MAAM,IAAI;EACxC;EACA,kBAAkB,UAAU,WAAW;EACvC,gBAAgB,UAAU,SAAS;CACrC;AACF;AAEA,SAAgB,8BAAoD;CAClE,MAAM,cAAc,MAAM,cAAc,CAAC,EAAE,YAAY;CACvD,OAAO,eAAe,MAAM,mBAAmB,WAAW,IACtD,EAAE,SAAS,YAAY,QAAQ,IAC/B,CAAC;AACP;AAEA,SAAgB,gCAGd,iBAGkE;CAClE,QAAQ,YAAY;EAClB,MAAM,WAAW,kBAAkB,OAAO;EAC1C,IAAI,QAAQ,KAAK,SAAS,SAAS,OAAO;EAC1C,MAAM,QAAQ,4BAA4B;EAC1C,OAAO,MAAM,YAAY,KAAA,IAAY,WAAW;GAAE,GAAG;GAAU,GAAG;EAAM;CAC1E;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assistant-cloud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Cloud integration for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
".": {
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./telemetry": {
|
|
21
|
+
"types": "./dist/telemetry/index.d.ts",
|
|
22
|
+
"default": "./dist/telemetry/index.js"
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
"main": "./dist/index.js",
|
|
@@ -27,11 +31,31 @@
|
|
|
27
31
|
],
|
|
28
32
|
"sideEffects": false,
|
|
29
33
|
"dependencies": {
|
|
30
|
-
"assistant-stream": "^0.3.
|
|
34
|
+
"assistant-stream": "^0.3.42"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@opentelemetry/api": "^1.9.0",
|
|
38
|
+
"@opentelemetry/exporter-trace-otlp-http": ">=0.200.0",
|
|
39
|
+
"@opentelemetry/sdk-trace-base": "^2.1.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@opentelemetry/api": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"@opentelemetry/exporter-trace-otlp-http": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"@opentelemetry/sdk-trace-base": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
31
51
|
},
|
|
32
52
|
"devDependencies": {
|
|
33
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
34
|
-
"@
|
|
53
|
+
"@assistant-ui/x-buildutils": "0.0.26",
|
|
54
|
+
"@opentelemetry/api": "^1.9.0",
|
|
55
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.205.0",
|
|
56
|
+
"@opentelemetry/sdk-trace-base": "^2.1.0",
|
|
57
|
+
"@types/node": "^26.4.1",
|
|
58
|
+
"jsdom": "^30.0.1",
|
|
35
59
|
"vitest": "^4.1.11"
|
|
36
60
|
},
|
|
37
61
|
"publishConfig": {
|
package/src/AssistantCloud.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { AssistantCloudProjects } from "./AssistantCloudProjects";
|
|
|
8
8
|
import { AssistantCloudRuns } from "./AssistantCloudRuns";
|
|
9
9
|
import { AssistantCloudThreads } from "./AssistantCloudThreads";
|
|
10
10
|
import { AssistantCloudFiles } from "./AssistantCloudFiles";
|
|
11
|
+
import { AssistantCloudEvents } from "./AssistantCloudEvents";
|
|
12
|
+
import { AssistantCloudScores } from "./AssistantCloudScores";
|
|
11
13
|
|
|
12
14
|
export class AssistantCloud {
|
|
13
15
|
public readonly threads;
|
|
@@ -15,10 +17,23 @@ export class AssistantCloud {
|
|
|
15
17
|
public readonly auth;
|
|
16
18
|
public readonly runs;
|
|
17
19
|
public readonly files;
|
|
20
|
+
public readonly events;
|
|
21
|
+
public readonly scores;
|
|
18
22
|
public readonly telemetry: AssistantCloudTelemetryConfig;
|
|
19
23
|
|
|
20
24
|
constructor(config: AssistantCloudConfig) {
|
|
21
25
|
const api = new AssistantCloudAPI(config);
|
|
26
|
+
const t = config.telemetry;
|
|
27
|
+
this.telemetry =
|
|
28
|
+
t === false
|
|
29
|
+
? { enabled: false }
|
|
30
|
+
: t === true || t === undefined
|
|
31
|
+
? { enabled: true }
|
|
32
|
+
: {
|
|
33
|
+
...t,
|
|
34
|
+
enabled: t.enabled !== false,
|
|
35
|
+
};
|
|
36
|
+
|
|
22
37
|
this.threads = new AssistantCloudThreads(api);
|
|
23
38
|
this.projects = new AssistantCloudProjects(api);
|
|
24
39
|
this.auth = {
|
|
@@ -26,13 +41,10 @@ export class AssistantCloud {
|
|
|
26
41
|
};
|
|
27
42
|
this.runs = new AssistantCloudRuns(api);
|
|
28
43
|
this.files = new AssistantCloudFiles(api);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
: t === true || t === undefined
|
|
35
|
-
? { enabled: true }
|
|
36
|
-
: { ...t, enabled: t.enabled !== false };
|
|
44
|
+
this.events = new AssistantCloudEvents(
|
|
45
|
+
api,
|
|
46
|
+
() => this.telemetry.enabled !== false && this.telemetry.events !== false,
|
|
47
|
+
);
|
|
48
|
+
this.scores = new AssistantCloudScores(api);
|
|
37
49
|
}
|
|
38
50
|
}
|
package/src/AssistantCloudAPI.ts
CHANGED
|
@@ -3,11 +3,25 @@ import {
|
|
|
3
3
|
AssistantCloudJWTAuthStrategy,
|
|
4
4
|
AssistantCloudAPIKeyAuthStrategy,
|
|
5
5
|
AssistantCloudAnonymousAuthStrategy,
|
|
6
|
+
normalizeBaseUrl,
|
|
6
7
|
} from "./AssistantCloudAuthStrategy";
|
|
7
8
|
import type { AssistantCloudRunReport } from "./AssistantCloudRuns";
|
|
8
9
|
|
|
9
10
|
export type AssistantCloudTelemetryConfig = {
|
|
11
|
+
/**
|
|
12
|
+
* Enables Assistant Cloud telemetry. Defaults to `true`. Set to `false` to
|
|
13
|
+
* disable both run reports and engagement events.
|
|
14
|
+
*/
|
|
10
15
|
enabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Enables Assistant Cloud engagement events. Defaults to `true` when
|
|
18
|
+
* telemetry is enabled. Set to `false` to keep run reports while disabling
|
|
19
|
+
* engagement events.
|
|
20
|
+
*/
|
|
21
|
+
events?: boolean;
|
|
22
|
+
release?: string;
|
|
23
|
+
environment?: string;
|
|
24
|
+
tags?: string[];
|
|
11
25
|
/**
|
|
12
26
|
* Called before each telemetry report is sent.
|
|
13
27
|
* Return a modified report to enrich it (e.g. add `model_id`),
|
|
@@ -50,10 +64,19 @@ export type AssistantCloudConfig = (
|
|
|
50
64
|
|
|
51
65
|
export class CloudAPIError extends Error {
|
|
52
66
|
public readonly status: number;
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
public readonly code?: string;
|
|
68
|
+
public readonly details?: Record<string, unknown>;
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
message: string,
|
|
72
|
+
status: number,
|
|
73
|
+
code?: string,
|
|
74
|
+
details?: Record<string, unknown>,
|
|
75
|
+
) {
|
|
55
76
|
super(message);
|
|
56
77
|
this.status = status;
|
|
78
|
+
if (code !== undefined) this.code = code;
|
|
79
|
+
if (details !== undefined) this.details = details;
|
|
57
80
|
this.name = "CloudAPIError";
|
|
58
81
|
}
|
|
59
82
|
}
|
|
@@ -63,11 +86,7 @@ type MakeRequestOptions = {
|
|
|
63
86
|
headers?: Record<string, string> | undefined;
|
|
64
87
|
query?: Record<string, string | number | boolean> | undefined;
|
|
65
88
|
body?: object | undefined;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const normalizeBaseUrl = (baseUrl: string) => {
|
|
69
|
-
if (!baseUrl || !baseUrl.endsWith("/")) return baseUrl;
|
|
70
|
-
return baseUrl.slice(0, -1);
|
|
89
|
+
keepalive?: boolean | undefined;
|
|
71
90
|
};
|
|
72
91
|
|
|
73
92
|
export class AssistantCloudAPI {
|
|
@@ -133,6 +152,7 @@ export class AssistantCloudAPI {
|
|
|
133
152
|
method: options.method ?? "GET",
|
|
134
153
|
headers,
|
|
135
154
|
body: options.body ? JSON.stringify(options.body) : null,
|
|
155
|
+
...(options.keepalive ? { keepalive: true } : {}),
|
|
136
156
|
});
|
|
137
157
|
|
|
138
158
|
this._auth.readAuthHeaders(response.headers);
|
|
@@ -140,15 +160,27 @@ export class AssistantCloudAPI {
|
|
|
140
160
|
if (!response.ok) {
|
|
141
161
|
const text = await response.text();
|
|
142
162
|
let message: string | undefined;
|
|
163
|
+
let code: string | undefined;
|
|
164
|
+
let details: Record<string, unknown> | undefined;
|
|
143
165
|
try {
|
|
144
|
-
const body = JSON.parse(text);
|
|
145
|
-
if (typeof body
|
|
146
|
-
|
|
166
|
+
const body = JSON.parse(text) as unknown;
|
|
167
|
+
if (typeof body === "object" && body !== null && !Array.isArray(body)) {
|
|
168
|
+
const record = body as Record<string, unknown>;
|
|
169
|
+
if (typeof record.message === "string" && record.message.length > 0) {
|
|
170
|
+
message = record.message;
|
|
171
|
+
}
|
|
172
|
+
if (typeof record.error === "string") {
|
|
173
|
+
code = record.error;
|
|
174
|
+
details = { ...record };
|
|
175
|
+
delete details.error;
|
|
176
|
+
}
|
|
147
177
|
}
|
|
148
178
|
} catch {}
|
|
149
179
|
throw new CloudAPIError(
|
|
150
180
|
message ?? `Request failed with status ${response.status}, ${text}`,
|
|
151
181
|
response.status,
|
|
182
|
+
code,
|
|
183
|
+
details,
|
|
152
184
|
);
|
|
153
185
|
}
|
|
154
186
|
|
|
@@ -4,6 +4,34 @@ import {
|
|
|
4
4
|
readCloudString,
|
|
5
5
|
} from "./cloudResponse";
|
|
6
6
|
|
|
7
|
+
const AUTH_TOKEN_REQUEST_TIMEOUT_MS = 30_000;
|
|
8
|
+
|
|
9
|
+
const withAuthTokenDeadline = async <T>(
|
|
10
|
+
operation: string,
|
|
11
|
+
run: (signal: AbortSignal) => Promise<T>,
|
|
12
|
+
): Promise<T> => {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
let timedOut = false;
|
|
15
|
+
const timeout = setTimeout(() => {
|
|
16
|
+
timedOut = true;
|
|
17
|
+
controller.abort();
|
|
18
|
+
}, AUTH_TOKEN_REQUEST_TIMEOUT_MS);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
return await run(controller.signal);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (timedOut) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Assistant Cloud ${operation} timed out after ${AUTH_TOKEN_REQUEST_TIMEOUT_MS}ms`,
|
|
26
|
+
{ cause: error },
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
} finally {
|
|
31
|
+
clearTimeout(timeout);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
7
35
|
export type AssistantCloudAuthStrategy = {
|
|
8
36
|
readonly strategy: "anon" | "jwt" | "api-key";
|
|
9
37
|
getAuthHeaders(): Promise<Record<string, string> | false>;
|
|
@@ -228,6 +256,19 @@ const readRefreshToken = (baseUrl: string): RefreshToken | undefined => {
|
|
|
228
256
|
}
|
|
229
257
|
};
|
|
230
258
|
|
|
259
|
+
export const normalizeBaseUrl = (baseUrl: string): string => {
|
|
260
|
+
if (!baseUrl || !baseUrl.endsWith("/")) return baseUrl;
|
|
261
|
+
return baseUrl.slice(0, -1);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/** The refresh token of the anonymous identity this browser holds for `baseUrl`, or null when it has none. */
|
|
265
|
+
export const readAnonymousRefreshToken = (baseUrl: string): string | null => {
|
|
266
|
+
const refreshToken = readRefreshToken(normalizeBaseUrl(baseUrl));
|
|
267
|
+
if (!refreshToken) return null;
|
|
268
|
+
const refreshExpiry = new Date(refreshToken.expires_at).getTime();
|
|
269
|
+
return refreshExpiry - Date.now() > 30 * 1000 ? refreshToken.token : null;
|
|
270
|
+
};
|
|
271
|
+
|
|
231
272
|
const writeRefreshToken = (
|
|
232
273
|
baseUrl: string,
|
|
233
274
|
refreshToken: RefreshToken,
|
|
@@ -247,6 +288,52 @@ const removeRefreshToken = (baseUrl: string): void => {
|
|
|
247
288
|
} catch {}
|
|
248
289
|
};
|
|
249
290
|
|
|
291
|
+
// In-flight sharing follows refresh-token storage scope to isolate server requests.
|
|
292
|
+
const anonymousAuthTokenRequests = new WeakMap<
|
|
293
|
+
Storage,
|
|
294
|
+
Map<string, Promise<string | null>>
|
|
295
|
+
>();
|
|
296
|
+
|
|
297
|
+
const getWebLockManager = (): LockManager | null => {
|
|
298
|
+
if (!("navigator" in globalThis)) return null;
|
|
299
|
+
return (
|
|
300
|
+
(globalThis as { navigator?: { locks?: LockManager } }).navigator?.locks ??
|
|
301
|
+
null
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const getAnonymousAuthLockName = (baseUrl: string): string =>
|
|
306
|
+
`assistant-cloud:anonymous-auth:${baseUrl}`;
|
|
307
|
+
|
|
308
|
+
const getSharedAnonymousAuthToken = (
|
|
309
|
+
baseUrl: string,
|
|
310
|
+
requestToken: () => Promise<string | null>,
|
|
311
|
+
): Promise<string | null> => {
|
|
312
|
+
const storage = getLocalStorage();
|
|
313
|
+
if (!storage) return requestToken();
|
|
314
|
+
|
|
315
|
+
let storageRequests = anonymousAuthTokenRequests.get(storage);
|
|
316
|
+
if (!storageRequests) {
|
|
317
|
+
storageRequests = new Map();
|
|
318
|
+
anonymousAuthTokenRequests.set(storage, storageRequests);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const activeRequest = storageRequests.get(baseUrl);
|
|
322
|
+
if (activeRequest) return activeRequest;
|
|
323
|
+
|
|
324
|
+
const locks = getWebLockManager();
|
|
325
|
+
const request = locks
|
|
326
|
+
? locks.request(getAnonymousAuthLockName(baseUrl), requestToken)
|
|
327
|
+
: requestToken();
|
|
328
|
+
const sharedRequest = request.finally(() => {
|
|
329
|
+
if (storageRequests.get(baseUrl) === sharedRequest) {
|
|
330
|
+
storageRequests.delete(baseUrl);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
storageRequests.set(baseUrl, sharedRequest);
|
|
334
|
+
return sharedRequest;
|
|
335
|
+
};
|
|
336
|
+
|
|
250
337
|
export class AssistantCloudAnonymousAuthStrategy implements AssistantCloudAuthStrategy {
|
|
251
338
|
public readonly strategy = "anon";
|
|
252
339
|
|
|
@@ -255,70 +342,90 @@ export class AssistantCloudAnonymousAuthStrategy implements AssistantCloudAuthSt
|
|
|
255
342
|
|
|
256
343
|
constructor(baseUrl: string) {
|
|
257
344
|
this.baseUrl = baseUrl;
|
|
258
|
-
|
|
345
|
+
const requestAuthToken = async (): Promise<string | null> => {
|
|
259
346
|
const currentTime = Date.now();
|
|
260
347
|
const storedRefreshToken = readRefreshToken(this.baseUrl);
|
|
261
348
|
|
|
262
349
|
if (storedRefreshToken) {
|
|
263
350
|
const refreshExpiry = new Date(storedRefreshToken.expires_at).getTime();
|
|
264
351
|
if (refreshExpiry - currentTime > 30 * 1000) {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
352
|
+
const refreshedAccessToken = await withAuthTokenDeadline(
|
|
353
|
+
"refresh token request",
|
|
354
|
+
async (signal) => {
|
|
355
|
+
const response = await fetch(
|
|
356
|
+
`${this.baseUrl}/v1/auth/tokens/refresh`,
|
|
357
|
+
{
|
|
358
|
+
method: "POST",
|
|
359
|
+
headers: { "Content-Type": "application/json" },
|
|
360
|
+
body: JSON.stringify({
|
|
361
|
+
refresh_token: storedRefreshToken.token,
|
|
362
|
+
}),
|
|
363
|
+
signal,
|
|
364
|
+
},
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (response.ok) {
|
|
368
|
+
const { data, accessToken } = await readAuthTokenResponse(
|
|
369
|
+
response,
|
|
370
|
+
"refresh auth token response",
|
|
371
|
+
);
|
|
372
|
+
if (data.refresh_token != null) {
|
|
373
|
+
writeRefreshToken(
|
|
374
|
+
this.baseUrl,
|
|
375
|
+
readRefreshTokenResponse(
|
|
376
|
+
data.refresh_token,
|
|
377
|
+
"refresh auth token response.refresh_token",
|
|
378
|
+
),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
return accessToken;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (response.status === 429 || response.status >= 500) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`Assistant Cloud token refresh failed with status ${response.status}`,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return null;
|
|
271
391
|
},
|
|
272
392
|
);
|
|
273
|
-
|
|
274
|
-
if (response.ok) {
|
|
275
|
-
const { data, accessToken } = await readAuthTokenResponse(
|
|
276
|
-
response,
|
|
277
|
-
"refresh auth token response",
|
|
278
|
-
);
|
|
279
|
-
if (data.refresh_token != null) {
|
|
280
|
-
writeRefreshToken(
|
|
281
|
-
this.baseUrl,
|
|
282
|
-
readRefreshTokenResponse(
|
|
283
|
-
data.refresh_token,
|
|
284
|
-
"refresh auth token response.refresh_token",
|
|
285
|
-
),
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
return accessToken;
|
|
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
|
-
}
|
|
393
|
+
if (refreshedAccessToken !== null) return refreshedAccessToken;
|
|
296
394
|
} else {
|
|
297
395
|
removeRefreshToken(this.baseUrl);
|
|
298
396
|
}
|
|
299
397
|
}
|
|
300
398
|
|
|
301
399
|
// No valid refresh token; request a new anonymous token
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
400
|
+
return withAuthTokenDeadline(
|
|
401
|
+
"anonymous token request",
|
|
402
|
+
async (signal) => {
|
|
403
|
+
const response = await fetch(
|
|
404
|
+
`${this.baseUrl}/v1/auth/tokens/anonymous`,
|
|
405
|
+
{ method: "POST", signal },
|
|
406
|
+
);
|
|
305
407
|
|
|
306
|
-
|
|
408
|
+
if (!response.ok) return null;
|
|
307
409
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
410
|
+
const { data, accessToken } = await readAuthTokenResponse(
|
|
411
|
+
response,
|
|
412
|
+
"anonymous auth token response",
|
|
413
|
+
);
|
|
312
414
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
415
|
+
writeRefreshToken(
|
|
416
|
+
this.baseUrl,
|
|
417
|
+
readRefreshTokenResponse(
|
|
418
|
+
data.refresh_token,
|
|
419
|
+
"anonymous auth token response.refresh_token",
|
|
420
|
+
),
|
|
421
|
+
);
|
|
422
|
+
return accessToken;
|
|
423
|
+
},
|
|
319
424
|
);
|
|
320
|
-
|
|
321
|
-
|
|
425
|
+
};
|
|
426
|
+
this.jwtStrategy = new AssistantCloudJWTAuthStrategy(() =>
|
|
427
|
+
getSharedAnonymousAuthToken(this.baseUrl, requestAuthToken),
|
|
428
|
+
);
|
|
322
429
|
}
|
|
323
430
|
|
|
324
431
|
public async getAuthHeaders(): Promise<Record<string, string> | false> {
|