@agentshouse/kit 0.0.1 → 0.1.0-alpha.2
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/LICENSE +21 -0
- package/README.md +1696 -2
- package/bin/connect-linux.sh +127 -0
- package/bin/kit.js +1023 -0
- package/package.json +33 -3
- package/src/acp.js +99 -0
- package/src/adapter-mcp.js +44 -0
- package/src/adapter.js +252 -0
- package/src/agent-logins.js +65 -0
- package/src/agents.js +449 -0
- package/src/apply.js +29 -0
- package/src/attach.js +52 -0
- package/src/binding.js +90 -0
- package/src/blocks.js +156 -0
- package/src/bridge.js +449 -0
- package/src/bytes.js +89 -0
- package/src/channel.js +37 -0
- package/src/command.js +109 -0
- package/src/configuration.js +465 -0
- package/src/connector-login.js +91 -0
- package/src/connector.js +107 -0
- package/src/continuation.js +61 -0
- package/src/contract.js +363 -0
- package/src/conversation-originals.js +143 -0
- package/src/custom/authorization.js +63 -0
- package/src/declaration.js +35 -0
- package/src/delivery.js +191 -0
- package/src/directory.js +102 -0
- package/src/draft.js +89 -0
- package/src/google/calendar.js +177 -0
- package/src/google/declared.js +146 -0
- package/src/google/drive.js +435 -0
- package/src/google/gateway.js +166 -0
- package/src/google/gmail.js +249 -0
- package/src/google/id.js +1 -0
- package/src/google/oauth.js +273 -0
- package/src/google/source.js +25 -0
- package/src/hook.js +376 -0
- package/src/host.js +121 -0
- package/src/house-main.js +79 -0
- package/src/house.js +1085 -0
- package/src/inbound.js +175 -0
- package/src/instance/google.js +1 -0
- package/src/launcher-main.js +19 -0
- package/src/launcher.js +276 -0
- package/src/local-files.js +113 -0
- package/src/login.js +274 -0
- package/src/mcp.js +71 -0
- package/src/migrator.js +248 -0
- package/src/mount.js +582 -0
- package/src/operation.js +20 -0
- package/src/original.js +631 -0
- package/src/peer.js +124 -0
- package/src/plan.js +57 -0
- package/src/question.js +316 -0
- package/src/refusal.js +79 -0
- package/src/repository.js +521 -0
- package/src/session-bin/house +2 -0
- package/src/settlement.js +57 -0
- package/src/setup.js +185 -0
- package/src/source-originals.js +116 -0
- package/src/source.js +286 -0
- package/src/starter-set/grilling/LICENSE +21 -0
- package/src/starter-set/grilling/SKILL.md +30 -0
- package/src/starter-set/grilling/agents/openai.yaml +3 -0
- package/src/starter-set.js +17 -0
- package/src/state.js +157 -0
- package/src/sync-library.js +293 -0
- package/src/sync-state.js +116 -0
- package/src/sync.js +477 -0
- package/src/telegram.js +1045 -0
- package/src/terminal.js +57 -0
- package/src/tmux.js +119 -0
- package/src/worker.js +756 -0
- package/src/workspace.js +500 -0
package/src/peer.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { connect } from "node:net";
|
|
2
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { readFrames, writeFrame } from "./channel.js";
|
|
4
|
+
import { BRIDGE_RECONNECT_MAX_MS, BRIDGE_RECONNECT_MS } from "./contract.js";
|
|
5
|
+
import { KitRefusal } from "./refusal.js";
|
|
6
|
+
|
|
7
|
+
function connected(path) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const socket = connect(path);
|
|
10
|
+
socket.once("connect", () => resolve(socket));
|
|
11
|
+
socket.once("error", reject);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function bridgePeer(options) {
|
|
16
|
+
const outbound = [];
|
|
17
|
+
const drains = [];
|
|
18
|
+
let socket = null;
|
|
19
|
+
let ready = false;
|
|
20
|
+
let ended = false;
|
|
21
|
+
let greeting = null;
|
|
22
|
+
let refuse;
|
|
23
|
+
const refused = new Promise((_, reject) => {
|
|
24
|
+
refuse = reject;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function flush() {
|
|
28
|
+
while (ready && socket !== null && outbound.length > 0) {
|
|
29
|
+
if (socket.destroyed || socket.writableEnded) {
|
|
30
|
+
socket = null;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
options.onSend?.(outbound[0]);
|
|
34
|
+
writeFrame(socket, outbound[0]);
|
|
35
|
+
outbound.shift();
|
|
36
|
+
}
|
|
37
|
+
if (outbound.length === 0) for (const settle of drains.splice(0)) settle();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function received(frame) {
|
|
41
|
+
if (greeting === null) {
|
|
42
|
+
options.onFrame?.(frame);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const settle = greeting;
|
|
46
|
+
greeting = null;
|
|
47
|
+
settle(frame);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function attach(first) {
|
|
51
|
+
let backoff = BRIDGE_RECONNECT_MS;
|
|
52
|
+
const retry = async () => {
|
|
53
|
+
if (first) throw new KitRefusal("kit_bridge_unavailable", options.socket);
|
|
54
|
+
await delay(backoff);
|
|
55
|
+
backoff = Math.min(backoff * 2, BRIDGE_RECONNECT_MAX_MS);
|
|
56
|
+
};
|
|
57
|
+
while (!ended) {
|
|
58
|
+
let opened;
|
|
59
|
+
try {
|
|
60
|
+
opened = await connected(options.socket);
|
|
61
|
+
} catch {
|
|
62
|
+
await retry();
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
socket = opened;
|
|
66
|
+
ready = false;
|
|
67
|
+
opened.on("error", () => opened.destroy());
|
|
68
|
+
const answer = new Promise((resolve) => {
|
|
69
|
+
greeting = resolve;
|
|
70
|
+
});
|
|
71
|
+
const departure = new Promise((resolve) => opened.once("close", () => resolve(null)));
|
|
72
|
+
readFrames(opened, { onFrame: received, onRefusal: () => opened.destroy() });
|
|
73
|
+
writeFrame(opened, {
|
|
74
|
+
type: "hello",
|
|
75
|
+
role: options.role,
|
|
76
|
+
nonce: options.nonce,
|
|
77
|
+
...options.hello(),
|
|
78
|
+
});
|
|
79
|
+
const greeted = await Promise.race([answer, departure]);
|
|
80
|
+
if (greeted === null) {
|
|
81
|
+
greeting = null;
|
|
82
|
+
socket = null;
|
|
83
|
+
await retry();
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (!options.greeted(greeted)) {
|
|
87
|
+
ended = true;
|
|
88
|
+
socket = null;
|
|
89
|
+
opened.destroy();
|
|
90
|
+
throw new KitRefusal("kit_bridge_refused", typeof greeted.code === "string" ? greeted.code : null);
|
|
91
|
+
}
|
|
92
|
+
ready = true;
|
|
93
|
+
flush();
|
|
94
|
+
void keep(opened).catch(refuse);
|
|
95
|
+
return greeted;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function keep(current) {
|
|
101
|
+
if (!current.destroyed) await new Promise((resolve) => current.once("close", resolve));
|
|
102
|
+
if (socket === current) socket = null;
|
|
103
|
+
ready = false;
|
|
104
|
+
if (ended) return;
|
|
105
|
+
await attach(false);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
refused,
|
|
110
|
+
open: () => attach(options.retryInitial !== true),
|
|
111
|
+
send(frame) {
|
|
112
|
+
outbound.push(frame);
|
|
113
|
+
flush();
|
|
114
|
+
},
|
|
115
|
+
drained: () => outbound.length === 0 ? Promise.resolve() : new Promise((resolve) => drains.push(resolve)),
|
|
116
|
+
end() {
|
|
117
|
+
ended = true;
|
|
118
|
+
const current = socket;
|
|
119
|
+
socket = null;
|
|
120
|
+
if (current === null || current.destroyed || current.writableEnded) return Promise.resolve();
|
|
121
|
+
return new Promise((resolve) => current.end(resolve));
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
package/src/plan.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const PLAN_INTERVAL_MS = 500;
|
|
2
|
+
|
|
3
|
+
function steps(entries) {
|
|
4
|
+
if (!Array.isArray(entries) || entries.length === 0) return null;
|
|
5
|
+
return entries.map((entry) => ({ label: entry.content, status: entry.status }));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function observedPlans(options) {
|
|
9
|
+
const intervalMs = options.intervalMs ?? PLAN_INTERVAL_MS;
|
|
10
|
+
const now = options.now ?? (() => Date.now());
|
|
11
|
+
let timer = null;
|
|
12
|
+
let sentAt = null;
|
|
13
|
+
let sent = null;
|
|
14
|
+
let latest = null;
|
|
15
|
+
let completed = false;
|
|
16
|
+
|
|
17
|
+
function write(terminal) {
|
|
18
|
+
if (latest === null) return;
|
|
19
|
+
const serialized = JSON.stringify(latest);
|
|
20
|
+
if (!terminal && serialized === sent) return;
|
|
21
|
+
sent = serialized;
|
|
22
|
+
sentAt = now();
|
|
23
|
+
options.send({ steps: latest, terminal });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function cancel() {
|
|
27
|
+
if (timer === null) return;
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
timer = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
observe(entries) {
|
|
34
|
+
if (completed) return;
|
|
35
|
+
const observed = steps(entries);
|
|
36
|
+
if (observed === null) return;
|
|
37
|
+
latest = observed;
|
|
38
|
+
if (timer !== null) return;
|
|
39
|
+
const wait = sentAt === null ? 0 : sentAt + intervalMs - now();
|
|
40
|
+
if (wait <= 0) {
|
|
41
|
+
write(false);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
timer = setTimeout(() => {
|
|
45
|
+
timer = null;
|
|
46
|
+
write(false);
|
|
47
|
+
}, wait);
|
|
48
|
+
timer.unref?.();
|
|
49
|
+
},
|
|
50
|
+
complete() {
|
|
51
|
+
if (completed) return;
|
|
52
|
+
completed = true;
|
|
53
|
+
cancel();
|
|
54
|
+
write(true);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
package/src/question.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ELICITATION_FORM_MODE,
|
|
3
|
+
ELICITATION_FREE_TEXT_META,
|
|
4
|
+
ELICITATION_SECRET_META,
|
|
5
|
+
ELICITATION_URL_MODE,
|
|
6
|
+
SECRET_CEREMONY_STEPS_MAX,
|
|
7
|
+
SECRET_STEP_LABEL_MAX,
|
|
8
|
+
SECRET_STEP_TEXT_MAX,
|
|
9
|
+
NATIVE_CODEX_META,
|
|
10
|
+
NATIVE_CUSTOM_ANSWER_META,
|
|
11
|
+
NATIVE_OTHER_OPTION,
|
|
12
|
+
NATIVE_USER_NOTE_ROLE,
|
|
13
|
+
INTERACTION_OPTIONS_MAX,
|
|
14
|
+
INTERACTION_PROPERTY_MAX,
|
|
15
|
+
} from "./contract.js";
|
|
16
|
+
|
|
17
|
+
function declared(value) {
|
|
18
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function withoutNativeMeta(value) {
|
|
22
|
+
if (Array.isArray(value)) return value.map(withoutNativeMeta);
|
|
23
|
+
const plain = declared(value);
|
|
24
|
+
if (plain === null) return value;
|
|
25
|
+
return Object.fromEntries(
|
|
26
|
+
Object.entries(plain)
|
|
27
|
+
.filter(([key]) => key !== "_meta")
|
|
28
|
+
.map(([key, nested]) => [key, withoutNativeMeta(nested)]),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function nativeCompanion(declaration) {
|
|
33
|
+
const meta = declared(declaration?._meta);
|
|
34
|
+
if (meta === null) return null;
|
|
35
|
+
const custom = declared(meta[NATIVE_CUSTOM_ANSWER_META]);
|
|
36
|
+
if (typeof custom?.questionId === "string") {
|
|
37
|
+
return { question: custom.questionId, other: null };
|
|
38
|
+
}
|
|
39
|
+
const codex = declared(meta[NATIVE_CODEX_META]);
|
|
40
|
+
if (codex?.role === NATIVE_USER_NOTE_ROLE && typeof codex.questionId === "string") {
|
|
41
|
+
return { question: codex.questionId, other: NATIVE_OTHER_OPTION };
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function nativeSecret(declaration) {
|
|
47
|
+
return declared(declaration?._meta)?.[NATIVE_CODEX_META]?.isSecret === true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function choiceHolder(declaration) {
|
|
51
|
+
return declaration?.type === "array" ? declared(declaration.items) : declared(declaration);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function choiceValues(declaration) {
|
|
55
|
+
const holder = choiceHolder(declaration);
|
|
56
|
+
const titled = holder?.oneOf ?? holder?.anyOf;
|
|
57
|
+
if (Array.isArray(titled)) {
|
|
58
|
+
return titled.map((option) => option?.const).filter((value) => typeof value === "string");
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(holder?.enum)) {
|
|
61
|
+
return holder.enum.filter((value) => typeof value === "string");
|
|
62
|
+
}
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function withoutChoice(holder, dropped) {
|
|
67
|
+
if (Array.isArray(holder.oneOf)) {
|
|
68
|
+
return { ...holder, oneOf: holder.oneOf.filter((option) => option?.const !== dropped) };
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(holder.anyOf)) {
|
|
71
|
+
return { ...holder, anyOf: holder.anyOf.filter((option) => option?.const !== dropped) };
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(holder.enum)) {
|
|
74
|
+
return { ...holder, enum: holder.enum.filter((value) => value !== dropped) };
|
|
75
|
+
}
|
|
76
|
+
return holder;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function withoutOtherChoice(declaration, dropped) {
|
|
80
|
+
const holder = choiceHolder(declaration);
|
|
81
|
+
if (dropped === null || holder === null) return declaration;
|
|
82
|
+
if (declaration.type === "array") {
|
|
83
|
+
return { ...declaration, items: withoutChoice(holder, dropped) };
|
|
84
|
+
}
|
|
85
|
+
return withoutChoice(holder, dropped);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function forwarded(declaration, name, marked) {
|
|
89
|
+
const plain = declared(declaration);
|
|
90
|
+
if (plain === null) return declaration;
|
|
91
|
+
const titled =
|
|
92
|
+
typeof plain.title === "string" && plain.title !== "" ? plain : { ...plain, title: name };
|
|
93
|
+
return marked ? { ...titled, _meta: { [ELICITATION_FREE_TEXT_META]: true } } : titled;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function foldedConventions(properties) {
|
|
97
|
+
const companions = new Map();
|
|
98
|
+
const conventions = new Map();
|
|
99
|
+
for (const [name, declaration] of Object.entries(properties)) {
|
|
100
|
+
const companion = nativeCompanion(declaration);
|
|
101
|
+
if (companion === null || !Object.hasOwn(properties, companion.question)) continue;
|
|
102
|
+
companions.set(name, companion.question);
|
|
103
|
+
conventions.set(companion.question, {
|
|
104
|
+
companion: name,
|
|
105
|
+
other: companion.other,
|
|
106
|
+
options: choiceValues(withoutOtherChoice(properties[companion.question], companion.other)),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return { companions, conventions };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function bounded(text, limit, fallback) {
|
|
113
|
+
const said = typeof text === "string" ? text.trim() : "";
|
|
114
|
+
return said === "" ? fallback : said.slice(0, limit);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const VISIT_LABEL = "Open the provider flow";
|
|
118
|
+
const COLLECT_LABEL = "Paste what it gives you";
|
|
119
|
+
|
|
120
|
+
export function elicitedVisit(params, correlation) {
|
|
121
|
+
if (params?.mode !== ELICITATION_URL_MODE || typeof params.url !== "string") return null;
|
|
122
|
+
let opened;
|
|
123
|
+
try {
|
|
124
|
+
opened = new URL(params.url);
|
|
125
|
+
} catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (opened.protocol !== "https:") return null;
|
|
129
|
+
return {
|
|
130
|
+
form: {
|
|
131
|
+
correlation: correlation.correlation,
|
|
132
|
+
provider_turn: correlation.providerTurn,
|
|
133
|
+
message: bounded(params.message, SECRET_STEP_TEXT_MAX, VISIT_LABEL),
|
|
134
|
+
requested_schema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
visited: { type: "boolean", title: bounded(params.message, 200, VISIT_LABEL) },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
_meta: { [ELICITATION_SECRET_META]: true },
|
|
141
|
+
},
|
|
142
|
+
ceremony: {
|
|
143
|
+
steps: [
|
|
144
|
+
{
|
|
145
|
+
kind: "visit",
|
|
146
|
+
label: bounded(params.message, SECRET_STEP_LABEL_MAX, VISIT_LABEL),
|
|
147
|
+
url: params.url,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
convention: {},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function secretCeremony(message, properties) {
|
|
156
|
+
const steps = [];
|
|
157
|
+
const shown = bounded(message, SECRET_STEP_TEXT_MAX, "");
|
|
158
|
+
if (shown !== "") steps.push({ kind: "show", label: "What the Agent is asking", text: shown });
|
|
159
|
+
for (const [name, declaration] of Object.entries(properties)) {
|
|
160
|
+
if (steps.length >= SECRET_CEREMONY_STEPS_MAX) break;
|
|
161
|
+
const property = declared(declaration);
|
|
162
|
+
steps.push({
|
|
163
|
+
kind: "collect",
|
|
164
|
+
label: bounded(property?.title, SECRET_STEP_LABEL_MAX, COLLECT_LABEL),
|
|
165
|
+
name,
|
|
166
|
+
...(typeof property?.description === "string" && property.description !== ""
|
|
167
|
+
? { description: property.description.slice(0, SECRET_STEP_TEXT_MAX) }
|
|
168
|
+
: {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return steps.length === 0 ? null : { steps };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function elicitedForm(params, correlation) {
|
|
175
|
+
if (params?.mode !== ELICITATION_FORM_MODE) return null;
|
|
176
|
+
const schema = declared(params.requestedSchema);
|
|
177
|
+
if (schema === null) return null;
|
|
178
|
+
const properties = declared(schema.properties) ?? {};
|
|
179
|
+
const { companions, conventions } = foldedConventions(properties);
|
|
180
|
+
const carried = {};
|
|
181
|
+
for (const [name, declaration] of Object.entries(properties)) {
|
|
182
|
+
if (companions.has(name)) continue;
|
|
183
|
+
const convention = conventions.get(name);
|
|
184
|
+
const listed =
|
|
185
|
+
convention === undefined ? declaration : withoutOtherChoice(declaration, convention.other);
|
|
186
|
+
carried[name] = forwarded(withoutNativeMeta(listed), name, convention !== undefined);
|
|
187
|
+
}
|
|
188
|
+
const { _meta: _schemaMeta, properties: _properties, required, ...rest } = schema;
|
|
189
|
+
const secret = Object.values(properties).some(nativeSecret);
|
|
190
|
+
const ceremony = secret ? secretCeremony(params.message, carried) : null;
|
|
191
|
+
if (secret && ceremony === null) return null;
|
|
192
|
+
return {
|
|
193
|
+
...(ceremony === null ? {} : { ceremony }),
|
|
194
|
+
form: {
|
|
195
|
+
correlation: correlation.correlation,
|
|
196
|
+
provider_turn: correlation.providerTurn,
|
|
197
|
+
message: typeof params.message === "string" ? params.message : "",
|
|
198
|
+
requested_schema: {
|
|
199
|
+
...withoutNativeMeta(rest),
|
|
200
|
+
properties: carried,
|
|
201
|
+
...(Array.isArray(required)
|
|
202
|
+
? { required: required.filter((name) => !companions.has(name)) }
|
|
203
|
+
: {}),
|
|
204
|
+
},
|
|
205
|
+
...(secret ? { _meta: { [ELICITATION_SECRET_META]: true } } : {}),
|
|
206
|
+
},
|
|
207
|
+
convention: Object.fromEntries(conventions),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function nativeAnswer(content, name, convention, value) {
|
|
212
|
+
if (Array.isArray(value)) {
|
|
213
|
+
content[name] = value.filter((item) => convention.options.includes(item));
|
|
214
|
+
const typed = value.filter((item) => !convention.options.includes(item));
|
|
215
|
+
if (typed.length > 0) content[convention.companion] = typed.join(", ");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (typeof value !== "string" || convention.options.includes(value)) {
|
|
219
|
+
content[name] = value;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
content[convention.companion] = value;
|
|
223
|
+
if (convention.other !== null) content[name] = convention.other;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function elicitationResponse(convention, response) {
|
|
227
|
+
if (response?.action !== "accept") return { action: "decline" };
|
|
228
|
+
const content = {};
|
|
229
|
+
for (const [name, value] of Object.entries(response.content)) {
|
|
230
|
+
const native = convention[name];
|
|
231
|
+
if (native === undefined) content[name] = value;
|
|
232
|
+
else nativeAnswer(content, name, native, value);
|
|
233
|
+
}
|
|
234
|
+
return { action: "accept", content };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function grokOption(option) {
|
|
238
|
+
if (typeof option?.label !== "string" || option.label.length === 0) return null;
|
|
239
|
+
return {
|
|
240
|
+
const: option.label,
|
|
241
|
+
title: option.label,
|
|
242
|
+
...(typeof option.description === "string" && option.description.length > 0
|
|
243
|
+
? { description: option.description }
|
|
244
|
+
: {}),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function grokForm(params, correlation) {
|
|
249
|
+
if (params?.mode !== "default" && params?.mode !== "plan") return null;
|
|
250
|
+
if (
|
|
251
|
+
!Array.isArray(params.questions) ||
|
|
252
|
+
params.questions.length === 0 ||
|
|
253
|
+
params.questions.length > INTERACTION_PROPERTY_MAX
|
|
254
|
+
) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
const properties = {};
|
|
258
|
+
const questions = {};
|
|
259
|
+
const named = new Set();
|
|
260
|
+
for (const [position, question] of params.questions.entries()) {
|
|
261
|
+
if (
|
|
262
|
+
typeof question?.question !== "string" ||
|
|
263
|
+
question.question.length === 0 ||
|
|
264
|
+
named.has(question.question) ||
|
|
265
|
+
!Array.isArray(question.options) ||
|
|
266
|
+
question.options.length > INTERACTION_OPTIONS_MAX
|
|
267
|
+
) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const options = question.options.map(grokOption);
|
|
271
|
+
if (options.some((option) => option === null)) return null;
|
|
272
|
+
const labels = options.map((option) => option.const);
|
|
273
|
+
if (new Set(labels).size !== labels.length) return null;
|
|
274
|
+
const name = `question_${position}`;
|
|
275
|
+
const choices = options.length === 0 ? {} : { oneOf: options };
|
|
276
|
+
properties[name] =
|
|
277
|
+
question.multiSelect === true
|
|
278
|
+
? {
|
|
279
|
+
type: "array",
|
|
280
|
+
title: question.question,
|
|
281
|
+
items: { type: "string", ...choices },
|
|
282
|
+
uniqueItems: true,
|
|
283
|
+
}
|
|
284
|
+
: { type: "string", title: question.question, ...choices };
|
|
285
|
+
questions[name] = question.question;
|
|
286
|
+
named.add(question.question);
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
form: {
|
|
290
|
+
correlation: correlation.correlation,
|
|
291
|
+
provider_turn: correlation.providerTurn,
|
|
292
|
+
message: `Grok Build needs your input before it continues${params.mode === "plan" ? " its plan" : ""}.`,
|
|
293
|
+
requested_schema: {
|
|
294
|
+
type: "object",
|
|
295
|
+
properties,
|
|
296
|
+
required: Object.keys(properties),
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
convention: questions,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function grokResponse(convention, response) {
|
|
304
|
+
if (response?.action !== "accept") return { outcome: "cancelled" };
|
|
305
|
+
const answers = {};
|
|
306
|
+
for (const [name, question] of Object.entries(convention)) {
|
|
307
|
+
const value = response.content?.[name];
|
|
308
|
+
if (typeof value === "string") answers[question] = [value];
|
|
309
|
+
else if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
|
310
|
+
answers[question] = value;
|
|
311
|
+
} else {
|
|
312
|
+
return { outcome: "cancelled" };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { outcome: "accepted", answers };
|
|
316
|
+
}
|
package/src/refusal.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export class KitRefusal extends Error {
|
|
2
|
+
constructor(code, detail = null) {
|
|
3
|
+
super(detail === null ? code : `${code}: ${detail}`);
|
|
4
|
+
this.code = code;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class EpochReportRequired extends Error {
|
|
9
|
+
constructor(epoch) {
|
|
10
|
+
super(`epoch_report_required: ${epoch}`);
|
|
11
|
+
this.code = "epoch_report_required";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class HousePending extends Error {
|
|
16
|
+
constructor(pending) {
|
|
17
|
+
super("pending");
|
|
18
|
+
this.pending = pending;
|
|
19
|
+
this.retryAfterSeconds = pending.retry_after;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function typedCode(answer) {
|
|
24
|
+
const code = answer?.error?.code;
|
|
25
|
+
return typeof code === "string" && code !== "" ? code : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function houseRefusal(status, answer, path = null) {
|
|
29
|
+
const code = typedCode(answer);
|
|
30
|
+
if (status >= 500 && code === null) return new KitRefusal("kit_house_unavailable", path);
|
|
31
|
+
if (status === 409 && code === "epoch_report_required") {
|
|
32
|
+
return new EpochReportRequired(answer.error.log_epoch);
|
|
33
|
+
}
|
|
34
|
+
const refusal = new KitRefusal(code ?? `house_http_${status}`, answer?.error?.declaration ?? null);
|
|
35
|
+
refusal.status = status;
|
|
36
|
+
refusal.payload = answer;
|
|
37
|
+
return refusal;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function housePending(answer) {
|
|
41
|
+
const pending = answer?.pending;
|
|
42
|
+
if (typeof pending !== "object" || pending === null || Array.isArray(pending)) return null;
|
|
43
|
+
if (!Number.isSafeInteger(pending.retry_after) || pending.retry_after < 1) return null;
|
|
44
|
+
return new HousePending(pending);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function throwHouseAnswer(status, answer, path = null) {
|
|
48
|
+
if (status === 202) {
|
|
49
|
+
const pending = housePending(answer);
|
|
50
|
+
if (pending !== null) throw pending;
|
|
51
|
+
throw new KitRefusal("kit_house_unavailable", path);
|
|
52
|
+
}
|
|
53
|
+
if (status < 200 || status >= 300) throw houseRefusal(status, answer, path);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function houseAnswered(error) {
|
|
57
|
+
return error instanceof KitRefusal && error.status !== undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function houseAnsweredFailure(error) {
|
|
61
|
+
return houseAnswered(error) && error.status >= 500;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function houseFailed(error) {
|
|
65
|
+
return (
|
|
66
|
+
error instanceof HousePending ||
|
|
67
|
+
(houseAnsweredFailure(error) && error.payload.error.retryable === true) ||
|
|
68
|
+
(error instanceof KitRefusal && error.code === "kit_house_unavailable")
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function formatCommandError(error) {
|
|
73
|
+
if (!(error instanceof Error)) return "kit_command_failed";
|
|
74
|
+
if (error instanceof HousePending) return JSON.stringify({ pending: error.pending });
|
|
75
|
+
if (error instanceof KitRefusal && error.payload !== undefined) {
|
|
76
|
+
return `${error.message}\n${JSON.stringify(error.payload)}`;
|
|
77
|
+
}
|
|
78
|
+
return error.message;
|
|
79
|
+
}
|