@toloka-platform/sdk 0.2.0 → 0.3.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/README.md +12 -6
- package/dist/cli/toloka.js +269 -142
- 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 +2 -0
- package/docs/cli.md +86 -23
- package/docs/getting-started.md +11 -9
- package/docs/limitations.md +6 -0
- package/docs/testing-examples.md +2 -2
- package/docs/testing.md +10 -2
- package/docs/troubleshooting.md +16 -4
- package/openapi/gen/api.ts +30 -0
- package/openapi/gen/models/gistingTrainingConfigurationGistTokens.ts +1 -1
- package/openapi/gen/models/gistingTrainingConfigurationInputGistTokens.ts +1 -1
- package/openapi/gen/models/index.ts +3 -0
- package/openapi/gen/models/listAudienceSpecializations200.ts +7 -0
- package/openapi/gen/models/listAudienceSpecializationsParams.ts +5 -0
- package/openapi/gen/models/specialization.ts +6 -0
- package/openapi/gen/models/trainingCostEstimateOneOfTwoseven.ts +0 -2
- package/openapi/gen/models/trainingRunQuote.ts +0 -2
- package/package.json +1 -1
- package/skills/catalog.json +10 -10
- package/skills/toloka-build/SKILL.md +8 -5
- package/skills/toloka-build/references/node-semantics.md +2 -2
- package/skills/toloka-build/references/workflow-and-cli.md +50 -25
- package/skills/toloka-fine-tuning/SKILL.md +3 -2
- package/skills/toloka-get-started/SKILL.md +4 -3
- package/skills/toloka-gisting/SKILL.md +4 -3
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/checks/task-ui-typecheck.ts +4 -1
- package/src/cli/analytics.ts +1 -0
- package/src/cli/audience.ts +31 -0
- package/src/cli/auth.ts +382 -81
- package/src/cli/auto-update.ts +363 -24
- package/src/cli/browser-login.ts +265 -0
- package/src/cli/browser-session.ts +244 -0
- package/src/cli/check.ts +51 -52
- package/src/cli/cli-entry.ts +31 -0
- package/src/cli/commands/audience.ts +22 -0
- package/src/cli/commands/auth.ts +22 -15
- package/src/cli/commands/project.ts +3 -3
- package/src/cli/commands/training.ts +10 -2
- package/src/cli/config.ts +41 -14
- package/src/cli/context.ts +45 -9
- package/src/cli/credentials.ts +13 -13
- package/src/cli/init.ts +7 -2
- package/src/cli/keycloak.ts +189 -0
- package/src/cli/models.ts +1 -21
- package/src/cli/native-runtime.ts +7 -0
- package/src/cli/program.ts +5 -0
- package/src/cli/sign-in-page.ts +153 -0
- package/src/cli/skill.ts +34 -26
- package/src/cli/style.ts +37 -0
- package/src/cli/table.ts +21 -0
- package/src/cli/terminal-io.ts +9 -2
- package/src/cli/trainings.ts +1 -22
- package/src/cli/version.ts +12 -1
- package/src/client.ts +20 -3
- package/src/index.ts +1 -0
- package/src/pipeline-graph-v1.contract.json +47 -24
- package/src/transport.ts +19 -11
package/src/cli/cli-entry.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { assertCli } from "./cli-error";
|
|
4
|
+
import { isNativeCli } from "./native-runtime";
|
|
4
5
|
|
|
5
6
|
const CLI_ENTRY = join("bin", "toloka.js");
|
|
7
|
+
export type CliRelaunchCommand = {
|
|
8
|
+
readonly executable: string;
|
|
9
|
+
readonly args: readonly string[];
|
|
10
|
+
readonly environment: NodeJS.ProcessEnv;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type CliRelaunchOptions = {
|
|
14
|
+
readonly native?: boolean;
|
|
15
|
+
readonly executable?: string;
|
|
16
|
+
readonly runtimeEntry?: string;
|
|
17
|
+
};
|
|
6
18
|
|
|
7
19
|
const runtimePath = (entry: string): string => {
|
|
8
20
|
try {
|
|
@@ -40,3 +52,22 @@ export const resolveCliEntry = (runtimeEntry: string | undefined = process.argv[
|
|
|
40
52
|
);
|
|
41
53
|
return workingDirectoryEntry;
|
|
42
54
|
};
|
|
55
|
+
|
|
56
|
+
export const cliRelaunchCommand = (
|
|
57
|
+
argv: readonly string[],
|
|
58
|
+
environment: NodeJS.ProcessEnv,
|
|
59
|
+
options: CliRelaunchOptions = {},
|
|
60
|
+
): CliRelaunchCommand => {
|
|
61
|
+
const executable = options.executable ?? process.execPath;
|
|
62
|
+
if (!(options.native ?? isNativeCli())) {
|
|
63
|
+
return {
|
|
64
|
+
executable:
|
|
65
|
+
options.executable ?? (Reflect.get(process.versions, "bun") ? process.execPath : "bun"),
|
|
66
|
+
args: [resolveCliEntry(options.runtimeEntry), ...argv],
|
|
67
|
+
environment,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const reexecEnvironment = { ...environment };
|
|
71
|
+
delete reexecEnvironment.BUN_BE_BUN;
|
|
72
|
+
return { executable, args: [...argv], environment: reexecEnvironment };
|
|
73
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { listSpecializations } from "../audience";
|
|
3
|
+
import { resolveContext, type CommandHost } from "../context";
|
|
4
|
+
|
|
5
|
+
export const registerAudienceCommands = (program: Command, host: CommandHost): void => {
|
|
6
|
+
const { context, globals } = host;
|
|
7
|
+
|
|
8
|
+
const audience = program
|
|
9
|
+
.command("audience")
|
|
10
|
+
.description("inspect the expert audience catalog used by node audience filters");
|
|
11
|
+
const specialization = audience
|
|
12
|
+
.command("specialization")
|
|
13
|
+
.description("expert specializations (platform qualifications)");
|
|
14
|
+
specialization
|
|
15
|
+
.command("list")
|
|
16
|
+
.option("--json", "print JSON", false)
|
|
17
|
+
.description("list specializations with their id, name, and expert count")
|
|
18
|
+
.action(async (options: { json: boolean }) => {
|
|
19
|
+
const { client } = await resolveContext(globals(), context);
|
|
20
|
+
await listSpecializations(client, { json: options.json }, context.io);
|
|
21
|
+
});
|
|
22
|
+
};
|
package/src/cli/commands/auth.ts
CHANGED
|
@@ -1,56 +1,66 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import { createClient } from "../../client";
|
|
3
2
|
import { authStatus, login, logout } from "../auth";
|
|
4
3
|
import {
|
|
5
4
|
identifyClientUser,
|
|
5
|
+
resolveClient,
|
|
6
6
|
resolveSelectedProject,
|
|
7
7
|
resolveSourceRoot,
|
|
8
8
|
type CommandHost,
|
|
9
9
|
} from "../context";
|
|
10
10
|
import { findCommandCredentials, readStoredCredentials } from "../credentials";
|
|
11
|
-
import { resolveTolokaConfig } from "../config";
|
|
12
11
|
import { readTolokaState } from "../../state";
|
|
13
12
|
|
|
13
|
+
type LoginCommandOptions = {
|
|
14
|
+
readonly apiKeyStdin: boolean;
|
|
15
|
+
readonly browser?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
14
18
|
export const registerAuthCommands = (program: Command, host: CommandHost): void => {
|
|
15
19
|
const { context, exitCode, globals } = host;
|
|
16
20
|
|
|
17
21
|
program
|
|
18
22
|
.command("login")
|
|
19
|
-
.description("store an API key
|
|
20
|
-
.option("--
|
|
21
|
-
.
|
|
23
|
+
.description("sign in through your browser, or store an API key, after verifying access")
|
|
24
|
+
.option("--browser", "sign in through your browser without asking; pick the provider on the page it opens")
|
|
25
|
+
.option("--no-browser", "the same, but print the sign-in link instead of opening a browser")
|
|
26
|
+
.option("--api-key-stdin", "store an API key read from standard input instead", false)
|
|
27
|
+
.action(async (options: LoginCommandOptions) => {
|
|
22
28
|
const apiKey = options.apiKeyStdin ? (await context.io.readStdin()).trim() : globals().apiKey;
|
|
23
29
|
const injectedClient = context.injectedClient;
|
|
24
30
|
const result = await login({
|
|
25
31
|
...(apiKey === undefined ? {} : { apiKey }),
|
|
32
|
+
...(options.browser === undefined ? {} : { browser: options.browser }),
|
|
26
33
|
...(globals().baseUrl === undefined ? {} : { baseUrl: globals().baseUrl }),
|
|
27
34
|
...(context.io.prompt === undefined ? {} : { prompt: context.io.prompt }),
|
|
35
|
+
...(context.io.choose === undefined ? {} : { choose: context.io.choose }),
|
|
28
36
|
...(injectedClient === undefined ? {} : { probe: () => injectedClient }),
|
|
37
|
+
...(context.browserLogin === undefined ? {} : { browserLogin: context.browserLogin }),
|
|
38
|
+
...(context.io.style === undefined ? {} : { style: context.io.style }),
|
|
29
39
|
write: context.io.stdout,
|
|
30
40
|
environment: context.environment,
|
|
31
41
|
});
|
|
32
42
|
context.io.stdout(result.output);
|
|
33
43
|
try {
|
|
34
44
|
const stored = await readStoredCredentials(context.environment);
|
|
35
|
-
|
|
36
|
-
const client = context.injectedClient ?? createClient(stored);
|
|
45
|
+
const { client } = await resolveClient(globals(), context, undefined, stored);
|
|
37
46
|
await identifyClientUser(client, context.analytics);
|
|
38
47
|
} catch {}
|
|
39
48
|
});
|
|
40
49
|
|
|
41
50
|
program
|
|
42
51
|
.command("logout")
|
|
43
|
-
.description("remove stored credentials from this machine")
|
|
52
|
+
.description("remove stored credentials from this machine and end the browser login")
|
|
44
53
|
.action(async () => {
|
|
45
|
-
const
|
|
54
|
+
const baseUrl = globals().baseUrl;
|
|
55
|
+
const result = await logout(context.environment, baseUrl === undefined ? {} : { baseUrl });
|
|
46
56
|
context.io.stdout(result.output);
|
|
47
57
|
});
|
|
48
58
|
|
|
49
59
|
program
|
|
50
60
|
.command("auth-status")
|
|
51
61
|
.alias("whoami")
|
|
52
|
-
.description("show which platform and
|
|
53
|
-
.option("--show-token", "print the API key instead of masking it", false)
|
|
62
|
+
.description("show which platform and login the CLI would use")
|
|
63
|
+
.option("--show-token", "print the API key or access token instead of masking it", false)
|
|
54
64
|
.action(async (options: { showToken: boolean }) => {
|
|
55
65
|
const flags = globals();
|
|
56
66
|
const credentialLookup = await findCommandCredentials(
|
|
@@ -79,10 +89,7 @@ export const registerAuthCommands = (program: Command, host: CommandHost): void
|
|
|
79
89
|
return;
|
|
80
90
|
}
|
|
81
91
|
try {
|
|
82
|
-
const
|
|
83
|
-
const client =
|
|
84
|
-
context.injectedClient ??
|
|
85
|
-
createClient({ apiKey: config.apiKey, baseUrl: config.baseUrl });
|
|
92
|
+
const { client } = await resolveClient(flags, context, state, stored);
|
|
86
93
|
await identifyClientUser(client, context.analytics);
|
|
87
94
|
} catch {}
|
|
88
95
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import { createClient } from "../../client";
|
|
3
2
|
import {
|
|
4
3
|
identifyClientUser,
|
|
4
|
+
resolveClient,
|
|
5
5
|
positiveInteger,
|
|
6
6
|
readCommandCredentials,
|
|
7
7
|
resolveContext,
|
|
@@ -42,7 +42,7 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
|
|
|
42
42
|
.action(async (alias?: string, projectId?: string) => {
|
|
43
43
|
const flags = globals();
|
|
44
44
|
const found = await requireStoredCredentials(context.environment);
|
|
45
|
-
const client = context
|
|
45
|
+
const { client } = await resolveClient(flags, context, undefined, found.credentials);
|
|
46
46
|
await addProject({
|
|
47
47
|
environment: context.environment,
|
|
48
48
|
flags,
|
|
@@ -147,7 +147,7 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
|
|
|
147
147
|
) => {
|
|
148
148
|
const flags = globals();
|
|
149
149
|
const found = await requireStoredCredentials(context.environment);
|
|
150
|
-
const client = context
|
|
150
|
+
const { client } = await resolveClient(flags, context, undefined, found.credentials);
|
|
151
151
|
const sourceRoot = await resolveSourceRoot(flags, context.io);
|
|
152
152
|
await createProject({
|
|
153
153
|
environment: context.environment,
|
|
@@ -64,7 +64,11 @@ export const registerTrainingCommands = (program: Command, host: CommandHost): v
|
|
|
64
64
|
.option("--train-filter <field=value>", "training Dataset field equality filter")
|
|
65
65
|
.option("--validation-filter <field=value>", "validation Dataset field equality filter")
|
|
66
66
|
.option("--lora-rank <rank>", "SFT LoRA adapter rank: 8, 16, or 32 (default 16)", trainingLoraRank)
|
|
67
|
-
.option(
|
|
67
|
+
.option(
|
|
68
|
+
"--gist-tokens <count>",
|
|
69
|
+
'gisting soft tokens: 8, 32, or 64, or a fraction of the prompt "1/4", "1/8", "1/16", "1/32" (default 32)',
|
|
70
|
+
trainingGistTokens,
|
|
71
|
+
)
|
|
68
72
|
.option("--epochs <count>", "training epochs, 1 through 5 (default 3)", trainingEpochs)
|
|
69
73
|
.option(
|
|
70
74
|
"--learning-rate <rate>",
|
|
@@ -111,7 +115,11 @@ export const registerTrainingCommands = (program: Command, host: CommandHost): v
|
|
|
111
115
|
"replacement validation Dataset field equality filter",
|
|
112
116
|
)
|
|
113
117
|
.option("--lora-rank <rank>", "SFT LoRA adapter rank: 8, 16, or 32 (default 16)", trainingLoraRank)
|
|
114
|
-
.option(
|
|
118
|
+
.option(
|
|
119
|
+
"--gist-tokens <count>",
|
|
120
|
+
'gisting soft tokens: 8, 32, or 64, or a fraction of the prompt "1/4", "1/8", "1/16", "1/32" (default 32)',
|
|
121
|
+
trainingGistTokens,
|
|
122
|
+
)
|
|
115
123
|
.option("--epochs <count>", "training epochs, 1 through 5 (default 3)", trainingEpochs)
|
|
116
124
|
.option(
|
|
117
125
|
"--learning-rate <rate>",
|
package/src/cli/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TolokaState } from "../state";
|
|
2
2
|
import type { StoredCredentials } from "./credentials";
|
|
3
|
+
import type { BrowserLoginProvider } from "./keycloak";
|
|
3
4
|
import { resolveProjectContext } from "./project-context";
|
|
4
5
|
import { assertCli } from "./cli-error";
|
|
5
6
|
|
|
@@ -15,9 +16,13 @@ export type TolokaEnvironment = Readonly<Record<string, string | undefined>> & {
|
|
|
15
16
|
readonly TOLOKA_PROJECT_ID?: string;
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
export type CliCredential =
|
|
20
|
+
| { readonly type: "apiKey"; readonly apiKey: string }
|
|
21
|
+
| { readonly type: "browser"; readonly provider: BrowserLoginProvider };
|
|
22
|
+
|
|
18
23
|
export type ResolvedTolokaConfig = {
|
|
19
|
-
readonly apiKey: string;
|
|
20
24
|
readonly baseUrl: string;
|
|
25
|
+
readonly credential: CliCredential;
|
|
21
26
|
readonly projectId?: string;
|
|
22
27
|
};
|
|
23
28
|
|
|
@@ -34,6 +39,9 @@ export const resolveInternalBaseUrl = (environment: TolokaEnvironment): string |
|
|
|
34
39
|
const PRODUCTION_CONSOLE_URL = "https://console.toloka.ai";
|
|
35
40
|
const INTERNAL_CONSOLE_URL = "https://prestable.console.toloka-test.ai";
|
|
36
41
|
|
|
42
|
+
export const registrationUrlFor = (baseUrl: string): string =>
|
|
43
|
+
new URL("/auth?redirectPath=%2Fregistration", baseUrl).href;
|
|
44
|
+
|
|
37
45
|
export const consoleUrlFor = (baseUrl: string): string | undefined => {
|
|
38
46
|
let host: string;
|
|
39
47
|
try {
|
|
@@ -47,28 +55,41 @@ export const consoleUrlFor = (baseUrl: string): string | undefined => {
|
|
|
47
55
|
return undefined;
|
|
48
56
|
};
|
|
49
57
|
|
|
50
|
-
const
|
|
51
|
-
"
|
|
58
|
+
export const MISSING_CREDENTIALS_MESSAGE = [
|
|
59
|
+
"Not logged in. Run toloka login once to sign in through your browser on this machine:",
|
|
52
60
|
"",
|
|
53
61
|
" toloka login",
|
|
54
62
|
"",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
63
|
+
"Automation can pass --api-key, set TOLOKA_API_KEY, or pipe a key to toloka login --api-key-stdin.",
|
|
64
|
+
"Run toloka auth-status to see what is in effect.",
|
|
57
65
|
].join("\n");
|
|
58
66
|
|
|
67
|
+
export const resolveApiKey = (
|
|
68
|
+
flags: TolokaCliFlags,
|
|
69
|
+
environment: TolokaEnvironment,
|
|
70
|
+
stored?: StoredCredentials,
|
|
71
|
+
): string | undefined => flags.apiKey ?? environment.TOLOKA_API_KEY ?? stored?.apiKey;
|
|
72
|
+
|
|
73
|
+
export const resolveBaseUrl = (
|
|
74
|
+
flags: TolokaCliFlags,
|
|
75
|
+
environment: TolokaEnvironment,
|
|
76
|
+
stored?: StoredCredentials,
|
|
77
|
+
): string =>
|
|
78
|
+
flags.baseUrl ??
|
|
79
|
+
environment.TOLOKA_BASE_URL ??
|
|
80
|
+
resolveInternalBaseUrl(environment) ??
|
|
81
|
+
stored?.baseUrl ??
|
|
82
|
+
PRODUCTION_BASE_URL;
|
|
83
|
+
|
|
59
84
|
export function resolveTolokaConfig(
|
|
60
85
|
flags: TolokaCliFlags,
|
|
61
86
|
state: TolokaState | undefined,
|
|
62
87
|
environment: TolokaEnvironment,
|
|
63
88
|
stored?: StoredCredentials,
|
|
89
|
+
browserLogin?: BrowserLoginProvider,
|
|
64
90
|
): ResolvedTolokaConfig {
|
|
65
|
-
const apiKey = flags
|
|
66
|
-
const baseUrl =
|
|
67
|
-
flags.baseUrl ??
|
|
68
|
-
environment.TOLOKA_BASE_URL ??
|
|
69
|
-
resolveInternalBaseUrl(environment) ??
|
|
70
|
-
stored?.baseUrl ??
|
|
71
|
-
PRODUCTION_BASE_URL;
|
|
91
|
+
const apiKey = resolveApiKey(flags, environment, stored);
|
|
92
|
+
const baseUrl = resolveBaseUrl(flags, environment, stored);
|
|
72
93
|
const project = resolveProjectContext({
|
|
73
94
|
flags,
|
|
74
95
|
state,
|
|
@@ -76,6 +97,12 @@ export function resolveTolokaConfig(
|
|
|
76
97
|
stored,
|
|
77
98
|
effectiveBaseUrl: baseUrl,
|
|
78
99
|
});
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
const credential: CliCredential | undefined =
|
|
101
|
+
apiKey !== undefined
|
|
102
|
+
? { type: "apiKey", apiKey }
|
|
103
|
+
: browserLogin !== undefined
|
|
104
|
+
? { type: "browser", provider: browserLogin }
|
|
105
|
+
: undefined;
|
|
106
|
+
assertCli(credential, MISSING_CREDENTIALS_MESSAGE, { hint: { disabled: true } });
|
|
107
|
+
return { baseUrl, credential, ...(project === undefined ? {} : { projectId: project.id }) };
|
|
81
108
|
}
|
package/src/cli/context.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { InvalidArgumentError, type Command } from "commander";
|
|
2
|
+
import type { Style } from "./style";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { createClient } from "../client";
|
|
4
5
|
import type { PotatoClient } from "../client";
|
|
5
6
|
import { readTolokaState } from "../state";
|
|
6
7
|
import type { TolokaState } from "../state";
|
|
7
|
-
import type { AuthPrompt } from "./auth";
|
|
8
|
+
import type { AuthChoose, AuthPrompt } from "./auth";
|
|
8
9
|
import type { CliAnalytics } from "./analytics";
|
|
10
|
+
import type { BrowserLoginFlow } from "./browser-login";
|
|
11
|
+
import { createBrowserSessionAuth, findBrowserLogin } from "./browser-session";
|
|
9
12
|
import type { checkProject } from "./check";
|
|
10
|
-
import { PRODUCTION_BASE_URL, resolveTolokaConfig } from "./config";
|
|
11
|
-
import type { TolokaEnvironment } from "./config";
|
|
13
|
+
import { PRODUCTION_BASE_URL, resolveApiKey, resolveBaseUrl, resolveTolokaConfig } from "./config";
|
|
14
|
+
import type { ResolvedTolokaConfig, TolokaEnvironment } from "./config";
|
|
12
15
|
import { findCommandCredentials } from "./credentials";
|
|
13
16
|
import type { StoredCredentials } from "./credentials";
|
|
14
17
|
import { resolveProjectContext } from "./project-context";
|
|
@@ -21,13 +24,16 @@ export type CliIo = {
|
|
|
21
24
|
readonly stderr: (text: string) => void;
|
|
22
25
|
readonly readStdin: () => Promise<string>;
|
|
23
26
|
readonly prompt?: AuthPrompt;
|
|
27
|
+
readonly choose?: AuthChoose;
|
|
24
28
|
readonly select?: (choices: readonly { name: string; value: string }[]) => Promise<string>;
|
|
29
|
+
readonly style?: Style;
|
|
25
30
|
};
|
|
26
31
|
|
|
27
32
|
export type ProgramContext = {
|
|
28
33
|
readonly environment: TolokaEnvironment;
|
|
29
34
|
readonly io: CliIo;
|
|
30
35
|
readonly injectedClient?: PotatoClient;
|
|
36
|
+
readonly browserLogin?: BrowserLoginFlow;
|
|
31
37
|
readonly runChecks?: typeof checkProject;
|
|
32
38
|
readonly analytics?: CliAnalytics;
|
|
33
39
|
};
|
|
@@ -57,7 +63,8 @@ export const CREDENTIAL_PRECEDENCE = [
|
|
|
57
63
|
"Credentials resolve in this order, highest first:",
|
|
58
64
|
" 1. --api-key / --base-url",
|
|
59
65
|
" 2. TOLOKA_API_KEY / TOLOKA_BASE_URL",
|
|
60
|
-
" 3. the
|
|
66
|
+
" 3. the API key stored by toloka login --api-key-stdin",
|
|
67
|
+
" 4. the browser login stored by toloka login",
|
|
61
68
|
`The platform defaults to ${PRODUCTION_BASE_URL}.`,
|
|
62
69
|
].join("\n");
|
|
63
70
|
|
|
@@ -96,10 +103,15 @@ export const trainingLoraRank = (raw: string): 8 | 16 | 32 => {
|
|
|
96
103
|
return value;
|
|
97
104
|
};
|
|
98
105
|
|
|
99
|
-
export const trainingGistTokens = (raw: string): 8 | 32 | 64 => {
|
|
106
|
+
export const trainingGistTokens = (raw: string): 8 | 32 | 64 | "1/4" | "1/8" | "1/16" | "1/32" => {
|
|
107
|
+
if (raw === "1/4" || raw === "1/8" || raw === "1/16" || raw === "1/32") {
|
|
108
|
+
return raw;
|
|
109
|
+
}
|
|
100
110
|
const value = Number(raw);
|
|
101
111
|
if (value !== 8 && value !== 32 && value !== 64) {
|
|
102
|
-
throw new InvalidArgumentError(
|
|
112
|
+
throw new InvalidArgumentError(
|
|
113
|
+
'Expected 8, 32, or 64 gist tokens, or a fraction of the prompt: "1/4", "1/8", "1/16", or "1/32".',
|
|
114
|
+
);
|
|
103
115
|
}
|
|
104
116
|
return value;
|
|
105
117
|
};
|
|
@@ -209,6 +221,32 @@ export const identifyClientUser = async (
|
|
|
209
221
|
} catch {}
|
|
210
222
|
};
|
|
211
223
|
|
|
224
|
+
export const resolveClient = async (
|
|
225
|
+
globals: GlobalOptions,
|
|
226
|
+
context: ProgramContext,
|
|
227
|
+
state: TolokaState | undefined,
|
|
228
|
+
stored: StoredCredentials | undefined,
|
|
229
|
+
): Promise<{ readonly config: ResolvedTolokaConfig; readonly client: PotatoClient }> => {
|
|
230
|
+
const { environment } = context;
|
|
231
|
+
const browserLogin =
|
|
232
|
+
resolveApiKey(globals, environment, stored) === undefined
|
|
233
|
+
? (await findBrowserLogin(resolveBaseUrl(globals, environment, stored), environment))?.provider
|
|
234
|
+
: undefined;
|
|
235
|
+
const config = resolveTolokaConfig(globals, state, environment, stored, browserLogin);
|
|
236
|
+
const { baseUrl, credential } = config;
|
|
237
|
+
const client =
|
|
238
|
+
context.injectedClient ??
|
|
239
|
+
createClient(
|
|
240
|
+
credential.type === "apiKey"
|
|
241
|
+
? { baseUrl, apiKey: credential.apiKey }
|
|
242
|
+
: {
|
|
243
|
+
baseUrl,
|
|
244
|
+
accessToken: createBrowserSessionAuth({ provider: credential.provider, baseUrl, environment }),
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
return { config, client };
|
|
248
|
+
};
|
|
249
|
+
|
|
212
250
|
export const resolveContext = async (
|
|
213
251
|
globals: GlobalOptions,
|
|
214
252
|
context: ProgramContext,
|
|
@@ -216,9 +254,7 @@ export const resolveContext = async (
|
|
|
216
254
|
const sourceRoot = await resolveSourceRoot(globals, context.io);
|
|
217
255
|
const state = await readTolokaState(sourceRoot);
|
|
218
256
|
const stored = await readCommandCredentials(globals, context);
|
|
219
|
-
const config =
|
|
220
|
-
const client =
|
|
221
|
-
context.injectedClient ?? createClient({ apiKey: config.apiKey, baseUrl: config.baseUrl });
|
|
257
|
+
const { config, client } = await resolveClient(globals, context, state, stored);
|
|
222
258
|
await identifyClientUser(client, context.analytics);
|
|
223
259
|
|
|
224
260
|
return {
|
package/src/cli/credentials.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type StoredProjectAlias = {
|
|
|
11
11
|
|
|
12
12
|
export type StoredCredentials = {
|
|
13
13
|
readonly baseUrl: string;
|
|
14
|
-
readonly apiKey
|
|
14
|
+
readonly apiKey?: string;
|
|
15
15
|
readonly current?: string;
|
|
16
16
|
readonly projects?: Readonly<Record<string, StoredProjectAlias>>;
|
|
17
17
|
};
|
|
@@ -132,15 +132,16 @@ const asCredentials = (payload: unknown): StoredCredentials | undefined => {
|
|
|
132
132
|
const baseUrl = readField(payload, "baseUrl");
|
|
133
133
|
const apiKey = readField(payload, "apiKey");
|
|
134
134
|
if (typeof baseUrl !== "string" || baseUrl.length === 0) return undefined;
|
|
135
|
-
if (typeof apiKey !== "string" || apiKey.length === 0) return undefined;
|
|
135
|
+
if (apiKey !== undefined && (typeof apiKey !== "string" || apiKey.length === 0)) return undefined;
|
|
136
|
+
const identity = apiKey === undefined ? { baseUrl } : { baseUrl, apiKey };
|
|
136
137
|
|
|
137
138
|
const currentField = readField(payload, "current");
|
|
138
139
|
const projectsField = readField(payload, "projects");
|
|
139
|
-
if (currentField === undefined && projectsField === undefined) return
|
|
140
|
+
if (currentField === undefined && projectsField === undefined) return identity;
|
|
140
141
|
|
|
141
142
|
const registry = asProjectRegistry(projectsField, currentField);
|
|
142
143
|
if (registry === undefined) return undefined;
|
|
143
|
-
return {
|
|
144
|
+
return { ...identity, ...registry };
|
|
144
145
|
};
|
|
145
146
|
|
|
146
147
|
export async function inspectCredentialsFile(path: string): Promise<InspectedCredentialsFile> {
|
|
@@ -223,7 +224,7 @@ const serializedCredentials = (credentials: StoredCredentials): StoredCredential
|
|
|
223
224
|
const hasProjects = projects !== undefined && Object.keys(projects).length > 0;
|
|
224
225
|
return {
|
|
225
226
|
baseUrl: credentials.baseUrl,
|
|
226
|
-
apiKey: credentials.apiKey,
|
|
227
|
+
...(credentials.apiKey === undefined ? {} : { apiKey: credentials.apiKey }),
|
|
227
228
|
...(credentials.current === undefined ? {} : { current: credentials.current }),
|
|
228
229
|
...(hasProjects ? { projects } : {}),
|
|
229
230
|
};
|
|
@@ -274,18 +275,17 @@ const fileExists = async (path: string): Promise<boolean> => {
|
|
|
274
275
|
}
|
|
275
276
|
};
|
|
276
277
|
|
|
278
|
+
export const storedCredentialsPaths = (environment: CliEnvironment = process.env): string[] => [
|
|
279
|
+
credentialsPath(environment),
|
|
280
|
+
...(environment.TOLOKA_CREDENTIALS_FILE === undefined ? [legacyCredentialsPath(environment)] : []),
|
|
281
|
+
];
|
|
282
|
+
|
|
277
283
|
export async function clearStoredCredentials(
|
|
278
284
|
environment: CliEnvironment = process.env,
|
|
279
285
|
): Promise<string[]> {
|
|
280
286
|
const removed: string[] = [];
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
...(environment.TOLOKA_CREDENTIALS_FILE === undefined
|
|
284
|
-
? [legacyCredentialsPath(environment)]
|
|
285
|
-
: []),
|
|
286
|
-
];
|
|
287
|
-
|
|
288
|
-
for (const path of paths) {
|
|
287
|
+
|
|
288
|
+
for (const path of storedCredentialsPaths(environment)) {
|
|
289
289
|
if (!(await fileExists(path))) continue;
|
|
290
290
|
await rm(path, { force: true });
|
|
291
291
|
removed.push(path);
|
package/src/cli/init.ts
CHANGED
|
@@ -223,7 +223,12 @@ const bindCheckout = async (
|
|
|
223
223
|
assertCli(
|
|
224
224
|
existing === undefined || existing.projectId === projectId,
|
|
225
225
|
`This directory is already bound to ${existing?.projectId}.`,
|
|
226
|
-
{
|
|
226
|
+
{
|
|
227
|
+
hint: {
|
|
228
|
+
description:
|
|
229
|
+
"Run toloka init without --project-id to keep the current binding, or remove toloka.json to bind another project.",
|
|
230
|
+
},
|
|
231
|
+
},
|
|
227
232
|
);
|
|
228
233
|
if (existing !== undefined) return undefined;
|
|
229
234
|
await writeTolokaState(targetRoot, { projectId, pipelines: {}, datasets: {} });
|
|
@@ -275,7 +280,7 @@ export async function initProject(
|
|
|
275
280
|
];
|
|
276
281
|
const warning = dependencyReady
|
|
277
282
|
? undefined
|
|
278
|
-
: `Warning: ${SDK_PACKAGE_NAME} is not listed in dependencies or devDependencies in ${join(targetRoot, "package.json")}.
|
|
283
|
+
: `Warning: ${SDK_PACKAGE_NAME} is not listed in dependencies or devDependencies in ${join(targetRoot, "package.json")}. Install it with npm or Bun before authoring pipelines, then re-run \`toloka init\`. Training commands do not require it.`;
|
|
279
284
|
return {
|
|
280
285
|
targetDirectory: targetRoot,
|
|
281
286
|
files,
|