setup-agent 0.0.1
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 +38 -0
- package/bin/setup-agent.js +4 -0
- package/dist/cli.d.ts +64 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +636 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +26 -0
- package/dist/client.js.map +1 -0
- package/dist/host.d.ts +12 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +291 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/presets.d.ts +32 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +175 -0
- package/dist/presets.js.map +1 -0
- package/dist/protocol.d.ts +289 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +184 -0
- package/dist/protocol.js.map +1 -0
- package/package.json +60 -0
- package/src/cli.ts +799 -0
- package/src/client.ts +35 -0
- package/src/host.ts +435 -0
- package/src/index.ts +26 -0
- package/src/presets.ts +185 -0
- package/src/protocol.ts +479 -0
package/dist/presets.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
//#region src/presets.ts
|
|
2
|
+
const TYPESCRIPT = {
|
|
3
|
+
id: "typescript",
|
|
4
|
+
label: "TypeScript"
|
|
5
|
+
};
|
|
6
|
+
const PYTHON = {
|
|
7
|
+
id: "python",
|
|
8
|
+
label: "Python"
|
|
9
|
+
};
|
|
10
|
+
const DOCS = "https://www.assistant-ui.com";
|
|
11
|
+
/** The standard inputs an agent can ask with `ask --preset <id>`. */
|
|
12
|
+
const INPUT_PRESETS = {
|
|
13
|
+
framework: {
|
|
14
|
+
kind: "choice",
|
|
15
|
+
prompt: "Which agent framework runs your backend?",
|
|
16
|
+
options: [
|
|
17
|
+
{
|
|
18
|
+
id: "ai-sdk",
|
|
19
|
+
label: "Vercel AI SDK",
|
|
20
|
+
description: "Shortest path for Next.js and React",
|
|
21
|
+
icon: "vercel",
|
|
22
|
+
variants: [TYPESCRIPT]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "mastra",
|
|
26
|
+
label: "Mastra",
|
|
27
|
+
description: "Workflows, memory and tools",
|
|
28
|
+
icon: "mastra",
|
|
29
|
+
variants: [TYPESCRIPT]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "langgraph",
|
|
33
|
+
label: "LangGraph",
|
|
34
|
+
description: "Stateful graphs, Python or TypeScript",
|
|
35
|
+
icon: "langgraph",
|
|
36
|
+
variants: [PYTHON, TYPESCRIPT]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
help: {
|
|
40
|
+
summary: "Already on one of these? Pick it. Starting fresh in Next.js, the AI SDK is the shortest path. Mastra adds workflows and memory in TypeScript. LangGraph fits a Python backend or multi-step graphs that pause for a human.",
|
|
41
|
+
href: `${DOCS}/docs/runtimes/pick-a-runtime`
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"llm-provider": {
|
|
45
|
+
kind: "model",
|
|
46
|
+
prompt: "Which model should the assistant use?",
|
|
47
|
+
options: [
|
|
48
|
+
{
|
|
49
|
+
id: "openai",
|
|
50
|
+
label: "OpenAI",
|
|
51
|
+
description: "OPENAI_API_KEY",
|
|
52
|
+
icon: "openai"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "anthropic",
|
|
56
|
+
label: "Anthropic",
|
|
57
|
+
description: "ANTHROPIC_API_KEY",
|
|
58
|
+
icon: "anthropic"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "google",
|
|
62
|
+
label: "Google Gemini",
|
|
63
|
+
description: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
64
|
+
icon: "google"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "openrouter",
|
|
68
|
+
label: "OpenRouter",
|
|
69
|
+
description: "OPENROUTER_API_KEY",
|
|
70
|
+
icon: "openrouter"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "xai",
|
|
74
|
+
label: "xAI",
|
|
75
|
+
description: "XAI_API_KEY",
|
|
76
|
+
icon: "xai"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "mistral",
|
|
80
|
+
label: "Mistral",
|
|
81
|
+
description: "MISTRAL_API_KEY",
|
|
82
|
+
icon: "mistral"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "deepseek",
|
|
86
|
+
label: "DeepSeek",
|
|
87
|
+
description: "DEEPSEEK_API_KEY",
|
|
88
|
+
icon: "deepseek"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "groq",
|
|
92
|
+
label: "Groq",
|
|
93
|
+
description: "GROQ_API_KEY",
|
|
94
|
+
icon: "groq"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "fireworks",
|
|
98
|
+
label: "Fireworks AI",
|
|
99
|
+
description: "FIREWORKS_API_KEY",
|
|
100
|
+
icon: "fireworks"
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
default: "openai",
|
|
104
|
+
help: { summary: "Any of them works with every framework here. Pick the one whose API key you already have, or OpenRouter to reach many models with one key." }
|
|
105
|
+
},
|
|
106
|
+
project: {
|
|
107
|
+
kind: "choice",
|
|
108
|
+
prompt: "Which project should this go into?",
|
|
109
|
+
options: [{
|
|
110
|
+
id: "new",
|
|
111
|
+
label: "New project",
|
|
112
|
+
description: "Start a fresh React app here",
|
|
113
|
+
variants: [
|
|
114
|
+
{
|
|
115
|
+
id: "next",
|
|
116
|
+
label: "Next.js"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "vite",
|
|
120
|
+
label: "Vite"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "react-router",
|
|
124
|
+
label: "React Router"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "tanstack-start",
|
|
128
|
+
label: "TanStack Start"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "expo",
|
|
132
|
+
label: "Expo"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}],
|
|
136
|
+
help: {
|
|
137
|
+
summary: "Pick the app the assistant should live in. For a new project, Next.js is the shortest path: the assistant-ui CLI scaffolds the chat route with it. Pick Vite, React Router or TanStack Start when the rest of your stack already uses them, and Expo for a native app.",
|
|
138
|
+
href: `${DOCS}/docs/installation`
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const isPresetId = (id) => id in INPUT_PRESETS;
|
|
143
|
+
/** Build the seed for a standard input, throwing on an unknown preset, option or default. */
|
|
144
|
+
const presetInput = (id, overrides = {}) => {
|
|
145
|
+
const preset = INPUT_PRESETS[id];
|
|
146
|
+
const listed = overrides.only ? overrides.only.map((optionId) => {
|
|
147
|
+
const option = preset.options.find((c) => c.id === optionId);
|
|
148
|
+
if (!option) throw new Error(`preset "${id}" has no option "${optionId}"`);
|
|
149
|
+
return option;
|
|
150
|
+
}) : preset.options;
|
|
151
|
+
const options = [...(overrides.found ?? []).map((option) => {
|
|
152
|
+
if (option.id.includes(":") || preset.options.some((o) => o.id === option.id)) throw new Error(`preset "${id}" cannot list "${option.id}" as an option`);
|
|
153
|
+
return {
|
|
154
|
+
...option,
|
|
155
|
+
label: option.id
|
|
156
|
+
};
|
|
157
|
+
}), ...listed];
|
|
158
|
+
const fallback = options.some((o) => o.id === preset.default) ? preset.default : void 0;
|
|
159
|
+
const chosen = overrides.default ?? fallback;
|
|
160
|
+
if (chosen !== void 0 && !options.some((o) => o.id === chosen)) throw new Error(`preset "${id}" has no option "${chosen}" to default to`);
|
|
161
|
+
return {
|
|
162
|
+
kind: preset.kind,
|
|
163
|
+
preset: id,
|
|
164
|
+
prompt: preset.prompt,
|
|
165
|
+
options,
|
|
166
|
+
...chosen !== void 0 && { default: chosen },
|
|
167
|
+
help: preset.help,
|
|
168
|
+
optional: overrides.optional ?? false,
|
|
169
|
+
...overrides.stepId !== void 0 && { stepId: overrides.stepId }
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
//#endregion
|
|
173
|
+
export { INPUT_PRESETS, isPresetId, presetInput };
|
|
174
|
+
|
|
175
|
+
//# sourceMappingURL=presets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.js","names":["Checkout","TYPESCRIPT","id","label","PYTHON","DOCS","PresetId","PresetOverrides","only","found","description","default","optional","stepId","Preset","kind","InputKind","prompt","options","ChoiceOption","help","InputHelp","INPUT_PRESETS","Record","framework","icon","variants","summary","href","project","isPresetId","presetInput","overrides","InputSeed","preset","listed","map","optionId","option","find","c","Error","includes","some","o","fallback","undefined","chosen"],"sources":["../src/presets.ts"],"sourcesContent":["import type { Checkout } from \"./protocol\";\n\nconst TYPESCRIPT = { id: \"typescript\", label: \"TypeScript\" };\nconst PYTHON = { id: \"python\", label: \"Python\" };\n\nconst DOCS = \"https://www.assistant-ui.com\";\n\nexport type PresetId = \"framework\" | \"llm-provider\" | \"project\";\n\nexport type PresetOverrides = {\n /** Restrict the options to these ids, in this order; one id locks the choice in. */\n only?: readonly string[];\n /** Options the agent found itself, listed before the preset's own. */\n found?: readonly { id: string; description?: string }[];\n default?: string;\n optional?: boolean;\n stepId?: string;\n};\n\ntype Preset = {\n kind: Checkout.InputKind;\n prompt: string;\n options: Checkout.ChoiceOption[];\n default?: string;\n help: Checkout.InputHelp;\n};\n\n/** The standard inputs an agent can ask with `ask --preset <id>`. */\nexport const INPUT_PRESETS: Record<PresetId, Preset> = {\n framework: {\n kind: \"choice\",\n prompt: \"Which agent framework runs your backend?\",\n options: [\n {\n id: \"ai-sdk\",\n label: \"Vercel AI SDK\",\n description: \"Shortest path for Next.js and React\",\n icon: \"vercel\",\n variants: [TYPESCRIPT],\n },\n {\n id: \"mastra\",\n label: \"Mastra\",\n description: \"Workflows, memory and tools\",\n icon: \"mastra\",\n variants: [TYPESCRIPT],\n },\n {\n id: \"langgraph\",\n label: \"LangGraph\",\n description: \"Stateful graphs, Python or TypeScript\",\n icon: \"langgraph\",\n variants: [PYTHON, TYPESCRIPT],\n },\n ],\n help: {\n summary:\n \"Already on one of these? Pick it. Starting fresh in Next.js, the AI SDK is the shortest path. Mastra adds workflows and memory in TypeScript. LangGraph fits a Python backend or multi-step graphs that pause for a human.\",\n href: `${DOCS}/docs/runtimes/pick-a-runtime`,\n },\n },\n \"llm-provider\": {\n kind: \"model\",\n prompt: \"Which model should the assistant use?\",\n options: [\n {\n id: \"openai\",\n label: \"OpenAI\",\n description: \"OPENAI_API_KEY\",\n icon: \"openai\",\n },\n {\n id: \"anthropic\",\n label: \"Anthropic\",\n description: \"ANTHROPIC_API_KEY\",\n icon: \"anthropic\",\n },\n {\n id: \"google\",\n label: \"Google Gemini\",\n description: \"GOOGLE_GENERATIVE_AI_API_KEY\",\n icon: \"google\",\n },\n {\n id: \"openrouter\",\n label: \"OpenRouter\",\n description: \"OPENROUTER_API_KEY\",\n icon: \"openrouter\",\n },\n { id: \"xai\", label: \"xAI\", description: \"XAI_API_KEY\", icon: \"xai\" },\n {\n id: \"mistral\",\n label: \"Mistral\",\n description: \"MISTRAL_API_KEY\",\n icon: \"mistral\",\n },\n {\n id: \"deepseek\",\n label: \"DeepSeek\",\n description: \"DEEPSEEK_API_KEY\",\n icon: \"deepseek\",\n },\n { id: \"groq\", label: \"Groq\", description: \"GROQ_API_KEY\", icon: \"groq\" },\n {\n id: \"fireworks\",\n label: \"Fireworks AI\",\n description: \"FIREWORKS_API_KEY\",\n icon: \"fireworks\",\n },\n ],\n default: \"openai\",\n help: {\n summary:\n \"Any of them works with every framework here. Pick the one whose API key you already have, or OpenRouter to reach many models with one key.\",\n },\n },\n project: {\n kind: \"choice\",\n prompt: \"Which project should this go into?\",\n options: [\n {\n id: \"new\",\n label: \"New project\",\n description: \"Start a fresh React app here\",\n variants: [\n { id: \"next\", label: \"Next.js\" },\n { id: \"vite\", label: \"Vite\" },\n { id: \"react-router\", label: \"React Router\" },\n { id: \"tanstack-start\", label: \"TanStack Start\" },\n { id: \"expo\", label: \"Expo\" },\n ],\n },\n ],\n help: {\n summary:\n \"Pick the app the assistant should live in. For a new project, Next.js is the shortest path: the assistant-ui CLI scaffolds the chat route with it. Pick Vite, React Router or TanStack Start when the rest of your stack already uses them, and Expo for a native app.\",\n href: `${DOCS}/docs/installation`,\n },\n },\n};\n\nexport const isPresetId = (id: string): id is PresetId => id in INPUT_PRESETS;\n\n/** Build the seed for a standard input, throwing on an unknown preset, option or default. */\nexport const presetInput = (\n id: PresetId,\n overrides: PresetOverrides = {},\n): Checkout.InputSeed & { stepId?: string } => {\n const preset = INPUT_PRESETS[id];\n const listed = overrides.only\n ? overrides.only.map((optionId) => {\n const option = preset.options.find((c) => c.id === optionId);\n if (!option)\n throw new Error(`preset \"${id}\" has no option \"${optionId}\"`);\n return option;\n })\n : preset.options;\n const found = (overrides.found ?? []).map((option) => {\n if (\n option.id.includes(\":\") ||\n preset.options.some((o) => o.id === option.id)\n ) {\n throw new Error(`preset \"${id}\" cannot list \"${option.id}\" as an option`);\n }\n return { ...option, label: option.id };\n });\n const options = [...found, ...listed];\n const fallback = options.some((o) => o.id === preset.default)\n ? preset.default\n : undefined;\n const chosen = overrides.default ?? fallback;\n if (chosen !== undefined && !options.some((o) => o.id === chosen)) {\n throw new Error(`preset \"${id}\" has no option \"${chosen}\" to default to`);\n }\n return {\n kind: preset.kind,\n preset: id,\n prompt: preset.prompt,\n options,\n ...(chosen !== undefined && { default: chosen }),\n help: preset.help,\n optional: overrides.optional ?? false,\n ...(overrides.stepId !== undefined && { stepId: overrides.stepId }),\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
//#region src/protocol.d.ts
|
|
2
|
+
declare namespace Checkout {
|
|
3
|
+
type StepStatus = "pending" | "active" | "done" | "skipped" | "blocked";
|
|
4
|
+
/** A unit of install work the agent declared; the catalog seeds none. */
|
|
5
|
+
type Step = {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
detail?: string;
|
|
9
|
+
status: StepStatus;
|
|
10
|
+
note?: string;
|
|
11
|
+
/** The product this step belongs to, when the agent said. */
|
|
12
|
+
product?: string;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
};
|
|
15
|
+
type Product = {
|
|
16
|
+
slug: string;
|
|
17
|
+
name: string;
|
|
18
|
+
/** The install reference the agent fetches while planning. */
|
|
19
|
+
guide?: string;
|
|
20
|
+
};
|
|
21
|
+
type InputStatus = "open" | "answered" | "dismissed";
|
|
22
|
+
/**
|
|
23
|
+
* product: the agent proposes adding a product to this checkout, such as one
|
|
24
|
+
* another product depends on. The browser owns the catalog, so it answers
|
|
25
|
+
* with `checkout/add-product`; dismissing the input declines.
|
|
26
|
+
*/
|
|
27
|
+
type InputKind = "text" | "choice" | "model" | "product";
|
|
28
|
+
type ChoiceVariant = {
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
};
|
|
32
|
+
type ChoiceOption = {
|
|
33
|
+
id: string;
|
|
34
|
+
label: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
/** A name from OPTION_ICONS, or the key of a brand mark on a preset option; unknown keys render without one. */
|
|
37
|
+
icon?: string;
|
|
38
|
+
/** A second pick under this option, such as the language of a framework. */
|
|
39
|
+
variants?: ChoiceVariant[];
|
|
40
|
+
};
|
|
41
|
+
/** A short nudge for users who need help choosing, with a longer guide to link to. */
|
|
42
|
+
type InputHelp = {
|
|
43
|
+
summary: string;
|
|
44
|
+
href?: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The answer to a model input, stored as JSON text. The API key itself is
|
|
48
|
+
* never part of it: the browser deposits it with the checkout and the
|
|
49
|
+
* agent's `env` command writes it straight to a file.
|
|
50
|
+
*/
|
|
51
|
+
type ModelAnswer = {
|
|
52
|
+
provider: string;
|
|
53
|
+
model: string;
|
|
54
|
+
reasoningEffort?: "low" | "medium" | "high";
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A choice answer is an option id, `<option>:<variant>` when the option has
|
|
58
|
+
* variants, or the user's own text when none of the options fit; a multiple
|
|
59
|
+
* choice answers a JSON array of those. A model answer is a JSON-encoded
|
|
60
|
+
* ModelAnswer whose provider is one of the options.
|
|
61
|
+
*/
|
|
62
|
+
type Input = {
|
|
63
|
+
phase: Status;
|
|
64
|
+
id: string;
|
|
65
|
+
kind: InputKind;
|
|
66
|
+
/** The standard input this was built from, when it was. */
|
|
67
|
+
preset?: string;
|
|
68
|
+
prompt: string;
|
|
69
|
+
placeholder?: string;
|
|
70
|
+
options?: ChoiceOption[];
|
|
71
|
+
/** The user may pick any number of options; the answer is a JSON array. */
|
|
72
|
+
multiple?: true;
|
|
73
|
+
/** The slug a product input proposes. */
|
|
74
|
+
product?: string;
|
|
75
|
+
default?: string;
|
|
76
|
+
help?: InputHelp;
|
|
77
|
+
optional: boolean;
|
|
78
|
+
status: InputStatus;
|
|
79
|
+
answer?: string;
|
|
80
|
+
/** A remark the user attached to the answer. */
|
|
81
|
+
note?: string;
|
|
82
|
+
stepId?: string;
|
|
83
|
+
createdAt: number;
|
|
84
|
+
answeredAt?: number;
|
|
85
|
+
};
|
|
86
|
+
type LogEntry = {
|
|
87
|
+
phase: Status;
|
|
88
|
+
id: string;
|
|
89
|
+
role: "agent" | "user";
|
|
90
|
+
acknowledgedAt?: number;
|
|
91
|
+
at: number;
|
|
92
|
+
text: string;
|
|
93
|
+
/** The step that was active when the line was posted; none while planning. */
|
|
94
|
+
stepId?: string;
|
|
95
|
+
};
|
|
96
|
+
type PlanStatus = "proposed" | "approved" | "changes-requested";
|
|
97
|
+
/** One revision of the agent's plan. Markdown the browser renders. */
|
|
98
|
+
type Plan = {
|
|
99
|
+
revision: number;
|
|
100
|
+
markdown: string;
|
|
101
|
+
status: PlanStatus;
|
|
102
|
+
submittedAt: number;
|
|
103
|
+
decidedAt?: number;
|
|
104
|
+
/** What the user asked to change, when they did. */
|
|
105
|
+
feedback?: string;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* waiting: no agent has joined. planning: the agent investigates and
|
|
109
|
+
* proposes a plan. installing: the plan is approved and steps run.
|
|
110
|
+
*/
|
|
111
|
+
type Status = "waiting" | "planning" | "installing" | "done" | "cancelled";
|
|
112
|
+
/**
|
|
113
|
+
* The agent's proposal to close the checkout. Only the user closes it, and
|
|
114
|
+
* they may keep messaging the agent instead; a later `done` renews it.
|
|
115
|
+
* `preview` is the loopback URL of a dev server the agent left running for
|
|
116
|
+
* the user to try before they close.
|
|
117
|
+
*/
|
|
118
|
+
type Completion = {
|
|
119
|
+
proposedAt: number;
|
|
120
|
+
preview?: string;
|
|
121
|
+
};
|
|
122
|
+
type State = {
|
|
123
|
+
version: 2;
|
|
124
|
+
/** The id the browser gave the current checkout; a create with another id replaces it on the same link. */
|
|
125
|
+
id: string | null;
|
|
126
|
+
status: Status;
|
|
127
|
+
completion?: Completion;
|
|
128
|
+
createdAt: number | null;
|
|
129
|
+
products: Product[];
|
|
130
|
+
instructions: string;
|
|
131
|
+
agent: {
|
|
132
|
+
lastSeenAt: number | null;
|
|
133
|
+
/** False once the agent's stream said goodbye; heartbeats set it again. */
|
|
134
|
+
connected: boolean;
|
|
135
|
+
cwd: string | null;
|
|
136
|
+
/** The coding agent that ran the command, as the CLI detected it from its environment. */
|
|
137
|
+
kind: string | null;
|
|
138
|
+
/** When the command was first run against this checkout, before the agent joined the stream. */
|
|
139
|
+
introducedAt: number | null;
|
|
140
|
+
};
|
|
141
|
+
/** Every revision in order; the last one is current. */
|
|
142
|
+
plans: Plan[];
|
|
143
|
+
steps: Step[];
|
|
144
|
+
inputs: Input[];
|
|
145
|
+
log: LogEntry[];
|
|
146
|
+
};
|
|
147
|
+
type ProductSeed = {
|
|
148
|
+
slug: string;
|
|
149
|
+
name: string;
|
|
150
|
+
guide?: string;
|
|
151
|
+
};
|
|
152
|
+
type InputSeed = {
|
|
153
|
+
kind?: InputKind;
|
|
154
|
+
preset?: string;
|
|
155
|
+
prompt: string;
|
|
156
|
+
placeholder?: string;
|
|
157
|
+
options?: ChoiceOption[];
|
|
158
|
+
multiple?: true;
|
|
159
|
+
product?: string;
|
|
160
|
+
default?: string;
|
|
161
|
+
help?: InputHelp;
|
|
162
|
+
optional?: boolean;
|
|
163
|
+
};
|
|
164
|
+
type PlanDecision = {
|
|
165
|
+
decision: "approve";
|
|
166
|
+
} | {
|
|
167
|
+
decision: "revise";
|
|
168
|
+
feedback: string;
|
|
169
|
+
};
|
|
170
|
+
type Commands = {
|
|
171
|
+
"checkout/create": (params: {
|
|
172
|
+
id: string;
|
|
173
|
+
products: ProductSeed[];
|
|
174
|
+
instructions?: string;
|
|
175
|
+
}) => void;
|
|
176
|
+
"checkout/begin-plan": () => void;
|
|
177
|
+
"checkout/answer": (params: {
|
|
178
|
+
inputId: string;
|
|
179
|
+
answer: string;
|
|
180
|
+
note?: string;
|
|
181
|
+
}) => void;
|
|
182
|
+
"checkout/add-product": (params: {
|
|
183
|
+
inputId: string;
|
|
184
|
+
product: ProductSeed;
|
|
185
|
+
}) => void;
|
|
186
|
+
"checkout/message": (params: {
|
|
187
|
+
text: string;
|
|
188
|
+
}) => void;
|
|
189
|
+
"agent/ack": (params: {
|
|
190
|
+
messageId: string;
|
|
191
|
+
}) => void;
|
|
192
|
+
"checkout/dismiss": (params: {
|
|
193
|
+
inputId: string;
|
|
194
|
+
}) => void;
|
|
195
|
+
"checkout/plan": (params: PlanDecision) => void;
|
|
196
|
+
"checkout/cancel": () => void;
|
|
197
|
+
"checkout/finish": () => void;
|
|
198
|
+
"agent/intro": (params: {
|
|
199
|
+
kind?: string;
|
|
200
|
+
}) => void;
|
|
201
|
+
"agent/hello": (params: {
|
|
202
|
+
cwd?: string;
|
|
203
|
+
kind?: string;
|
|
204
|
+
}) => void;
|
|
205
|
+
"agent/heartbeat": () => void;
|
|
206
|
+
"agent/bye": () => void;
|
|
207
|
+
"agent/plan": (params: {
|
|
208
|
+
markdown: string;
|
|
209
|
+
}) => {
|
|
210
|
+
revision: number;
|
|
211
|
+
};
|
|
212
|
+
"agent/add-step": (params: {
|
|
213
|
+
title: string;
|
|
214
|
+
detail?: string;
|
|
215
|
+
product?: string;
|
|
216
|
+
/** Start the step in the same call. */
|
|
217
|
+
active?: boolean;
|
|
218
|
+
}) => {
|
|
219
|
+
stepId: string;
|
|
220
|
+
};
|
|
221
|
+
"agent/step": (params: {
|
|
222
|
+
stepId: string;
|
|
223
|
+
status: StepStatus;
|
|
224
|
+
note?: string;
|
|
225
|
+
}) => void;
|
|
226
|
+
"agent/ask": (params: InputSeed & {
|
|
227
|
+
stepId?: string;
|
|
228
|
+
}) => {
|
|
229
|
+
inputId: string;
|
|
230
|
+
};
|
|
231
|
+
"agent/log": (params: {
|
|
232
|
+
text: string;
|
|
233
|
+
}) => void;
|
|
234
|
+
"agent/done": (params?: {
|
|
235
|
+
summary?: string;
|
|
236
|
+
preview?: string;
|
|
237
|
+
}) => void;
|
|
238
|
+
};
|
|
239
|
+
type RejectionReason = "already-created" | "not-created" | "closed" | "planning-not-started" | "plan-required" | "no-plan" | "plan-decided" | "finish-not-proposed" | "feedback-required" | "empty-plan" | "unknown-step" | "unknown-product" | "unknown-input" | "input-closed" | "invalid-input" | "invalid-answer" | "invalid-preview" | "empty-message" | "unknown-message";
|
|
240
|
+
}
|
|
241
|
+
declare const AGENT_PRESENCE_MS = 15000;
|
|
242
|
+
declare const AGENT_HEARTBEAT_MS = 5000;
|
|
243
|
+
declare const initialCheckoutState: () => Checkout.State;
|
|
244
|
+
declare const isAgentPresent: (state: Checkout.State, now?: number) => boolean;
|
|
245
|
+
declare const isClosed: (state: Checkout.State) => boolean;
|
|
246
|
+
/** True while the agent's proposal to close waits for the user. */
|
|
247
|
+
declare const finishProposed: (state: Checkout.State) => boolean;
|
|
248
|
+
/** True when the user messaged the agent after it last proposed to close. */
|
|
249
|
+
declare const followedUpSinceProposal: (state: Checkout.State) => boolean;
|
|
250
|
+
/**
|
|
251
|
+
* The URL a preview names, or `undefined` unless it is an http(s) URL on this
|
|
252
|
+
* machine's loopback interface. The browser opens it, so nothing else passes.
|
|
253
|
+
*/
|
|
254
|
+
declare const parsePreviewUrl: (value: string | undefined) => URL | undefined;
|
|
255
|
+
declare const openInputs: (state: Checkout.State) => Checkout.Input[];
|
|
256
|
+
/** The current plan revision, or `undefined` before the agent proposed one. */
|
|
257
|
+
declare const currentPlan: (state: Checkout.State) => Checkout.Plan | undefined;
|
|
258
|
+
/** True while a proposed plan waits for the user's decision. */
|
|
259
|
+
declare const planNeedsReview: (state: Checkout.State) => boolean;
|
|
260
|
+
/** The icons an agent can put on its own options, by name. */
|
|
261
|
+
declare const OPTION_ICONS: readonly ["code", "terminal", "braces", "git-branch", "package", "database", "server", "cloud", "globe", "monitor", "smartphone", "key", "lock", "shield", "brain", "sparkles", "bot", "workflow", "file", "folder", "book", "table", "image", "video", "mic", "speech", "paperclip", "message", "mail", "bell", "calendar", "clock", "user", "users", "settings", "wrench", "plug", "link", "palette", "search", "zap", "check", "question"];
|
|
262
|
+
type OptionIcon = (typeof OPTION_ICONS)[number];
|
|
263
|
+
declare const isOptionIcon: (value: string) => value is OptionIcon;
|
|
264
|
+
declare const parseChoiceAnswer: (answer: string) => {
|
|
265
|
+
variant?: string;
|
|
266
|
+
option: string;
|
|
267
|
+
};
|
|
268
|
+
/** The entries of a multiple choice answer, or `undefined` when the text is not a JSON array of strings. */
|
|
269
|
+
declare const parseMultipleAnswer: (answer: string) => string[] | undefined;
|
|
270
|
+
type ChoiceAnswerKind = "option" | "custom" | "invalid";
|
|
271
|
+
/**
|
|
272
|
+
* "option" when `answer` names one of the input's options (with a variant
|
|
273
|
+
* when the option has them), "custom" for the user's own text, "invalid"
|
|
274
|
+
* for an option named without its variant or with an unknown one. A
|
|
275
|
+
* multiple choice is classified entry by entry: "invalid" when any entry is
|
|
276
|
+
* or the array is empty, "custom" when any entry is the user's own text.
|
|
277
|
+
*/
|
|
278
|
+
declare const classifyChoiceAnswer: (input: Checkout.Input, answer: string) => ChoiceAnswerKind;
|
|
279
|
+
/** The model answer an input holds, or `undefined` when the text is not one. */
|
|
280
|
+
declare const parseModelAnswer: (answer: string) => Checkout.ModelAnswer | undefined;
|
|
281
|
+
/** Whether `answer` is a model answer naming one of the input's providers. */
|
|
282
|
+
declare const isValidModelAnswer: (input: Checkout.Input, answer: string) => boolean;
|
|
283
|
+
declare const stepProgress: (state: Checkout.State) => {
|
|
284
|
+
total: number;
|
|
285
|
+
done: number;
|
|
286
|
+
};
|
|
287
|
+
//#endregion
|
|
288
|
+
export { AGENT_HEARTBEAT_MS, AGENT_PRESENCE_MS, Checkout, OPTION_ICONS, OptionIcon, classifyChoiceAnswer, currentPlan, finishProposed, followedUpSinceProposal, initialCheckoutState, isAgentPresent, isClosed, isOptionIcon, isValidModelAnswer, openInputs, parseChoiceAnswer, parseModelAnswer, parseMultipleAnswer, parsePreviewUrl, planNeedsReview, stepProgress };
|
|
289
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","names":[],"sources":["../src/protocol.ts"],"mappings":";kBAAiB;OACH;;OAQA;IACV;IACA;IACA;IACA,QAAQ;IACR;;IAEA;IACA;;OAGU;IACV;IACA;;IAEA;;OAGU;;;;;;OAOA;OAEA;IAAkB;IAAY;;OAE9B;IACV;IACA;IACA;;IAEA;;IAEA,WAAW;;;OAID;IAAc;IAAiB;;;;;;;OAO/B;IACV;IACA;IACA;;;;;;;;OASU;IACV,OAAO;IACP;IACA,MAAM;;IAEN;IACA;IACA;IACA,UAAU;;IAEV;;IAEA;IACA;IACA,OAAO;IACP;IACA,QAAQ;IACR;;IAEA;IACA;IACA;IACA;;OAGU;IACV,OAAO;IACP;IACA;IACA;IACA;IACA;;IAEA;;OAGU;;OAGA;IACV;IACA;IACA,QAAQ;IACR;IACA;;IAEA;;;;;;OAOU;;;;;;;OAaA;IAAe;IAAoB;;OAEnC;IACV;;IAEA;IACA,QAAQ;IACR,aAAa;IACb;IACA,UAAU;IACV;IACA;MACE;;MAEA;MACA;;MAEA;;MAEA;;;IAGF,OAAO;IACP,OAAO;IACP,QAAQ;IACR,KAAK;;OAGK;IAAgB;IAAc;IAAc;;OAE5C;IACV,OAAO;IACP;IACA;IACA;IACA,UAAU;IACV;IACA;IACA;IACA,OAAO;IACP;;OAGU;IACN;;IACA;IAAoB;;OAEd;IACV,oBAAoB;MAClB;MACA,UAAU;MACV;;IAEF;IACA,oBAAoB;MAClB;MACA;MACA;;IAEF,yBAAyB;MACvB;MACA,SAAS;;IAEX,qBAAqB;MAAU;;IAC/B,cAAc;MAAU;;IACxB,qBAAqB;MAAU;;IAC/B,kBAAkB,QAAQ;IAC1B;IACA;IACA,gBAAgB;MAAU;;IAC1B,gBAAgB;MAAU;MAAc;;IACxC;IACA;IACA,eAAe;MAAU;;MAAyB;;IAClD,mBAAmB;MACjB;MACA;MACA;;MAEA;;MACM;;IACR,eAAe;MACb;MACA,QAAQ;MACR;;IAEF,cAAc,QAAQ;MAAc;;MAClC;;IAEF,cAAc;MAAU;;IACxB,eAAe;MAAW;MAAkB;;;OAGlC;;cAsBD;cACA;cAEA,4BAA2B,SAAS;cAoBpC,iBAAkB,OAAO,SAAS,OAAO;cAKzC,WAAY,OAAO,SAAS;;cAI5B,iBAAkB,OAAO,SAAS;;cAIlC,0BAA2B,OAAO,SAAS;;;;;cAc3C,kBAAmB,8BAAyB;cAe5C,aAAc,OAAO,SAAS,UAAK,SAAA;;cAInC,cAAe,OAAO,SAAS,UAAQ,SAAS;;cAIhD,kBAAmB,OAAO,SAAS;;cAInC;KA8CD,qBAAqB;cAEpB,eAAgB,kBAAgB,SAAS;cAGzC,oBAAqB;;;;;cAMrB,sBAAuB;KAa/B;;;;;;;;cAwBQ,uBACX,OAAO,SAAS,OAChB,mBACC;;cAiBU,mBACX,mBACC,SAAS;;cAuBC,qBAAsB,OAAO,SAAS,OAAO;cAQ7C,eAAgB,OAAO,SAAS"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
//#region src/protocol.ts
|
|
2
|
+
const AGENT_PRESENCE_MS = 15e3;
|
|
3
|
+
const AGENT_HEARTBEAT_MS = 5e3;
|
|
4
|
+
const initialCheckoutState = () => ({
|
|
5
|
+
version: 2,
|
|
6
|
+
id: null,
|
|
7
|
+
status: "waiting",
|
|
8
|
+
createdAt: null,
|
|
9
|
+
products: [],
|
|
10
|
+
instructions: "",
|
|
11
|
+
agent: {
|
|
12
|
+
lastSeenAt: null,
|
|
13
|
+
connected: false,
|
|
14
|
+
cwd: null,
|
|
15
|
+
kind: null,
|
|
16
|
+
introducedAt: null
|
|
17
|
+
},
|
|
18
|
+
plans: [],
|
|
19
|
+
steps: [],
|
|
20
|
+
inputs: [],
|
|
21
|
+
log: []
|
|
22
|
+
});
|
|
23
|
+
const isAgentPresent = (state, now = Date.now()) => state.agent.connected && state.agent.lastSeenAt !== null && now - state.agent.lastSeenAt < 15e3;
|
|
24
|
+
const isClosed = (state) => state.status === "done" || state.status === "cancelled";
|
|
25
|
+
/** True while the agent's proposal to close waits for the user. */
|
|
26
|
+
const finishProposed = (state) => !isClosed(state) && state.completion !== void 0;
|
|
27
|
+
/** True when the user messaged the agent after it last proposed to close. */
|
|
28
|
+
const followedUpSinceProposal = (state) => {
|
|
29
|
+
const proposedAt = state.completion?.proposedAt;
|
|
30
|
+
return proposedAt !== void 0 && state.log.some((entry) => entry.role === "user" && entry.at > proposedAt);
|
|
31
|
+
};
|
|
32
|
+
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
33
|
+
"localhost",
|
|
34
|
+
"127.0.0.1",
|
|
35
|
+
"[::1]"
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* The URL a preview names, or `undefined` unless it is an http(s) URL on this
|
|
39
|
+
* machine's loopback interface. The browser opens it, so nothing else passes.
|
|
40
|
+
*/
|
|
41
|
+
const parsePreviewUrl = (value) => {
|
|
42
|
+
if (!value) return void 0;
|
|
43
|
+
let url;
|
|
44
|
+
try {
|
|
45
|
+
url = new URL(value);
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
50
|
+
if (url.username !== "" || url.password !== "") return void 0;
|
|
51
|
+
return LOOPBACK_HOSTS.has(url.hostname) || url.hostname.endsWith(".localhost") ? url : void 0;
|
|
52
|
+
};
|
|
53
|
+
const openInputs = (state) => state.inputs.filter((input) => input.status === "open");
|
|
54
|
+
/** The current plan revision, or `undefined` before the agent proposed one. */
|
|
55
|
+
const currentPlan = (state) => state.plans.at(-1);
|
|
56
|
+
/** True while a proposed plan waits for the user's decision. */
|
|
57
|
+
const planNeedsReview = (state) => !isClosed(state) && currentPlan(state)?.status === "proposed";
|
|
58
|
+
/** The icons an agent can put on its own options, by name. */
|
|
59
|
+
const OPTION_ICONS = [
|
|
60
|
+
"code",
|
|
61
|
+
"terminal",
|
|
62
|
+
"braces",
|
|
63
|
+
"git-branch",
|
|
64
|
+
"package",
|
|
65
|
+
"database",
|
|
66
|
+
"server",
|
|
67
|
+
"cloud",
|
|
68
|
+
"globe",
|
|
69
|
+
"monitor",
|
|
70
|
+
"smartphone",
|
|
71
|
+
"key",
|
|
72
|
+
"lock",
|
|
73
|
+
"shield",
|
|
74
|
+
"brain",
|
|
75
|
+
"sparkles",
|
|
76
|
+
"bot",
|
|
77
|
+
"workflow",
|
|
78
|
+
"file",
|
|
79
|
+
"folder",
|
|
80
|
+
"book",
|
|
81
|
+
"table",
|
|
82
|
+
"image",
|
|
83
|
+
"video",
|
|
84
|
+
"mic",
|
|
85
|
+
"speech",
|
|
86
|
+
"paperclip",
|
|
87
|
+
"message",
|
|
88
|
+
"mail",
|
|
89
|
+
"bell",
|
|
90
|
+
"calendar",
|
|
91
|
+
"clock",
|
|
92
|
+
"user",
|
|
93
|
+
"users",
|
|
94
|
+
"settings",
|
|
95
|
+
"wrench",
|
|
96
|
+
"plug",
|
|
97
|
+
"link",
|
|
98
|
+
"palette",
|
|
99
|
+
"search",
|
|
100
|
+
"zap",
|
|
101
|
+
"check",
|
|
102
|
+
"question"
|
|
103
|
+
];
|
|
104
|
+
const isOptionIcon = (value) => OPTION_ICONS.includes(value);
|
|
105
|
+
const parseChoiceAnswer = (answer) => {
|
|
106
|
+
const [option = "", variant] = answer.split(":", 2);
|
|
107
|
+
return {
|
|
108
|
+
option,
|
|
109
|
+
...variant !== void 0 && { variant }
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
/** The entries of a multiple choice answer, or `undefined` when the text is not a JSON array of strings. */
|
|
113
|
+
const parseMultipleAnswer = (answer) => {
|
|
114
|
+
let value;
|
|
115
|
+
try {
|
|
116
|
+
value = JSON.parse(answer);
|
|
117
|
+
} catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
|
|
121
|
+
};
|
|
122
|
+
const classifyChoiceEntry = (input, answer) => {
|
|
123
|
+
const { option, variant } = parseChoiceAnswer(answer);
|
|
124
|
+
const match = input.options?.find((candidate) => candidate.id === option);
|
|
125
|
+
if (!match) return answer.trim() === "" ? "invalid" : "custom";
|
|
126
|
+
if (!match.variants || match.variants.length === 0) return variant === void 0 ? "option" : "invalid";
|
|
127
|
+
return match.variants.some((candidate) => candidate.id === variant) ? "option" : "invalid";
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* "option" when `answer` names one of the input's options (with a variant
|
|
131
|
+
* when the option has them), "custom" for the user's own text, "invalid"
|
|
132
|
+
* for an option named without its variant or with an unknown one. A
|
|
133
|
+
* multiple choice is classified entry by entry: "invalid" when any entry is
|
|
134
|
+
* or the array is empty, "custom" when any entry is the user's own text.
|
|
135
|
+
*/
|
|
136
|
+
const classifyChoiceAnswer = (input, answer) => {
|
|
137
|
+
if (!input.multiple) return classifyChoiceEntry(input, answer);
|
|
138
|
+
const entries = parseMultipleAnswer(answer);
|
|
139
|
+
if (!entries || entries.length === 0) return "invalid";
|
|
140
|
+
const kinds = entries.map((entry) => classifyChoiceEntry(input, entry));
|
|
141
|
+
if (kinds.includes("invalid")) return "invalid";
|
|
142
|
+
return kinds.includes("custom") ? "custom" : "option";
|
|
143
|
+
};
|
|
144
|
+
const REASONING_EFFORTS = /* @__PURE__ */ new Set([
|
|
145
|
+
"low",
|
|
146
|
+
"medium",
|
|
147
|
+
"high"
|
|
148
|
+
]);
|
|
149
|
+
const isReasoningEffort = (value) => typeof value === "string" && REASONING_EFFORTS.has(value);
|
|
150
|
+
/** The model answer an input holds, or `undefined` when the text is not one. */
|
|
151
|
+
const parseModelAnswer = (answer) => {
|
|
152
|
+
let value;
|
|
153
|
+
try {
|
|
154
|
+
value = JSON.parse(answer);
|
|
155
|
+
} catch {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
159
|
+
const { provider, model, reasoningEffort } = value;
|
|
160
|
+
if (typeof provider !== "string" || typeof model !== "string" || provider === "" || model === "") return;
|
|
161
|
+
if (reasoningEffort === void 0) return {
|
|
162
|
+
provider,
|
|
163
|
+
model
|
|
164
|
+
};
|
|
165
|
+
if (!isReasoningEffort(reasoningEffort)) return void 0;
|
|
166
|
+
return {
|
|
167
|
+
provider,
|
|
168
|
+
model,
|
|
169
|
+
reasoningEffort
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
/** Whether `answer` is a model answer naming one of the input's providers. */
|
|
173
|
+
const isValidModelAnswer = (input, answer) => {
|
|
174
|
+
const parsed = parseModelAnswer(answer);
|
|
175
|
+
return parsed !== void 0 && (input.options?.some((option) => option.id === parsed.provider) ?? false);
|
|
176
|
+
};
|
|
177
|
+
const stepProgress = (state) => ({
|
|
178
|
+
total: state.steps.length,
|
|
179
|
+
done: state.steps.filter((step) => step.status === "done" || step.status === "skipped").length
|
|
180
|
+
});
|
|
181
|
+
//#endregion
|
|
182
|
+
export { AGENT_HEARTBEAT_MS, AGENT_PRESENCE_MS, OPTION_ICONS, classifyChoiceAnswer, currentPlan, finishProposed, followedUpSinceProposal, initialCheckoutState, isAgentPresent, isClosed, isOptionIcon, isValidModelAnswer, openInputs, parseChoiceAnswer, parseModelAnswer, parseMultipleAnswer, parsePreviewUrl, planNeedsReview, stepProgress };
|
|
183
|
+
|
|
184
|
+
//# sourceMappingURL=protocol.js.map
|