mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-20 00:18:55 +00:00
fix(openai): accept full heartbeat bootstrap envelopes
This commit is contained in:
@@ -166,3 +166,44 @@ func TestNormalizeCodexAutomationBootstrapPreservesOrderAndIsIdempotent(t *testi
|
||||
require.False(t, changedAgain)
|
||||
require.Equal(t, got, again)
|
||||
}
|
||||
|
||||
func TestNormalizeCodexAutomationBootstrapFullHeartbeat(t *testing.T) {
|
||||
const output = `<heartbeat>
|
||||
<automation_id>flutter</automation_id>
|
||||
<current_time_iso>2026-09-09T00:33:34.775Z</current_time_iso>
|
||||
<instructions>
|
||||
Read the reference index and report changes. Preserve A & B and <tags>.
|
||||
</instructions>
|
||||
</heartbeat>`
|
||||
body := codexAutomationBootstrapBody(t, output, "")
|
||||
got, changed := normalizeCodexAutomationBootstrap(body)
|
||||
require.True(t, changed)
|
||||
require.Equal(t, "user", gjson.GetBytes(got, "input.0.role").String())
|
||||
require.Equal(t, output, gjson.GetBytes(got, "input.0.content.0.text").String())
|
||||
again, changedAgain := normalizeCodexAutomationBootstrap(got)
|
||||
require.False(t, changedAgain)
|
||||
require.Equal(t, got, again)
|
||||
|
||||
for _, field := range []string{"automation_id", "current_time_iso", "instructions"} {
|
||||
duplicate := strings.Replace(output, "</heartbeat>", "<"+field+">duplicate</"+field+"></heartbeat>", 1)
|
||||
require.False(t, validCodexAutomationHeartbeat(duplicate), field)
|
||||
}
|
||||
for _, invalid := range []string{
|
||||
strings.Replace(output, "2026-09-09T00:33:34.775Z", "yesterday", 1),
|
||||
strings.Replace(output, "<current_time_iso>2026-09-09T00:33:34.775Z</current_time_iso>", "", 1),
|
||||
strings.Replace(output, "Read the reference index and report changes. Preserve A & B and <tags>.", " ", 1),
|
||||
strings.Replace(output, "<instructions>", `<instructions source="other">`, 1),
|
||||
strings.Replace(output, "<instructions>", "<instructions><nested/>", 1),
|
||||
} {
|
||||
require.False(t, validCodexAutomationHeartbeat(invalid))
|
||||
}
|
||||
for _, guarded := range [][]byte{
|
||||
codexAutomationBootstrapBody(t, output, `,"call_id":"call-1"`),
|
||||
[]byte(strings.Replace(string(body), `"model":"gpt-5"`, `"model":"gpt-5","previous_response_id":"resp-1"`, 1)),
|
||||
[]byte(strings.Replace(string(body), `"namespace":"codex_app"`, `"namespace":"other"`, 1)),
|
||||
} {
|
||||
unchanged, normalized := normalizeCodexAutomationBootstrap(guarded)
|
||||
require.False(t, normalized)
|
||||
require.Equal(t, guarded, unchanged)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1889,12 +1889,24 @@ func validCodexAutomationLastRun(value string) bool {
|
||||
func validCodexAutomationHeartbeat(value string) bool {
|
||||
decoder := xml.NewDecoder(strings.NewReader(value))
|
||||
var rootSeen, automationIDSeen bool
|
||||
var automationID bytes.Buffer
|
||||
var childName string
|
||||
var childText bytes.Buffer
|
||||
fields := make(map[string]string)
|
||||
depth := 0
|
||||
for {
|
||||
token, err := decoder.Token()
|
||||
if err == io.EOF {
|
||||
id := automationID.String()
|
||||
id := fields["automation_id"]
|
||||
timestamp, hasTime := fields["current_time_iso"]
|
||||
instructions, hasInstructions := fields["instructions"]
|
||||
if hasTime != hasInstructions {
|
||||
return false
|
||||
}
|
||||
if hasTime {
|
||||
if _, err := time.Parse(time.RFC3339Nano, timestamp); err != nil || strings.TrimSpace(instructions) == "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return rootSeen && automationIDSeen && depth == 0 &&
|
||||
strings.TrimSpace(id) == id && validCodexAutomationID(id)
|
||||
}
|
||||
@@ -1912,21 +1924,33 @@ func validCodexAutomationHeartbeat(value string) bool {
|
||||
return false
|
||||
}
|
||||
rootSeen = true
|
||||
} else if automationIDSeen || current.Name.Local != "automation_id" {
|
||||
return false
|
||||
} else {
|
||||
childName = current.Name.Local
|
||||
switch childName {
|
||||
case "automation_id", "current_time_iso", "instructions":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if _, duplicate := fields[childName]; duplicate {
|
||||
return false
|
||||
}
|
||||
childText.Reset()
|
||||
}
|
||||
automationIDSeen = depth == 2
|
||||
case xml.EndElement:
|
||||
if current.Name.Space != "" {
|
||||
return false
|
||||
}
|
||||
if depth == 2 {
|
||||
fields[childName] = childText.String()
|
||||
automationIDSeen = automationIDSeen || childName == "automation_id"
|
||||
}
|
||||
depth--
|
||||
if depth < 0 {
|
||||
return false
|
||||
}
|
||||
case xml.CharData:
|
||||
if depth == 2 {
|
||||
_, _ = automationID.Write(current)
|
||||
_, _ = childText.Write(current)
|
||||
} else if len(bytes.TrimSpace(current)) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user