@hunsu/bridge 0.1.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,337 @@
1
+ import { err, makeNonEmptyText, makeNonNegativeInteger, ok } from "@hunsu/protocol";
2
+ import { executeError } from "./execute-model.js";
3
+ export function parseExecutionPlan(finalResponse) {
4
+ const parsed = parseJsonObject(finalResponse, "Team planning final response must be a JSON ExecutionPlan object");
5
+ if (!parsed.ok) {
6
+ return parsed;
7
+ }
8
+ return decodeExecutionPlan(parsed.value, "execution");
9
+ }
10
+ export function parseGoalEvaluation(finalResponse) {
11
+ const parsed = parseJsonObject(finalResponse, "Goal evaluator final response must be a JSON object");
12
+ if (!parsed.ok) {
13
+ return parsed;
14
+ }
15
+ const record = parsed.value;
16
+ if (record.type === "pass") {
17
+ if (typeof record.summary !== "string" || record.summary.trim() === "") {
18
+ return err(executeError("goal_evaluation_invalid", "pass evaluation must include a non-empty summary"));
19
+ }
20
+ const evidence = decodeOptionalStringArray(record.evidence, "evidence");
21
+ if (!evidence.ok) {
22
+ return evidence;
23
+ }
24
+ return ok({
25
+ type: "pass",
26
+ summary: record.summary,
27
+ evidence: evidence.value
28
+ });
29
+ }
30
+ if (record.type === "fail") {
31
+ if (typeof record.reason !== "string" || record.reason.trim() === "") {
32
+ return err(executeError("goal_evaluation_invalid", "fail evaluation must include a non-empty reason"));
33
+ }
34
+ if (typeof record.feedback !== "string" || record.feedback.trim() === "") {
35
+ return err(executeError("goal_evaluation_invalid", "fail evaluation must include non-empty feedback"));
36
+ }
37
+ if (record.nextGoal !== undefined && typeof record.nextGoal !== "string") {
38
+ return err(executeError("goal_evaluation_invalid", "fail evaluation nextGoal must be a string when present"));
39
+ }
40
+ const evidence = decodeOptionalStringArray(record.evidence, "evidence");
41
+ if (!evidence.ok) {
42
+ return evidence;
43
+ }
44
+ return ok({
45
+ type: "fail",
46
+ reason: record.reason,
47
+ feedback: record.feedback,
48
+ nextGoal: typeof record.nextGoal === "string" && record.nextGoal.trim() !== "" ? record.nextGoal : undefined,
49
+ evidence: evidence.value
50
+ });
51
+ }
52
+ return err(executeError("goal_evaluation_invalid", "Goal evaluator response type must be pass or fail"));
53
+ }
54
+ export function memberPathForGoalRole(execution, role, pathId, requires) {
55
+ if (role === "assignee") {
56
+ return {
57
+ id: pathId,
58
+ executorId: execution.assignee.executorId,
59
+ goal: execution.assignee.goal,
60
+ requires
61
+ };
62
+ }
63
+ const roleConfig = execution.evaluator;
64
+ if (!roleConfig) {
65
+ throw new Error(`Goal ${execution.id} does not define an evaluator`);
66
+ }
67
+ return {
68
+ id: pathId,
69
+ executorId: roleConfig.executorId,
70
+ goal: roleConfig.prompt,
71
+ requires
72
+ };
73
+ }
74
+ export function nextQueueExecution(queue, headResult) {
75
+ const [, ...tail] = queue.items;
76
+ if (headResult) {
77
+ return { ...queue, items: [headResult, ...tail] };
78
+ }
79
+ if (tail.length === 0) {
80
+ return undefined;
81
+ }
82
+ return { ...queue, items: tail };
83
+ }
84
+ function decodeExecutionPlan(value, path) {
85
+ if (!isRecord(value)) {
86
+ return err(executeError("execution_plan_invalid", `${path} must be an object`));
87
+ }
88
+ if (value.kind === "queue") {
89
+ return decodeQueueExecutionPlan(value, path);
90
+ }
91
+ if (value.kind === "goal") {
92
+ return decodeGoalExecutionPlan(value, path);
93
+ }
94
+ if (value.kind === "continuation") {
95
+ return decodeExecutionContinuation(value, path);
96
+ }
97
+ return err(executeError("execution_plan_invalid", `${path}.kind must be queue, goal, or continuation`));
98
+ }
99
+ function decodeQueueExecutionPlan(record, path) {
100
+ const id = decodeNonEmptyString(record.id, `${path}.id`);
101
+ if (!id.ok) {
102
+ return id;
103
+ }
104
+ if (!Array.isArray(record.items)) {
105
+ return err(executeError("execution_plan_invalid", `${path}.items must be an array`));
106
+ }
107
+ const items = [];
108
+ for (const [index, item] of record.items.entries()) {
109
+ const decoded = decodeExecutionPlan(item, `${path}.items[${index}]`);
110
+ if (!decoded.ok) {
111
+ return decoded;
112
+ }
113
+ items.push(decoded.value);
114
+ }
115
+ return ok({ kind: "queue", id: id.value, items });
116
+ }
117
+ function decodeGoalExecutionPlan(record, path) {
118
+ if (record.stage === "needs_evaluation") {
119
+ return decodeGoalPathEvaluationStage(record, path);
120
+ }
121
+ if (record.stage === "needs_execution") {
122
+ return decodeGoalExecutionPlanStage(record, path);
123
+ }
124
+ return err(executeError("execution_plan_invalid", `${path}.stage must be needs_evaluation or needs_execution`));
125
+ }
126
+ function decodeExecutionContinuation(record, path) {
127
+ const id = decodePathId(record.id, `${path}.id`);
128
+ if (!id.ok) {
129
+ return id;
130
+ }
131
+ const teamScopeId = decodeMemberId(record.teamScopeId, `${path}.teamScopeId`);
132
+ if (!teamScopeId.ok) {
133
+ return teamScopeId;
134
+ }
135
+ const execution = decodeExecutionPlan(record.execution, `${path}.execution`);
136
+ if (!execution.ok) {
137
+ return execution;
138
+ }
139
+ const continuation = record.continuation === undefined
140
+ ? ok(undefined)
141
+ : isRecord(record.continuation)
142
+ ? decodeGoalExecutionPlan(record.continuation, `${path}.continuation`)
143
+ : err(executeError("execution_plan_invalid", `${path}.continuation must be an object`));
144
+ if (!continuation.ok) {
145
+ return continuation;
146
+ }
147
+ return ok({
148
+ kind: "continuation",
149
+ id: id.value,
150
+ teamScopeId: teamScopeId.value,
151
+ execution: execution.value,
152
+ continuation: continuation.value
153
+ });
154
+ }
155
+ function decodeGoalPathEvaluationStage(record, path) {
156
+ const id = decodePathId(record.id, `${path}.id`);
157
+ if (!id.ok) {
158
+ return id;
159
+ }
160
+ const assignee = decodeGoalRole(record.assignee, `${path}.assignee`, "goal");
161
+ if (!assignee.ok) {
162
+ return assignee;
163
+ }
164
+ const evaluator = record.evaluator === undefined || record.evaluator === null ? ok(undefined) : decodeGoalRole(record.evaluator, `${path}.evaluator`, "prompt");
165
+ if (!evaluator.ok) {
166
+ return evaluator;
167
+ }
168
+ const remainingAttempts = makeNonNegativeInteger(record.remainingAttempts, `${path}.remainingAttempts`);
169
+ if (!remainingAttempts.ok) {
170
+ return err(executeError("execution_plan_invalid", remainingAttempts.error.message));
171
+ }
172
+ const requires = decodePathRequires(record.requires, `${path}.requires`);
173
+ if (!requires.ok) {
174
+ return requires;
175
+ }
176
+ return ok({
177
+ kind: "goal",
178
+ stage: "needs_evaluation",
179
+ id: id.value,
180
+ assignee: {
181
+ executorId: assignee.value.executorId,
182
+ goal: assignee.value.text
183
+ },
184
+ evaluator: evaluator.value
185
+ ? {
186
+ executorId: evaluator.value.executorId,
187
+ prompt: evaluator.value.text
188
+ }
189
+ : undefined,
190
+ remainingAttempts: remainingAttempts.value,
191
+ requires: requires.value
192
+ });
193
+ }
194
+ function decodeGoalExecutionPlanStage(record, path) {
195
+ const decoded = decodeGoalPathEvaluationStage({ ...record, stage: "needs_evaluation" }, path);
196
+ if (!decoded.ok) {
197
+ return decoded;
198
+ }
199
+ const evaluationPathId = decodePathId(record.evaluationPathId, `${path}.evaluationPathId`);
200
+ if (!evaluationPathId.ok) {
201
+ return evaluationPathId;
202
+ }
203
+ const evaluation = decodeFailGoalEvaluation(record.evaluation, `${path}.evaluation`);
204
+ if (!evaluation.ok) {
205
+ return evaluation;
206
+ }
207
+ if (!Array.isArray(record.requires)) {
208
+ return err(executeError("execution_plan_invalid", `${path}.requires must be an array for needs_execution`));
209
+ }
210
+ const requires = decodePathIdArray(record.requires, `${path}.requires`);
211
+ if (!requires.ok) {
212
+ return requires;
213
+ }
214
+ if (requires.value.length !== 1 || requires.value[0] !== evaluationPathId.value) {
215
+ return err(executeError("execution_plan_invalid", `${path}.requires must contain only ${path}.evaluationPathId`));
216
+ }
217
+ return ok({
218
+ ...decoded.value,
219
+ stage: "needs_execution",
220
+ evaluationPathId: evaluationPathId.value,
221
+ evaluation: evaluation.value,
222
+ requires: requires.value
223
+ });
224
+ }
225
+ function decodeGoalRole(value, path, textField) {
226
+ if (!isRecord(value)) {
227
+ return err(executeError("execution_plan_invalid", `${path} must be an object`));
228
+ }
229
+ const executorId = decodeMemberId(value.executorId, `${path}.executorId`);
230
+ if (!executorId.ok) {
231
+ return executorId;
232
+ }
233
+ const text = decodeNonEmptyString(value[textField], `${path}.${textField}`);
234
+ if (!text.ok) {
235
+ return text;
236
+ }
237
+ return ok({ executorId: executorId.value, text: text.value });
238
+ }
239
+ function decodeFailGoalEvaluation(value, path) {
240
+ if (!isRecord(value)) {
241
+ return err(executeError("execution_plan_invalid", `${path} must be an object`));
242
+ }
243
+ if (value.type !== "fail") {
244
+ return err(executeError("execution_plan_invalid", `${path}.type must be fail`));
245
+ }
246
+ if (typeof value.reason !== "string" || value.reason.trim() === "") {
247
+ return err(executeError("execution_plan_invalid", `${path}.reason must be a non-empty string`));
248
+ }
249
+ if (typeof value.feedback !== "string" || value.feedback.trim() === "") {
250
+ return err(executeError("execution_plan_invalid", `${path}.feedback must be a non-empty string`));
251
+ }
252
+ if (value.nextGoal !== undefined && typeof value.nextGoal !== "string") {
253
+ return err(executeError("execution_plan_invalid", `${path}.nextGoal must be a string when present`));
254
+ }
255
+ const evidence = decodeOptionalStringArray(value.evidence, `${path}.evidence`);
256
+ if (!evidence.ok) {
257
+ return evidence;
258
+ }
259
+ return ok({
260
+ type: "fail",
261
+ reason: value.reason,
262
+ feedback: value.feedback,
263
+ nextGoal: typeof value.nextGoal === "string" && value.nextGoal.trim() !== "" ? value.nextGoal : undefined,
264
+ evidence: evidence.value
265
+ });
266
+ }
267
+ function decodeNonEmptyString(value, path) {
268
+ if (typeof value !== "string" || value.trim() === "") {
269
+ return err(executeError("execution_plan_invalid", `${path} must be a non-empty string`));
270
+ }
271
+ return ok(value);
272
+ }
273
+ function decodePathId(value, path) {
274
+ const decoded = makeNonEmptyText(value, path);
275
+ if (!decoded.ok) {
276
+ return err(executeError("execution_plan_invalid", decoded.error.message));
277
+ }
278
+ return ok(decoded.value);
279
+ }
280
+ function decodeMemberId(value, path) {
281
+ const decoded = makeNonEmptyText(value, path);
282
+ if (!decoded.ok) {
283
+ return err(executeError("execution_plan_invalid", decoded.error.message));
284
+ }
285
+ return ok(decoded.value);
286
+ }
287
+ function decodePathRequires(value, path) {
288
+ if (value === "PrevMove") {
289
+ return ok("PrevMove");
290
+ }
291
+ return decodePathIdArray(value, path);
292
+ }
293
+ function decodePathIdArray(value, path) {
294
+ const decoded = decodeStringArray(value, path, "execution_plan_invalid");
295
+ if (!decoded.ok) {
296
+ return decoded;
297
+ }
298
+ const pathIds = [];
299
+ for (const [index, item] of decoded.value.entries()) {
300
+ const pathId = makeNonEmptyText(item, `${path}[${index}]`);
301
+ if (!pathId.ok) {
302
+ return err(executeError("execution_plan_invalid", pathId.error.message));
303
+ }
304
+ pathIds.push(pathId.value);
305
+ }
306
+ return ok(pathIds);
307
+ }
308
+ function decodeOptionalStringArray(value, path) {
309
+ if (value === undefined) {
310
+ return ok(undefined);
311
+ }
312
+ return decodeStringArray(value, path, "goal_evaluation_invalid");
313
+ }
314
+ function decodeStringArray(value, path, code) {
315
+ if (!Array.isArray(value)) {
316
+ return err(executeError(code, `${path} must be an array of strings`));
317
+ }
318
+ if (!value.every(item => typeof item === "string")) {
319
+ return err(executeError(code, `${path} must be an array of strings`));
320
+ }
321
+ return ok([...value]);
322
+ }
323
+ function parseJsonObject(text, errorMessage) {
324
+ try {
325
+ const parsed = JSON.parse(text);
326
+ if (!isRecord(parsed)) {
327
+ return err(executeError("execution_plan_invalid", errorMessage));
328
+ }
329
+ return ok(parsed);
330
+ }
331
+ catch (error) {
332
+ return err(executeError("execution_plan_invalid", errorMessage, { cause: error instanceof Error ? error.message : String(error) }));
333
+ }
334
+ }
335
+ function isRecord(value) {
336
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
337
+ }