@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.
- package/CHANGELOG.md +69 -0
- package/README.md +411 -0
- package/dist/cjs/Account/index.d.ts +60 -0
- package/dist/cjs/Account/index.d.ts.map +1 -0
- package/dist/cjs/Account/index.js +188 -0
- package/dist/cjs/Account/index.js.map +1 -0
- package/dist/cjs/Account/types.d.ts +43 -0
- package/dist/cjs/Account/types.d.ts.map +1 -0
- package/dist/cjs/Account/types.js +18 -0
- package/dist/cjs/Account/types.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/events.d.ts +139 -0
- package/dist/cjs/ApiClient/WebSocketClient/events.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/events.js +3 -0
- package/dist/cjs/ApiClient/WebSocketClient/events.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/index.d.ts +45 -0
- package/dist/cjs/ApiClient/WebSocketClient/index.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/index.js +283 -0
- package/dist/cjs/ApiClient/WebSocketClient/index.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/normalize.d.ts +45 -0
- package/dist/cjs/ApiClient/WebSocketClient/normalize.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/normalize.js +245 -0
- package/dist/cjs/ApiClient/WebSocketClient/normalize.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/types.d.ts +9 -0
- package/dist/cjs/ApiClient/WebSocketClient/types.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/types.js +3 -0
- package/dist/cjs/ApiClient/WebSocketClient/types.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/wireTypes.d.ts +161 -0
- package/dist/cjs/ApiClient/WebSocketClient/wireTypes.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/wireTypes.js +11 -0
- package/dist/cjs/ApiClient/WebSocketClient/wireTypes.js.map +1 -0
- package/dist/cjs/ApiClient/index.d.ts +60 -0
- package/dist/cjs/ApiClient/index.d.ts.map +1 -0
- package/dist/cjs/ApiClient/index.js +87 -0
- package/dist/cjs/ApiClient/index.js.map +1 -0
- package/dist/cjs/Chat/ChatStream.d.ts +42 -0
- package/dist/cjs/Chat/ChatStream.d.ts.map +1 -0
- package/dist/cjs/Chat/ChatStream.js +146 -0
- package/dist/cjs/Chat/ChatStream.js.map +1 -0
- package/dist/cjs/Chat/index.d.ts +61 -0
- package/dist/cjs/Chat/index.d.ts.map +1 -0
- package/dist/cjs/Chat/index.js +179 -0
- package/dist/cjs/Chat/index.js.map +1 -0
- package/dist/cjs/Chat/tools.d.ts +13 -0
- package/dist/cjs/Chat/tools.d.ts.map +1 -0
- package/dist/cjs/Chat/tools.js +22 -0
- package/dist/cjs/Chat/tools.js.map +1 -0
- package/dist/cjs/Chat/types.d.ts +153 -0
- package/dist/cjs/Chat/types.d.ts.map +1 -0
- package/dist/cjs/Chat/types.js +3 -0
- package/dist/cjs/Chat/types.js.map +1 -0
- package/dist/cjs/Projects/ControlNet.d.ts +30 -0
- package/dist/cjs/Projects/ControlNet.d.ts.map +1 -0
- package/dist/cjs/Projects/ControlNet.js +45 -0
- package/dist/cjs/Projects/ControlNet.js.map +1 -0
- package/dist/cjs/Projects/createJobRequestMessage.d.ts +11 -0
- package/dist/cjs/Projects/createJobRequestMessage.d.ts.map +1 -0
- package/dist/cjs/Projects/createJobRequestMessage.js +239 -0
- package/dist/cjs/Projects/createJobRequestMessage.js.map +1 -0
- package/dist/cjs/Projects/index.d.ts +96 -0
- package/dist/cjs/Projects/index.d.ts.map +1 -0
- package/dist/cjs/Projects/index.js +536 -0
- package/dist/cjs/Projects/index.js.map +1 -0
- package/dist/cjs/Projects/restNormalize.d.ts +60 -0
- package/dist/cjs/Projects/restNormalize.d.ts.map +1 -0
- package/dist/cjs/Projects/restNormalize.js +89 -0
- package/dist/cjs/Projects/restNormalize.js.map +1 -0
- package/dist/cjs/Projects/types.d.ts +233 -0
- package/dist/cjs/Projects/types.d.ts.map +1 -0
- package/dist/cjs/Projects/types.js +3 -0
- package/dist/cjs/Projects/types.js.map +1 -0
- package/dist/cjs/Projects/utils.d.ts +23 -0
- package/dist/cjs/Projects/utils.d.ts.map +1 -0
- package/dist/cjs/Projects/utils.js +65 -0
- package/dist/cjs/Projects/utils.js.map +1 -0
- package/dist/cjs/index.d.ts +63 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +116 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.d.ts +15 -0
- package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.js +47 -0
- package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.js.map +1 -0
- package/dist/cjs/lib/AuthManager/AuthManagerBase.d.ts +23 -0
- package/dist/cjs/lib/AuthManager/AuthManagerBase.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/AuthManagerBase.js +14 -0
- package/dist/cjs/lib/AuthManager/AuthManagerBase.js.map +1 -0
- package/dist/cjs/lib/AuthManager/AuthStorage.d.ts +11 -0
- package/dist/cjs/lib/AuthManager/AuthStorage.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/AuthStorage.js +50 -0
- package/dist/cjs/lib/AuthManager/AuthStorage.js.map +1 -0
- package/dist/cjs/lib/AuthManager/TokenAuthManager.d.ts +35 -0
- package/dist/cjs/lib/AuthManager/TokenAuthManager.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/TokenAuthManager.js +164 -0
- package/dist/cjs/lib/AuthManager/TokenAuthManager.js.map +1 -0
- package/dist/cjs/lib/AuthManager/index.d.ts +7 -0
- package/dist/cjs/lib/AuthManager/index.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/index.js +13 -0
- package/dist/cjs/lib/AuthManager/index.js.map +1 -0
- package/dist/cjs/lib/Cache.d.ts +12 -0
- package/dist/cjs/lib/Cache.d.ts.map +1 -0
- package/dist/cjs/lib/Cache.js +36 -0
- package/dist/cjs/lib/Cache.js.map +1 -0
- package/dist/cjs/lib/DefaultLogger.d.ts +17 -0
- package/dist/cjs/lib/DefaultLogger.d.ts.map +1 -0
- package/dist/cjs/lib/DefaultLogger.js +36 -0
- package/dist/cjs/lib/DefaultLogger.js.map +1 -0
- package/dist/cjs/lib/RestClient.d.ts +18 -0
- package/dist/cjs/lib/RestClient.d.ts.map +1 -0
- package/dist/cjs/lib/RestClient.js +67 -0
- package/dist/cjs/lib/RestClient.js.map +1 -0
- package/dist/cjs/lib/TypedEventEmitter.d.ts +16 -0
- package/dist/cjs/lib/TypedEventEmitter.d.ts.map +1 -0
- package/dist/cjs/lib/TypedEventEmitter.js +46 -0
- package/dist/cjs/lib/TypedEventEmitter.js.map +1 -0
- package/dist/cjs/lib/base64.d.ts +3 -0
- package/dist/cjs/lib/base64.d.ts.map +1 -0
- package/dist/cjs/lib/base64.js +21 -0
- package/dist/cjs/lib/base64.js.map +1 -0
- package/dist/cjs/lib/getUUID.d.ts +12 -0
- package/dist/cjs/lib/getUUID.d.ts.map +1 -0
- package/dist/cjs/lib/getUUID.js +29 -0
- package/dist/cjs/lib/getUUID.js.map +1 -0
- package/dist/cjs/lib/mediaInput.d.ts +33 -0
- package/dist/cjs/lib/mediaInput.d.ts.map +1 -0
- package/dist/cjs/lib/mediaInput.js +32 -0
- package/dist/cjs/lib/mediaInput.js.map +1 -0
- package/dist/cjs/lib/utils.d.ts +10 -0
- package/dist/cjs/lib/utils.d.ts.map +1 -0
- package/dist/cjs/lib/utils.js +24 -0
- package/dist/cjs/lib/utils.js.map +1 -0
- package/dist/cjs/types/ErrorData.d.ts +7 -0
- package/dist/cjs/types/ErrorData.d.ts.map +1 -0
- package/dist/cjs/types/ErrorData.js +3 -0
- package/dist/cjs/types/ErrorData.js.map +1 -0
- package/dist/cjs/types/json.d.ts +6 -0
- package/dist/cjs/types/json.d.ts.map +1 -0
- package/dist/cjs/types/json.js +3 -0
- package/dist/cjs/types/json.js.map +1 -0
- package/dist/cjs/types/token.d.ts +2 -0
- package/dist/cjs/types/token.d.ts.map +1 -0
- package/dist/cjs/types/token.js +3 -0
- package/dist/cjs/types/token.js.map +1 -0
- package/dist/cjs/version.d.ts +2 -0
- package/dist/cjs/version.d.ts.map +1 -0
- package/dist/cjs/version.js +5 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/cjs/wallet.d.ts +59 -0
- package/dist/cjs/wallet.d.ts.map +1 -0
- package/dist/cjs/wallet.js +125 -0
- package/dist/cjs/wallet.js.map +1 -0
- package/dist/esm/Account/index.js +183 -0
- package/dist/esm/Account/index.js.map +1 -0
- package/dist/esm/Account/types.js +15 -0
- package/dist/esm/Account/types.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/events.js +2 -0
- package/dist/esm/ApiClient/WebSocketClient/events.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/index.js +278 -0
- package/dist/esm/ApiClient/WebSocketClient/index.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/normalize.js +229 -0
- package/dist/esm/ApiClient/WebSocketClient/normalize.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/types.js +2 -0
- package/dist/esm/ApiClient/WebSocketClient/types.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/wireTypes.js +10 -0
- package/dist/esm/ApiClient/WebSocketClient/wireTypes.js.map +1 -0
- package/dist/esm/ApiClient/index.js +80 -0
- package/dist/esm/ApiClient/index.js.map +1 -0
- package/dist/esm/Chat/ChatStream.js +144 -0
- package/dist/esm/Chat/ChatStream.js.map +1 -0
- package/dist/esm/Chat/index.js +174 -0
- package/dist/esm/Chat/index.js.map +1 -0
- package/dist/esm/Chat/tools.js +19 -0
- package/dist/esm/Chat/tools.js.map +1 -0
- package/dist/esm/Chat/types.js +2 -0
- package/dist/esm/Chat/types.js.map +1 -0
- package/dist/esm/Projects/ControlNet.js +41 -0
- package/dist/esm/Projects/ControlNet.js.map +1 -0
- package/dist/esm/Projects/createJobRequestMessage.js +236 -0
- package/dist/esm/Projects/createJobRequestMessage.js.map +1 -0
- package/dist/esm/Projects/index.js +531 -0
- package/dist/esm/Projects/index.js.map +1 -0
- package/dist/esm/Projects/restNormalize.js +86 -0
- package/dist/esm/Projects/restNormalize.js.map +1 -0
- package/dist/esm/Projects/types.js +2 -0
- package/dist/esm/Projects/types.js.map +1 -0
- package/dist/esm/Projects/utils.js +55 -0
- package/dist/esm/Projects/utils.js.map +1 -0
- package/dist/esm/index.js +72 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/AuthManager/ApiKeyAuthManager.js +42 -0
- package/dist/esm/lib/AuthManager/ApiKeyAuthManager.js.map +1 -0
- package/dist/esm/lib/AuthManager/AuthManagerBase.js +9 -0
- package/dist/esm/lib/AuthManager/AuthManagerBase.js.map +1 -0
- package/dist/esm/lib/AuthManager/AuthStorage.js +13 -0
- package/dist/esm/lib/AuthManager/AuthStorage.js.map +1 -0
- package/dist/esm/lib/AuthManager/TokenAuthManager.js +159 -0
- package/dist/esm/lib/AuthManager/TokenAuthManager.js.map +1 -0
- package/dist/esm/lib/AuthManager/index.js +5 -0
- package/dist/esm/lib/AuthManager/index.js.map +1 -0
- package/dist/esm/lib/Cache.js +33 -0
- package/dist/esm/lib/Cache.js.map +1 -0
- package/dist/esm/lib/DefaultLogger.js +32 -0
- package/dist/esm/lib/DefaultLogger.js.map +1 -0
- package/dist/esm/lib/RestClient.js +62 -0
- package/dist/esm/lib/RestClient.js.map +1 -0
- package/dist/esm/lib/TypedEventEmitter.js +44 -0
- package/dist/esm/lib/TypedEventEmitter.js.map +1 -0
- package/dist/esm/lib/base64.js +17 -0
- package/dist/esm/lib/base64.js.map +1 -0
- package/dist/esm/lib/getUUID.js +26 -0
- package/dist/esm/lib/getUUID.js.map +1 -0
- package/dist/esm/lib/mediaInput.js +28 -0
- package/dist/esm/lib/mediaInput.js.map +1 -0
- package/dist/esm/lib/utils.js +19 -0
- package/dist/esm/lib/utils.js.map +1 -0
- package/dist/esm/types/ErrorData.js +2 -0
- package/dist/esm/types/ErrorData.js.map +1 -0
- package/dist/esm/types/json.js +2 -0
- package/dist/esm/types/json.js.map +1 -0
- package/dist/esm/types/token.js +2 -0
- package/dist/esm/types/token.js.map +1 -0
- package/dist/esm/version.js +2 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/esm/wallet.js +117 -0
- package/dist/esm/wallet.js.map +1 -0
- package/package.json +92 -0
- package/src/Account/index.ts +214 -0
- package/src/Account/types.ts +61 -0
- package/src/ApiClient/WebSocketClient/events.ts +168 -0
- package/src/ApiClient/WebSocketClient/index.ts +345 -0
- package/src/ApiClient/WebSocketClient/normalize.ts +287 -0
- package/src/ApiClient/WebSocketClient/types.ts +10 -0
- package/src/ApiClient/WebSocketClient/wireTypes.ts +183 -0
- package/src/ApiClient/index.ts +147 -0
- package/src/Chat/ChatStream.ts +164 -0
- package/src/Chat/index.ts +224 -0
- package/src/Chat/tools.ts +23 -0
- package/src/Chat/types.ts +165 -0
- package/src/Projects/ControlNet.ts +84 -0
- package/src/Projects/createJobRequestMessage.ts +241 -0
- package/src/Projects/index.ts +608 -0
- package/src/Projects/restNormalize.ts +154 -0
- package/src/Projects/types.ts +266 -0
- package/src/Projects/utils.ts +60 -0
- package/src/index.ts +217 -0
- package/src/lib/AuthManager/ApiKeyAuthManager.ts +52 -0
- package/src/lib/AuthManager/AuthManagerBase.ts +35 -0
- package/src/lib/AuthManager/AuthStorage.ts +21 -0
- package/src/lib/AuthManager/TokenAuthManager.ts +179 -0
- package/src/lib/AuthManager/index.ts +8 -0
- package/src/lib/Cache.ts +45 -0
- package/src/lib/DefaultLogger.ts +47 -0
- package/src/lib/RestClient.ts +77 -0
- package/src/lib/TypedEventEmitter.ts +52 -0
- package/src/lib/base64.ts +17 -0
- package/src/lib/getUUID.ts +27 -0
- package/src/lib/mediaInput.ts +54 -0
- package/src/lib/utils.ts +21 -0
- package/src/types/ErrorData.ts +7 -0
- package/src/types/json.ts +5 -0
- package/src/types/token.ts +1 -0
- package/src/version.ts +1 -0
- 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
|
+
}
|