@sogni-ai/expo-client 1.0.0-alpha.10

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 (262) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +411 -0
  3. package/dist/cjs/Account/index.d.ts +60 -0
  4. package/dist/cjs/Account/index.d.ts.map +1 -0
  5. package/dist/cjs/Account/index.js +188 -0
  6. package/dist/cjs/Account/index.js.map +1 -0
  7. package/dist/cjs/Account/types.d.ts +43 -0
  8. package/dist/cjs/Account/types.d.ts.map +1 -0
  9. package/dist/cjs/Account/types.js +18 -0
  10. package/dist/cjs/Account/types.js.map +1 -0
  11. package/dist/cjs/ApiClient/WebSocketClient/events.d.ts +139 -0
  12. package/dist/cjs/ApiClient/WebSocketClient/events.d.ts.map +1 -0
  13. package/dist/cjs/ApiClient/WebSocketClient/events.js +3 -0
  14. package/dist/cjs/ApiClient/WebSocketClient/events.js.map +1 -0
  15. package/dist/cjs/ApiClient/WebSocketClient/index.d.ts +45 -0
  16. package/dist/cjs/ApiClient/WebSocketClient/index.d.ts.map +1 -0
  17. package/dist/cjs/ApiClient/WebSocketClient/index.js +283 -0
  18. package/dist/cjs/ApiClient/WebSocketClient/index.js.map +1 -0
  19. package/dist/cjs/ApiClient/WebSocketClient/normalize.d.ts +45 -0
  20. package/dist/cjs/ApiClient/WebSocketClient/normalize.d.ts.map +1 -0
  21. package/dist/cjs/ApiClient/WebSocketClient/normalize.js +245 -0
  22. package/dist/cjs/ApiClient/WebSocketClient/normalize.js.map +1 -0
  23. package/dist/cjs/ApiClient/WebSocketClient/types.d.ts +9 -0
  24. package/dist/cjs/ApiClient/WebSocketClient/types.d.ts.map +1 -0
  25. package/dist/cjs/ApiClient/WebSocketClient/types.js +3 -0
  26. package/dist/cjs/ApiClient/WebSocketClient/types.js.map +1 -0
  27. package/dist/cjs/ApiClient/WebSocketClient/wireTypes.d.ts +161 -0
  28. package/dist/cjs/ApiClient/WebSocketClient/wireTypes.d.ts.map +1 -0
  29. package/dist/cjs/ApiClient/WebSocketClient/wireTypes.js +11 -0
  30. package/dist/cjs/ApiClient/WebSocketClient/wireTypes.js.map +1 -0
  31. package/dist/cjs/ApiClient/index.d.ts +60 -0
  32. package/dist/cjs/ApiClient/index.d.ts.map +1 -0
  33. package/dist/cjs/ApiClient/index.js +87 -0
  34. package/dist/cjs/ApiClient/index.js.map +1 -0
  35. package/dist/cjs/Chat/ChatStream.d.ts +42 -0
  36. package/dist/cjs/Chat/ChatStream.d.ts.map +1 -0
  37. package/dist/cjs/Chat/ChatStream.js +146 -0
  38. package/dist/cjs/Chat/ChatStream.js.map +1 -0
  39. package/dist/cjs/Chat/index.d.ts +61 -0
  40. package/dist/cjs/Chat/index.d.ts.map +1 -0
  41. package/dist/cjs/Chat/index.js +179 -0
  42. package/dist/cjs/Chat/index.js.map +1 -0
  43. package/dist/cjs/Chat/tools.d.ts +13 -0
  44. package/dist/cjs/Chat/tools.d.ts.map +1 -0
  45. package/dist/cjs/Chat/tools.js +22 -0
  46. package/dist/cjs/Chat/tools.js.map +1 -0
  47. package/dist/cjs/Chat/types.d.ts +153 -0
  48. package/dist/cjs/Chat/types.d.ts.map +1 -0
  49. package/dist/cjs/Chat/types.js +3 -0
  50. package/dist/cjs/Chat/types.js.map +1 -0
  51. package/dist/cjs/Projects/ControlNet.d.ts +30 -0
  52. package/dist/cjs/Projects/ControlNet.d.ts.map +1 -0
  53. package/dist/cjs/Projects/ControlNet.js +45 -0
  54. package/dist/cjs/Projects/ControlNet.js.map +1 -0
  55. package/dist/cjs/Projects/createJobRequestMessage.d.ts +11 -0
  56. package/dist/cjs/Projects/createJobRequestMessage.d.ts.map +1 -0
  57. package/dist/cjs/Projects/createJobRequestMessage.js +239 -0
  58. package/dist/cjs/Projects/createJobRequestMessage.js.map +1 -0
  59. package/dist/cjs/Projects/index.d.ts +96 -0
  60. package/dist/cjs/Projects/index.d.ts.map +1 -0
  61. package/dist/cjs/Projects/index.js +536 -0
  62. package/dist/cjs/Projects/index.js.map +1 -0
  63. package/dist/cjs/Projects/restNormalize.d.ts +60 -0
  64. package/dist/cjs/Projects/restNormalize.d.ts.map +1 -0
  65. package/dist/cjs/Projects/restNormalize.js +89 -0
  66. package/dist/cjs/Projects/restNormalize.js.map +1 -0
  67. package/dist/cjs/Projects/types.d.ts +233 -0
  68. package/dist/cjs/Projects/types.d.ts.map +1 -0
  69. package/dist/cjs/Projects/types.js +3 -0
  70. package/dist/cjs/Projects/types.js.map +1 -0
  71. package/dist/cjs/Projects/utils.d.ts +23 -0
  72. package/dist/cjs/Projects/utils.d.ts.map +1 -0
  73. package/dist/cjs/Projects/utils.js +65 -0
  74. package/dist/cjs/Projects/utils.js.map +1 -0
  75. package/dist/cjs/index.d.ts +63 -0
  76. package/dist/cjs/index.d.ts.map +1 -0
  77. package/dist/cjs/index.js +116 -0
  78. package/dist/cjs/index.js.map +1 -0
  79. package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.d.ts +15 -0
  80. package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.d.ts.map +1 -0
  81. package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.js +47 -0
  82. package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.js.map +1 -0
  83. package/dist/cjs/lib/AuthManager/AuthManagerBase.d.ts +23 -0
  84. package/dist/cjs/lib/AuthManager/AuthManagerBase.d.ts.map +1 -0
  85. package/dist/cjs/lib/AuthManager/AuthManagerBase.js +14 -0
  86. package/dist/cjs/lib/AuthManager/AuthManagerBase.js.map +1 -0
  87. package/dist/cjs/lib/AuthManager/AuthStorage.d.ts +11 -0
  88. package/dist/cjs/lib/AuthManager/AuthStorage.d.ts.map +1 -0
  89. package/dist/cjs/lib/AuthManager/AuthStorage.js +50 -0
  90. package/dist/cjs/lib/AuthManager/AuthStorage.js.map +1 -0
  91. package/dist/cjs/lib/AuthManager/TokenAuthManager.d.ts +35 -0
  92. package/dist/cjs/lib/AuthManager/TokenAuthManager.d.ts.map +1 -0
  93. package/dist/cjs/lib/AuthManager/TokenAuthManager.js +164 -0
  94. package/dist/cjs/lib/AuthManager/TokenAuthManager.js.map +1 -0
  95. package/dist/cjs/lib/AuthManager/index.d.ts +7 -0
  96. package/dist/cjs/lib/AuthManager/index.d.ts.map +1 -0
  97. package/dist/cjs/lib/AuthManager/index.js +13 -0
  98. package/dist/cjs/lib/AuthManager/index.js.map +1 -0
  99. package/dist/cjs/lib/Cache.d.ts +12 -0
  100. package/dist/cjs/lib/Cache.d.ts.map +1 -0
  101. package/dist/cjs/lib/Cache.js +36 -0
  102. package/dist/cjs/lib/Cache.js.map +1 -0
  103. package/dist/cjs/lib/DefaultLogger.d.ts +17 -0
  104. package/dist/cjs/lib/DefaultLogger.d.ts.map +1 -0
  105. package/dist/cjs/lib/DefaultLogger.js +36 -0
  106. package/dist/cjs/lib/DefaultLogger.js.map +1 -0
  107. package/dist/cjs/lib/RestClient.d.ts +18 -0
  108. package/dist/cjs/lib/RestClient.d.ts.map +1 -0
  109. package/dist/cjs/lib/RestClient.js +67 -0
  110. package/dist/cjs/lib/RestClient.js.map +1 -0
  111. package/dist/cjs/lib/TypedEventEmitter.d.ts +16 -0
  112. package/dist/cjs/lib/TypedEventEmitter.d.ts.map +1 -0
  113. package/dist/cjs/lib/TypedEventEmitter.js +46 -0
  114. package/dist/cjs/lib/TypedEventEmitter.js.map +1 -0
  115. package/dist/cjs/lib/base64.d.ts +3 -0
  116. package/dist/cjs/lib/base64.d.ts.map +1 -0
  117. package/dist/cjs/lib/base64.js +21 -0
  118. package/dist/cjs/lib/base64.js.map +1 -0
  119. package/dist/cjs/lib/getUUID.d.ts +12 -0
  120. package/dist/cjs/lib/getUUID.d.ts.map +1 -0
  121. package/dist/cjs/lib/getUUID.js +29 -0
  122. package/dist/cjs/lib/getUUID.js.map +1 -0
  123. package/dist/cjs/lib/mediaInput.d.ts +33 -0
  124. package/dist/cjs/lib/mediaInput.d.ts.map +1 -0
  125. package/dist/cjs/lib/mediaInput.js +32 -0
  126. package/dist/cjs/lib/mediaInput.js.map +1 -0
  127. package/dist/cjs/lib/utils.d.ts +10 -0
  128. package/dist/cjs/lib/utils.d.ts.map +1 -0
  129. package/dist/cjs/lib/utils.js +24 -0
  130. package/dist/cjs/lib/utils.js.map +1 -0
  131. package/dist/cjs/types/ErrorData.d.ts +7 -0
  132. package/dist/cjs/types/ErrorData.d.ts.map +1 -0
  133. package/dist/cjs/types/ErrorData.js +3 -0
  134. package/dist/cjs/types/ErrorData.js.map +1 -0
  135. package/dist/cjs/types/json.d.ts +6 -0
  136. package/dist/cjs/types/json.d.ts.map +1 -0
  137. package/dist/cjs/types/json.js +3 -0
  138. package/dist/cjs/types/json.js.map +1 -0
  139. package/dist/cjs/types/token.d.ts +2 -0
  140. package/dist/cjs/types/token.d.ts.map +1 -0
  141. package/dist/cjs/types/token.js +3 -0
  142. package/dist/cjs/types/token.js.map +1 -0
  143. package/dist/cjs/version.d.ts +2 -0
  144. package/dist/cjs/version.d.ts.map +1 -0
  145. package/dist/cjs/version.js +5 -0
  146. package/dist/cjs/version.js.map +1 -0
  147. package/dist/cjs/wallet.d.ts +59 -0
  148. package/dist/cjs/wallet.d.ts.map +1 -0
  149. package/dist/cjs/wallet.js +125 -0
  150. package/dist/cjs/wallet.js.map +1 -0
  151. package/dist/esm/Account/index.js +183 -0
  152. package/dist/esm/Account/index.js.map +1 -0
  153. package/dist/esm/Account/types.js +15 -0
  154. package/dist/esm/Account/types.js.map +1 -0
  155. package/dist/esm/ApiClient/WebSocketClient/events.js +2 -0
  156. package/dist/esm/ApiClient/WebSocketClient/events.js.map +1 -0
  157. package/dist/esm/ApiClient/WebSocketClient/index.js +278 -0
  158. package/dist/esm/ApiClient/WebSocketClient/index.js.map +1 -0
  159. package/dist/esm/ApiClient/WebSocketClient/normalize.js +229 -0
  160. package/dist/esm/ApiClient/WebSocketClient/normalize.js.map +1 -0
  161. package/dist/esm/ApiClient/WebSocketClient/types.js +2 -0
  162. package/dist/esm/ApiClient/WebSocketClient/types.js.map +1 -0
  163. package/dist/esm/ApiClient/WebSocketClient/wireTypes.js +10 -0
  164. package/dist/esm/ApiClient/WebSocketClient/wireTypes.js.map +1 -0
  165. package/dist/esm/ApiClient/index.js +80 -0
  166. package/dist/esm/ApiClient/index.js.map +1 -0
  167. package/dist/esm/Chat/ChatStream.js +144 -0
  168. package/dist/esm/Chat/ChatStream.js.map +1 -0
  169. package/dist/esm/Chat/index.js +174 -0
  170. package/dist/esm/Chat/index.js.map +1 -0
  171. package/dist/esm/Chat/tools.js +19 -0
  172. package/dist/esm/Chat/tools.js.map +1 -0
  173. package/dist/esm/Chat/types.js +2 -0
  174. package/dist/esm/Chat/types.js.map +1 -0
  175. package/dist/esm/Projects/ControlNet.js +41 -0
  176. package/dist/esm/Projects/ControlNet.js.map +1 -0
  177. package/dist/esm/Projects/createJobRequestMessage.js +236 -0
  178. package/dist/esm/Projects/createJobRequestMessage.js.map +1 -0
  179. package/dist/esm/Projects/index.js +531 -0
  180. package/dist/esm/Projects/index.js.map +1 -0
  181. package/dist/esm/Projects/restNormalize.js +86 -0
  182. package/dist/esm/Projects/restNormalize.js.map +1 -0
  183. package/dist/esm/Projects/types.js +2 -0
  184. package/dist/esm/Projects/types.js.map +1 -0
  185. package/dist/esm/Projects/utils.js +55 -0
  186. package/dist/esm/Projects/utils.js.map +1 -0
  187. package/dist/esm/index.js +72 -0
  188. package/dist/esm/index.js.map +1 -0
  189. package/dist/esm/lib/AuthManager/ApiKeyAuthManager.js +42 -0
  190. package/dist/esm/lib/AuthManager/ApiKeyAuthManager.js.map +1 -0
  191. package/dist/esm/lib/AuthManager/AuthManagerBase.js +9 -0
  192. package/dist/esm/lib/AuthManager/AuthManagerBase.js.map +1 -0
  193. package/dist/esm/lib/AuthManager/AuthStorage.js +13 -0
  194. package/dist/esm/lib/AuthManager/AuthStorage.js.map +1 -0
  195. package/dist/esm/lib/AuthManager/TokenAuthManager.js +159 -0
  196. package/dist/esm/lib/AuthManager/TokenAuthManager.js.map +1 -0
  197. package/dist/esm/lib/AuthManager/index.js +5 -0
  198. package/dist/esm/lib/AuthManager/index.js.map +1 -0
  199. package/dist/esm/lib/Cache.js +33 -0
  200. package/dist/esm/lib/Cache.js.map +1 -0
  201. package/dist/esm/lib/DefaultLogger.js +32 -0
  202. package/dist/esm/lib/DefaultLogger.js.map +1 -0
  203. package/dist/esm/lib/RestClient.js +62 -0
  204. package/dist/esm/lib/RestClient.js.map +1 -0
  205. package/dist/esm/lib/TypedEventEmitter.js +44 -0
  206. package/dist/esm/lib/TypedEventEmitter.js.map +1 -0
  207. package/dist/esm/lib/base64.js +17 -0
  208. package/dist/esm/lib/base64.js.map +1 -0
  209. package/dist/esm/lib/getUUID.js +26 -0
  210. package/dist/esm/lib/getUUID.js.map +1 -0
  211. package/dist/esm/lib/mediaInput.js +28 -0
  212. package/dist/esm/lib/mediaInput.js.map +1 -0
  213. package/dist/esm/lib/utils.js +19 -0
  214. package/dist/esm/lib/utils.js.map +1 -0
  215. package/dist/esm/types/ErrorData.js +2 -0
  216. package/dist/esm/types/ErrorData.js.map +1 -0
  217. package/dist/esm/types/json.js +2 -0
  218. package/dist/esm/types/json.js.map +1 -0
  219. package/dist/esm/types/token.js +2 -0
  220. package/dist/esm/types/token.js.map +1 -0
  221. package/dist/esm/version.js +2 -0
  222. package/dist/esm/version.js.map +1 -0
  223. package/dist/esm/wallet.js +117 -0
  224. package/dist/esm/wallet.js.map +1 -0
  225. package/package.json +92 -0
  226. package/src/Account/index.ts +214 -0
  227. package/src/Account/types.ts +61 -0
  228. package/src/ApiClient/WebSocketClient/events.ts +168 -0
  229. package/src/ApiClient/WebSocketClient/index.ts +345 -0
  230. package/src/ApiClient/WebSocketClient/normalize.ts +287 -0
  231. package/src/ApiClient/WebSocketClient/types.ts +10 -0
  232. package/src/ApiClient/WebSocketClient/wireTypes.ts +183 -0
  233. package/src/ApiClient/index.ts +147 -0
  234. package/src/Chat/ChatStream.ts +164 -0
  235. package/src/Chat/index.ts +224 -0
  236. package/src/Chat/tools.ts +23 -0
  237. package/src/Chat/types.ts +165 -0
  238. package/src/Projects/ControlNet.ts +84 -0
  239. package/src/Projects/createJobRequestMessage.ts +241 -0
  240. package/src/Projects/index.ts +608 -0
  241. package/src/Projects/restNormalize.ts +154 -0
  242. package/src/Projects/types.ts +266 -0
  243. package/src/Projects/utils.ts +60 -0
  244. package/src/index.ts +217 -0
  245. package/src/lib/AuthManager/ApiKeyAuthManager.ts +52 -0
  246. package/src/lib/AuthManager/AuthManagerBase.ts +35 -0
  247. package/src/lib/AuthManager/AuthStorage.ts +21 -0
  248. package/src/lib/AuthManager/TokenAuthManager.ts +179 -0
  249. package/src/lib/AuthManager/index.ts +8 -0
  250. package/src/lib/Cache.ts +45 -0
  251. package/src/lib/DefaultLogger.ts +47 -0
  252. package/src/lib/RestClient.ts +77 -0
  253. package/src/lib/TypedEventEmitter.ts +52 -0
  254. package/src/lib/base64.ts +17 -0
  255. package/src/lib/getUUID.ts +27 -0
  256. package/src/lib/mediaInput.ts +54 -0
  257. package/src/lib/utils.ts +21 -0
  258. package/src/types/ErrorData.ts +7 -0
  259. package/src/types/json.ts +5 -0
  260. package/src/types/token.ts +1 -0
  261. package/src/version.ts +1 -0
  262. package/src/wallet.ts +158 -0
