| 3989 | } |
| 3990 | } else if (!strcmp(key, "text")) { |
| 3991 | /* The text field of a typed content block is a plain JSON |
| 3992 | * string. Accept null as the empty string for parity with |
| 3993 | * upstream serializers that emit null for empty blocks. */ |
| 3994 | json_ws(p); |
| 3995 | if (json_lit(p, "null")) { |
| 3996 | free(text); |
| 3997 | text = xstrdup(""); |
| 3998 | } else if (!json_string_replace(p, &text)) { |
| 3999 | free(key); |
| 4000 | free(type); |
| 4001 | free(text); |
| 4002 | free(image_url); |
| 4003 | goto fail; |
| 4004 | } |
| 4005 | } else if (!strcmp(key, "image_url")) { |
| 4006 | free(image_url); |
| 4007 | image_url = NULL; |
| 4008 | if (!parse_image_url_value(p, &image_url)) { |
| 4009 | free(key); |
| 4010 | free(type); |
| 4011 | free(text); |
| 4012 | goto fail; |
| 4013 | } |
| 4014 | } else if (!json_skip_value(p)) { |
| 4015 | free(key); |
| 4016 | free(type); |
| 4017 | free(text); |
| 4018 | free(image_url); |
| 4019 | goto fail; |
| 4020 | } |
| 4021 | free(key); |
| 4022 | json_ws(p); |
| 4023 | if (**p == ',') (*p)++; |
| 4024 | json_ws(p); |
| 4025 | } |
| 4026 | if (**p != '}') { |
| 4027 | free(type); |
| 4028 | free(text); |
| 4029 | free(image_url); |
| 4030 | goto fail; |
| 4031 | } |
| 4032 | (*p)++; |
| 4033 | /* Fail closed: a content object must carry a known text-like type |
| 4034 | * AND a text field. Anything else — missing type, missing text, |
| 4035 | * image/file/audio types, future schema-drift — is rejected so the |
| 4036 | * client gets a 400 instead of an answer built on context the |
| 4037 | * server discarded silently. */ |
| 4038 | bool is_text_block = type && ( |
| 4039 | !strcmp(type, "input_text") || |
| 4040 | !strcmp(type, "output_text") || |
| 4041 | !strcmp(type, "text") || |
| 4042 | !strcmp(type, "summary_text") || |
| 4043 | !strcmp(type, "reasoning_text")); |
| 4044 | bool is_image_block = type && !strcmp(type, "input_image"); |
| 4045 | if (is_image_block) { |
| 4046 | char marker[SERVER_IMAGE_MARKER_BYTES]; |
| 4047 | if (!images || |
| 4048 | !server_image_inputs_push_data_uri(images, image_url, marker)) { |
no test coverage detected