assistant-cloud 0.2.0 → 0.2.1

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.
Files changed (80) hide show
  1. package/README.md +26 -0
  2. package/dist/AssistantCloud.d.ts +2 -1
  3. package/dist/AssistantCloud.d.ts.map +1 -1
  4. package/dist/AssistantCloud.js +2 -0
  5. package/dist/AssistantCloud.js.map +1 -1
  6. package/dist/AssistantCloudAPI.d.ts +11 -2
  7. package/dist/AssistantCloudAPI.d.ts.map +1 -1
  8. package/dist/AssistantCloudAPI.js +17 -1
  9. package/dist/AssistantCloudAPI.js.map +1 -1
  10. package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
  11. package/dist/AssistantCloudAuthStrategy.js +4 -2
  12. package/dist/AssistantCloudAuthStrategy.js.map +1 -1
  13. package/dist/AssistantCloudEvents.d.ts.map +1 -1
  14. package/dist/AssistantCloudEvents.js +4 -0
  15. package/dist/AssistantCloudEvents.js.map +1 -1
  16. package/dist/AssistantCloudRuns.d.ts +1 -0
  17. package/dist/AssistantCloudRuns.d.ts.map +1 -1
  18. package/dist/AssistantCloudRuns.js +2 -1
  19. package/dist/AssistantCloudRuns.js.map +1 -1
  20. package/dist/CloudEngagementReporter.d.ts +74 -0
  21. package/dist/CloudEngagementReporter.d.ts.map +1 -0
  22. package/dist/CloudEngagementReporter.js +132 -0
  23. package/dist/CloudEngagementReporter.js.map +1 -0
  24. package/dist/CloudMessagePersistence.d.ts +8 -9
  25. package/dist/CloudMessagePersistence.d.ts.map +1 -1
  26. package/dist/CloudMessagePersistence.js +18 -11
  27. package/dist/CloudMessagePersistence.js.map +1 -1
  28. package/dist/CloudRunReporter.d.ts +20 -0
  29. package/dist/CloudRunReporter.d.ts.map +1 -0
  30. package/dist/CloudRunReporter.js +38 -0
  31. package/dist/CloudRunReporter.js.map +1 -0
  32. package/dist/FormattedCloudPersistence.d.ts +3 -8
  33. package/dist/FormattedCloudPersistence.d.ts.map +1 -1
  34. package/dist/FormattedCloudPersistence.js +3 -8
  35. package/dist/FormattedCloudPersistence.js.map +1 -1
  36. package/dist/ai-sdk/index.d.ts +33 -0
  37. package/dist/ai-sdk/index.d.ts.map +1 -0
  38. package/dist/ai-sdk/index.js +147 -0
  39. package/dist/ai-sdk/index.js.map +1 -0
  40. package/dist/generateThreadTitle.js +1 -0
  41. package/dist/generateThreadTitle.js.map +1 -1
  42. package/dist/index.d.ts +5 -3
  43. package/dist/index.js +4 -2
  44. package/dist/instrumentMcpSampling.d.ts.map +1 -1
  45. package/dist/instrumentMcpSampling.js +12 -2
  46. package/dist/instrumentMcpSampling.js.map +1 -1
  47. package/dist/runTelemetry.d.ts +17 -1
  48. package/dist/runTelemetry.d.ts.map +1 -1
  49. package/dist/runTelemetry.js.map +1 -1
  50. package/dist/version.d.ts +5 -0
  51. package/dist/version.d.ts.map +1 -0
  52. package/dist/version.js +6 -0
  53. package/dist/version.js.map +1 -0
  54. package/package.json +19 -6
  55. package/src/AssistantCloud.ts +3 -0
  56. package/src/AssistantCloudAPI.ts +31 -1
  57. package/src/AssistantCloudAuthStrategy.ts +4 -2
  58. package/src/AssistantCloudEvents.test.ts +27 -2
  59. package/src/AssistantCloudEvents.ts +5 -0
  60. package/src/AssistantCloudRuns.ts +1 -0
  61. package/src/CloudEngagementReporter.ts +231 -0
  62. package/src/CloudMessagePersistence.ts +24 -13
  63. package/src/CloudRunReporter.ts +38 -0
  64. package/src/FormattedCloudPersistence.ts +3 -8
  65. package/src/ai-sdk/index.test.ts +258 -0
  66. package/src/ai-sdk/index.ts +222 -0
  67. package/src/generateThreadTitle.test.ts +32 -0
  68. package/src/generateThreadTitle.ts +1 -0
  69. package/src/index.ts +11 -1
  70. package/src/instrumentMcpSampling.test.ts +102 -0
  71. package/src/instrumentMcpSampling.ts +16 -2
  72. package/src/runTelemetry.ts +17 -0
  73. package/src/tests/AssistantCloud.test.ts +33 -1
  74. package/src/tests/AssistantCloudAPI.test.ts +51 -0
  75. package/src/tests/AssistantCloudAuthStrategy.test.ts +22 -0
  76. package/src/tests/AssistantCloudProjects.test.ts +1 -5
  77. package/src/tests/CloudEngagementReporter.test.ts +184 -0
  78. package/src/tests/CloudMessagePersistence.test.ts +235 -0
  79. package/src/tests/CloudRunReporter.test.ts +100 -0
  80. package/src/version.ts +4 -0
