恢复gpt
This commit is contained in:
@@ -449,7 +449,7 @@ func (c *Client) uploadReferenceImages(ctx context.Context, session tlsclient.Ht
|
|||||||
|
|
||||||
func imageModelSlug(model string) string {
|
func imageModelSlug(model string) string {
|
||||||
if strings.EqualFold(strings.TrimSpace(model), "gpt-image-2") {
|
if strings.EqualFold(strings.TrimSpace(model), "gpt-image-2") {
|
||||||
return "gpt-5-3"
|
return "gpt-5-5-thinking"
|
||||||
}
|
}
|
||||||
return "auto"
|
return "auto"
|
||||||
}
|
}
|
||||||
@@ -820,6 +820,28 @@ func (c *Client) startImageGeneration(ctx context.Context, session tlsclient.Htt
|
|||||||
scanner := bufio.NewScanner(resp.Body)
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
scanner.Buffer(make([]byte, 0, 1024*1024), 8*1024*1024)
|
scanner.Buffer(make([]byte, 0, 1024*1024), 8*1024*1024)
|
||||||
var chunks []string
|
var chunks []string
|
||||||
|
sseStart := time.Now()
|
||||||
|
// Watchdog: once the conversation id is known, the stream may go silent
|
||||||
|
// without ever emitting the async marker, leaving scanner.Scan() blocked on
|
||||||
|
// a read for the whole ctx budget. Closing the body unblocks the read so the
|
||||||
|
// loop exits and we fall through to polling.
|
||||||
|
convFound := make(chan struct{})
|
||||||
|
watchdogDone := make(chan struct{})
|
||||||
|
defer close(watchdogDone)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-convFound:
|
||||||
|
case <-watchdogDone:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timer := time.NewTimer(sseAsyncGrace)
|
||||||
|
defer timer.Stop()
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
resp.Body.Close()
|
||||||
|
case <-watchdogDone:
|
||||||
|
}
|
||||||
|
}()
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if !strings.HasPrefix(line, "data:") {
|
if !strings.HasPrefix(line, "data:") {
|
||||||
@@ -836,6 +858,7 @@ func (c *Client) startImageGeneration(ctx context.Context, session tlsclient.Htt
|
|||||||
if conversationID == "" {
|
if conversationID == "" {
|
||||||
if match := conversationIDRE.FindStringSubmatch(payload); len(match) >= 2 {
|
if match := conversationIDRE.FindStringSubmatch(payload); len(match) >= 2 {
|
||||||
conversationID = match[1]
|
conversationID = match[1]
|
||||||
|
close(convFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newFiles, newSeds := scanForIDs(payload)
|
newFiles, newSeds := scanForIDs(payload)
|
||||||
@@ -850,7 +873,12 @@ func (c *Client) startImageGeneration(ctx context.Context, session tlsclient.Htt
|
|||||||
// conversation id there is nothing more to read here, so stop instead of
|
// conversation id there is nothing more to read here, so stop instead of
|
||||||
// holding the SSE open until [DONE] (a stalled stream would otherwise burn
|
// holding the SSE open until [DONE] (a stalled stream would otherwise burn
|
||||||
// the whole generation budget and surface as "context deadline exceeded").
|
// the whole generation budget and surface as "context deadline exceeded").
|
||||||
if asyncStarted && conversationID != "" {
|
//
|
||||||
|
// The async marker normally arrives within ~1s of the conversation id. We
|
||||||
|
// still only wait a short grace for it (the watchdog above unblocks a
|
||||||
|
// silent stream) so a request that never engages the async pipeline is
|
||||||
|
// detected quickly instead of burning the whole budget.
|
||||||
|
if conversationID != "" && (asyncStarted || time.Since(sseStart) >= sseAsyncGrace) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -863,6 +891,17 @@ func (c *Client) startImageGeneration(ctx context.Context, session tlsclient.Htt
|
|||||||
if conversationID == "" {
|
if conversationID == "" {
|
||||||
return "", nil, nil, errors.New("chatgpt SSE closed without conversation_id")
|
return "", nil, nil, errors.New("chatgpt SSE closed without conversation_id")
|
||||||
}
|
}
|
||||||
|
// Intermittently (~10% on gpt-5-5-thinking) the stream returns a conversation
|
||||||
|
// id but never emits the async pipeline marker and no image is ever produced —
|
||||||
|
// polling such a conversation only burns the whole budget and surfaces as the
|
||||||
|
// non-retryable "image poll timeout". The async marker is the reliable "the
|
||||||
|
// image generation task actually started" signal, so when it is absent (and
|
||||||
|
// nothing was streamed inline) treat the attempt as a transient upstream
|
||||||
|
// failure. That is retryable: a fresh submission reliably engages the pipeline,
|
||||||
|
// so the pool's same-account retry recovers instead of failing the request.
|
||||||
|
if !asyncStarted && len(fileIDs) == 0 && len(sedimentIDs) == 0 {
|
||||||
|
return "", nil, nil, fmt.Errorf("%w: image generation did not start (no async marker)", ErrTemporaryUpstream)
|
||||||
|
}
|
||||||
return conversationID, fileIDs, sedimentIDs, nil
|
return conversationID, fileIDs, sedimentIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ const (
|
|||||||
defaultClientVersion = "prod-db390ebea64862bf1899c420a4c736e0cf639747"
|
defaultClientVersion = "prod-db390ebea64862bf1899c420a4c736e0cf639747"
|
||||||
defaultClientBuildNumber = "7904904"
|
defaultClientBuildNumber = "7904904"
|
||||||
defaultPOWScript = "https://chatgpt.com/backend-api/sentinel/sdk.js"
|
defaultPOWScript = "https://chatgpt.com/backend-api/sentinel/sdk.js"
|
||||||
|
// sseAsyncGrace bounds how long startImageGeneration keeps reading the SSE
|
||||||
|
// waiting for the image_gen_async marker after the conversation id is known.
|
||||||
|
// The marker normally arrives within ~1s; when it never streams (intermittent
|
||||||
|
// on gpt-5-5-thinking) we must not hold the stream open for the whole ctx
|
||||||
|
// budget, so we break after this grace and fall through to polling.
|
||||||
|
sseAsyncGrace = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
Reference in New Issue
Block a user