@@ -0,0 +1,164 @@
1
+ import ErrorData from '../types/ErrorData';
2
+ import {
3
+ ChatCompletionChunk,
4
+ ChatCompletionResult,
5
+ ToolCall,
6
+ ToolCallDelta,
7
+ TokenUsage
8
+ } from './types';
9
+
10
+ type Resolver = (value: IteratorResult<ChatCompletionChunk>) => void;
11
+ type Rejecter = (err: Error) => void;
12
+
13
+ /**
14
+ * Async iterable representing a streaming chat completion.
15
+ *
16
+ * Iterate with `for await (const chunk of stream)`. Each chunk has `content`
17
+ * (token text) and may carry a `finishReason` on the final chunk. After the
18
+ * stream completes, `stream.result` resolves to the full ChatCompletionResult.
19
+ *
20
+ * Cancel mid-stream with `stream.cancel()` (sends a server-side abort).
21
+ */
22
+ class ChatStream implements AsyncIterable<ChatCompletionChunk> {
23
+ readonly jobId: string;
24
+ private _queue: ChatCompletionChunk[] = [];
25
+ private _pendingResolver: Resolver | null = null;
26
+ private _pendingRejecter: Rejecter | null = null;
27
+ private _content = '';
28
+ private _role: string = 'assistant';
29
+ private _finishReason: string | null = null;
30
+ private _usage: TokenUsage = { prompt_tokens: 0, completion_tokens: 0 };
31
+ private _timeTaken = 0;
32
+ private _workerName: string | undefined;
33
+ private _toolCallAccumulator = new Map<number, ToolCall>();
34
+ private _done = false;
35
+ private _error: Error | null = null;
36
+ private _resultPromise: Promise<ChatCompletionResult>;
37
+ private _resultResolve!: (r: ChatCompletionResult) => void;
38
+ private _resultReject!: (e: Error) => void;
39
+ private _onCancel: () => Promise<void>;
40
+
41
+ constructor(jobId: string, onCancel: () => Promise<void>) {
42
+ this.jobId = jobId;
43
+ this._onCancel = onCancel;
44
+ this._resultPromise = new Promise<ChatCompletionResult>((resolve, reject) => {
45
+ this._resultResolve = resolve;
46
+ this._resultReject = reject;
47
+ });
48
+ }
49
+
50
+ /** Resolves to the full result after the stream completes. */
51
+ get result(): Promise<ChatCompletionResult> {
52
+ return this._resultPromise;
53
+ }
54
+
55
+ async cancel(): Promise<void> {
56
+ await this._onCancel();
57
+ }
58
+
59
+ [Symbol.asyncIterator](): AsyncIterator<ChatCompletionChunk> {
60
+ return {
61
+ next: () => this._next()
62
+ };
63
+ }
64
+
65
+ private _next(): Promise<IteratorResult<ChatCompletionChunk>> {
66
+ return new Promise((resolve, reject) => {
67
+ if (this._error) {
68
+ reject(this._error);
69
+ return;
70
+ }
71
+ if (this._queue.length > 0) {
72
+ resolve({ value: this._queue.shift()!, done: false });
73
+ return;
74
+ }
75
+ if (this._done) {
76
+ resolve({ value: undefined as unknown as ChatCompletionChunk, done: true });
77
+ return;
78
+ }
79
+ this._pendingResolver = resolve;
80
+ this._pendingRejecter = reject;
81
+ });
82
+ }
83
+
84
+ _pushChunk(chunk: ChatCompletionChunk): void {
85
+ if (chunk.content) this._content += chunk.content;
86
+ if (chunk.role) this._role = chunk.role;
87
+ if (chunk.finishReason !== undefined && chunk.finishReason !== null) {
88
+ this._finishReason = chunk.finishReason;
89
+ }
90
+ if (chunk.usage) this._usage = chunk.usage;
91
+ if (chunk.tool_calls) this._accumulateToolCalls(chunk.tool_calls);
92
+
93
+ if (this._pendingResolver) {
94
+ const resolve = this._pendingResolver;
95
+ this._pendingResolver = null;
96
+ this._pendingRejecter = null;
97
+ resolve({ value: chunk, done: false });
98
+ } else {
99
+ this._queue.push(chunk);
100
+ }
101
+ }
102
+
103
+ _complete(timeTaken: number, usage?: TokenUsage, workerName?: string): void {
104
+ if (this._done) return;
105
+ if (usage) this._usage = usage;
106
+ this._timeTaken = timeTaken;
107
+ if (workerName) this._workerName = workerName;
108
+ this._done = true;
109
+
110
+ const toolCalls = Array.from(this._toolCallAccumulator.values()).filter(
111
+ (tc) => tc.id && tc.function.name
112
+ );
113
+ const result: ChatCompletionResult = {
114
+ jobId: this.jobId,
115
+ content: this._content,
116
+ role: this._role,
117
+ finishReason: this._finishReason,
118
+ usage: this._usage,
119
+ timeTaken: this._timeTaken,
120
+ ...(this._workerName ? { workerName: this._workerName } : {}),
121
+ ...(toolCalls.length ? { tool_calls: toolCalls } : {})
122
+ };
123
+ this._resultResolve(result);
124
+
125
+ if (this._pendingResolver) {
126
+ const resolve = this._pendingResolver;
127
+ this._pendingResolver = null;
128
+ this._pendingRejecter = null;
129
+ resolve({ value: undefined as unknown as ChatCompletionChunk, done: true });
130
+ }
131
+ }
132
+
133
+ _fail(error: ErrorData): void {
134
+ if (this._done) return;
135
+ this._done = true;
136
+ const err = new Error(error.message || 'Chat completion failed');
137
+ (err as Error & { code?: number; rawCode?: string }).code = error.code;
138
+ (err as Error & { code?: number; rawCode?: string }).rawCode = error.originalCode;
139
+ this._error = err;
140
+ this._resultReject(err);
141
+ if (this._pendingRejecter) {
142
+ const reject = this._pendingRejecter;
143
+ this._pendingResolver = null;
144
+ this._pendingRejecter = null;
145
+ reject(err);
146
+ }
147
+ }
148
+
149
+ private _accumulateToolCalls(deltas: ToolCallDelta[]): void {
150
+ for (const delta of deltas) {
151
+ const existing = this._toolCallAccumulator.get(delta.index) ?? {
152
+ id: delta.id ?? '',
153
+ type: 'function' as const,
154
+ function: { name: '', arguments: '' }
155
+ };
156
+ if (delta.id) existing.id = delta.id;
157
+ if (delta.function?.name) existing.function.name += delta.function.name;
158
+ if (delta.function?.arguments) existing.function.arguments += delta.function.arguments;
159
+ this._toolCallAccumulator.set(delta.index, existing);
160
+ }
161
+ }
162
+ }
163
+
164
+ export default ChatStream;
@@ -0,0 +1,224 @@
1
+ import ApiClient from '../ApiClient';
2
+ import {
3
+ ChatErrorEvent,
4
+ ChatResultEvent,
5
+ ChatTokensEvent,
6
+ SwarmLLMModelsEvent
7
+ } from '../ApiClient/WebSocketClient/events';
8
+ import TypedEventEmitter from '../lib/TypedEventEmitter';
9
+ import getUUID from '../lib/getUUID';
10
+ import ChatStream from './ChatStream';
11
+ import {
12
+ ChatCompletionChunk,
13
+ ChatCompletionParams,
14
+ ChatCompletionResult,
15
+ ChatMessage,
16
+ LLMModelInfo,
17
+ ToolRound
18
+ } from './types';
19
+
20
+ interface ChatApiEvents {
21
+ /** Fired for each streaming chunk on any active completion. */
22
+ token: ChatCompletionChunk;
23
+ /** Fired when any completion finishes successfully. */
24
+ completed: ChatCompletionResult;
25
+ /** Fired when any completion fails. */
26
+ failed: { jobId: string; error: Error };
27
+ /** Fired when the server pushes an updated LLM model list. */
28
+ modelsUpdated: Record<string, LLMModelInfo>;
29
+ }
30
+
31
+ /**
32
+ * Chat / LLM API — OpenAI-compatible completions over WebSocket.
33
+ *
34
+ * Phase 4b MVP: streaming + non-streaming text and multimodal (vision)
35
+ * completions. Tool-calling deltas are exposed on chunks/results for apps
36
+ * that drive their own tool loops, but the auto-execution loop and Sogni
37
+ * hosted tools are not yet wired up.
38
+ */
39
+ class ChatApi extends TypedEventEmitter<ChatApiEvents> {
40
+ private _client: ApiClient;
41
+ private _streams = new Map<string, ChatStream>();
42
+ private _availableModels: Record<string, LLMModelInfo> = {};
43
+
44
+ constructor(client: ApiClient) {
45
+ super();
46
+ this._client = client;
47
+ client.socket.on('chatTokens', (e) => this._onTokens(e));
48
+ client.socket.on('chatResult', (e) => this._onResult(e));
49
+ client.socket.on('chatError', (e) => this._onError(e));
50
+ client.socket.on('swarmLLMModels', (e) => this._onModels(e));
51
+ }
52
+
53
+ /** Snapshot of the latest LLM model list pushed by the server. */
54
+ getAvailableModels(): Record<string, LLMModelInfo> {
55
+ return { ...this._availableModels };
56
+ }
57
+
58
+ /** Subscribe to model-list updates. Returns an unsubscribe function. */
59
+ onModelsUpdated(listener: (models: Record<string, LLMModelInfo>) => void): () => void {
60
+ return this.on('modelsUpdated', listener);
61
+ }
62
+
63
+ completions = {
64
+ create: ((params: ChatCompletionParams) => this._create(params)) as {
65
+ (params: ChatCompletionParams & { stream: true }): Promise<ChatStream>;
66
+ (params: ChatCompletionParams & { stream?: false }): Promise<ChatCompletionResult>;
67
+ }
68
+ };
69
+
70
+ private async _create(params: ChatCompletionParams): Promise<ChatStream | ChatCompletionResult> {
71
+ if (!this._client.socket.isConnected) {
72
+ throw new Error(
73
+ 'Cannot start chat completion: WebSocket is not connected. Authenticate first.'
74
+ );
75
+ }
76
+ if (params.autoExecuteTools) {
77
+ if (params.stream) {
78
+ throw new Error(
79
+ 'autoExecuteTools is not supported with stream: true. Drive the tool loop yourself instead.'
80
+ );
81
+ }
82
+ if (!params.onToolCall) {
83
+ throw new Error('autoExecuteTools requires an onToolCall handler');
84
+ }
85
+ return this._runAutoToolLoop(params);
86
+ }
87
+
88
+ const stream = await this._dispatchCompletion(params);
89
+ if (params.stream) return stream;
90
+ return stream.result;
91
+ }
92
+
93
+ private async _dispatchCompletion(params: ChatCompletionParams): Promise<ChatStream> {
94
+ const jobId = getUUID();
95
+ const request = {
96
+ jobID: jobId,
97
+ type: 'llm' as const,
98
+ model: params.model,
99
+ messages: params.messages,
100
+ max_tokens: params.max_tokens,
101
+ temperature: params.temperature,
102
+ top_p: params.top_p,
103
+ top_k: params.top_k,
104
+ min_p: params.min_p,
105
+ stream: !!params.stream,
106
+ repetition_penalty: params.repetition_penalty,
107
+ frequency_penalty: params.frequency_penalty,
108
+ presence_penalty: params.presence_penalty,
109
+ stop: params.stop,
110
+ tokenType: params.tokenType,
111
+ tools: params.tools,
112
+ tool_choice: params.tool_choice,
113
+ chat_template_kwargs: params.chat_template_kwargs,
114
+ taskProfile: params.taskProfile
115
+ };
116
+ const stream = new ChatStream(jobId, () => this._cancelStream(jobId));
117
+ this._streams.set(jobId, stream);
118
+ await this._client.socket.send('llmJobRequest', request);
119
+ return stream;
120
+ }
121
+
122
+ /**
123
+ * Multi-round tool-calling loop. Each round sends a completion, then
124
+ * executes any returned tool_calls via the user's onToolCall handler and
125
+ * appends the results to the conversation. Stops when the model returns a
126
+ * normal completion (no tool calls) or when maxToolRounds is reached.
127
+ */
128
+ private async _runAutoToolLoop(params: ChatCompletionParams): Promise<ChatCompletionResult> {
129
+ const maxRounds = params.maxToolRounds ?? 5;
130
+ const onToolCall = params.onToolCall!;
131
+ const history: ToolRound[] = [];
132
+ let messages: ChatMessage[] = params.messages.slice();
133
+
134
+ for (let round = 1; round <= maxRounds; round++) {
135
+ const stream = await this._dispatchCompletion({
136
+ ...params,
137
+ messages,
138
+ stream: false,
139
+ autoExecuteTools: false,
140
+ onToolCall: undefined
141
+ });
142
+ const result = await stream.result;
143
+
144
+ if (!result.tool_calls?.length || result.finishReason !== 'tool_calls') {
145
+ return { ...result, ...(history.length ? { toolHistory: history } : {}) };
146
+ }
147
+
148
+ const toolResults: Array<{ tool_call_id: string; content: string }> = [];
149
+ for (const call of result.tool_calls) {
150
+ const content = await onToolCall(call);
151
+ toolResults.push({ tool_call_id: call.id, content });
152
+ }
153
+ history.push({ round, toolCalls: result.tool_calls, toolResults });
154
+
155
+ messages = [
156
+ ...messages,
157
+ {
158
+ role: 'assistant',
159
+ content: result.content || null,
160
+ tool_calls: result.tool_calls
161
+ },
162
+ ...result.tool_calls.map((call, i) => ({
163
+ role: 'tool' as const,
164
+ content: toolResults[i]!.content,
165
+ tool_call_id: call.id,
166
+ name: call.function.name
167
+ }))
168
+ ];
169
+ }
170
+
171
+ throw new Error(
172
+ `autoExecuteTools exceeded maxToolRounds (${maxRounds}) — the model kept requesting tool calls`
173
+ );
174
+ }
175
+
176
+ private _onTokens(e: ChatTokensEvent) {
177
+ const stream = this._streams.get(e.jobId);
178
+ if (!stream) return;
179
+ const chunk: ChatCompletionChunk = {
180
+ jobId: e.jobId,
181
+ content: e.content,
182
+ ...(e.role ? { role: e.role } : {}),
183
+ ...(e.finishReason !== undefined ? { finishReason: e.finishReason } : {}),
184
+ ...(e.usage ? { usage: e.usage } : {}),
185
+ ...(e.tool_calls ? { tool_calls: e.tool_calls } : {})
186
+ };
187
+ stream._pushChunk(chunk);
188
+ this.emit('token', chunk);
189
+ }
190
+
191
+ private _onResult(e: ChatResultEvent) {
192
+ const stream = this._streams.get(e.jobId);
193
+ if (!stream) return;
194
+ stream._complete(e.timeTaken, e.usage, e.workerName);
195
+ stream.result.then((r) => this.emit('completed', r)).catch(() => undefined);
196
+ this._streams.delete(e.jobId);
197
+ }
198
+
199
+ private _onError(e: ChatErrorEvent) {
200
+ const stream = this._streams.get(e.jobId);
201
+ if (!stream) return;
202
+ stream._fail({ code: 5000, originalCode: e.rawCode, message: e.message });
203
+ this.emit('failed', {
204
+ jobId: e.jobId,
205
+ error: new Error(e.message)
206
+ });
207
+ this._streams.delete(e.jobId);
208
+ }
209
+
210
+ private _onModels(e: SwarmLLMModelsEvent) {
211
+ this._availableModels = e.models;
212
+ this.emit('modelsUpdated', this._availableModels);
213
+ }
214
+
215
+ private async _cancelStream(jobId: string): Promise<void> {
216
+ try {
217
+ await this._client.socket.send('llmJobCancel', { jobID: jobId });
218
+ } catch (e) {
219
+ this._client.logger.warn(`Failed to cancel chat job ${jobId}:`, e);
220
+ }
221
+ }
222
+ }
223
+
224
+ export default ChatApi;
@@ -0,0 +1,23 @@
1
+ import { ToolCall } from './types';
2
+
3
+ /**
4
+ * Parse a tool call's arguments JSON into a typed object.
5
+ *
6
+ * Tool call arguments arrive from the model as a string (so they can be
7
+ * streamed character-by-character). This helper does the JSON.parse with
8
+ * a clearer error message than a raw SyntaxError.
9
+ *
10
+ * @example
11
+ * const args = parseToolCallArguments<{ description: string; tags: string[] }>(call);
12
+ */
13
+ export function parseToolCallArguments<T = Record<string, unknown>>(toolCall: ToolCall): T {
14
+ try {
15
+ return JSON.parse(toolCall.function.arguments) as T;
16
+ } catch (err) {
17
+ throw new Error(
18
+ `Failed to parse arguments for tool ${toolCall.function.name} (id=${toolCall.id}): ${
19
+ err instanceof Error ? err.message : String(err)
20
+ }. Raw arguments: ${toolCall.function.arguments}`
21
+ );
22
+ }
23
+ }
@@ -0,0 +1,165 @@
1
+ import { TokenType } from '../types/token';
2
+
3
+ export interface TextContentPart {
4
+ type: 'text';
5
+ text: string;
6
+ }
7
+
8
+ /**
9
+ * Image input for multimodal (vision) chat. Pass a base64-encoded data URI like
10
+ * `data:image/jpeg;base64,...`. Server limits: max 20 images per request, 10 MB
11
+ * each, longest side 1024px.
12
+ */
13
+ export interface ImageUrlContentPart {
14
+ type: 'image_url';
15
+ image_url: {
16
+ url: string;
17
+ detail?: 'auto' | 'low' | 'high';
18
+ };
19
+ }
20
+
21
+ export type ContentPart = TextContentPart | ImageUrlContentPart;
22
+
23
+ export interface ChatMessage {
24
+ role: 'system' | 'user' | 'assistant' | 'tool';
25
+ content: string | ContentPart[] | null;
26
+ /** Tool calls returned by an assistant message. */
27
+ tool_calls?: ToolCall[];
28
+ /** Identifies which tool call this message responds to. */
29
+ tool_call_id?: string;
30
+ name?: string;
31
+ }
32
+
33
+ /**
34
+ * Tool-calling types are kept structural only for Phase 4b. The SDK exposes
35
+ * the wire shape (so apps that want to drive their own tool loops can do so),
36
+ * but the multi-round auto-execution loop and Sogni hosted tools are deferred
37
+ * to a follow-up release.
38
+ */
39
+ export interface ToolFunction {
40
+ name: string;
41
+ description?: string;
42
+ parameters?: Record<string, unknown>;
43
+ }
44
+
45
+ export interface ToolDefinition {
46
+ type: 'function';
47
+ function: ToolFunction;
48
+ }
49
+
50
+ export interface ToolCallFunction {
51
+ name: string;
52
+ arguments: string;
53
+ }
54
+
55
+ export interface ToolCall {
56
+ id: string;
57
+ type: 'function';
58
+ function: ToolCallFunction;
59
+ }
60
+
61
+ export interface ToolCallDelta {
62
+ index: number;
63
+ id?: string;
64
+ type?: 'function';
65
+ function?: {
66
+ name?: string;
67
+ arguments?: string;
68
+ };
69
+ }
70
+
71
+ export type ToolChoice =
72
+ | 'none'
73
+ | 'auto'
74
+ | 'required'
75
+ | { type: 'function'; function: { name: string } };
76
+
77
+ export interface ChatCompletionParams {
78
+ model: string;
79
+ messages: ChatMessage[];
80
+ max_tokens?: number;
81
+ temperature?: number;
82
+ top_p?: number;
83
+ top_k?: number;
84
+ min_p?: number;
85
+ stream?: boolean;
86
+ repetition_penalty?: number;
87
+ frequency_penalty?: number;
88
+ presence_penalty?: number;
89
+ stop?: string | string[];
90
+ /** Token type to use for billing. Defaults to 'sogni'. */
91
+ tokenType?: TokenType;
92
+ tools?: ToolDefinition[];
93
+ tool_choice?: ToolChoice;
94
+ /** Per-request chat template arguments (e.g. `{ enable_thinking: false }`). */
95
+ chat_template_kwargs?: Record<string, unknown>;
96
+ /** Hint for server-side preset selection. */
97
+ taskProfile?: 'general' | 'coding' | 'reasoning';
98
+ /**
99
+ * Automatically run a multi-round tool calling loop. The SDK sends the
100
+ * completion, executes any returned tool calls via `onToolCall`, feeds the
101
+ * results back as `role: 'tool'` messages, and repeats until the model
102
+ * answers without calling more tools (or `maxToolRounds` is reached).
103
+ *
104
+ * Only supported when `stream: false`. For streaming flows, drive the loop
105
+ * yourself by inspecting `result.tool_calls` and issuing follow-up
106
+ * completions.
107
+ */
108
+ autoExecuteTools?: boolean;
109
+ /**
110
+ * Required when `autoExecuteTools` is true. Called for each tool call the
111
+ * model emits. Must return the tool result content (a string the model
112
+ * will see as the tool's output).
113
+ */
114
+ onToolCall?: (toolCall: ToolCall) => Promise<string> | string;
115
+ /**
116
+ * Max number of tool-calling rounds when `autoExecuteTools` is true.
117
+ * Defaults to 5. Prevents infinite loops if the model keeps calling tools.
118
+ */
119
+ maxToolRounds?: number;
120
+ }
121
+
122
+ /** Re-export for app-side use. */
123
+
124
+ /** One round of tool calls + results captured during an autoExecuteTools loop. */
125
+ export interface ToolRound {
126
+ round: number;
127
+ toolCalls: ToolCall[];
128
+ toolResults: Array<{ tool_call_id: string; content: string }>;
129
+ }
130
+
131
+ export interface TokenUsage {
132
+ prompt_tokens: number;
133
+ completion_tokens: number;
134
+ total_tokens?: number;
135
+ }
136
+
137
+ export interface ChatCompletionChunk {
138
+ jobId: string;
139
+ content: string;
140
+ role?: string;
141
+ finishReason?: string | null;
142
+ usage?: TokenUsage;
143
+ tool_calls?: ToolCallDelta[];
144
+ }
145
+
146
+ export interface ChatCompletionResult {
147
+ jobId: string;
148
+ content: string;
149
+ role: string;
150
+ finishReason: string | null;
151
+ usage: TokenUsage;
152
+ timeTaken: number;
153
+ workerName?: string;
154
+ tool_calls?: ToolCall[];
155
+ /** History of tool-calling rounds when `autoExecuteTools` was used. */
156
+ toolHistory?: ToolRound[];
157
+ }
158
+
159
+ export interface LLMModelInfo {
160
+ /** Worker count currently serving this model on the selected network. */
161
+ workers: number;
162
+ /** Optional richer metadata when the server sends the full LLMModelInfo shape. */
163
+ displayName?: string;
164
+ contextLength?: number;
165
+ }
@@ -0,0 +1,84 @@
1
+ import { MediaInput } from '../lib/mediaInput';
2
+
3
+ export type ControlNetName =
4
+ | 'canny'
5
+ | 'depth'
6
+ | 'inpaint'
7
+ | 'instrp2p'
8
+ | 'lineart'
9
+ | 'lineartanime'
10
+ | 'mlsd'
11
+ | 'normalbae'
12
+ | 'openpose'
13
+ | 'scribble'
14
+ | 'segmentation'
15
+ | 'shuffle'
16
+ | 'softedge'
17
+ | 'tile'
18
+ | 'instantid';
19
+
20
+ export type ControlNetMode = 'balanced' | 'prompt_priority' | 'cn_priority';
21
+
22
+ export interface ControlNetParams {
23
+ /** Short name of the ControlNet model. */
24
+ name: ControlNetName;
25
+ /** Control image input. Omit `image` and pass `imageAlreadyUploaded: true` if the image is already on the server. */
26
+ image?: MediaInput;
27
+ /** True when the control image was uploaded out of band and only the flag should be sent. */
28
+ imageAlreadyUploaded?: boolean;
29
+ /** 0..1. 0 = full control to prompt, 1 = full control to ControlNet. */
30
+ strength?: number;
31
+ mode?: ControlNetMode;
32
+ /** Step at which the ControlNet first applies (0..1). Must be < guidanceEnd. */
33
+ guidanceStart?: number;
34
+ /** Step at which the ControlNet last applies (0..1). Must be > guidanceStart. */
35
+ guidanceEnd?: number;
36
+ }
37
+
38
+ export interface ControlNetParamsRaw {
39
+ name: ControlNetName;
40
+ cnImageState: 'original';
41
+ hasImage: boolean;
42
+ controlStrength?: number;
43
+ controlMode?: 0 | 1 | 2;
44
+ controlGuidanceStart?: number;
45
+ controlGuidanceEnd?: number;
46
+ }
47
+
48
+ const MODE_MAP: Record<ControlNetMode, 0 | 1 | 2> = {
49
+ balanced: 0,
50
+ prompt_priority: 1,
51
+ cn_priority: 2
52
+ };
53
+
54
+ export function toControlNetRaw(params: ControlNetParams): ControlNetParamsRaw {
55
+ const raw: ControlNetParamsRaw = {
56
+ name: params.name,
57
+ cnImageState: 'original',
58
+ hasImage: !!params.image || !!params.imageAlreadyUploaded
59
+ };
60
+ if (params.strength !== undefined) raw.controlStrength = clamp01(params.strength, 'strength');
61
+ if (params.mode) raw.controlMode = MODE_MAP[params.mode];
62
+ if (params.guidanceStart !== undefined) {
63
+ raw.controlGuidanceStart = clamp01(params.guidanceStart, 'guidanceStart');
64
+ }
65
+ if (params.guidanceEnd !== undefined) {
66
+ raw.controlGuidanceEnd = clamp01(params.guidanceEnd, 'guidanceEnd');
67
+ }
68
+ return raw;
69
+ }
70
+
71
+ function clamp01(value: number, name: string): number {
72
+ if (Number.isNaN(value) || value < 0 || value > 1) {
73
+ throw new Error(`${name} must be between 0 and 1 (got ${value})`);
74
+ }
75
+ return value;
76
+ }
77
+
78
+ export function getMaxContextImages(modelId: string): number {
79
+ if (modelId === 'gpt-image-2') return 16;
80
+ if (modelId.startsWith('flux2_')) return 6;
81
+ if (modelId.startsWith('qwen_image_')) return 3;
82
+ if (modelId.includes('kontext')) return 2;
83
+ return 3;
84
+ }