@@ -0,0 +1,132 @@
1
+ //#region src/CloudEngagementReporter.ts
2
+ const MAX_REMEMBERED_THREADS = 256;
3
+ function remember(map, threadId, value) {
4
+ map.delete(threadId);
5
+ map.set(threadId, value);
6
+ if (map.size > MAX_REMEMBERED_THREADS) map.delete(map.keys().next().value);
7
+ }
8
+ function mark(set, threadId) {
9
+ set.delete(threadId);
10
+ set.add(threadId);
11
+ if (set.size > MAX_REMEMBERED_THREADS) set.delete(set.values().next().value);
12
+ }
13
+ const passThroughIds = (threadId, messageId) => ({
14
+ thread_id: threadId,
15
+ ...messageId !== void 0 ? { message_id: messageId } : void 0
16
+ });
17
+ /**
18
+ * Derives engagement events from what a chat integration observes and keeps
19
+ * the per thread state the events need: a run's start for the stop duration,
20
+ * a run's end for the time to the next message, one error and one suggestion
21
+ * list per run or thread. A started run is kept until it ends or stops; the
22
+ * rest is kept for the 256 most recently touched threads, so a long session
23
+ * does not grow it without bound. Delivery goes through the cloud's event
24
+ * buffer, so a disabled telemetry setting drops everything here as well.
25
+ */
26
+ var CloudEngagementReporter = class {
27
+ runStartedAt = /* @__PURE__ */ new Map();
28
+ runEndedAt = /* @__PURE__ */ new Map();
29
+ shownErrors = /* @__PURE__ */ new Set();
30
+ shownSuggestions = /* @__PURE__ */ new Set();
31
+ getCloud;
32
+ resolveIds;
33
+ constructor(cloud, resolveIds = passThroughIds) {
34
+ this.getCloud = typeof cloud === "function" ? cloud : () => cloud;
35
+ this.resolveIds = resolveIds;
36
+ }
37
+ runStarted(threadId) {
38
+ this.runStartedAt.set(threadId, Date.now());
39
+ this.shownErrors.delete(threadId);
40
+ }
41
+ runEnded(threadId) {
42
+ this.runStartedAt.delete(threadId);
43
+ remember(this.runEndedAt, threadId, Date.now());
44
+ }
45
+ /** Reported once per started run, with the time the run had been going. */
46
+ runStopped(threadId) {
47
+ const startedAt = this.runStartedAt.get(threadId);
48
+ if (startedAt === void 0) return;
49
+ this.runStartedAt.delete(threadId);
50
+ this.track("run_stopped", threadId, void 0, { value: Math.max(0, Date.now() - startedAt) });
51
+ }
52
+ /** Carries the time since the previous run of the thread ended, when known. */
53
+ messageSent(threadId, init) {
54
+ const previousRunEndedAt = this.runEndedAt.get(threadId);
55
+ this.track("message_sent", threadId, init.messageId, {
56
+ props: {
57
+ chars: init.chars,
58
+ attachments: init.attachments
59
+ },
60
+ ...previousRunEndedAt !== void 0 ? { value: Math.max(0, Date.now() - previousRunEndedAt) } : void 0
61
+ }, { awaitThread: true });
62
+ }
63
+ messageEdited(threadId, init) {
64
+ this.track("message_edited", threadId, init.messageId, { props: { chars: init.chars } });
65
+ }
66
+ messageRegenerated(threadId, messageId) {
67
+ this.track("message_regenerated", threadId, messageId);
68
+ }
69
+ /** Reported once per run, so a retried render of the same error stays one event. */
70
+ errorShown(threadId, init) {
71
+ if (this.shownErrors.has(threadId)) return;
72
+ mark(this.shownErrors, threadId);
73
+ this.track("error_shown", threadId, init.messageId, { props: { reason: init.reason } });
74
+ }
75
+ /** Reported once per thread, with the number of suggestions on offer. */
76
+ suggestionsShown(threadId, count) {
77
+ if (this.shownSuggestions.has(threadId)) return;
78
+ mark(this.shownSuggestions, threadId);
79
+ this.track("suggestions_shown", threadId, void 0, { value: count });
80
+ }
81
+ suggestionClicked(threadId) {
82
+ this.track("suggestion_clicked", threadId);
83
+ }
84
+ attachmentAdded(threadId, init) {
85
+ this.track("attachment_added", threadId, init.messageId, { ...init.contentType ? { props: { type: init.contentType } } : void 0 });
86
+ }
87
+ attachmentFailed(threadId, init) {
88
+ this.track("attachment_failed", threadId, init.messageId, { ...init.contentType ? { props: { type: init.contentType } } : void 0 });
89
+ }
90
+ voiceStarted(threadId) {
91
+ this.track("voice_started", threadId);
92
+ }
93
+ speechStarted(threadId, messageId) {
94
+ this.track("speech_started", threadId, messageId);
95
+ }
96
+ branchSwitched(threadId, messageId) {
97
+ this.track("branch_switched", threadId, messageId);
98
+ }
99
+ messageCopied(threadId, messageId) {
100
+ this.track("message_copied", threadId, messageId);
101
+ }
102
+ toolApproved(threadId, messageId, toolCallId, toolName) {
103
+ this.track("tool_approved", threadId, messageId, { props: {
104
+ toolCallId,
105
+ toolName
106
+ } });
107
+ }
108
+ toolRejected(threadId, messageId, toolCallId, toolName) {
109
+ this.track("tool_rejected", threadId, messageId, { props: {
110
+ toolCallId,
111
+ toolName
112
+ } });
113
+ }
114
+ /** Reported only for a thread the cloud already knows. */
115
+ threadSwitched(threadId) {
116
+ this.track("thread_switched", threadId);
117
+ }
118
+ track(kind, threadId, messageId, init = {}, options = { awaitThread: false }) {
119
+ Promise.resolve().then(() => this.resolveIds(threadId, messageId, options)).then((ids) => {
120
+ if (kind === "thread_switched" && !ids.thread_id) return;
121
+ this.getCloud().events.track({
122
+ kind,
123
+ ...init,
124
+ ...ids
125
+ });
126
+ }).catch(() => {});
127
+ }
128
+ };
129
+ //#endregion
130
+ export { CloudEngagementReporter };
131
+
132
+ //# sourceMappingURL=CloudEngagementReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CloudEngagementReporter.js","names":[],"sources":["../src/CloudEngagementReporter.ts"],"sourcesContent":["import type { AssistantCloud } from \"./AssistantCloud\";\nimport type {\n AssistantCloudEvent,\n AssistantCloudEventKind,\n} from \"./AssistantCloudEvents\";\n\nexport type EngagementEventIds = Pick<\n AssistantCloudEvent,\n \"thread_id\" | \"message_id\" | \"run_id\"\n>;\n\n/**\n * Turns the ids an integration knows (its own thread and message ids) into the\n * ids the cloud stores. A send is the one event that may create the remote\n * thread, so it asks for `awaitThread`; every other event reads the ids that\n * already exist.\n */\nexport type EngagementIdResolver = (\n threadId: string,\n messageId: string | undefined,\n options: { awaitThread: boolean },\n) => EngagementEventIds | Promise<EngagementEventIds>;\n\ntype EngagementEventInit = Pick<AssistantCloudEvent, \"value\" | \"props\">;\n\nconst MAX_REMEMBERED_THREADS = 256;\n\nfunction remember<T>(map: Map<string, T>, threadId: string, value: T): void {\n map.delete(threadId);\n map.set(threadId, value);\n if (map.size > MAX_REMEMBERED_THREADS) {\n map.delete(map.keys().next().value!);\n }\n}\n\nfunction mark(set: Set<string>, threadId: string): void {\n set.delete(threadId);\n set.add(threadId);\n if (set.size > MAX_REMEMBERED_THREADS) {\n set.delete(set.values().next().value!);\n }\n}\n\nconst passThroughIds: EngagementIdResolver = (threadId, messageId) => ({\n thread_id: threadId,\n ...(messageId !== undefined ? { message_id: messageId } : undefined),\n});\n\n/**\n * Derives engagement events from what a chat integration observes and keeps\n * the per thread state the events need: a run's start for the stop duration,\n * a run's end for the time to the next message, one error and one suggestion\n * list per run or thread. A started run is kept until it ends or stops; the\n * rest is kept for the 256 most recently touched threads, so a long session\n * does not grow it without bound. Delivery goes through the cloud's event\n * buffer, so a disabled telemetry setting drops everything here as well.\n */\nexport class CloudEngagementReporter {\n private readonly runStartedAt = new Map<string, number>();\n private readonly runEndedAt = new Map<string, number>();\n private readonly shownErrors = new Set<string>();\n private readonly shownSuggestions = new Set<string>();\n\n private readonly getCloud: () => AssistantCloud;\n private readonly resolveIds: EngagementIdResolver;\n\n constructor(\n cloud: AssistantCloud | (() => AssistantCloud),\n resolveIds: EngagementIdResolver = passThroughIds,\n ) {\n this.getCloud = typeof cloud === \"function\" ? cloud : () => cloud;\n this.resolveIds = resolveIds;\n }\n\n public runStarted(threadId: string): void {\n this.runStartedAt.set(threadId, Date.now());\n this.shownErrors.delete(threadId);\n }\n\n public runEnded(threadId: string): void {\n this.runStartedAt.delete(threadId);\n remember(this.runEndedAt, threadId, Date.now());\n }\n\n /** Reported once per started run, with the time the run had been going. */\n public runStopped(threadId: string): void {\n const startedAt = this.runStartedAt.get(threadId);\n if (startedAt === undefined) return;\n this.runStartedAt.delete(threadId);\n this.track(\"run_stopped\", threadId, undefined, {\n value: Math.max(0, Date.now() - startedAt),\n });\n }\n\n /** Carries the time since the previous run of the thread ended, when known. */\n public messageSent(\n threadId: string,\n init: {\n messageId?: string | undefined;\n chars: number;\n attachments: number;\n },\n ): void {\n const previousRunEndedAt = this.runEndedAt.get(threadId);\n this.track(\n \"message_sent\",\n threadId,\n init.messageId,\n {\n props: { chars: init.chars, attachments: init.attachments },\n ...(previousRunEndedAt !== undefined\n ? { value: Math.max(0, Date.now() - previousRunEndedAt) }\n : undefined),\n },\n { awaitThread: true },\n );\n }\n\n public messageEdited(\n threadId: string,\n init: { messageId: string; chars: number },\n ): void {\n this.track(\"message_edited\", threadId, init.messageId, {\n props: { chars: init.chars },\n });\n }\n\n public messageRegenerated(threadId: string, messageId?: string): void {\n this.track(\"message_regenerated\", threadId, messageId);\n }\n\n /** Reported once per run, so a retried render of the same error stays one event. */\n public errorShown(\n threadId: string,\n init: { messageId?: string | undefined; reason: string },\n ): void {\n if (this.shownErrors.has(threadId)) return;\n mark(this.shownErrors, threadId);\n this.track(\"error_shown\", threadId, init.messageId, {\n props: { reason: init.reason },\n });\n }\n\n /** Reported once per thread, with the number of suggestions on offer. */\n public suggestionsShown(threadId: string, count: number): void {\n if (this.shownSuggestions.has(threadId)) return;\n mark(this.shownSuggestions, threadId);\n this.track(\"suggestions_shown\", threadId, undefined, { value: count });\n }\n\n public suggestionClicked(threadId: string): void {\n this.track(\"suggestion_clicked\", threadId);\n }\n\n public attachmentAdded(\n threadId: string,\n init: { messageId?: string | undefined; contentType?: string | undefined },\n ): void {\n this.track(\"attachment_added\", threadId, init.messageId, {\n ...(init.contentType ? { props: { type: init.contentType } } : undefined),\n });\n }\n\n public attachmentFailed(\n threadId: string,\n init: { messageId?: string | undefined; contentType?: string | undefined },\n ): void {\n this.track(\"attachment_failed\", threadId, init.messageId, {\n ...(init.contentType ? { props: { type: init.contentType } } : undefined),\n });\n }\n\n public voiceStarted(threadId: string): void {\n this.track(\"voice_started\", threadId);\n }\n\n public speechStarted(threadId: string, messageId?: string): void {\n this.track(\"speech_started\", threadId, messageId);\n }\n\n public branchSwitched(threadId: string, messageId?: string): void {\n this.track(\"branch_switched\", threadId, messageId);\n }\n\n public messageCopied(threadId: string, messageId?: string): void {\n this.track(\"message_copied\", threadId, messageId);\n }\n\n public toolApproved(\n threadId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n ): void {\n this.track(\"tool_approved\", threadId, messageId, {\n props: { toolCallId, toolName },\n });\n }\n\n public toolRejected(\n threadId: string,\n messageId: string,\n toolCallId: string,\n toolName: string,\n ): void {\n this.track(\"tool_rejected\", threadId, messageId, {\n props: { toolCallId, toolName },\n });\n }\n\n /** Reported only for a thread the cloud already knows. */\n public threadSwitched(threadId: string): void {\n this.track(\"thread_switched\", threadId);\n }\n\n private track(\n kind: AssistantCloudEventKind,\n threadId: string,\n messageId?: string,\n init: EngagementEventInit = {},\n options: { awaitThread: boolean } = { awaitThread: false },\n ): void {\n void Promise.resolve()\n .then(() => this.resolveIds(threadId, messageId, options))\n .then((ids) => {\n if (kind === \"thread_switched\" && !ids.thread_id) return;\n this.getCloud().events.track({ kind, ...init, ...ids });\n })\n .catch(() => {});\n }\n}\n"],"mappings":"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"}
@@ -3,15 +3,9 @@ import { AssistantCloud } from "./AssistantCloud.js";
3
3
  import { ReadonlyJSONObject } from "assistant-stream/utils";
