@beeuni/sdk 0.1.0 → 0.3.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.
Files changed (291) hide show
  1. package/README.md +53 -92
  2. package/dist/beeuni.esm.min.js +36 -34
  3. package/dist/beeuni.global.js +36 -34
  4. package/dist/browser/cache/idb-sync-cache.js +15 -5
  5. package/dist/browser/session-sync/server-transcript-mirror.d.ts +7 -1
  6. package/dist/browser/session-sync/server-transcript-mirror.js +30 -24
  7. package/dist/browser/session-sync/session-sync-registry.d.ts +3 -1
  8. package/dist/browser/session-sync/session-sync-registry.js +48 -3
  9. package/dist/browser/stores/sync-store/keep-unchanged.d.ts +4 -0
  10. package/dist/browser/stores/sync-store/keep-unchanged.js +34 -0
  11. package/dist/browser/stores/sync-store.d.ts +7 -0
  12. package/dist/browser/stores/sync-store.js +122 -35
  13. package/dist/core/attachments/prompt-attachments.d.ts +4 -0
  14. package/dist/core/attachments/prompt-attachments.js +34 -0
  15. package/dist/core/capture/rules.d.ts +17 -0
  16. package/dist/core/capture/rules.js +27 -0
  17. package/dist/core/client/beeuni.d.ts +99 -34
  18. package/dist/core/client/beeuni.js +5 -11
  19. package/dist/core/client/capture.d.ts +105 -0
  20. package/dist/core/client/capture.js +48 -0
  21. package/dist/core/client/facade-catalog.d.ts +0 -4
  22. package/dist/core/client/facade-catalog.js +1 -5
  23. package/dist/core/client/facade-identity.d.ts +1 -0
  24. package/dist/core/client/facade-identity.js +1 -0
  25. package/dist/core/client/project-access-resources.d.ts +3 -1
  26. package/dist/core/client/project-access-resources.js +1 -1
  27. package/dist/core/client/project-connections.d.ts +2 -2
  28. package/dist/core/client/project-connections.js +2 -2
  29. package/dist/core/client/project-operations-admin.d.ts +4 -4
  30. package/dist/core/client/project-operations-review.d.ts +2 -3
  31. package/dist/core/client/project-operations-review.js +1 -0
  32. package/dist/core/client/project-operations-workspace.d.ts +4 -4
  33. package/dist/core/client/project-operations-workspace.js +1 -1
  34. package/dist/core/client/project-operations.d.ts +9 -19
  35. package/dist/core/client/project-operations.js +2 -6
  36. package/dist/core/client/project-platform-security.d.ts +1 -1
  37. package/dist/core/client/project-platform-settings.d.ts +1 -1
  38. package/dist/core/client/session-context.d.ts +2 -0
  39. package/dist/core/client/session-handle.d.ts +13 -0
  40. package/dist/core/client/session-lifecycle-resources.d.ts +11 -0
  41. package/dist/core/client/session-lifecycle-resources.js +4 -0
  42. package/dist/core/client/session-lifecycle-security.d.ts +2 -0
  43. package/dist/core/client/session-lifecycle-security.js +1 -0
  44. package/dist/core/client/session-readiness.d.ts +2 -0
  45. package/dist/core/client/session-readiness.js +20 -1
  46. package/dist/core/http/api-client.d.ts +1 -7
  47. package/dist/core/http/api-client.js +2 -37
  48. package/dist/core/http/auth.d.ts +2 -4
  49. package/dist/core/http/auth.js +2 -8
  50. package/dist/core/http/config.d.ts +2 -0
  51. package/dist/core/http/response-error.js +26 -11
  52. package/dist/core/http/transport.js +6 -1
  53. package/dist/core/models/composer-model.d.ts +14 -0
  54. package/dist/core/models/composer-model.js +21 -1
  55. package/dist/core/rest/platform-client/capture.d.ts +363 -0
  56. package/dist/core/rest/platform-client/capture.js +116 -0
  57. package/dist/core/rest/platform-client/client-policy.d.ts +8 -0
  58. package/dist/core/rest/platform-client/client-policy.js +30 -0
  59. package/dist/core/rest/platform-client/index.d.ts +4 -4
  60. package/dist/core/rest/platform-client/index.js +4 -4
  61. package/dist/core/rest/platform-client/lifecycle.js +18 -12
  62. package/dist/core/rest/platform-client/shared.d.ts +5 -0
  63. package/dist/core/rest/platform-client/shared.js +18 -0
  64. package/dist/core/rest/platform-client/types.d.ts +1 -1
  65. package/dist/core/rest/projects-client/account-secret-resources.d.ts +2 -1
  66. package/dist/core/rest/projects-client/accounts.d.ts +34 -0
  67. package/dist/core/rest/projects-client/accounts.js +10 -0
  68. package/dist/core/rest/projects-client/admin-providers.d.ts +0 -61
  69. package/dist/core/rest/projects-client/admin-providers.js +0 -6
  70. package/dist/core/rest/projects-client/agent-config.d.ts +3 -0
  71. package/dist/core/rest/projects-client/apps-fleet.d.ts +5 -1
  72. package/dist/core/rest/projects-client/apps-fleet.js +2 -2
  73. package/dist/core/rest/projects-client/apps.d.ts +11 -1
  74. package/dist/core/rest/projects-client/change-requests.d.ts +2 -0
  75. package/dist/core/rest/projects-client/change-requests.js +2 -0
  76. package/dist/core/rest/projects-client/connectors.d.ts +13 -55
  77. package/dist/core/rest/projects-client/connectors.js +5 -29
  78. package/dist/core/rest/projects-client/files.d.ts +3 -0
  79. package/dist/core/rest/projects-client/files.js +4 -0
  80. package/dist/core/rest/projects-client/gateway.d.ts +5 -0
  81. package/dist/core/rest/projects-client/git-backend.d.ts +1 -10
  82. package/dist/core/rest/projects-client/git-backend.js +0 -10
  83. package/dist/core/rest/projects-client/iam-actions.d.ts +76 -0
  84. package/dist/core/rest/projects-client/iam-actions.js +101 -0
  85. package/dist/core/rest/projects-client/iam-sso.d.ts +0 -10
  86. package/dist/core/rest/projects-client/iam-sso.js +0 -3
  87. package/dist/core/rest/projects-client/iam.d.ts +1 -0
  88. package/dist/core/rest/projects-client/iam.js +1 -0
  89. package/dist/core/rest/projects-client/index.d.ts +1 -4
  90. package/dist/core/rest/projects-client/index.js +1 -4
  91. package/dist/core/rest/projects-client/notifications.d.ts +1 -1
  92. package/dist/core/rest/projects-client/notifications.js +4 -0
  93. package/dist/core/rest/projects-client/projects.d.ts +4 -2
  94. package/dist/core/rest/projects-client/projects.js +4 -2
  95. package/dist/core/rest/projects-client/review-items.d.ts +2 -0
  96. package/dist/core/rest/projects-client/review-items.js +4 -0
  97. package/dist/core/rest/projects-client/review.d.ts +17 -3
  98. package/dist/core/rest/projects-client/review.js +11 -0
  99. package/dist/core/rest/projects-client/sandbox.d.ts +8 -9
  100. package/dist/core/rest/projects-client/secrets.d.ts +3 -0
  101. package/dist/core/rest/projects-client/session-attachments.js +14 -1
  102. package/dist/core/rest/projects-client/session-sandbox.d.ts +1 -1
  103. package/dist/core/rest/projects-client/sessions.d.ts +31 -4
  104. package/dist/core/rest/projects-client/sessions.js +34 -4
  105. package/dist/core/runtime/runtime-rest-client.js +1 -1
  106. package/dist/core/runtime/runtime-types.d.ts +1 -0
  107. package/dist/core/session/control-stream.d.ts +1 -0
  108. package/dist/core/session/control-stream.js +18 -1
  109. package/dist/core/session/open-bundle.d.ts +4 -4
  110. package/dist/core/session/open-bundle.js +29 -46
  111. package/dist/core/session/turn-end-watch.d.ts +47 -0
  112. package/dist/core/session/turn-end-watch.js +141 -0
  113. package/dist/core/session-log/guard.d.ts +6 -0
  114. package/dist/core/session-log/guard.js +109 -0
  115. package/dist/core/session-log/index.d.ts +7 -0
  116. package/dist/core/session-log/index.js +5 -0
  117. package/dist/core/session-log/saved-log.d.ts +10 -0
  118. package/dist/core/session-log/saved-log.js +25 -0
  119. package/dist/core/session-log/source.d.ts +25 -0
  120. package/dist/core/session-log/source.js +30 -0
  121. package/dist/core/session-log/store.d.ts +21 -0
  122. package/dist/core/session-log/store.js +126 -0
  123. package/dist/core/session-log/tool-call.d.ts +11 -0
  124. package/dist/core/session-log/tool-call.js +65 -0
  125. package/dist/core/session-log/turns/blocks.d.ts +60 -0
  126. package/dist/core/session-log/turns/blocks.js +124 -0
  127. package/dist/core/session-log/turns/history.d.ts +32 -0
  128. package/dist/core/session-log/turns/history.js +99 -0
  129. package/dist/core/session-log/turns/index.d.ts +4 -0
  130. package/dist/core/session-log/turns/index.js +4 -0
  131. package/dist/core/session-log/turns/metrics.d.ts +21 -0
  132. package/dist/core/session-log/turns/metrics.js +29 -0
  133. package/dist/core/session-log/turns/turn.d.ts +55 -0
  134. package/dist/core/session-log/turns/turn.js +279 -0
  135. package/dist/core/session-log/turns/v1-parts.d.ts +8 -0
  136. package/dist/core/session-log/turns/v1-parts.js +43 -0
  137. package/dist/core/session-log/types.d.ts +264 -0
  138. package/dist/core/session-log/types.js +6 -0
  139. package/dist/core/session-log/v1-view.d.ts +13 -0
  140. package/dist/core/session-log/v1-view.js +215 -0
  141. package/dist/core/session-log/v1.d.ts +31 -0
  142. package/dist/core/session-log/v1.js +473 -0
  143. package/dist/core/session-sync/copy-store-types.d.ts +19 -0
  144. package/dist/core/session-sync/copy-store-types.js +1 -0
  145. package/dist/core/session-sync/saved-copy-store.js +25 -4
  146. package/dist/core/session-sync/saved-transcript.d.ts +15 -1
  147. package/dist/core/session-sync/saved-transcript.js +55 -0
  148. package/dist/core/session-sync/session-sync-controller.js +3 -0
  149. package/dist/core/stream/event-stream.js +2 -2
  150. package/dist/core/turns/classify.d.ts +2 -1
  151. package/dist/core/turns/classify.js +7 -3
  152. package/dist/core/turns/edit-blocks.d.ts +1 -0
  153. package/dist/core/turns/edit-blocks.js +15 -0
  154. package/dist/core/turns/parts.d.ts +3 -2
  155. package/dist/core/turns/parts.js +4 -2
  156. package/dist/core/turns/segments/burst-summary.d.ts +1 -0
  157. package/dist/core/turns/segments/burst-summary.js +24 -0
  158. package/dist/core/turns/segments/group-steps.d.ts +4 -3
  159. package/dist/core/turns/segments/group-steps.js +9 -6
  160. package/dist/core/turns/segments/index.d.ts +1 -0
  161. package/dist/core/turns/segments/index.js +1 -0
  162. package/dist/core/turns/segments/narration.d.ts +5 -4
  163. package/dist/core/turns/segments/narration.js +12 -3
  164. package/dist/core/turns/segments/session-activity-groups.d.ts +2 -1
  165. package/dist/core/turns/segments/session-activity-groups.js +3 -1
  166. package/dist/core/turns/segments/step-label.d.ts +2 -1
  167. package/dist/core/turns/segments/step-label.js +3 -1
  168. package/dist/core/turns/segments/work-split.d.ts +10 -0
  169. package/dist/core/turns/segments/work-split.js +40 -0
  170. package/dist/core/turns/segments/working-turn.d.ts +4 -2
  171. package/dist/core/turns/segments/working-turn.js +9 -8
  172. package/dist/core/turns/state.d.ts +2 -7
  173. package/dist/core/turns/state.js +28 -134
  174. package/dist/core/turns/tool-kind.d.ts +1 -1
  175. package/dist/core/turns/tool-kind.js +3 -2
  176. package/dist/core/turns/tool-registry.js +1 -0
  177. package/dist/core/turns/tools/tool-meta.d.ts +3 -2
  178. package/dist/core/turns/tools/tool-meta.js +5 -2
  179. package/dist/core/turns/tools/tool-outcome.d.ts +2 -1
  180. package/dist/core/turns/tools/tool-outcome.js +3 -1
  181. package/dist/core/turns/types.d.ts +1 -1
  182. package/dist/core/turns/view-model.js +1 -13
  183. package/dist/core/warm-session-pool.d.ts +29 -0
  184. package/dist/core/warm-session-pool.js +127 -0
  185. package/dist/genui/catalog.d.ts +28 -0
  186. package/dist/genui/catalog.js +396 -0
  187. package/dist/genui/expansion.d.ts +3 -0
  188. package/dist/genui/expansion.js +295 -0
  189. package/dist/genui/fence-entry.d.ts +2 -0
  190. package/dist/genui/fence-entry.js +1 -0
  191. package/dist/genui/fence.d.ts +21 -0
  192. package/dist/genui/fence.js +138 -0
  193. package/dist/genui/index.d.ts +6 -0
  194. package/dist/genui/index.js +6 -0
  195. package/dist/genui/markdown.d.ts +12 -0
  196. package/dist/genui/markdown.js +104 -0
  197. package/dist/genui/parse.d.ts +6 -0
  198. package/dist/genui/parse.js +75 -0
  199. package/dist/genui/prompt.d.ts +2 -0
  200. package/dist/genui/prompt.js +206 -0
  201. package/dist/genui/react/default-components.d.ts +2 -0
  202. package/dist/genui/react/default-components.js +72 -0
  203. package/dist/genui/react/default-components.test.d.ts +1 -0
  204. package/dist/genui/react/default-components.test.js +94 -0
  205. package/dist/genui/react/genui-block-disabled.test.d.ts +1 -0
  206. package/dist/genui/react/genui-block-disabled.test.js +50 -0
  207. package/dist/genui/react/genui-block.d.ts +28 -0
  208. package/dist/genui/react/genui-block.js +91 -0
  209. package/dist/genui/react/genui-block.test.d.ts +1 -0
  210. package/dist/genui/react/genui-block.test.js +205 -0
  211. package/dist/genui/react/index.d.ts +2 -0
  212. package/dist/genui/react/index.js +2 -0
  213. package/dist/genui/share.d.ts +5 -0
  214. package/dist/genui/share.js +30 -0
  215. package/dist/genui/test-fixtures.d.ts +2 -0
  216. package/dist/genui/test-fixtures.js +16 -0
  217. package/dist/genui/types.d.ts +29 -0
  218. package/dist/genui/types.js +1 -0
  219. package/dist/genui/urls.d.ts +1 -0
  220. package/dist/genui/urls.js +18 -0
  221. package/dist/genui/validate.d.ts +9 -0
  222. package/dist/genui/validate.js +116 -0
  223. package/dist/index.d.ts +4 -4
  224. package/dist/index.js +4 -4
  225. package/dist/internal/connector-catalog.d.ts +48 -0
  226. package/dist/internal/connector-catalog.js +27 -0
  227. package/dist/internal/host-configuration.d.ts +22 -0
  228. package/dist/internal/host-configuration.js +28 -0
  229. package/dist/internal/platform-admin-react.d.ts +12 -0
  230. package/dist/internal/platform-admin-react.js +13 -0
  231. package/dist/internal/platform-admin.d.ts +9 -0
  232. package/dist/internal/platform-admin.js +9 -0
  233. package/dist/platform/ui.d.ts +8 -2
  234. package/dist/platform/ui.js +19 -4
  235. package/dist/react/chat/use-chat-turns.d.ts +1 -1
  236. package/dist/react/human-presence.d.ts +9 -0
  237. package/dist/react/human-presence.js +12 -0
  238. package/dist/react/index.d.ts +4 -13
  239. package/dist/react/index.js +4 -13
  240. package/dist/react/opencode.d.ts +1 -0
  241. package/dist/react/opencode.js +1 -0
  242. package/dist/react/query-contracts.d.ts +6 -0
  243. package/dist/react/query-contracts.js +6 -0
  244. package/dist/react/query-keys.d.ts +17 -2
  245. package/dist/react/query-keys.js +24 -2
  246. package/dist/react/reset-identity-state.js +2 -0
  247. package/dist/react/session-cache-write.d.ts +1 -0
  248. package/dist/react/session-cache-write.js +20 -0
  249. package/dist/react/session-transcript-subscription.js +3 -1
  250. package/dist/react/sign-in-with-beeuni.d.ts +6 -6
  251. package/dist/react/turn-end-handoff.d.ts +13 -0
  252. package/dist/react/turn-end-handoff.js +101 -0
  253. package/dist/react/use-admin-providers.d.ts +3 -6
  254. package/dist/react/use-admin-providers.js +1 -11
  255. package/dist/react/use-capture.d.ts +47 -0
  256. package/dist/react/use-capture.js +173 -0
  257. package/dist/react/use-feature-flag.js +1 -0
  258. package/dist/react/use-opencode-events/handle-event.d.ts +1 -0
  259. package/dist/react/use-opencode-events/handle-event.js +5 -0
  260. package/dist/react/use-opencode-events/handle-interaction-event.js +8 -3
  261. package/dist/react/use-opencode-events/handle-session-event.js +13 -8
  262. package/dist/react/use-opencode-events/handler-context.d.ts +1 -0
  263. package/dist/react/use-opencode-events/helpers.d.ts +1 -0
  264. package/dist/react/use-opencode-events/helpers.js +11 -1
  265. package/dist/react/use-opencode-events/index.js +1 -0
  266. package/dist/react/use-opencode-local.d.ts +5 -1
  267. package/dist/react/use-opencode-local.js +15 -2
  268. package/dist/react/use-opencode-sessions/commands.d.ts +11 -1
  269. package/dist/react/use-opencode-sessions/commands.js +21 -1
  270. package/dist/react/use-opencode-sessions/permissions.js +28 -1
  271. package/dist/react/use-project-sessions.d.ts +1 -1
  272. package/dist/react/use-project-sessions.js +6 -5
  273. package/dist/react/use-prompt-attachments.js +1 -0
  274. package/dist/react/use-review-summary.d.ts +13 -0
  275. package/dist/react/use-review-summary.js +32 -0
  276. package/dist/react/use-runtime-reconnect.js +1 -1
  277. package/dist/react/use-session-list-state.d.ts +6 -0
  278. package/dist/react/use-session-list-state.js +33 -0
  279. package/dist/react/use-session-log.d.ts +16 -0
  280. package/dist/react/use-session-log.js +143 -0
  281. package/dist/react/use-session-messages.d.ts +2 -1
  282. package/dist/react/use-session-messages.js +15 -14
  283. package/dist/react/use-session-stream.js +12 -1
  284. package/dist/react/use-session-sync.d.ts +2 -2
  285. package/dist/react/use-session-sync.js +10 -8
  286. package/dist/react/use-session-transcript-history.js +2 -12
  287. package/dist/react/use-session-working.d.ts +2 -0
  288. package/dist/react/use-session-working.js +10 -0
  289. package/dist/react/use-session.d.ts +6 -0
  290. package/dist/react/use-session.js +88 -6
  291. package/package.json +62 -8
