@alpacakit/agents-conventions 0.1.0-beta.36 → 0.1.0-beta.39
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 +130 -10
- package/dist/cli-output.d.ts +209 -0
- package/dist/cli-output.d.ts.map +1 -0
- package/dist/cli-output.js +208 -0
- package/dist/cli.d.ts +750 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +388 -0
- package/dist/home.d.ts +65 -0
- package/dist/home.d.ts.map +1 -0
- package/dist/home.js +105 -0
- package/dist/index.d.ts +23 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -56
- package/dist/names.d.ts +38 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +41 -0
- package/dist/paths.d.ts +41 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +72 -0
- package/dist/register.d.ts +122 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +212 -0
- package/dist/remove.d.ts +40 -0
- package/dist/remove.d.ts.map +1 -0
- package/dist/remove.js +73 -0
- package/dist/values.d.ts +30 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +28 -0
- package/package.json +15 -4
package/dist/index.js
CHANGED
|
@@ -1,59 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* AlpacaKit-family conventions for `@alpacakit/agents` machine-local config.
|
|
3
|
+
* This lives OUTSIDE the neutral `@alpacakit/agents` package on purpose: it
|
|
4
|
+
* encodes the family's shared home, keyring service names, managed-home
|
|
5
|
+
* layout, and the ORDER and FAILURE POLICY of registering an agent — none of
|
|
6
|
+
* which a general OSS consumer needs. It builds on the agents package's
|
|
7
|
+
* PUBLIC API only, so it is a plain consumer: the same shape any third party
|
|
8
|
+
* would write for their own family convention.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Products call the procedures here and print the result; they never re-decide
|
|
11
|
+
* the order or the failure policy. The `agent` command surface that mounts
|
|
12
|
+
* them lives on `./cli` (data only), so importing the procedures never pulls
|
|
13
|
+
* in `@alpacakit/channels`.
|
|
11
14
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
17
|
-
export
|
|
18
|
-
const ALPACA_LOOP_AGENTS_CONFIG_MANAGED_HOMES_DIRECTORY = "managed-homes";
|
|
19
|
-
const ALPACA_LOOP_AGENTS_CONFIG_KEYRING_ACCOUNT = {
|
|
20
|
-
prefix: "profile",
|
|
21
|
-
apiKeySuffix: "api-key",
|
|
22
|
-
separator: ":",
|
|
23
|
-
};
|
|
24
|
-
export function resolveAgentsConfigOptions(input) {
|
|
25
|
-
return {
|
|
26
|
-
rootDir: resolveAgentsConfigRootDir(input),
|
|
27
|
-
secretResolvers: [createKeyringResolverEntry()],
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
export function createKeyringSecretRef(profileId) {
|
|
31
|
-
return {
|
|
32
|
-
resolver: ALPACA_LOOP_AGENTS_CONFIG_KEYRING_RESOLVER,
|
|
33
|
-
id: [
|
|
34
|
-
ALPACA_LOOP_AGENTS_CONFIG_KEYRING_ACCOUNT.prefix,
|
|
35
|
-
profileId,
|
|
36
|
-
ALPACA_LOOP_AGENTS_CONFIG_KEYRING_ACCOUNT.apiKeySuffix,
|
|
37
|
-
].join(ALPACA_LOOP_AGENTS_CONFIG_KEYRING_ACCOUNT.separator),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
export function resolveManagedHomePath(input) {
|
|
41
|
-
return path.join(input.rootDir, ALPACA_LOOP_AGENTS_CONFIG_MANAGED_HOMES_DIRECTORY, input.profileId);
|
|
42
|
-
}
|
|
43
|
-
function resolveAgentsConfigRootDir(input) {
|
|
44
|
-
return (readNonBlankEnvValue(input.env, ALPACA_LOOP_AGENTS_CONFIG_ENV_VAR) ??
|
|
45
|
-
path.join(input.homeDir, ALPACA_LOOP_AGENTS_CONFIG_DIRECTORY));
|
|
46
|
-
}
|
|
47
|
-
function createKeyringResolverEntry() {
|
|
48
|
-
return {
|
|
49
|
-
name: ALPACA_LOOP_AGENTS_CONFIG_KEYRING_RESOLVER,
|
|
50
|
-
resolver: keyringResolver({
|
|
51
|
-
service: ALPACA_LOOP_AGENTS_CONFIG_KEYRING_SERVICE,
|
|
52
|
-
}),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
/** Inlined from the agents package's internal env helper (not exported). */
|
|
56
|
-
function readNonBlankEnvValue(env, name) {
|
|
57
|
-
const value = env[name];
|
|
58
|
-
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
59
|
-
}
|
|
15
|
+
export { createRuntimeFactory, listAgentsHome, openAgentsHome, } from "./home.js";
|
|
16
|
+
export { AGENTS_HOME_RECORD_NAME_MAX_LENGTH, AGENTS_HOME_RECORD_NAME_PATTERN, AGENTS_HOME_RECORD_NAME_RULE, INVALID_NAME_RESULT_KIND, isAgentsHomeRecordName, rejectInvalidName, } from "./names.js";
|
|
17
|
+
export { createKeyringSecretRef, isManagedHomePath, resolveAgentsConfigOptions, resolveAgentsHome, resolveManagedHomePath, } from "./paths.js";
|
|
18
|
+
export { ADD_AGENT_CONFIG_RESULT, ADD_API_KEY_PROFILE_RESULT, addAgentConfig, addApiKeyProfile, LOGIN_MANAGED_HOME_RESULT, loginManagedHome, } from "./register.js";
|
|
19
|
+
export { REMOVE_AGENTS_HOME_ENTRY_RESULT, removeAgentsHomeEntry, } from "./remove.js";
|
|
20
|
+
export { AGENT_ADD_KIND, AGENTS_HOME_DIRECTORY, AGENTS_HOME_ENV_VAR, AGENTS_KEYRING_ACCOUNT, AGENTS_KEYRING_RESOLVER, AGENTS_KEYRING_SERVICE, AGENTS_MANAGED_HOMES_DIRECTORY, } from "./values.js";
|
package/dist/names.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grammar of a record name in the shared home — profile ids and agent
|
|
3
|
+
* config ids — spelled ONCE.
|
|
4
|
+
*
|
|
5
|
+
* A record name is not free text: it becomes a path segment under
|
|
6
|
+
* `managed-homes/` and a segment of a keychain account
|
|
7
|
+
* (`profile:<name>:api-key`). An unconstrained name therefore decides where
|
|
8
|
+
* this package writes and what it deletes — `../../../escape` would put a
|
|
9
|
+
* login home outside the shared home and make `agent remove` delete a tree
|
|
10
|
+
* that is not ours. Constraining the vocabulary is the fix; checking for
|
|
11
|
+
* `..` at each use site is not, because the next use site forgets.
|
|
12
|
+
*
|
|
13
|
+
* This narrows, and so satisfies, the agents package's own id rule
|
|
14
|
+
* (non-empty, non-blank).
|
|
15
|
+
*/
|
|
16
|
+
/** Lowercase, starts with a letter or digit, no separators of any kind. */
|
|
17
|
+
export declare const AGENTS_HOME_RECORD_NAME_PATTERN: RegExp;
|
|
18
|
+
export declare const AGENTS_HOME_RECORD_NAME_MAX_LENGTH = 64;
|
|
19
|
+
/**
|
|
20
|
+
* The rule in words, carried on the rejection so a product can tell the
|
|
21
|
+
* operator what to type without restating the grammar.
|
|
22
|
+
*/
|
|
23
|
+
export declare const AGENTS_HOME_RECORD_NAME_RULE = "A name must be 1 to 64 characters of lowercase letters, digits, \".\", \"-\" or \"_\", and must start with a letter or digit.";
|
|
24
|
+
export declare const INVALID_NAME_RESULT_KIND: "invalid_name";
|
|
25
|
+
/** Shared by every procedure that takes a record name (I4: expected, not a fault). */
|
|
26
|
+
export type InvalidNameResult = {
|
|
27
|
+
readonly kind: typeof INVALID_NAME_RESULT_KIND;
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly rule: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function isAgentsHomeRecordName(value: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* `null` when the name is usable. Procedures call this at their entry so a
|
|
34
|
+
* caller that reaches them without going through the CLI codec gets the same
|
|
35
|
+
* answer the CLI would have given.
|
|
36
|
+
*/
|
|
37
|
+
export declare function rejectInvalidName(name: string): InvalidNameResult | null;
|
|
38
|
+
//# sourceMappingURL=names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../src/names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,+BAA+B,QAAiC,CAAC;AAE9E,eAAO,MAAM,kCAAkC,KAAK,CAAC;AAErD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,kIAA+J,CAAC;AAEzM,eAAO,MAAM,wBAAwB,EAAG,cAAuB,CAAC;AAEhE,sFAAsF;AACtF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,wBAAwB,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAQxE"}
|
package/dist/names.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grammar of a record name in the shared home — profile ids and agent
|
|
3
|
+
* config ids — spelled ONCE.
|
|
4
|
+
*
|
|
5
|
+
* A record name is not free text: it becomes a path segment under
|
|
6
|
+
* `managed-homes/` and a segment of a keychain account
|
|
7
|
+
* (`profile:<name>:api-key`). An unconstrained name therefore decides where
|
|
8
|
+
* this package writes and what it deletes — `../../../escape` would put a
|
|
9
|
+
* login home outside the shared home and make `agent remove` delete a tree
|
|
10
|
+
* that is not ours. Constraining the vocabulary is the fix; checking for
|
|
11
|
+
* `..` at each use site is not, because the next use site forgets.
|
|
12
|
+
*
|
|
13
|
+
* This narrows, and so satisfies, the agents package's own id rule
|
|
14
|
+
* (non-empty, non-blank).
|
|
15
|
+
*/
|
|
16
|
+
/** Lowercase, starts with a letter or digit, no separators of any kind. */
|
|
17
|
+
export const AGENTS_HOME_RECORD_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
|
|
18
|
+
export const AGENTS_HOME_RECORD_NAME_MAX_LENGTH = 64;
|
|
19
|
+
/**
|
|
20
|
+
* The rule in words, carried on the rejection so a product can tell the
|
|
21
|
+
* operator what to type without restating the grammar.
|
|
22
|
+
*/
|
|
23
|
+
export const AGENTS_HOME_RECORD_NAME_RULE = `A name must be 1 to ${AGENTS_HOME_RECORD_NAME_MAX_LENGTH} characters of lowercase letters, digits, ".", "-" or "_", and must start with a letter or digit.`;
|
|
24
|
+
export const INVALID_NAME_RESULT_KIND = "invalid_name";
|
|
25
|
+
export function isAgentsHomeRecordName(value) {
|
|
26
|
+
return AGENTS_HOME_RECORD_NAME_PATTERN.test(value);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `null` when the name is usable. Procedures call this at their entry so a
|
|
30
|
+
* caller that reaches them without going through the CLI codec gets the same
|
|
31
|
+
* answer the CLI would have given.
|
|
32
|
+
*/
|
|
33
|
+
export function rejectInvalidName(name) {
|
|
34
|
+
return isAgentsHomeRecordName(name)
|
|
35
|
+
? null
|
|
36
|
+
: {
|
|
37
|
+
kind: INVALID_NAME_RESULT_KIND,
|
|
38
|
+
name,
|
|
39
|
+
rule: AGENTS_HOME_RECORD_NAME_RULE,
|
|
40
|
+
};
|
|
41
|
+
}
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SecretRef } from "@alpacakit/agents";
|
|
2
|
+
import type { OpenAgentsConfigOptions } from "@alpacakit/agents/config";
|
|
3
|
+
/**
|
|
4
|
+
* What every family procedure starts from: the machine's home directory and
|
|
5
|
+
* its environment. Nothing here reads `os.homedir()` or `process.env` — the
|
|
6
|
+
* caller owns both, so a test can point the whole family at a temp dir.
|
|
7
|
+
*/
|
|
8
|
+
export type AgentsConfigOptionsInput = {
|
|
9
|
+
readonly homeDir: string;
|
|
10
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
11
|
+
};
|
|
12
|
+
export type ManagedHomePathInput = {
|
|
13
|
+
readonly rootDir: string;
|
|
14
|
+
readonly profileId: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function resolveAgentsConfigOptions(input: AgentsConfigOptionsInput): OpenAgentsConfigOptions;
|
|
17
|
+
/** The shared home every family product opens. */
|
|
18
|
+
export declare function resolveAgentsHome(input: AgentsConfigOptionsInput): string;
|
|
19
|
+
export declare function createKeyringSecretRef(profileId: string): SecretRef;
|
|
20
|
+
/**
|
|
21
|
+
* The ONE place a managed-home path is built, and therefore the one place the
|
|
22
|
+
* invariant belongs: the result is always a direct child of
|
|
23
|
+
* `<agents home>/managed-homes`. Names are validated at every procedure's
|
|
24
|
+
* entry (`rejectInvalidName`), so a name that could escape means a caller
|
|
25
|
+
* bypassed that — a programming fault, not an operator's mistake, and it
|
|
26
|
+
* throws rather than quietly writing outside the shared home.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveManagedHomePath(input: ManagedHomePathInput): string;
|
|
29
|
+
/**
|
|
30
|
+
* Whether `path` is a login home this package created under the shared home,
|
|
31
|
+
* and may therefore delete. A home the operator pointed at themselves is
|
|
32
|
+
* theirs, so `agent remove` leaves it alone — and so is anything a name that
|
|
33
|
+
* cannot form a managed-home path could point at, which is why this answers
|
|
34
|
+
* `false` where `resolveManagedHomePath` throws.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isManagedHomePath(input: {
|
|
37
|
+
readonly rootDir: string;
|
|
38
|
+
readonly profileId: string;
|
|
39
|
+
readonly path: string;
|
|
40
|
+
}): boolean;
|
|
41
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAEV,uBAAuB,EACxB,MAAM,0BAA0B,CAAC;AAelC;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CAC5D,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,wBAAwB,GAC9B,uBAAuB,CAKzB;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,MAAM,CAKzE;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CASnE;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAQ1E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAGV"}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { keyringResolver } from "@alpacakit/core/keyring";
|
|
3
|
+
import { AGENTS_HOME_RECORD_NAME_RULE, isAgentsHomeRecordName, } from "./names.js";
|
|
4
|
+
import { AGENTS_HOME_DIRECTORY, AGENTS_HOME_ENV_VAR, AGENTS_KEYRING_ACCOUNT, AGENTS_KEYRING_RESOLVER, AGENTS_KEYRING_SERVICE, AGENTS_MANAGED_HOMES_DIRECTORY, } from "./values.js";
|
|
5
|
+
export function resolveAgentsConfigOptions(input) {
|
|
6
|
+
return {
|
|
7
|
+
rootDir: resolveAgentsHome(input),
|
|
8
|
+
secretResolvers: [createKeyringResolverEntry()],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** The shared home every family product opens. */
|
|
12
|
+
export function resolveAgentsHome(input) {
|
|
13
|
+
return (readNonBlankEnvValue(input.env, AGENTS_HOME_ENV_VAR) ??
|
|
14
|
+
path.join(input.homeDir, AGENTS_HOME_DIRECTORY));
|
|
15
|
+
}
|
|
16
|
+
export function createKeyringSecretRef(profileId) {
|
|
17
|
+
return {
|
|
18
|
+
resolver: AGENTS_KEYRING_RESOLVER,
|
|
19
|
+
id: [
|
|
20
|
+
AGENTS_KEYRING_ACCOUNT.prefix,
|
|
21
|
+
profileId,
|
|
22
|
+
AGENTS_KEYRING_ACCOUNT.apiKeySuffix,
|
|
23
|
+
].join(AGENTS_KEYRING_ACCOUNT.separator),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The ONE place a managed-home path is built, and therefore the one place the
|
|
28
|
+
* invariant belongs: the result is always a direct child of
|
|
29
|
+
* `<agents home>/managed-homes`. Names are validated at every procedure's
|
|
30
|
+
* entry (`rejectInvalidName`), so a name that could escape means a caller
|
|
31
|
+
* bypassed that — a programming fault, not an operator's mistake, and it
|
|
32
|
+
* throws rather than quietly writing outside the shared home.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveManagedHomePath(input) {
|
|
35
|
+
const resolved = managedHomePathOrNull(input);
|
|
36
|
+
if (resolved === null) {
|
|
37
|
+
throw new Error(`"${input.profileId}" is not a usable managed-home name: ${AGENTS_HOME_RECORD_NAME_RULE}`);
|
|
38
|
+
}
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether `path` is a login home this package created under the shared home,
|
|
43
|
+
* and may therefore delete. A home the operator pointed at themselves is
|
|
44
|
+
* theirs, so `agent remove` leaves it alone — and so is anything a name that
|
|
45
|
+
* cannot form a managed-home path could point at, which is why this answers
|
|
46
|
+
* `false` where `resolveManagedHomePath` throws.
|
|
47
|
+
*/
|
|
48
|
+
export function isManagedHomePath(input) {
|
|
49
|
+
const managed = managedHomePathOrNull(input);
|
|
50
|
+
return managed !== null && path.resolve(input.path) === managed;
|
|
51
|
+
}
|
|
52
|
+
/** `null` when the name would not land directly under `managed-homes`. */
|
|
53
|
+
function managedHomePathOrNull(input) {
|
|
54
|
+
const parent = path.resolve(input.rootDir, AGENTS_MANAGED_HOMES_DIRECTORY);
|
|
55
|
+
const resolved = path.resolve(parent, input.profileId);
|
|
56
|
+
const containedSegment = path.dirname(resolved) === parent &&
|
|
57
|
+
path.basename(resolved) === input.profileId;
|
|
58
|
+
return isAgentsHomeRecordName(input.profileId) && containedSegment
|
|
59
|
+
? resolved
|
|
60
|
+
: null;
|
|
61
|
+
}
|
|
62
|
+
function createKeyringResolverEntry() {
|
|
63
|
+
return {
|
|
64
|
+
name: AGENTS_KEYRING_RESOLVER,
|
|
65
|
+
resolver: keyringResolver({ service: AGENTS_KEYRING_SERVICE }),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Inlined from the agents package's internal env helper (not exported). */
|
|
69
|
+
function readNonBlankEnvValue(env, name) {
|
|
70
|
+
const value = env[name];
|
|
71
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
72
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three ways an agent enters the shared home — a browser sign-in into a
|
|
3
|
+
* managed home, a pasted credential, or a plain OpenAI-compatible endpoint.
|
|
4
|
+
* Each owns its ORDER and its FAILURE POLICY, which is the whole reason these
|
|
5
|
+
* live here and not in an app: a half-registered credential must not depend
|
|
6
|
+
* on which product ran the command.
|
|
7
|
+
*/
|
|
8
|
+
import { type LoginEvent, type LoginMethod } from "@alpacakit/agents";
|
|
9
|
+
import type { AgentConfig, OpenAiCompatibleEngineKind, ProfileRecord } from "@alpacakit/agents/config";
|
|
10
|
+
import { type AgentsHomeInput } from "./home.js";
|
|
11
|
+
import { type InvalidNameResult } from "./names.js";
|
|
12
|
+
import { AGENT_ADD_KIND } from "./values.js";
|
|
13
|
+
export declare const LOGIN_MANAGED_HOME_RESULT: {
|
|
14
|
+
readonly registered: "registered";
|
|
15
|
+
readonly profileExists: "profile_exists";
|
|
16
|
+
readonly unknownAgent: "unknown_agent";
|
|
17
|
+
readonly unsupported: "unsupported";
|
|
18
|
+
readonly loginFailed: "login_failed";
|
|
19
|
+
};
|
|
20
|
+
export declare const ADD_API_KEY_PROFILE_RESULT: {
|
|
21
|
+
readonly registered: "registered";
|
|
22
|
+
readonly profileExists: "profile_exists";
|
|
23
|
+
readonly emptyKey: "empty_key";
|
|
24
|
+
};
|
|
25
|
+
export declare const ADD_AGENT_CONFIG_RESULT: {
|
|
26
|
+
readonly added: "added";
|
|
27
|
+
readonly exists: "exists";
|
|
28
|
+
};
|
|
29
|
+
export type LoginManagedHomeInput = AgentsHomeInput & {
|
|
30
|
+
readonly profileId: string;
|
|
31
|
+
readonly agentId: string;
|
|
32
|
+
readonly method: LoginMethod;
|
|
33
|
+
readonly signal?: AbortSignal;
|
|
34
|
+
readonly onEvent?: (event: LoginEvent) => void;
|
|
35
|
+
};
|
|
36
|
+
export type LoginManagedHomeResult = {
|
|
37
|
+
readonly kind: typeof LOGIN_MANAGED_HOME_RESULT.registered;
|
|
38
|
+
readonly profile: ProfileRecord;
|
|
39
|
+
readonly managedHome: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly kind: typeof LOGIN_MANAGED_HOME_RESULT.profileExists;
|
|
42
|
+
readonly profileId: string;
|
|
43
|
+
} | {
|
|
44
|
+
/** No agent config in this home projects that id — nothing to sign in to. */
|
|
45
|
+
readonly kind: typeof LOGIN_MANAGED_HOME_RESULT.unknownAgent;
|
|
46
|
+
readonly agentId: string;
|
|
47
|
+
readonly known: readonly string[];
|
|
48
|
+
} | {
|
|
49
|
+
/** The agent is here; it just does not offer that sign-in method. */
|
|
50
|
+
readonly kind: typeof LOGIN_MANAGED_HOME_RESULT.unsupported;
|
|
51
|
+
readonly agentId: string;
|
|
52
|
+
readonly method: LoginMethod;
|
|
53
|
+
readonly supported: readonly LoginMethod[];
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: typeof LOGIN_MANAGED_HOME_RESULT.loginFailed;
|
|
56
|
+
readonly detail: string;
|
|
57
|
+
readonly managedHome: string;
|
|
58
|
+
} | InvalidNameResult;
|
|
59
|
+
export type AddApiKeyProfileInput = AgentsHomeInput & {
|
|
60
|
+
readonly profileId: string;
|
|
61
|
+
readonly agentId: string;
|
|
62
|
+
readonly apiKey: string;
|
|
63
|
+
};
|
|
64
|
+
export type AddApiKeyProfileResult = {
|
|
65
|
+
readonly kind: typeof ADD_API_KEY_PROFILE_RESULT.registered;
|
|
66
|
+
readonly profile: ProfileRecord;
|
|
67
|
+
} | {
|
|
68
|
+
readonly kind: typeof ADD_API_KEY_PROFILE_RESULT.profileExists;
|
|
69
|
+
readonly profileId: string;
|
|
70
|
+
} | {
|
|
71
|
+
readonly kind: typeof ADD_API_KEY_PROFILE_RESULT.emptyKey;
|
|
72
|
+
readonly profileId: string;
|
|
73
|
+
} | InvalidNameResult;
|
|
74
|
+
/**
|
|
75
|
+
* One request shape per addable kind, discriminated by `kind` — the same
|
|
76
|
+
* discriminator `agent add --kind` carries, so the CLI projection IS this
|
|
77
|
+
* input. A new kind is a new member and a new row in the builder record.
|
|
78
|
+
*/
|
|
79
|
+
export type OpenAiCompatibleAgentConfigRequest = {
|
|
80
|
+
readonly kind: typeof AGENT_ADD_KIND.openaiCompatible;
|
|
81
|
+
readonly id: string;
|
|
82
|
+
readonly baseUrl: string;
|
|
83
|
+
readonly model: string;
|
|
84
|
+
/** Env var holding the key; stored as a `${VAR}` template, never the secret (A9). */
|
|
85
|
+
readonly apiKeyEnv?: string;
|
|
86
|
+
readonly engine?: OpenAiCompatibleEngineKind;
|
|
87
|
+
};
|
|
88
|
+
export type AgentConfigRequest = OpenAiCompatibleAgentConfigRequest;
|
|
89
|
+
export type AddAgentConfigInput = AgentsHomeInput & AgentConfigRequest;
|
|
90
|
+
export type AddAgentConfigResult = {
|
|
91
|
+
readonly kind: typeof ADD_AGENT_CONFIG_RESULT.added;
|
|
92
|
+
readonly agentConfig: AgentConfig;
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: typeof ADD_AGENT_CONFIG_RESULT.exists;
|
|
95
|
+
readonly id: string;
|
|
96
|
+
} | InvalidNameResult;
|
|
97
|
+
/**
|
|
98
|
+
* Sign in with the agent's own CLI into a home this package owns, then record
|
|
99
|
+
* it. A failed sign-in leaves the directory in place — a device flow that
|
|
100
|
+
* timed out has already written state the next attempt continues from — but
|
|
101
|
+
* NEVER writes the profile, so the home never points at a home that cannot
|
|
102
|
+
* authenticate. A sign-in that succeeds and then cannot be recorded is a
|
|
103
|
+
* fault, not a result: silently discarding a completed browser flow would
|
|
104
|
+
* leave a live credential nobody knows about.
|
|
105
|
+
*/
|
|
106
|
+
export declare function loginManagedHome(input: LoginManagedHomeInput): Promise<LoginManagedHomeResult>;
|
|
107
|
+
/**
|
|
108
|
+
* Store the key in the family keychain, then reference it from the home. If
|
|
109
|
+
* the reference cannot be written the stored secret is removed again, so a
|
|
110
|
+
* failed `credential add` never leaves an orphaned key in the operator's
|
|
111
|
+
* keychain that no command can ever name.
|
|
112
|
+
*
|
|
113
|
+
* A blank key is rejected before the keychain is touched: it is the operator's
|
|
114
|
+
* mistake (an empty paste, an empty pipe), not a keychain failure, so it comes
|
|
115
|
+
* back as a result and leaves nothing behind. The key is stored trimmed —
|
|
116
|
+
* `echo "sk-…" | … --stdin` otherwise saves a trailing newline that every
|
|
117
|
+
* later request sends to the provider.
|
|
118
|
+
*/
|
|
119
|
+
export declare function addApiKeyProfile(input: AddApiKeyProfileInput): Promise<AddApiKeyProfileResult>;
|
|
120
|
+
/** Add an agent of the requested kind. `input.kind` is the discriminator. */
|
|
121
|
+
export declare function addAgentConfig(input: AddAgentConfigInput): Promise<AddAgentConfigResult>;
|
|
122
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"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"}
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three ways an agent enters the shared home — a browser sign-in into a
|
|
3
|
+
* managed home, a pasted credential, or a plain OpenAI-compatible endpoint.
|
|
4
|
+
* Each owns its ORDER and its FAILURE POLICY, which is the whole reason these
|
|
5
|
+
* live here and not in an app: a half-registered credential must not depend
|
|
6
|
+
* on which product ran the command.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdir } from "node:fs/promises";
|
|
9
|
+
import { isAgentError, } from "@alpacakit/agents";
|
|
10
|
+
import { openAiCompatibleAgentConfig } from "@alpacakit/agents/config";
|
|
11
|
+
import { createRuntimeFactory, openAgentsHome, } from "./home.js";
|
|
12
|
+
import { rejectInvalidName } from "./names.js";
|
|
13
|
+
import { createKeyringSecretRef, resolveManagedHomePath } from "./paths.js";
|
|
14
|
+
import { AGENT_ADD_KIND } from "./values.js";
|
|
15
|
+
const PRIVATE_DIRECTORY_MODE = 0o700;
|
|
16
|
+
const REGISTER_FAULT_MESSAGE = {
|
|
17
|
+
loginNotRegistered: (profileId, managedHome) => `Sign-in succeeded into "${managedHome}" but profile "${profileId}" could not be registered. The credential is on disk and unreferenced; fix the agents home and run the same login again.`,
|
|
18
|
+
};
|
|
19
|
+
export const LOGIN_MANAGED_HOME_RESULT = {
|
|
20
|
+
registered: "registered",
|
|
21
|
+
profileExists: "profile_exists",
|
|
22
|
+
unknownAgent: "unknown_agent",
|
|
23
|
+
unsupported: "unsupported",
|
|
24
|
+
loginFailed: "login_failed",
|
|
25
|
+
};
|
|
26
|
+
export const ADD_API_KEY_PROFILE_RESULT = {
|
|
27
|
+
registered: "registered",
|
|
28
|
+
profileExists: "profile_exists",
|
|
29
|
+
emptyKey: "empty_key",
|
|
30
|
+
};
|
|
31
|
+
export const ADD_AGENT_CONFIG_RESULT = {
|
|
32
|
+
added: "added",
|
|
33
|
+
exists: "exists",
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Sign in with the agent's own CLI into a home this package owns, then record
|
|
37
|
+
* it. A failed sign-in leaves the directory in place — a device flow that
|
|
38
|
+
* timed out has already written state the next attempt continues from — but
|
|
39
|
+
* NEVER writes the profile, so the home never points at a home that cannot
|
|
40
|
+
* authenticate. A sign-in that succeeds and then cannot be recorded is a
|
|
41
|
+
* fault, not a result: silently discarding a completed browser flow would
|
|
42
|
+
* leave a live credential nobody knows about.
|
|
43
|
+
*/
|
|
44
|
+
export async function loginManagedHome(input) {
|
|
45
|
+
const invalidName = rejectInvalidName(input.profileId);
|
|
46
|
+
if (invalidName !== null) {
|
|
47
|
+
return invalidName;
|
|
48
|
+
}
|
|
49
|
+
const home = openAgentsHome(input);
|
|
50
|
+
if (await findProfile(home.config, input.profileId)) {
|
|
51
|
+
return {
|
|
52
|
+
kind: LOGIN_MANAGED_HOME_RESULT.profileExists,
|
|
53
|
+
profileId: input.profileId,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const runtimes = createRuntimeFactory(home, await home.config.loadRuntimeConfig());
|
|
57
|
+
const agents = runtimes.ambient.agents();
|
|
58
|
+
const agent = agents.find((candidate) => candidate.id === input.agentId);
|
|
59
|
+
// Two different problems with two different fixes: an id this home does not
|
|
60
|
+
// have (add or enable the agent config) versus a method this agent's CLI
|
|
61
|
+
// does not offer (use another method). Collapsing them would send the
|
|
62
|
+
// operator looking in the wrong place.
|
|
63
|
+
if (agent === undefined) {
|
|
64
|
+
return {
|
|
65
|
+
kind: LOGIN_MANAGED_HOME_RESULT.unknownAgent,
|
|
66
|
+
agentId: input.agentId,
|
|
67
|
+
known: agents.map((candidate) => candidate.id),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const login = agent.login;
|
|
71
|
+
if (login === null || !login.supports(input.method)) {
|
|
72
|
+
return {
|
|
73
|
+
kind: LOGIN_MANAGED_HOME_RESULT.unsupported,
|
|
74
|
+
agentId: input.agentId,
|
|
75
|
+
method: input.method,
|
|
76
|
+
supported: login?.methods() ?? [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const managedHome = resolveManagedHomePath({
|
|
80
|
+
rootDir: home.agentsHome,
|
|
81
|
+
profileId: input.profileId,
|
|
82
|
+
});
|
|
83
|
+
await mkdir(managedHome, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
|
|
84
|
+
const request = {
|
|
85
|
+
method: input.method,
|
|
86
|
+
homeDir: managedHome,
|
|
87
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
88
|
+
};
|
|
89
|
+
const handle = login.start(request, home.location);
|
|
90
|
+
const drained = drainLoginEvents(handle.events, input.onEvent);
|
|
91
|
+
try {
|
|
92
|
+
await handle.result;
|
|
93
|
+
}
|
|
94
|
+
catch (thrown) {
|
|
95
|
+
await drained;
|
|
96
|
+
if (!isAgentError(thrown)) {
|
|
97
|
+
throw thrown;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
kind: LOGIN_MANAGED_HOME_RESULT.loginFailed,
|
|
101
|
+
detail: thrown.message,
|
|
102
|
+
managedHome,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
await drained;
|
|
106
|
+
let profile;
|
|
107
|
+
try {
|
|
108
|
+
profile = await home.config.profiles.addHomeDir({
|
|
109
|
+
id: input.profileId,
|
|
110
|
+
agentId: input.agentId,
|
|
111
|
+
path: managedHome,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (cause) {
|
|
115
|
+
throw new Error(REGISTER_FAULT_MESSAGE.loginNotRegistered(input.profileId, managedHome), { cause });
|
|
116
|
+
}
|
|
117
|
+
return { kind: LOGIN_MANAGED_HOME_RESULT.registered, profile, managedHome };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Store the key in the family keychain, then reference it from the home. If
|
|
121
|
+
* the reference cannot be written the stored secret is removed again, so a
|
|
122
|
+
* failed `credential add` never leaves an orphaned key in the operator's
|
|
123
|
+
* keychain that no command can ever name.
|
|
124
|
+
*
|
|
125
|
+
* A blank key is rejected before the keychain is touched: it is the operator's
|
|
126
|
+
* mistake (an empty paste, an empty pipe), not a keychain failure, so it comes
|
|
127
|
+
* back as a result and leaves nothing behind. The key is stored trimmed —
|
|
128
|
+
* `echo "sk-…" | … --stdin` otherwise saves a trailing newline that every
|
|
129
|
+
* later request sends to the provider.
|
|
130
|
+
*/
|
|
131
|
+
export async function addApiKeyProfile(input) {
|
|
132
|
+
const invalidName = rejectInvalidName(input.profileId);
|
|
133
|
+
if (invalidName !== null) {
|
|
134
|
+
return invalidName;
|
|
135
|
+
}
|
|
136
|
+
const apiKey = input.apiKey.trim();
|
|
137
|
+
if (apiKey === "") {
|
|
138
|
+
return {
|
|
139
|
+
kind: ADD_API_KEY_PROFILE_RESULT.emptyKey,
|
|
140
|
+
profileId: input.profileId,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const home = openAgentsHome(input);
|
|
144
|
+
if (await findProfile(home.config, input.profileId)) {
|
|
145
|
+
return {
|
|
146
|
+
kind: ADD_API_KEY_PROFILE_RESULT.profileExists,
|
|
147
|
+
profileId: input.profileId,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const secret = createKeyringSecretRef(input.profileId);
|
|
151
|
+
await home.keyring.set(secret.id, apiKey);
|
|
152
|
+
try {
|
|
153
|
+
const profile = await home.config.profiles.addApiKey({
|
|
154
|
+
id: input.profileId,
|
|
155
|
+
agentId: input.agentId,
|
|
156
|
+
secret,
|
|
157
|
+
});
|
|
158
|
+
return { kind: ADD_API_KEY_PROFILE_RESULT.registered, profile };
|
|
159
|
+
}
|
|
160
|
+
catch (thrown) {
|
|
161
|
+
await home.keyring.delete(secret.id);
|
|
162
|
+
throw thrown;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* One record per addable kind (design principle 7): the key IS the support
|
|
167
|
+
* declaration, so adding a kind is adding a row rather than a branch.
|
|
168
|
+
*/
|
|
169
|
+
const AGENT_CONFIG_BUILDER = {
|
|
170
|
+
[AGENT_ADD_KIND.openaiCompatible]: (request) => openAiCompatibleAgentConfig({
|
|
171
|
+
id: request.id,
|
|
172
|
+
baseUrl: request.baseUrl,
|
|
173
|
+
model: request.model,
|
|
174
|
+
...(request.apiKeyEnv === undefined
|
|
175
|
+
? {}
|
|
176
|
+
: { apiKey: envTemplate(request.apiKeyEnv) }),
|
|
177
|
+
...(request.engine === undefined ? {} : { engine: request.engine }),
|
|
178
|
+
}),
|
|
179
|
+
};
|
|
180
|
+
/** Add an agent of the requested kind. `input.kind` is the discriminator. */
|
|
181
|
+
export async function addAgentConfig(input) {
|
|
182
|
+
const invalidName = rejectInvalidName(input.id);
|
|
183
|
+
if (invalidName !== null) {
|
|
184
|
+
return invalidName;
|
|
185
|
+
}
|
|
186
|
+
const home = openAgentsHome(input);
|
|
187
|
+
const existing = await home.config.agentConfigs.list();
|
|
188
|
+
if (existing.some((agentConfig) => agentConfig.id === input.id)) {
|
|
189
|
+
return { kind: ADD_AGENT_CONFIG_RESULT.exists, id: input.id };
|
|
190
|
+
}
|
|
191
|
+
const agentConfig = await home.config.agentConfigs.add(AGENT_CONFIG_BUILDER[input.kind](input));
|
|
192
|
+
return { kind: ADD_AGENT_CONFIG_RESULT.added, agentConfig };
|
|
193
|
+
}
|
|
194
|
+
function envTemplate(name) {
|
|
195
|
+
return `\${${name}}`;
|
|
196
|
+
}
|
|
197
|
+
async function findProfile(config, profileId) {
|
|
198
|
+
return (await config.profiles.list()).some((profile) => profile.id === profileId);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Events are informational; a consumer that does not want them must not stall
|
|
202
|
+
* the sign-in, and a consumer that does must see them all before the result is
|
|
203
|
+
* reported. Awaiting the drain after the result gives both.
|
|
204
|
+
*/
|
|
205
|
+
async function drainLoginEvents(events, onEvent) {
|
|
206
|
+
if (onEvent === undefined) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
for await (const event of events) {
|
|
210
|
+
onEvent(event);
|
|
211
|
+
}
|
|
212
|
+
}
|
package/dist/remove.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one way an agent leaves the shared home. `name` is a record name, the
|
|
3
|
+
* same position `connection login <name>` uses, and profiles win over agent
|
|
4
|
+
* configs because a profile is the more specific thing an operator names.
|
|
5
|
+
*/
|
|
6
|
+
import type { AgentConfig, ProfileRecord } from "@alpacakit/agents/config";
|
|
7
|
+
import { type AgentsHomeInput } from "./home.js";
|
|
8
|
+
import { type InvalidNameResult } from "./names.js";
|
|
9
|
+
export declare const REMOVE_AGENTS_HOME_ENTRY_RESULT: {
|
|
10
|
+
readonly removedProfile: "removed_profile";
|
|
11
|
+
readonly removedAgentConfig: "removed_agent_config";
|
|
12
|
+
readonly notFound: "not_found";
|
|
13
|
+
};
|
|
14
|
+
export type RemoveAgentsHomeEntryInput = AgentsHomeInput & {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
};
|
|
17
|
+
export type RemoveAgentsHomeEntryResult = {
|
|
18
|
+
readonly kind: typeof REMOVE_AGENTS_HOME_ENTRY_RESULT.removedProfile;
|
|
19
|
+
readonly profile: ProfileRecord;
|
|
20
|
+
/** The login home deleted, or `null` when the path was not ours to delete. */
|
|
21
|
+
readonly removedManagedHome: string | null;
|
|
22
|
+
readonly removedSecret: boolean;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: typeof REMOVE_AGENTS_HOME_ENTRY_RESULT.removedAgentConfig;
|
|
25
|
+
readonly agentConfig: AgentConfig;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: typeof REMOVE_AGENTS_HOME_ENTRY_RESULT.notFound;
|
|
28
|
+
readonly name: string;
|
|
29
|
+
} | InvalidNameResult;
|
|
30
|
+
/**
|
|
31
|
+
* The record goes first, then the credential it pointed at: if deleting the
|
|
32
|
+
* credential fails the operator is left with an inert directory or an orphaned
|
|
33
|
+
* keychain entry and a loud error — never with a live record pointing at a
|
|
34
|
+
* credential that is already gone.
|
|
35
|
+
*
|
|
36
|
+
* Only artifacts this package created are deleted. A home the operator pointed
|
|
37
|
+
* at themselves, or a secret behind someone else's resolver, is theirs.
|
|
38
|
+
*/
|
|
39
|
+
export declare function removeAgentsHomeEntry(input: RemoveAgentsHomeEntryInput): Promise<RemoveAgentsHomeEntryResult>;
|
|
40
|
+
//# sourceMappingURL=remove.d.ts.map
|