@opengeni/sdk 3.3.2 → 3.4.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 (299) hide show
  1. package/README.md +1007 -72
  2. package/dist/adapters/express.d.ts +31 -0
  3. package/dist/adapters/express.js +87 -0
  4. package/dist/adapters/express.js.map +1 -0
  5. package/dist/adapters/hono.d.ts +16 -0
  6. package/dist/adapters/hono.js +8 -0
  7. package/dist/adapters/hono.js.map +1 -0
  8. package/dist/adapters/next.d.ts +29 -0
  9. package/dist/adapters/next.js +22 -0
  10. package/dist/adapters/next.js.map +1 -0
  11. package/dist/allowance-refusal.d.ts +2 -0
  12. package/dist/allowance-refusal.js +50 -0
  13. package/dist/allowance-refusal.js.map +1 -0
  14. package/dist/app-links.d.ts +42 -0
  15. package/dist/app-links.js +66 -0
  16. package/dist/app-links.js.map +1 -0
  17. package/dist/artifact-catalog.d.ts +40 -0
  18. package/dist/artifact-client.d.ts +20 -8
  19. package/dist/artifacts.d.ts +1 -1
  20. package/dist/artifacts.js +8 -6
  21. package/dist/automations.js +8 -2
  22. package/dist/automations.js.map +1 -1
  23. package/dist/azure-live-transport.d.ts +32 -0
  24. package/dist/browser.d.ts +7 -2
  25. package/dist/browser.js +27 -5
  26. package/dist/chat/fold.d.ts +75 -0
  27. package/dist/chat/handler.d.ts +41 -0
  28. package/dist/chat/http.d.ts +67 -0
  29. package/dist/chat/ids.d.ts +34 -0
  30. package/dist/chat/index.d.ts +9 -0
  31. package/dist/chat/index.js +1369 -0
  32. package/dist/chat/index.js.map +1 -0
  33. package/dist/chat/openai.d.ts +30 -0
  34. package/dist/chat/opengeni.d.ts +129 -0
  35. package/dist/chat/types.d.ts +207 -0
  36. package/dist/chat/vercel.d.ts +49 -0
  37. package/dist/chats.d.ts +8 -0
  38. package/dist/chunk-4AHR4KGA.js +301 -0
  39. package/dist/chunk-4AHR4KGA.js.map +1 -0
  40. package/dist/chunk-6GYGMLX4.js +332 -0
  41. package/dist/chunk-6GYGMLX4.js.map +1 -0
  42. package/dist/chunk-7EWT3PV6.js +368 -0
  43. package/dist/chunk-7EWT3PV6.js.map +1 -0
  44. package/dist/chunk-FTWVVPR6.js +1665 -0
  45. package/dist/chunk-FTWVVPR6.js.map +1 -0
  46. package/dist/{chunk-VYCTL62C.js → chunk-GZQSWAKD.js} +1 -1
  47. package/dist/chunk-GZQSWAKD.js.map +1 -0
  48. package/dist/chunk-KGEABAID.js +71 -0
  49. package/dist/chunk-KGEABAID.js.map +1 -0
  50. package/dist/{chunk-YKZME56C.js → chunk-KWADQ227.js} +3 -3
  51. package/dist/{chunk-YKZME56C.js.map → chunk-KWADQ227.js.map} +1 -1
  52. package/dist/{chunk-JUS2UYRP.js → chunk-NCU5OAN7.js} +27 -2
  53. package/dist/chunk-NCU5OAN7.js.map +1 -0
  54. package/dist/{chunk-YXQX7RQT.js → chunk-OI5YLASH.js} +73 -2
  55. package/dist/chunk-OI5YLASH.js.map +1 -0
  56. package/dist/chunk-OV3POE62.js +359 -0
  57. package/dist/chunk-OV3POE62.js.map +1 -0
  58. package/dist/chunk-P7UWKORG.js +561 -0
  59. package/dist/chunk-P7UWKORG.js.map +1 -0
  60. package/dist/chunk-QGEIL4TR.js +195 -0
  61. package/dist/chunk-QGEIL4TR.js.map +1 -0
  62. package/dist/{chunk-TRNI7KEO.js → chunk-S2NLS3ES.js} +3669 -1281
  63. package/dist/chunk-S2NLS3ES.js.map +1 -0
  64. package/dist/chunk-UM4ZXXJR.js +9 -0
  65. package/dist/chunk-UM4ZXXJR.js.map +1 -0
  66. package/dist/{chunk-QT3KFVDP.js → chunk-UME3X7CP.js} +28 -32
  67. package/dist/chunk-UME3X7CP.js.map +1 -0
  68. package/dist/chunk-VT4YM4XQ.js +210 -0
  69. package/dist/chunk-VT4YM4XQ.js.map +1 -0
  70. package/dist/chunk-WSYYXONG.js +1602 -0
  71. package/dist/chunk-WSYYXONG.js.map +1 -0
  72. package/dist/{chunk-C2H7HBNP.js → chunk-WVCJRSGJ.js} +119 -14
  73. package/dist/chunk-WVCJRSGJ.js.map +1 -0
  74. package/dist/chunk-X5ITQVXE.js +15 -0
  75. package/dist/chunk-X5ITQVXE.js.map +1 -0
  76. package/dist/chunk-XA7HVIHI.js +126 -0
  77. package/dist/chunk-XA7HVIHI.js.map +1 -0
  78. package/dist/{chunk-KKRO2VCP.js → chunk-YJCHB7R3.js} +60 -12
  79. package/dist/chunk-YJCHB7R3.js.map +1 -0
  80. package/dist/chunk-YKIHNLWD.js +153 -0
  81. package/dist/chunk-YKIHNLWD.js.map +1 -0
  82. package/dist/chunk-ZDUBPV33.js +277 -0
  83. package/dist/chunk-ZDUBPV33.js.map +1 -0
  84. package/dist/client.d.ts +1068 -118
  85. package/dist/codex-realtime-controller.d.ts +13 -0
  86. package/dist/codex-realtime-controller.js +4 -2
  87. package/dist/codex-realtime-v3-wire.d.ts +3 -2
  88. package/dist/codex-realtime.d.ts +5 -1
  89. package/dist/company-brain.d.ts +1 -11
  90. package/dist/core.d.ts +7 -2
  91. package/dist/core.js +25 -6
  92. package/dist/deprecation.d.ts +47 -0
  93. package/dist/document-authority-client.d.ts +24 -2
  94. package/dist/document-authority.js +7 -5
  95. package/dist/editable-artifacts/index.d.ts +1 -1
  96. package/dist/editable-artifacts/types.d.ts +12 -2
  97. package/dist/editable-artifacts.js +19 -3
  98. package/dist/editable-artifacts.js.map +1 -1
  99. package/dist/embedding-client.d.ts +129 -0
  100. package/dist/errors.d.ts +76 -1
  101. package/dist/external-vault-receiver.d.ts +203 -0
  102. package/dist/external-vault-receiver.js +564 -0
  103. package/dist/external-vault-receiver.js.map +1 -0
  104. package/dist/feedback.d.ts +23 -0
  105. package/dist/gateway-realtime-transport.js +1 -1
  106. package/dist/github-repositories.d.ts +10 -0
  107. package/dist/github-repositories.js +36 -0
  108. package/dist/github-repositories.js.map +1 -0
  109. package/dist/index.d.ts +62 -11
  110. package/dist/index.js +557 -338
  111. package/dist/index.js.map +1 -1
  112. package/dist/insights-usage.d.ts +33 -0
  113. package/dist/insights-usage.js +7 -0
  114. package/dist/insights-usage.js.map +1 -0
  115. package/dist/interaction.d.ts +165 -71
  116. package/dist/interaction.js +2 -2
  117. package/dist/knowledge.d.ts +7 -0
  118. package/dist/knowledge.js +20 -0
  119. package/dist/knowledge.js.map +1 -0
  120. package/dist/known-tools.d.ts +4 -0
  121. package/dist/message-links.d.ts +68 -0
  122. package/dist/model-context.d.ts +68 -0
  123. package/dist/model-display.d.ts +2 -0
  124. package/dist/model-display.js +17 -0
  125. package/dist/model-display.js.map +1 -0
  126. package/dist/model-picker-order.d.ts +1 -0
  127. package/dist/model-picker-order.js +10 -0
  128. package/dist/model-picker-order.js.map +1 -0
  129. package/dist/organization-integration-policy.d.ts +26 -0
  130. package/dist/organization-integration-policy.js +26 -0
  131. package/dist/organization-integration-policy.js.map +1 -0
  132. package/dist/pr-review-client.d.ts +1 -1
  133. package/dist/pr-review.d.ts +1 -1
  134. package/dist/pr-review.js.map +1 -1
  135. package/dist/preference-registry.d.ts +1 -1
  136. package/dist/prepared-mcp-setup.d.ts +5 -0
  137. package/dist/proxy.d.ts +13 -11
  138. package/dist/realtime.d.ts +1 -1
  139. package/dist/realtime.js +253 -7
  140. package/dist/realtime.js.map +1 -1
  141. package/dist/session-history-import.d.ts +22 -0
  142. package/dist/session-history-import.js +36 -0
  143. package/dist/session-history-import.js.map +1 -0
  144. package/dist/session-list-entries.d.ts +1 -0
  145. package/dist/session-list-entries.js +7 -0
  146. package/dist/session-list-entries.js.map +1 -0
  147. package/dist/session-message-search.d.ts +53 -0
  148. package/dist/session-proxy-client.d.ts +28 -0
  149. package/dist/session-proxy.d.ts +334 -0
  150. package/dist/session-proxy.js +24 -0
  151. package/dist/session-proxy.js.map +1 -0
  152. package/dist/session-titles.d.ts +2 -28
  153. package/dist/session-usage-limits.d.ts +10 -0
  154. package/dist/session-usage-limits.js +43 -0
  155. package/dist/session-usage-limits.js.map +1 -0
  156. package/dist/shared-catalog-loader.d.ts +34 -0
  157. package/dist/site-ai.d.ts +35 -0
  158. package/dist/site-bridge-host.d.ts +112 -0
  159. package/dist/site-bridge-host.js +286 -0
  160. package/dist/site-bridge-host.js.map +1 -0
  161. package/dist/site-browser-entry.d.ts +1 -0
  162. package/dist/site-browser-runtime.gen.d.ts +1 -0
  163. package/dist/site-data.d.ts +178 -0
  164. package/dist/site-document.d.ts +5 -0
  165. package/dist/site-document.js +20 -0
  166. package/dist/site-document.js.map +1 -0
  167. package/dist/site-document.test.d.ts +1 -0
  168. package/dist/site-http.d.ts +17 -0
  169. package/dist/site-sql.d.ts +31 -0
  170. package/dist/site-tool-bridge.d.ts +57 -0
  171. package/dist/site.d.ts +186 -0
  172. package/dist/site.js +63 -0
  173. package/dist/site.js.map +1 -0
  174. package/dist/skills.d.ts +75 -0
  175. package/dist/sse.d.ts +1 -1
  176. package/dist/stream.d.ts +2 -0
  177. package/dist/tenant-workspaces.d.ts +47 -0
  178. package/dist/tenant-workspaces.js +8 -0
  179. package/dist/tenant-workspaces.js.map +1 -0
  180. package/dist/tool-auth.d.ts +87 -0
  181. package/dist/tool-auth.js +23 -0
  182. package/dist/tool-auth.js.map +1 -0
  183. package/dist/tool-display-metadata.d.ts +3 -0
  184. package/dist/tools-37DCTCNZ.js +12 -0
  185. package/dist/tools-37DCTCNZ.js.map +1 -0
  186. package/dist/tools.d.ts +81 -0
  187. package/dist/transcription.d.ts +1 -1
  188. package/dist/types.d.ts +2807 -583
  189. package/dist/usage-allowances.d.ts +36 -0
  190. package/dist/usage-allowances.js +80 -0
  191. package/dist/usage-allowances.js.map +1 -0
  192. package/dist/vault-types.d.ts +324 -0
  193. package/dist/vaults-api.d.ts +65 -0
  194. package/dist/workspace-artifacts.d.ts +47 -5
  195. package/dist/workspace-instruction-policies.d.ts +2 -2
  196. package/dist/workspace-integrations.d.ts +74 -0
  197. package/dist/workspace-integrations.js +67 -0
  198. package/dist/workspace-integrations.js.map +1 -0
  199. package/dist/workspace-state.d.ts +8 -71
  200. package/package.json +103 -5
  201. package/src/adapters/express.ts +136 -0
  202. package/src/adapters/hono.ts +18 -0
  203. package/src/adapters/next.ts +42 -0
  204. package/src/allowance-refusal.ts +5 -0
  205. package/src/app-links.ts +117 -0
  206. package/src/artifact-catalog.ts +51 -0
  207. package/src/artifact-client.ts +40 -49
  208. package/src/artifacts.ts +1 -0
  209. package/src/automations-client.ts +8 -2
  210. package/src/azure-live-transport.ts +286 -0
  211. package/src/browser.ts +37 -0
  212. package/src/chat/fold.ts +431 -0
  213. package/src/chat/handler.ts +240 -0
  214. package/src/chat/http.ts +257 -0
  215. package/src/chat/ids.ts +85 -0
  216. package/src/chat/index.ts +88 -0
  217. package/src/chat/openai.ts +439 -0
  218. package/src/chat/opengeni.ts +737 -0
  219. package/src/chat/types.ts +219 -0
  220. package/src/chat/vercel.ts +204 -0
  221. package/src/chats.ts +16 -0
  222. package/src/client.ts +5697 -1833
  223. package/src/codex-realtime-controller.ts +163 -8
  224. package/src/codex-realtime-v3-wire.ts +4 -3
  225. package/src/codex-realtime-v3.ts +12 -3
  226. package/src/codex-realtime.ts +11 -3
  227. package/src/company-brain.ts +0 -40
  228. package/src/core.ts +20 -0
  229. package/src/deprecation.ts +149 -0
  230. package/src/document-authority-client.ts +104 -1
  231. package/src/editable-artifacts/controller.ts +36 -2
  232. package/src/editable-artifacts/index.ts +1 -0
  233. package/src/editable-artifacts/types.ts +29 -16
  234. package/src/embedding-client.ts +519 -0
  235. package/src/errors.ts +303 -4
  236. package/src/external-vault-receiver.ts +914 -0
  237. package/src/feedback.ts +20 -0
  238. package/src/gateway-realtime-transport.ts +3 -3
  239. package/src/github-repositories.ts +60 -0
  240. package/src/index.ts +603 -65
  241. package/src/insights-usage.ts +64 -0
  242. package/src/interaction.ts +315 -93
  243. package/src/knowledge.ts +72 -0
  244. package/src/known-tools.ts +279 -0
  245. package/src/message-links.ts +178 -0
  246. package/src/model-context.ts +113 -0
  247. package/src/model-display.ts +11 -0
  248. package/src/model-picker-order.ts +6 -0
  249. package/src/organization-integration-policy.ts +54 -0
  250. package/src/pr-review-client.ts +1 -1
  251. package/src/pr-review.ts +1 -1
  252. package/src/preference-registry.ts +2 -0
  253. package/src/prepared-mcp-setup.ts +123 -0
  254. package/src/proxy.ts +23 -12
  255. package/src/realtime.ts +15 -6
  256. package/src/session-history-import.ts +84 -0
  257. package/src/session-list-entries.ts +1 -0
  258. package/src/session-message-search.ts +47 -0
  259. package/src/session-proxy-client.ts +68 -0
  260. package/src/session-proxy.ts +2098 -0
  261. package/src/session-titles.ts +8 -82
  262. package/src/session-usage-limits.ts +71 -0
  263. package/src/shared-catalog-loader.ts +169 -0
  264. package/src/site-ai.ts +284 -0
  265. package/src/site-bridge-host.ts +422 -0
  266. package/src/site-browser-entry.ts +6 -0
  267. package/src/site-browser-runtime.gen.ts +2 -0
  268. package/src/site-data.ts +372 -0
  269. package/src/site-document.test.ts +64 -0
  270. package/src/site-document.ts +18 -0
  271. package/src/site-http.ts +171 -0
  272. package/src/site-sql.ts +153 -0
  273. package/src/site-tool-bridge.ts +401 -0
  274. package/src/site.ts +878 -0
  275. package/src/skills.ts +69 -0
  276. package/src/sse.ts +9 -3
  277. package/src/stream.ts +33 -10
  278. package/src/tenant-workspaces.ts +148 -0
  279. package/src/tool-auth.ts +301 -0
  280. package/src/tool-display-metadata.ts +27 -0
  281. package/src/tools.ts +452 -0
  282. package/src/transcription.ts +3 -0
  283. package/src/types.ts +3170 -582
  284. package/src/usage-allowances.ts +189 -0
  285. package/src/vault-types.ts +379 -0
  286. package/src/vaults-api.ts +339 -0
  287. package/src/workspace-artifacts.ts +55 -6
  288. package/src/workspace-instruction-policies.ts +2 -1
  289. package/src/workspace-integrations.ts +402 -0
  290. package/src/workspace-state.ts +8 -82
  291. package/dist/chunk-C2H7HBNP.js.map +0 -1
  292. package/dist/chunk-C4DJPZRU.js +0 -119
  293. package/dist/chunk-C4DJPZRU.js.map +0 -1
  294. package/dist/chunk-JUS2UYRP.js.map +0 -1
  295. package/dist/chunk-KKRO2VCP.js.map +0 -1
  296. package/dist/chunk-QT3KFVDP.js.map +0 -1
  297. package/dist/chunk-TRNI7KEO.js.map +0 -1
  298. package/dist/chunk-VYCTL62C.js.map +0 -1
  299. package/dist/chunk-YXQX7RQT.js.map +0 -1
