package protocol import ( "bytes" "encoding/json" "fmt" "io" ) type Kind string const ( Chat Kind = "openai-chat" Responses Kind = "openai-responses" Anthropic Kind = "anthropic" ) type Usage struct { Input int64 `json:"input_tokens"` Output int64 `json:"output_tokens"` CacheRead int64 `json:"cache_read_tokens"` CacheWrite int64 `json:"cache_write_tokens"` Known bool `json:"known"` Raw json.RawMessage `json:"raw,omitempty"` } type object = map[string]any // maxJSONNodes bounds how many JSON tokens one body may contain before it is // materialized into maps. Without it a 16 MiB body of micro-values expands // into hundreds of MB of Go objects per request. const maxJSONNodes = 1 << 18 func valid(k Kind) bool { return k == Chat || k == Responses || k == Anthropic } // scanNodeCount walks b with the streaming decoder and fails once more than // limit tokens have been seen. Malformed JSON is not an error here; the // materializing pass reports its usual message. func scanNodeCount(b []byte, limit int) error { d := json.NewDecoder(bytes.NewReader(b)) n := 0 for { _, err := d.Token() if err != nil { return nil } if n++; n > limit { return fmt.Errorf("JSON exceeds %d nodes", limit) } } } func decode(b []byte) (object, error) { if err := scanNodeCount(b, maxJSONNodes); err != nil { return nil, err } d := json.NewDecoder(bytes.NewReader(b)) d.UseNumber() var v object if err := d.Decode(&v); err != nil { return nil, err } if v == nil { return nil, fmt.Errorf("expected JSON object") } var extra any if err := d.Decode(&extra); err != io.EOF { return nil, fmt.Errorf("trailing JSON data") } return v, nil } func obj(v any) object { m, _ := v.(map[string]any); return m } func str(v any) string { s, _ := v.(string); return s } func arr(v any) []any { a, _ := v.([]any); return a } func num(v any) int64 { switch n := v.(type) { case json.Number: i, _ := n.Int64() return i case int64: return n case int: return int64(n) case float64: return int64(n) } return 0 } func fields(m object, names ...string) error { allowed := map[string]bool{} for _, n := range names { allowed[n] = true } for k := range m { if !allowed[k] { return fmt.Errorf("unsupported field %q", k) } } return nil } func required(m object, k string) (string, error) { s, ok := m[k].(string) if !ok || s == "" { return "", fmt.Errorf("missing or invalid %s", k) } return s, nil } func ExtractUsage(kind Kind, body []byte) Usage { m, e := decode(body) if e != nil { return Usage{} } if r := obj(m["response"]); r != nil { m = r } if r := obj(m["message"]); r != nil { m = r } return usage(kind, obj(m["usage"])) } func usage(k Kind, m object) Usage { if m == nil { return Usage{} } raw, _ := json.Marshal(m) u := Usage{Raw: raw} switch k { case Chat: _, a := m["prompt_tokens"] _, b := m["completion_tokens"] u.Known = a || b u.Input = num(m["prompt_tokens"]) u.Output = num(m["completion_tokens"]) u.CacheRead = num(obj(m["prompt_tokens_details"])["cached_tokens"]) case Responses: _, a := m["input_tokens"] _, b := m["output_tokens"] u.Known = a || b u.Input = num(m["input_tokens"]) u.Output = num(m["output_tokens"]) u.CacheRead = num(obj(m["input_tokens_details"])["cached_tokens"]) case Anthropic: _, a := m["input_tokens"] _, b := m["output_tokens"] u.Known = a || b u.CacheRead = num(m["cache_read_input_tokens"]) u.CacheWrite = num(m["cache_creation_input_tokens"]) u.Input = num(m["input_tokens"]) + u.CacheRead + u.CacheWrite u.Output = num(m["output_tokens"]) } return u } func usageJSON(k Kind, u Usage) object { switch k { case Chat: return object{"prompt_tokens": u.Input, "completion_tokens": u.Output, "total_tokens": u.Input + u.Output, "prompt_tokens_details": object{"cached_tokens": u.CacheRead}} case Responses: return object{"input_tokens": u.Input, "output_tokens": u.Output, "total_tokens": u.Input + u.Output, "input_tokens_details": object{"cached_tokens": u.CacheRead}} default: return object{"input_tokens": u.Input - u.CacheRead - u.CacheWrite, "output_tokens": u.Output, "cache_read_input_tokens": u.CacheRead, "cache_creation_input_tokens": u.CacheWrite} } } type part struct{ typ, text, id, name, args, url string } type message struct { role string parts []part } type request struct { messages []message tools []object options object choice any } func textPart(s string) part { return part{typ: "text", text: s} } func ConvertRequest(from, to Kind, body []byte, model string) ([]byte, error) { if !valid(from) || !valid(to) { return nil, fmt.Errorf("unknown protocol") } m, err := decode(body) if err != nil { return nil, err } if from == to { if model != "" { m["model"] = model } return json.Marshal(m) } r, err := readRequest(from, m) if err != nil { return nil, err } out, err := writeRequest(to, r) if err != nil { return nil, err } if model == "" { model = str(m["model"]) } out["model"] = model return json.Marshal(out) } func readRequest(k Kind, m object) (request, error) { r := request{options: object{}} var err error common := []string{"model", "stream", "temperature", "top_p", "tools", "tool_choice"} switch k { case Chat: err = fields(m, append(common, "messages", "max_tokens", "max_completion_tokens", "stop", "stream_options", "parallel_tool_calls", "n")...) case Responses: err = fields(m, append(common, "input", "instructions", "max_output_tokens", "parallel_tool_calls", "store")...) case Anthropic: err = fields(m, append(common, "messages", "system", "max_tokens", "stop_sequences")...) } if err != nil { return r, err } if n, ok := m["n"]; ok && num(n) != 1 { return r, fmt.Errorf("multiple choices unsupported") } if v, ok := m["store"]; ok && v != false { return r, fmt.Errorf("stored Responses unsupported") } if v, ok := m["stream_options"]; ok { o := obj(v) if o == nil { return r, fmt.Errorf("invalid stream_options") } if e := fields(o, "include_usage"); e != nil { return r, e } } for _, key := range []string{"stream", "temperature", "top_p", "parallel_tool_calls"} { if v, ok := m[key]; ok { r.options[key] = v } } for _, key := range []string{"max_tokens", "max_completion_tokens", "max_output_tokens"} { if v, ok := m[key]; ok { if _, exists := r.options["max"]; exists { return r, fmt.Errorf("conflicting token limits") } r.options["max"] = v } } for _, key := range []string{"stop", "stop_sequences"} { if v, ok := m[key]; ok { if s, ok := v.(string); ok { v = []any{s} } if _, ok := v.([]any); !ok { return r, fmt.Errorf("invalid stop sequences") } r.options["stop"] = v } } if k == Responses { if s, ok := m["instructions"]; ok { v, ok := s.(string) if !ok { return r, fmt.Errorf("invalid instructions") } r.messages = append(r.messages, message{"system", []part{textPart(v)}}) } if s, ok := m["input"].(string); ok { r.messages = append(r.messages, message{"user", []part{textPart(s)}}) } else { items, ok := m["input"].([]any) if !ok { return r, fmt.Errorf("invalid input") } for _, v := range items { x := obj(v) typ := str(x["type"]) switch typ { case "function_call": if e := fields(x, "type", "id", "call_id", "name", "arguments", "status"); e != nil { return r, e } p, e := callPart(x, "call_id", "arguments") if e != nil { return r, e } r.messages = append(r.messages, message{"assistant", []part{p}}) case "function_call_output": if e := fields(x, "type", "id", "call_id", "output", "status"); e != nil { return r, e } id, e := required(x, "call_id") if e != nil { return r, e } s, ok := x["output"].(string) if !ok { return r, fmt.Errorf("non-text function output unsupported") } r.messages = append(r.messages, message{"tool", []part{{typ: "result", id: id, text: s}}}) case "", "message": mm, e := readMessage(k, x) if e != nil { return r, e } r.messages = append(r.messages, mm) default: return r, fmt.Errorf("unsupported input item %q", typ) } } } } else { if k == Anthropic { if s, ok := m["system"]; ok { ps, e := readContent(k, s) if e != nil { return r, e } for _, p := range ps { if p.typ != "text" { return r, fmt.Errorf("non-text system") } } r.messages = append(r.messages, message{"system", ps}) } } ms, ok := m["messages"].([]any) if !ok { return r, fmt.Errorf("invalid messages") } for _, v := range ms { mm, e := readMessage(k, obj(v)) if e != nil { return r, e } r.messages = append(r.messages, mm) } } if v, ok := m["tools"]; ok { ts, ok := v.([]any) if !ok { return r, fmt.Errorf("invalid tools") } for _, v := range ts { t := obj(v) var f object switch k { case Chat: if e := fields(t, "type", "function"); e != nil { return r, e } if str(t["type"]) != "function" { return r, fmt.Errorf("unsupported tool") } f = obj(t["function"]) case Responses: if str(t["type"]) != "function" { return r, fmt.Errorf("unsupported tool") } f = object{} for key, val := range t { if key != "type" { f[key] = val } } case Anthropic: f = object{} for key, val := range t { if key == "input_schema" { key = "parameters" } f[key] = val } } if e := fields(f, "name", "description", "parameters", "strict"); e != nil { return r, e } if _, e := required(f, "name"); e != nil { return r, e } if obj(f["parameters"]) == nil { return r, fmt.Errorf("missing tool schema") } r.tools = append(r.tools, f) } } if v, ok := m["tool_choice"]; ok { if s, ok := v.(string); ok { if s != "auto" && s != "none" && s != "required" { return r, fmt.Errorf("unsupported tool_choice") } r.choice = s } else { x := obj(v) if k == Chat { if e := fields(x, "type", "function"); e != nil { return r, e } if str(x["type"]) != "function" { return r, fmt.Errorf("unsupported tool_choice") } f := obj(x["function"]) if e := fields(f, "name"); e != nil { return r, e } s, e := required(f, "name") if e != nil { return r, e } r.choice = object{"name": s} } else if k == Responses { if e := fields(x, "type", "name"); e != nil { return r, e } if str(x["type"]) != "function" { return r, fmt.Errorf("unsupported tool_choice") } s, e := required(x, "name") if e != nil { return r, e } r.choice = object{"name": s} } else { if e := fields(x, "type", "name", "disable_parallel_tool_use"); e != nil { return r, e } switch str(x["type"]) { case "auto", "none": r.choice = x["type"] case "any": r.choice = "required" case "tool": s, e := required(x, "name") if e != nil { return r, e } r.choice = object{"name": s} default: return r, fmt.Errorf("unsupported tool_choice") } if b, ok := x["disable_parallel_tool_use"].(bool); ok { r.options["parallel_tool_calls"] = !b } } } } return r, nil } func callPart(x object, idKey, argsKey string) (part, error) { id, e := required(x, idKey) if e != nil { return part{}, e } name, e := required(x, "name") if e != nil { return part{}, e } s, ok := x[argsKey].(string) if !ok { return part{}, fmt.Errorf("invalid tool arguments") } if _, e := decode([]byte(s)); e != nil { return part{}, fmt.Errorf("tool arguments must be JSON object: %w", e) } return part{typ: "call", id: id, name: name, args: s}, nil } func readMessage(k Kind, x object) (message, error) { m := message{role: str(x["role"])} if x == nil { return m, fmt.Errorf("invalid message") } if e := fields(x, "role", "content", "type", "id", "status", "tool_calls", "tool_call_id", "refusal"); e != nil { return m, e } if x["refusal"] != nil { return m, fmt.Errorf("refusal content cannot be preserved") } switch m.role { case "user", "assistant", "system", "developer", "tool": default: return m, fmt.Errorf("unsupported role %q", m.role) } if m.role == "tool" { if k != Chat { return m, fmt.Errorf("invalid tool role") } id, e := required(x, "tool_call_id") if e != nil { return m, e } s, ok := x["content"].(string) if !ok { return m, fmt.Errorf("non-text tool result unsupported") } m.parts = []part{{typ: "result", id: id, text: s}} return m, nil } if c := x["content"]; c != nil { ps, e := readContent(k, c) if e != nil { return m, e } m.parts = ps } if tc, ok := x["tool_calls"]; ok { if k != Chat || m.role != "assistant" { return m, fmt.Errorf("invalid tool_calls") } ts, ok := tc.([]any) if !ok { return m, fmt.Errorf("invalid tool_calls") } for _, v := range ts { t := obj(v) if e := fields(t, "id", "type", "function"); e != nil { return m, e } if str(t["type"]) != "function" { return m, fmt.Errorf("unsupported tool call") } f := obj(t["function"]) if e := fields(f, "name", "arguments"); e != nil { return m, e } p, e := callPart(object{"id": t["id"], "name": f["name"], "arguments": f["arguments"]}, "id", "arguments") if e != nil { return m, e } m.parts = append(m.parts, p) } } return m, nil } func readContent(k Kind, v any) ([]part, error) { if s, ok := v.(string); ok { return []part{textPart(s)}, nil } a, ok := v.([]any) if !ok { return nil, fmt.Errorf("invalid content") } var ps []part for _, v := range a { x := obj(v) switch str(x["type"]) { case "text", "input_text", "output_text": if e := fields(x, "type", "text", "annotations"); e != nil { return nil, e } if an, ok := x["annotations"]; ok && len(arr(an)) != 0 { return nil, fmt.Errorf("annotations unsupported") } s, ok := x["text"].(string) if !ok { return nil, fmt.Errorf("invalid text") } ps = append(ps, textPart(s)) case "image_url": if k != Chat { return nil, fmt.Errorf("invalid image block") } if e := fields(x, "type", "image_url"); e != nil { return nil, e } im := obj(x["image_url"]) if e := fields(im, "url", "detail"); e != nil { return nil, e } if d := str(im["detail"]); d != "" && d != "auto" { return nil, fmt.Errorf("image detail cannot be preserved") } s, e := required(im, "url") if e != nil { return nil, e } ps = append(ps, part{typ: "image", url: s}) case "input_image": if k != Responses { return nil, fmt.Errorf("invalid image block") } if e := fields(x, "type", "image_url", "detail"); e != nil { return nil, e } if d := str(x["detail"]); d != "" && d != "auto" { return nil, fmt.Errorf("image detail cannot be preserved") } s, e := required(x, "image_url") if e != nil { return nil, e } ps = append(ps, part{typ: "image", url: s}) case "image": if k != Anthropic { return nil, fmt.Errorf("invalid image block") } if e := fields(x, "type", "source"); e != nil { return nil, e } src := obj(x["source"]) var url string switch str(src["type"]) { case "url": if e := fields(src, "type", "url"); e != nil { return nil, e } url = str(src["url"]) case "base64": if e := fields(src, "type", "media_type", "data"); e != nil { return nil, e } if str(src["media_type"]) == "" || str(src["data"]) == "" { return nil, fmt.Errorf("invalid image source") } url = "data:" + str(src["media_type"]) + ";base64," + str(src["data"]) default: return nil, fmt.Errorf("unsupported image source") } if url == "" { return nil, fmt.Errorf("empty image URL") } ps = append(ps, part{typ: "image", url: url}) case "tool_use": if k != Anthropic { return nil, fmt.Errorf("invalid tool block") } if e := fields(x, "type", "id", "name", "input"); e != nil { return nil, e } b, e := json.Marshal(x["input"]) if e != nil { return nil, e } p, e := callPart(object{"id": x["id"], "name": x["name"], "arguments": string(b)}, "id", "arguments") if e != nil { return nil, e } ps = append(ps, p) case "tool_result": if k != Anthropic { return nil, fmt.Errorf("invalid result block") } if e := fields(x, "type", "tool_use_id", "content", "is_error"); e != nil { return nil, e } if x["is_error"] == true { return nil, fmt.Errorf("tool error flag cannot be preserved") } id, e := required(x, "tool_use_id") if e != nil { return nil, e } s, ok := x["content"].(string) if !ok { pp, e := readContent(k, x["content"]) if e != nil { return nil, e } for _, p := range pp { if p.typ != "text" { return nil, fmt.Errorf("non-text tool result unsupported") } s += p.text } } ps = append(ps, part{typ: "result", id: id, text: s}) default: return nil, fmt.Errorf("unsupported content type %q", str(x["type"])) } } return ps, nil }