@opengeni/contracts 0.44.1 → 1.0.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 (382) hide show
  1. package/dist/agent-authored-durable-text.d.ts +70 -0
  2. package/dist/agent-config.d.ts +869 -0
  3. package/dist/agent-instruction-changes.d.ts +110 -0
  4. package/dist/agent-learning.d.ts +243 -0
  5. package/dist/agent-topology.d.ts +292 -0
  6. package/dist/allowance-refusal.d.ts +9 -0
  7. package/dist/allowance-refusal.js +50 -0
  8. package/dist/allowance-refusal.js.map +1 -0
  9. package/dist/analytics-consent-report.d.ts +19 -0
  10. package/dist/analytics-consent-report.js +10 -0
  11. package/dist/analytics-consent-report.js.map +1 -0
  12. package/dist/api-key-presets.d.ts +19 -0
  13. package/dist/artifact-catalog.d.ts +161 -0
  14. package/dist/artifacts.d.ts +135 -11
  15. package/dist/atlassian-native-retirement.d.ts +16 -0
  16. package/dist/atlassian-native-retirement.js +30 -0
  17. package/dist/atlassian-native-retirement.js.map +1 -0
  18. package/dist/atlassian.d.ts +2 -0
  19. package/dist/atlassian.js +27 -11
  20. package/dist/browser-storage.d.ts +5 -0
  21. package/dist/browser-storage.js +9 -0
  22. package/dist/browser-storage.js.map +1 -0
  23. package/dist/bundled-skills.d.ts +40 -0
  24. package/dist/canonical-human-identities.d.ts +157 -0
  25. package/dist/canonical-human-identities.js +23 -0
  26. package/dist/canonical-human-identities.js.map +1 -0
  27. package/dist/chunk-2ACYHZFB.js +292 -0
  28. package/dist/chunk-2ACYHZFB.js.map +1 -0
  29. package/dist/chunk-2VORLN2R.js +353 -0
  30. package/dist/chunk-2VORLN2R.js.map +1 -0
  31. package/dist/{chunk-Y3MLSLB4.js → chunk-3SIZGL3O.js} +42 -30
  32. package/dist/chunk-3SIZGL3O.js.map +1 -0
  33. package/dist/chunk-46KLD2UH.js +186 -0
  34. package/dist/chunk-46KLD2UH.js.map +1 -0
  35. package/dist/chunk-4JGHSSB7.js +11 -0
  36. package/dist/chunk-4JGHSSB7.js.map +1 -0
  37. package/dist/{chunk-52SGB2QO.js → chunk-5IXYF6XN.js} +50 -10
  38. package/dist/chunk-5IXYF6XN.js.map +1 -0
  39. package/dist/chunk-BCS7WHCK.js +139 -0
  40. package/dist/chunk-BCS7WHCK.js.map +1 -0
  41. package/dist/chunk-EK7NDGFR.js +128 -0
  42. package/dist/chunk-EK7NDGFR.js.map +1 -0
  43. package/dist/{chunk-5UDNP7AO.js → chunk-FZ6BXQHL.js} +78 -38
  44. package/dist/chunk-FZ6BXQHL.js.map +1 -0
  45. package/dist/chunk-GRG7PHWQ.js +25 -0
  46. package/dist/chunk-GRG7PHWQ.js.map +1 -0
  47. package/dist/chunk-JUFK3T4Q.js +80 -0
  48. package/dist/chunk-JUFK3T4Q.js.map +1 -0
  49. package/dist/chunk-KHHZTBP7.js +11 -0
  50. package/dist/chunk-KHHZTBP7.js.map +1 -0
  51. package/dist/chunk-KR2V42UZ.js +187 -0
  52. package/dist/chunk-KR2V42UZ.js.map +1 -0
  53. package/dist/chunk-MZLBPKFE.js +168 -0
  54. package/dist/chunk-MZLBPKFE.js.map +1 -0
  55. package/dist/{chunk-H5BVCZKF.js → chunk-NRMCVHAU.js} +6 -5
  56. package/dist/chunk-NRMCVHAU.js.map +1 -0
  57. package/dist/{chunk-TZ6YEYN4.js → chunk-NVJMJWLL.js} +3 -1
  58. package/dist/chunk-NVJMJWLL.js.map +1 -0
  59. package/dist/chunk-O77VSFYZ.js +70 -0
  60. package/dist/chunk-O77VSFYZ.js.map +1 -0
  61. package/dist/chunk-PX3VCKAU.js +29 -0
  62. package/dist/chunk-PX3VCKAU.js.map +1 -0
  63. package/dist/chunk-QR5D2IQC.js +61 -0
  64. package/dist/chunk-QR5D2IQC.js.map +1 -0
  65. package/dist/chunk-RJ76DRPO.js +117 -0
  66. package/dist/chunk-RJ76DRPO.js.map +1 -0
  67. package/dist/chunk-RKX3NQYS.js +28 -0
  68. package/dist/chunk-RKX3NQYS.js.map +1 -0
  69. package/dist/{chunk-FJBG5E4O.js → chunk-SIOU2K4U.js} +45 -22
  70. package/dist/chunk-SIOU2K4U.js.map +1 -0
  71. package/dist/chunk-TSCDAGET.js +75 -0
  72. package/dist/chunk-TSCDAGET.js.map +1 -0
  73. package/dist/{chunk-LSB7GRLG.js → chunk-TXTQIYCV.js} +15 -11
  74. package/dist/chunk-TXTQIYCV.js.map +1 -0
  75. package/dist/chunk-X7ETVO4P.js +26544 -0
  76. package/dist/chunk-X7ETVO4P.js.map +1 -0
  77. package/dist/{chunk-CADSX6UN.js → chunk-Y3KNXVTD.js} +2 -2
  78. package/dist/chunk-YBW5JSA3.js +251 -0
  79. package/dist/chunk-YBW5JSA3.js.map +1 -0
  80. package/dist/chunk-YRXAWWCU.js +45 -0
  81. package/dist/chunk-YRXAWWCU.js.map +1 -0
  82. package/dist/claude-provider-account-authority.d.ts +18 -0
  83. package/dist/claude-subscription-oauth.d.ts +42 -0
  84. package/dist/claude-subscription-usage.d.ts +159 -0
  85. package/dist/client-error-report.d.ts +109 -0
  86. package/dist/client-error-report.js +87 -0
  87. package/dist/client-error-report.js.map +1 -0
  88. package/dist/code-search.d.ts +52 -0
  89. package/dist/code-search.js +40 -0
  90. package/dist/code-search.js.map +1 -0
  91. package/dist/codex-provider-account-authority.d.ts +23 -0
  92. package/dist/codex-provider-account-authority.js +23 -0
  93. package/dist/codex-provider-account-authority.js.map +1 -0
  94. package/dist/company-brain-governed-writes.d.ts +475 -0
  95. package/dist/company-brain.d.ts +336 -0
  96. package/dist/company-profile.d.ts +566 -0
  97. package/dist/connect.d.ts +325 -0
  98. package/dist/connect.js +191 -0
  99. package/dist/connect.js.map +1 -0
  100. package/dist/connection-account-label.d.ts +2 -0
  101. package/dist/connection-account-label.js +35 -0
  102. package/dist/connection-account-label.js.map +1 -0
  103. package/dist/connection-authority.d.ts +193 -0
  104. package/dist/connection-authority.js +50 -0
  105. package/dist/connection-authority.js.map +1 -0
  106. package/dist/connector-attachments.d.ts +95 -0
  107. package/dist/connector-attachments.js +51 -0
  108. package/dist/connector-attachments.js.map +1 -0
  109. package/dist/connector-tool-permissions.d.ts +73 -0
  110. package/dist/direct-model-provider.d.ts +36 -0
  111. package/dist/editable-artifact-codec-registry.d.ts +16 -7
  112. package/dist/editable-artifact-codec-registry.js +19 -9
  113. package/dist/editable-artifact-committed-transaction.d.ts +2 -2
  114. package/dist/editable-artifact-committed-transaction.js +2 -2
  115. package/dist/editable-artifact-live.d.ts +11 -12
  116. package/dist/editable-artifact-live.js +110 -45
  117. package/dist/editable-artifact-live.js.map +1 -1
  118. package/dist/editable-artifact-serialized-commit.d.ts +2 -1
  119. package/dist/editable-artifact-serialized-commit.js +8 -7
  120. package/dist/editable-artifact-versions.d.ts +20 -5
  121. package/dist/editable-artifact-versions.js +23 -5
  122. package/dist/editable-artifacts.d.ts +2 -0
  123. package/dist/editable-artifacts.js +48 -36
  124. package/dist/external-identities.d.ts +784 -0
  125. package/dist/external-identities.js +44 -0
  126. package/dist/external-identities.js.map +1 -0
  127. package/dist/feedback.d.ts +51 -0
  128. package/dist/github-repository-contracts.d.ts +36 -0
  129. package/dist/github-repository-contracts.js +59 -0
  130. package/dist/github-repository-contracts.js.map +1 -0
  131. package/dist/github-repository.d.ts +14 -0
  132. package/dist/github-repository.js +36 -0
  133. package/dist/github-repository.js.map +1 -0
  134. package/dist/google-drive.d.ts +90 -1
  135. package/dist/google-drive.js +91 -15
  136. package/dist/google-drive.js.map +1 -1
  137. package/dist/governed-learning-activation.d.ts +154 -0
  138. package/dist/governed-learning-evaluator.d.ts +152 -0
  139. package/dist/host-mcp-bindings.d.ts +326 -0
  140. package/dist/host-mcp-bindings.js +252 -0
  141. package/dist/host-mcp-bindings.js.map +1 -0
  142. package/dist/image-generation.d.ts +1 -0
  143. package/dist/index.d.ts +27574 -5374
  144. package/dist/index.js +2531 -177
  145. package/dist/insights-usage.d.ts +877 -0
  146. package/dist/insights-usage.js +509 -0
  147. package/dist/insights-usage.js.map +1 -0
  148. package/dist/interaction.d.ts +1752 -13
  149. package/dist/knowledge-entries.d.ts +982 -0
  150. package/dist/knowledge-preparation.d.ts +237 -0
  151. package/dist/knowledge.d.ts +740 -0
  152. package/dist/managed-auth-session-sets.d.ts +241 -0
  153. package/dist/managed-auth-session-sets.js +191 -0
  154. package/dist/managed-auth-session-sets.js.map +1 -0
  155. package/dist/managed-sign-in-methods.d.ts +56 -0
  156. package/dist/managed-sign-in-methods.js +46 -0
  157. package/dist/managed-sign-in-methods.js.map +1 -0
  158. package/dist/mcp-catalog-limits.d.ts +2 -0
  159. package/dist/mcp-endpoint.d.ts +7 -0
  160. package/dist/mcp-endpoint.js +9 -0
  161. package/dist/mcp-endpoint.js.map +1 -0
  162. package/dist/mcp-oauth.d.ts +71 -0
  163. package/dist/modal-native-proof-v2.d.ts +587 -0
  164. package/dist/modal-native-proof-v2.js +37 -0
  165. package/dist/modal-native-proof-v2.js.map +1 -0
  166. package/dist/model-availability.d.ts +15 -0
  167. package/dist/model-connection-access.d.ts +24 -0
  168. package/dist/model-context-inspector.d.ts +329 -0
  169. package/dist/model-display.d.ts +38 -0
  170. package/dist/model-display.js +134 -0
  171. package/dist/model-display.js.map +1 -0
  172. package/dist/model-picker-order.d.ts +25 -0
  173. package/dist/model-picker-order.js +42 -0
  174. package/dist/model-picker-order.js.map +1 -0
  175. package/dist/organization-access.d.ts +231 -0
  176. package/dist/organization-access.js +34 -0
  177. package/dist/organization-access.js.map +1 -0
  178. package/dist/organization-integration-policy.d.ts +27 -0
  179. package/dist/organization-membership-lifecycle.d.ts +1022 -0
  180. package/dist/organization-model-usage.d.ts +146 -0
  181. package/dist/organization-model-usage.js +67 -0
  182. package/dist/organization-model-usage.js.map +1 -0
  183. package/dist/organization-recovery.d.ts +337 -0
  184. package/dist/organization-recovery.js +37 -0
  185. package/dist/organization-recovery.js.map +1 -0
  186. package/dist/organization-usage.d.ts +138 -0
  187. package/dist/permissions.d.ts +54 -0
  188. package/dist/personal-github.d.ts +320 -0
  189. package/dist/personal-github.js +222 -0
  190. package/dist/personal-github.js.map +1 -0
  191. package/dist/plugin-discovery.d.ts +35 -0
  192. package/dist/plugin-discovery.js +7 -0
  193. package/dist/plugin-discovery.js.map +1 -0
  194. package/dist/preference-registry.d.ts +44 -1
  195. package/dist/product-analytics.d.ts +113 -0
  196. package/dist/product-lifecycle-facts.d.ts +73 -0
  197. package/dist/remember.d.ts +350 -0
  198. package/dist/retained-output.d.ts +8 -0
  199. package/dist/sandbox-file-artifacts.d.ts +57 -0
  200. package/dist/sandbox-provider-command.d.ts +113 -0
  201. package/dist/sandbox-recovery.d.ts +146 -0
  202. package/dist/sandbox-snapshots.d.ts +39 -0
  203. package/dist/session-final-reply.d.ts +6 -0
  204. package/dist/session-final-reply.js +9 -0
  205. package/dist/session-final-reply.js.map +1 -0
  206. package/dist/session-goal-reports.d.ts +21 -0
  207. package/dist/session-history-import.d.ts +947 -0
  208. package/dist/session-list-entries.d.ts +4 -0
  209. package/dist/session-list-entries.js +51 -0
  210. package/dist/session-list-entries.js.map +1 -0
  211. package/dist/session-mcp-projections.d.ts +215 -0
  212. package/dist/session-message-search.d.ts +68 -0
  213. package/dist/session-titles.d.ts +68 -0
  214. package/dist/session-titles.js +25 -0
  215. package/dist/session-titles.js.map +1 -0
  216. package/dist/session-topology-primitives.d.ts +22 -0
  217. package/dist/signup-attribution.d.ts +39 -0
  218. package/dist/site-request-headers.test.d.ts +1 -0
  219. package/dist/site-session-http.d.ts +11 -0
  220. package/dist/site-session-http.js +11 -0
  221. package/dist/site-session-http.js.map +1 -0
  222. package/dist/skill-catalog-context.d.ts +7 -0
  223. package/dist/skill-files.d.ts +12 -0
  224. package/dist/skill-metadata.d.ts +9 -0
  225. package/dist/skill-use.d.ts +61 -0
  226. package/dist/skills.d.ts +185 -0
  227. package/dist/slack-bot-scopes.d.ts +46 -21
  228. package/dist/slack-bot-scopes.js +14 -3
  229. package/dist/slack-rest-mcp.d.ts +31 -0
  230. package/dist/slack-rest-mcp.js +19 -0
  231. package/dist/slack-rest-mcp.js.map +1 -0
  232. package/dist/slack-task-policy.d.ts +294 -0
  233. package/dist/spreadsheet-artifact-commands.d.ts +2 -3
  234. package/dist/spreadsheet-artifact-commands.js +4 -2
  235. package/dist/spreadsheet-artifact-query.js +3 -2
  236. package/dist/subscription-account-pools.d.ts +292 -0
  237. package/dist/task-notes.d.ts +169 -0
  238. package/dist/tool-catalog.d.ts +286 -0
  239. package/dist/tool-result-spill.d.ts +16 -0
  240. package/dist/usage-allowances.d.ts +285 -0
  241. package/dist/usage-allowances.js +49 -0
  242. package/dist/usage-allowances.js.map +1 -0
  243. package/dist/video-generation.d.ts +51 -0
  244. package/dist/video-generation.js +9 -1
  245. package/dist/work-claims.d.ts +411 -0
  246. package/dist/workspace-instruction-policies.d.ts +94 -0
  247. package/dist/workspace-integration-wire.d.ts +36 -0
  248. package/dist/workspace-integration-wire.js +23 -0
  249. package/dist/workspace-integration-wire.js.map +1 -0
  250. package/dist/workspace-integrations.d.ts +948 -0
  251. package/dist/workspace-learning-administration.d.ts +289 -0
  252. package/dist/workspace-learning-policy.d.ts +1 -1
  253. package/dist/workspace-state.d.ts +85 -416
  254. package/dist/xai-provider-account-authority.d.ts +26 -0
  255. package/dist/xai-provider-account-authority.js +9 -0
  256. package/dist/xai-provider-account-authority.js.map +1 -0
  257. package/package.json +146 -1
  258. package/src/agent-authored-durable-text.ts +116 -0
  259. package/src/agent-config.ts +1527 -0
  260. package/src/agent-instruction-changes.ts +98 -0
  261. package/src/agent-learning.ts +161 -0
  262. package/src/agent-topology.ts +69 -0
  263. package/src/allowance-refusal.ts +81 -0
  264. package/src/analytics-consent-report.ts +20 -0
  265. package/src/api-key-presets.ts +25 -0
  266. package/src/artifact-catalog.ts +60 -0
  267. package/src/artifacts.ts +146 -18
  268. package/src/atlassian-native-retirement.ts +33 -0
  269. package/src/browser-storage.ts +10 -0
  270. package/src/bundled-skills.ts +73 -0
  271. package/src/canonical-human-identities.ts +93 -0
  272. package/src/claude-provider-account-authority.ts +12 -0
  273. package/src/claude-subscription-oauth.ts +62 -0
  274. package/src/claude-subscription-usage.ts +59 -0
  275. package/src/client-error-report.ts +184 -0
  276. package/src/code-search.ts +105 -0
  277. package/src/codex-provider-account-authority.ts +37 -0
  278. package/src/company-brain-governed-writes.ts +356 -0
  279. package/src/company-brain.ts +201 -0
  280. package/src/company-profile.ts +167 -1
  281. package/src/connect.ts +202 -0
  282. package/src/connection-account-label.ts +46 -0
  283. package/src/connection-authority.ts +213 -0
  284. package/src/connector-attachments.ts +327 -0
  285. package/src/connector-tool-permissions.ts +60 -0
  286. package/src/direct-model-provider.ts +98 -0
  287. package/src/editable-artifact-codec-registry.ts +55 -17
  288. package/src/editable-artifact-committed-transaction.ts +44 -31
  289. package/src/editable-artifact-live.ts +123 -57
  290. package/src/editable-artifact-serialized-commit.ts +2 -1
  291. package/src/editable-artifact-versions.ts +26 -5
  292. package/src/editable-artifacts.ts +2 -0
  293. package/src/external-identities.ts +285 -0
  294. package/src/feedback.ts +39 -0
  295. package/src/github-repository-contracts.ts +85 -0
  296. package/src/github-repository.ts +59 -0
  297. package/src/google-drive.ts +76 -4
  298. package/src/governed-learning-activation.ts +191 -0
  299. package/src/governed-learning-evaluator.ts +176 -0
  300. package/src/host-mcp-bindings.ts +298 -0
  301. package/src/image-generation.ts +8 -2
  302. package/src/index.ts +8019 -1799
  303. package/src/insights-usage.ts +679 -0
  304. package/src/interaction.ts +1102 -6
  305. package/src/knowledge-entries.ts +375 -0
  306. package/src/knowledge-preparation.ts +50 -0
  307. package/src/knowledge.ts +335 -0
  308. package/src/managed-auth-session-sets.ts +253 -0
  309. package/src/managed-sign-in-methods.ts +37 -0
  310. package/src/mcp-catalog-limits.ts +2 -0
  311. package/src/mcp-endpoint.ts +30 -0
  312. package/src/mcp-oauth.ts +66 -0
  313. package/src/modal-native-proof-v2.ts +398 -0
  314. package/src/model-availability.ts +22 -0
  315. package/src/model-connection-access.ts +17 -0
  316. package/src/model-context-inspector.ts +139 -0
  317. package/src/model-display.ts +195 -0
  318. package/src/model-picker-order.ts +97 -0
  319. package/src/organization-access.ts +176 -0
  320. package/src/organization-integration-policy.ts +54 -0
  321. package/src/organization-membership-lifecycle.ts +559 -0
  322. package/src/organization-model-usage.ts +72 -0
  323. package/src/organization-recovery.ts +186 -0
  324. package/src/organization-usage.ts +79 -0
  325. package/src/permissions.ts +58 -0
  326. package/src/personal-github.ts +267 -0
  327. package/src/plugin-discovery.ts +33 -0
  328. package/src/preference-registry.ts +20 -1
  329. package/src/product-analytics.ts +253 -0
  330. package/src/product-lifecycle-facts.ts +121 -0
  331. package/src/remember.ts +243 -0
  332. package/src/retained-output.ts +20 -3
  333. package/src/sandbox-file-artifacts.ts +73 -0
  334. package/src/sandbox-provider-command.ts +82 -0
  335. package/src/sandbox-recovery.ts +116 -0
  336. package/src/sandbox-snapshots.ts +142 -0
  337. package/src/session-final-reply.ts +16 -0
  338. package/src/session-goal-reports.ts +55 -0
  339. package/src/session-history-import.ts +159 -0
  340. package/src/session-list-entries.ts +91 -0
  341. package/src/session-mcp-projections.ts +273 -0
  342. package/src/session-message-search.ts +54 -0
  343. package/src/session-titles.ts +467 -0
  344. package/src/session-topology-primitives.ts +21 -0
  345. package/src/signup-attribution.ts +81 -0
  346. package/src/site-request-headers.test.ts +28 -0
  347. package/src/site-session-http.ts +63 -0
  348. package/src/skill-catalog-context.ts +50 -0
  349. package/src/skill-files.ts +52 -0
  350. package/src/skill-metadata.ts +40 -0
  351. package/src/skill-use.ts +77 -0
  352. package/src/skills.ts +136 -0
  353. package/src/slack-bot-scopes.ts +106 -42
  354. package/src/slack-rest-mcp.ts +194 -0
  355. package/src/slack-task-policy.ts +215 -0
  356. package/src/spreadsheet-artifact-commands.ts +12 -13
  357. package/src/subscription-account-pools.ts +44 -0
  358. package/src/task-notes.ts +105 -0
  359. package/src/tool-catalog.ts +155 -0
  360. package/src/tool-result-spill.ts +35 -0
  361. package/src/usage-allowances.ts +187 -0
  362. package/src/video-generation.ts +43 -6
  363. package/src/work-claims.ts +266 -0
  364. package/src/workspace-instruction-policies.ts +31 -0
  365. package/src/workspace-integration-wire.ts +99 -0
  366. package/src/workspace-integrations.ts +640 -0
  367. package/src/workspace-learning-administration.ts +88 -0
  368. package/src/workspace-learning-policy.ts +4 -3
  369. package/src/workspace-state.ts +16 -126
  370. package/src/xai-provider-account-authority.ts +38 -0
  371. package/dist/chunk-52SGB2QO.js.map +0 -1
  372. package/dist/chunk-5UDNP7AO.js.map +0 -1
  373. package/dist/chunk-BVGIYW7D.js +0 -11
  374. package/dist/chunk-BVGIYW7D.js.map +0 -1
  375. package/dist/chunk-FJBG5E4O.js.map +0 -1
  376. package/dist/chunk-H5BVCZKF.js.map +0 -1
  377. package/dist/chunk-LSB7GRLG.js.map +0 -1
  378. package/dist/chunk-TZ6YEYN4.js.map +0 -1
  379. package/dist/chunk-VA22KRHS.js +0 -15177
  380. package/dist/chunk-VA22KRHS.js.map +0 -1
  381. package/dist/chunk-Y3MLSLB4.js.map +0 -1
  382. /package/dist/{chunk-CADSX6UN.js.map → chunk-Y3KNXVTD.js.map} +0 -0
