return ModelResponse{}, errors.Join(err, readErr)
}
- return modelReadStream(httpResponse.Body)
+ response, err = modelReadStream(httpResponse.Body)
+ if err != nil {
+ return response, fmt.Errorf("read model response stream: %w", err)
+ }
+
+ return response, nil
}
// Write appends record to the file with one write.
return nil
}
-// modelEndpoint returns the URL and the authorizer of the endpoint in config.
-// The ChatGPT backend uses the token file in dir. The OpenAI API uses the key
-// in OPENAI_API_KEY, and there is no fallback from one to the other. All
-// clients share the authorizer, so one refresh runs at a time.
-func modelEndpoint(config Config, dir string) (string, ModelAuthorizer, error) {
- if config.Endpoint == configEndpointAPI {
- key := os.Getenv("OPENAI_API_KEY")
- if key == "" {
- return "", nil, errors.New("endpoint api needs OPENAI_API_KEY")
- }
-
- return modelAPIURL, ModelAPIKey(key), nil
- }
-
- return modelChatGPTURL, newChatGPTAuth(filepath.Join(dir, chatgptTokenFileName), chatgptIssuer), nil
-}
-
-// modelUserMessage returns a user input item with one text part for each text.
-func modelUserMessage(texts ...string) any {
- content := make([]ModelInputPart, len(texts))
- for n, text := range texts {
- content[n] = ModelInputPart{
- Type: "input_text",
- Text: text,
- }
- }
-
- return modelUserParts(content)
-}
-
-// modelUserParts keeps text and image parts in one user message.
-func modelUserParts(content []ModelInputPart) any {
- return struct {
- Role string `json:"role"`
- Content []ModelInputPart `json:"content"`
- }{
- Role: "user",
- Content: content,
- }
-}
-
-// modelOutputText returns the text of the message items of output.
-func modelOutputText(output []json.RawMessage) (string, error) {
- var text strings.Builder
-
- for _, raw := range output {
- var item ModelOutputItem
-
- err := json.Unmarshal(raw, &item)
- if err != nil {
- return "", fmt.Errorf("decode output item: %w", err)
- }
-
- if item.Type != "message" {
- continue
- }
-
- for _, part := range item.Content {
- if part.Type == "output_text" {
- text.WriteString(part.Text)
- }
- }
- }
-
- return text.String(), nil
-}
-
// modelQuotaHeaders returns the x-codex-* headers, with lowercase names,
// without the turn state.
func modelQuotaHeaders(header http.Header) map[string]string {
}
response.Text, err = modelOutputText(response.Output)
+ if err != nil {
+ return true, fmt.Errorf("read completed response text: %w", err)
+ }
- return true, err
+ return true, nil
case "response.incomplete":
reason := "unknown"
if event.Response != nil && event.Response.IncompleteDetails != nil {
return false, nil
}
+
+// modelOutputText returns the text of the message items of output.
+func modelOutputText(output []json.RawMessage) (string, error) {
+ var text strings.Builder
+
+ for _, raw := range output {
+ var item ModelOutputItem
+
+ err := json.Unmarshal(raw, &item)
+ if err != nil {
+ return "", fmt.Errorf("decode output item: %w", err)
+ }
+
+ if item.Type != "message" {
+ continue
+ }
+
+ for _, part := range item.Content {
+ if part.Type == "output_text" {
+ text.WriteString(part.Text)
+ }
+ }
+ }
+
+ return text.String(), nil
+}
+
+// modelEndpoint returns the URL and the authorizer of the endpoint in config.
+// The ChatGPT backend uses the token file in dir. The OpenAI API uses the key
+// in OPENAI_API_KEY, and there is no fallback from one to the other. All
+// clients share the authorizer, so one refresh runs at a time.
+func modelEndpoint(config Config, dir string) (string, ModelAuthorizer, error) {
+ if config.Endpoint == configEndpointAPI {
+ key := os.Getenv("OPENAI_API_KEY")
+ if key == "" {
+ return "", nil, errors.New("endpoint api needs OPENAI_API_KEY")
+ }
+
+ return modelAPIURL, ModelAPIKey(key), nil
+ }
+
+ return modelChatGPTURL, newChatGPTAuth(filepath.Join(dir, chatgptTokenFileName), chatgptIssuer), nil
+}
+
+// modelUserMessage returns a user input item with one text part for each text.
+func modelUserMessage(texts ...string) any {
+ content := make([]ModelInputPart, len(texts))
+ for n, text := range texts {
+ content[n] = ModelInputPart{
+ Type: "input_text",
+ Text: text,
+ }
+ }
+
+ return modelUserParts(content)
+}
+
+// modelUserParts keeps text and image parts in one user message.
+func modelUserParts(content []ModelInputPart) any {
+ return struct {
+ Role string `json:"role"`
+ Content []ModelInputPart `json:"content"`
+ }{
+ Role: "user",
+ Content: content,
+ }
+}