@superxd-studio/adptr 0.5.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 (221) hide show
  1. package/GENERATIVE.md +101 -0
  2. package/INTERACTIONS.md +51 -0
  3. package/LICENSE +21 -0
  4. package/README.md +57 -0
  5. package/REUSE.md +170 -0
  6. package/REVIEW.md +68 -0
  7. package/THIRD-PARTY-NOTICES.md +28 -0
  8. package/ai-sdk/index.js +96 -0
  9. package/assets/ibm-plex-mono-cyrillic-400-normal.woff2 +0 -0
  10. package/assets/ibm-plex-mono-cyrillic-500-normal.woff2 +0 -0
  11. package/assets/ibm-plex-mono-cyrillic-ext-400-normal.woff2 +0 -0
  12. package/assets/ibm-plex-mono-cyrillic-ext-500-normal.woff2 +0 -0
  13. package/assets/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  14. package/assets/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  15. package/assets/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  16. package/assets/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  17. package/assets/ibm-plex-mono-vietnamese-400-normal.woff2 +0 -0
  18. package/assets/ibm-plex-mono-vietnamese-500-normal.woff2 +0 -0
  19. package/assets/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
  20. package/assets/instrument-sans-latin-wght-normal.woff2 +0 -0
  21. package/assistant-ui/index.js +139 -0
  22. package/chunks/callout-DKMHSnjc.js +342 -0
  23. package/chunks/canvas-BJ15-XxE.js +598 -0
  24. package/chunks/controller-Cfpg0C7v.js +180 -0
  25. package/chunks/core-BCqVRLUy.js +312 -0
  26. package/chunks/text-field-BLtoaZFn.js +41 -0
  27. package/chunks/types-BGF7r1my.js +115 -0
  28. package/chunks/ui-9DyNsYXY.js +967 -0
  29. package/core/index.js +4 -0
  30. package/custom/index.js +64 -0
  31. package/examples/ai-sdk-backend/package.json +11 -0
  32. package/examples/ai-sdk-backend/server.mjs +316 -0
  33. package/examples/custom-backend/copy.mjs +7 -0
  34. package/examples/custom-backend/server.mjs +118 -0
  35. package/examples/generative/specs.json +2094 -0
  36. package/examples/review-backend/copy.mjs +27 -0
  37. package/examples/review-backend/package.json +11 -0
  38. package/examples/review-backend/server.mjs +276 -0
  39. package/examples/web-ai-sdk/index.html +12 -0
  40. package/examples/web-ai-sdk/package.json +21 -0
  41. package/examples/web-ai-sdk/src/copy.ts +1 -0
  42. package/examples/web-ai-sdk/src/main.tsx +64 -0
  43. package/examples/web-ai-sdk/tsconfig.json +13 -0
  44. package/examples/web-generative/index.html +12 -0
  45. package/examples/web-generative/package.json +23 -0
  46. package/examples/web-generative/src/copy.ts +42 -0
  47. package/examples/web-generative/src/main.tsx +74 -0
  48. package/examples/web-generative/tsconfig.json +14 -0
  49. package/examples/web-independent/index.html +12 -0
  50. package/examples/web-independent/package.json +20 -0
  51. package/examples/web-independent/src/main.tsx +32 -0
  52. package/examples/web-independent/tsconfig.json +14 -0
  53. package/examples/web-interactions/index.html +12 -0
  54. package/examples/web-interactions/package.json +21 -0
  55. package/examples/web-interactions/src/copy.ts +215 -0
  56. package/examples/web-interactions/src/examples.tsx +628 -0
  57. package/examples/web-interactions/src/main.tsx +116 -0
  58. package/examples/web-interactions/tsconfig.json +14 -0
  59. package/examples/web-review/index.html +12 -0
  60. package/examples/web-review/package.json +20 -0
  61. package/examples/web-review/src/main.tsx +59 -0
  62. package/examples/web-review/tsconfig.json +14 -0
  63. package/fonts.css +117 -0
  64. package/generative/index.js +1560 -0
  65. package/interactions/index.js +1591 -0
  66. package/langgraph-review/index.js +36 -0
  67. package/licenses/ai-sdk.txt +13 -0
  68. package/licenses/assistant-ui-generative.txt +21 -0
  69. package/licenses/assistant-ui.txt +23 -0
  70. package/licenses/ibm-plex-mono.txt +93 -0
  71. package/licenses/instrument-sans.txt +93 -0
  72. package/licenses/internationalized-date.txt +201 -0
  73. package/licenses/langgraph.txt +21 -0
  74. package/licenses/lucide.txt +43 -0
  75. package/licenses/react-markdown.txt +21 -0
  76. package/licenses/remark-gfm.txt +22 -0
  77. package/licenses/zod.txt +21 -0
  78. package/package.json +138 -0
  79. package/review/index.js +31 -0
  80. package/source/components/avatar.tsx +50 -0
  81. package/source/components/button.tsx +155 -0
  82. package/source/components/callout.tsx +65 -0
  83. package/source/components/canvas/connector.tsx +14 -0
  84. package/source/components/canvas/copy.ts +34 -0
  85. package/source/components/canvas/index.tsx +257 -0
  86. package/source/components/canvas/recorder.ts +36 -0
  87. package/source/components/date-time-field.tsx +77 -0
  88. package/source/components/flow-motion.tsx +109 -0
  89. package/source/components/icons.tsx +275 -0
  90. package/source/components/responsive-overlay.tsx +91 -0
  91. package/source/components/scenario-picker/copy.ts +5 -0
  92. package/source/components/scenario-picker/index.tsx +142 -0
  93. package/source/components/scroll-area.tsx +59 -0
  94. package/source/components/search-picker.tsx +127 -0
  95. package/source/components/segmented-control.tsx +123 -0
  96. package/source/components/skeleton.tsx +11 -0
  97. package/source/components/spinner.tsx +19 -0
  98. package/source/components/superxd-arrow.tsx +22 -0
  99. package/source/components/switch.tsx +45 -0
  100. package/source/components/text-field.tsx +81 -0
  101. package/source/components/thinking.tsx +16 -0
  102. package/source/components/ui/copy.ts +12 -0
  103. package/source/components/ui/display.tsx +276 -0
  104. package/source/components/ui/navigation.tsx +346 -0
  105. package/source/components/ui/portal-theme.tsx +28 -0
  106. package/source/components/ui/selection.tsx +336 -0
  107. package/source/lib/cn.ts +23 -0
  108. package/source/reusable/ai-sdk/index.ts +114 -0
  109. package/source/reusable/assistant-ui/index.tsx +266 -0
  110. package/source/reusable/core/attachments.ts +163 -0
  111. package/source/reusable/core/index.ts +417 -0
  112. package/source/reusable/custom/index.ts +90 -0
  113. package/source/reusable/generative/actions.ts +188 -0
  114. package/source/reusable/generative/copy.ts +47 -0
  115. package/source/reusable/generative/data.tsx +126 -0
  116. package/source/reusable/generative/index.tsx +517 -0
  117. package/source/reusable/generative/schema.ts +327 -0
  118. package/source/reusable/generative/timeline.tsx +214 -0
  119. package/source/reusable/generative/vocabulary.tsx +847 -0
  120. package/source/reusable/interactions/agent.ts +259 -0
  121. package/source/reusable/interactions/artifact.ts +236 -0
  122. package/source/reusable/interactions/context.tsx +38 -0
  123. package/source/reusable/interactions/copy.ts +117 -0
  124. package/source/reusable/interactions/feedback.ts +73 -0
  125. package/source/reusable/interactions/index.ts +11 -0
  126. package/source/reusable/interactions/presentation.tsx +483 -0
  127. package/source/reusable/interactions/store.ts +74 -0
  128. package/source/reusable/interactions/threads.ts +126 -0
  129. package/source/reusable/interactions/web.tsx +574 -0
  130. package/source/reusable/langgraph-review/index.ts +73 -0
  131. package/source/reusable/review/controller.ts +263 -0
  132. package/source/reusable/review/index.ts +47 -0
  133. package/source/reusable/review/types.ts +189 -0
  134. package/source/reusable/ui/index.ts +25 -0
  135. package/source/reusable/uploads/index.ts +48 -0
  136. package/source/reusable/web/attachments/copy.ts +23 -0
  137. package/source/reusable/web/attachments/index.tsx +233 -0
  138. package/source/reusable/web/copy.ts +22 -0
  139. package/source/reusable/web/index.tsx +470 -0
  140. package/source/reusable/web/message/copy.ts +26 -0
  141. package/source/reusable/web/message/index.tsx +388 -0
  142. package/source/reusable/web/message/streaming.ts +64 -0
  143. package/source/reusable/web/message/styles.css +21 -0
  144. package/source/reusable/web/review/copy.ts +78 -0
  145. package/source/reusable/web/review/flow.ts +24 -0
  146. package/source/reusable/web/review/index.tsx +506 -0
  147. package/source/reusable/web/styles.ts +2 -0
  148. package/source/styles/library.css +612 -0
  149. package/source/styles/theme.css +228 -0
  150. package/styles.css +3 -0
  151. package/styles.d.ts +1 -0
  152. package/types/components/avatar.d.ts +14 -0
  153. package/types/components/button.d.ts +36 -0
  154. package/types/components/callout.d.ts +19 -0
  155. package/types/components/canvas/connector.d.ts +2 -0
  156. package/types/components/canvas/copy.d.ts +34 -0
  157. package/types/components/canvas/index.d.ts +33 -0
  158. package/types/components/canvas/recorder.d.ts +10 -0
  159. package/types/components/date-time-field.d.ts +11 -0
  160. package/types/components/flow-motion.d.ts +38 -0
  161. package/types/components/icons.d.ts +138 -0
  162. package/types/components/responsive-overlay.d.ts +15 -0
  163. package/types/components/scenario-picker/copy.d.ts +5 -0
  164. package/types/components/scenario-picker/index.d.ts +14 -0
  165. package/types/components/scroll-area.d.ts +14 -0
  166. package/types/components/search-picker.d.ts +27 -0
  167. package/types/components/segmented-control.d.ts +23 -0
  168. package/types/components/skeleton.d.ts +4 -0
  169. package/types/components/spinner.d.ts +4 -0
  170. package/types/components/superxd-arrow.d.ts +5 -0
  171. package/types/components/switch.d.ts +8 -0
  172. package/types/components/text-field.d.ts +19 -0
  173. package/types/components/thinking.d.ts +8 -0
  174. package/types/components/ui/copy.d.ts +12 -0
  175. package/types/components/ui/display.d.ts +50 -0
  176. package/types/components/ui/navigation.d.ts +63 -0
  177. package/types/components/ui/portal-theme.d.ts +8 -0
  178. package/types/components/ui/selection.d.ts +37 -0
  179. package/types/lib/cn.d.ts +3 -0
  180. package/types/reusable/ai-sdk/index.d.ts +15 -0
  181. package/types/reusable/assistant-ui/index.d.ts +12 -0
  182. package/types/reusable/core/attachments.d.ts +54 -0
  183. package/types/reusable/core/index.d.ts +107 -0
  184. package/types/reusable/custom/index.d.ts +14 -0
  185. package/types/reusable/generative/actions.d.ts +45 -0
  186. package/types/reusable/generative/copy.d.ts +48 -0
  187. package/types/reusable/generative/data.d.ts +4 -0
  188. package/types/reusable/generative/index.d.ts +30 -0
  189. package/types/reusable/generative/schema.d.ts +303 -0
  190. package/types/reusable/generative/timeline.d.ts +21 -0
  191. package/types/reusable/generative/vocabulary.d.ts +34 -0
  192. package/types/reusable/interactions/agent.d.ts +98 -0
  193. package/types/reusable/interactions/artifact.d.ts +118 -0
  194. package/types/reusable/interactions/context.d.ts +130 -0
  195. package/types/reusable/interactions/copy.d.ts +115 -0
  196. package/types/reusable/interactions/feedback.d.ts +34 -0
  197. package/types/reusable/interactions/index.d.ts +10 -0
  198. package/types/reusable/interactions/presentation.d.ts +97 -0
  199. package/types/reusable/interactions/store.d.ts +29 -0
  200. package/types/reusable/interactions/threads.d.ts +38 -0
  201. package/types/reusable/interactions/web.d.ts +22 -0
  202. package/types/reusable/langgraph-review/index.d.ts +5 -0
  203. package/types/reusable/review/controller.d.ts +52 -0
  204. package/types/reusable/review/index.d.ts +12 -0
  205. package/types/reusable/review/types.d.ts +61 -0
  206. package/types/reusable/ui/index.d.ts +19 -0
  207. package/types/reusable/uploads/index.d.ts +11 -0
  208. package/types/reusable/web/attachments/copy.d.ts +25 -0
  209. package/types/reusable/web/attachments/index.d.ts +24 -0
  210. package/types/reusable/web/copy.d.ts +24 -0
  211. package/types/reusable/web/index.d.ts +41 -0
  212. package/types/reusable/web/message/copy.d.ts +27 -0
  213. package/types/reusable/web/message/index.d.ts +56 -0
  214. package/types/reusable/web/message/streaming.d.ts +27 -0
  215. package/types/reusable/web/review/copy.d.ts +72 -0
  216. package/types/reusable/web/review/flow.d.ts +3 -0
  217. package/types/reusable/web/review/index.d.ts +38 -0
  218. package/types/reusable/web/styles.d.ts +2 -0
  219. package/ui/index.js +4 -0
  220. package/uploads/index.js +34 -0
  221. package/web/index.js +931 -0
