fix(openai): artifact selector 스트림을 분류 전 버퍼링한다
This commit is contained in:
parent
85c6fc837c
commit
1f1be982c9
2 changed files with 41 additions and 1 deletions
|
|
@ -603,7 +603,10 @@ func (c *anthropicHotPathCodec) runInitialPresetTurn(
|
|||
gate hotPathSelectorGate
|
||||
err error
|
||||
)
|
||||
if c.stream {
|
||||
selectorState := s.artifactFrontiers.selectorInstructionState(
|
||||
runMeta["iop_logical_request_id"], s.edgeIDValue(),
|
||||
)
|
||||
if c.stream && selectorState == selectorInstructionNone {
|
||||
outer := c.callerOuterTurn("", hotPathOutputTokenCap(runMeta))
|
||||
if err := c.prepareProgressiveWriter(w, outer, false); err != nil {
|
||||
return stage, false, err
|
||||
|
|
@ -612,6 +615,8 @@ func (c *anthropicHotPathCodec) runInitialPresetTurn(
|
|||
r.Context(), dispatch, "anthropic", runMeta["iop_stage_id"], result, outer,
|
||||
)
|
||||
} else {
|
||||
// Artifact selector calls are private IOP control data. Keep them buffered
|
||||
// until classification expands them into caller-visible workspace writes.
|
||||
stage, gate, err = s.collectPresetSelectorResult(r.Context(), dispatch, "anthropic", result)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"iop/packages/go/config"
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
|
||||
|
|
@ -150,6 +151,40 @@ func TestHotPathAnthropicDirectStreamPreservesEmptyToolInput(t *testing.T) {
|
|||
assertHotPathWaiting(t, srv, "msg-empty-tool-tool-1", "provider-zero-arg-tool")
|
||||
}
|
||||
|
||||
func TestHotPathAnthropicArtifactSelectorBuffersPrivateCalls(t *testing.T) {
|
||||
fixture := newScriptedLightFixture(t, "anthropic", false)
|
||||
alternative := scriptedLightWorkspaceAlternative()
|
||||
write := alternative.Operations["write"]
|
||||
write.CreatesParents = true
|
||||
alternative.Operations["write"] = write
|
||||
preset := hotPathSelectorPreset([]string{config.ModeDirect, config.ModeLight})
|
||||
preset.WorkspaceTools = []config.ExecutionWorkspaceToolAlternative{alternative}
|
||||
fixture.server.SetExecutionPresets([]config.ExecutionPreset{preset})
|
||||
fixture.service.responses[0] = func(requestID string) string {
|
||||
return scriptedArtifactPair("anthropic", requestID)
|
||||
}
|
||||
|
||||
response := fixture.requestWithOptions(64, true)
|
||||
if response.Code != http.StatusOK || strings.Contains(response.Body.String(), `"type":"error"`) ||
|
||||
strings.Contains(response.Body.String(), hotPathArtifactPairToolName) {
|
||||
t.Fatalf("artifact selector leaked or failed: status=%d body=%s", response.Code, response.Body.String())
|
||||
}
|
||||
events := decodeHotPathAnthropicSSE(t, response.Body.String())
|
||||
var writeCalls int
|
||||
for _, event := range events {
|
||||
if event.name != "content_block_start" {
|
||||
continue
|
||||
}
|
||||
block := hotPathAnthropicMap(t, event.payload["content_block"])
|
||||
if block["type"] == "tool_use" && block["name"] == "write_file" {
|
||||
writeCalls++
|
||||
}
|
||||
}
|
||||
if writeCalls != 2 {
|
||||
t.Fatalf("caller-visible write calls=%d, want 2; body=%s", writeCalls, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHotPathAnthropicLightStreamAggregatesStages(t *testing.T) {
|
||||
fixture := newScriptedLightFixture(t, "anthropic", false)
|
||||
fixture.service.responses[4] = func(requestID string) string {
|
||||
|
|
|
|||
Loading…
Reference in a new issue