@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/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
@@ -1,3 +0,0 @@
1
- export { YetterImageClient } from "./api.js";
2
- export * from "./types.js";
3
- export { yetter } from "./client.js";
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
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2019",
4
- "module": "ESNext",
5
- "moduleResolution": "node",
6
- "declaration": true,
7
- "outDir": "dist",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true
11
- },
12
- "include": ["src/**/*"]
13
- }