package/core/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "../chunks/core-BCqVRLUy.js";
2
+ import { a as o, i as s, n as c, o as l, r as u, t as d } from "../chunks/types-BGF7r1my.js";
3
+ import { n as f, t as p } from "../chunks/controller-Cfpg0C7v.js";
4
+ export { r as AttachmentController, a as ChatController, n as ChatInterrupted, p as ReviewController, d as ReviewFault, i as parseChatEvent, t as parseChatFile, e as parseChatRequest, c as parseReviewDecision, u as parseReviewProposal, s as parseReviewReceipt, o as parseReviewRecord, l as parseReviewRecovery, f as reviewReducer };
@@ -0,0 +1,64 @@
1
+ import { n as e, r as t } from "../chunks/core-BCqVRLUy.js";
2
+ //#region src/reusable/custom/index.ts
3
+ function n(n) {
4
+ return {
5
+ capabilities: {
6
+ send: !0,
7
+ cancel: !0,
8
+ retry: n.retry ?? !0,
9
+ ...n.conversationActions ? {
10
+ edit: !0,
11
+ regenerate: !0,
12
+ branches: !0
13
+ } : {}
14
+ },
15
+ async *run(r, i) {
16
+ let a = await (n.fetch ?? globalThis.fetch)(n.endpoint, {
17
+ method: "POST",
18
+ signal: i,
19
+ credentials: n.credentials ?? "same-origin",
20
+ headers: {
21
+ ...Object.fromEntries(new Headers(await n.headers?.()).entries()),
22
+ "content-type": "application/json"
23
+ },
24
+ body: JSON.stringify(r)
25
+ });
26
+ if (!a.ok || !a.body || !a.headers.get("content-type")?.startsWith("application/x-ndjson")) throw Error("Response unavailable");
27
+ let o = a.body.getReader(), s = new TextDecoder(), c = "", l = !1, u = () => {
28
+ o.cancel().catch(() => {});
29
+ };
30
+ i.addEventListener("abort", u, { once: !0 });
31
+ try {
32
+ if (i.aborted) return;
33
+ for (; !l;) {
34
+ let { done: n, value: r } = await o.read();
35
+ if (i.aborted) return;
36
+ c += s.decode(r, { stream: !n });
37
+ let a;
38
+ for (; (a = c.indexOf("\n")) !== -1;) {
39
+ if (a > 2e5) throw Error("Stream frame too large");
40
+ let e = c.slice(0, a);
41
+ if (c = c.slice(a + 1), !e.trim()) continue;
42
+ let n = t(JSON.parse(e));
43
+ if (yield n, n.type === "finish" || n.type === "error") {
44
+ l = !0;
45
+ break;
46
+ }
47
+ }
48
+ if (!l && c.length > 2e5) throw Error("Stream frame too large");
49
+ if (n) {
50
+ if (!l) throw new e();
51
+ break;
52
+ }
53
+ }
54
+ } catch (t) {
55
+ if (i.aborted) return;
56
+ throw t instanceof TypeError ? new e() : t;
57
+ } finally {
58
+ i.removeEventListener("abort", u), await o.cancel().catch(() => {}), o.releaseLock();
59
+ }
60
+ }
61
+ };
62
+ }
63
+ //#endregion
64
+ export { n as createHttpChatAdapter };
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "superxd-sdk-server-example",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "node server.mjs"
7
+ },
8
+ "dependencies": {
9
+ "ai": "7.0.126"
10
+ }
11
+ }
@@ -0,0 +1,316 @@
1
+ import { createServer } from "node:http";
2
+ import { setTimeout as delay } from "node:timers/promises";
3
+ import { pathToFileURL } from "node:url";
4
+ import { realpathSync } from "node:fs";
5
+ import { randomUUID, createHash } from "node:crypto";
6
+ import { createUIMessageStream, pipeUIMessageStreamToResponse, safeValidateUIMessages } from "ai";
7
+ import { parseChatRequest } from "../../core/index.js";
8
+
9
+ const allowedTypes = ["image/png", "image/jpeg", "application/pdf", "text/plain"];
10
+ const maxFileBytes = 5_000_000;
11
+ const maxStoredBytes = 20_000_000;
12
+ const lifetime = 15 * 60_000;
13
+
14
+ async function readBody(request, limit) {
15
+ const chunks = [];
16
+ let size = 0;
17
+ for await (const chunk of request) {
18
+ size += chunk.length;
19
+ if (size > limit) throw new Error("Body too large");
20
+ chunks.push(chunk);
21
+ }
22
+ return Buffer.concat(chunks);
23
+ }
24
+ function contentMatches(bytes, mediaType) {
25
+ if (!bytes.length) return false;
26
+ if (mediaType === "image/png")
27
+ return bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
28
+ if (mediaType === "image/jpeg") return bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
29
+ if (mediaType === "application/pdf") return bytes.subarray(0, 5).toString() === "%PDF-";
30
+ if (mediaType === "text/plain") {
31
+ try {
32
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes);
33
+ return !bytes.includes(0);
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+
41
+ /** Executable SDK + upload integration. Temporary loopback storage; no model or production auth. */
42
+ export function createAiSdkServer({ wait = 100 } = {}) {
43
+ const stored = new Map();
44
+ const uploadIds = new Map();
45
+ const pendingUploads = new Set();
46
+ const seen = new Set();
47
+ const failed = new Set();
48
+ // Keep the most recent fixture identities instead of forgetting the entire window at once.
49
+ const remember = (set, key) => {
50
+ set.add(key);
51
+ if (set.size > 500) set.delete(set.values().next().value);
52
+ };
53
+ let storedBytes = 0;
54
+ let reservedBytes = 0;
55
+ const forget = (id) => {
56
+ const entry = stored.get(id);
57
+ if (entry) {
58
+ uploadIds.delete(entry.uploadId);
59
+ storedBytes -= entry.bytes.length;
60
+ stored.delete(id);
61
+ }
62
+ };
63
+ const prune = () => {
64
+ for (const [id, entry] of stored) if (entry.expires < Date.now()) forget(id);
65
+ };
66
+ const server = createServer(async (request, response) => {
67
+ if (request.headers.host !== `127.0.0.1:${request.socket.localPort}`) {
68
+ response.writeHead(403).end();
69
+ return;
70
+ }
71
+ prune();
72
+ const origin = request.headers.origin;
73
+ if (
74
+ origin &&
75
+ !["http://127.0.0.1:5190", "http://127.0.0.1:5300", "http://127.0.0.1:5301"].includes(origin)
76
+ ) {
77
+ response.writeHead(403).end();
78
+ return;
79
+ }
80
+ if (origin) response.setHeader("access-control-allow-origin", origin);
81
+ response.setHeader("vary", "Origin");
82
+ response.setHeader("cache-control", "no-store");
83
+ response.setHeader("x-content-type-options", "nosniff");
84
+ if (request.method === "OPTIONS") {
85
+ response
86
+ .writeHead(204, {
87
+ "access-control-allow-methods": "POST,GET,DELETE",
88
+ "access-control-allow-headers":
89
+ "content-type,x-file-name,x-upload-id,x-demo-scenario,x-demo-session,x-upload-scenario",
90
+ })
91
+ .end();
92
+ return;
93
+ }
94
+ if (request.url === "/health" && request.method === "GET") {
95
+ response.end("ready");
96
+ return;
97
+ }
98
+ const fileId = /^\/files\/([\w-]{1,100})$/.exec(request.url)?.[1];
99
+ if (fileId && ["GET", "DELETE"].includes(request.method)) {
100
+ const entry = stored.get(fileId);
101
+ if (!entry) {
102
+ response.writeHead(404).end();
103
+ return;
104
+ }
105
+ if (request.method === "DELETE") {
106
+ if (entry.claimed) {
107
+ response.writeHead(409).end();
108
+ return;
109
+ }
110
+ forget(fileId);
111
+ response.writeHead(204).end();
112
+ return;
113
+ }
114
+ response.setHeader("content-type", entry.file.mediaType);
115
+ response.setHeader(
116
+ "content-disposition",
117
+ `attachment; filename*=UTF-8''${encodeURIComponent(entry.file.name)}`,
118
+ );
119
+ response.end(entry.bytes);
120
+ return;
121
+ }
122
+ const abort = new AbortController();
123
+ response.on("close", () => abort.abort());
124
+ if (request.url === "/uploads" && request.method === "POST") {
125
+ const uploadId = String(request.headers["x-upload-id"] ?? "");
126
+ if (!/^[\w-]{1,100}$/.test(uploadId)) {
127
+ response.writeHead(400).end();
128
+ return;
129
+ }
130
+ if (pendingUploads.has(uploadId)) {
131
+ response.writeHead(409).end();
132
+ return;
133
+ }
134
+ const length = Number(request.headers["content-length"]);
135
+ const mediaType = request.headers["content-type"];
136
+ if (
137
+ !allowedTypes.includes(mediaType) ||
138
+ !Number.isSafeInteger(length) ||
139
+ length < 1 ||
140
+ length > maxFileBytes
141
+ ) {
142
+ response.writeHead(415).end();
143
+ return;
144
+ }
145
+ if (
146
+ storedBytes + reservedBytes + length > maxStoredBytes ||
147
+ stored.size + pendingUploads.size >= 100
148
+ ) {
149
+ response.writeHead(503).end();
150
+ return;
151
+ }
152
+ reservedBytes += length;
153
+ pendingUploads.add(uploadId);
154
+ try {
155
+ const name = decodeURIComponent(String(request.headers["x-file-name"] ?? ""));
156
+ if (
157
+ !name.trim() ||
158
+ name.length > 255 ||
159
+ Array.from(name).some((char) => char.charCodeAt(0) < 32)
160
+ )
161
+ throw new Error("Invalid filename");
162
+ const bytes = await readBody(request, maxFileBytes);
163
+ if (bytes.length !== length || !contentMatches(bytes, mediaType))
164
+ throw new Error("Invalid content");
165
+ const digest = createHash("sha256").update(bytes).digest("hex");
166
+ const existing = stored.get(uploadIds.get(uploadId));
167
+ if (existing) {
168
+ if (
169
+ existing.digest !== digest ||
170
+ existing.file.name !== name ||
171
+ existing.file.mediaType !== mediaType
172
+ )
173
+ throw new Error("Upload identifier mismatch");
174
+ response
175
+ .writeHead(200, { "content-type": "application/json" })
176
+ .end(JSON.stringify(existing.file));
177
+ return;
178
+ }
179
+ await delay(request.headers["x-upload-scenario"] === "slow" ? wait * 20 : wait, undefined, {
180
+ signal: abort.signal,
181
+ });
182
+ const key = `upload:${request.headers["x-demo-session"]}:${name}`;
183
+ if (request.headers["x-upload-scenario"] === "fail-once" && !failed.has(key)) {
184
+ remember(failed, key);
185
+ response.writeHead(503).end();
186
+ return;
187
+ }
188
+ if (abort.signal.aborted) return;
189
+ const id = randomUUID();
190
+ const file = {
191
+ id,
192
+ name,
193
+ mediaType,
194
+ size: bytes.length,
195
+ url: `http://127.0.0.1:${server.address().port}/files/${id}`,
196
+ };
197
+ stored.set(id, {
198
+ file,
199
+ bytes,
200
+ expires: Date.now() + lifetime,
201
+ claimed: false,
202
+ uploadId,
203
+ digest,
204
+ });
205
+ uploadIds.set(uploadId, id);
206
+ storedBytes += bytes.length;
207
+ response.writeHead(201, { "content-type": "application/json" }).end(JSON.stringify(file));
208
+ } catch {
209
+ if (!abort.signal.aborted) response.writeHead(400).end();
210
+ } finally {
211
+ reservedBytes -= length;
212
+ pendingUploads.delete(uploadId);
213
+ }
214
+ return;
215
+ }
216
+ if (request.url !== "/chat" || request.method !== "POST") {
217
+ response.writeHead(404).end();
218
+ return;
219
+ }
220
+ try {
221
+ const body = JSON.parse((await readBody(request, 512_000)).toString("utf8"));
222
+ const validated = await safeValidateUIMessages({ messages: body.messages });
223
+ if (!validated.success) throw new Error("Invalid SDK messages");
224
+ const input = parseChatRequest({
225
+ requestId: body.requestId,
226
+ messages: validated.data.map((message) => {
227
+ if (message.parts.some((part) => !["text", "file"].includes(part.type)))
228
+ throw new Error("Unsupported part");
229
+ const files = message.metadata?.superxdFiles ?? [];
230
+ const parts = message.parts.filter((part) => part.type === "file");
231
+ if (
232
+ !Array.isArray(files) ||
233
+ parts.length !== files.length ||
234
+ parts.some(
235
+ (part, index) =>
236
+ part.url !== files[index]?.url ||
237
+ part.filename !== files[index]?.name ||
238
+ part.mediaType !== files[index]?.mediaType,
239
+ )
240
+ )
241
+ throw new Error("Invalid file parts");
242
+ return {
243
+ id: message.id,
244
+ role: message.role,
245
+ text: message.parts
246
+ .filter((part) => part.type === "text")
247
+ .map((part) => part.text)
248
+ .join(""),
249
+ files,
250
+ };
251
+ }),
252
+ });
253
+ for (const message of input.messages)
254
+ for (const file of message.files ?? []) {
255
+ const entry = stored.get(file.id);
256
+ if (!entry || JSON.stringify(entry.file) !== JSON.stringify(file))
257
+ throw new Error("Unknown file");
258
+ }
259
+ if (seen.has(input.requestId)) {
260
+ response.writeHead(409).end();
261
+ return;
262
+ }
263
+ remember(seen, input.requestId);
264
+ for (const message of input.messages)
265
+ for (const file of message.files ?? []) stored.get(file.id).claimed = true;
266
+ const last = input.messages.at(-1);
267
+ const scenario = request.headers["x-demo-scenario"];
268
+ const key = `chat:${request.headers["x-demo-session"]}:${last.id}`;
269
+ const stream = createUIMessageStream({
270
+ onError: () => "The response could not finish.",
271
+ execute: async ({ writer }) => {
272
+ writer.write({
273
+ type: "start",
274
+ messageId: randomUUID(),
275
+ messageMetadata: { integration: "sdk-example", receivedFiles: last.files?.length ?? 0 },
276
+ });
277
+ writer.write({ type: "text-start", id: "response" });
278
+ const chunks = [
279
+ "Start with the person and the task they need to finish.\n\n",
280
+ ...(last.files?.length
281
+ ? [
282
+ `Received ${last.files.length} verified file reference${last.files.length === 1 ? "" : "s"}: ${last.files.map((file) => file.name).join(", ")}.\n\n`,
283
+ ]
284
+ : []),
285
+ last.text || "Review the attached files.",
286
+ "\n\nThis is a deterministic SDK response; no model is connected.",
287
+ ];
288
+ for (let index = 0; index < chunks.length; index++) {
289
+ await delay(scenario === "slow" ? wait * 8 : wait, undefined, { signal: abort.signal });
290
+ if (index === 1 && scenario === "fail-once" && !failed.has(key)) {
291
+ remember(failed, key);
292
+ throw new Error("Demo failure");
293
+ }
294
+ // No finish event: the client must retain an interrupted response, not claim success.
295
+ if (index === 1 && scenario === "interrupted") return;
296
+ writer.write({ type: "text-delta", id: "response", delta: chunks[index] });
297
+ }
298
+ writer.write({ type: "text-end", id: "response" });
299
+ writer.write({ type: "finish", finishReason: "stop" });
300
+ },
301
+ });
302
+ await pipeUIMessageStreamToResponse({ response, stream });
303
+ } catch {
304
+ if (abort.signal.aborted) return;
305
+ if (!response.headersSent) response.writeHead(400).end();
306
+ else response.end();
307
+ }
308
+ });
309
+ return server;
310
+ }
311
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
312
+ const port = Number(process.env.SUPERXD_EXAMPLE_PORT ?? 5193);
313
+ createAiSdkServer().listen(port, "127.0.0.1", () =>
314
+ console.log(`Local SDK/upload example ready at http://127.0.0.1:${port}`),
315
+ );
316
+ }
@@ -0,0 +1,7 @@
1
+ /** Sample content for the executable library example. No provider or generated content. */
2
+ export const exampleCopy = {
3
+ alternate:
4
+ '## Start with their unfinished work\n\nA second way to frame the same brief is to focus on what people can return to.\n\n1. **Keep their draft.** Save the useful work they have already done.\n2. **Name the next step.** Help them continue without starting again.\n3. **Show what changed.** Make progress easy to recognize.\n\n```ts\nconst nextStep = draft.completed ? "review" : "continue";\n```\n\nThis is another sample response; no model is connected.',
5
+ response:
6
+ '## A clearer first step\n\nHelp people finish one useful action before asking them to configure everything.\n\n### What to include\n\n1. A clear next step\n2. A useful default they can change\n3. A way to return to unfinished work\n\n> Keep the person’s progress when they leave and return.\n\nAn example of the state you might preserve:\n\n```ts\nconst draft = {\n step: "first-action",\n completed: false,\n title: "My first project",\n};\n```\n\nUse `completed` to distinguish a draft from finished work.\n\n| Moment | Keep available |\n| --- | --- |\n| Working | The next useful action |\n| Returning | Their unfinished draft |\n\nSee [React’s state guide](https://react.dev/learn/state-a-components-memory) for the implementation pattern.\n\nThis is a sample response; no model is connected.',
7
+ };
@@ -0,0 +1,118 @@
1
+ import { createServer } from "node:http";
2
+ import { setTimeout as delay } from "node:timers/promises";
3
+ import { pathToFileURL } from "node:url";
4
+ import { realpathSync } from "node:fs";
5
+ import { parseChatRequest } from "../../core/index.js";
6
+ import { exampleCopy } from "./copy.mjs";
7
+
8
+ /** Deterministic integration server. Loopback only, no model, auth, tools or side effects. */
9
+ export function createChatServer({ wait = 100 } = {}) {
10
+ const seen = new Set();
11
+ const failed = new Set();
12
+ const richRuns = new Map();
13
+ return createServer(async (request, response) => {
14
+ const origin = request.headers.origin;
15
+ if (
16
+ origin &&
17
+ !["http://127.0.0.1:5190", "http://127.0.0.1:5300", "http://127.0.0.1:5301"].includes(origin)
18
+ ) {
19
+ response.writeHead(403).end();
20
+ return;
21
+ }
22
+ if (origin) response.setHeader("access-control-allow-origin", origin);
23
+ response.setHeader("vary", "Origin");
24
+ if (request.method === "OPTIONS") {
25
+ response
26
+ .writeHead(204, {
27
+ "access-control-allow-methods": "POST",
28
+ "access-control-allow-headers":
29
+ "content-type,x-demo-scenario,x-demo-session,x-demo-format",
30
+ })
31
+ .end();
32
+ return;
33
+ }
34
+ if (request.url === "/health" && request.method === "GET") {
35
+ response.end("ready");
36
+ return;
37
+ }
38
+ if (request.url !== "/chat" || request.method !== "POST") {
39
+ response.writeHead(404).end();
40
+ return;
41
+ }
42
+ const controller = new AbortController();
43
+ response.on("close", () => controller.abort());
44
+ try {
45
+ const parts = [];
46
+ let size = 0;
47
+ for await (const chunk of request) {
48
+ parts.push(chunk);
49
+ size += chunk.length;
50
+ if (size > 128_000) {
51
+ response.writeHead(413).end();
52
+ return;
53
+ }
54
+ }
55
+ const body = Buffer.concat(parts).toString("utf8");
56
+ const input = parseChatRequest(JSON.parse(body));
57
+ if (seen.has(input.requestId)) {
58
+ response.writeHead(409).end();
59
+ return;
60
+ }
61
+ // Bounded process-local deduplication for this side-effect-free example only.
62
+ if (seen.size > 500) seen.clear();
63
+ seen.add(input.requestId);
64
+ const text = input.messages.at(-1).text;
65
+ const scenario = request.headers["x-demo-scenario"];
66
+ const key = String(request.headers["x-demo-session"]) + text;
67
+ const run = (richRuns.get(key) ?? 0) + 1;
68
+ if (request.headers["x-demo-format"] === "rich") {
69
+ if (richRuns.size > 500) richRuns.clear();
70
+ richRuns.set(key, run);
71
+ }
72
+ response.writeHead(200, {
73
+ "content-type": "application/x-ndjson; charset=utf-8",
74
+ "cache-control": "no-store",
75
+ "x-content-type-options": "nosniff",
76
+ });
77
+ await delay(wait, undefined, { signal: controller.signal });
78
+ const chunks =
79
+ request.headers["x-demo-format"] === "rich"
80
+ ? ((run % 2 === 0 ? exampleCopy.alternate : exampleCopy.response).match(
81
+ /[\s\S]{1,80}/g,
82
+ ) ?? [])
83
+ : [
84
+ "Start with the person and the task they need to finish.\n\n",
85
+ "Choose one useful improvement for: ",
86
+ text,
87
+ "\n\nKeep the first release small enough to learn from.",
88
+ ];
89
+ for (let index = 0; index < chunks.length; index++) {
90
+ if (index === 1 && scenario === "fail-once" && !failed.has(key)) {
91
+ if (failed.size > 500) failed.clear();
92
+ failed.add(key);
93
+ response.end(JSON.stringify({ type: "error" }) + "\n");
94
+ return;
95
+ }
96
+ if (index === 1 && scenario === "interrupted") {
97
+ response.end();
98
+ return;
99
+ }
100
+ response.write(JSON.stringify({ type: "text", text: chunks[index] }) + "\n");
101
+ await delay(scenario === "slow" ? wait * 8 : wait, undefined, {
102
+ signal: controller.signal,
103
+ });
104
+ }
105
+ response.end(JSON.stringify({ type: "finish" }) + "\n");
106
+ } catch {
107
+ if (controller.signal.aborted) return;
108
+ if (!response.headersSent) response.writeHead(400).end();
109
+ else response.end(JSON.stringify({ type: "error" }) + "\n");
110
+ }
111
+ });
112
+ }
113
+
114
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
115
+ createChatServer().listen(5192, "127.0.0.1", () =>
116
+ console.log("Local integration example ready at http://127.0.0.1:5192"),
117
+ );
118
+ }