aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--web/openai/assistant/assistant.go178
-rw-r--r--web/openai/assistant/message.go116
-rw-r--r--web/openai/assistant/run.go189
-rw-r--r--web/openai/assistant/thread.go95
-rw-r--r--web/openai/models.go40
5 files changed, 618 insertions, 0 deletions
diff --git a/web/openai/assistant/assistant.go b/web/openai/assistant/assistant.go
new file mode 100644
index 0000000..8fad266
--- /dev/null
+++ b/web/openai/assistant/assistant.go
@@ -0,0 +1,178 @@
+package assistant
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "within.website/x/web"
+ "within.website/x/web/openai/chatgpt"
+)
+
+type Assistant struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ Created int64 `json:"created"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Model string `json:"model"`
+ Instructions string `json:"instructions"`
+ Tools []Tool `json:"tools"`
+ FileIDs []string `json:"file_ids"`
+ Metadata map[string]string `json:"metadata"`
+}
+
+type Tool struct {
+ Type string `json:"type"`
+ Function *chatgpt.Function `json:"function,omitempty"`
+}
+
+type Client struct {
+ // OpenAIKey is the API key used to authenticate with the OpenAI API.
+ OpenAIKey string
+
+ // Client is the HTTP client used to make requests to the OpenAI API.
+ Client *http.Client
+}
+
+func (c *Client) Do(req *http.Request) (*http.Response, error) {
+ req.Header.Set("Authorization", "Bearer "+c.OpenAIKey)
+ req.Header.Set("OpenAI-Beta", "assistants=v1")
+
+ return c.Client.Do(req)
+}
+
+type CreateAssistant struct {
+ Instructions string `json:"instructions"`
+ Name string `json:"name"`
+ Tools []Tool `json:"tools"`
+ Model string `json:"model"`
+}
+
+func (c *Client) CreateAssistant(ctx context.Context, ca CreateAssistant) (*Assistant, error) {
+ buf := new(bytes.Buffer)
+ if err := json.NewEncoder(buf).Encode(ca); err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/assistants", buf)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var result Assistant
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (c *Client) GetAssistant(ctx context.Context, id string) (*Assistant, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.openai.com/v1/assistants/"+id, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var result Assistant
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (c *Client) DeleteAssistant(ctx context.Context, id string) error {
+ req, err := http.NewRequestWithContext(ctx, http.MethodDelete, "https://api.openai.com/v1/assistants/"+id, nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return web.NewError(http.StatusOK, resp)
+ }
+
+ return nil
+}
+
+func (c *Client) ModifyAssistant(ctx context.Context, id string, ca CreateAssistant) (*Assistant, error) {
+ buf := new(bytes.Buffer)
+ if err := json.NewEncoder(buf).Encode(ca); err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/assistants/"+id, buf)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var result Assistant
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (c *Client) ListAssistants(ctx context.Context) ([]Assistant, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.openai.com/v1/assistants", nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var result struct {
+ Data []Assistant `json:"data"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return result.Data, nil
+}
diff --git a/web/openai/assistant/message.go b/web/openai/assistant/message.go
new file mode 100644
index 0000000..5d629a2
--- /dev/null
+++ b/web/openai/assistant/message.go
@@ -0,0 +1,116 @@
+package assistant
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "within.website/x/web"
+)
+
+type Content struct {
+ Type string `json:"type"`
+ Text Text `json:"text"`
+}
+
+type Text struct {
+ Value string `json:"value"`
+ Annotations json.RawMessage `json:"annotations"`
+}
+
+type Message struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ CreatedAt int64 `json:"created_at"`
+ ThreadID string `json:"thread_id"`
+ Role string `json:"role"`
+ Content []Content `json:"content"`
+ FileIDs []string `json:"file_ids"`
+ AssistantID *string `json:"assistant_id,omitempty"`
+ RunID *string `json:"run_id,omitempty"`
+ Metadata map[string]string `json:"metadata"`
+}
+
+type CreateMessage struct {
+ Role string `json:"role"`
+ Content string `json:"content"`
+}
+
+func (c *Client) CreateMessage(ctx context.Context, tid string, cm CreateMessage) (*Message, error) {
+ buf := new(bytes.Buffer)
+ if err := json.NewEncoder(buf).Encode(cm); err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/threads/"+tid+"/messages", buf)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var m Message
+ if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
+ return nil, err
+ }
+
+ return &m, nil
+}
+
+func (c *Client) GetMessage(ctx context.Context, tid, mid string) (*Message, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.openai.com/v1/threads/"+tid+"/messages/"+mid, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var m Message
+ if err := json.NewDecoder(resp.Body).Decode(&m); err != nil {
+ return nil, err
+ }
+
+ return &m, nil
+}
+
+func (c *Client) ListMessages(ctx context.Context, tid string) ([]Message, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.openai.com/v1/threads/"+tid+"/messages", nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ type response struct {
+ Data []Message `json:"data"`
+ }
+
+ var result response
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return result.Data, nil
+}
diff --git a/web/openai/assistant/run.go b/web/openai/assistant/run.go
new file mode 100644
index 0000000..39d74f2
--- /dev/null
+++ b/web/openai/assistant/run.go
@@ -0,0 +1,189 @@
+package assistant
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "within.website/x/web"
+)
+
+type RunStatus string
+
+const (
+ RunStatusQueued RunStatus = "queued"
+ RunStatusInProgress RunStatus = "in_progress"
+ RunStatusRequiresAction RunStatus = "requires_action"
+ RunStatusCancelling RunStatus = "cancelling"
+ RunStatusCancelled RunStatus = "cancelled"
+ RunStatusFailed RunStatus = "failed"
+ RunStatusCompleted RunStatus = "completed"
+ RunStatusExpired RunStatus = "expired"
+)
+
+type RequiredAction struct {
+ Type string `json:"type"`
+ SubmitToolOutputs []ToolCall `json:"submit_tool_outputs"`
+}
+
+type ToolCall struct {
+ ID string `json:"id"`
+ Type string `json:"type"`
+ Function ToolCallFunction `json:"function"`
+}
+
+type ToolCallFunction struct {
+ Name string `json:"name"`
+ Arguments []string `json:"arguments"`
+}
+
+type ToolOutput struct {
+ ToolCallID string `json:"tool_call_id"`
+ Output string `json:"output"`
+}
+
+type Run struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ CreatedAt int64 `json:"created_at"`
+ AssistantID string `json:"assistant_id"`
+ ThreadID string `json:"thread_id"`
+ Status RunStatus `json:"status"`
+ StartedAt int64 `json:"started_at"`
+ ExpiresAt *int64 `json:"expires_at,omitempty"`
+ CancelledAt *int64 `json:"cancelled_at,omitempty"`
+ FailedAt *int64 `json:"failed_at,omitempty"`
+ LastError *string `json:"last_error,omitempty"`
+ Model string `json:"model"`
+ Instructions *string `json:"instructions,omitempty"`
+ Tools []Tool `json:"tools"`
+ FileIDs []string `json:"file_ids"`
+ Metadata map[string]string `json:"metadata"`
+}
+
+func (c *Client) CreateRun(ctx context.Context, tid, aid string) (*Run, error) {
+ type request struct {
+ AssistantID string `json:"assistant_id"`
+ }
+
+ buf := new(bytes.Buffer)
+ if err := json.NewEncoder(buf).Encode(request{aid}); err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/threads/"+tid+"/runs", buf)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var result Run
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (c *Client) GetRun(ctx context.Context, tid, rid string) (*Run, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.openai.com/v1/threads/"+tid+"/runs/"+rid, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var result Run
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+func (c *Client) CancelRun(ctx context.Context, tid, rid string) error {
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/threads/"+tid+"/runs/"+rid+"/cancel", nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return web.NewError(http.StatusOK, resp)
+ }
+
+ return nil
+}
+
+func (c *Client) ListRuns(ctx context.Context, tid string) ([]Run, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.openai.com/v1/threads/"+tid+"/runs", nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ var result struct {
+ Data []Run `json:"data"`
+ }
+
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return result.Data, nil
+}
+
+func (c *Client) SubmitToolOutput(ctx context.Context, tid, rid string, to ToolOutput) error {
+ buf := new(bytes.Buffer)
+ if err := json.NewEncoder(buf).Encode(to); err != nil {
+ return err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/threads/"+tid+"/runs/"+rid+"/submit_tool_output", buf)
+ if err != nil {
+ return err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return web.NewError(http.StatusOK, resp)
+ }
+
+ return nil
+}
diff --git a/web/openai/assistant/thread.go b/web/openai/assistant/thread.go
new file mode 100644
index 0000000..af052fc
--- /dev/null
+++ b/web/openai/assistant/thread.go
@@ -0,0 +1,95 @@
+package assistant
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "within.website/x/web"
+)
+
+type Thread struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ CreatedAt int64 `json:"created_at"`
+ Metadata map[string]string `json:"metadata"`
+}
+
+func (c *Client) CreateThread(ctx context.Context) (*Thread, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.openai.com/v1/threads", nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ var t Thread
+ if err := json.NewDecoder(resp.Body).Decode(&t); err != nil {
+ return nil, err
+ }
+
+ return &t, nil
+}
+
+func (c *Client) GetThread(ctx context.Context, id string) (*Thread, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.openai.com/v1/threads/"+id, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ var t Thread
+ if err := json.NewDecoder(resp.Body).Decode(&t); err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, web.NewError(http.StatusOK, resp)
+ }
+
+ return &t, nil
+}
+
+func (c *Client) UpdateThread(ctx context.Context, id string, metadata map[string]string) error {
+ req, err := http.NewRequestWithContext(ctx, http.MethodPatch, "https://api.openai.com/v1/threads/"+id, nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return web.NewError(http.StatusOK, resp)
+ }
+
+ return nil
+}
+
+func (c *Client) DeleteThread(ctx context.Context, id string) error {
+ req, err := http.NewRequestWithContext(ctx, http.MethodDelete, "https://api.openai.com/v1/threads/"+id, nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := c.Do(req)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ return web.NewError(http.StatusOK, resp)
+ }
+
+ return nil
+}
diff --git a/web/openai/models.go b/web/openai/models.go
new file mode 100644
index 0000000..423c11b
--- /dev/null
+++ b/web/openai/models.go
@@ -0,0 +1,40 @@
+package openai
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+)
+
+type Model struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ Created int64 `json:"created"`
+ OwnedBy string `json:"owned_by"`
+}
+
+// ListModels returns a list of all models available to the user.
+func ListModels(ctx context.Context, cli *http.Client, openAIKey string) ([]Model, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", "https://api.openai.com/v1/models", nil)
+ if err != nil {
+ return nil, err
+ }
+
+ type response struct {
+ Object string `json:"object"`
+ Data []Model `json:"data"`
+ }
+
+ resp, err := cli.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var result response
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return nil, err
+ }
+
+ return result.Data, nil
+}