@@ -0,0 +1,439 @@
1
+ import {
2
+ chatErrorSummary,
3
+ clientConversation,
4
+ errorResponse,
5
+ importedHistoryBefore,
6
+ jsonResponse,
7
+ lastUserMessageText,
8
+ messageContentText,
9
+ openResolvedChat,
10
+ readJsonObject,
11
+ resolveChatRequest,
12
+ sseByteStream,
13
+ sseHeaders,
14
+ sseLine,
15
+ type ChatResolve,
16
+ } from "./http";
17
+ import { isUuid } from "./ids";
18
+ import type { Chat, Opengeni } from "./opengeni";
19
+ import { OpenGeniChatError, type ChatChunk, type ChatReply } from "./types";
20
+
21
+ /**
22
+ * OpenAI-compatible adapters over the stable subset of the Chat Completions
23
+ * and Responses wire formats. `model` and `user` in the body are echoed, never
24
+ * trusted: identity comes from the host's `resolve` hook.
25
+ */
26
+
27
+ const DEFAULT_MODEL_LABEL = "opengeni";
28
+
29
+ type ReplyExtension = {
30
+ sessionId: string;
31
+ workspaceId: string;
32
+ turnId: string | null;
33
+ status: ChatReply["status"];
34
+ pending: ChatReply["pending"];
35
+ };
36
+
37
+ function replyExtension(reply: ChatReply): ReplyExtension {
38
+ return {
39
+ sessionId: reply.sessionId,
40
+ workspaceId: reply.workspaceId,
41
+ turnId: reply.turnId,
42
+ status: reply.status,
43
+ pending: reply.pending,
44
+ };
45
+ }
46
+
47
+ function conversationFromBody(body: Record<string, unknown> | null): string | undefined {
48
+ const conversation = body?.conversation;
49
+ if (typeof conversation === "string" && conversation) return conversation;
50
+ if (conversation && typeof conversation === "object") {
51
+ const id = (conversation as { id?: unknown }).id;
52
+ if (typeof id === "string" && id) return id;
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ function modelLabel(body: Record<string, unknown> | null): string {
58
+ return typeof body?.model === "string" && body.model ? body.model : DEFAULT_MODEL_LABEL;
59
+ }
60
+
61
+ function wantsStream(body: Record<string, unknown> | null): boolean {
62
+ return body?.stream === true;
63
+ }
64
+
65
+ // --- Chat Completions ---------------------------------------------------------
66
+
67
+ const CHAT_COMPLETION_PART_TYPES = ["text", "input_text"];
68
+
69
+ function chatCompletionChunk(
70
+ sessionId: string,
71
+ model: string,
72
+ created: number,
73
+ delta: Record<string, unknown>,
74
+ finishReason: "stop" | null,
75
+ extension?: ReplyExtension,
76
+ ): string {
77
+ return sseLine(
78
+ JSON.stringify({
79
+ id: `chatcmpl-${sessionId}`,
80
+ object: "chat.completion.chunk",
81
+ created,
82
+ model,
83
+ choices: [{ index: 0, delta, finish_reason: finishReason }],
84
+ ...(extension ? { opengeni: extension } : {}),
85
+ }),
86
+ );
87
+ }
88
+
89
+ export async function* chatCompletionBlocks(
90
+ chat: Chat,
91
+ chunks: AsyncIterable<ChatChunk>,
92
+ model: string,
93
+ ): AsyncGenerator<string, void, void> {
94
+ const created = Math.floor(Date.now() / 1000);
95
+ yield chatCompletionChunk(
96
+ chat.sessionId,
97
+ model,
98
+ created,
99
+ { role: "assistant", content: "" },
100
+ null,
101
+ );
102
+ try {
103
+ for await (const chunk of chunks) {
104
+ if (chunk.type === "text") {
105
+ yield chatCompletionChunk(chat.sessionId, model, created, { content: chunk.text }, null);
106
+ } else if (chunk.type === "done") {
107
+ yield chatCompletionChunk(
108
+ chat.sessionId,
109
+ model,
110
+ created,
111
+ {},
112
+ "stop",
113
+ replyExtension(chunk.reply),
114
+ );
115
+ }
116
+ }
117
+ } catch (error) {
118
+ const summary = chatErrorSummary(error);
119
+ yield sseLine(
120
+ JSON.stringify({
121
+ error: { message: summary.message, code: summary.code, type: "server_error" },
122
+ }),
123
+ );
124
+ }
125
+ yield sseLine("[DONE]");
126
+ }
127
+
128
+ export function chatCompletionObject(reply: ChatReply, model: string): Record<string, unknown> {
129
+ return {
130
+ id: `chatcmpl-${reply.sessionId}`,
131
+ object: "chat.completion",
132
+ created: Math.floor(Date.now() / 1000),
133
+ model,
134
+ choices: [
135
+ { index: 0, message: { role: "assistant", content: reply.text }, finish_reason: "stop" },
136
+ ],
137
+ opengeni: replyExtension(reply),
138
+ };
139
+ }
140
+
141
+ /**
142
+ * `POST /v1/chat/completions` shape: `{ messages, stream?, model?, user?, metadata? }`.
143
+ * Conversation: the host's `resolve`, else the `x-opengeni-conversation` header,
144
+ * else `metadata.conversation_id`; 400 when none. Only the last user message
145
+ * is sent; the messages before it are imported once as context when this
146
+ * request creates the session.
147
+ */
148
+ export async function handleChatCompletionsRequest(
149
+ og: Opengeni,
150
+ request: Request,
151
+ resolve: ChatResolve,
152
+ ): Promise<Response> {
153
+ const body = await readJsonObject(request);
154
+ const prompt =
155
+ lastUserMessageText(body?.messages, CHAT_COMPLETION_PART_TYPES, "content")?.trim() ?? "";
156
+ if (!prompt) {
157
+ return errorResponse(400, "The last user message has no text.", "message_required");
158
+ }
159
+ const resolved = await resolveChatRequest(request, resolve);
160
+ if (resolved.response) return resolved.response;
161
+ const metadata = body?.metadata;
162
+ const metadataConversation =
163
+ metadata && typeof metadata === "object"
164
+ ? (metadata as { conversation_id?: unknown }).conversation_id
165
+ : undefined;
166
+ const opened = await openResolvedChat(
167
+ og,
168
+ resolved.resolution,
169
+ clientConversation(request, metadataConversation),
170
+ );
171
+ if (opened.response) return opened.response;
172
+ const model = modelLabel(body);
173
+ const importedHistory = importedHistoryBefore(
174
+ body?.messages,
175
+ CHAT_COMPLETION_PART_TYPES,
176
+ "content",
177
+ );
178
+ if (wantsStream(body)) {
179
+ const chunks = opened.chat.stream(prompt, { signal: request.signal, importedHistory });
180
+ return new Response(sseByteStream(chatCompletionBlocks(opened.chat, chunks, model)), {
181
+ headers: sseHeaders(),
182
+ });
183
+ }
184
+ try {
185
+ const reply = await opened.chat.send(prompt, { signal: request.signal, importedHistory });
186
+ return jsonResponse(chatCompletionObject(reply, model));
187
+ } catch (error) {
188
+ const summary = chatErrorSummary(error);
189
+ return errorResponse(summary.status, summary.message, summary.code);
190
+ }
191
+ }
192
+
193
+ // --- Responses ----------------------------------------------------------------
194
+
195
+ /** Non-streaming response id; the session id alone addresses the conversation. */
196
+ export function encodeResponseId(sessionId: string, sequence: number): string {
197
+ return `resp_${sessionId}_${sequence}`;
198
+ }
199
+
200
+ export function decodeResponseId(value: unknown): string | null {
201
+ if (typeof value !== "string") return null;
202
+ // Streams allocate a UUID before the final event sequence is known. Keep
203
+ // accepting existing sequence-based response ids for conversation continuity.
204
+ const match = /^resp_([0-9a-f-]{36})(?:_(\d+|[0-9a-f-]{36}))?$/i.exec(value);
205
+ const sessionId = match?.[1];
206
+ const suffix = match?.[2];
207
+ if (suffix && !/^\d+$/.test(suffix) && !isUuid(suffix)) return null;
208
+ return sessionId && isUuid(sessionId) ? sessionId : null;
209
+ }
210
+
211
+ type ResponseIdentity = { responseId: string; itemId: string; createdAt: number };
212
+ type ResponseStatus = "in_progress" | "completed" | "incomplete";
213
+
214
+ function responseOutputItem(itemId: string, status: ResponseStatus, text: string | null) {
215
+ return {
216
+ type: "message",
217
+ id: itemId,
218
+ status,
219
+ role: "assistant",
220
+ content: text === null ? [] : [{ type: "output_text", text, annotations: [] }],
221
+ };
222
+ }
223
+
224
+ function responseObject(
225
+ reply: ChatReply | null,
226
+ chat: Chat,
227
+ model: string,
228
+ status: ResponseStatus,
229
+ previousResponseId: string | null,
230
+ text: string,
231
+ identity?: ResponseIdentity,
232
+ ): Record<string, unknown> {
233
+ const sequence = reply?.events.at(-1)?.sequence ?? 0;
234
+ return {
235
+ id: identity?.responseId ?? encodeResponseId(chat.sessionId, sequence),
236
+ object: "response",
237
+ created_at: identity?.createdAt ?? Math.floor(Date.now() / 1000),
238
+ status,
239
+ model,
240
+ previous_response_id: previousResponseId,
241
+ output:
242
+ status === "in_progress"
243
+ ? []
244
+ : [
245
+ responseOutputItem(
246
+ identity?.itemId ?? `msg_${chat.sessionId}_${sequence}`,
247
+ status,
248
+ text,
249
+ ),
250
+ ],
251
+ output_text: text,
252
+ ...(reply ? { opengeni: replyExtension(reply) } : {}),
253
+ };
254
+ }
255
+
256
+ export async function* responsesBlocks(
257
+ chat: Chat,
258
+ chunks: AsyncIterable<ChatChunk>,
259
+ model: string,
260
+ previousResponseId: string | null,
261
+ ): AsyncGenerator<string, void, void> {
262
+ let sequenceNumber = 0;
263
+ const requestId = crypto.randomUUID();
264
+ const identity: ResponseIdentity = {
265
+ responseId: `resp_${chat.sessionId}_${requestId}`,
266
+ itemId: `msg_${chat.sessionId}_${requestId}`,
267
+ createdAt: Math.floor(Date.now() / 1000),
268
+ };
269
+ const { itemId } = identity;
270
+ const event = (type: string, data: Record<string, unknown>): string =>
271
+ sseLine(JSON.stringify({ type, sequence_number: sequenceNumber++, ...data }), type);
272
+ const initial = responseObject(
273
+ null,
274
+ chat,
275
+ model,
276
+ "in_progress",
277
+ previousResponseId,
278
+ "",
279
+ identity,
280
+ );
281
+ yield event("response.created", { response: initial });
282
+ yield event("response.in_progress", { response: initial });
283
+ yield event("response.output_item.added", {
284
+ output_index: 0,
285
+ item: responseOutputItem(itemId, "in_progress", null),
286
+ });
287
+ yield event("response.content_part.added", {
288
+ item_id: itemId,
289
+ output_index: 0,
290
+ content_index: 0,
291
+ part: { type: "output_text", text: "", annotations: [] },
292
+ });
293
+ try {
294
+ for await (const chunk of chunks) {
295
+ if (chunk.type === "text") {
296
+ yield event("response.output_text.delta", {
297
+ item_id: itemId,
298
+ output_index: 0,
299
+ content_index: 0,
300
+ delta: chunk.text,
301
+ logprobs: [],
302
+ });
303
+ } else if (chunk.type === "done") {
304
+ const status = chunk.reply.status === "completed" ? "completed" : "incomplete";
305
+ const item = responseOutputItem(itemId, status, chunk.reply.text);
306
+ yield event("response.output_text.done", {
307
+ item_id: itemId,
308
+ output_index: 0,
309
+ content_index: 0,
310
+ text: chunk.reply.text,
311
+ logprobs: [],
312
+ });
313
+ yield event("response.content_part.done", {
314
+ item_id: itemId,
315
+ output_index: 0,
316
+ content_index: 0,
317
+ part: item.content[0]!,
318
+ });
319
+ yield event("response.output_item.done", { output_index: 0, item });
320
+ yield event(`response.${status}`, {
321
+ response: responseObject(
322
+ chunk.reply,
323
+ chat,
324
+ model,
325
+ status,
326
+ previousResponseId,
327
+ chunk.reply.text,
328
+ identity,
329
+ ),
330
+ });
331
+ }
332
+ }
333
+ } catch (error) {
334
+ const summary = chatErrorSummary(error);
335
+ yield event("error", { code: summary.code, message: summary.message, param: null });
336
+ }
337
+ }
338
+
339
+ /** Text of the Responses `input`: a string, or the last user item's `input_text` parts. */
340
+ export function responsesInputText(input: unknown): string | null {
341
+ if (typeof input === "string") return input;
342
+ if (!Array.isArray(input)) return null;
343
+ const fromRole = lastUserMessageText(input, ["input_text", "text"], "content");
344
+ if (fromRole !== null) return fromRole;
345
+ const last = input.at(-1);
346
+ return last && typeof last === "object"
347
+ ? messageContentText((last as { content?: unknown }).content, ["input_text", "text"])
348
+ : null;
349
+ }
350
+
351
+ /**
352
+ * `POST /v1/responses` shape: `{ input, previous_response_id?, conversation?, stream?, model?, user? }`.
353
+ * Conversation: the host's `resolve`, else the `x-opengeni-conversation`
354
+ * header, else `conversation` / `conversation.id`. A `previous_response_id`
355
+ * may continue the session it encodes only when the host named the user that
356
+ * session belongs to (403 otherwise); when a conversation is named as well,
357
+ * the two must agree (409). 400 when nothing names a conversation. Only the
358
+ * last user item is sent; earlier array items are imported once as context
359
+ * when this request creates the session.
360
+ */
361
+ export async function handleResponsesRequest(
362
+ og: Opengeni,
363
+ request: Request,
364
+ resolve: ChatResolve,
365
+ ): Promise<Response> {
366
+ const body = await readJsonObject(request);
367
+ const prompt = responsesInputText(body?.input)?.trim() ?? "";
368
+ if (!prompt) return errorResponse(400, "The input has no user text.", "message_required");
369
+ const resolved = await resolveChatRequest(request, resolve);
370
+ if (resolved.response) return resolved.response;
371
+ const previousResponseId =
372
+ typeof body?.previous_response_id === "string" ? body.previous_response_id : null;
373
+ const conversation = clientConversation(request, conversationFromBody(body));
374
+ const previousSessionId = decodeResponseId(previousResponseId);
375
+ const namedConversation =
376
+ resolved.resolution.conversation ?? (resolved.resolution.user ? conversation : undefined);
377
+ let chat: Chat;
378
+ if (previousSessionId && !namedConversation) {
379
+ if (!resolved.resolution.user) {
380
+ return errorResponse(
381
+ 400,
382
+ "Return a conversation from resolve, or a user so client conversation ids are scoped to that user.",
383
+ "conversation_required",
384
+ );
385
+ }
386
+ try {
387
+ const workspaceId = await og.workspaceId(resolved.resolution);
388
+ chat = await og.chatBySessionId({
389
+ workspaceId,
390
+ sessionId: previousSessionId,
391
+ user: resolved.resolution.user,
392
+ });
393
+ } catch (error) {
394
+ const summary = chatErrorSummary(error);
395
+ return errorResponse(summary.status, summary.message, summary.code);
396
+ }
397
+ } else {
398
+ const opened = await openResolvedChat(og, resolved.resolution, conversation);
399
+ if (opened.response) return opened.response;
400
+ chat = opened.chat;
401
+ if (previousSessionId && previousSessionId !== chat.sessionId) {
402
+ const summary = chatErrorSummary(
403
+ new OpenGeniChatError(
404
+ "conversation_mismatch",
405
+ "previous_response_id belongs to a different conversation than the one named for this request.",
406
+ ),
407
+ );
408
+ return errorResponse(summary.status, summary.message, summary.code);
409
+ }
410
+ }
411
+ const model = modelLabel(body);
412
+ const importedHistory = importedHistoryBefore(
413
+ body?.input,
414
+ ["input_text", "text", "output_text"],
415
+ "content",
416
+ );
417
+ if (wantsStream(body)) {
418
+ const chunks = chat.stream(prompt, { signal: request.signal, importedHistory });
419
+ return new Response(sseByteStream(responsesBlocks(chat, chunks, model, previousResponseId)), {
420
+ headers: sseHeaders(),
421
+ });
422
+ }
423
+ try {
424
+ const reply = await chat.send(prompt, { signal: request.signal, importedHistory });
425
+ return jsonResponse(
426
+ responseObject(
427
+ reply,
428
+ chat,
429
+ model,
430
+ reply.status === "completed" ? "completed" : "incomplete",
431
+ previousResponseId,
432
+ reply.text,
433
+ ),
434
+ );
435
+ } catch (error) {
436
+ const summary = chatErrorSummary(error);
437
+ return errorResponse(summary.status, summary.message, summary.code);
438
+ }
439
+ }