4
4
  //#region src/CloudMessagePersistence.d.ts
5
5
  /**
6
- * Shared persistence logic for cloud message storage.
7
- *
8
- * Handles ID mapping (local → remote) and parent_id chaining for both:
9
- * - AssistantCloudThreadHistoryAdapter (assistant-ui runtime)
10
- * - useCloudChat (standalone AI SDK hook)
11
- *
12
- * The promise-based ID resolution handles concurrent appends — if message B's
13
- * parent is message A, and A is still being created, we await A's promise
14
- * to get its remote ID before creating B.
6
+ * Appends, updates and loads cloud messages while mapping local ids to cloud
7
+ * ids and chaining parent_id. A parent that is still being created is awaited,
8
+ * so concurrent appends land under the right parent.
15
9
  */
16
10
  declare class CloudMessagePersistence {
17
11
  private idMapping;
@@ -51,6 +45,8 @@ declare class CloudMessagePersistence {
51
45
  * The ID mapping is populated so that `isPersisted()` returns true for
52
46
  * loaded messages, preventing re-persistence of already-stored messages.
53
47
  *
48
+ * A loaded ID that an append already maps keeps the remote ID from that append, and falls back to the loaded ID if the append fails.
49
+ *
54
50
  * @param threadId - Remote thread ID
55
51
  * @param format - Optional format filter
56
52
  * @returns Array of cloud messages
@@ -58,6 +54,9 @@ declare class CloudMessagePersistence {
58
54
  load(threadId: string, format?: string): Promise<CloudMessage[]>;
59
55
  /**
60
56
  * Reset the ID mapping (call when switching threads).
57
+ *
58
+ * Pending `load()` and `append()` calls are not cancelled and still settle
59
+ * normally, but their results no longer populate the ID mapping.
61
60
  */
62
61
  reset(): void;
63
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CloudMessagePersistence.d.ts","names":[],"sources":["../src/CloudMessagePersistence.ts"],"mappings":";;;;;;;;;;;;;;;cAiBa;UACH;UACA;EAEI,YAAA,OAAO;EACP,YAAA,gBAAgB;;;;;;;;;;EActB,OACJ,kBACA,mBACA,yBACA,gBACA,SAAS,qBACR;;;;EAsCG,OACJ,kBACA,mBACA,iBACA,SAAS,qBACR;;;;EAeH,YAAY;;;;;EAQN,YAAY,oBAAoB;EAMtC,oBAAoB;;;;;;;;;;;;;;EAkBd,KAAK,kBAAkB,kBAAe,QAAA;;;;EAoC5C"}
1
+ {"version":3,"file":"CloudMessagePersistence.d.ts","names":[],"sources":["../src/CloudMessagePersistence.ts"],"mappings":";;;;;;;;;cAWa;UACH;UACA;EAEI,YAAA,OAAO;EACP,YAAA,gBAAgB;;;;;;;;;;EActB,OACJ,kBACA,mBACA,yBACA,gBACA,SAAS,qBACR;;;;EAsCG,OACJ,kBACA,mBACA,iBACA,SAAS,qBACR;;;;EAeH,YAAY;;;;;EAQN,YAAY,oBAAoB;EAMtC,oBAAoB;;;;;;;;;;;;;;;;EAoBd,KAAK,kBAAkB,kBAAe,QAAA;;;;;;;EAmD5C"}
@@ -1,15 +1,9 @@
1
1
  //#region src/CloudMessagePersistence.ts
2
2
  const CLOUD_MESSAGE_PAGE_SIZE = 200;
3
3
  /**
4
- * Shared persistence logic for cloud message storage.
5
- *
6
- * Handles ID mapping (local → remote) and parent_id chaining for both:
7
- * - AssistantCloudThreadHistoryAdapter (assistant-ui runtime)
8
- * - useCloudChat (standalone AI SDK hook)
9
- *
10
- * The promise-based ID resolution handles concurrent appends — if message B's
11
- * parent is message A, and A is still being created, we await A's promise
12
- * to get its remote ID before creating B.
4
+ * Appends, updates and loads cloud messages while mapping local ids to cloud
5
+ * ids and chaining parent_id. A parent that is still being created is awaited,
6
+ * so concurrent appends land under the right parent.
13
7
  */
14
8
  var CloudMessagePersistence = class {
15
9
  idMapping = /* @__PURE__ */ new Map();
@@ -92,11 +86,14 @@ var CloudMessagePersistence = class {
92
86
  * The ID mapping is populated so that `isPersisted()` returns true for
93
87
  * loaded messages, preventing re-persistence of already-stored messages.
94
88
  *
89
+ * A loaded ID that an append already maps keeps the remote ID from that append, and falls back to the loaded ID if the append fails.
90
+ *
95
91
  * @param threadId - Remote thread ID
96
92
  * @param format - Optional format filter
97
93
  * @returns Array of cloud messages
98
94
  */
99
95
  async load(threadId, format) {
96
+ const idMapping = this.idMapping;
100
97
  const cloud = this.getCloud();
101
98
  const messages = [];
102
99
  const seen = /* @__PURE__ */ new Set();
@@ -116,14 +113,24 @@ var CloudMessagePersistence = class {
116
113
  if (page.messages.length < CLOUD_MESSAGE_PAGE_SIZE) break;
117
114
  after = last.id;
118
115
  }
119
- for (const m of messages) this.idMapping.set(m.id, m.id);
116
+ if (this.idMapping === idMapping) for (const m of messages) {
117
+ const entry = idMapping.get(m.id);
118
+ if (entry === void 0) idMapping.set(m.id, m.id);
119
+ else if (entry instanceof Promise) entry.catch(() => {
120
+ const current = idMapping.get(m.id);
121
+ if (current === void 0 || current === entry) idMapping.set(m.id, m.id);
122
+ });
123
+ }
120
124
  return messages;
121
125
  }
122
126
  /**
123
127
  * Reset the ID mapping (call when switching threads).
128
+ *
129
+ * Pending `load()` and `append()` calls are not cancelled and still settle
130
+ * normally, but their results no longer populate the ID mapping.
124
131
  */
125
132
  reset() {
126
- this.idMapping.clear();
133
+ this.idMapping = /* @__PURE__ */ new Map();
127
134
  }
128
135
  };
129
136
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"CloudMessagePersistence.js","names":[],"sources":["../src/CloudMessagePersistence.ts"],"sourcesContent":["import type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport type { AssistantCloud } from \"./AssistantCloud\";\nimport type { CloudMessage } from \"./AssistantCloudThreadMessages\";\n\nconst CLOUD_MESSAGE_PAGE_SIZE = 200;\n\n/**\n * Shared persistence logic for cloud message storage.\n *\n * Handles ID mapping (local → remote) and parent_id chaining for both:\n * - AssistantCloudThreadHistoryAdapter (assistant-ui runtime)\n * - useCloudChat (standalone AI SDK hook)\n *\n * The promise-based ID resolution handles concurrent appends — if message B's\n * parent is message A, and A is still being created, we await A's promise\n * to get its remote ID before creating B.\n */\nexport class CloudMessagePersistence {\n private idMapping = new Map<string, string | Promise<string>>();\n private getCloud: () => AssistantCloud;\n\n constructor(cloud: AssistantCloud);\n constructor(getCloud: () => AssistantCloud);\n constructor(cloud: AssistantCloud | (() => AssistantCloud)) {\n this.getCloud = typeof cloud === \"function\" ? cloud : () => cloud;\n }\n\n /**\n * Persist a message to the cloud.\n *\n * @param threadId - Remote thread ID\n * @param messageId - Local message ID (used for tracking)\n * @param parentId - Local parent message ID (or null for first message)\n * @param format - Message format (e.g., \"aui/v0\", \"ai-sdk/v6\")\n * @param content - Message content (format-specific)\n */\n async append(\n threadId: string,\n messageId: string,\n parentId: string | null,\n format: string,\n content: ReadonlyJSONObject,\n ): Promise<void> {\n const cloud = this.getCloud();\n const existing = this.idMapping.get(messageId);\n if (existing instanceof Promise) {\n await existing;\n return;\n }\n\n const task = (async () => {\n const parentEntry = parentId ? this.idMapping.get(parentId) : undefined;\n const resolvedParentId = parentId\n ? ((await parentEntry) ?? parentId)\n : null;\n const { message_id } = await cloud.threads.messages.create(threadId, {\n parent_id: resolvedParentId,\n format,\n content,\n });\n return message_id;\n })();\n\n this.idMapping.set(messageId, task);\n try {\n const remoteId = await task;\n if (this.idMapping.get(messageId) === task) {\n this.idMapping.set(messageId, remoteId);\n }\n } catch (err) {\n if (this.idMapping.get(messageId) === task) {\n this.idMapping.delete(messageId);\n }\n throw err;\n }\n }\n\n /**\n * Update an already-persisted message in the cloud.\n */\n async update(\n threadId: string,\n messageId: string,\n _format: string,\n content: ReadonlyJSONObject,\n ): Promise<void> {\n const cloud = this.getCloud();\n const remoteId = await this.getRemoteId(messageId);\n if (!remoteId) {\n console.warn(\n `Skipping update for message ${messageId}: no remote id is mapped.`,\n );\n return;\n }\n await cloud.threads.messages.update(threadId, remoteId, { content });\n }\n\n /**\n * Check if a message has been persisted (or is currently being persisted).\n */\n isPersisted(messageId: string): boolean {\n return this.idMapping.has(messageId);\n }\n\n /**\n * Get the remote ID for a local message ID (resolved).\n * Returns undefined if not persisted.\n */\n async getRemoteId(messageId: string): Promise<string | undefined> {\n const entry = this.idMapping.get(messageId);\n if (!entry) return undefined;\n return entry;\n }\n\n getResolvedRemoteId(messageId: string): string | undefined {\n const entry = this.idMapping.get(messageId);\n return typeof entry === \"string\" ? entry : undefined;\n }\n\n /**\n * Load messages from the cloud and populate the ID mapping.\n *\n * The list endpoint caps a response at 200 rows, so pages are followed by\n * message ID cursor until a short page and concatenated in server order.\n *\n * The ID mapping is populated so that `isPersisted()` returns true for\n * loaded messages, preventing re-persistence of already-stored messages.\n *\n * @param threadId - Remote thread ID\n * @param format - Optional format filter\n * @returns Array of cloud messages\n */\n async load(threadId: string, format?: string) {\n const cloud = this.getCloud();\n const messages: CloudMessage[] = [];\n const seen = new Set<string>();\n let after: string | undefined;\n\n while (true) {\n const page = await cloud.threads.messages.list(threadId, {\n ...(format ? { format } : undefined),\n limit: CLOUD_MESSAGE_PAGE_SIZE,\n ...(after ? { after } : undefined),\n });\n const last = page.messages.at(-1);\n if (!last) break;\n\n // A cursor the server cannot resolve drops the keyset filter and replays\n // an earlier page, so already-seen rows end the walk instead of repeating.\n const fresh = page.messages.filter((m) => !seen.has(m.id));\n if (fresh.length === 0) break;\n for (const m of fresh) seen.add(m.id);\n\n messages.push(...fresh);\n if (page.messages.length < CLOUD_MESSAGE_PAGE_SIZE) break;\n after = last.id;\n }\n\n // Populate ID mapping so isPersisted() recognizes loaded messages\n for (const m of messages) {\n this.idMapping.set(m.id, m.id);\n }\n return messages;\n }\n\n /**\n * Reset the ID mapping (call when switching threads).\n */\n reset() {\n this.idMapping.clear();\n }\n}\n"],"mappings":";AAIA,MAAM,0BAA0B;;;;;;;;;;;;AAahC,IAAa,0BAAb,MAAqC;CACnC,4BAAoB,IAAI,IAAsC;CAC9D;CAIA,YAAY,OAAgD;EAC1D,KAAK,WAAW,OAAO,UAAU,aAAa,cAAc;CAC9D;;;;;;;;;;CAWA,MAAM,OACJ,UACA,WACA,UACA,QACA,SACe;EACf,MAAM,QAAQ,KAAK,SAAS;EAC5B,MAAM,WAAW,KAAK,UAAU,IAAI,SAAS;EAC7C,IAAI,oBAAoB,SAAS;GAC/B,MAAM;GACN;EACF;EAEA,MAAM,QAAQ,YAAY;GACxB,MAAM,cAAc,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI,KAAA;GAC9D,MAAM,mBAAmB,WACnB,MAAM,eAAgB,WACxB;GACJ,MAAM,EAAE,eAAe,MAAM,MAAM,QAAQ,SAAS,OAAO,UAAU;IACnE,WAAW;IACX;IACA;GACF,CAAC;GACD,OAAO;EACT,EAAA,CAAG;EAEH,KAAK,UAAU,IAAI,WAAW,IAAI;EAClC,IAAI;GACF,MAAM,WAAW,MAAM;GACvB,IAAI,KAAK,UAAU,IAAI,SAAS,MAAM,MACpC,KAAK,UAAU,IAAI,WAAW,QAAQ;EAE1C,SAAS,KAAK;GACZ,IAAI,KAAK,UAAU,IAAI,SAAS,MAAM,MACpC,KAAK,UAAU,OAAO,SAAS;GAEjC,MAAM;EACR;CACF;;;;CAKA,MAAM,OACJ,UACA,WACA,SACA,SACe;EACf,MAAM,QAAQ,KAAK,SAAS;EAC5B,MAAM,WAAW,MAAM,KAAK,YAAY,SAAS;EACjD,IAAI,CAAC,UAAU;GACb,QAAQ,KACN,+BAA+B,UAAU,0BAC3C;GACA;EACF;EACA,MAAM,MAAM,QAAQ,SAAS,OAAO,UAAU,UAAU,EAAE,QAAQ,CAAC;CACrE;;;;CAKA,YAAY,WAA4B;EACtC,OAAO,KAAK,UAAU,IAAI,SAAS;CACrC;;;;;CAMA,MAAM,YAAY,WAAgD;EAChE,MAAM,QAAQ,KAAK,UAAU,IAAI,SAAS;EAC1C,IAAI,CAAC,OAAO,OAAO,KAAA;EACnB,OAAO;CACT;CAEA,oBAAoB,WAAuC;EACzD,MAAM,QAAQ,KAAK,UAAU,IAAI,SAAS;EAC1C,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;CAC7C;;;;;;;;;;;;;;CAeA,MAAM,KAAK,UAAkB,QAAiB;EAC5C,MAAM,QAAQ,KAAK,SAAS;EAC5B,MAAM,WAA2B,CAAC;EAClC,MAAM,uBAAO,IAAI,IAAY;EAC7B,IAAI;EAEJ,OAAO,MAAM;GACX,MAAM,OAAO,MAAM,MAAM,QAAQ,SAAS,KAAK,UAAU;IACvD,GAAI,SAAS,EAAE,OAAO,IAAI,KAAA;IAC1B,OAAO;IACP,GAAI,QAAQ,EAAE,MAAM,IAAI,KAAA;GAC1B,CAAC;GACD,MAAM,OAAO,KAAK,SAAS,GAAG,EAAE;GAChC,IAAI,CAAC,MAAM;GAIX,MAAM,QAAQ,KAAK,SAAS,QAAQ,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;GACzD,IAAI,MAAM,WAAW,GAAG;GACxB,KAAK,MAAM,KAAK,OAAO,KAAK,IAAI,EAAE,EAAE;GAEpC,SAAS,KAAK,GAAG,KAAK;GACtB,IAAI,KAAK,SAAS,SAAS,yBAAyB;GACpD,QAAQ,KAAK;EACf;EAGA,KAAK,MAAM,KAAK,UACd,KAAK,UAAU,IAAI,EAAE,IAAI,EAAE,EAAE;EAE/B,OAAO;CACT;;;;CAKA,QAAQ;EACN,KAAK,UAAU,MAAM;CACvB;AACF"}
1
+ {"version":3,"file":"CloudMessagePersistence.js","names":[],"sources":["../src/CloudMessagePersistence.ts"],"sourcesContent":["import type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport type { AssistantCloud } from \"./AssistantCloud\";\nimport type { CloudMessage } from \"./AssistantCloudThreadMessages\";\n\nconst CLOUD_MESSAGE_PAGE_SIZE = 200;\n\n/**\n * Appends, updates and loads cloud messages while mapping local ids to cloud\n * ids and chaining parent_id. A parent that is still being created is awaited,\n * so concurrent appends land under the right parent.\n */\nexport class CloudMessagePersistence {\n private idMapping = new Map<string, string | Promise<string>>();\n private getCloud: () => AssistantCloud;\n\n constructor(cloud: AssistantCloud);\n constructor(getCloud: () => AssistantCloud);\n constructor(cloud: AssistantCloud | (() => AssistantCloud)) {\n this.getCloud = typeof cloud === \"function\" ? cloud : () => cloud;\n }\n\n /**\n * Persist a message to the cloud.\n *\n * @param threadId - Remote thread ID\n * @param messageId - Local message ID (used for tracking)\n * @param parentId - Local parent message ID (or null for first message)\n * @param format - Message format (e.g., \"aui/v0\", \"ai-sdk/v6\")\n * @param content - Message content (format-specific)\n */\n async append(\n threadId: string,\n messageId: string,\n parentId: string | null,\n format: string,\n content: ReadonlyJSONObject,\n ): Promise<void> {\n const cloud = this.getCloud();\n const existing = this.idMapping.get(messageId);\n if (existing instanceof Promise) {\n await existing;\n return;\n }\n\n const task = (async () => {\n const parentEntry = parentId ? this.idMapping.get(parentId) : undefined;\n const resolvedParentId = parentId\n ? ((await parentEntry) ?? parentId)\n : null;\n const { message_id } = await cloud.threads.messages.create(threadId, {\n parent_id: resolvedParentId,\n format,\n content,\n });\n return message_id;\n })();\n\n this.idMapping.set(messageId, task);\n try {\n const remoteId = await task;\n if (this.idMapping.get(messageId) === task) {\n this.idMapping.set(messageId, remoteId);\n }\n } catch (err) {\n if (this.idMapping.get(messageId) === task) {\n this.idMapping.delete(messageId);\n }\n throw err;\n }\n }\n\n /**\n * Update an already-persisted message in the cloud.\n */\n async update(\n threadId: string,\n messageId: string,\n _format: string,\n content: ReadonlyJSONObject,\n ): Promise<void> {\n const cloud = this.getCloud();\n const remoteId = await this.getRemoteId(messageId);\n if (!remoteId) {\n console.warn(\n `Skipping update for message ${messageId}: no remote id is mapped.`,\n );\n return;\n }\n await cloud.threads.messages.update(threadId, remoteId, { content });\n }\n\n /**\n * Check if a message has been persisted (or is currently being persisted).\n */\n isPersisted(messageId: string): boolean {\n return this.idMapping.has(messageId);\n }\n\n /**\n * Get the remote ID for a local message ID (resolved).\n * Returns undefined if not persisted.\n */\n async getRemoteId(messageId: string): Promise<string | undefined> {\n const entry = this.idMapping.get(messageId);\n if (!entry) return undefined;\n return entry;\n }\n\n getResolvedRemoteId(messageId: string): string | undefined {\n const entry = this.idMapping.get(messageId);\n return typeof entry === \"string\" ? entry : undefined;\n }\n\n /**\n * Load messages from the cloud and populate the ID mapping.\n *\n * The list endpoint caps a response at 200 rows, so pages are followed by\n * message ID cursor until a short page and concatenated in server order.\n *\n * The ID mapping is populated so that `isPersisted()` returns true for\n * loaded messages, preventing re-persistence of already-stored messages.\n *\n * A loaded ID that an append already maps keeps the remote ID from that append, and falls back to the loaded ID if the append fails.\n *\n * @param threadId - Remote thread ID\n * @param format - Optional format filter\n * @returns Array of cloud messages\n */\n async load(threadId: string, format?: string) {\n const idMapping = this.idMapping;\n const cloud = this.getCloud();\n const messages: CloudMessage[] = [];\n const seen = new Set<string>();\n let after: string | undefined;\n\n while (true) {\n const page = await cloud.threads.messages.list(threadId, {\n ...(format ? { format } : undefined),\n limit: CLOUD_MESSAGE_PAGE_SIZE,\n ...(after ? { after } : undefined),\n });\n const last = page.messages.at(-1);\n if (!last) break;\n\n // A cursor the server cannot resolve drops the keyset filter and replays\n // an earlier page, so already-seen rows end the walk instead of repeating.\n const fresh = page.messages.filter((m) => !seen.has(m.id));\n if (fresh.length === 0) break;\n for (const m of fresh) seen.add(m.id);\n\n messages.push(...fresh);\n if (page.messages.length < CLOUD_MESSAGE_PAGE_SIZE) break;\n after = last.id;\n }\n\n if (this.idMapping === idMapping) {\n for (const m of messages) {\n const entry = idMapping.get(m.id);\n if (entry === undefined) {\n idMapping.set(m.id, m.id);\n } else if (entry instanceof Promise) {\n void entry.catch(() => {\n const current = idMapping.get(m.id);\n if (current === undefined || current === entry) {\n idMapping.set(m.id, m.id);\n }\n });\n }\n }\n }\n return messages;\n }\n\n /**\n * Reset the ID mapping (call when switching threads).\n *\n * Pending `load()` and `append()` calls are not cancelled and still settle\n * normally, but their results no longer populate the ID mapping.\n */\n reset() {\n this.idMapping = new Map();\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { RunReportInit } from "./runTelemetry.js";
2
+ import { AssistantCloud } from "./AssistantCloud.js";
3
+ //#region src/CloudRunReporter.d.ts
4
+ type CloudRunReportInit = Omit<RunReportInit, "telemetry">;
5
+ /**
6
+ * Sends run reports the way every client integration has to: nothing while
7
+ * telemetry is off, the cloud's environment, release and tags on every report,
8
+ * the `beforeReport` hook applied last, and a failed send that never surfaces.
9
+ * A report given a key is sent once per key, so an integration that observes
10
+ * the same finished run twice reports it once.
11
+ */
12
+ declare class CloudRunReporter {
13
+ private readonly reported;
14
+ private readonly getCloud;
15
+ constructor(cloud: AssistantCloud | (() => AssistantCloud));
16
+ report(init: CloudRunReportInit, key?: string): Promise<void>;
17
+ }
18
+ //#endregion
19
+ export { CloudRunReportInit, CloudRunReporter };
20
+ //# sourceMappingURL=CloudRunReporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CloudRunReporter.d.ts","names":[],"sources":["../src/CloudRunReporter.ts"],"mappings":";;;KAGY,qBAAqB,KAAK;;;;;;;;cASzB;mBACM;mBACA;EAEL,YAAA,OAAO,wBAAwB;EAI9B,OAAO,MAAM,oBAAoB,eAAe"}
@@ -0,0 +1,38 @@
1
+ import { createRunReport } from "./runTelemetry.js";
2
+ //#region src/CloudRunReporter.ts
3
+ /**
4
+ * Sends run reports the way every client integration has to: nothing while
5
+ * telemetry is off, the cloud's environment, release and tags on every report,
6
+ * the `beforeReport` hook applied last, and a failed send that never surfaces.
7
+ * A report given a key is sent once per key, so an integration that observes
8
+ * the same finished run twice reports it once.
9
+ */
10
+ var CloudRunReporter = class {
11
+ reported = /* @__PURE__ */ new Set();
12
+ getCloud;
13
+ constructor(cloud) {
14
+ this.getCloud = typeof cloud === "function" ? cloud : () => cloud;
15
+ }
16
+ async report(init, key) {
17
+ try {
18
+ const cloud = this.getCloud();
19
+ if (!cloud.telemetry.enabled) return;
20
+ if (key !== void 0 && this.reported.has(key)) return;
21
+ const initial = createRunReport({
22
+ ...init,
23
+ telemetry: cloud.telemetry
24
+ });
25
+ const { beforeReport } = cloud.telemetry;
26
+ const report = beforeReport ? beforeReport(initial) : initial;
27
+ if (!report) return;
28
+ if (key !== void 0) this.reported.add(key);
29
+ await cloud.runs.report(report);
30
+ } catch {
31
+ return;
32
+ }
33
+ }
34
+ };
35
+ //#endregion
36
+ export { CloudRunReporter };
37
+
38
+ //# sourceMappingURL=CloudRunReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CloudRunReporter.js","names":[],"sources":["../src/CloudRunReporter.ts"],"sourcesContent":["import type { AssistantCloud } from \"./AssistantCloud\";\nimport { createRunReport, type RunReportInit } from \"./runTelemetry\";\n\nexport type CloudRunReportInit = Omit<RunReportInit, \"telemetry\">;\n\n/**\n * Sends run reports the way every client integration has to: nothing while\n * telemetry is off, the cloud's environment, release and tags on every report,\n * the `beforeReport` hook applied last, and a failed send that never surfaces.\n * A report given a key is sent once per key, so an integration that observes\n * the same finished run twice reports it once.\n */\nexport class CloudRunReporter {\n private readonly reported = new Set<string>();\n private readonly getCloud: () => AssistantCloud;\n\n constructor(cloud: AssistantCloud | (() => AssistantCloud)) {\n this.getCloud = typeof cloud === \"function\" ? cloud : () => cloud;\n }\n\n public async report(init: CloudRunReportInit, key?: string): Promise<void> {\n try {\n const cloud = this.getCloud();\n if (!cloud.telemetry.enabled) return;\n if (key !== undefined && this.reported.has(key)) return;\n\n const initial = createRunReport({ ...init, telemetry: cloud.telemetry });\n const { beforeReport } = cloud.telemetry;\n const report = beforeReport ? beforeReport(initial) : initial;\n if (!report) return;\n\n if (key !== undefined) this.reported.add(key);\n await cloud.runs.report(report);\n } catch {\n return;\n }\n }\n}\n"],"mappings":";;;;;;;;;AAYA,IAAa,mBAAb,MAA8B;CAC5B,2BAA4B,IAAI,IAAY;CAC5C;CAEA,YAAY,OAAgD;EAC1D,KAAK,WAAW,OAAO,UAAU,aAAa,cAAc;CAC9D;CAEA,MAAa,OAAO,MAA0B,KAA6B;EACzE,IAAI;GACF,MAAM,QAAQ,KAAK,SAAS;GAC5B,IAAI,CAAC,MAAM,UAAU,SAAS;GAC9B,IAAI,QAAQ,KAAA,KAAa,KAAK,SAAS,IAAI,GAAG,GAAG;GAEjD,MAAM,UAAU,gBAAgB;IAAE,GAAG;IAAM,WAAW,MAAM;GAAU,CAAC;GACvE,MAAM,EAAE,iBAAiB,MAAM;GAC/B,MAAM,SAAS,eAAe,aAAa,OAAO,IAAI;GACtD,IAAI,CAAC,QAAQ;GAEb,IAAI,QAAQ,KAAA,GAAW,KAAK,SAAS,IAAI,GAAG;GAC5C,MAAM,MAAM,KAAK,OAAO,MAAM;EAChC,QAAQ;GACN;EACF;CACF;AACF"}
@@ -23,14 +23,9 @@ type MessageFormatAdapter<TMessage, TStorageFormat> = {
23
23
  getId(message: TMessage): string;
24
24
  };
25
25
  /**
26
- * Wraps a CloudMessagePersistence instance with format-aware encode/decode.
27
- *
28
- * This centralizes the pattern used by both:
29
- * - useCloudChat (standalone AI SDK hook)
30
- * - AssistantCloudThreadHistoryAdapter.withFormat() (assistant-ui runtime)
31
- *
32
- * The persistence parameter is typed structurally (not by class) so callers
33
- * don't need to import CloudMessagePersistence directly.
26
+ * Wraps a CloudMessagePersistence with a MessageFormatAdapter's encode and
27
+ * decode. The persistence parameter is typed structurally, so a caller does
28
+ * not need to import the class.
34
29
  */
35
30
  declare const createFormattedPersistence: <TMessage, TStorageFormat>(persistence: {
36
31
  append: (threadId: string, messageId: string, parentId: string | null, format: string, content: ReadonlyJSONObject) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"FormattedCloudPersistence.d.ts","names":[],"sources":["../src/FormattedCloudPersistence.ts"],"mappings":";;;;;;;KAOY,qBAAqB,UAAU;EACzC;EACA,OAAO;IAAQ;IAAyB,SAAS;MAAa;EAC9D,OAAO;IACL;IACA;IACA;IACA,SAAS;;IACL;IAAyB,SAAS;;EACxC,MAAM,SAAS;;;;;;;;;;;;cAaJ,6BAA8B,UAAU,gBACnD;EACE,SACE,kBACA,mBACA,yBACA,gBACA,SAAS,uBACN;EACL,OAAO,kBAAkB,oBAAoB;EAC7C,cAAc;EACd,UACE,kBACA,mBACA,gBACA,SAAS,uBACN;GAEP,SAAS,qBAAqB,UAAU;EAG5B,SAAA,kBAAM;IACR;IAAyB,SAAS;QACzC;EAaa,UAAA,kBAAM;IACR;IAAyB,SAAS;KAAU,sBAEnD;EAUgB,OAAA,qBAAM;;MA/Db;;;;EA+ES,cAAA"}
1
+ {"version":3,"file":"FormattedCloudPersistence.d.ts","names":[],"sources":["../src/FormattedCloudPersistence.ts"],"mappings":";;;;;;;KAOY,qBAAqB,UAAU;EACzC;EACA,OAAO;IAAQ;IAAyB,SAAS;MAAa;EAC9D,OAAO;IACL;IACA;IACA;IACA,SAAS;;IACL;IAAyB,SAAS;;EACxC,MAAM,SAAS;;;;;;;cAQJ,6BAA8B,UAAU,gBACnD;EACE,SACE,kBACA,mBACA,yBACA,gBACA,SAAS,uBACN;EACL,OAAO,kBAAkB,oBAAoB;EAC7C,cAAc;EACd,UACE,kBACA,mBACA,gBACA,SAAS,uBACN;GAEP,SAAS,qBAAqB,UAAU;EAG5B,SAAA,kBAAM;IACR;IAAyB,SAAS;QACzC;EAaa,UAAA,kBAAM;IACR;IAAyB,SAAS;KAAU,sBAEnD;EAUgB,OAAA,qBAAM;;MA1Db;;;;EA0ES,cAAA"}
@@ -1,13 +1,8 @@
1
1
  //#region src/FormattedCloudPersistence.ts
2
2
  /**
3
- * Wraps a CloudMessagePersistence instance with format-aware encode/decode.
4
- *
5
- * This centralizes the pattern used by both:
6
- * - useCloudChat (standalone AI SDK hook)
7
- * - AssistantCloudThreadHistoryAdapter.withFormat() (assistant-ui runtime)
8
- *
9
- * The persistence parameter is typed structurally (not by class) so callers
10
- * don't need to import CloudMessagePersistence directly.
3
+ * Wraps a CloudMessagePersistence with a MessageFormatAdapter's encode and
4
+ * decode. The persistence parameter is typed structurally, so a caller does
5
+ * not need to import the class.
11
6
  */
12
7
  const createFormattedPersistence = (persistence, adapter) => ({
13
8
  append: async (threadId, item) => {
@@ -1 +1 @@
1
- {"version":3,"file":"FormattedCloudPersistence.js","names":[],"sources":["../src/FormattedCloudPersistence.ts"],"sourcesContent":["import type { ReadonlyJSONObject } from \"assistant-stream/utils\";\n\n/**\n * Format adapter shape — structurally identical to the MessageFormatAdapter\n * in @assistant-ui/react, but defined here to avoid cross-package type moves.\n * TypeScript's structural typing ensures these are interchangeable.\n */\nexport type MessageFormatAdapter<TMessage, TStorageFormat> = {\n format: string;\n encode(item: { parentId: string | null; message: TMessage }): TStorageFormat;\n decode(stored: {\n id: string;\n parent_id: string | null;\n format: string;\n content: TStorageFormat;\n }): { parentId: string | null; message: TMessage };\n getId(message: TMessage): string;\n};\n\n/**\n * Wraps a CloudMessagePersistence instance with format-aware encode/decode.\n *\n * This centralizes the pattern used by both:\n * - useCloudChat (standalone AI SDK hook)\n * - AssistantCloudThreadHistoryAdapter.withFormat() (assistant-ui runtime)\n *\n * The persistence parameter is typed structurally (not by class) so callers\n * don't need to import CloudMessagePersistence directly.\n */\nexport const createFormattedPersistence = <TMessage, TStorageFormat>(\n persistence: {\n append: (\n threadId: string,\n messageId: string,\n parentId: string | null,\n format: string,\n content: ReadonlyJSONObject,\n ) => Promise<void>;\n load: (threadId: string, format?: string) => Promise<any[]>;\n isPersisted: (messageId: string) => boolean;\n update?: (\n threadId: string,\n messageId: string,\n format: string,\n content: ReadonlyJSONObject,\n ) => Promise<void>;\n },\n adapter: MessageFormatAdapter<TMessage, TStorageFormat>,\n) => ({\n append: async (\n threadId: string,\n item: { parentId: string | null; message: TMessage },\n ): Promise<void> => {\n const messageId = adapter.getId(item.message);\n const encoded = adapter.encode(item);\n return persistence.append(\n threadId,\n messageId,\n item.parentId,\n adapter.format,\n encoded as ReadonlyJSONObject,\n );\n },\n update: persistence.update\n ? async (\n threadId: string,\n item: { parentId: string | null; message: TMessage },\n messageId: string,\n ): Promise<void> => {\n const encoded = adapter.encode(item);\n return persistence.update!(\n threadId,\n messageId,\n adapter.format,\n encoded as ReadonlyJSONObject,\n );\n }\n : undefined,\n load: async (threadId: string) => {\n const messages = await persistence.load(threadId, adapter.format);\n return {\n messages: messages\n .filter((m) => m.format === adapter.format)\n .map((m) =>\n adapter.decode({\n id: m.id,\n parent_id: m.parent_id,\n format: m.format,\n content: m.content as TStorageFormat,\n }),\n )\n .reverse(),\n };\n },\n isPersisted: (messageId: string) => persistence.isPersisted(messageId),\n});\n"],"mappings":";;;;;;;;;;;AA6BA,MAAa,8BACX,aAiBA,aACI;CACJ,QAAQ,OACN,UACA,SACkB;EAClB,MAAM,YAAY,QAAQ,MAAM,KAAK,OAAO;EAC5C,MAAM,UAAU,QAAQ,OAAO,IAAI;EACnC,OAAO,YAAY,OACjB,UACA,WACA,KAAK,UACL,QAAQ,QACR,OACF;CACF;CACA,QAAQ,YAAY,SAChB,OACE,UACA,MACA,cACkB;EAClB,MAAM,UAAU,QAAQ,OAAO,IAAI;EACnC,OAAO,YAAY,OACjB,UACA,WACA,QAAQ,QACR,OACF;CACF,IACA,KAAA;CACJ,MAAM,OAAO,aAAqB;EAEhC,OAAO,EACL,WAAU,MAFW,YAAY,KAAK,UAAU,QAAQ,MAAM,EAAA,CAG3D,QAAQ,MAAM,EAAE,WAAW,QAAQ,MAAM,CAAC,CAC1C,KAAK,MACJ,QAAQ,OAAO;GACb,IAAI,EAAE;GACN,WAAW,EAAE;GACb,QAAQ,EAAE;GACV,SAAS,EAAE;EACb,CAAC,CACH,CAAC,CACA,QAAQ,EACb;CACF;CACA,cAAc,cAAsB,YAAY,YAAY,SAAS;AACvE"}
1
+ {"version":3,"file":"FormattedCloudPersistence.js","names":[],"sources":["../src/FormattedCloudPersistence.ts"],"sourcesContent":["import type { ReadonlyJSONObject } from \"assistant-stream/utils\";\n\n/**\n * Format adapter shape — structurally identical to the MessageFormatAdapter\n * in @assistant-ui/react, but defined here to avoid cross-package type moves.\n * TypeScript's structural typing ensures these are interchangeable.\n */\nexport type MessageFormatAdapter<TMessage, TStorageFormat> = {\n format: string;\n encode(item: { parentId: string | null; message: TMessage }): TStorageFormat;\n decode(stored: {\n id: string;\n parent_id: string | null;\n format: string;\n content: TStorageFormat;\n }): { parentId: string | null; message: TMessage };\n getId(message: TMessage): string;\n};\n\n/**\n * Wraps a CloudMessagePersistence with a MessageFormatAdapter's encode and\n * decode. The persistence parameter is typed structurally, so a caller does\n * not need to import the class.\n */\nexport const createFormattedPersistence = <TMessage, TStorageFormat>(\n persistence: {\n append: (\n threadId: string,\n messageId: string,\n parentId: string | null,\n format: string,\n content: ReadonlyJSONObject,\n ) => Promise<void>;\n load: (threadId: string, format?: string) => Promise<any[]>;\n isPersisted: (messageId: string) => boolean;\n update?: (\n threadId: string,\n messageId: string,\n format: string,\n content: ReadonlyJSONObject,\n ) => Promise<void>;\n },\n adapter: MessageFormatAdapter<TMessage, TStorageFormat>,\n) => ({\n append: async (\n threadId: string,\n item: { parentId: string | null; message: TMessage },\n ): Promise<void> => {\n const messageId = adapter.getId(item.message);\n const encoded = adapter.encode(item);\n return persistence.append(\n threadId,\n messageId,\n item.parentId,\n adapter.format,\n encoded as ReadonlyJSONObject,\n );\n },\n update: persistence.update\n ? async (\n threadId: string,\n item: { parentId: string | null; message: TMessage },\n messageId: string,\n ): Promise<void> => {\n const encoded = adapter.encode(item);\n return persistence.update!(\n threadId,\n messageId,\n adapter.format,\n encoded as ReadonlyJSONObject,\n );\n }\n : undefined,\n load: async (threadId: string) => {\n const messages = await persistence.load(threadId, adapter.format);\n return {\n messages: messages\n .filter((m) => m.format === adapter.format)\n .map((m) =>\n adapter.decode({\n id: m.id,\n parent_id: m.parent_id,\n format: m.format,\n content: m.content as TStorageFormat,\n }),\n )\n .reverse(),\n };\n },\n isPersisted: (messageId: string) => persistence.isPersisted(messageId),\n});\n"],"mappings":";;;;;;AAwBA,MAAa,8BACX,aAiBA,aACI;CACJ,QAAQ,OACN,UACA,SACkB;EAClB,MAAM,YAAY,QAAQ,MAAM,KAAK,OAAO;EAC5C,MAAM,UAAU,QAAQ,OAAO,IAAI;EACnC,OAAO,YAAY,OACjB,UACA,WACA,KAAK,UACL,QAAQ,QACR,OACF;CACF;CACA,QAAQ,YAAY,SAChB,OACE,UACA,MACA,cACkB;EAClB,MAAM,UAAU,QAAQ,OAAO,IAAI;EACnC,OAAO,YAAY,OACjB,UACA,WACA,QAAQ,QACR,OACF;CACF,IACA,KAAA;CACJ,MAAM,OAAO,aAAqB;EAEhC,OAAO,EACL,WAAU,MAFW,YAAY,KAAK,UAAU,QAAQ,MAAM,EAAA,CAG3D,QAAQ,MAAM,EAAE,WAAW,QAAQ,MAAM,CAAC,CAC1C,KAAK,MACJ,QAAQ,OAAO;GACb,IAAI,EAAE;GACN,WAAW,EAAE;GACb,QAAQ,EAAE;GACV,SAAS,EAAE;EACb,CAAC,CACH,CAAC,CACA,QAAQ,EACb;CACF;CACA,cAAc,cAAsB,YAAY,YAAY,SAAS;AACvE"}
@@ -0,0 +1,33 @@
1
+ import { RunMessageTelemetry } from "../runTelemetry.js";
2
+ import { MessageFormatAdapter } from "../FormattedCloudPersistence.js";
3
+ import { UIMessage } from "ai";
4
+ //#region src/ai-sdk/index.d.ts
5
+ type AISDKStorageFormat = Omit<UIMessage, "id">;
6
+ /** The stored form of an AI SDK message: the message without its id. */
7
+ declare const aiSDKV6FormatAdapter: MessageFormatAdapter<UIMessage, AISDKStorageFormat>;
8
+ /**
9
+ * A message as an AI SDK integration holds it, or as the cloud stored it under
10
+ * the ai-sdk/v6 format, which drops the id. Parts are typed by their `type`
11
+ * alone, so this shape and the telemetry read from it need nothing from `ai`.
12
+ */
13
+ type AISDKMessageLike = {
14
+ id?: string | undefined;
15
+ role: string;
16
+ parts: readonly {
17
+ type: string;
18
+ [key: string]: unknown;
19
+ }[];
20
+ metadata?: unknown;
21
+ };
22
+ /**
23
+ * Reads the run report fields out of the assistant messages of one run. A run
24
+ * the AI SDK streamed as one message is one element; a run the cloud stored as
25
+ * several assistant rows is aggregated, step by step, in order. Returns null
26
+ * when no assistant message is present. Status reads completed when the run
27
+ * produced text or tool calls, as a live finish with reason `tool-calls` does;
28
+ * an integration that observed the finish event overrides it.
29
+ */
30
+ declare function extractAISDKRunTelemetry(messages: readonly AISDKMessageLike[]): RunMessageTelemetry | null;
31
+ //#endregion
32
+ export { AISDKMessageLike, AISDKStorageFormat, aiSDKV6FormatAdapter, extractAISDKRunTelemetry };
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/ai-sdk/index.ts"],"mappings":";;;;KAeY,qBAAqB,KAAK;;cAGzB,sBAAsB,qBACjC,WACA;;;;;;KAgBU;EACV;EACA;EACA;IAAkB;KAAe;;EACjC;;;;;;;;;;iBAgHc,yBACd,mBAAmB,qBAClB"}
@@ -0,0 +1,147 @@
1
+ import { createRunTelemetryToolCall, extractRunTelemetryModelId, normalizeRunTelemetryUsage, truncateRunTelemetryText } from "../runTelemetry.js";
2
+ //#region src/ai-sdk/index.ts
3
+ /** The stored form of an AI SDK message: the message without its id. */
4
+ const aiSDKV6FormatAdapter = {
5
+ format: "ai-sdk/v6",
6
+ encode: ({ message: { id: _id, ...message } }) => message,
7
+ decode: (stored) => ({
8
+ parentId: stored.parent_id,
9
+ message: {
10
+ id: stored.id,
11
+ ...stored.content
12
+ }
13
+ }),
14
+ getId: (message) => message.id
15
+ };
16
+ function isRecord(value) {
17
+ return value !== null && typeof value === "object";
18
+ }
19
+ function isPart(value) {
20
+ return isRecord(value) && typeof value.type === "string";
21
+ }
22
+ /**
23
+ * The AI SDK's own tool part rules, kept here so the entry loads without the
24
+ * `ai` runtime: a static tool part is `tool-<name>`, a dynamic one is
25
+ * `dynamic-tool` with its name in `toolName`.
26
+ */
27
+ function toolCallOf(part) {
28
+ if (typeof part.toolCallId !== "string") return void 0;
29
+ const isStatic = part.type.startsWith("tool-");
30
+ if (!isStatic && part.type !== "dynamic-tool") return void 0;
31
+ const toolName = isStatic ? part.type.slice(5) : typeof part.toolName === "string" ? part.toolName : void 0;
32
+ if (!toolName) return void 0;
33
+ return createRunTelemetryToolCall({
34
+ toolName,
35
+ toolCallId: part.toolCallId,
36
+ args: part.input ?? part.args,
37
+ result: part.output ?? part.result,
38
+ toolSource: isStatic ? "frontend" : "mcp"
39
+ });
40
+ }
41
+ function attachSamplingCalls(toolCalls, metadata) {
42
+ const samplingCalls = isRecord(metadata?.samplingCalls) ? metadata.samplingCalls : void 0;
43
+ if (!samplingCalls) return;
44
+ for (const toolCall of toolCalls) {
45
+ const calls = samplingCalls[toolCall.tool_call_id];
46
+ if (Array.isArray(calls) && calls.length > 0) toolCall.sampling_calls = calls;
47
+ }
48
+ }
49
+ function stepUsages(metadata) {
50
+ const steps = metadata?.steps;
51
+ if (!Array.isArray(steps)) return [];
52
+ return steps.map((step) => isRecord(step) && isRecord(step.usage) ? step.usage : void 0);
53
+ }
54
+ function sumUsage(usages) {
55
+ const total = {};
56
+ for (const usage of usages) {
57
+ if (usage.inputTokens != null) total.inputTokens = (total.inputTokens ?? 0) + usage.inputTokens;
58
+ if (usage.outputTokens != null) total.outputTokens = (total.outputTokens ?? 0) + usage.outputTokens;
59
+ if (usage.reasoningTokens != null) total.reasoningTokens = (total.reasoningTokens ?? 0) + usage.reasoningTokens;
60
+ if (usage.cachedInputTokens != null) total.cachedInputTokens = (total.cachedInputTokens ?? 0) + usage.cachedInputTokens;
61
+ }
62
+ return total;
63
+ }
64
+ /**
65
+ * The usage a message reports: `metadata.usage` when the integration copied
66
+ * the run total there, else the sum over `metadata.steps[].usage`.
67
+ */
68
+ function messageUsage(metadata) {
69
+ const total = isRecord(metadata?.usage) ? normalizeRunTelemetryUsage(metadata.usage) : void 0;
70
+ if (total) return total;
71
+ const perStep = stepUsages(metadata).flatMap((usage) => {
72
+ const normalized = usage ? normalizeRunTelemetryUsage(usage) : void 0;
73
+ return normalized ? [normalized] : [];
74
+ });
75
+ return perStep.length > 0 ? sumUsage(perStep) : void 0;
76
+ }
77
+ /**
78
+ * Reads the run report fields out of the assistant messages of one run. A run
79
+ * the AI SDK streamed as one message is one element; a run the cloud stored as
80
+ * several assistant rows is aggregated, step by step, in order. Returns null
81
+ * when no assistant message is present. Status reads completed when the run
82
+ * produced text or tool calls, as a live finish with reason `tool-calls` does;
83
+ * an integration that observed the finish event overrides it.
84
+ */
85
+ function extractAISDKRunTelemetry(messages) {
86
+ const textParts = [];
87
+ const toolCalls = [];
88
+ const steps = [];
89
+ const usages = [];
90
+ let assistant;
91
+ for (const message of messages) {
92
+ if (message.role !== "assistant") continue;
93
+ assistant = message;
94
+ const metadata = isRecord(message.metadata) ? message.metadata : void 0;
95
+ const usagePerStep = stepUsages(metadata);
96
+ const messageToolCalls = [];
97
+ let step;
98
+ let stepIndex = -1;
99
+ for (const part of message.parts) {
100
+ if (!isPart(part)) continue;
101
+ if (part.type === "step-start") {
102
+ stepIndex += 1;
103
+ const usage = usagePerStep[stepIndex];
104
+ step = usage ? { usage } : {};
105
+ steps.push(step);
106
+ continue;
107
+ }
108
+ if (part.type === "text" && typeof part.text === "string" && part.text) {
109
+ textParts.push(part.text);
110
+ continue;
111
+ }
112
+ const toolCall = toolCallOf(part);
113
+ if (!toolCall) continue;
114
+ toolCalls.push(toolCall);
115
+ messageToolCalls.push(toolCall);
116
+ if (step) {
117
+ step.toolCalls = [...step.toolCalls ?? [], toolCall];
118
+ step.finishReason = "tool-calls";
119
+ }
120
+ }
121
+ attachSamplingCalls(messageToolCalls, metadata);
122
+ const usage = messageUsage(metadata);
123
+ if (usage) usages.push(usage);
124
+ }
125
+ if (!assistant) return null;
126
+ const metadata = isRecord(assistant.metadata) ? assistant.metadata : void 0;
127
+ const usage = usages.length > 0 ? sumUsage(usages) : void 0;
128
+ const modelId = extractRunTelemetryModelId(metadata);
129
+ const completed = textParts.length > 0 || toolCalls.length > 0;
130
+ return {
131
+ ...assistant.id !== void 0 ? { assistantMessageId: assistant.id } : void 0,
132
+ status: completed ? "completed" : "incomplete",
133
+ ...toolCalls.length > 0 ? { toolCalls } : void 0,
134
+ ...steps.length > 0 ? {
135
+ steps,
136
+ totalSteps: steps.length
137
+ } : void 0,
138
+ ...textParts.length > 0 ? { outputText: truncateRunTelemetryText(textParts.join("")) } : void 0,
139
+ ...usage ? { usage } : void 0,
140
+ ...modelId ? { modelId } : void 0,
141
+ ...metadata ? { metadata } : void 0
142
+ };
143
+ }
144
+ //#endregion
145
+ export { aiSDKV6FormatAdapter, extractAISDKRunTelemetry };
146
+
147
+ //# sourceMappingURL=index.js.map