@agentorg/cli 0.0.5 → 0.0.6
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 +18 -102
- package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.d.ts +2 -7
- package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.js +4 -144
- package/dist/node_modules/@agentorg/hub/dist/src/index.d.ts +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/index.js +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/router.d.ts +6 -57
- package/dist/node_modules/@agentorg/hub/dist/src/router.js +38 -413
- package/dist/node_modules/@agentorg/hub/dist/src/state.d.ts +17 -317
- package/dist/node_modules/@agentorg/hub/dist/src/state.js +42 -536
- package/dist/node_modules/@agentorg/hub/dist/src/validate.d.ts +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/validate.js +3 -15
- package/dist/node_modules/@agentorg/hub/package.json +0 -3
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.d.ts +132 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.js +105 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-settings.d.ts +42 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-settings.js +53 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.d.ts +15 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.js +14 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/hub.d.ts +16 -286
- package/dist/node_modules/@agentorg/interfaces/dist/src/hub.js +17 -159
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.d.ts +6 -8
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +6 -8
- package/dist/node_modules/@agentorg/interfaces/dist/src/kv.d.ts +2 -2
- package/dist/node_modules/@agentorg/interfaces/dist/src/kv.js +2 -2
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.d.ts +272 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +185 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.d.ts +88 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.js +78 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/tools.d.ts +39 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/tools.js +43 -0
- package/dist/node_modules/@agentorg/manager/dist/src/index.d.ts +1 -0
- package/dist/node_modules/@agentorg/manager/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.d.ts +21 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.js +528 -0
- package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +25 -0
- package/dist/node_modules/@agentorg/manager/dist/src/skills.js +145 -0
- package/dist/node_modules/@agentorg/manager/package.json +30 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.d.ts +33 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +290 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +1 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +50 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +69 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +22 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +167 -0
- package/dist/node_modules/@agentorg/mcp/package.json +31 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/cli.js +3 -23
- package/dist/node_modules/@agentorg/sdk/dist/src/client.d.ts +4 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/client.js +5 -55
- package/dist/node_modules/@agentorg/sdk/dist/src/errors.d.ts +1 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/errors.js +3 -3
- package/dist/node_modules/@agentorg/sdk/dist/src/index.d.ts +2 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/index.js +1 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/interface.d.ts +6 -34
- package/dist/node_modules/@agentorg/sdk/dist/src/interface.js +3 -24
- package/dist/node_modules/@agentorg/sdk/dist/src/module-host.d.ts +8 -17
- package/dist/node_modules/@agentorg/sdk/dist/src/module-host.js +15 -87
- package/dist/node_modules/@agentorg/sdk/dist/src/module.d.ts +10 -76
- package/dist/node_modules/@agentorg/sdk/dist/src/module.js +3 -23
- package/dist/node_modules/@agentorg/sdk/dist/src/person-commands.js +0 -5
- package/dist/node_modules/@agentorg/sdk/dist/src/runtime.d.ts +3 -27
- package/dist/node_modules/@agentorg/sdk/dist/src/runtime.js +5 -100
- package/dist/node_modules/@agentorg/sdk/dist/src/testing.d.ts +7 -8
- package/dist/node_modules/@agentorg/sdk/dist/src/testing.js +12 -24
- package/dist/node_modules/@agentorg/sdk/package.json +4 -37
- package/dist/node_modules/@agentorg/shell/dist/app.css +3435 -739
- package/dist/node_modules/@agentorg/shell/dist/app.js +204 -202
- package/dist/node_modules/@agentorg/shell/package.json +18 -10
- package/dist/node_modules/@agentorg/wire/dist/src/frames.d.ts +2 -3
- package/dist/node_modules/@agentorg/wire/dist/src/frames.js +2 -3
- package/dist/node_modules/@agentorg/wire/dist/src/ops.d.ts +5 -34
- package/dist/node_modules/@agentorg/wire/dist/src/ops.js +2 -17
- package/dist/node_modules/assistant/dist/src/agent.d.ts +62 -0
- package/dist/node_modules/assistant/dist/src/agent.js +226 -0
- package/dist/node_modules/assistant/dist/src/engine.d.ts +23 -0
- package/dist/node_modules/assistant/dist/src/engine.js +88 -0
- package/dist/node_modules/assistant/dist/src/history.d.ts +97 -0
- package/dist/node_modules/assistant/dist/src/history.js +235 -0
- package/dist/node_modules/assistant/dist/src/local-store.d.ts +3 -0
- package/dist/node_modules/assistant/dist/src/local-store.js +58 -0
- package/dist/node_modules/assistant/dist/src/provider.d.ts +21 -0
- package/dist/node_modules/assistant/dist/src/provider.js +231 -0
- package/dist/node_modules/assistant/dist/src/settings.d.ts +17 -0
- package/dist/node_modules/assistant/dist/src/settings.js +43 -0
- package/dist/node_modules/assistant/dist/src/tools.d.ts +74 -0
- package/dist/node_modules/assistant/dist/src/tools.js +123 -0
- package/dist/node_modules/assistant/package.json +29 -0
- package/dist/node_modules/chatgpt/dist/src/account.d.ts +52 -0
- package/dist/node_modules/chatgpt/dist/src/account.js +270 -0
- package/dist/node_modules/chatgpt/dist/src/encoding.d.ts +7 -0
- package/dist/node_modules/chatgpt/dist/src/encoding.js +20 -0
- package/dist/node_modules/chatgpt/dist/src/file-store.d.ts +9 -0
- package/dist/node_modules/chatgpt/dist/src/file-store.js +68 -0
- package/dist/node_modules/chatgpt/dist/src/index.d.ts +1 -0
- package/dist/node_modules/chatgpt/dist/src/index.js +1 -0
- package/dist/node_modules/chatgpt/dist/src/jwt.d.ts +15 -0
- package/dist/node_modules/chatgpt/dist/src/jwt.js +37 -0
- package/dist/node_modules/chatgpt/dist/src/oauth.d.ts +43 -0
- package/dist/node_modules/chatgpt/dist/src/oauth.js +104 -0
- package/dist/node_modules/chatgpt/dist/src/program.d.ts +23 -0
- package/dist/node_modules/chatgpt/dist/src/program.js +41 -0
- package/dist/node_modules/chatgpt/dist/src/responses.d.ts +54 -0
- package/dist/node_modules/chatgpt/dist/src/responses.js +300 -0
- package/dist/node_modules/chatgpt/dist/src/serve.d.ts +65 -0
- package/dist/node_modules/chatgpt/dist/src/serve.js +46 -0
- package/dist/node_modules/chatgpt/package.json +33 -0
- package/dist/src/api.d.ts +5 -34
- package/dist/src/api.js +6 -68
- package/dist/src/cli.js +3 -7
- package/dist/src/gateway.d.ts +6 -7
- package/dist/src/gateway.js +39 -285
- package/dist/src/index.d.ts +4 -5
- package/dist/src/index.js +3 -4
- package/dist/src/notes.d.ts +11 -0
- package/dist/src/notes.js +64 -0
- package/dist/src/oauth.d.ts +16 -0
- package/dist/src/oauth.js +46 -0
- package/dist/src/programs.d.ts +29 -0
- package/dist/src/programs.js +171 -0
- package/package.json +3 -11
- package/dist/bundled/index.json +0 -6
- package/dist/bundled/sha256-0e91cc3d0e9762b81abc2427b5496b20f383abcd6fb97d928a8d50e956b54d1f.agentorg +0 -0
- package/dist/bundled/sha256-1f64ca62a0209d767d6aa6c4feb50b0653d60f10bebbc301f79bd3a37e0153c1.agentorg +0 -0
- package/dist/bundled/sha256-4055f8fddf6b90f3d356543f554b2a9e9a2c4a8827a8774f8f87952dc1692331.agentorg +0 -0
- package/dist/bundled/sha256-e356ea3c1899138a8494baf521556831006f38534fa9e92ae873e7e6956c3a46.agentorg +0 -0
- package/dist/node_modules/@agentorg/hub/dist/src/build.d.ts +0 -17
- package/dist/node_modules/@agentorg/hub/dist/src/build.js +0 -120
- package/dist/node_modules/@agentorg/hub/dist/src/logs.d.ts +0 -33
- package/dist/node_modules/@agentorg/hub/dist/src/logs.js +0 -162
- package/dist/node_modules/@agentorg/hub/dist/src/net.d.ts +0 -41
- package/dist/node_modules/@agentorg/hub/dist/src/net.js +0 -265
- package/dist/node_modules/@agentorg/hub/dist/src/processes.d.ts +0 -78
- package/dist/node_modules/@agentorg/hub/dist/src/processes.js +0 -485
- package/dist/node_modules/@agentorg/hub/dist/src/storage.d.ts +0 -38
- package/dist/node_modules/@agentorg/hub/dist/src/storage.js +0 -194
- package/dist/node_modules/@agentorg/interfaces/dist/src/http-egress.d.ts +0 -6
- package/dist/node_modules/@agentorg/interfaces/dist/src/http-egress.js +0 -7
- package/dist/node_modules/@agentorg/interfaces/dist/src/installer.d.ts +0 -191
- package/dist/node_modules/@agentorg/interfaces/dist/src/installer.js +0 -136
- package/dist/node_modules/@agentorg/interfaces/dist/src/layout.d.ts +0 -47
- package/dist/node_modules/@agentorg/interfaces/dist/src/layout.js +0 -52
- package/dist/node_modules/@agentorg/interfaces/dist/src/rules.d.ts +0 -35
- package/dist/node_modules/@agentorg/interfaces/dist/src/rules.js +0 -37
- package/dist/node_modules/@agentorg/interfaces/dist/src/storage-admin.d.ts +0 -23
- package/dist/node_modules/@agentorg/interfaces/dist/src/storage-admin.js +0 -31
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-shell.d.ts +0 -14
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-shell.js +0 -26
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-view.d.ts +0 -27
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-view.js +0 -26
- package/dist/node_modules/@agentorg/kit/dist/src/chat.d.ts +0 -37
- package/dist/node_modules/@agentorg/kit/dist/src/chat.js +0 -131
- package/dist/node_modules/@agentorg/kit/dist/src/components.d.ts +0 -94
- package/dist/node_modules/@agentorg/kit/dist/src/components.js +0 -66
- package/dist/node_modules/@agentorg/kit/dist/src/icons.d.ts +0 -17
- package/dist/node_modules/@agentorg/kit/dist/src/icons.js +0 -30
- package/dist/node_modules/@agentorg/kit/dist/src/index.d.ts +0 -6
- package/dist/node_modules/@agentorg/kit/dist/src/index.js +0 -10
- package/dist/node_modules/@agentorg/kit/dist/src/markdown.d.ts +0 -9
- package/dist/node_modules/@agentorg/kit/dist/src/markdown.js +0 -14
- package/dist/node_modules/@agentorg/kit/dist/src/resizable.d.ts +0 -5
- package/dist/node_modules/@agentorg/kit/dist/src/resizable.js +0 -8
- package/dist/node_modules/@agentorg/kit/dist/src/styles.d.ts +0 -11
- package/dist/node_modules/@agentorg/kit/dist/src/styles.js +0 -166
- package/dist/node_modules/@agentorg/kit/package.json +0 -44
- package/dist/node_modules/@agentorg/sdk/dist/frame/runtime.js +0 -753
- package/dist/node_modules/@agentorg/sdk/dist/host/runner.js +0 -21963
- package/dist/node_modules/@agentorg/sdk/dist/src/build.d.ts +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/build.js +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/client-http.d.ts +0 -24
- package/dist/node_modules/@agentorg/sdk/dist/src/client-http.js +0 -247
- package/dist/node_modules/@agentorg/sdk/dist/src/describe.d.ts +0 -13
- package/dist/node_modules/@agentorg/sdk/dist/src/describe.js +0 -16
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-base.d.ts +0 -8
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-base.js +0 -64
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-entry.d.ts +0 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-entry.js +0 -35
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-host.d.ts +0 -57
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-host.js +0 -293
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-protocol.d.ts +0 -124
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-protocol.js +0 -16
- package/dist/node_modules/@agentorg/sdk/dist/src/frame.d.ts +0 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/frame.js +0 -248
- package/dist/node_modules/@agentorg/sdk/dist/src/host-main.d.ts +0 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/host-main.js +0 -60
- package/dist/node_modules/@agentorg/sdk/dist/src/host.d.ts +0 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/host.js +0 -92
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest-api.d.ts +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest-api.js +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest.d.ts +0 -56
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest.js +0 -165
- package/dist/node_modules/@agentorg/sdk/dist/src/package.d.ts +0 -60
- package/dist/node_modules/@agentorg/sdk/dist/src/package.js +0 -538
- package/dist/node_modules/@agentorg/sdk/dist/src/tar.d.ts +0 -7
- package/dist/node_modules/@agentorg/sdk/dist/src/tar.js +0 -71
- package/dist/node_modules/@agentorg/shell/dist/agent-bindings.js +0 -59
- package/dist/node_modules/@agentorg/shell/src/agent-bindings.ts +0 -107
- package/dist/src/frames.d.ts +0 -23
- package/dist/src/frames.js +0 -78
- package/dist/src/layout.d.ts +0 -44
- package/dist/src/layout.js +0 -90
- package/dist/src/model.d.ts +0 -54
- package/dist/src/model.js +0 -299
- package/dist/src/rules.d.ts +0 -71
- package/dist/src/rules.js +0 -157
- package/dist/src/transfer.d.ts +0 -30
- package/dist/src/transfer.js +0 -110
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** The longest instructions a request can carry. */
|
|
2
|
+
export const MAX_INSTRUCTIONS = 200_000;
|
|
3
|
+
const SLOT = "settings";
|
|
4
|
+
const HEADING = `## Skills
|
|
5
|
+
A skill is written instructions for a kind of task. When one fits what you're asked, read it with settings__skill and follow it before you start. Pass path to read a file its SKILL.md refers to.`;
|
|
6
|
+
/** The agent's instructions and the skills that fit in them; any that don't are reported. */
|
|
7
|
+
export function instructionsWith(instructions, skills) {
|
|
8
|
+
const leftOut = [];
|
|
9
|
+
if (skills.length === 0)
|
|
10
|
+
return { instructions, leftOut };
|
|
11
|
+
let text = `${instructions}\n\n${HEADING}`;
|
|
12
|
+
for (const { name, description } of skills) {
|
|
13
|
+
const line = `\n- ${name}: ${description}`;
|
|
14
|
+
if (text.length + line.length > MAX_INSTRUCTIONS)
|
|
15
|
+
leftOut.push({ slot: SLOT, tool: name, reason: "limit" });
|
|
16
|
+
else
|
|
17
|
+
text += line;
|
|
18
|
+
}
|
|
19
|
+
return leftOut.length === skills.length
|
|
20
|
+
? { instructions, leftOut }
|
|
21
|
+
: { instructions: text, leftOut };
|
|
22
|
+
}
|
|
23
|
+
/** The one way the model reads a skill: its `SKILL.md`, or a file of its folder by `path`. */
|
|
24
|
+
export function skillTool(slot) {
|
|
25
|
+
return {
|
|
26
|
+
slot: SLOT,
|
|
27
|
+
name: "skill",
|
|
28
|
+
description: "Read a skill: its SKILL.md, or with path a file in its folder.",
|
|
29
|
+
parameters: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
name: { type: "string", description: "The skill's name, as your instructions list it." },
|
|
33
|
+
path: { type: "string", description: "A file in the skill's folder, relative to it." },
|
|
34
|
+
},
|
|
35
|
+
required: ["name"],
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
},
|
|
38
|
+
run: async (args) => {
|
|
39
|
+
const { text } = await slot.call.skill(args);
|
|
40
|
+
return { output: text, ok: true };
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant's tools, gathered afresh for each model request. A `tools@1` slot gives whatever
|
|
3
|
+
* it lists now, with its server's own input schemas. Each is offered as
|
|
4
|
+
* `<slot>__<name>`, and a call runs through the slot, so the Hub checks and audits it like any
|
|
5
|
+
* other.
|
|
6
|
+
*/
|
|
7
|
+
import { type Tool } from "@agentorg/interfaces";
|
|
8
|
+
import { type DescribedInterface } from "@agentorg/sdk";
|
|
9
|
+
import type { z } from "zod";
|
|
10
|
+
/** How a function call ended, as the model and the record see it. */
|
|
11
|
+
export interface Outcome {
|
|
12
|
+
output: string;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
code?: string;
|
|
15
|
+
}
|
|
16
|
+
interface Found {
|
|
17
|
+
tool: z.input<typeof Tool>;
|
|
18
|
+
run(input: unknown): Promise<Outcome>;
|
|
19
|
+
}
|
|
20
|
+
export type Tools = Map<string, Found>;
|
|
21
|
+
/** A tool as its slot gives it, before it has the name a request offers it under. */
|
|
22
|
+
export interface Candidate {
|
|
23
|
+
slot: string;
|
|
24
|
+
name: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
parameters: Record<string, z.core.util.JSONType>;
|
|
27
|
+
run: Found["run"];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Something the model isn't offered, and why: `name` (past 64 characters as a function name),
|
|
31
|
+
* `duplicate` (its function name is already taken), `limit` (past what a request can carry), or,
|
|
32
|
+
* with no `tool`, the code its slot's list failed with.
|
|
33
|
+
*/
|
|
34
|
+
export interface LeftOut {
|
|
35
|
+
slot: string;
|
|
36
|
+
tool?: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
}
|
|
39
|
+
/** A function's output is cut to this many bytes of UTF-8, so one result can't fill a request. */
|
|
40
|
+
export declare const MAX_OUTPUT: number;
|
|
41
|
+
/** What a request can carry: its tools count, and, with the record and instructions, one frame. */
|
|
42
|
+
export declare const MAX_TOOLS = 128;
|
|
43
|
+
export declare const TOOLS_BUDGET: number;
|
|
44
|
+
/** The code and message of anything a call or a turn can throw. */
|
|
45
|
+
export declare function codeOf(error: unknown): {
|
|
46
|
+
code: string;
|
|
47
|
+
message: string;
|
|
48
|
+
};
|
|
49
|
+
/** At most `MAX_OUTPUT` bytes of `text`, cut at a character, saying so when it is cut. */
|
|
50
|
+
export declare function capped(text: string): string;
|
|
51
|
+
/** Whether a slot is a `tools@1`, whose tools are whatever it lists. */
|
|
52
|
+
export declare const listsTools: (described: DescribedInterface) => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* What a `tools@1` slot lists now. A slot that can't answer gives no tools and says why, and is
|
|
55
|
+
* asked again at the next request.
|
|
56
|
+
*/
|
|
57
|
+
export declare function listedBy(name: string, slot: {
|
|
58
|
+
readonly call: Readonly<Record<string, (input: unknown) => Promise<unknown>>>;
|
|
59
|
+
}): Promise<{
|
|
60
|
+
candidates: Candidate[];
|
|
61
|
+
leftOut: LeftOut[];
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* The tools one request offers, by function name, and what it can't. A character a function name
|
|
65
|
+
* can't have becomes `_`. Candidates are taken in order, so the first ones given are the ones kept
|
|
66
|
+
* when a request is full; what doesn't fit is reported, never sent.
|
|
67
|
+
*/
|
|
68
|
+
export declare function offered(candidates: Candidate[]): {
|
|
69
|
+
tools: Tools;
|
|
70
|
+
leftOut: LeftOut[];
|
|
71
|
+
};
|
|
72
|
+
/** Run a function call through its slot: the result, or the refusal, as the function's output. */
|
|
73
|
+
export declare function runTool(tools: Tools, name: string, args: string): Promise<Outcome>;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant's tools, gathered afresh for each model request. A `tools@1` slot gives whatever
|
|
3
|
+
* it lists now, with its server's own input schemas. Each is offered as
|
|
4
|
+
* `<slot>__<name>`, and a call runs through the slot, so the Hub checks and audits it like any
|
|
5
|
+
* other.
|
|
6
|
+
*/
|
|
7
|
+
import { tools as toolsInterface } from "@agentorg/interfaces";
|
|
8
|
+
import { describeInterface, NotConnected, OutcomeUnknown, Refused, } from "@agentorg/sdk";
|
|
9
|
+
import { head } from "./history.js";
|
|
10
|
+
/** A function's output is cut to this many bytes of UTF-8, so one result can't fill a request. */
|
|
11
|
+
export const MAX_OUTPUT = 16 * 1024;
|
|
12
|
+
/** What a request can carry: its tools count, and, with the record and instructions, one frame. */
|
|
13
|
+
export const MAX_TOOLS = 128;
|
|
14
|
+
export const TOOLS_BUDGET = 256 * 1024;
|
|
15
|
+
const MAX_NAME = 64;
|
|
16
|
+
const MAX_DESCRIPTION = 1024;
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
const TOOLS_ID = describeInterface(toolsInterface).id;
|
|
19
|
+
/** The code and message of anything a call or a turn can throw. */
|
|
20
|
+
export function codeOf(error) {
|
|
21
|
+
if (error instanceof Refused)
|
|
22
|
+
return { code: error.code, message: error.message };
|
|
23
|
+
if (error instanceof NotConnected)
|
|
24
|
+
return { code: "not_connected", message: `${error.target} is not connected.` };
|
|
25
|
+
if (error instanceof OutcomeUnknown)
|
|
26
|
+
return { code: "outcome_unknown", message: error.message };
|
|
27
|
+
return { code: "failed", message: "The call failed." };
|
|
28
|
+
}
|
|
29
|
+
/** At most `MAX_OUTPUT` bytes of `text`, cut at a character, saying so when it is cut. */
|
|
30
|
+
export function capped(text) {
|
|
31
|
+
const bytes = encoder.encode(text);
|
|
32
|
+
if (bytes.byteLength <= MAX_OUTPUT)
|
|
33
|
+
return text;
|
|
34
|
+
const head = new TextDecoder().decode(bytes.subarray(0, MAX_OUTPUT - 16)).replace(/�$/, "");
|
|
35
|
+
return `${head}… (cut)`;
|
|
36
|
+
}
|
|
37
|
+
/** Whether a slot is a `tools@1`, whose tools are whatever it lists. */
|
|
38
|
+
export const listsTools = (described) => described.id === TOOLS_ID;
|
|
39
|
+
/**
|
|
40
|
+
* What a `tools@1` slot lists now. A slot that can't answer gives no tools and says why, and is
|
|
41
|
+
* asked again at the next request.
|
|
42
|
+
*/
|
|
43
|
+
export async function listedBy(name, slot) {
|
|
44
|
+
const list = slot.call.list;
|
|
45
|
+
const call = slot.call.call;
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
({ tools: entries } = (await list({})));
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return { candidates: [], leftOut: [{ slot: name, reason: codeOf(error).code }] };
|
|
52
|
+
}
|
|
53
|
+
const candidates = entries.map((entry) => ({
|
|
54
|
+
slot: name,
|
|
55
|
+
name: entry.name,
|
|
56
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
57
|
+
parameters: entry.inputSchema,
|
|
58
|
+
run: async (args) => {
|
|
59
|
+
const { output, failed } = (await call({ name: entry.name, arguments: args }));
|
|
60
|
+
return failed ? { output, ok: false, code: "failed" } : { output, ok: true };
|
|
61
|
+
},
|
|
62
|
+
}));
|
|
63
|
+
return { candidates, leftOut: [] };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The tools one request offers, by function name, and what it can't. A character a function name
|
|
67
|
+
* can't have becomes `_`. Candidates are taken in order, so the first ones given are the ones kept
|
|
68
|
+
* when a request is full; what doesn't fit is reported, never sent.
|
|
69
|
+
*/
|
|
70
|
+
export function offered(candidates) {
|
|
71
|
+
const tools = new Map();
|
|
72
|
+
const leftOut = [];
|
|
73
|
+
let bytes = 0;
|
|
74
|
+
for (const { slot, name, description, parameters, run } of candidates) {
|
|
75
|
+
const toolName = `${slot}__${name}`.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
76
|
+
const tool = {
|
|
77
|
+
name: toolName,
|
|
78
|
+
...(description ? { description: head(description, MAX_DESCRIPTION) } : {}),
|
|
79
|
+
parameters,
|
|
80
|
+
};
|
|
81
|
+
const size = encoder.encode(JSON.stringify(tool)).byteLength;
|
|
82
|
+
const reason = toolName.length > MAX_NAME
|
|
83
|
+
? "name"
|
|
84
|
+
: tools.has(toolName)
|
|
85
|
+
? "duplicate"
|
|
86
|
+
: tools.size >= MAX_TOOLS || bytes + size > TOOLS_BUDGET
|
|
87
|
+
? "limit"
|
|
88
|
+
: undefined;
|
|
89
|
+
if (reason) {
|
|
90
|
+
leftOut.push({ slot, tool: name, reason });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
bytes += size;
|
|
94
|
+
tools.set(toolName, { tool, run });
|
|
95
|
+
}
|
|
96
|
+
return { tools, leftOut };
|
|
97
|
+
}
|
|
98
|
+
const failure = ({ code, message }) => JSON.stringify({ error: { code, message } });
|
|
99
|
+
/** Run a function call through its slot: the result, or the refusal, as the function's output. */
|
|
100
|
+
export async function runTool(tools, name, args) {
|
|
101
|
+
const refused = (code, message) => ({
|
|
102
|
+
output: failure({ code, message }),
|
|
103
|
+
ok: false,
|
|
104
|
+
code,
|
|
105
|
+
});
|
|
106
|
+
const found = tools.get(name);
|
|
107
|
+
if (!found)
|
|
108
|
+
return refused("unknown_tool", `No tool is named ${name.slice(0, 64)}.`);
|
|
109
|
+
let input;
|
|
110
|
+
try {
|
|
111
|
+
input = args.trim() === "" ? {} : JSON.parse(args);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return refused("invalid", "The arguments aren't JSON.");
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return await found.run(input);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const { code, message } = codeOf(error);
|
|
121
|
+
return refused(code, message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "assistant",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "The agent engine, and the bundled provider that runs it",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./provider": {
|
|
10
|
+
"types": "./dist/src/provider.d.ts",
|
|
11
|
+
"default": "./dist/src/provider.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/src"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
|
|
19
|
+
"test": "node --test \"dist/test/**/*.test.js\""
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@agentorg/interfaces": "0.0.0",
|
|
23
|
+
"@agentorg/sdk": "0.0.0",
|
|
24
|
+
"zod": "^4.6.7"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@agentorg/hub": "0.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ChatGPT sign-in this Actor owns: its registration, its tokens and their refresh. The tokens
|
|
3
|
+
* stay inside, apart from where they are kept: nothing here returns, emits or logs one.
|
|
4
|
+
*/
|
|
5
|
+
import type { kv, LoginState } from "@agentorg/interfaces";
|
|
6
|
+
import { Refused, type Slot } from "@agentorg/sdk";
|
|
7
|
+
import type { z } from "zod";
|
|
8
|
+
import { type Fetch } from "./oauth.js";
|
|
9
|
+
type State = z.output<typeof LoginState>;
|
|
10
|
+
/** Where the sign-in is kept: the program's own file. */
|
|
11
|
+
export type Store = Pick<Slot<typeof kv>["call"], "get" | "set" | "delete">;
|
|
12
|
+
export declare const notSignedIn: () => Refused;
|
|
13
|
+
export declare const notEligible: () => Refused;
|
|
14
|
+
export declare function createAccount(options: {
|
|
15
|
+
fetch: Fetch;
|
|
16
|
+
store: Store;
|
|
17
|
+
changed(state: State): void;
|
|
18
|
+
now?: () => number;
|
|
19
|
+
}): {
|
|
20
|
+
status(): Promise<{
|
|
21
|
+
state: "ineligible" | "signed_in" | "signed_out";
|
|
22
|
+
account: {
|
|
23
|
+
email?: string | undefined;
|
|
24
|
+
plan?: string | undefined;
|
|
25
|
+
};
|
|
26
|
+
} | {
|
|
27
|
+
account?: undefined;
|
|
28
|
+
state: "ineligible" | "signed_in" | "signed_out";
|
|
29
|
+
}>;
|
|
30
|
+
start({ redirectUri }: {
|
|
31
|
+
redirectUri: string;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
authorizeUrl: string;
|
|
34
|
+
}>;
|
|
35
|
+
complete({ callbackUrl }: {
|
|
36
|
+
callbackUrl: string;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
account: {
|
|
39
|
+
email?: string | undefined;
|
|
40
|
+
plan?: string | undefined;
|
|
41
|
+
};
|
|
42
|
+
}>;
|
|
43
|
+
logout(): Promise<{
|
|
44
|
+
revoked: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
/** A usable access token, refreshed when it's near expiry or when `stale`. */
|
|
47
|
+
token(stale?: boolean): Promise<string>;
|
|
48
|
+
/** OpenAI said the account isn't eligible: say so until the next sign-in. */
|
|
49
|
+
ineligible(): Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
export type Account = ReturnType<typeof createAccount>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { Refused } from "@agentorg/sdk";
|
|
2
|
+
import { challengeOf, random } from "./encoding.js";
|
|
3
|
+
import { verifyIdToken } from "./jwt.js";
|
|
4
|
+
import { AGENT_NAME, AUTHORIZE, DYNAMIC_CLIENT, discover, GrantError, grant, ISSUER, PLAN_SCOPE, RESOURCE, revoke, SCOPES, TERMINAL, } from "./oauth.js";
|
|
5
|
+
/** How long a started sign-in can be completed, how many can wait, and how early to refresh. */
|
|
6
|
+
const PENDING_FOR = 10 * 60_000;
|
|
7
|
+
const MAX_PENDING = 16;
|
|
8
|
+
const REFRESH_BEFORE = 5 * 60_000;
|
|
9
|
+
export const notSignedIn = () => new Refused("not_signed_in", "Not signed in to ChatGPT. Continue with ChatGPT to sign in.");
|
|
10
|
+
export const notEligible = () => new Refused("ineligible", "This ChatGPT account isn't eligible to use its plan in this app. Signing in again won't change that.");
|
|
11
|
+
const unreachable = () => new Refused("unavailable", "OpenAI couldn't be reached. Try again later.");
|
|
12
|
+
const address = (url) => `${url.origin}${url.pathname}`;
|
|
13
|
+
/** Who signed in, from verified id_token claims; the plan claim isn't documented, so optional. */
|
|
14
|
+
function identity(claims) {
|
|
15
|
+
const auth = claims["https://api.openai.com/auth"];
|
|
16
|
+
return {
|
|
17
|
+
...(typeof claims.sub === "string" ? { sub: claims.sub } : {}),
|
|
18
|
+
...(typeof claims.email === "string" ? { email: claims.email.slice(0, 320) } : {}),
|
|
19
|
+
...(typeof auth?.chatgpt_plan_type === "string"
|
|
20
|
+
? { plan: auth.chatgpt_plan_type.slice(0, 64) }
|
|
21
|
+
: {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const tokensOf = ({ accessToken, refreshToken, expiresAt, scopes }) => ({
|
|
25
|
+
accessToken,
|
|
26
|
+
refreshToken,
|
|
27
|
+
expiresAt,
|
|
28
|
+
scopes,
|
|
29
|
+
});
|
|
30
|
+
export function createAccount(options) {
|
|
31
|
+
const { fetch, store, changed } = options;
|
|
32
|
+
const now = options.now ?? Date.now;
|
|
33
|
+
const pending = new Map();
|
|
34
|
+
let issuer;
|
|
35
|
+
let refreshing;
|
|
36
|
+
/** Bumped by every sign-in and sign-out, so a refresh that lands after one is dropped. */
|
|
37
|
+
let generation = 0;
|
|
38
|
+
const issuerOf = () => {
|
|
39
|
+
issuer ??= discover(fetch).catch((error) => {
|
|
40
|
+
issuer = undefined;
|
|
41
|
+
throw error;
|
|
42
|
+
});
|
|
43
|
+
return issuer;
|
|
44
|
+
};
|
|
45
|
+
/** Verify an id_token; on a key the issuer no longer has, read its keys once more. */
|
|
46
|
+
const verify = async (token, expect) => {
|
|
47
|
+
const check = async () => verifyIdToken(token, (await issuerOf()).keys, { issuer: ISSUER, ...expect, now: now() });
|
|
48
|
+
try {
|
|
49
|
+
return await check();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (!(error instanceof Error && error.message === "no matching signing key"))
|
|
53
|
+
throw error;
|
|
54
|
+
issuer = undefined;
|
|
55
|
+
return check();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const read = async (key) => (await store.get({ key })).value;
|
|
59
|
+
const write = (key, value) => store.set({ key, value: value });
|
|
60
|
+
const credential = () => read("credential");
|
|
61
|
+
const registration = async () => {
|
|
62
|
+
const kept = await read("registration");
|
|
63
|
+
if (kept)
|
|
64
|
+
return kept;
|
|
65
|
+
const fresh = { hostId: `urn:uuid:${crypto.randomUUID()}` };
|
|
66
|
+
await write("registration", fresh);
|
|
67
|
+
return fresh;
|
|
68
|
+
};
|
|
69
|
+
const stateOf = (held) => !held ? "signed_out" : held.ineligible ? "ineligible" : "signed_in";
|
|
70
|
+
const accountOf = (held) => ({
|
|
71
|
+
...(held.email === undefined ? {} : { email: held.email }),
|
|
72
|
+
...(held.plan === undefined ? {} : { plan: held.plan }),
|
|
73
|
+
});
|
|
74
|
+
const clear = async () => {
|
|
75
|
+
generation++;
|
|
76
|
+
await store.delete({ key: "credential" });
|
|
77
|
+
changed("signed_out");
|
|
78
|
+
};
|
|
79
|
+
/** Revoke tokens that won't be kept, as well as we can; nothing waits for it. */
|
|
80
|
+
const discard = (clientId, refreshToken) => void issuerOf()
|
|
81
|
+
.then((found) => revoke(fetch, found, clientId, refreshToken))
|
|
82
|
+
.catch(() => false);
|
|
83
|
+
/** Refresh the tokens, one refresh at a time; the refresh token rotates each time. */
|
|
84
|
+
const refresh = () => {
|
|
85
|
+
refreshing ??= (async () => {
|
|
86
|
+
const started = generation;
|
|
87
|
+
try {
|
|
88
|
+
const held = await credential();
|
|
89
|
+
if (!held)
|
|
90
|
+
throw notSignedIn();
|
|
91
|
+
const next = await grant(fetch, {
|
|
92
|
+
grant_type: "refresh_token",
|
|
93
|
+
client_id: held.clientId,
|
|
94
|
+
refresh_token: held.refreshToken,
|
|
95
|
+
}, now());
|
|
96
|
+
let kept = { ...held, ...tokensOf(next) };
|
|
97
|
+
if (next.idToken) {
|
|
98
|
+
const claims = await verify(next.idToken, { audience: held.clientId }).catch(() => undefined);
|
|
99
|
+
if (claims)
|
|
100
|
+
kept = { ...kept, ...identity(claims) };
|
|
101
|
+
}
|
|
102
|
+
// Signed out or in again meanwhile: these tokens belong to nothing now.
|
|
103
|
+
if (started !== generation) {
|
|
104
|
+
discard(held.clientId, next.refreshToken);
|
|
105
|
+
throw notSignedIn();
|
|
106
|
+
}
|
|
107
|
+
await write("credential", kept);
|
|
108
|
+
return kept;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error instanceof GrantError) {
|
|
112
|
+
if (!TERMINAL.has(error.code))
|
|
113
|
+
throw new Refused("failed", `OpenAI refused to refresh the sign-in (${error.code}).`);
|
|
114
|
+
if (started === generation)
|
|
115
|
+
await clear();
|
|
116
|
+
throw notSignedIn();
|
|
117
|
+
}
|
|
118
|
+
if (error instanceof Refused)
|
|
119
|
+
throw error;
|
|
120
|
+
throw unreachable();
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
refreshing = undefined;
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
return refreshing;
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
async status() {
|
|
130
|
+
const held = await credential();
|
|
131
|
+
return held ? { state: stateOf(held), account: accountOf(held) } : { state: stateOf(held) };
|
|
132
|
+
},
|
|
133
|
+
async start({ redirectUri }) {
|
|
134
|
+
const { hostId, clientId = DYNAMIC_CLIENT } = await registration();
|
|
135
|
+
for (const [state, attempt] of pending)
|
|
136
|
+
if (now() - attempt.at > PENDING_FOR || pending.size >= MAX_PENDING)
|
|
137
|
+
pending.delete(state);
|
|
138
|
+
const verifier = random(48);
|
|
139
|
+
const state = random();
|
|
140
|
+
const nonce = random();
|
|
141
|
+
const redirect = address(new URL(redirectUri));
|
|
142
|
+
pending.set(state, { verifier, nonce, redirectUri, redirect, clientId, at: now() });
|
|
143
|
+
const url = new URL(AUTHORIZE);
|
|
144
|
+
url.search = new URLSearchParams({
|
|
145
|
+
response_type: "code",
|
|
146
|
+
client_id: clientId,
|
|
147
|
+
redirect_uri: redirectUri,
|
|
148
|
+
scope: SCOPES,
|
|
149
|
+
resource: RESOURCE,
|
|
150
|
+
code_challenge: await challengeOf(verifier),
|
|
151
|
+
code_challenge_method: "S256",
|
|
152
|
+
state,
|
|
153
|
+
nonce,
|
|
154
|
+
ext_agent_host_id: hostId,
|
|
155
|
+
...(clientId === DYNAMIC_CLIENT ? { agent_name_hint: AGENT_NAME } : {}),
|
|
156
|
+
}).toString();
|
|
157
|
+
return { authorizeUrl: url.toString() };
|
|
158
|
+
},
|
|
159
|
+
async complete({ callbackUrl }) {
|
|
160
|
+
let url;
|
|
161
|
+
try {
|
|
162
|
+
url = new URL(callbackUrl);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
throw new Refused("invalid", "callbackUrl isn't an address.");
|
|
166
|
+
}
|
|
167
|
+
const params = url.searchParams;
|
|
168
|
+
const state = params.get("state");
|
|
169
|
+
const attempt = state === null ? undefined : pending.get(state);
|
|
170
|
+
if (state === null || !attempt || now() - attempt.at > PENDING_FOR)
|
|
171
|
+
throw new Refused("expired", "This sign-in wasn't started here, or it expired. Start again.");
|
|
172
|
+
if (address(url) !== attempt.redirect)
|
|
173
|
+
throw new Refused("invalid", "The callback isn't the address this sign-in redirects to.");
|
|
174
|
+
pending.delete(state);
|
|
175
|
+
const error = params.get("error");
|
|
176
|
+
if (error === "access_denied")
|
|
177
|
+
throw new Refused("denied", "Sign-in was declined.");
|
|
178
|
+
if (error !== null)
|
|
179
|
+
throw new Refused("failed", "OpenAI returned an error to the sign-in.");
|
|
180
|
+
const code = params.get("code");
|
|
181
|
+
if (!code)
|
|
182
|
+
throw new Refused("failed", "The callback carries no code.");
|
|
183
|
+
const issued = params.get("client_id");
|
|
184
|
+
let clientId = attempt.clientId;
|
|
185
|
+
if (clientId === DYNAMIC_CLIENT) {
|
|
186
|
+
if (!issued || issued === DYNAMIC_CLIENT)
|
|
187
|
+
throw new Refused("failed", "OpenAI didn't issue a client id; registration isn't complete.");
|
|
188
|
+
clientId = issued;
|
|
189
|
+
}
|
|
190
|
+
else if (issued !== null && issued !== clientId)
|
|
191
|
+
throw new Refused("failed", "The callback names another client, so it was refused.");
|
|
192
|
+
let granted;
|
|
193
|
+
try {
|
|
194
|
+
granted = await grant(fetch, {
|
|
195
|
+
grant_type: "authorization_code",
|
|
196
|
+
client_id: clientId,
|
|
197
|
+
code,
|
|
198
|
+
code_verifier: attempt.verifier,
|
|
199
|
+
redirect_uri: attempt.redirectUri,
|
|
200
|
+
}, now());
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (error instanceof GrantError)
|
|
204
|
+
throw new Refused("failed", `OpenAI refused the sign-in (${error.code}). Start again.`);
|
|
205
|
+
throw unreachable();
|
|
206
|
+
}
|
|
207
|
+
if (!granted.scopes.includes(PLAN_SCOPE)) {
|
|
208
|
+
discard(clientId, granted.refreshToken);
|
|
209
|
+
throw new Refused("failed", "The sign-in wasn't granted ChatGPT plan usage.");
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
await issuerOf();
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
discard(clientId, granted.refreshToken);
|
|
216
|
+
throw unreachable();
|
|
217
|
+
}
|
|
218
|
+
let claims;
|
|
219
|
+
try {
|
|
220
|
+
if (!granted.idToken)
|
|
221
|
+
throw new Error("no id_token");
|
|
222
|
+
claims = await verify(granted.idToken, { audience: clientId, nonce: attempt.nonce });
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
discard(clientId, granted.refreshToken);
|
|
226
|
+
throw new Refused("failed", "OpenAI's identity token didn't verify.");
|
|
227
|
+
}
|
|
228
|
+
// A refresh still running belongs to the sign-in this one replaces, which is revoked.
|
|
229
|
+
await refreshing?.catch(() => { });
|
|
230
|
+
generation++;
|
|
231
|
+
const replaced = await credential();
|
|
232
|
+
if (replaced)
|
|
233
|
+
discard(replaced.clientId, replaced.refreshToken);
|
|
234
|
+
const held = { clientId, ...tokensOf(granted), ...identity(claims) };
|
|
235
|
+
await write("registration", { ...(await registration()), clientId });
|
|
236
|
+
await write("credential", held);
|
|
237
|
+
changed("signed_in");
|
|
238
|
+
return { account: accountOf(held) };
|
|
239
|
+
},
|
|
240
|
+
async logout() {
|
|
241
|
+
// A refresh still running would write its tokens back after the sign-out.
|
|
242
|
+
await refreshing?.catch(() => { });
|
|
243
|
+
const held = await credential();
|
|
244
|
+
if (!held)
|
|
245
|
+
return { revoked: true };
|
|
246
|
+
await clear();
|
|
247
|
+
const revoked = await issuerOf().then((found) => revoke(fetch, found, held.clientId, held.refreshToken), () => false);
|
|
248
|
+
return { revoked };
|
|
249
|
+
},
|
|
250
|
+
/** A usable access token, refreshed when it's near expiry or when `stale`. */
|
|
251
|
+
async token(stale = false) {
|
|
252
|
+
const held = await credential();
|
|
253
|
+
if (!held)
|
|
254
|
+
throw notSignedIn();
|
|
255
|
+
if (held.ineligible)
|
|
256
|
+
throw notEligible();
|
|
257
|
+
if (!stale && held.expiresAt - REFRESH_BEFORE > now())
|
|
258
|
+
return held.accessToken;
|
|
259
|
+
return (await refresh()).accessToken;
|
|
260
|
+
},
|
|
261
|
+
/** OpenAI said the account isn't eligible: say so until the next sign-in. */
|
|
262
|
+
async ineligible() {
|
|
263
|
+
const held = await credential();
|
|
264
|
+
if (!held || held.ineligible)
|
|
265
|
+
return;
|
|
266
|
+
await write("credential", { ...held, ineligible: true });
|
|
267
|
+
changed("ineligible");
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Base64url without padding, as PKCE and JWTs use it. */
|
|
2
|
+
export declare function base64url(bytes: Uint8Array): string;
|
|
3
|
+
export declare function fromBase64url(text: string): Uint8Array<ArrayBuffer>;
|
|
4
|
+
/** A fresh random value, base64url: `bytes` of entropy. */
|
|
5
|
+
export declare function random(bytes?: number): string;
|
|
6
|
+
/** The PKCE S256 challenge of a verifier. */
|
|
7
|
+
export declare function challengeOf(verifier: string): Promise<string>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Base64url without padding, as PKCE and JWTs use it. */
|
|
2
|
+
export function base64url(bytes) {
|
|
3
|
+
let binary = "";
|
|
4
|
+
for (const byte of bytes)
|
|
5
|
+
binary += String.fromCharCode(byte);
|
|
6
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
7
|
+
}
|
|
8
|
+
export function fromBase64url(text) {
|
|
9
|
+
const binary = atob(text.replaceAll("-", "+").replaceAll("_", "/"));
|
|
10
|
+
return Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
11
|
+
}
|
|
12
|
+
/** A fresh random value, base64url: `bytes` of entropy. */
|
|
13
|
+
export function random(bytes = 32) {
|
|
14
|
+
return base64url(crypto.getRandomValues(new Uint8Array(bytes)));
|
|
15
|
+
}
|
|
16
|
+
/** The PKCE S256 challenge of a verifier. */
|
|
17
|
+
export async function challengeOf(verifier) {
|
|
18
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
19
|
+
return base64url(new Uint8Array(digest));
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Log } from "@agentorg/sdk";
|
|
2
|
+
import type { Store } from "./account.js";
|
|
3
|
+
/**
|
|
4
|
+
* The sign-in, in one private file, read each time so nothing is held that the file no longer says.
|
|
5
|
+
* Each change is on disk before it is acknowledged, since a refresh token that rotated can't be had
|
|
6
|
+
* again. A file that can't be read is removed, and the person signs in again; nothing of it is ever
|
|
7
|
+
* logged, a parse error's text included.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fileStore(dir: string, log: Log): Store;
|