@yetter/client 0.0.11 → 0.0.13
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/README.md +121 -17
- package/dist/api.d.ts +16 -1
- package/dist/api.js +130 -40
- package/dist/client.d.ts +30 -3
- package/dist/client.js +412 -156
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +63 -0
- package/package.json +9 -2
- package/examples/stream.ts +0 -54
- package/examples/submit.ts +0 -80
- package/examples/subscribe.ts +0 -41
- package/src/api.ts +0 -105
- package/src/client.ts +0 -338
- package/src/index.ts +0 -3
- package/src/types.ts +0 -116
- package/tsconfig.json +0 -13
package/src/client.ts
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { YetterImageClient } from "./api.js";
|
|
2
|
-
import { EventSourcePolyfill } from 'event-source-polyfill';
|
|
3
|
-
import {
|
|
4
|
-
ClientOptions,
|
|
5
|
-
GetStatusResponse,
|
|
6
|
-
GetResponseResponse,
|
|
7
|
-
SubscribeOptions,
|
|
8
|
-
GenerateResponse,
|
|
9
|
-
SubmitQueueOptions,
|
|
10
|
-
GetResultOptions,
|
|
11
|
-
GetResultResponse,
|
|
12
|
-
StatusOptions,
|
|
13
|
-
StatusResponse,
|
|
14
|
-
StreamOptions,
|
|
15
|
-
StreamEvent,
|
|
16
|
-
YetterStream,
|
|
17
|
-
} from "./types.js";
|
|
18
|
-
|
|
19
|
-
export class yetter {
|
|
20
|
-
private static apiKey: string = 'Key ' + (process.env.YTR_API_KEY || process.env.REACT_APP_YTR_API_KEY || "");
|
|
21
|
-
private static endpoint: string = "https://api.yetter.ai";
|
|
22
|
-
|
|
23
|
-
public static configure(options: ClientOptions) {
|
|
24
|
-
if (options.apiKey) {
|
|
25
|
-
if (options.is_bearer) {
|
|
26
|
-
yetter.apiKey = 'Bearer ' + options.apiKey;
|
|
27
|
-
} else {
|
|
28
|
-
yetter.apiKey = 'Key ' + options.apiKey;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (options.endpoint) {
|
|
32
|
-
yetter.endpoint = options.endpoint;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public static async subscribe(
|
|
37
|
-
model: string,
|
|
38
|
-
options: SubscribeOptions
|
|
39
|
-
): Promise<GetResponseResponse> {
|
|
40
|
-
if (!yetter.apiKey) {
|
|
41
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
42
|
-
}
|
|
43
|
-
const client = new YetterImageClient({
|
|
44
|
-
apiKey: yetter.apiKey,
|
|
45
|
-
endpoint: yetter.endpoint
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const generateResponse = await client.generate({
|
|
49
|
-
model: model,
|
|
50
|
-
...options.input,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
let status = generateResponse.status;
|
|
54
|
-
let lastStatusResponse: GetStatusResponse | undefined;
|
|
55
|
-
|
|
56
|
-
const startTime = Date.now();
|
|
57
|
-
const timeoutMilliseconds = 30 * 60 * 1000; // 30 minutes
|
|
58
|
-
|
|
59
|
-
while (status !== "COMPLETED" && status !== "FAILED") {
|
|
60
|
-
if (Date.now() - startTime > timeoutMilliseconds) {
|
|
61
|
-
console.warn(`Subscription timed out after 30 minutes for request ID: ${generateResponse.request_id}. Attempting to cancel.`);
|
|
62
|
-
try {
|
|
63
|
-
await client.cancel({ url: generateResponse.cancel_url });
|
|
64
|
-
console.log(`Cancellation request sent for request ID: ${generateResponse.request_id}.`);
|
|
65
|
-
} catch (cancelError: any) {
|
|
66
|
-
console.error(`Failed to cancel request ID: ${generateResponse.request_id} after timeout:`, cancelError.message || cancelError);
|
|
67
|
-
}
|
|
68
|
-
throw new Error(`Subscription timed out after 30 minutes for request ID: ${generateResponse.request_id}.`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
lastStatusResponse = await client.getStatus({
|
|
73
|
-
url: generateResponse.status_url,
|
|
74
|
-
logs: options.logs,
|
|
75
|
-
});
|
|
76
|
-
status = lastStatusResponse.status;
|
|
77
|
-
|
|
78
|
-
if (options.onQueueUpdate) {
|
|
79
|
-
options.onQueueUpdate(lastStatusResponse);
|
|
80
|
-
}
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error("Error during status polling:", error);
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (status === "FAILED") {
|
|
88
|
-
const errorMessage = lastStatusResponse?.logs?.map(log => log.message).join("\n") || "Image generation failed.";
|
|
89
|
-
throw new Error(errorMessage);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const finalResponse = await client.getResponse({
|
|
93
|
-
url: generateResponse.response_url,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
return finalResponse;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
public static queue = {
|
|
100
|
-
submit: async (
|
|
101
|
-
model: string,
|
|
102
|
-
options: SubmitQueueOptions
|
|
103
|
-
): Promise<GenerateResponse> => {
|
|
104
|
-
if (!yetter.apiKey) {
|
|
105
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
106
|
-
}
|
|
107
|
-
const client = new YetterImageClient({
|
|
108
|
-
apiKey: yetter.apiKey,
|
|
109
|
-
endpoint: yetter.endpoint
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const generateResponse = await client.generate({
|
|
113
|
-
model: model,
|
|
114
|
-
...options.input,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return generateResponse;
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
status: async (
|
|
121
|
-
model: string,
|
|
122
|
-
options: StatusOptions
|
|
123
|
-
): Promise<StatusResponse> => {
|
|
124
|
-
if (!yetter.apiKey) {
|
|
125
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
126
|
-
}
|
|
127
|
-
const client = new YetterImageClient({
|
|
128
|
-
apiKey: yetter.apiKey,
|
|
129
|
-
endpoint: yetter.endpoint
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const endpoint = client.getApiEndpoint();
|
|
133
|
-
|
|
134
|
-
const statusUrl = `${endpoint}/${model}/requests/${options.requestId}/status`;
|
|
135
|
-
|
|
136
|
-
const statusData = await client.getStatus({ url: statusUrl });
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
data: statusData,
|
|
140
|
-
requestId: options.requestId,
|
|
141
|
-
};
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
result: async (
|
|
145
|
-
model: string,
|
|
146
|
-
options: GetResultOptions
|
|
147
|
-
): Promise<GetResultResponse> => {
|
|
148
|
-
if (!yetter.apiKey) {
|
|
149
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
150
|
-
}
|
|
151
|
-
const client = new YetterImageClient({
|
|
152
|
-
apiKey: yetter.apiKey,
|
|
153
|
-
endpoint: yetter.endpoint
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const endpoint = client.getApiEndpoint();
|
|
157
|
-
|
|
158
|
-
const responseUrl = `${endpoint}/${model}/requests/${options.requestId}`;
|
|
159
|
-
|
|
160
|
-
const responseData = await client.getResponse({ url: responseUrl });
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
data: responseData,
|
|
164
|
-
requestId: options.requestId,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
public static async stream(
|
|
170
|
-
model: string,
|
|
171
|
-
options: StreamOptions
|
|
172
|
-
): Promise<YetterStream> {
|
|
173
|
-
if (!yetter.apiKey) {
|
|
174
|
-
throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
|
|
175
|
-
}
|
|
176
|
-
const client = new YetterImageClient({
|
|
177
|
-
apiKey: yetter.apiKey,
|
|
178
|
-
endpoint: yetter.endpoint
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const initialApiResponse = await client.generate({
|
|
182
|
-
model: model,
|
|
183
|
-
...options.input,
|
|
184
|
-
});
|
|
185
|
-
const requestId = initialApiResponse.request_id;
|
|
186
|
-
const responseUrl = initialApiResponse.response_url;
|
|
187
|
-
const cancelUrl = initialApiResponse.cancel_url;
|
|
188
|
-
const sseStreamUrl = `${client.getApiEndpoint()}/${model}/requests/${requestId}/status/stream`;
|
|
189
|
-
|
|
190
|
-
let eventSource: EventSource;
|
|
191
|
-
let streamEnded = false;
|
|
192
|
-
|
|
193
|
-
// Setup the promise for the done() method
|
|
194
|
-
let resolveDonePromise: (value: GetResponseResponse) => void;
|
|
195
|
-
let rejectDonePromise: (reason?: any) => void;
|
|
196
|
-
const donePromise = new Promise<GetResponseResponse>((resolve, reject) => {
|
|
197
|
-
resolveDonePromise = resolve;
|
|
198
|
-
rejectDonePromise = reject;
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const controller = {
|
|
202
|
-
// This will be used by the async iterator to pull events
|
|
203
|
-
// It needs a way to buffer events or signal availability
|
|
204
|
-
events: [] as StreamEvent[],
|
|
205
|
-
resolvers: [] as Array<(value: IteratorResult<StreamEvent, any>) => void>,
|
|
206
|
-
isClosed: false,
|
|
207
|
-
push(event: StreamEvent) {
|
|
208
|
-
if (this.isClosed) return;
|
|
209
|
-
if (this.resolvers.length > 0) {
|
|
210
|
-
this.resolvers.shift()!({ value: event, done: false });
|
|
211
|
-
} else {
|
|
212
|
-
this.events.push(event);
|
|
213
|
-
}
|
|
214
|
-
// Check for terminal events to resolve/reject the donePromise
|
|
215
|
-
if (event.status === "COMPLETED") {
|
|
216
|
-
streamEnded = true;
|
|
217
|
-
client.getResponse({ url: responseUrl })
|
|
218
|
-
.then(resolveDonePromise)
|
|
219
|
-
.catch(rejectDonePromise)
|
|
220
|
-
.finally(() => this.close());
|
|
221
|
-
} else if (event.status === "FAILED") {
|
|
222
|
-
streamEnded = true;
|
|
223
|
-
rejectDonePromise(new Error(event.logs?.map(l => l.message).join('\n') || `Stream reported FAILED for ${requestId}`));
|
|
224
|
-
this.close();
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
error(err: any) {
|
|
228
|
-
if (this.isClosed) return;
|
|
229
|
-
|
|
230
|
-
// If streamEnded is true, it means a terminal event (COMPLETED/FAILED)
|
|
231
|
-
// has already been processed and is handling the donePromise.
|
|
232
|
-
// This error is likely a secondary effect of the connection closing.
|
|
233
|
-
if (!streamEnded) {
|
|
234
|
-
rejectDonePromise(err); // Only reject if no terminal event was processed
|
|
235
|
-
} else {
|
|
236
|
-
console.warn("SSE 'onerror' event after stream was considered ended (COMPLETED/FAILED). This error will not alter the done() promise.", err);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
streamEnded = true; // Ensure it's marked as ended
|
|
240
|
-
if (this.resolvers.length > 0) {
|
|
241
|
-
this.resolvers.shift()!({ value: undefined, done: true }); // Signal error to iterator consumer
|
|
242
|
-
}
|
|
243
|
-
this.close(); // Proceed to close EventSource etc.
|
|
244
|
-
},
|
|
245
|
-
close() {
|
|
246
|
-
if (this.isClosed) return;
|
|
247
|
-
this.isClosed = true;
|
|
248
|
-
this.resolvers.forEach(resolve => resolve({ value: undefined, done: true }));
|
|
249
|
-
this.resolvers = [];
|
|
250
|
-
eventSource?.close();
|
|
251
|
-
// If donePromise is still pending, reject it as stream closed prematurely
|
|
252
|
-
// Use a timeout to allow any final event processing for COMPLETED/FAILED to settle it first
|
|
253
|
-
setTimeout(() => {
|
|
254
|
-
donePromise.catch(() => {}).finally(() => { // check if it's already settled
|
|
255
|
-
if (!streamEnded) { // If not explicitly completed or failed
|
|
256
|
-
rejectDonePromise(new Error("Stream closed prematurely or unexpectedly."));
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
}, 100);
|
|
260
|
-
},
|
|
261
|
-
next(): Promise<IteratorResult<StreamEvent, any>> {
|
|
262
|
-
if (this.events.length > 0) {
|
|
263
|
-
return Promise.resolve({ value: this.events.shift()!, done: false });
|
|
264
|
-
}
|
|
265
|
-
if (this.isClosed) {
|
|
266
|
-
return Promise.resolve({ value: undefined, done: true });
|
|
267
|
-
}
|
|
268
|
-
return new Promise(resolve => this.resolvers.push(resolve));
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
eventSource = new EventSourcePolyfill(sseStreamUrl, {
|
|
273
|
-
headers: { 'Authorization': `${yetter.apiKey}` },
|
|
274
|
-
} as any);
|
|
275
|
-
|
|
276
|
-
eventSource.onopen = (event: Event) => {
|
|
277
|
-
console.log("SSE Connection Opened:", event);
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
eventSource.addEventListener('data', (event: MessageEvent) => {
|
|
281
|
-
// console.log("SSE 'data' event received, raw data:", event.data);
|
|
282
|
-
try {
|
|
283
|
-
const statusData: GetStatusResponse = JSON.parse(event.data as string);
|
|
284
|
-
controller.push(statusData as StreamEvent);
|
|
285
|
-
} catch (e: any) {
|
|
286
|
-
console.error("Error parsing SSE 'data' event:", e, "Raw data:", event.data);
|
|
287
|
-
controller.error(new Error(`Error parsing SSE 'data' event: ${e.message}`));
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
eventSource.addEventListener('done', (event: MessageEvent) => {
|
|
292
|
-
// console.log("SSE 'done' event received, raw data:", event.data);
|
|
293
|
-
controller.close();
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
eventSource.onerror = (err: Event | MessageEvent) => {
|
|
297
|
-
const message = (err as any)?.error?.message || (err as any)?.message || '';
|
|
298
|
-
const isIdleTimeout = typeof message === 'string' && message.includes('No activity within') && message.includes('Reconnecting');
|
|
299
|
-
if (isIdleTimeout) {
|
|
300
|
-
console.warn("SSE idle timeout; letting EventSource auto-reconnect.", err);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
console.warn("SSE Connection Error (onerror) - will allow auto-reconnect:", err);
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
// Handle if API immediately returns a terminal status in initialApiResponse (e.g. already completed/failed)
|
|
307
|
-
if (initialApiResponse.status === "COMPLETED"){
|
|
308
|
-
controller.push(initialApiResponse as any as StreamEvent);
|
|
309
|
-
} else if (initialApiResponse.status === "FAILED"){
|
|
310
|
-
controller.push(initialApiResponse as any as StreamEvent);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
async *[Symbol.asyncIterator]() {
|
|
315
|
-
while (!controller.isClosed || controller.events.length > 0) {
|
|
316
|
-
const event = await controller.next();
|
|
317
|
-
if (event.done) break;
|
|
318
|
-
yield event.value;
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
done: () => donePromise,
|
|
322
|
-
cancel: async () => {
|
|
323
|
-
controller.close();
|
|
324
|
-
try {
|
|
325
|
-
await client.cancel({ url: cancelUrl });
|
|
326
|
-
console.log(`Stream for ${requestId} - underlying request cancelled.`);
|
|
327
|
-
} catch (e: any) {
|
|
328
|
-
console.error(`Error cancelling underlying request for stream ${requestId}:`, e.message);
|
|
329
|
-
}
|
|
330
|
-
// Ensure donePromise is settled if not already
|
|
331
|
-
if (!streamEnded) {
|
|
332
|
-
rejectDonePromise(new Error("Stream was cancelled by user."));
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
getRequestId: () => requestId,
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
}
|
package/src/index.ts
DELETED
package/src/types.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
export interface ClientOptions {
|
|
2
|
-
apiKey: string;
|
|
3
|
-
endpoint?: string;
|
|
4
|
-
is_bearer?: boolean;
|
|
5
|
-
promptEnhancerModel?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface LoraWeight {
|
|
9
|
-
path: string;
|
|
10
|
-
scale?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface GenerateRequest {
|
|
14
|
-
[key: string]: any;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface GenerateResponse {
|
|
18
|
-
status: string;
|
|
19
|
-
request_id: string;
|
|
20
|
-
response_url: string;
|
|
21
|
-
status_url: string;
|
|
22
|
-
cancel_url: string;
|
|
23
|
-
queue_position: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface LogEntry {
|
|
27
|
-
message: string;
|
|
28
|
-
// Add other potential log fields here, e.g., timestamp, level
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface GetStatusRequest {
|
|
32
|
-
url: string;
|
|
33
|
-
logs?: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface GetStatusResponse {
|
|
37
|
-
status: string;
|
|
38
|
-
request_id?: string;
|
|
39
|
-
response_url?: string;
|
|
40
|
-
status_url?: string;
|
|
41
|
-
cancel_url?: string;
|
|
42
|
-
queue_position?: number;
|
|
43
|
-
logs?: LogEntry[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface CancelRequest {
|
|
47
|
-
url: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface CancelResponse {
|
|
51
|
-
status: string;
|
|
52
|
-
request_id?: string;
|
|
53
|
-
response_url?: string;
|
|
54
|
-
status_url?: string;
|
|
55
|
-
cancel_url?: string;
|
|
56
|
-
queue_position?: number;
|
|
57
|
-
logs?: string[];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface GetResponseRequest {
|
|
61
|
-
url: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface GetResponseResponse {
|
|
65
|
-
images: string[] | Record<string, any>[];
|
|
66
|
-
prompt: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface SubscribeOptions {
|
|
70
|
-
input: Omit<GenerateRequest, 'model'>; // model is a direct param to subscribe
|
|
71
|
-
logs?: boolean;
|
|
72
|
-
onQueueUpdate?: (update: GetStatusResponse) => void;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface SubmitQueueOptions {
|
|
76
|
-
input: Omit<GenerateRequest, 'model'>;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface GetResultOptions {
|
|
80
|
-
requestId: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface GetResultResponse {
|
|
84
|
-
data: GetResponseResponse;
|
|
85
|
-
requestId: string;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface StatusOptions {
|
|
89
|
-
requestId: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface StatusResponse {
|
|
93
|
-
data: GetStatusResponse;
|
|
94
|
-
requestId: string;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// --- Stream Types ---
|
|
98
|
-
export interface StreamOptions {
|
|
99
|
-
input: Omit<GenerateRequest, 'model'>;
|
|
100
|
-
// logs?: boolean; // Optional: if you want to request logs via stream if API supports it
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Represents an event received from the SSE stream.
|
|
105
|
-
* The `data` field is expected to be compatible with GetStatusResponse.
|
|
106
|
-
*/
|
|
107
|
-
export interface StreamEvent extends GetStatusResponse { // SSE events directly reflect status updates
|
|
108
|
-
// type: string; // Could be 'status', 'error', 'open', etc. if the stream sends typed messages.
|
|
109
|
-
// If the stream ONLY sends GetStatusResponse JSON, this might not be needed.
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export interface YetterStream extends AsyncIterable<StreamEvent> {
|
|
113
|
-
done(): Promise<GetResponseResponse>; // Resolves with the final image data
|
|
114
|
-
cancel(): Promise<void>; // Cancels the stream and attempts to cancel the underlying API request
|
|
115
|
-
getRequestId(): string; // Helper to get the request ID associated with the stream
|
|
116
|
-
}
|
package/tsconfig.json
DELETED