@ccmsg/cli 0.3.5 → 0.4.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.
@@ -0,0 +1,231 @@
1
+ import { bool, count, lines, list, optional, row, str } from "./record.ts";
2
+
3
+ /** What each tool's call and result are read as.
4
+ *
5
+ * The contract names fields for the tools worth reading closely and leaves
6
+ * every other tool to a generic shape. Fields sharpen how a tool reads; they
7
+ * never decide whether it is kept, so a tool absent from this table still
8
+ * arrives carrying what it was called with and what it answered.
9
+ *
10
+ * A reader returns nothing when the record does not hold what the contract
11
+ * requires of that shape — a `Bash` call with no command is not a `Bash` call
12
+ * as the contract spells one — and the generic shape answers instead. That
13
+ * keeps a malformed line from producing an item nothing can validate, without
14
+ * losing the line. */
15
+ type Reader = (source: Record<string, unknown>) => Record<string, unknown> | undefined;
16
+
17
+ /** The call's own arguments, as the harness recorded them. */
18
+ const USE: Record<string, Reader> = {
19
+ Bash: (input) => {
20
+ const command = str(input["command"]);
21
+ return command === undefined
22
+ ? undefined
23
+ : { command, ...optional("description", str(input["description"])) };
24
+ },
25
+ Read: (input) => {
26
+ const file_path = str(input["file_path"]);
27
+ return file_path === undefined
28
+ ? undefined
29
+ : {
30
+ file_path,
31
+ ...optional("offset", count(input["offset"])),
32
+ ...optional("limit", count(input["limit"])),
33
+ };
34
+ },
35
+ /** Bodies are counted, never carried: an edit's two sides are the file's
36
+ * content, and a dump that inlined them would be the file. */
37
+ Write: (input) => {
38
+ const file_path = str(input["file_path"]);
39
+ return file_path === undefined
40
+ ? undefined
41
+ : { file_path, ...optional("lines", lines(str(input["content"]))) };
42
+ },
43
+ Edit: (input) => {
44
+ const file_path = str(input["file_path"]);
45
+ return file_path === undefined
46
+ ? undefined
47
+ : {
48
+ file_path,
49
+ ...optional("old_lines", lines(str(input["old_string"]))),
50
+ ...optional("new_lines", lines(str(input["new_string"]))),
51
+ };
52
+ },
53
+ Grep: pattern,
54
+ Glob: pattern,
55
+ WebFetch: (input) => {
56
+ const url = str(input["url"]);
57
+ return url === undefined ? undefined : { url, ...optional("prompt", str(input["prompt"])) };
58
+ },
59
+ WebSearch: (input) => {
60
+ const query = str(input["query"]);
61
+ return query === undefined ? undefined : { query };
62
+ },
63
+ Agent: (input) => {
64
+ const prompt = str(input["prompt"]);
65
+ return prompt === undefined
66
+ ? undefined
67
+ : {
68
+ prompt,
69
+ ...optional("name", str(input["name"])),
70
+ ...optional("subagent_type", str(input["subagent_type"])),
71
+ ...optional("description", str(input["description"])),
72
+ };
73
+ },
74
+ SendMessage: (input) => {
75
+ const to = str(input["to"]);
76
+ return to === undefined ? undefined : { to, ...optional("summary", str(input["summary"])) };
77
+ },
78
+ Monitor: (input) => {
79
+ const description = str(input["description"]);
80
+ return description === undefined
81
+ ? undefined
82
+ : {
83
+ description,
84
+ ...optional("command", str(input["command"])),
85
+ ...optional("persistent", bool(input["persistent"])),
86
+ ...optional("timeout_ms", count(input["timeout_ms"])),
87
+ };
88
+ },
89
+ Skill: (input) => {
90
+ const skill = str(input["skill"]);
91
+ return skill === undefined ? undefined : { skill, ...optional("args", str(input["args"])) };
92
+ },
93
+ TodoWrite: (input) => {
94
+ const todos = list(input["todos"]).flatMap((entry) => {
95
+ const each = row(entry);
96
+ const content = each === undefined ? undefined : str(each["content"]);
97
+ const status = each === undefined ? undefined : str(each["status"]);
98
+ return content === undefined || status === undefined ? [] : [{ content, status }];
99
+ });
100
+ return { todos };
101
+ },
102
+ TaskStop: (input) => {
103
+ const task_id = str(input["task_id"]) ?? str(input["shell_id"]);
104
+ return task_id === undefined ? undefined : { task_id };
105
+ },
106
+ CronCreate: (input) => {
107
+ const cron = str(input["cron"]);
108
+ return cron === undefined ? undefined : { cron, ...optional("prompt", str(input["prompt"])) };
109
+ },
110
+ };
111
+
112
+ function pattern(input: Record<string, unknown>): Record<string, unknown> | undefined {
113
+ const found = str(input["pattern"]);
114
+ return found === undefined
115
+ ? undefined
116
+ : { pattern: found, ...optional("path", str(input["path"])) };
117
+ }
118
+
119
+ /** What came back.
120
+ *
121
+ * The harness writes a tool's answer to `toolUseResult` in whatever shape that
122
+ * tool settled on, so each reader knows one shape. `ok` stands where a tool
123
+ * says nothing but whether it worked, and the block's own error flag is what
124
+ * decides it. */
125
+ const RESULT: Record<string, (result: unknown, failed: boolean) => Record<string, unknown>> = {
126
+ Bash: (result) => {
127
+ const fields = row(result);
128
+ if (fields === undefined) return { ...optional("stdout", str(result)) };
129
+ return {
130
+ ...optional("stdout", str(fields["stdout"])),
131
+ ...optional("stderr", str(fields["stderr"])),
132
+ ...optional("interrupted", bool(fields["interrupted"])),
133
+ };
134
+ },
135
+ Read: (result) => {
136
+ const fields = row(result);
137
+ const file = fields === undefined ? undefined : row(fields["file"]);
138
+ const content = file === undefined ? undefined : str(file["content"]);
139
+ return {
140
+ ...optional(
141
+ "lines",
142
+ file === undefined ? undefined : (count(file["numLines"]) ?? lines(content)),
143
+ ),
144
+ ...optional("bytes", content === undefined ? undefined : Buffer.byteLength(content)),
145
+ };
146
+ },
147
+ Write: (_result, failed) => ({ ok: !failed }),
148
+ Edit: (_result, failed) => ({ ok: !failed }),
149
+ Grep: matches,
150
+ Glob: matches,
151
+ WebFetch: (result) => {
152
+ const fields = row(result);
153
+ return {
154
+ ...optional(
155
+ "text",
156
+ str(result) ?? (fields === undefined ? undefined : str(fields["result"])),
157
+ ),
158
+ };
159
+ },
160
+ WebSearch: (result) => {
161
+ const fields = row(result);
162
+ if (fields === undefined || !Array.isArray(fields["results"])) return {};
163
+ return { results: fields["results"].length };
164
+ },
165
+ Agent: (result) => {
166
+ const fields = row(result) ?? {};
167
+ return {
168
+ ...optional("agent_id", str(fields["agentId"]) ?? str(fields["agent_id"])),
169
+ ...optional("status", str(fields["status"])),
170
+ };
171
+ },
172
+ SendMessage: (result) => {
173
+ const fields = row(result) ?? {};
174
+ return {
175
+ ...optional("msg_id", str(fields["msg_id"])),
176
+ ...optional("routing", str(fields["routing"])),
177
+ };
178
+ },
179
+ Monitor: (result) => {
180
+ const fields = row(result) ?? {};
181
+ return { ...optional("task_id", str(fields["taskId"]) ?? str(fields["task_id"])) };
182
+ },
183
+ Skill: (result) => {
184
+ const fields = row(result) ?? {};
185
+ return {
186
+ ...optional("agent_id", str(fields["agentId"]) ?? str(fields["agent_id"])),
187
+ ...optional("background", bool(fields["background"])),
188
+ ...optional("status", str(fields["status"])),
189
+ };
190
+ },
191
+ TodoWrite: (_result, failed) => ({ ok: !failed }),
192
+ TaskStop: (_result, failed) => ({ ok: !failed }),
193
+ CronCreate: (result) => {
194
+ const fields = row(result) ?? {};
195
+ return { ...optional("cron_id", str(fields["cron_id"]) ?? str(fields["id"])) };
196
+ },
197
+ };
198
+
199
+ function matches(result: unknown): Record<string, unknown> {
200
+ const fields = row(result);
201
+ if (fields === undefined) return {};
202
+ const found = count(fields["numFiles"]) ?? count(fields["numLines"]);
203
+ return { ...optional("matches", found) };
204
+ }
205
+
206
+ /** The fields of one tool call, or nothing where the generic shape should
207
+ * answer for it. */
208
+ export function useFields(name: string, input: unknown): Record<string, unknown> | undefined {
209
+ const reader = USE[name];
210
+ const fields = row(input) ?? {};
211
+ return reader === undefined ? undefined : reader(fields);
212
+ }
213
+
214
+ export function resultFields(
215
+ name: string | undefined,
216
+ result: unknown,
217
+ failed: boolean,
218
+ ): Record<string, unknown> | undefined {
219
+ const reader = name === undefined ? undefined : RESULT[name];
220
+ return reader === undefined ? undefined : reader(result, failed);
221
+ }
222
+
223
+ /** Whatever a tool answered, for a tool nothing knows the shape of. A string
224
+ * answer becomes one field rather than being dropped for not being an
225
+ * object. */
226
+ export function genericResult(result: unknown): Record<string, unknown> {
227
+ const fields = row(result);
228
+ if (fields !== undefined) return fields;
229
+ const text = str(result);
230
+ return text === undefined ? {} : { text };
231
+ }