feat(roadblock): collect and strip CLAUDEDO_BLOCKED markers in StreamAnalyzer
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ClaudeDo.Worker.Runner;
|
||||
@@ -11,6 +12,7 @@ public sealed class StreamResult
|
||||
public int TokensIn { get; set; }
|
||||
public int TokensOut { get; set; }
|
||||
public int ApiRetryCount { get; set; }
|
||||
public IReadOnlyList<string> Blocks { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public sealed class StreamAnalyzer
|
||||
@@ -22,6 +24,8 @@ public sealed class StreamAnalyzer
|
||||
private int _tokensIn;
|
||||
private int _tokensOut;
|
||||
private int _apiRetryCount;
|
||||
private readonly List<string> _blocks = new();
|
||||
private const string BlockedPrefix = "CLAUDEDO_BLOCKED:";
|
||||
|
||||
public void ProcessLine(string ndjsonLine)
|
||||
{
|
||||
@@ -39,7 +43,7 @@ public sealed class StreamAnalyzer
|
||||
{
|
||||
case "result":
|
||||
if (root.TryGetProperty("result", out var resultProp))
|
||||
_resultMarkdown = resultProp.GetString();
|
||||
_resultMarkdown = StripAndCollect(resultProp.GetString());
|
||||
if (root.TryGetProperty("structured_output", out var structuredProp))
|
||||
_structuredOutputJson = structuredProp.ToString();
|
||||
if (root.TryGetProperty("session_id", out var sessionProp))
|
||||
@@ -56,6 +60,7 @@ public sealed class StreamAnalyzer
|
||||
|
||||
case "assistant":
|
||||
_turnCount++;
|
||||
CollectFromAssistant(root);
|
||||
break;
|
||||
|
||||
case "system":
|
||||
@@ -81,6 +86,7 @@ public sealed class StreamAnalyzer
|
||||
TokensIn = _tokensIn,
|
||||
TokensOut = _tokensOut,
|
||||
ApiRetryCount = _apiRetryCount,
|
||||
Blocks = _blocks,
|
||||
};
|
||||
|
||||
private string? FallbackResult()
|
||||
@@ -97,6 +103,37 @@ public sealed class StreamAnalyzer
|
||||
return _structuredOutputJson;
|
||||
}
|
||||
|
||||
private void CollectFromAssistant(JsonElement root)
|
||||
{
|
||||
if (!root.TryGetProperty("message", out var msg)) return;
|
||||
if (msg.ValueKind != JsonValueKind.Object) return;
|
||||
if (!msg.TryGetProperty("content", out var content) || content.ValueKind != JsonValueKind.Array) return;
|
||||
foreach (var block in content.EnumerateArray())
|
||||
if (block.TryGetProperty("type", out var t) && t.GetString() == "text"
|
||||
&& block.TryGetProperty("text", out var txt))
|
||||
ScanForBlocks(txt.GetString());
|
||||
}
|
||||
|
||||
private void ScanForBlocks(string? text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
foreach (var line in text.Split('\n'))
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (trimmed.StartsWith(BlockedPrefix, StringComparison.Ordinal))
|
||||
_blocks.Add(trimmed[BlockedPrefix.Length..].Trim());
|
||||
}
|
||||
}
|
||||
|
||||
private string? StripAndCollect(string? text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return text;
|
||||
ScanForBlocks(text);
|
||||
var kept = text.Split('\n')
|
||||
.Where(l => !l.Trim().StartsWith(BlockedPrefix, StringComparison.Ordinal));
|
||||
return string.Join('\n', kept).Trim();
|
||||
}
|
||||
|
||||
private void TryAccumulateUsage(JsonElement root)
|
||||
{
|
||||
if (!root.TryGetProperty("event", out var eventProp)) return;
|
||||
|
||||
@@ -120,4 +120,36 @@ public sealed class StreamAnalyzerTests
|
||||
Assert.Contains("output", result.ResultMarkdown);
|
||||
Assert.Contains("42", result.ResultMarkdown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Collects_Blocked_Markers_From_Assistant_Text()
|
||||
{
|
||||
var analyzer = new StreamAnalyzer();
|
||||
analyzer.ProcessLine("""{"type":"assistant","message":{"content":[{"type":"text","text":"working\nCLAUDEDO_BLOCKED: missing API key\nmoving on"}]}}""");
|
||||
analyzer.ProcessLine("""{"type":"assistant","message":{"content":[{"type":"text","text":"CLAUDEDO_BLOCKED: cannot reach db"}]}}""");
|
||||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||||
var result = analyzer.GetResult();
|
||||
Assert.Equal(2, result.Blocks.Count);
|
||||
Assert.Equal("missing API key", result.Blocks[0]);
|
||||
Assert.Equal("cannot reach db", result.Blocks[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Strips_Blocked_Markers_From_Result_Text()
|
||||
{
|
||||
var analyzer = new StreamAnalyzer();
|
||||
analyzer.ProcessLine("""{"type":"result","result":"All set.\nCLAUDEDO_BLOCKED: no creds\nDone.","session_id":"s1"}""");
|
||||
var result = analyzer.GetResult();
|
||||
Assert.DoesNotContain("CLAUDEDO_BLOCKED", result.ResultMarkdown);
|
||||
Assert.Single(result.Blocks);
|
||||
Assert.Equal("no creds", result.Blocks[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_Markers_Means_Empty_Blocks()
|
||||
{
|
||||
var analyzer = new StreamAnalyzer();
|
||||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||||
Assert.Empty(analyzer.GetResult().Blocks);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user