@@ -0,0 +1,467 @@
1
+ /** Human-authored session titles keep the existing public API ceiling. */
2
+ export const SESSION_TITLE_MAX_CHARACTERS = 200;
3
+
4
+ /**
5
+ * Automatic titles are intentionally much shorter than the manual rename
6
+ * ceiling. This is measured in grapheme clusters so emoji and combining text
7
+ * are never split into malformed display strings.
8
+ */
9
+ export const AUTOMATIC_SESSION_TITLE_MAX_GRAPHEMES = 80;
10
+
11
+ /**
12
+ * Safe durable title used until semantic generation succeeds. It contains no
13
+ * user prompt bytes, so provider failure/offline operation cannot leak a
14
+ * credential or leave a raw prompt prefix in navigation surfaces.
15
+ */
16
+ export const AUTOMATIC_SESSION_TITLE_FALLBACK = "New conversation";
17
+
18
+ // Session creation accepts a body far larger than a navigation label. Bound
19
+ // the source before any replace/split/normalization so a persisted large prompt
20
+ // cannot amplify memory or CPU on every client render.
21
+ export const PROMPT_PREVIEW_SCAN_MAX_CODE_UNITS = 4_096;
22
+
23
+ const SESSION_ID_PATTERN =
24
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
25
+
26
+ let titleSegmenter: Intl.Segmenter | null | undefined;
27
+
28
+ const KNOWN_SENSITIVE_VALUE_PATTERNS = [
29
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/iu,
30
+ /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/iu,
31
+ /\b(?:sk-(?:proj-)?|gh[oprsu]_|github_pat_|glpat-|xox[baprs]-)[A-Za-z0-9_-]{8,}/iu,
32
+ /\bAKIA[0-9A-Z]{16}\b/u,
33
+ /\bAIza[0-9A-Za-z_-]{20,}\b/u,
34
+ /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/u,
35
+ /[?&](?:access_token|api_key|apikey|password|secret|token)=[^\s&#]+/iu,
36
+ ] as const;
37
+
38
+ const URI_SCHEME_CANDIDATE_PATTERN = /\b[a-z][a-z0-9+.-]*:\S+/giu;
39
+ const WINDOWS_DRIVE_PATH_PATTERN = /^[a-z]:[\\/](?![\\/])[^:]*$/iu;
40
+
41
+ const SCHEMELESS_HOST_CANDIDATE_PATTERN =
42
+ /\b(?:www\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?(?::\d{1,5})?(?:[/?#][^\s]*)?/giu;
43
+
44
+ const SCHEMELESS_LOCAL_NETWORK_PATTERNS = [
45
+ /\blocalhost(?:(?::\d{1,5})(?:[/?#][^\s]*)?|[/?#][^\s]*)/iu,
46
+ /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:(?::\d{1,5})(?:[/?#][^\s]*)?|[/?#][^\s]*)/u,
47
+ /\[(?=[0-9a-f:.]*:[0-9a-f:.]*\])[0-9a-f:.]+\](?:(?::\d{1,5})(?:[/?#][^\s]*)?|[/?#][^\s]*)/iu,
48
+ ] as const;
49
+
50
+ const FILE_LIKE_HOST_SUFFIXES = new Set([
51
+ "css",
52
+ "html",
53
+ "js",
54
+ "json",
55
+ "jsx",
56
+ "lock",
57
+ "md",
58
+ "sql",
59
+ "toml",
60
+ "ts",
61
+ "tsx",
62
+ "xml",
63
+ "yaml",
64
+ "yml",
65
+ ]);
66
+
67
+ // These authorities are established framework/namespace notation whose exact
68
+ // casing is meaningful. Keep this exception deliberately narrow: generic
69
+ // PascalCase or uppercase authorities remain host-like because an uppercase
70
+ // real domain (for example MICROSOFT.COM/Admin) must not bypass the URL gate.
71
+ const DOTTED_TECHNOLOGY_PATH_AUTHORITIES = new Set([
72
+ "ASP.NET",
73
+ "AWS.SDK",
74
+ "Microsoft.Extensions",
75
+ "System.IO",
76
+ ]);
77
+
78
+ const SECRET_ASSIGNMENT_CANDIDATE_PATTERN =
79
+ /(?:^|[^A-Za-z0-9])(?:(['"])([A-Za-z][A-Za-z0-9_. -]*)\1|([A-Za-z][A-Za-z0-9_.-]*))\s*[=:]\s*[^\s,;]+/gu;
80
+
81
+ const SECRET_LABEL_ASSIGNMENT_PATTERN =
82
+ /\b(?:api[ _-]?key|access[ _-]?token|auth[ _-]?token|credential|credentials|password|passwd|private[ _-]?key|secret|token)\b\s*[=:]\s*[^\s,;]+/iu;
83
+
84
+ const SENSITIVE_ASSIGNMENT_KEY_SUFFIXES = new Set([
85
+ "credential",
86
+ "credentials",
87
+ "password",
88
+ "passwd",
89
+ "secret",
90
+ "token",
91
+ ]);
92
+
93
+ const COMPACT_SENSITIVE_ASSIGNMENT_KEY_SUFFIXES = [
94
+ "apikey",
95
+ "accesskey",
96
+ "accesskeyid",
97
+ "accesstoken",
98
+ "authtoken",
99
+ "credential",
100
+ "credentials",
101
+ "password",
102
+ "passwd",
103
+ "privatekey",
104
+ "secret",
105
+ "secretkey",
106
+ "token",
107
+ ] as const;
108
+
109
+ const SENSITIVE_ASSIGNMENT_KEY_WORD_SUFFIXES = [
110
+ ["api", "key"],
111
+ ["access", "key"],
112
+ ["access", "key", "id"],
113
+ ["auth", "key"],
114
+ ["private", "key"],
115
+ ["secret", "key"],
116
+ ] as const;
117
+
118
+ function hasWordSuffix(words: readonly string[], suffix: readonly string[]): boolean {
119
+ if (words.length < suffix.length) return false;
120
+ const offset = words.length - suffix.length;
121
+ return suffix.every((word, index) => words[offset + index] === word);
122
+ }
123
+
124
+ function containsSensitiveAssignment(value: string): boolean {
125
+ for (const match of value.matchAll(SECRET_ASSIGNMENT_CANDIDATE_PATTERN)) {
126
+ const key = match[2] ?? match[3];
127
+ if (!key) continue;
128
+ const words = key
129
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
130
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
131
+ .toLowerCase()
132
+ .split(/[^a-z0-9]+/)
133
+ .filter(Boolean);
134
+ const last = words.at(-1);
135
+ if (last && SENSITIVE_ASSIGNMENT_KEY_SUFFIXES.has(last)) return true;
136
+ if (last && COMPACT_SENSITIVE_ASSIGNMENT_KEY_SUFFIXES.some((suffix) => last.endsWith(suffix))) {
137
+ return true;
138
+ }
139
+ if (SENSITIVE_ASSIGNMENT_KEY_WORD_SUFFIXES.some((suffix) => hasWordSuffix(words, suffix))) {
140
+ return true;
141
+ }
142
+ }
143
+ return false;
144
+ }
145
+
146
+ function containsUriScheme(value: string): boolean {
147
+ for (const match of value.matchAll(URI_SCHEME_CANDIDATE_PATTERN)) {
148
+ // A Windows drive path is the one scheme-shaped token accepted here. Keep
149
+ // the exception exact: one separator after the drive letter and no later
150
+ // colon, so x://host and a nested scheme remain rejected.
151
+ if (!WINDOWS_DRIVE_PATH_PATTERN.test(match[0])) return true;
152
+ }
153
+ return false;
154
+ }
155
+
156
+ function isDottedTechnologyPath(candidate: string): boolean {
157
+ if (/[?:#]/u.test(candidate)) return false;
158
+ const [authority, ...pathSegments] = candidate.split("/");
159
+ if (!authority || pathSegments.length === 0 || pathSegments.some((segment) => !segment)) {
160
+ return false;
161
+ }
162
+ if (
163
+ !DOTTED_TECHNOLOGY_PATH_AUTHORITIES.has(authority) ||
164
+ pathSegments.some((segment) => !/^[A-Z][A-Za-z0-9_-]*$/u.test(segment))
165
+ ) {
166
+ return false;
167
+ }
168
+ return true;
169
+ }
170
+
171
+ function containsSchemelessUrl(value: string): boolean {
172
+ if (SCHEMELESS_LOCAL_NETWORK_PATTERNS.some((pattern) => pattern.test(value))) return true;
173
+
174
+ for (const match of value.matchAll(SCHEMELESS_HOST_CANDIDATE_PATTERN)) {
175
+ const candidate = match[0];
176
+ if (candidate.toLowerCase().startsWith("www.")) return true;
177
+ if (!/[/?#]/u.test(candidate)) continue;
178
+
179
+ const authority = candidate.split(/[/?#]/u, 1)[0] ?? "";
180
+ const hostname = authority.replace(/:\d{1,5}$/u, "");
181
+ const suffix = hostname.split(".").at(-1)?.toLowerCase();
182
+ if (suffix && FILE_LIKE_HOST_SUFFIXES.has(suffix)) continue;
183
+ if (isDottedTechnologyPath(candidate)) continue;
184
+ return true;
185
+ }
186
+ return false;
187
+ }
188
+
189
+ const OPAQUE_IDENTIFIER_PATTERN =
190
+ /\b(?=[A-Za-z0-9_-]{32,}\b)(?=[A-Za-z0-9_-]*[A-Za-z])(?=[A-Za-z0-9_-]*\d)[A-Za-z0-9_-]+\b/u;
191
+
192
+ const DEFAULT_IGNORABLE_CODE_POINTS = /\p{Default_Ignorable_Code_Point}+/gu;
193
+ const ESCAPED_QUOTE_DELIMITERS = /\\+(?=["'])/gu;
194
+
195
+ const TITLE_LABEL_PATTERN =
196
+ /^(?:(?:suggested|generated|concise)\s+)?(?:(?:session|chat|conversation|task)\s+)?title\s*[:\-–—]\s*/iu;
197
+
198
+ const LEADING_BOILERPLATE_PATTERNS = [
199
+ /^(?:hi|hello|hey)(?:\s+there)?\s*[,!:.\-–—]*\s*/iu,
200
+ /^(?:i\s+(?:would\s+like|want|need)\s+you\s+to|i['’]d\s+like\s+you\s+to)\s+/iu,
201
+ /^(?:please\s+)?(?:can|could|would|will)\s+you\s+/iu,
202
+ /^(?:your|the)\s+(?:task|job)\s+is\s+to\s+/iu,
203
+ /^(?:please\s+)?help\s+me\s+(?:to\s+)?/iu,
204
+ /^please(?:\s*[,!:.\-–—]+\s*|\s+|$)/iu,
205
+ ] as const;
206
+
207
+ function automaticTitleDetectionValue(value: string): string {
208
+ // Detection uses a compatibility-normalized shadow value so fullwidth
209
+ // punctuation/letters, invisible token splits, and serialized quote
210
+ // delimiters cannot evade the policy.
211
+ // The accepted title itself stays byte-for-byte in the user's language and
212
+ // emoji form; this shadow is never returned or persisted.
213
+ return value
214
+ .normalize("NFKC")
215
+ .replace(DEFAULT_IGNORABLE_CODE_POINTS, "")
216
+ .replace(ESCAPED_QUOTE_DELIMITERS, "");
217
+ }
218
+
219
+ function hasVisibleAutomaticTitleContent(value: string): boolean {
220
+ return automaticTitleDetectionValue(value).trim().length > 0;
221
+ }
222
+
223
+ /**
224
+ * Whether a bounded automatic-title candidate contains a credential, URL, or
225
+ * opaque identifier that must stay out of navigation and other display-title
226
+ * surfaces. Callers handling an unbounded source must bound it before invoking
227
+ * this helper.
228
+ */
229
+ export function containsSensitiveAutomaticSessionTitleValue(value: string): boolean {
230
+ const detectionValue = automaticTitleDetectionValue(value);
231
+
232
+ if (KNOWN_SENSITIVE_VALUE_PATTERNS.some((pattern) => pattern.test(detectionValue))) return true;
233
+ if (containsUriScheme(detectionValue)) return true;
234
+ if (containsSchemelessUrl(detectionValue)) return true;
235
+ if (SECRET_LABEL_ASSIGNMENT_PATTERN.test(detectionValue)) return true;
236
+ if (containsSensitiveAssignment(detectionValue)) return true;
237
+ if (OPAQUE_IDENTIFIER_PATTERN.test(detectionValue)) return true;
238
+ return false;
239
+ }
240
+
241
+ function stripAutomaticTitleBoilerplate(value: string): string {
242
+ let title = value;
243
+ for (let pass = 0; pass < 4; pass += 1) {
244
+ const before = title;
245
+ title = title
246
+ .replace(/^(?:```[^\n]*|[\s#>*_`"'“”‘’\-–—]+)+/u, "")
247
+ .replace(TITLE_LABEL_PATTERN, "");
248
+ for (const pattern of LEADING_BOILERPLATE_PATTERNS) {
249
+ title = title.replace(pattern, "");
250
+ }
251
+ title = title.trim();
252
+ if (title === before) break;
253
+ }
254
+ return title;
255
+ }
256
+
257
+ const AUTOMATIC_TITLE_TRAILING_PUNCTUATION = /[\s.!?,;:\-–—]+$/u;
258
+
259
+ // Closing quote and markdown marks, mapped to the distinct opening mark that
260
+ // would pair with them inside the title.
261
+ const TRAILING_WRAPPER_MARKS = new Map<string, string | null>([
262
+ ['"', null],
263
+ ["'", null],
264
+ ["`", null],
265
+ ["*", null],
266
+ ["”", "“"],
267
+ ["’", "‘"],
268
+ ["»", "«"],
269
+ ]);
270
+
271
+ /**
272
+ * Remove trailing punctuation and a closing quote or markdown mark whose
273
+ * opening counterpart was already stripped as a leading wrapper, so
274
+ * `"Pod Crash Debugging"` and `**Pod Crash Debugging**` do not keep a dangling
275
+ * closing mark. A mark that still pairs with one inside the title stays.
276
+ */
277
+ function trimAutomaticTitleEnd(value: string): string {
278
+ let title = value;
279
+ for (let pass = 0; pass < 4; pass += 1) {
280
+ const before = title;
281
+ title = title.replace(AUTOMATIC_TITLE_TRAILING_PUNCTUATION, "");
282
+ const last = title.at(-1);
283
+ const opening = last === undefined ? undefined : TRAILING_WRAPPER_MARKS.get(last);
284
+ if (last !== undefined && opening !== undefined) {
285
+ let rest = title;
286
+ while (rest.endsWith(last)) rest = rest.slice(0, -last.length);
287
+ if (!rest.includes(last) && (opening === null || !rest.includes(opening))) {
288
+ title = rest;
289
+ }
290
+ }
291
+ if (title === before) break;
292
+ }
293
+ return title.trim();
294
+ }
295
+
296
+ function automaticTitleGraphemes(value: string): string[] {
297
+ if (titleSegmenter === undefined) {
298
+ titleSegmenter =
299
+ typeof Intl.Segmenter === "function"
300
+ ? new Intl.Segmenter(undefined, { granularity: "grapheme" })
301
+ : null;
302
+ }
303
+ if (titleSegmenter) {
304
+ return Array.from(titleSegmenter.segment(value), (part) => part.segment);
305
+ }
306
+
307
+ // Older embedded runtimes may not ship Intl.Segmenter. Preserve surrogate
308
+ // pairs plus common combining/emoji sequences instead of failing module load
309
+ // or slicing UTF-16 code units.
310
+ const graphemes: string[] = [];
311
+ for (const point of value) {
312
+ const prior = graphemes.at(-1);
313
+ if (
314
+ prior &&
315
+ (/^[\p{Mark}\u{FE0E}\u{FE0F}\p{Emoji_Modifier}]$/u.test(point) ||
316
+ point === "\u200d" ||
317
+ prior.endsWith("\u200d"))
318
+ ) {
319
+ graphemes[graphemes.length - 1] = `${prior}${point}`;
320
+ } else {
321
+ graphemes.push(point);
322
+ }
323
+ }
324
+ return graphemes;
325
+ }
326
+
327
+ /** Bound an already-normalized automatic-title candidate without splitting graphemes. */
328
+ export function boundAutomaticSessionTitle(value: string): string {
329
+ const words = value.split(/\s+/u);
330
+ const wordBounded = words.length > 10 ? words.slice(0, 10).join(" ") : value;
331
+ const graphemes = automaticTitleGraphemes(wordBounded);
332
+ if (graphemes.length <= AUTOMATIC_SESSION_TITLE_MAX_GRAPHEMES) return wordBounded;
333
+
334
+ const prefix = graphemes.slice(0, AUTOMATIC_SESSION_TITLE_MAX_GRAPHEMES).join("");
335
+ const lastWhitespace = prefix.search(/\s+\S*$/u);
336
+ return (lastWhitespace >= 16 ? prefix.slice(0, lastWhitespace) : prefix).trimEnd();
337
+ }
338
+
339
+ /**
340
+ * Normalize a model/system-authored title before it reaches durable session
341
+ * metadata. Human renames intentionally do not use this path.
342
+ *
343
+ * Returns null when the candidate is empty, only boilerplate, or appears to
344
+ * contain a credential/opaque prompt value. Callers should retain the current
345
+ * title (normally {@link AUTOMATIC_SESSION_TITLE_FALLBACK}) in that case.
346
+ */
347
+ export function normalizeAutomaticSessionTitle(value: string): string | null {
348
+ const firstLine = value
349
+ .replace(/[\u0000-\u001f\u007f-\u009f]+/gu, "\n")
350
+ .split(/\n+/u)
351
+ .map((line) => line.trim())
352
+ .find(Boolean);
353
+ if (
354
+ !firstLine ||
355
+ !hasVisibleAutomaticTitleContent(firstLine) ||
356
+ containsSensitiveAutomaticSessionTitleValue(firstLine)
357
+ ) {
358
+ return null;
359
+ }
360
+
361
+ let title = trimAutomaticTitleEnd(
362
+ stripAutomaticTitleBoilerplate(firstLine).replace(/\s+/gu, " "),
363
+ );
364
+ if (
365
+ !title ||
366
+ !hasVisibleAutomaticTitleContent(title) ||
367
+ containsSensitiveAutomaticSessionTitleValue(title)
368
+ ) {
369
+ return null;
370
+ }
371
+
372
+ title = trimAutomaticTitleEnd(boundAutomaticSessionTitle(title));
373
+ if (!title || !hasVisibleAutomaticTitleContent(title)) return null;
374
+ return title;
375
+ }
376
+
377
+ export type SessionDisplayTitleInput = {
378
+ id?: string | null | undefined;
379
+ title?: string | null | undefined;
380
+ titleSource?: "user" | "agent" | null | undefined;
381
+ initialMessage?: string | null | undefined;
382
+ metadata?: Readonly<Record<string, unknown>> | undefined;
383
+ };
384
+
385
+ export type SessionDisplayTitleOptions = {
386
+ /** Optional metadata fields to try before the opening-prompt preview. */
387
+ metadataKeys?: readonly string[] | undefined;
388
+ };
389
+
390
+ /**
391
+ * Derive a bounded, sensitive-safe preview from the opening prompt.
392
+ *
393
+ * Unsafe leading lines are skipped instead of forcing the whole session back
394
+ * to a generic label. This covers prompts that begin with a pasted URL or
395
+ * identifier followed by an ordinary natural-language request on the next
396
+ * line, without putting the rejected value into navigation surfaces.
397
+ */
398
+ export function deriveAutomaticSessionTitlePreview(value: unknown): string | null {
399
+ if (typeof value !== "string") return null;
400
+
401
+ const lines = value
402
+ .slice(0, PROMPT_PREVIEW_SCAN_MAX_CODE_UNITS)
403
+ .replace(/[\u0000-\u001f\u007f-\u009f]+/gu, "\n")
404
+ .split(/\n+/u);
405
+
406
+ for (const candidate of lines) {
407
+ const line = candidate.trim();
408
+ if (!line || containsSensitiveAutomaticSessionTitleValue(line)) continue;
409
+
410
+ const preview = boundAutomaticSessionTitle(line.replace(/\s+/gu, " "))
411
+ .replace(/[\s.!?,;:\-–—]+$/u, "")
412
+ .trim();
413
+ if (preview) return preview;
414
+ }
415
+
416
+ return null;
417
+ }
418
+
419
+ function automaticSessionReferenceTitle(id: unknown): string {
420
+ const sessionId = typeof id === "string" ? id.trim() : "";
421
+ return SESSION_ID_PATTERN.test(sessionId)
422
+ ? `Conversation ${sessionId.slice(0, 13)}`
423
+ : AUTOMATIC_SESSION_TITLE_FALLBACK;
424
+ }
425
+
426
+ /**
427
+ * Whether the durable title still represents the automatic-title pending state.
428
+ * A user-authored title always wins, even when its literal value is the marker.
429
+ */
430
+ export function sessionTitleIsPending(input: SessionDisplayTitleInput): boolean {
431
+ const title = input.title?.trim() ?? "";
432
+ return input.titleSource !== "user" && (!title || title === AUTOMATIC_SESSION_TITLE_FALLBACK);
433
+ }
434
+
435
+ /**
436
+ * Derive the title a human-facing client should display for a session.
437
+ *
438
+ * A semantic agent title or human rename wins. While automatic naming is still
439
+ * pending, clients show a short, sensitive-safe preview of the opening prompt.
440
+ * If no safe prompt text exists, a UUID-derived reference keeps real sessions
441
+ * distinguishable without exposing prompt bytes. The durable pending marker is
442
+ * therefore an internal lifecycle value rather than the ordinary visible name.
443
+ */
444
+ export function deriveSessionDisplayTitle(
445
+ input: SessionDisplayTitleInput,
446
+ options: SessionDisplayTitleOptions = {},
447
+ ): string {
448
+ const title = input.title?.trim() ?? "";
449
+ if (input.titleSource === "user") {
450
+ return title || automaticSessionReferenceTitle(input.id);
451
+ }
452
+ if (title && !sessionTitleIsPending(input)) {
453
+ return title;
454
+ }
455
+
456
+ for (const key of options.metadataKeys ?? []) {
457
+ const value = input.metadata?.[key];
458
+ if (typeof value === "string" && value.trim().length > 0) {
459
+ return value.trim();
460
+ }
461
+ }
462
+
463
+ return (
464
+ deriveAutomaticSessionTitlePreview(input.initialMessage) ??
465
+ automaticSessionReferenceTitle(input.id)
466
+ );
467
+ }
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+
3
+ export const SessionStatus = z.enum([
4
+ "queued",
5
+ "running",
6
+ "idle",
7
+ "requires_action",
8
+ "recovering",
9
+ "waiting_capacity",
10
+ "failed",
11
+ "cancelled",
12
+ ]);
13
+ export type SessionStatus = z.infer<typeof SessionStatus>;
14
+
15
+ export const SessionGoalStatus = z.enum(["active", "paused", "completed"]);
16
+ export type SessionGoalStatus = z.infer<typeof SessionGoalStatus>;
17
+
18
+ /** Physical ceiling of the PostgreSQL integer columns that persist depth policy. */
19
+ export const MAX_NESTED_AGENT_DEPTH = 2_147_483_647;
20
+ export const NestedAgentDepthValue = z.number().int().nonnegative().max(MAX_NESTED_AGENT_DEPTH);
21
+ export type NestedAgentDepthValue = z.infer<typeof NestedAgentDepthValue>;
@@ -0,0 +1,81 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * First-touch marketing parameters accepted on the web app landing URL. The
5
+ * browser keeps them in memory only and forwards them with a sign-up request,
6
+ * so nothing is written to the device before analytics consent.
7
+ */
8
+ export const SIGNUP_ATTRIBUTION_URL_PARAMETERS = {
9
+ utmSource: "utm_source",
10
+ utmMedium: "utm_medium",
11
+ utmCampaign: "utm_campaign",
12
+ utmContent: "utm_content",
13
+ ref: "ref",
14
+ } as const;
15
+
16
+ /** Maximum retained length of one attribution value. */
17
+ export const SIGNUP_ATTRIBUTION_VALUE_MAX_LENGTH = 100;
18
+
19
+ /**
20
+ * Campaign labels are marketer-chosen slug tokens such as `hero-cta` or
21
+ * `opengeni.ai`. The closed character set has no space, `@`, `:`, `/`, `?`,
22
+ * `=`, or `%`, so free text, email addresses, and URL payloads are rejected
23
+ * before they reach any sink. The web boot capture and the session-set social
24
+ * start request mirror this rule; tests pin each copy to it.
25
+ */
26
+ export const SIGNUP_ATTRIBUTION_VALUE_PATTERN = /^[A-Za-z0-9._~+-]+$/;
27
+
28
+ const SignupAttributionValue = z
29
+ .string()
30
+ .trim()
31
+ .min(1)
32
+ .max(SIGNUP_ATTRIBUTION_VALUE_MAX_LENGTH)
33
+ .regex(SIGNUP_ATTRIBUTION_VALUE_PATTERN);
34
+
35
+ export const SignupAttribution = z
36
+ .object({
37
+ utmSource: SignupAttributionValue.optional(),
38
+ utmMedium: SignupAttributionValue.optional(),
39
+ utmCampaign: SignupAttributionValue.optional(),
40
+ utmContent: SignupAttributionValue.optional(),
41
+ ref: SignupAttributionValue.optional(),
42
+ })
43
+ .strict();
44
+ export type SignupAttribution = z.infer<typeof SignupAttribution>;
45
+
46
+ /** Closed acquisition channels reported by server-side sign-up metrics. */
47
+ export const SIGNUP_ACQUISITION_SOURCES = ["producthunt", "website", "direct", "other"] as const;
48
+ export type SignupAcquisitionSource = (typeof SIGNUP_ACQUISITION_SOURCES)[number];
49
+
50
+ /**
51
+ * Normalize untrusted first-touch attribution to a closed channel. Missing
52
+ * attribution is `direct`; malformed or unrecognized attribution is `other`.
53
+ */
54
+ export function signupAcquisitionSource(input: unknown): SignupAcquisitionSource {
55
+ if (input === undefined || input === null) return "direct";
56
+ const parsed = SignupAttribution.safeParse(input);
57
+ if (!parsed.success) return "other";
58
+ const attribution = parsed.data;
59
+ const values = Object.values(attribution).filter((value) => value !== undefined);
60
+ if (values.length === 0) return "direct";
61
+ const source = attribution.utmSource?.toLowerCase();
62
+ const medium = attribution.utmMedium?.toLowerCase();
63
+ const ref = attribution.ref?.toLowerCase();
64
+ if ([source, ref].some((value) => value !== undefined && isProductHunt(value))) {
65
+ return "producthunt";
66
+ }
67
+ if (
68
+ source === "opengeni.ai" ||
69
+ source?.endsWith(".opengeni.ai") ||
70
+ source === "opengeni" ||
71
+ medium === "website"
72
+ ) {
73
+ return "website";
74
+ }
75
+ return "other";
76
+ }
77
+
78
+ function isProductHunt(value: string): boolean {
79
+ const compact = value.replace(/[\s._-]/g, "");
80
+ return compact === "ph" || compact.includes("producthunt");
81
+ }
@@ -0,0 +1,28 @@
1
+ import { expect, test } from "bun:test";
2
+ import { siteRequestHeaders } from "./site-session-http";
3
+
4
+ test("Site forwarding preserves application headers and removes host authority and transport", () => {
5
+ const input = {
6
+ range: "bytes=0-1023",
7
+ "if-match": '"version"',
8
+ "idempotency-key": "operation",
9
+ "x-opengeni-chunk-sha256": "digest",
10
+ "x-custom-option": "value",
11
+ authorization: "forged",
12
+ cookie: "session=forged",
13
+ "x-opengeni-access-key": "forged",
14
+ "x-opengeni-external-actor": "forged",
15
+ "x-opengeni-site-id": "forged",
16
+ connection: "x-private-hop",
17
+ "x-private-hop": "transport",
18
+ "content-length": "999",
19
+ };
20
+ const headers = siteRequestHeaders(input);
21
+ expect(Object.fromEntries(headers)).toEqual({
22
+ range: "bytes=0-1023",
23
+ "if-match": '"version"',
24
+ "idempotency-key": "operation",
25
+ "x-opengeni-chunk-sha256": "digest",
26
+ "x-custom-option": "value",
27
+ });
28
+ });
@@ -0,0 +1,63 @@
1
+ const SITE_WORKSPACE_PREFIX = "/v1/workspaces/site-host";
2
+
3
+ /** A Site/Codemode SDK request named a route outside the proxied surface. */
4
+ export class SiteSessionPathError extends Error {
5
+ readonly code = "site_session_path_unsupported";
6
+
7
+ constructor() {
8
+ super("Unsupported Site session API path");
9
+ this.name = "SiteSessionPathError";
10
+ }
11
+ }
12
+
13
+ /** Route browser/preview SDK requests to the host workspace. This is a URL
14
+ * boundary, not an endpoint or HTTP-method allowlist. Ordinary API handlers
15
+ * authorize every operation with the viewer or agent's existing authority. */
16
+ export function siteSessionPath(
17
+ path: string,
18
+ workspaceId: string,
19
+ _method = "GET",
20
+ siteId?: string,
21
+ ): string {
22
+ const pathname = path.split("?")[0]!;
23
+ const workspace =
24
+ pathname === SITE_WORKSPACE_PREFIX || pathname.startsWith(`${SITE_WORKSPACE_PREFIX}/`);
25
+ const context = pathname === "/v1/config/client";
26
+ if (
27
+ (!workspace && !context) ||
28
+ /[%\\#\u0000-\u0020\u007f]/u.test(pathname) ||
29
+ pathname.split("/").some((p) => p === "." || p === "..")
30
+ ) {
31
+ throw new SiteSessionPathError();
32
+ }
33
+ const rewritten = path.replace(
34
+ "/workspaces/site-host",
35
+ `/workspaces/${encodeURIComponent(workspaceId)}`,
36
+ );
37
+ // Preview has no published Site identity. Never widen a Site-only list to the workspace.
38
+ const url = new URL(rewritten, "http://site.invalid");
39
+ if (url.searchParams.get("originSiteId") === "current" && workspaceId !== "site-host") {
40
+ url.searchParams.set("originSiteId", siteId ?? "00000000-0000-0000-0000-000000000000");
41
+ return url.pathname + url.search;
42
+ }
43
+ return rewritten;
44
+ }
45
+
46
+ /** Preserve SDK request semantics while the host supplies identity and transport headers. */
47
+ export function siteRequestHeaders(input: HeadersInit): Headers {
48
+ const headers = new Headers(input);
49
+ const connectionHeaders = (headers.get("connection") ?? "")
50
+ .split(",")
51
+ .map((value) => value.trim().toLowerCase());
52
+ for (const name of [...headers.keys()]) {
53
+ if (
54
+ connectionHeaders.includes(name) ||
55
+ /^(authorization|proxy-authorization|cookie|cookie2|host|origin|referer|connection|keep-alive|proxy-connection|te|trailer|transfer-encoding|upgrade|content-length|accept-encoding|forwarded|x-api-key)$/i.test(
56
+ name,
57
+ ) ||
58
+ /^(sec-|x-forwarded-|x-opengeni-(access-key|external-actor|site-|managed-actor))/i.test(name)
59
+ )
60
+ headers.delete(name);
61
+ }
62
+ return headers;
63
+ }