@testchimp/cli 0.1.57 → 0.1.58
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/dist/chimphands/run.js +83 -26
- package/package.json +1 -1
package/dist/chimphands/run.js
CHANGED
|
@@ -280,6 +280,30 @@ function extractOpencodeFatalError(raw) {
|
|
|
280
280
|
}
|
|
281
281
|
return null;
|
|
282
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* OpenCode `message.part.delta` always sends `field: "text"` for both reasoning and
|
|
285
|
+
* answer parts. Resolve kind from partID → type learned via `message.part.updated`.
|
|
286
|
+
*/
|
|
287
|
+
function resolveDeltaPartKind(partId, field, partType, partTypeById) {
|
|
288
|
+
if (partType) {
|
|
289
|
+
partTypeById.set(partId, partType);
|
|
290
|
+
}
|
|
291
|
+
const known = partTypeById.get(partId);
|
|
292
|
+
if (known === "reasoning" || partType === "reasoning") {
|
|
293
|
+
partTypeById.set(partId, "reasoning");
|
|
294
|
+
return "reasoning";
|
|
295
|
+
}
|
|
296
|
+
if (known === "text" || partType === "text") {
|
|
297
|
+
partTypeById.set(partId, "text");
|
|
298
|
+
return "text";
|
|
299
|
+
}
|
|
300
|
+
// Unknown part: field is unreliable (reasoning deltas also use field "text").
|
|
301
|
+
if (field === "reasoning") {
|
|
302
|
+
partTypeById.set(partId, "reasoning");
|
|
303
|
+
return "reasoning";
|
|
304
|
+
}
|
|
305
|
+
return "text";
|
|
306
|
+
}
|
|
283
307
|
function parseOpencodeEvent(line) {
|
|
284
308
|
try {
|
|
285
309
|
return JSON.parse(line);
|
|
@@ -292,8 +316,11 @@ function parseOpencodeEvent(line) {
|
|
|
292
316
|
* Newer OpenCode `--format json` lines often use the SSE bus shape
|
|
293
317
|
* (`message.part.updated` + `properties.part`) instead of legacy `type: "text"`.
|
|
294
318
|
* Normalize both into the same OpencodeEvent used by the stdout switch.
|
|
319
|
+
*
|
|
320
|
+
* `partTypeById` must be shared across lines: deltas use `field: "text"` for
|
|
321
|
+
* reasoning parts too — type comes from earlier `message.part.updated`.
|
|
295
322
|
*/
|
|
296
|
-
function normalizeStdoutOpencodeEvent(raw) {
|
|
323
|
+
function normalizeStdoutOpencodeEvent(raw, partTypeById) {
|
|
297
324
|
if (!raw || typeof raw !== "object")
|
|
298
325
|
return null;
|
|
299
326
|
const o = raw;
|
|
@@ -312,18 +339,25 @@ function normalizeStdoutOpencodeEvent(raw) {
|
|
|
312
339
|
const deltaProps = props;
|
|
313
340
|
const part = deltaProps.part;
|
|
314
341
|
if (part) {
|
|
315
|
-
const
|
|
342
|
+
const partId = part.id || part.messageID || deltaProps.partID || "";
|
|
343
|
+
const kind = partId
|
|
344
|
+
? resolveDeltaPartKind(partId, deltaProps.field, part.type, partTypeById)
|
|
345
|
+
: part.type === "reasoning"
|
|
346
|
+
? "reasoning"
|
|
347
|
+
: "text";
|
|
316
348
|
let mapped;
|
|
317
|
-
if (
|
|
349
|
+
if (kind === "text" || part.type === "text")
|
|
318
350
|
mapped = "text";
|
|
319
|
-
else if (
|
|
351
|
+
else if (kind === "reasoning" || part.type === "reasoning")
|
|
320
352
|
mapped = "reasoning";
|
|
321
|
-
else if (
|
|
353
|
+
else if (part.type === "tool")
|
|
322
354
|
mapped = "tool_use";
|
|
323
355
|
else
|
|
324
356
|
return null;
|
|
325
357
|
if (deltaProps.delta && !part.text)
|
|
326
358
|
part.text = deltaProps.delta;
|
|
359
|
+
if (mapped === "reasoning")
|
|
360
|
+
part.type = "reasoning";
|
|
327
361
|
return {
|
|
328
362
|
type: mapped,
|
|
329
363
|
sessionID: part.sessionID || deltaProps.sessionID,
|
|
@@ -331,22 +365,29 @@ function normalizeStdoutOpencodeEvent(raw) {
|
|
|
331
365
|
};
|
|
332
366
|
}
|
|
333
367
|
const partID = deltaProps.partID;
|
|
334
|
-
const field = deltaProps.field || "text";
|
|
335
368
|
const delta = deltaProps.delta;
|
|
336
369
|
if (!partID || delta == null || delta === "")
|
|
337
370
|
return null;
|
|
338
|
-
if (field !== "text" && field !== "reasoning")
|
|
371
|
+
if (deltaProps.field && deltaProps.field !== "text" && deltaProps.field !== "reasoning") {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
const kind = resolveDeltaPartKind(partID, deltaProps.field, undefined, partTypeById);
|
|
375
|
+
if (kind !== "text" && kind !== "reasoning")
|
|
339
376
|
return null;
|
|
340
377
|
return {
|
|
341
|
-
type:
|
|
378
|
+
type: kind,
|
|
342
379
|
sessionID: deltaProps.sessionID,
|
|
343
|
-
part: { id: partID, type:
|
|
380
|
+
part: { id: partID, type: kind, text: delta },
|
|
344
381
|
};
|
|
345
382
|
}
|
|
346
383
|
if (type === "message.part.updated") {
|
|
347
384
|
const part = props.part;
|
|
348
385
|
if (!part)
|
|
349
386
|
return null;
|
|
387
|
+
const partId = part.id || part.messageID;
|
|
388
|
+
if (partId && part.type) {
|
|
389
|
+
partTypeById.set(partId, part.type);
|
|
390
|
+
}
|
|
350
391
|
const partType = part.type || "";
|
|
351
392
|
let mapped;
|
|
352
393
|
if (partType === "text")
|
|
@@ -487,6 +528,8 @@ function startOpencodeSseRelay(attachUrl, callbacks) {
|
|
|
487
528
|
const ac = new AbortController();
|
|
488
529
|
let stopped = false;
|
|
489
530
|
const textByPartId = new Map();
|
|
531
|
+
/** partID → "text" | "reasoning" | … from message.part.updated (deltas lie about field). */
|
|
532
|
+
const partTypeById = new Map();
|
|
490
533
|
const directory = process.cwd();
|
|
491
534
|
const sessionMatches = (sessionId) => {
|
|
492
535
|
const active = callbacks.getActiveSessionId();
|
|
@@ -519,18 +562,27 @@ function startOpencodeSseRelay(attachUrl, callbacks) {
|
|
|
519
562
|
const ev = unwrapped;
|
|
520
563
|
const type = ev.type || "";
|
|
521
564
|
const props = ev.properties || {};
|
|
522
|
-
// Token stream: { partID, field, delta } —
|
|
565
|
+
// Token stream: { partID, field, delta } — field is usually "text" even for reasoning.
|
|
523
566
|
if (type === "message.part.delta") {
|
|
524
567
|
const part = props.part;
|
|
525
568
|
const partId = props.partID || part?.id || part?.messageID;
|
|
526
|
-
const field = props.field || part?.type || "text";
|
|
527
569
|
const sessionId = part?.sessionID || props.sessionID;
|
|
528
570
|
if (!sessionMatches(sessionId))
|
|
529
571
|
return;
|
|
530
572
|
callbacks.noteSessionId(sessionId);
|
|
531
573
|
if (!partId || props.delta == null || props.delta === "")
|
|
532
574
|
return;
|
|
533
|
-
|
|
575
|
+
const kind = resolveDeltaPartKind(partId, props.field, part?.type, partTypeById);
|
|
576
|
+
if (kind === "reasoning") {
|
|
577
|
+
const key = `reasoning:${partId}`;
|
|
578
|
+
const next = (textByPartId.get(key) || "") + props.delta;
|
|
579
|
+
textByPartId.set(key, next);
|
|
580
|
+
callbacks.postEvent(ROLE_REASONING, next, liveOpts({
|
|
581
|
+
messageId: `oc_reasoning_${partId}`,
|
|
582
|
+
}));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (kind === "text") {
|
|
534
586
|
const next = (textByPartId.get(partId) || "") + props.delta;
|
|
535
587
|
textByPartId.set(partId, next);
|
|
536
588
|
if (isTextDuplicateOfReasoning(next, reasoningBodiesFromPartMap(textByPartId))) {
|
|
@@ -539,15 +591,6 @@ function startOpencodeSseRelay(attachUrl, callbacks) {
|
|
|
539
591
|
callbacks.postEvent(ROLE_ASSISTANT, next, liveOpts({
|
|
540
592
|
messageId: `oc_text_${partId}`,
|
|
541
593
|
}));
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
if (field === "reasoning") {
|
|
545
|
-
const key = `reasoning:${partId}`;
|
|
546
|
-
const next = (textByPartId.get(key) || "") + props.delta;
|
|
547
|
-
textByPartId.set(key, next);
|
|
548
|
-
callbacks.postEvent(ROLE_REASONING, next, liveOpts({
|
|
549
|
-
messageId: `oc_reasoning_${partId}`,
|
|
550
|
-
}));
|
|
551
594
|
}
|
|
552
595
|
return;
|
|
553
596
|
}
|
|
@@ -559,8 +602,11 @@ function startOpencodeSseRelay(attachUrl, callbacks) {
|
|
|
559
602
|
if (!sessionMatches(sessionId))
|
|
560
603
|
return;
|
|
561
604
|
callbacks.noteSessionId(sessionId);
|
|
605
|
+
const partId = part.id || part.messageID;
|
|
606
|
+
if (partId && part.type) {
|
|
607
|
+
partTypeById.set(partId, part.type);
|
|
608
|
+
}
|
|
562
609
|
if (part.type === "text") {
|
|
563
|
-
const partId = part.id || part.messageID;
|
|
564
610
|
if (!partId)
|
|
565
611
|
return;
|
|
566
612
|
let next = part.text || "";
|
|
@@ -584,12 +630,19 @@ function startOpencodeSseRelay(attachUrl, callbacks) {
|
|
|
584
630
|
return;
|
|
585
631
|
}
|
|
586
632
|
if (part.type === "reasoning") {
|
|
587
|
-
const partId = part.id || part.messageID;
|
|
588
633
|
if (!partId)
|
|
589
634
|
return;
|
|
635
|
+
// Deltas before type was known may have been buffered under the text key.
|
|
636
|
+
const orphanText = textByPartId.get(partId);
|
|
637
|
+
if (orphanText) {
|
|
638
|
+
textByPartId.delete(partId);
|
|
639
|
+
}
|
|
590
640
|
let next = part.text || "";
|
|
591
641
|
if (props.delta && !part.text) {
|
|
592
|
-
next = (textByPartId.get(`reasoning:${partId}`) || "") + props.delta;
|
|
642
|
+
next = (textByPartId.get(`reasoning:${partId}`) || orphanText || "") + props.delta;
|
|
643
|
+
}
|
|
644
|
+
else if (!next && orphanText) {
|
|
645
|
+
next = orphanText;
|
|
593
646
|
}
|
|
594
647
|
if (part.text)
|
|
595
648
|
next = part.text;
|
|
@@ -776,9 +829,12 @@ function isAssistantEchoOfSentPrompt(assistant, ...sentPrompts) {
|
|
|
776
829
|
continue;
|
|
777
830
|
if (a === p)
|
|
778
831
|
return true;
|
|
779
|
-
// Streaming echo
|
|
832
|
+
// Streaming echo from the start of the wrapped prompt.
|
|
780
833
|
if (p.length >= 64 && a.length >= 24 && p.startsWith(a))
|
|
781
834
|
return true;
|
|
835
|
+
// Mid-wrap regurgitation (e.g. only the "Conversation so far:" section).
|
|
836
|
+
if (p.length >= 64 && a.length >= 40 && p.includes(a))
|
|
837
|
+
return true;
|
|
782
838
|
}
|
|
783
839
|
return false;
|
|
784
840
|
}
|
|
@@ -1020,6 +1076,7 @@ function runOpencode(prompt, model, childEnv, opencodeSessionId, callbacks, atta
|
|
|
1020
1076
|
let buf = "";
|
|
1021
1077
|
let fatalError = null;
|
|
1022
1078
|
const textByPartId = new Map();
|
|
1079
|
+
const partTypeById = new Map();
|
|
1023
1080
|
let sawStdout = false;
|
|
1024
1081
|
let progressTicker = setInterval(() => {
|
|
1025
1082
|
if (sawStdout) {
|
|
@@ -1064,7 +1121,7 @@ function runOpencode(prompt, model, childEnv, opencodeSessionId, callbacks, atta
|
|
|
1064
1121
|
return;
|
|
1065
1122
|
}
|
|
1066
1123
|
// Newer OpenCode --format json uses bus shape (message.part.updated); normalize first.
|
|
1067
|
-
const ev = normalizeStdoutOpencodeEvent(parsed) || parseOpencodeEvent(line);
|
|
1124
|
+
const ev = normalizeStdoutOpencodeEvent(parsed, partTypeById) || parseOpencodeEvent(line);
|
|
1068
1125
|
if (!ev?.type)
|
|
1069
1126
|
return;
|
|
1070
1127
|
noteSessionId(ev.sessionID);
|
package/package.json
CHANGED