let error.openai = error.register("openai")

openai = ()

# @flag hidden
def parse_openai_error(ans, err) =
  try
    let json.parse (e : {error: {message: string, type: string}}) = ans

    e = e.error

    error.raise(
      error.openai,
      "#{e.type}: #{e.message}"
    )
  catch _ do
    error.raise(err)
  end
end

# Query ChatGPT API.
# @param ~base_url Base URL for the API query
# @param ~key OpenAI API key.
# @param ~model Language model.
# @param ~timeout Timeout for network operations in seconds.
# @param messages Messages initially exchanged.
# @category Internet
# @flag extra
def openai.chat(
  ~key,
  ~base_url="https://api.openai.com",
  ~model="gpt-3.5-turbo",
  ~timeout=null(30.),
  (
  messages:
  [
    {
      role: string,
      content: string,
      name?: string,
      tool_calls?: [
        {id: string, type: string, function: {name: string, arguments: string}}
      ],
      tool_call_id?: string
    }
  ]
  )
) =
  payload = {model=model, messages=messages}

  ans =
    http.post(
      data=json.stringify(payload),
      timeout=timeout,
      headers=
        [
          ("Content-Type", "application/json"),
          (
            "Authorization",
            "Bearer #{(key : string)}"
          )
        ],
      "#{base_url}/v1/chat/completions"
    )

  if
    ans.status_code != 200
  then
    error.raise(
      error.http,
      "#{ans.status_code}: #{ans.status_message}"
    )
  end

  try
    let json.parse (ans :
      {
        choices: [
          {
            finish_reason: string,
            index: int,
            message: {content: string, role: string}
          }
        ],
        created: int,
        model: string,
        object: string,
        usage: {completion_tokens: int, prompt_tokens: int, total_tokens: int}
      }
    ) = ans
    ans
  catch err : [error.json] do
    parse_openai_error(ans, err)
  end
end

# Generate speech using openai. Returns the encoded audio data.
# @param ~base_url Base URL for the API query
# @param ~key OpenAI API key.
# @param ~model Language model.
# @param ~timeout Timeout for network operations in seconds.
# @param ~voice The voice to use when generating the audio. Supported voices are `"alloy"`, `"echo"`, `"fable"`, `"onyx"`, `"nova"`, and `"shimmer"`
# @param ~response_format The format to audio in. Supported formats are: `"mp3"`, `"opus"`, `"aac"`, and `"flac"`.
# @param ~speed The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default.
# @params ~on_data Function executed when receiving the audio data.
# @category Internet
# @flag extra
def openai.speech(
  ~key,
  ~base_url="https://api.openai.com",
  ~model="tts-1",
  ~timeout=null(30.),
  ~voice,
  ~response_format="mp3",
  ~speed=1.,
  ~on_data,
  (input:string)
) =
  payload =
    {
      model=model,
      input=input,
      voice=(voice : string),
      response_format=response_format,
      speed=speed
    }

  ans =
    http.post.stream(
      data=json.stringify(payload),
      timeout=timeout,
      headers=
        [
          ("Content-Type", "application/json"),
          (
            "Authorization",
            "Bearer #{(key : string)}"
          )
        ],
      on_body_data=on_data,
      "#{base_url}/v1/audio/speech"
    )

  if
    ans.status_code != 200
  then
    error.raise(
      error.http,
      "#{ans.status_code}: #{ans.status_message}"
    )
  end
end
