@toloka-platform/sdk 0.1.0-preview.5 → 0.1.0-preview.7
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 +7 -11
- package/dist/cli/toloka.js +211 -125
- package/dist/cli/toloka.js.jsc +0 -0
- package/dist/cli/toloka.js.jsc.sha256 +1 -1
- package/dist/cli/toloka.js.sha256 +1 -1
- package/docs/authoring.md +17 -4
- package/docs/cli-v2.md +3 -3
- package/docs/cli.md +55 -17
- package/docs/datasets.md +8 -9
- package/docs/getting-started.md +7 -12
- package/docs/limitations.md +10 -13
- package/docs/testing-examples.md +248 -0
- package/docs/testing.md +115 -0
- package/docs/troubleshooting.md +0 -11
- package/examples/face-image-collection/README.md +7 -1
- package/examples/face-image-collection/fields.ts +20 -8
- package/examples/face-image-collection/nodes/annotate-faces/node.ts +1 -9
- package/examples/face-image-collection/nodes/order-faces/node.ts +1 -1
- package/examples/face-image-collection/pipeline.browser.integration.spec.ts +78 -0
- package/examples/video-pair-rating/README.md +14 -6
- package/examples/video-pair-rating/nodes/adjudicate/node.ts +1 -3
- package/examples/video-pair-rating/nodes/adjudicate_op/node.ts +1 -3
- package/examples/video-pair-rating/nodes/overall_preference_pick/node.ts +1 -1
- package/examples/video-pair-rating/nodes/rate_pair/node.ts +1 -1
- package/examples/video-pair-rating/pipeline.browser.integration.spec.ts +267 -0
- package/openapi/gen/api.ts +819 -898
- package/openapi/gen/models/baseModel.ts +9 -0
- package/openapi/gen/models/baseModelId.ts +2 -0
- package/openapi/gen/models/baseModelIdProperty.ts +2 -0
- package/openapi/gen/models/createDatasetExportBody.ts +3 -3
- package/openapi/gen/models/createDatasetExportBodyOneOf.ts +6 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfFormat.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThree.ts +10 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeContract.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeFormat.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilter.ts +9 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/createProjectModelBody.ts +8 -0
- package/openapi/gen/models/createProjectModelBodyDescription.ts +2 -0
- package/openapi/gen/models/datasetExportReport.ts +9 -0
- package/openapi/gen/models/datasetExportReportErrorsItem.ts +5 -0
- package/openapi/gen/models/datasetExportReportTokens.ts +7 -0
- package/openapi/gen/models/exportJob.ts +2 -0
- package/openapi/gen/models/exportJobReport.ts +3 -0
- package/openapi/gen/models/generationNodeSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/openapi/gen/models/index.ts +64 -0
- package/openapi/gen/models/listBaseModels200.ts +5 -0
- package/openapi/gen/models/listModelVersions200.ts +7 -0
- package/openapi/gen/models/listModelVersionsParams.ts +5 -0
- package/openapi/gen/models/listProjectModels200.ts +7 -0
- package/openapi/gen/models/listProjectModelsParams.ts +5 -0
- package/openapi/gen/models/model.ts +16 -0
- package/openapi/gen/models/modelArchivedAt.ts +2 -0
- package/openapi/gen/models/modelDescription.ts +2 -0
- package/openapi/gen/models/modelId.ts +2 -0
- package/openapi/gen/models/modelVersion.ts +9 -0
- package/openapi/gen/models/modelVersionId.ts +2 -0
- package/openapi/gen/models/pipelineGraphSnapshotGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/openapi/gen/models/sampleDatasetItemsBody.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOf.ts +8 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilter.ts +9 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethree.ts +8 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilter.ts +9 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/sampleJob.ts +13 -0
- package/openapi/gen/models/sampleJobStatus.ts +2 -0
- package/openapi/gen/models/sampleResult.ts +5 -0
- package/openapi/gen/models/validatePipelineGraphBodyGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/package.json +23 -2
- package/skills/catalog.json +14 -6
- package/skills/toloka-build/SKILL.md +34 -12
- package/skills/toloka-build/references/node-semantics.md +27 -24
- package/skills/toloka-build/references/workflow-and-cli.md +67 -27
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/checks/ast.ts +32 -0
- package/src/checks/index.ts +14 -0
- package/src/checks/postprocess-collisions.ts +145 -0
- package/src/checks/task-ui-declarations.generated.ts +19 -0
- package/src/checks/task-ui-typecheck.ts +345 -0
- package/src/checks/typescript-vfs.d.ts +26 -0
- package/src/checks/typescript-vfs.js +5 -0
- package/src/cli/analytics.ts +1 -0
- package/src/cli/check.ts +977 -0
- package/src/cli/data.ts +148 -25
- package/src/cli/models.ts +188 -0
- package/src/cli/program.ts +246 -20
- package/src/cli/project-context.ts +14 -4
- package/src/cli/runs.ts +21 -11
- package/src/cli/skill.ts +17 -6
- package/src/client.ts +109 -35
- package/src/errors.ts +25 -10
- package/src/testing/code-node.ts +160 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/task-ui-browser.ts +120 -0
- package/src/testing/task-ui-validation.ts +135 -0
- package/src/testing/task-ui.ts +557 -0
- package/src/testing/test-evidence.ts +170 -0
- package/src/testing/types.ts +141 -0
- package/src/transport.ts +16 -13
package/src/cli/data.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assert } from "es-toolkit";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname } from "node:path";
|
|
4
|
-
import type { Dataset, InferredField, PotatoClient } from "../client";
|
|
4
|
+
import type { Dataset, InferredField, PotatoClient, UploadId } from "../client";
|
|
5
5
|
import { PotatoApiError } from "../errors";
|
|
6
6
|
import { generateFieldDeclarationsSource } from "../importer";
|
|
7
7
|
import { readTolokaState } from "../state";
|
|
@@ -14,6 +14,14 @@ import type { ProjectSource } from "./source";
|
|
|
14
14
|
const RAW_UPLOAD_FIELD_NAME = "uploaded";
|
|
15
15
|
const DATASET_ID_PATTERN =
|
|
16
16
|
/^ds\.[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
17
|
+
const FINALIZE_UPLOAD_ATTEMPTS = 3;
|
|
18
|
+
|
|
19
|
+
class DatasetUploadError extends Error {
|
|
20
|
+
constructor(message: string, cause: unknown) {
|
|
21
|
+
super(message, { cause });
|
|
22
|
+
this.name = "DatasetUploadError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
17
25
|
|
|
18
26
|
const canonicalDatasetId = (token: string): string | undefined =>
|
|
19
27
|
DATASET_ID_PATTERN.test(token) ? token.toLowerCase() : undefined;
|
|
@@ -22,6 +30,16 @@ type UploadFieldDeclaration = NonNullable<
|
|
|
22
30
|
Parameters<PotatoClient["datasets"]["startUpload"]>[0]["fields"]
|
|
23
31
|
>[number];
|
|
24
32
|
|
|
33
|
+
type DatasetUploadFormat = NonNullable<
|
|
34
|
+
Parameters<PotatoClient["datasets"]["startUpload"]>[0]["format"]
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
const DATASET_UPLOAD_CONTENT_TYPES: Record<DatasetUploadFormat, string> = {
|
|
38
|
+
json: "application/json",
|
|
39
|
+
jsonl: "application/x-ndjson",
|
|
40
|
+
csv: "text/csv",
|
|
41
|
+
};
|
|
42
|
+
|
|
25
43
|
export type UploadDatasetOptions = {
|
|
26
44
|
readonly sourceRoot: string;
|
|
27
45
|
readonly datasetName: string;
|
|
@@ -201,29 +219,138 @@ const resolveLiveDataset = async (
|
|
|
201
219
|
return { datasetId: created.id, name: created.name, nameIsUnique: true };
|
|
202
220
|
};
|
|
203
221
|
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
222
|
+
const resolveDatasetUploadFormat = (filename: string): DatasetUploadFormat => {
|
|
223
|
+
const normalizedFilename = filename.toLowerCase();
|
|
224
|
+
|
|
225
|
+
if (normalizedFilename.endsWith(".json")) return "json";
|
|
226
|
+
if (normalizedFilename.endsWith(".jsonl")) return "jsonl";
|
|
227
|
+
if (normalizedFilename.endsWith(".csv")) return "csv";
|
|
228
|
+
|
|
229
|
+
assert(false, `${filename} must have a .json, .jsonl, or .csv extension`);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const readDatasetUpload = async (filePath: string) => {
|
|
233
|
+
const filename = basename(filePath);
|
|
234
|
+
const format = resolveDatasetUploadFormat(filename);
|
|
235
|
+
const payload = await readFile(filePath, "utf8");
|
|
236
|
+
|
|
237
|
+
if (format === "jsonl") {
|
|
238
|
+
for (const { line, lineNumber } of payload
|
|
239
|
+
.split(/\r?\n/u)
|
|
240
|
+
.map((line, index) => ({ line, lineNumber: index + 1 }))
|
|
241
|
+
.filter(({ line }) => line.length > 0)) {
|
|
211
242
|
let value: unknown;
|
|
212
243
|
try {
|
|
213
244
|
value = JSON.parse(line);
|
|
214
245
|
} catch {
|
|
215
|
-
assert(false, `${
|
|
246
|
+
assert(false, `${filename}:${lineNumber} is not valid JSON`);
|
|
216
247
|
}
|
|
217
|
-
assert(isJsonObject(value), `${
|
|
218
|
-
|
|
248
|
+
assert(isJsonObject(value), `${filename}:${lineNumber} must contain a JSON object`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { payload, format, contentType: DATASET_UPLOAD_CONTENT_TYPES[format] };
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
type StartedDatasetUpload = { readonly uploadUrl: string; readonly job: { readonly id: UploadId } };
|
|
256
|
+
|
|
257
|
+
const cancelStartedUpload = async (
|
|
258
|
+
client: PotatoClient,
|
|
259
|
+
uploadId: StartedDatasetUpload["job"]["id"],
|
|
260
|
+
error: unknown,
|
|
261
|
+
): Promise<never> => {
|
|
262
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
await client.datasets.cancelUpload({ uploadId });
|
|
266
|
+
} catch (cancelError) {
|
|
267
|
+
const cancelReason = cancelError instanceof Error ? cancelError.message : String(cancelError);
|
|
268
|
+
throw new AggregateError(
|
|
269
|
+
[error, cancelError],
|
|
270
|
+
`Dataset upload ${uploadId} failed and could not be canceled. Upload error: ${reason}. Cancellation error: ${cancelReason}.`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
throw new DatasetUploadError(
|
|
275
|
+
`Dataset upload ${uploadId} failed and was canceled: ${reason}`,
|
|
276
|
+
error,
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const transferAndFinalizeUpload = async ({
|
|
281
|
+
client,
|
|
282
|
+
started,
|
|
283
|
+
payload,
|
|
284
|
+
contentType,
|
|
285
|
+
fetch,
|
|
286
|
+
retryIntervalMs,
|
|
287
|
+
}: {
|
|
288
|
+
client: PotatoClient;
|
|
289
|
+
started: StartedDatasetUpload;
|
|
290
|
+
payload: string;
|
|
291
|
+
contentType: string;
|
|
292
|
+
fetch: typeof globalThis.fetch;
|
|
293
|
+
retryIntervalMs: number;
|
|
294
|
+
}): Promise<void> => {
|
|
295
|
+
let uploadResponse: Response;
|
|
296
|
+
try {
|
|
297
|
+
uploadResponse = await fetch(started.uploadUrl, {
|
|
298
|
+
method: "PUT",
|
|
299
|
+
headers: {
|
|
300
|
+
"Content-Type": contentType,
|
|
301
|
+
"x-ms-blob-type": "BlockBlob",
|
|
302
|
+
},
|
|
303
|
+
body: payload,
|
|
219
304
|
});
|
|
305
|
+
} catch (error) {
|
|
306
|
+
return cancelStartedUpload(client, started.job.id, error);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!uploadResponse.ok) {
|
|
310
|
+
return cancelStartedUpload(
|
|
311
|
+
client,
|
|
312
|
+
started.job.id,
|
|
313
|
+
new DatasetUploadError(
|
|
314
|
+
`Dataset upload failed with HTTP ${uploadResponse.status}.`,
|
|
315
|
+
undefined,
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let transientFailures = 0;
|
|
321
|
+
while (true) {
|
|
322
|
+
try {
|
|
323
|
+
await client.datasets.finalizeUpload({ uploadId: started.job.id });
|
|
324
|
+
return;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
if (!(error instanceof PotatoApiError && error.status === 503)) {
|
|
327
|
+
return cancelStartedUpload(client, started.job.id, error);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
transientFailures += 1;
|
|
331
|
+
if (transientFailures === FINALIZE_UPLOAD_ATTEMPTS) {
|
|
332
|
+
return cancelStartedUpload(
|
|
333
|
+
client,
|
|
334
|
+
started.job.id,
|
|
335
|
+
new DatasetUploadError(
|
|
336
|
+
`Storage remained unavailable after ${FINALIZE_UPLOAD_ATTEMPTS} finalization attempts.`,
|
|
337
|
+
error,
|
|
338
|
+
),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
343
|
+
setTimeout(resolve, retryIntervalMs);
|
|
344
|
+
await promise;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
220
347
|
};
|
|
221
348
|
|
|
222
349
|
export async function uploadDataset(
|
|
223
350
|
client: PotatoClient,
|
|
224
351
|
options: UploadDatasetOptions,
|
|
225
352
|
): Promise<UploadDatasetResult> {
|
|
226
|
-
const
|
|
353
|
+
const { payload, format, contentType } = await readDatasetUpload(options.filePath);
|
|
227
354
|
const resolved = await resolveLiveDataset(client, options, true);
|
|
228
355
|
const datasetId = resolved.datasetId;
|
|
229
356
|
|
|
@@ -236,25 +363,21 @@ export async function uploadDataset(
|
|
|
236
363
|
if (!fieldsByName.has(field.name)) fieldsByName.set(field.name, field);
|
|
237
364
|
}
|
|
238
365
|
const fields = [...fieldsByName.values()];
|
|
239
|
-
const payload = JSON.stringify(rows);
|
|
240
|
-
const payloadBytes = new TextEncoder().encode(payload);
|
|
241
366
|
const started = await client.datasets.startUpload({
|
|
242
367
|
datasetId,
|
|
243
368
|
filename: basename(options.filePath),
|
|
244
|
-
format
|
|
245
|
-
declaredSizeBytes:
|
|
369
|
+
format,
|
|
370
|
+
declaredSizeBytes: new TextEncoder().encode(payload).byteLength,
|
|
246
371
|
fields,
|
|
247
372
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
373
|
+
await transferAndFinalizeUpload({
|
|
374
|
+
client,
|
|
375
|
+
started,
|
|
376
|
+
payload,
|
|
377
|
+
contentType,
|
|
378
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
379
|
+
retryIntervalMs: options.waitFor?.intervalMs ?? 1000,
|
|
255
380
|
});
|
|
256
|
-
assert(uploadResponse.ok, `Dataset upload failed with HTTP ${uploadResponse.status}.`);
|
|
257
|
-
await client.datasets.finalizeUpload({ uploadId: started.job.id });
|
|
258
381
|
const completed = await client.waitFor(
|
|
259
382
|
{ type: "datasetUpload", uploadId: started.job.id },
|
|
260
383
|
options.waitFor,
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { assert } from "es-toolkit";
|
|
2
|
+
|
|
3
|
+
import type { BaseModel, BaseModelId, Model, ModelVersion, PotatoClient } from "../client";
|
|
4
|
+
|
|
5
|
+
type ModelApi = Pick<PotatoClient, "baseModels" | "models">;
|
|
6
|
+
type ModelIo = { readonly stdout: (text: string) => void };
|
|
7
|
+
type TableRow = readonly string[];
|
|
8
|
+
|
|
9
|
+
const BASE_MODEL_IDS = [
|
|
10
|
+
"qwen3-4b",
|
|
11
|
+
"qwen3-8b",
|
|
12
|
+
"qwen3-32b",
|
|
13
|
+
"qwen3-235b",
|
|
14
|
+
] satisfies readonly BaseModelId[];
|
|
15
|
+
|
|
16
|
+
const formatTable = (rows: readonly TableRow[]): string => {
|
|
17
|
+
const widths = rows.reduce<number[]>(
|
|
18
|
+
(current, row) => row.map((cell, index) => Math.max(current[index] ?? 0, cell.length)),
|
|
19
|
+
[],
|
|
20
|
+
);
|
|
21
|
+
return rows
|
|
22
|
+
.map((row) =>
|
|
23
|
+
row
|
|
24
|
+
.map((cell, index) => cell.padEnd(widths[index] ?? cell.length))
|
|
25
|
+
.join(" ")
|
|
26
|
+
.trimEnd(),
|
|
27
|
+
)
|
|
28
|
+
.join("\n");
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const writeOutput = (io: ModelIo, json: boolean, value: unknown, formatted: string): void => {
|
|
32
|
+
io.stdout(json ? JSON.stringify(value, null, 2) : formatted);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const formatBaseModel = (model: BaseModel): string =>
|
|
36
|
+
formatTable([
|
|
37
|
+
["ID", model.id],
|
|
38
|
+
["Name", model.displayName],
|
|
39
|
+
["Provider", model.provider],
|
|
40
|
+
["Upstream model", model.upstreamModel],
|
|
41
|
+
["Upstream revision", model.upstreamRevision],
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const formatModel = (model: Model): string =>
|
|
45
|
+
formatTable([
|
|
46
|
+
["ID", model.id],
|
|
47
|
+
["Project", model.projectId],
|
|
48
|
+
["Name", model.name],
|
|
49
|
+
["Base model", model.baseModelId],
|
|
50
|
+
["Description", model.description ?? "-"],
|
|
51
|
+
["Archived at", model.archivedAt ?? "-"],
|
|
52
|
+
["Created at", model.createdAt],
|
|
53
|
+
["Updated at", model.updatedAt],
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
export const parseBaseModelId = (value: string): BaseModelId => {
|
|
57
|
+
const modelId = BASE_MODEL_IDS.find((candidate) => candidate === value);
|
|
58
|
+
assert(modelId, `Unknown base model ${value}.`);
|
|
59
|
+
return modelId;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const resolveModel = async (
|
|
63
|
+
client: ModelApi,
|
|
64
|
+
projectId: string,
|
|
65
|
+
nameOrId: string,
|
|
66
|
+
): Promise<Model> => {
|
|
67
|
+
if (nameOrId.startsWith("mdl.")) return client.models.get({ projectId, modelId: nameOrId });
|
|
68
|
+
const matches: Model[] = [];
|
|
69
|
+
for await (const model of client.models.listAll({ projectId, limit: 100 })) {
|
|
70
|
+
if (model.name === nameOrId) matches.push(model);
|
|
71
|
+
}
|
|
72
|
+
assert(matches.length === 1, `Model ${nameOrId} was not found or is ambiguous.`);
|
|
73
|
+
return matches[0]!;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const listBaseModels = async (
|
|
77
|
+
client: ModelApi,
|
|
78
|
+
input: { json: boolean },
|
|
79
|
+
io: ModelIo,
|
|
80
|
+
): Promise<void> => {
|
|
81
|
+
const models = await client.baseModels.list();
|
|
82
|
+
writeOutput(
|
|
83
|
+
io,
|
|
84
|
+
input.json,
|
|
85
|
+
models,
|
|
86
|
+
models.length === 0
|
|
87
|
+
? "No base models found."
|
|
88
|
+
: formatTable([
|
|
89
|
+
["ID", "NAME", "PROVIDER", "UPSTREAM MODEL"],
|
|
90
|
+
...models.map((model) => [
|
|
91
|
+
model.id,
|
|
92
|
+
model.displayName,
|
|
93
|
+
model.provider,
|
|
94
|
+
model.upstreamModel,
|
|
95
|
+
]),
|
|
96
|
+
]),
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const getBaseModel = async (
|
|
101
|
+
client: ModelApi,
|
|
102
|
+
input: { id: BaseModelId; json: boolean },
|
|
103
|
+
io: ModelIo,
|
|
104
|
+
): Promise<void> => {
|
|
105
|
+
const model = await client.baseModels.get({ id: input.id });
|
|
106
|
+
writeOutput(io, input.json, model, formatBaseModel(model));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const listModels = async (
|
|
110
|
+
client: ModelApi,
|
|
111
|
+
input: { projectId: string; json: boolean },
|
|
112
|
+
io: ModelIo,
|
|
113
|
+
): Promise<void> => {
|
|
114
|
+
const page = await client.models.listPage({ projectId: input.projectId, limit: 100, offset: 0 });
|
|
115
|
+
writeOutput(
|
|
116
|
+
io,
|
|
117
|
+
input.json,
|
|
118
|
+
page.models,
|
|
119
|
+
page.models.length === 0
|
|
120
|
+
? "No models found."
|
|
121
|
+
: formatTable([
|
|
122
|
+
["ID", "NAME", "BASE MODEL"],
|
|
123
|
+
...page.models.map((model) => [model.id, model.name, model.baseModelId]),
|
|
124
|
+
]),
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const getModel = async (
|
|
129
|
+
client: ModelApi,
|
|
130
|
+
input: { projectId: string; nameOrId: string; json: boolean },
|
|
131
|
+
io: ModelIo,
|
|
132
|
+
): Promise<void> => {
|
|
133
|
+
const model = await resolveModel(client, input.projectId, input.nameOrId);
|
|
134
|
+
writeOutput(io, input.json, model, formatModel(model));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const listModelVersions = async (
|
|
138
|
+
client: ModelApi,
|
|
139
|
+
input: { projectId: string; nameOrId: string; json: boolean },
|
|
140
|
+
io: ModelIo,
|
|
141
|
+
): Promise<void> => {
|
|
142
|
+
const model = await resolveModel(client, input.projectId, input.nameOrId);
|
|
143
|
+
const versions: ModelVersion[] = [];
|
|
144
|
+
for await (const version of client.models.listAllVersions({
|
|
145
|
+
projectId: input.projectId,
|
|
146
|
+
modelId: model.id,
|
|
147
|
+
limit: 100,
|
|
148
|
+
})) {
|
|
149
|
+
versions.push(version);
|
|
150
|
+
}
|
|
151
|
+
writeOutput(
|
|
152
|
+
io,
|
|
153
|
+
input.json,
|
|
154
|
+
versions,
|
|
155
|
+
versions.length === 0
|
|
156
|
+
? "No Model Versions found."
|
|
157
|
+
: formatTable([
|
|
158
|
+
["VERSION", "ID", "CREATED AT"],
|
|
159
|
+
...versions.map((version) => [version.version, version.id, version.createdAt]),
|
|
160
|
+
]),
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const createModel = async (
|
|
165
|
+
client: ModelApi,
|
|
166
|
+
input: {
|
|
167
|
+
projectId: string;
|
|
168
|
+
name: string;
|
|
169
|
+
description?: string;
|
|
170
|
+
baseModelId: BaseModelId;
|
|
171
|
+
json: boolean;
|
|
172
|
+
},
|
|
173
|
+
io: ModelIo,
|
|
174
|
+
): Promise<void> => {
|
|
175
|
+
const { json, ...body } = input;
|
|
176
|
+
const model = await client.models.create(body);
|
|
177
|
+
writeOutput(io, json, model, formatModel(model));
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const archiveModel = async (
|
|
181
|
+
client: ModelApi,
|
|
182
|
+
input: { projectId: string; nameOrId: string; json: boolean },
|
|
183
|
+
io: ModelIo,
|
|
184
|
+
): Promise<void> => {
|
|
185
|
+
const model = await resolveModel(client, input.projectId, input.nameOrId);
|
|
186
|
+
const archived = await client.models.archive({ projectId: input.projectId, modelId: model.id });
|
|
187
|
+
writeOutput(io, input.json, archived, `Archived Model ${archived.id}.`);
|
|
188
|
+
};
|