@eightstate/escli 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONVENTIONS.md +59 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/RELEASE-NOTES-0.5.0.md +34 -0
- package/dist/base-command.js +166 -0
- package/dist/commands/audio/get.js +39 -0
- package/dist/commands/audio/index.js +18 -0
- package/dist/commands/audio/list.js +39 -0
- package/dist/commands/audio/status.js +34 -0
- package/dist/commands/audio/transcribe.js +99 -0
- package/dist/commands/auth/index.js +18 -0
- package/dist/commands/auth/login.js +38 -0
- package/dist/commands/auth/logout.js +27 -0
- package/dist/commands/auth/profiles.js +31 -0
- package/dist/commands/auth/status.js +27 -0
- package/dist/commands/auth/switch.js +24 -0
- package/dist/commands/docs/fetch.js +37 -0
- package/dist/commands/docs/get.js +47 -0
- package/dist/commands/docs/index.js +18 -0
- package/dist/commands/docs/search.js +55 -0
- package/dist/commands/fetch.js +55 -0
- package/dist/commands/image/edit.js +59 -0
- package/dist/commands/image/generate.js +67 -0
- package/dist/commands/image/index.js +18 -0
- package/dist/commands/models.js +27 -0
- package/dist/commands/research.js +92 -0
- package/dist/commands/search.js +54 -0
- package/dist/commands/social.js +69 -0
- package/dist/commands/usage.js +51 -0
- package/dist/commands/version.js +22 -0
- package/dist/entry.js +120 -0
- package/dist/io/io.js +322 -0
- package/dist/lib/build-flags.js +2 -0
- package/dist/lib/command-metadata.js +8 -0
- package/dist/lib/envelope.js +28 -0
- package/dist/lib/escli-error.js +20 -0
- package/dist/lib/global-flags.js +29 -0
- package/dist/lib/globals.js +2 -0
- package/dist/lib/manifest.js +67 -0
- package/dist/lib/oclif-manifest-check.js +11 -0
- package/dist/lib/registry.js +228 -0
- package/dist/services/audio.js +454 -0
- package/dist/services/auth.js +329 -0
- package/dist/services/credentials.js +137 -0
- package/dist/services/docs.js +303 -0
- package/dist/services/fetch.js +197 -0
- package/dist/services/image.js +297 -0
- package/dist/services/models.js +131 -0
- package/dist/services/research.js +504 -0
- package/dist/services/search.js +195 -0
- package/dist/services/social.js +224 -0
- package/dist/services/usage.js +165 -0
- package/oclif.manifest.json +3377 -0
- package/package.json +57 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { extname, dirname, resolve, join } from 'node:path';
|
|
3
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
4
|
+
import { ExitCodes } from '@eightstate/contracts/exit-codes';
|
|
5
|
+
import { EscliError } from '../lib/escli-error.js';
|
|
6
|
+
import { callWithRetry, DEFAULT_ENDPOINT, resolveApiKey, resolveEndpoint } from './credentials.js';
|
|
7
|
+
const DEFAULT_IMAGE_MODEL = 'gpt-image-2';
|
|
8
|
+
const DEFAULT_IMAGE_QUALITY = 'high';
|
|
9
|
+
const DEFAULT_IMAGE_SIZE = '1024x1024';
|
|
10
|
+
const DEFAULT_IMAGE_FORMAT = 'png';
|
|
11
|
+
const DEFAULT_OUT_DIR = '.';
|
|
12
|
+
const SIZE_ALIASES = new Map([
|
|
13
|
+
['square', '1024x1024'],
|
|
14
|
+
['sq', '1024x1024'],
|
|
15
|
+
['1024x1024', '1024x1024'],
|
|
16
|
+
['landscape', '1536x1024'],
|
|
17
|
+
['land', '1536x1024'],
|
|
18
|
+
['ls', '1536x1024'],
|
|
19
|
+
['wide', '1536x1024'],
|
|
20
|
+
['1536x1024', '1536x1024'],
|
|
21
|
+
['portrait', '1024x1536'],
|
|
22
|
+
['port', '1024x1536'],
|
|
23
|
+
['tall', '1024x1536'],
|
|
24
|
+
['1024x1536', '1024x1536'],
|
|
25
|
+
]);
|
|
26
|
+
export async function generateImage(options) {
|
|
27
|
+
const prompt = options.prompt.trim();
|
|
28
|
+
if (!prompt)
|
|
29
|
+
throw new EscliError('prompt required', { code: ErrorCode.ImagePromptRequired, exitCode: ExitCodes.Usage });
|
|
30
|
+
const started = Date.now();
|
|
31
|
+
const size = resolveImageSize(options.size ?? envDefault('ESCLI_IMAGE_SIZE', DEFAULT_IMAGE_SIZE));
|
|
32
|
+
const quality = options.quality ?? envDefault('ESCLI_IMAGE_QUALITY', DEFAULT_IMAGE_QUALITY);
|
|
33
|
+
const format = options.format ?? envDefault('ESCLI_IMAGE_FORMAT', DEFAULT_IMAGE_FORMAT);
|
|
34
|
+
const model = options.model ?? envDefault('ESCLI_IMAGE_MODEL', DEFAULT_IMAGE_MODEL);
|
|
35
|
+
const outputPath = resolveOutputPath(options.output, options.outDir, prompt, format);
|
|
36
|
+
const body = {
|
|
37
|
+
model,
|
|
38
|
+
prompt,
|
|
39
|
+
size,
|
|
40
|
+
quality,
|
|
41
|
+
response_format: 'b64_json',
|
|
42
|
+
};
|
|
43
|
+
const extraBody = {};
|
|
44
|
+
const outputFormat = apiOutputFormat(format);
|
|
45
|
+
if (outputFormat !== 'png')
|
|
46
|
+
extraBody.output_format = outputFormat;
|
|
47
|
+
if (options.compression !== undefined)
|
|
48
|
+
extraBody.output_compression = options.compression;
|
|
49
|
+
if (options.background)
|
|
50
|
+
extraBody.background = options.background;
|
|
51
|
+
if (options.moderation)
|
|
52
|
+
extraBody.moderation = options.moderation;
|
|
53
|
+
if (Object.keys(extraBody).length > 0)
|
|
54
|
+
body.extra_body = extraBody;
|
|
55
|
+
const data = await performImageRequest({ path: '/images/generations', body, options });
|
|
56
|
+
return writeImageResult({ data, outputPath, prompt, model, size, quality, elapsedMs: Date.now() - started });
|
|
57
|
+
}
|
|
58
|
+
export async function editImage(options) {
|
|
59
|
+
const prompt = options.prompt.trim();
|
|
60
|
+
if (!prompt)
|
|
61
|
+
throw new EscliError('prompt required', { code: ErrorCode.ImagePromptRequired, exitCode: ExitCodes.Usage });
|
|
62
|
+
const inputPath = resolve(options.input);
|
|
63
|
+
const inputBytes = await readInputImage(inputPath);
|
|
64
|
+
const started = Date.now();
|
|
65
|
+
const size = resolveImageSize(options.size ?? envDefault('ESCLI_IMAGE_SIZE', DEFAULT_IMAGE_SIZE));
|
|
66
|
+
const quality = options.quality ?? envDefault('ESCLI_IMAGE_QUALITY', DEFAULT_IMAGE_QUALITY);
|
|
67
|
+
const format = options.format ?? envDefault('ESCLI_IMAGE_FORMAT', DEFAULT_IMAGE_FORMAT);
|
|
68
|
+
const model = options.model ?? envDefault('ESCLI_IMAGE_MODEL', DEFAULT_IMAGE_MODEL);
|
|
69
|
+
const outputPath = resolveOutputPath(options.output, options.outDir, prompt, format);
|
|
70
|
+
const outputFormat = apiOutputFormat(format);
|
|
71
|
+
const body = {
|
|
72
|
+
model,
|
|
73
|
+
prompt,
|
|
74
|
+
images: [{ image_url: `data:${mimeForPath(inputPath)};base64,${Buffer.from(inputBytes).toString('base64')}` }],
|
|
75
|
+
size,
|
|
76
|
+
quality,
|
|
77
|
+
response_format: 'b64_json',
|
|
78
|
+
};
|
|
79
|
+
if (outputFormat !== 'png')
|
|
80
|
+
body.output_format = outputFormat;
|
|
81
|
+
if (options.compression !== undefined)
|
|
82
|
+
body.output_compression = options.compression;
|
|
83
|
+
if (options.fidelity)
|
|
84
|
+
body.input_fidelity = options.fidelity;
|
|
85
|
+
const data = await performImageRequest({ path: '/images/edits', body, options });
|
|
86
|
+
return writeImageResult({ data, outputPath, prompt, inputPath, model, size, quality, elapsedMs: Date.now() - started });
|
|
87
|
+
}
|
|
88
|
+
function resolveImageSize(value) {
|
|
89
|
+
const resolved = SIZE_ALIASES.get(value.toLowerCase().trim());
|
|
90
|
+
if (!resolved)
|
|
91
|
+
throw new EscliError(`invalid size: ${value}`, { code: ErrorCode.ImageSizeInvalid, exitCode: ExitCodes.Usage, details: { size: value } });
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
async function performImageRequest(input) {
|
|
95
|
+
const timeoutMs = (input.options.timeoutSeconds ?? 300) * 1000;
|
|
96
|
+
const explicitKey = resolveApiKey(input.options.apiKey);
|
|
97
|
+
if (explicitKey) {
|
|
98
|
+
return requestImageJson({ path: input.path, body: input.body, apiKey: explicitKey, baseUrl: resolveEndpoint(input.options.baseUrl), timeoutMs });
|
|
99
|
+
}
|
|
100
|
+
const response = await mapImageErrors(async () => callWithRetry('image', (key) => requestImageJson({
|
|
101
|
+
path: input.path,
|
|
102
|
+
body: input.body,
|
|
103
|
+
apiKey: key.apiKey,
|
|
104
|
+
baseUrl: key.baseUrl ?? DEFAULT_ENDPOINT,
|
|
105
|
+
timeoutMs,
|
|
106
|
+
})));
|
|
107
|
+
if (response === null) {
|
|
108
|
+
throw new EscliError('not authenticated', {
|
|
109
|
+
code: ErrorCode.AuthRequired,
|
|
110
|
+
exitCode: ExitCodes.Auth,
|
|
111
|
+
remediation: { command: 'escli auth login' },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
async function requestImageJson(options) {
|
|
117
|
+
return mapImageErrors(async () => {
|
|
118
|
+
const response = await fetchWithTimeout(`${options.baseUrl.replace(/\/+$/u, '')}${options.path}`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'authorization': `Bearer ${options.apiKey}`, 'content-type': 'application/json', 'accept': 'application/json' },
|
|
121
|
+
body: JSON.stringify(options.body),
|
|
122
|
+
}, options.timeoutMs);
|
|
123
|
+
const text = await response.text();
|
|
124
|
+
const body = parseJsonResponse(text);
|
|
125
|
+
if (!response.ok)
|
|
126
|
+
throw httpStatusError(response.status, formatHttpError(response.status, body), body);
|
|
127
|
+
return body;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
133
|
+
try {
|
|
134
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw mapNetworkError(error);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function mapImageErrors(fn) {
|
|
144
|
+
try {
|
|
145
|
+
return await fn();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (error instanceof EscliError)
|
|
149
|
+
throw error;
|
|
150
|
+
if (isHttpStatusError(error))
|
|
151
|
+
throw mapHttpStatusError(error);
|
|
152
|
+
throw mapNetworkError(error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function parseJsonResponse(text) {
|
|
156
|
+
if (!text)
|
|
157
|
+
return {};
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse(text);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
throw new EscliError(`invalid JSON response: ${error instanceof Error ? error.message : String(error)}`, {
|
|
163
|
+
code: ErrorCode.GateInvalidResponse,
|
|
164
|
+
details: text,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function writeImageResult(input) {
|
|
169
|
+
const item = firstImageData(input.data);
|
|
170
|
+
const b64 = stringValue(item.b64_json);
|
|
171
|
+
if (!b64)
|
|
172
|
+
throw new EscliError('invalid image response: missing b64_json', { code: ErrorCode.ImageGenerationFailed, details: input.data });
|
|
173
|
+
const bytes = Buffer.from(b64, 'base64');
|
|
174
|
+
try {
|
|
175
|
+
await mkdir(dirname(input.outputPath), { recursive: true });
|
|
176
|
+
await writeFile(input.outputPath, bytes);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new EscliError(`failed to write file: ${input.outputPath}`, { code: ErrorCode.FileWriteFailed, details: error instanceof Error ? error.message : error });
|
|
180
|
+
}
|
|
181
|
+
const result = {
|
|
182
|
+
path: input.outputPath,
|
|
183
|
+
size_bytes: bytes.length,
|
|
184
|
+
elapsed_seconds: Math.round(input.elapsedMs / 100) / 10,
|
|
185
|
+
prompt: input.prompt,
|
|
186
|
+
model: input.model,
|
|
187
|
+
image_size: input.size,
|
|
188
|
+
quality: input.quality,
|
|
189
|
+
};
|
|
190
|
+
if (input.inputPath)
|
|
191
|
+
result.input = input.inputPath;
|
|
192
|
+
const revisedPrompt = stringValue(item.revised_prompt);
|
|
193
|
+
if (revisedPrompt && !input.inputPath)
|
|
194
|
+
result.revised_prompt = revisedPrompt;
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
function firstImageData(data) {
|
|
198
|
+
const first = arrayValue(recordValue(data)?.data)[0];
|
|
199
|
+
const item = recordValue(first);
|
|
200
|
+
if (!item)
|
|
201
|
+
throw new EscliError('invalid image response: missing data', { code: ErrorCode.ImageGenerationFailed, details: data });
|
|
202
|
+
return item;
|
|
203
|
+
}
|
|
204
|
+
async function readInputImage(path) {
|
|
205
|
+
try {
|
|
206
|
+
return await readFile(path);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
const nodeError = error;
|
|
210
|
+
if (nodeError.code === 'ENOENT')
|
|
211
|
+
throw new EscliError(`file not found: ${path}`, { code: ErrorCode.FileNotFound, details: { path } });
|
|
212
|
+
throw new EscliError(`failed to read file: ${path}`, { code: ErrorCode.FileReadFailed, details: error instanceof Error ? error.message : error });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function mapHttpStatusError(error) {
|
|
216
|
+
const status = error.status;
|
|
217
|
+
if (status === 401)
|
|
218
|
+
return new EscliError(error.message, { code: ErrorCode.ApiUnauthorized, details: error.details });
|
|
219
|
+
if (status === 403)
|
|
220
|
+
return new EscliError(error.message, { code: ErrorCode.ApiForbidden, details: error.details });
|
|
221
|
+
if (status === 429)
|
|
222
|
+
return new EscliError(error.message, { code: ErrorCode.ApiRateLimited, details: error.details });
|
|
223
|
+
if (status >= 500)
|
|
224
|
+
return new EscliError(error.message, { code: ErrorCode.ServiceUnavailable, details: error.details });
|
|
225
|
+
if (status === 404)
|
|
226
|
+
return new EscliError(error.message, { code: ErrorCode.ApiError, exitCode: ExitCodes.NotFound, details: error.details });
|
|
227
|
+
return new EscliError(error.message, { code: ErrorCode.ImageGenerationFailed, details: error.details });
|
|
228
|
+
}
|
|
229
|
+
function mapNetworkError(error) {
|
|
230
|
+
const isAbort = error instanceof Error && error.name === 'AbortError';
|
|
231
|
+
return new EscliError(isAbort ? 'Connection timeout.' : 'Connection error.', {
|
|
232
|
+
code: isAbort ? ErrorCode.NetworkTimeout : ErrorCode.NetworkError,
|
|
233
|
+
details: error instanceof Error ? error.message : error,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function formatHttpError(status, body) {
|
|
237
|
+
const message = extractErrorMessage(body);
|
|
238
|
+
return message ? `Error code: ${status} - ${message}` : `Error code: ${status}`;
|
|
239
|
+
}
|
|
240
|
+
function extractErrorMessage(body) {
|
|
241
|
+
const root = recordValue(body);
|
|
242
|
+
const error = recordValue(root?.error);
|
|
243
|
+
return stringValue(error?.message) ?? stringValue(root?.message);
|
|
244
|
+
}
|
|
245
|
+
function httpStatusError(status, message, details) {
|
|
246
|
+
const error = new Error(message);
|
|
247
|
+
error.status = status;
|
|
248
|
+
error.details = details;
|
|
249
|
+
return error;
|
|
250
|
+
}
|
|
251
|
+
function isHttpStatusError(error) {
|
|
252
|
+
return error instanceof Error && typeof error.status === 'number';
|
|
253
|
+
}
|
|
254
|
+
function resolveOutputPath(output, outDir, prompt, format) {
|
|
255
|
+
if (output)
|
|
256
|
+
return resolve(output);
|
|
257
|
+
return resolve(join(outDir ?? envDefault('ESCLI_OUT_DIR', DEFAULT_OUT_DIR), autoFilename(prompt, outputExtension(format))));
|
|
258
|
+
}
|
|
259
|
+
function apiOutputFormat(format) {
|
|
260
|
+
return format === 'jpg' ? 'jpeg' : format;
|
|
261
|
+
}
|
|
262
|
+
function outputExtension(format) {
|
|
263
|
+
return format === 'jpeg' ? 'jpg' : format;
|
|
264
|
+
}
|
|
265
|
+
function autoFilename(prompt, ext) {
|
|
266
|
+
const now = new Date();
|
|
267
|
+
const stamp = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}-${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
|
|
268
|
+
return `${stamp}-${slugify(prompt)}.${ext}`;
|
|
269
|
+
}
|
|
270
|
+
function slugify(value) {
|
|
271
|
+
const slug = value.toLowerCase().trim().replace(/[^\p{L}\p{N} ]/gu, '').split(/\s+/u).filter(Boolean).join('-').slice(0, 40);
|
|
272
|
+
return slug || 'image';
|
|
273
|
+
}
|
|
274
|
+
function mimeForPath(path) {
|
|
275
|
+
const suffix = extname(path).toLowerCase();
|
|
276
|
+
if (suffix === '.jpg' || suffix === '.jpeg')
|
|
277
|
+
return 'image/jpeg';
|
|
278
|
+
if (suffix === '.webp')
|
|
279
|
+
return 'image/webp';
|
|
280
|
+
return 'image/png';
|
|
281
|
+
}
|
|
282
|
+
function envDefault(name, fallback) {
|
|
283
|
+
return process.env[name] ?? fallback;
|
|
284
|
+
}
|
|
285
|
+
function pad2(value) {
|
|
286
|
+
return String(value).padStart(2, '0');
|
|
287
|
+
}
|
|
288
|
+
function recordValue(value) {
|
|
289
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
290
|
+
}
|
|
291
|
+
function arrayValue(value) {
|
|
292
|
+
return Array.isArray(value) ? value : [];
|
|
293
|
+
}
|
|
294
|
+
function stringValue(value) {
|
|
295
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
2
|
+
import { ExitCodes } from '@eightstate/contracts/exit-codes';
|
|
3
|
+
import { EscliError } from '../lib/escli-error.js';
|
|
4
|
+
import { callWithRetry, DEFAULT_ENDPOINT, resolveApiKey, resolveEndpoint } from './credentials.js';
|
|
5
|
+
export async function listModels(options) {
|
|
6
|
+
const explicitKey = resolveApiKey(options.apiKey);
|
|
7
|
+
const timeoutMs = (options.timeoutSeconds ?? 30) * 1000;
|
|
8
|
+
let response;
|
|
9
|
+
if (explicitKey) {
|
|
10
|
+
response = await callAndMapErrors(() => requestModels({ apiKey: explicitKey, fingerprint: 'explicit', baseUrl: resolveEndpoint(options.baseUrl) }, timeoutMs));
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
response = await callAndMapErrors(async () => callWithRetry('image', (key) => requestModels(key, timeoutMs)));
|
|
14
|
+
if (response === null) {
|
|
15
|
+
throw new EscliError('not authenticated', {
|
|
16
|
+
code: ErrorCode.AuthRequired,
|
|
17
|
+
exitCode: ExitCodes.Auth,
|
|
18
|
+
remediation: { command: 'escli auth login' },
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const models = parseModelIds(response);
|
|
23
|
+
return { models, count: models.length };
|
|
24
|
+
}
|
|
25
|
+
async function requestModels(key, timeoutMs) {
|
|
26
|
+
const baseUrl = key.baseUrl ?? DEFAULT_ENDPOINT;
|
|
27
|
+
const url = `${baseUrl.replace(/\/+$/u, '')}/models`;
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
method: 'GET',
|
|
33
|
+
headers: { 'authorization': `Bearer ${key.apiKey}`, 'accept': 'application/json' },
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
});
|
|
36
|
+
const body = await parseBody(response);
|
|
37
|
+
if (!response.ok)
|
|
38
|
+
throw httpStatusError(response.status, formatHttpError(response.status, body), body);
|
|
39
|
+
return body;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (isHttpStatusError(error))
|
|
43
|
+
throw error;
|
|
44
|
+
throw mapNetworkError(error);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
clearTimeout(timeout);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function callAndMapErrors(fn) {
|
|
51
|
+
try {
|
|
52
|
+
return await fn();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw mapApiError(error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function parseBody(response) {
|
|
59
|
+
const text = await response.text();
|
|
60
|
+
if (!text)
|
|
61
|
+
return {};
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(text);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function parseModelIds(body) {
|
|
70
|
+
const data = recordValue(body)?.data;
|
|
71
|
+
if (!Array.isArray(data)) {
|
|
72
|
+
throw new EscliError('invalid models response', { code: ErrorCode.ModelsListFailed, details: body });
|
|
73
|
+
}
|
|
74
|
+
return data.map((item) => stringValue(recordValue(item)?.id)).filter((id) => id !== undefined).sort();
|
|
75
|
+
}
|
|
76
|
+
function mapApiError(error) {
|
|
77
|
+
if (error instanceof EscliError)
|
|
78
|
+
return error;
|
|
79
|
+
const status = extractStatus(error);
|
|
80
|
+
if (status !== undefined) {
|
|
81
|
+
const message = error instanceof Error ? error.message : `Error code: ${status}`;
|
|
82
|
+
const details = recordValue(error)?.details;
|
|
83
|
+
if (status === 401)
|
|
84
|
+
return new EscliError(message, { code: ErrorCode.ApiUnauthorized, exitCode: ExitCodes.Auth, details });
|
|
85
|
+
if (status === 403)
|
|
86
|
+
return new EscliError(message, { code: ErrorCode.ApiForbidden, exitCode: ExitCodes.Auth, details });
|
|
87
|
+
if (status === 429)
|
|
88
|
+
return new EscliError(message, { code: ErrorCode.ApiRateLimited, exitCode: ExitCodes.Transient, details });
|
|
89
|
+
if (status >= 500)
|
|
90
|
+
return new EscliError(message, { code: ErrorCode.ServiceUnavailable, exitCode: ExitCodes.Transient, details });
|
|
91
|
+
return new EscliError(message, { code: ErrorCode.ApiError, details });
|
|
92
|
+
}
|
|
93
|
+
if (error instanceof Error)
|
|
94
|
+
return new EscliError(error.message, { code: ErrorCode.ModelsListFailed });
|
|
95
|
+
return new EscliError('models list failed', { code: ErrorCode.ModelsListFailed, details: error });
|
|
96
|
+
}
|
|
97
|
+
function mapNetworkError(error) {
|
|
98
|
+
const message = error instanceof Error && error.name === 'AbortError' ? 'Connection timeout.' : 'Connection error.';
|
|
99
|
+
const code = error instanceof Error && error.name === 'AbortError' ? ErrorCode.NetworkTimeout : ErrorCode.NetworkError;
|
|
100
|
+
return new EscliError(message, { code, exitCode: ExitCodes.Transient, details: error instanceof Error ? error.message : error });
|
|
101
|
+
}
|
|
102
|
+
function formatHttpError(status, body) {
|
|
103
|
+
const message = extractErrorMessage(body);
|
|
104
|
+
return message ? `Error code: ${status} - ${message}` : `Error code: ${status}`;
|
|
105
|
+
}
|
|
106
|
+
function extractErrorMessage(body) {
|
|
107
|
+
const root = recordValue(body);
|
|
108
|
+
const error = recordValue(root?.error);
|
|
109
|
+
return stringValue(error?.message) ?? stringValue(root?.message);
|
|
110
|
+
}
|
|
111
|
+
function httpStatusError(status, message, details) {
|
|
112
|
+
const error = new Error(message);
|
|
113
|
+
error.status = status;
|
|
114
|
+
error.details = details;
|
|
115
|
+
return error;
|
|
116
|
+
}
|
|
117
|
+
function isHttpStatusError(error) {
|
|
118
|
+
return error instanceof Error && typeof error.status === 'number';
|
|
119
|
+
}
|
|
120
|
+
function extractStatus(error) {
|
|
121
|
+
const value = recordValue(error);
|
|
122
|
+
const status = value ? value.status ?? value.statusCode ?? value.status_code : undefined;
|
|
123
|
+
return typeof status === 'number' ? status : undefined;
|
|
124
|
+
}
|
|
125
|
+
function recordValue(value) {
|
|
126
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
127
|
+
}
|
|
128
|
+
function stringValue(value) {
|
|
129
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=models.js.map
|