@@ -0,0 +1,473 @@
1
+ import { BEEUNI_TRANSCRIPT_SCHEMA, isTurnErrorCode } from '../runtime/transcript-types.js';
2
+ import { SESSION_LOG_MINOR, SESSION_LOG_SCHEMA, } from './types.js';
3
+ const IMPORT = 'beeuni-v1-import';
4
+ const PRODUCER = { harness: IMPORT, harness_version: BEEUNI_TRANSCRIPT_SCHEMA, adapter_version: '1' };
5
+ const V = { schema: SESSION_LOG_SCHEMA, v: SESSION_LOG_MINOR };
6
+ const INTERRUPTED = { opencode: '[Tool execution was interrupted]', pi: 'No result provided' };
7
+ const CLEARED = '[Old tool result content cleared]';
8
+ const SUBTASK_TEXT = 'The following tool was executed by the user';
9
+ const KIND = new Map(Object.entries({
10
+ bash: 'shell', powershell: 'shell', read: 'read', write: 'write', edit: 'edit', multiedit: 'edit', patch: 'patch', apply_patch: 'patch',
11
+ list: 'list', ls: 'list', glob: 'glob', find: 'glob', grep: 'grep', webfetch: 'web_fetch', websearch: 'web_search', codesearch: 'web_search',
12
+ todowrite: 'todo', todoread: 'todo', task: 'task', question: 'question', plan_enter: 'plan', plan_exit: 'plan',
13
+ }));
14
+ const kindOf = (name) => KIND.get(name) ?? (name.includes('_') ? 'mcp' : 'other');
15
+ const FINISH_V2 = new Map([['stop', 'stop'], ['tool-calls', 'tool_calls'], ['length', 'length'], ['error', 'error']]);
16
+ const FINISH_V1 = new Map([['stop', 'stop'], ['tool_calls', 'tool-calls'], ['length', 'length'], ['error', 'error']]);
17
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
18
+ const str = (v) => (typeof v === 'string' ? v : undefined);
19
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);
20
+ const own = (o, k) => (Object.hasOwn(o, k) ? o[k] : undefined);
21
+ const define = (o, k, v) => Object.defineProperty(o, k, { value: v, enumerable: true, writable: true, configurable: true });
22
+ const iso = (ms) => (num(ms) !== undefined && Math.abs(ms) <= 8.64e15 ? new Date(ms).toISOString() : null);
23
+ const msOf = (s) => {
24
+ const t = s ? Date.parse(s) : NaN;
25
+ return Number.isNaN(t) ? undefined : t;
26
+ };
27
+ function same(a, b) {
28
+ if (a === b)
29
+ return true;
30
+ if (Array.isArray(a) || Array.isArray(b))
31
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((x, i) => same(x, b[i]));
32
+ if (!isObj(a) || !isObj(b))
33
+ return false;
34
+ const ka = Object.keys(a).filter((k) => a[k] !== undefined);
35
+ return ka.length === Object.keys(b).filter((k) => b[k] !== undefined).length && ka.every((k) => same(a[k], own(b, k)));
36
+ }
37
+ function diff(stored, built) {
38
+ if (same(stored, built))
39
+ return undefined;
40
+ if (stored === undefined)
41
+ return { del: true };
42
+ if (!isObj(stored) || !isObj(built))
43
+ return { set: stored };
44
+ const sub = {};
45
+ for (const k of new Set([...Object.keys(stored), ...Object.keys(built)])) {
46
+ const change = diff(own(stored, k), own(built, k));
47
+ if (change)
48
+ define(sub, k, change);
49
+ }
50
+ return { sub };
51
+ }
52
+ function apply(built, change) {
53
+ if (!change)
54
+ return built;
55
+ if ('set' in change)
56
+ return change.set;
57
+ if ('del' in change)
58
+ return undefined;
59
+ const out = isObj(built) ? { ...built } : {};
60
+ for (const [k, c] of Object.entries(change.sub)) {
61
+ const v = apply(own(out, k), c);
62
+ if (v === undefined)
63
+ delete out[k];
64
+ else
65
+ define(out, k, v);
66
+ }
67
+ return out;
68
+ }
69
+ const v1Ext = (ext) => Object.values(ext ?? {}).find((e) => e.version === BEEUNI_TRANSCRIPT_SCHEMA);
70
+ const extOf = (h, entry) => ({ [h]: { version: BEEUNI_TRANSCRIPT_SCHEMA, ...entry } });
71
+ const urlOf = (a, c) => a.url !== undefined ? { url: a.url } : a.ref !== undefined && !c.fromV1 ? { url: a.ref } : {};
72
+ const textOf = (content) => (content ?? []).flatMap((c) => (c.type === 'text' ? [c.text] : [])).join('');
73
+ const head = (b, c) => ({ id: b.id, ...(c.sessionID !== null && { sessionID: c.sessionID }), messageID: c.messageID });
74
+ const detailsOf = (b) => {
75
+ if (isObj(b.result?.details))
76
+ return b.result.details;
77
+ const own = Object.values(b.ext ?? {}).find((e) => e.version !== BEEUNI_TRANSCRIPT_SCHEMA && isObj(e.data?.details));
78
+ return own ? own.data.details : {};
79
+ };
80
+ function toolState(b, c, base) {
81
+ const r = b.result;
82
+ const input = b.input === null || b.input === undefined ? {} : { input: b.input };
83
+ const start = msOf(b.started_at) ?? c.created;
84
+ const end = msOf(b.ended_at) ?? start;
85
+ const meta = r?.exit_code !== undefined ? { exit: r.exit_code } : {};
86
+ if (b.status === 'pending')
87
+ return { status: 'pending', input: b.input ?? {}, raw: '' };
88
+ if (b.status === 'running')
89
+ return { status: 'running', input: b.input ?? {}, ...(b.title && { title: b.title }), time: { start } };
90
+ if (b.status === 'error')
91
+ return { status: 'error', ...input, error: textOf(r?.content), ...(r?.exit_code !== undefined && { metadata: meta }), time: { start, end } };
92
+ const atts = (r?.content ?? []).flatMap((x, i) => x.type === 'attachment' ? [{ ...base, id: `${b.id}-a${i}`, type: 'file', mime: x.mime, ...urlOf(x, c) }] : []);
93
+ const compacted = msOf(r?.cleared_at);
94
+ return {
95
+ status: 'completed', ...input, output: textOf(r?.content), title: b.title ?? '', metadata: { ...detailsOf(b), ...meta },
96
+ time: { start, end, ...(compacted !== undefined && { compacted }) },
97
+ ...(atts.length && { attachments: atts }),
98
+ };
99
+ }
100
+ function partOf(b, c) {
101
+ const base = head(b, c);
102
+ switch (b.type) {
103
+ case 'text': return { ...base, type: 'text', text: b.text, ...(b.synthetic !== undefined && { synthetic: b.synthetic }) };
104
+ case 'reasoning': return { ...base, type: 'reasoning', text: b.text, time: { start: c.created } };
105
+ case 'attachment': return { ...base, type: 'file', mime: b.mime, ...(b.name !== undefined && { filename: b.name }), ...urlOf(b, c) };
106
+ case 'tool_call': return { ...base, type: 'tool', callID: b.call_id, tool: b.name, state: toolState(b, c, { ...(c.sessionID !== null && { sessionID: c.sessionID }), messageID: c.messageID }) };
107
+ case 'step': return b.phase === 'start'
108
+ ? { ...base, type: 'step-start' }
109
+ : { ...base, type: 'step-finish', reason: 'stop', cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } };
110
+ case 'compaction': return {
111
+ ...base, type: 'compaction', auto: b.trigger === 'auto' || b.trigger === 'overflow',
112
+ ...(b.trigger === 'overflow' && { overflow: true }), ...(b.first_kept_message_id !== null && { tail_start_id: b.first_kept_message_id }),
113
+ };
114
+ case 'harness':
115
+ if (c.fromV1 && (b.harness === 'opencode' || b.harness === 'pi'))
116
+ return b.data;
117
+ return b.model_visible && b.fallback_text !== undefined ? { ...base, type: 'text', text: b.fallback_text, synthetic: true } : undefined;
118
+ case 'subtask': return undefined;
119
+ }
120
+ }
121
+ function errorV1(e) {
122
+ if (!isTurnErrorCode(e.code))
123
+ return { name: e.code, data: { message: e.message } };
124
+ return { name: e.code === 'aborted' ? 'MessageAbortedError' : 'UnknownError', code: e.code, data: { message: e.message } };
125
+ }
126
+ function assistantInfo(m, c, over = {}) {
127
+ const u = m.usage;
128
+ const finish = m.finish === null ? undefined : FINISH_V1.get(m.finish);
129
+ return {
130
+ id: m.message_id, ...(c.sessionID !== null && { sessionID: c.sessionID }), role: 'assistant',
131
+ time: { created: c.created, ...(m.completed_at !== null && { completed: msOf(m.completed_at) }) },
132
+ ...(m.reply_to && { parentID: m.reply_to }),
133
+ modelID: m.model?.model ?? '', providerID: m.model?.provider ?? '', ...(m.model?.variant !== undefined && { variant: m.model.variant }),
134
+ mode: m.agent ?? '', agent: m.agent ?? '', path: { cwd: '', root: '' },
135
+ cost: u?.cost ?? 0, tokens: { input: u?.input ?? 0, output: u?.output ?? 0, reasoning: u?.reasoning ?? 0, cache: { read: u?.cache_read ?? 0, write: u?.cache_write ?? 0 } },
136
+ ...(finish && { finish }), ...(m.error && { error: errorV1(m.error) }),
137
+ ...over,
138
+ };
139
+ }
140
+ function infoOf(m, c) {
141
+ if (m.role === 'assistant')
142
+ return assistantInfo(m, c);
143
+ return {
144
+ id: m.message_id, ...(c.sessionID !== null && { sessionID: c.sessionID }), role: 'user', time: { created: c.created },
145
+ agent: m.agent ?? '', model: { providerID: m.model?.provider ?? '', modelID: m.model?.model ?? '', ...(m.model?.variant !== undefined && { variant: m.model.variant }) },
146
+ };
147
+ }
148
+ function summaryOf(m, cb, c) {
149
+ const id = `${m.message_id}-summary`;
150
+ const info = assistantInfo({ ...m, reply_to: m.message_id, agent: 'compaction', finish: m.status === 'complete' ? 'stop' : null }, c, { id, summary: true });
151
+ return { info, parts: [{ id: `${id}-p0`, ...(c.sessionID !== null && { sessionID: c.sessionID }), messageID: id, type: 'text', text: cb.summary }] };
152
+ }
153
+ function rowsOf(m, threadNative, v1Thread) {
154
+ const v1 = v1Thread || m.producer.harness === IMPORT;
155
+ if (!v1 && (m.role === 'system' || m.kind === 'context'))
156
+ return [];
157
+ const d = v1Ext(m.ext)?.data;
158
+ const sessionID = d && 'runtime_session_id' in d ? d.runtime_session_id ?? null : threadNative;
159
+ const c = { sessionID, messageID: m.message_id, created: msOf(m.created_at), fromV1: v1 };
160
+ const parts = d && 'parts' in d
161
+ ? d.parts
162
+ : m.blocks.flatMap((b) => {
163
+ const p = partOf(b, c);
164
+ return p === undefined ? [] : [apply(p, v1Ext(b.ext)?.data)];
165
+ });
166
+ const rows = [{ runtime_session_id: sessionID, info: apply(infoOf(m, c), d?.info), parts }];
167
+ const cb = m.blocks.find((b) => b.type === 'compaction');
168
+ if (m.kind === 'compaction' && cb && cb.summary !== null) {
169
+ const s = apply(summaryOf(m, cb, c), d?.summary);
170
+ rows.push({ runtime_session_id: sessionID, info: s.info, parts: s.parts });
171
+ }
172
+ return rows;
173
+ }
174
+ const created = (r) => num(isObj(r.info) && isObj(r.info.time) ? r.info.time.created : undefined) ?? -Infinity;
175
+ const idOf = (r) => String(isObj(r.info) ? r.info.id : '');
176
+ const rowOrder = (a, b) => created(a) - created(b) || (idOf(a) < idOf(b) ? -1 : idOf(a) > idOf(b) ? 1 : 0);
177
+ const threadNativeOf = (t) => {
178
+ const e = v1Ext(t.ext);
179
+ return e ? e.native_id ?? null : t.thread_id;
180
+ };
181
+ export function toV1(input) {
182
+ const log = structuredClone(input);
183
+ const root = log.threads.find((t) => t.parent_thread_id === null) ?? log.threads[0];
184
+ const rows = log.threads.flatMap((t) => t.messages.flatMap((m) => rowsOf(m, threadNativeOf(t), v1Ext(t.ext) !== undefined)));
185
+ const stated = v1Ext(log.ext)?.data;
186
+ const rootId = stated && 'root_id' in stated ? stated.root_id ?? null : root ? threadNativeOf(root) : null;
187
+ return { session_id: log.session_id, root_id: rootId, rows: rows.sort(rowOrder) };
188
+ }
189
+ function modelOf(info) {
190
+ const src = info.role === 'assistant' ? { p: info.providerID, m: info.modelID, v: info.variant } : isObj(info.model) ? { p: info.model.providerID, m: info.model.modelID, v: info.model.variant } : {};
191
+ const provider = str(src.p);
192
+ const model = str(src.m);
193
+ return provider === undefined || model === undefined ? null : { provider, model, ...(str(src.v) !== undefined && { variant: src.v }) };
194
+ }
195
+ function usageOf(info) {
196
+ if (!isObj(info.tokens))
197
+ return null;
198
+ const t = info.tokens;
199
+ const cache = isObj(t.cache) ? t.cache : {};
200
+ return {
201
+ input: num(t.input) ?? 0, output: num(t.output) ?? 0, cache_read: num(cache.read) ?? 0, cache_write: num(cache.write) ?? 0,
202
+ ...(num(t.reasoning) !== undefined && { reasoning: t.reasoning }), ...(num(info.cost) !== undefined && { cost: info.cost }),
203
+ };
204
+ }
205
+ const errorOf = (e) => ({ code: str(e.code) || str(e.name) || 'UnknownError', message: str(isObj(e.data) ? e.data.message : undefined) ?? '' });
206
+ function attachment(p, c, lost) {
207
+ const r = c.resolve(p);
208
+ if (!r)
209
+ lost.attachment++;
210
+ return {
211
+ mime: str(p.mime) ?? 'application/octet-stream',
212
+ ...(r && { ref: r.ref, bytes: r.bytes, ...(r.sha256 !== undefined && { sha256: r.sha256 }) }),
213
+ ...(str(p.url) !== undefined && { url: p.url }),
214
+ };
215
+ }
216
+ function toolBlock(p, id, c, lost) {
217
+ const st = p.state;
218
+ const time = isObj(st.time) ? st.time : {};
219
+ if (!isObj(st.input))
220
+ lost.tool_input++;
221
+ const exit = isObj(st.metadata) && num(st.metadata.exit) !== undefined ? { exit_code: st.metadata.exit } : {};
222
+ let status;
223
+ let result;
224
+ let input = st.input ?? null;
225
+ if (st.status === 'completed') {
226
+ const atts = (Array.isArray(st.attachments) ? st.attachments : []).filter(isObj).map((a) => {
227
+ const { bytes: _bytes, ...content } = attachment(a, c, lost);
228
+ return { type: 'attachment', ...content };
229
+ });
230
+ const cleared = iso(time.compacted);
231
+ status = 'complete';
232
+ result = {
233
+ content: [...(str(st.output) !== undefined ? [{ type: 'text', text: st.output }] : []), ...atts], is_error: false, ...exit,
234
+ ...(cleared && { model_content: [{ type: 'text', text: CLEARED }], cleared_at: cleared }),
235
+ };
236
+ }
237
+ else if (st.status === 'error') {
238
+ const error = str(st.error);
239
+ status = 'error';
240
+ result = { content: error !== undefined ? [{ type: 'text', text: error }] : [], is_error: true, ...(error !== undefined && { error: { message: error } }), ...exit };
241
+ }
242
+ else if (c.live) {
243
+ status = st.status === 'pending' ? 'pending' : 'running';
244
+ if (status === 'pending' && str(st.raw) && !(isObj(st.input) && Object.keys(st.input).length))
245
+ input = st.raw;
246
+ }
247
+ else {
248
+ const text = INTERRUPTED[c.h];
249
+ status = 'error';
250
+ result = { content: [{ type: 'text', text }], is_error: true, synthetic: true, error: { code: 'interrupted', message: text } };
251
+ }
252
+ const started = iso(time.start);
253
+ const ended = iso(time.end);
254
+ return {
255
+ type: 'tool_call', id, call_id: p.callID, name: p.tool, kind: kindOf(p.tool), input, status, ...(result && { result }),
256
+ ...(str(st.title) && { title: st.title }), ...(started && { started_at: started }), ...(ended && { ended_at: ended }),
257
+ };
258
+ }
259
+ function blockOf(p, id, marker, summary, c, lost) {
260
+ const harness = (kind, model_visible = false) => ({
261
+ type: 'harness', id, harness: c.h, kind, data: p, model_visible, ...(model_visible && { fallback_text: SUBTASK_TEXT }),
262
+ });
263
+ if (!isObj(p))
264
+ return harness('unknown');
265
+ switch (p.type) {
266
+ case 'text':
267
+ return typeof p.text !== 'string' || p.ignored === true ? harness('text') : { type: 'text', id, text: p.text, ...(typeof p.synthetic === 'boolean' && { synthetic: p.synthetic }) };
268
+ case 'reasoning':
269
+ return typeof p.text !== 'string' ? harness('reasoning') : { type: 'reasoning', id, text: p.text };
270
+ case 'file': {
271
+ if (p.mime === 'text/plain' || p.mime === 'application/x-directory')
272
+ return harness('file');
273
+ return { type: 'attachment', id, ...attachment(p, c, lost), ...(str(p.filename) !== undefined && { name: p.filename }) };
274
+ }
275
+ case 'tool':
276
+ return typeof p.callID === 'string' && typeof p.tool === 'string' && isObj(p.state) ? toolBlock(p, id, c, lost) : harness('tool');
277
+ case 'step-start':
278
+ case 'step-finish':
279
+ return { type: 'step', id, phase: p.type === 'step-start' ? 'start' : 'finish' };
280
+ case 'compaction': {
281
+ if (p !== marker)
282
+ return harness('compaction');
283
+ const trigger = p.overflow === true ? 'overflow' : p.auto === true ? 'auto' : 'manual';
284
+ return { type: 'compaction', id, summary, first_kept_message_id: str(p.tail_start_id) ?? null, trigger };
285
+ }
286
+ case 'subtask':
287
+ return harness('subtask', true);
288
+ default:
289
+ return harness(str(p.type) || 'unknown');
290
+ }
291
+ }
292
+ function blockIds(parts) {
293
+ const given = parts.map((p, i) => (isObj(p) && typeof p.id === 'string' && p.id ? p.id : `#${i}`));
294
+ const taken = new Set(given);
295
+ const seen = new Set();
296
+ return given.map((id) => {
297
+ if (!seen.has(id)) {
298
+ seen.add(id);
299
+ return id;
300
+ }
301
+ let n = 2;
302
+ while (taken.has(`${id}#${n}`))
303
+ n++;
304
+ taken.add(`${id}#${n}`);
305
+ return `${id}#${n}`;
306
+ });
307
+ }
308
+ function toMessage(row, sum, threadId, seq, superseded, c) {
309
+ const info = isObj(row.info) ? row.info : {};
310
+ const parts = Array.isArray(row.parts) ? row.parts : [];
311
+ const marker = info.role === 'user' ? parts.find((p) => isObj(p) && p.type === 'compaction') : undefined;
312
+ const sInfo = sum && isObj(sum.info) ? sum.info : undefined;
313
+ const role = info.role === 'assistant' || info.role === 'user' ? info.role : 'system';
314
+ const src = marker ? sInfo : role === 'assistant' ? info : undefined;
315
+ const err = src && isObj(src.error) ? src.error : undefined;
316
+ const aborted = err?.name === 'MessageAbortedError';
317
+ const completedAt = src && isObj(src.time) ? iso(src.time.completed) : null;
318
+ const open = c.live ? 'streaming' : 'interrupted';
319
+ const settled = !src ? (marker ? open : 'complete') : err ? (aborted ? 'aborted' : 'error') : completedAt ? 'complete' : open;
320
+ const createdAt = iso(isObj(info.time) ? info.time.created : undefined) ?? '';
321
+ const summary = sum ? (Array.isArray(sum.parts) ? sum.parts : []).flatMap((p) => (isObj(p) && p.type === 'text' && typeof p.text === 'string' ? [p.text] : [])).join('') : null;
322
+ const lost = { tool_input: 0, cut_point: 0, attachment: 0 };
323
+ const ids = blockIds(parts);
324
+ const blocks = parts.map((p, i) => blockOf(p, ids[i], marker, summary, c, lost));
325
+ const visible = blocks.some((b) => b.type === 'text' || b.type === 'attachment' || b.type === 'tool_call' || (b.type === 'harness' && b.model_visible));
326
+ const inContext = !superseded && role !== 'system' && (marker ? settled === 'complete' : !err || (aborted && visible));
327
+ const status = c.live && inContext && settled !== 'streaming' && blocks.some((b) => b.type === 'tool_call' && (b.status === 'pending' || b.status === 'running')) ? 'streaming' : settled;
328
+ const hidden = superseded ? 'superseded' : inContext || !err ? undefined : aborted ? 'aborted' : 'failed_attempt';
329
+ if (marker && isObj(marker) && str(marker.tail_start_id) === undefined)
330
+ lost.cut_point++;
331
+ const m = {
332
+ ...V, message_id: str(info.id) ?? '', thread_id: threadId, seq, role, kind: marker ? 'compaction' : 'turn', status, in_context: inContext,
333
+ ...(hidden && { hidden_reason: hidden }),
334
+ agent: str(info.agent) ?? null,
335
+ ...(role === 'assistant' && { reply_to: str(info.parentID) ?? null }),
336
+ model: modelOf(info), usage: src ? usageOf(src) : null,
337
+ finish: !src || marker ? null : aborted ? 'aborted' : err ? 'error' : FINISH_V2.get(info.finish) ?? null,
338
+ error: err ? errorOf(err) : null,
339
+ created_at: createdAt, completed_at: src || marker ? completedAt : createdAt || null,
340
+ producer: PRODUCER, blocks,
341
+ };
342
+ return { m, lost };
343
+ }
344
+ function keepResidual(m, row, sum, threadNative, h) {
345
+ const data = {};
346
+ if (row.runtime_session_id !== threadNative)
347
+ data.runtime_session_id = row.runtime_session_id;
348
+ const c = { sessionID: row.runtime_session_id, messageID: m.message_id, created: msOf(m.created_at), fromV1: true };
349
+ if (Array.isArray(row.parts)) {
350
+ m.blocks.forEach((b, i) => {
351
+ if (b.type === 'harness' || b.type === 'subtask')
352
+ return;
353
+ const change = diff(row.parts[i], partOf(b, c));
354
+ if (change)
355
+ b.ext = extOf(h, { data: change });
356
+ });
357
+ }
358
+ else
359
+ data.parts = row.parts;
360
+ const info = diff(row.info, infoOf(m, c));
361
+ if (info)
362
+ data.info = info;
363
+ const cb = m.blocks.find((b) => b.type === 'compaction');
364
+ if (sum && cb) {
365
+ const change = diff({ info: sum.info, parts: sum.parts }, summaryOf(m, cb, c));
366
+ if (change)
367
+ data.summary = change;
368
+ }
369
+ if (Object.keys(data).length)
370
+ m.ext = extOf(h, { data });
371
+ }
372
+ function spawnerOf(rows, id, text) {
373
+ if (!id)
374
+ return undefined;
375
+ for (const exact of [true, false]) {
376
+ for (const row of rows) {
377
+ if (row.runtime_session_id === id || !Array.isArray(row.parts))
378
+ continue;
379
+ for (const p of row.parts) {
380
+ if (!isObj(p) || p.type !== 'tool' || !isObj(p.state))
381
+ continue;
382
+ if (exact ? isObj(p.state.metadata) && p.state.metadata.sessionId === id : text(p.state).includes(id))
383
+ return { row, part: p };
384
+ }
385
+ }
386
+ }
387
+ return undefined;
388
+ }
389
+ export function fromV1(v1, options = {}) {
390
+ const c = { h: options.harness ?? 'opencode', resolve: options.resolveAttachment ?? (() => null), counts: { tool_input: 0, cut_point: 0, attachment: 0 }, live: options.live === true };
391
+ const rows = structuredClone(v1.rows).sort(rowOrder);
392
+ const groups = new Map();
393
+ for (const r of rows) {
394
+ const list = groups.get(r.runtime_session_id);
395
+ if (list)
396
+ list.push(r);
397
+ else
398
+ groups.set(r.runtime_session_id, [r]);
399
+ }
400
+ const stated = v1.root_id;
401
+ const usable = groups.has(stated);
402
+ const texts = new Map();
403
+ const text = (state) => texts.get(state) ?? (texts.set(state, JSON.stringify(state)), texts.get(state));
404
+ const spawners = new Map();
405
+ for (const id of groups.keys()) {
406
+ const s = usable && id === stated ? undefined : spawnerOf(rows, id, text);
407
+ if (s)
408
+ spawners.set(id, s);
409
+ }
410
+ const latest = [...rows].reverse().find((r) => r.runtime_session_id === null || !spawners.has(r.runtime_session_id));
411
+ const root = usable || !latest ? stated : latest.runtime_session_id;
412
+ if (!groups.has(root))
413
+ groups.set(root, []);
414
+ const rootThread = root || v1.session_id;
415
+ const threadOf = (id) => (id !== null && spawners.has(id) ? id : rootThread);
416
+ const sessionCreated = iso(rows.map(created).find(Number.isFinite)) ?? '';
417
+ const threads = [];
418
+ for (const [id, own] of groups) {
419
+ if (id !== root && (id === null || !spawners.has(id)))
420
+ continue;
421
+ const isRoot = id === root;
422
+ const superseded = isRoot ? rows.filter((r) => r.runtime_session_id !== root && threadOf(r.runtime_session_id) === rootThread) : [];
423
+ const list = [...superseded, ...own];
424
+ const old = new Set(superseded);
425
+ const summaries = new Map();
426
+ const paired = new Set();
427
+ for (const row of list) {
428
+ const info = isObj(row.info) ? row.info : {};
429
+ if (info.role !== 'user' || !Array.isArray(row.parts) || !row.parts.some((p) => isObj(p) && p.type === 'compaction'))
430
+ continue;
431
+ const sum = list.find((r) => !paired.has(r) && r.runtime_session_id === row.runtime_session_id && isObj(r.info) && r.info.role === 'assistant' && r.info.summary === true && r.info.parentID === info.id);
432
+ if (!sum)
433
+ continue;
434
+ summaries.set(row, sum);
435
+ paired.add(sum);
436
+ }
437
+ const messages = [];
438
+ for (const row of list) {
439
+ if (paired.has(row))
440
+ continue;
441
+ const sum = summaries.get(row);
442
+ const { m, lost } = toMessage(row, sum, isRoot ? rootThread : id, messages.length + 1, old.has(row), c);
443
+ keepResidual(m, row, sum, id, c.h);
444
+ if (m.in_context)
445
+ for (const k of ['tool_input', 'cut_point', 'attachment'])
446
+ c.counts[k] += lost[k];
447
+ messages.push(m);
448
+ }
449
+ const spawn = id === null ? undefined : spawners.get(id);
450
+ const firstUser = messages.find((m) => m.role === 'user' && m.kind === 'turn');
451
+ threads.push({
452
+ ...V, thread_id: isRoot ? rootThread : id,
453
+ parent_thread_id: spawn ? threadOf(spawn.row.runtime_session_id) : null,
454
+ spawned_by: spawn && typeof spawn.part.callID === 'string' ? { message_id: str(spawn.row.info?.id) ?? '', call_id: spawn.part.callID } : null,
455
+ agent: firstUser?.agent ?? null, title: null, created_at: messages[0]?.created_at || sessionCreated, messages,
456
+ ext: extOf(c.h, id === null ? {} : { native_id: id }),
457
+ });
458
+ }
459
+ threads.sort((a, b) => (a.parent_thread_id === null ? -1 : b.parent_thread_id === null ? 1 : rowsIndex(a) - rowsIndex(b)));
460
+ const lastUser = threads[0]?.messages.filter((m) => m.role === 'user' && m.kind === 'turn').at(-1);
461
+ const partial = c.counts.tool_input + c.counts.cut_point + c.counts.attachment > 0;
462
+ return {
463
+ ...V, session_id: v1.session_id, title: null, created_at: sessionCreated,
464
+ restore_grade: partial ? 'partial' : 'converted', grade_counts: c.counts,
465
+ harness: { current: c.h, history: [] },
466
+ selection: { agent: lastUser?.agent ?? null, model: lastUser?.model ?? null },
467
+ todos: [], pending: { questions: [], permissions: [] }, threads,
468
+ ...(root !== stated && { ext: extOf(c.h, { data: { root_id: stated } }) }),
469
+ };
470
+ function rowsIndex(t) {
471
+ return rows.findIndex((r) => r.runtime_session_id === t.thread_id);
472
+ }
473
+ }
@@ -0,0 +1,19 @@
1
+ import type { SessionTranscriptSyncEnvelope } from '../rest/projects-client/sessions.js';
2
+ export interface StoredCopy<T = SessionTranscriptSyncEnvelope> {
3
+ v: number;
4
+ user: string;
5
+ at: number;
6
+ e: T;
7
+ }
8
+ export interface CopyCodec<T> {
9
+ paintable(value: T | null | undefined): value is T;
10
+ fit(stored: StoredCopy<T>, maxChars: number): string | null;
11
+ capturedAt(value: T): number;
12
+ }
13
+ export interface CopyStore<T> {
14
+ keyFor(projectId: string, sessionId: string): string;
15
+ read(projectId: string, sessionId: string): T | null | Promise<T | null>;
16
+ write(projectId: string, sessionId: string, value: T): Promise<void>;
17
+ remove(projectId: string, sessionId: string): Promise<void>;
18
+ clear(): Promise<void>;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,4 @@
1
+ import { registerSessionLogCopies, SESSION_LOG_COPY } from '../session-log/saved-log.js';
1
2
  export function isPaintableSavedCopy(envelope) {
2
3
  return (!!envelope &&
3
4
  envelope.available === true &&
@@ -58,7 +59,27 @@ function capturedAtMs(envelope) {
58
59
  const parsed = envelope.captured_at ? Date.parse(envelope.captured_at) : Number.NaN;
59
60
  return Number.isFinite(parsed) ? parsed : 0;
60
61
  }
62
+ const ENVELOPE = {
63
+ paintable: isPaintableSavedCopy,
64
+ fit: fitSavedCopy,
65
+ capturedAt: capturedAtMs,
66
+ };
61
67
  export function createSavedCopyStore(options) {
68
+ const v1 = createCopyStore(options, ENVELOPE);
69
+ const v2 = createCopyStore({ ...options, namespace: `${options.namespace ?? DEFAULT_NAMESPACE}.log` }, SESSION_LOG_COPY);
70
+ const store = {
71
+ ...v1,
72
+ remove: async (projectId, sessionId) => {
73
+ await Promise.all([v1.remove(projectId, sessionId), v2.remove(projectId, sessionId)]);
74
+ },
75
+ clear: async () => {
76
+ await Promise.all([v1.clear(), v2.clear()]);
77
+ },
78
+ };
79
+ registerSessionLogCopies(store, v2, v1.remove);
80
+ return store;
81
+ }
82
+ function createCopyStore(options, codec) {
62
83
  const { storage, userId, maxSessions = DEFAULT_MAX_SESSIONS, maxBytes = DEFAULT_MAX_BYTES, maxEnvelopeBytes = DEFAULT_MAX_ENVELOPE_BYTES, maxAgeMs = DEFAULT_MAX_AGE_MS, namespace = DEFAULT_NAMESPACE, now = () => Date.now(), } = options;
63
84
  const prefix = `${namespace}:${userId}:`;
64
85
  const indexKey = `${prefix}index`;
@@ -163,7 +184,7 @@ export function createSavedCopyStore(options) {
163
184
  stored.v !== VERSION ||
164
185
  stored.user !== userId ||
165
186
  now() - stored.at > maxAgeMs ||
166
- !isPaintableSavedCopy(stored.e)) {
187
+ !codec.paintable(stored.e)) {
167
188
  removeNow(keyFor(projectId, sessionId));
168
189
  void forgetScope(scope);
169
190
  return null;
@@ -174,13 +195,13 @@ export function createSavedCopyStore(options) {
174
195
  },
175
196
  write(projectId, sessionId, envelope) {
176
197
  const scope = `${projectId}/${sessionId}`;
177
- if (!isPaintableSavedCopy(envelope))
198
+ if (!codec.paintable(envelope))
178
199
  return forgetScope(scope);
179
200
  const stored = { v: VERSION, user: userId, at: now(), e: envelope };
180
- const text = fitSavedCopy(stored, maxEnvelopeBytes);
201
+ const text = codec.fit(stored, maxEnvelopeBytes);
181
202
  if (text === null)
182
203
  return forgetScope(scope);
183
- const captured = capturedAtMs(envelope);
204
+ const captured = codec.capturedAt(envelope);
184
205
  return enqueue(async () => {
185
206
  const entries = await loadIndex();
186
207
  const existing = entries.find((entry) => entry.s === scope);
@@ -1,4 +1,18 @@
1
- import type { SessionTranscriptSyncEnvelope } from '../rest/projects-client/sessions.js';
1
+ import { type SessionTranscriptSyncEnvelope } from '../rest/projects-client/sessions.js';
2
+ export declare const SAVED_HISTORY_SNAPSHOT_WAIT_MS = 2000;
3
+ export declare function loadSavedTranscript(projectId: string, sessionId: string, options?: {
4
+ signal?: AbortSignal;
5
+ snapshotWaitMs?: number;
6
+ }): Promise<SessionTranscriptSyncEnvelope>;
7
+ export interface SavedSessionCopy {
8
+ envelope: SessionTranscriptSyncEnvelope | null;
9
+ empty: boolean;
10
+ }
11
+ export declare function readSavedSessionCopy(input: {
12
+ projectId: string;
13
+ sessionId: string;
14
+ runtimeSessionId: string;
15
+ }): Promise<SavedSessionCopy>;
2
16
  export type SavedTranscript = 'loading' | 'shown' | 'none';
3
17
  export interface SavedTranscriptInput {
4
18
  enabled: boolean;
@@ -1,3 +1,58 @@
1
+ import { getSessionTranscriptSync, getSessionTurn, } from '../rest/projects-client/sessions.js';
2
+ import { OPEN_BUNDLE_TRANSCRIPT_LIMIT, claimOpenBundle, heldSavedWindow, holdSavedWindow, openBundleHistory, } from '../session/open-bundle.js';
3
+ export const SAVED_HISTORY_SNAPSHOT_WAIT_MS = 2_000;
4
+ export async function loadSavedTranscript(projectId, sessionId, options = {}) {
5
+ return heldSavedWindow(projectId, sessionId) ?? readSavedWindow(projectId, sessionId, options);
6
+ }
7
+ async function readSavedWindow(projectId, sessionId, options) {
8
+ const claimed = claimOpenBundle(projectId, sessionId);
9
+ let timer;
10
+ const bundle = claimed
11
+ ? await Promise.race([
12
+ claimed,
13
+ new Promise((resolve) => {
14
+ timer = setTimeout(resolve, options.snapshotWaitMs ?? SAVED_HISTORY_SNAPSHOT_WAIT_MS, null);
15
+ }),
16
+ ]).finally(() => clearTimeout(timer))
17
+ : null;
18
+ return ((bundle && openBundleHistory(bundle)) ??
19
+ getSessionTranscriptSync(projectId, sessionId, {
20
+ limit: OPEN_BUNDLE_TRANSCRIPT_LIMIT,
21
+ history: true,
22
+ signal: options.signal,
23
+ }));
24
+ }
25
+ export async function readSavedSessionCopy(input) {
26
+ const { projectId, sessionId, runtimeSessionId } = input;
27
+ if (!projectId || !sessionId || !runtimeSessionId)
28
+ return { envelope: null, empty: false };
29
+ let envelope;
30
+ try {
31
+ envelope = await readSavedWindow(projectId, sessionId, {});
32
+ }
33
+ catch {
34
+ return { envelope: null, empty: false };
35
+ }
36
+ holdSavedWindow(projectId, sessionId, envelope);
37
+ if (savedCopyEmptyRoot(envelope) !== runtimeSessionId)
38
+ return { envelope, empty: false };
39
+ try {
40
+ const turn = await getSessionTurn(projectId, sessionId);
41
+ return {
42
+ envelope,
43
+ empty: isEmptyConversation({
44
+ savedEmptyRoot: runtimeSessionId,
45
+ rootSessionId: runtimeSessionId,
46
+ turnRead: true,
47
+ hasEndedTurn: turn.last_ended != null,
48
+ hasOpenOrQueuedTurn: turn.turns.length > 0,
49
+ }),
50
+ };
51
+ }
52
+ catch {
53
+ return { envelope, empty: false };
54
+ }
55
+ }
1
56
  export function resolveSavedTranscript(input) {
2
57
  if (input.hasMessages)
3
58
  return 'shown';
@@ -132,6 +132,9 @@ export class SessionSyncController {
132
132
  reconcile(reason = 'manual') {
133
133
  if (this.destroyed)
134
134
  return Promise.resolve();
135
+ if (reason === 'visible' && this.streamReliable && this.snapshot.freshness === 'fresh') {
136
+ return Promise.resolve();
137
+ }
135
138
  if (this.tailRequest)
136
139
  return this.tailRequest;
137
140
  this.update({
@@ -1,4 +1,4 @@
1
- import { getSupabaseAccessToken, invalidateTokenCache } from '../http/auth.js';
1
+ import { getAuthToken as getSupabaseAccessToken, invalidateTokenCache } from '../http/auth.js';
2
2
  import { getClientForUrl } from '../runtime/client.js';
3
3
  import { isAuthFailure } from '../http/api/errors.js';
4
4
  import { logger } from '../http/logger.js';
@@ -222,8 +222,8 @@ function createLiveStream(client, opts, subscribers) {
222
222
  schedule();
223
223
  if (t.now() - yieldedAt < YIELD_INTERVAL_MS)
224
224
  continue;
225
- yieldedAt = t.now();
226
225
  await new Promise((resolve) => t.setTimeout(resolve, 0));
226
+ yieldedAt = t.now();
227
227
  }
228
228
  stableConnection = streamHadWork;
229
229
  }