├── .github ├── release-drafter.yml └── workflows │ ├── ci.yml │ └── required-labels.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── audio.go ├── audio_rooms.go ├── audio_rooms_test.go ├── audio_speech.go ├── audio_speech_test.go ├── audio_transcription.go ├── audio_transcription_test.go ├── audio_voices.go ├── audio_voices_test.go ├── auth.go ├── auth_test.go ├── auth_token.go ├── auth_token_test.go ├── base_model.go ├── bots.go ├── bots_test.go ├── chats.go ├── chats_messages.go ├── chats_messages_test.go ├── chats_test.go ├── client.go ├── client_test.go ├── codecov.yml ├── common.go ├── const.go ├── conversations.go ├── conversations_messages.go ├── conversations_messages_test.go ├── conversations_test.go ├── datasets.go ├── datasets_documents.go ├── datasets_documents_test.go ├── datasets_images.go ├── datasets_images_test.go ├── datasets_test.go ├── error.go ├── error_test.go ├── examples ├── audio │ ├── rooms │ │ └── create │ │ │ └── main.go │ ├── speech │ │ ├── create │ │ │ └── main.go │ │ └── transcription │ │ │ └── main.go │ └── voices │ │ ├── clone │ │ └── main.go │ │ └── list │ │ └── main.go ├── auth │ ├── device_oauth │ │ └── main.go │ ├── error │ │ └── main.go │ ├── jwt_oauth │ │ └── main.go │ ├── pkce_oauth │ │ └── main.go │ ├── token │ │ └── main.go │ └── web_oauth │ │ └── main.go ├── bots │ ├── publish │ │ └── main.go │ └── retrieve │ │ └── main.go ├── chats │ ├── chat │ │ └── main.go │ ├── chat_with_image │ │ └── main.go │ ├── stream │ │ └── main.go │ └── submit_tool_output │ │ └── main.go ├── client │ ├── error │ │ └── main.go │ ├── init │ │ └── main.go │ └── log │ │ └── main.go ├── conversations │ ├── crud │ │ └── main.go │ ├── list │ │ └── main.go │ └── messages │ │ ├── crud │ │ └── main.go │ │ └── list │ │ └── main.go ├── datasets │ ├── crud │ │ └── main.go │ ├── documents │ │ ├── crud │ │ │ └── main.go │ │ └── list │ │ │ └── main.go │ ├── image │ │ ├── crud │ │ │ └── main.go │ │ └── list │ │ │ └── main.go │ └── list │ │ └── main.go ├── files │ └── main.go ├── template │ └── duplicate │ │ └── main.go ├── workflow │ └── chat │ │ └── stream │ │ └── main.go ├── workflows │ └── runs │ │ ├── async_run │ │ └── main.go │ │ ├── create │ │ └── main.go │ │ └── stream │ │ └── main.go └── workspaces │ └── list │ └── main.go ├── files.go ├── files_test.go ├── go.mod ├── go.sum ├── logger.go ├── pagination.go ├── pagination_test.go ├── request.go ├── request_test.go ├── stream_reader.go ├── stream_reader_test.go ├── templates.go ├── user_agent.go ├── users.go ├── users_test.go ├── utils.go ├── utils_test.go ├── variables.go ├── variables_test.go ├── workflows.go ├── workflows_chat.go ├── workflows_chat_test.go ├── workflows_runs.go ├── workflows_runs_histories.go ├── workflows_runs_histories_test.go ├── workflows_runs_test.go ├── workspaces.go └── workspaces_test.go /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: '🐛 Bug Fixes' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - title: '🧰 Maintenance' 14 | labels: 15 | - 'chore' 16 | - 'documentation' 17 | autolabeler: 18 | - label: 'chore' 19 | files: 20 | - '*.md' 21 | branch: 22 | - '/docs{0,1}\/.+/' 23 | - label: 'bug' 24 | branch: 25 | - '/fix\/.+/' 26 | title: 27 | - '/fix/i' 28 | - label: 'enhancement' 29 | branch: 30 | - '/feature\/.+/' 31 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 32 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 33 | version-resolver: 34 | major: 35 | labels: 36 | - 'major' 37 | minor: 38 | labels: 39 | - 'minor' 40 | patch: 41 | labels: 42 | - 'patch' 43 | default: patch 44 | template: | 45 | ## Changes 46 | 47 | $CHANGES 48 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | go-version: [ '1.18', '1.19', '1.20', '1.21', '1.22' , '1.23'] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | 25 | - name: Install dependencies 26 | run: go mod download 27 | 28 | go install mvdan.cc/gofumpt@v0.5.0 29 | 30 | - name: Run golangci-lint 31 | uses: golangci/golangci-lint-action@v3 32 | with: 33 | version: latest 34 | args: --out-format=colored-line-number 35 | 36 | - name: Run go build 37 | run: | 38 | go build ./... 39 | 40 | - name: Run gofumpt 41 | run: | 42 | if ! test -z "$(gofumpt -d -e . | tee /dev/stderr)"; then 43 | echo "❗️ gofumpt check failed" 44 | exit 1 45 | fi 46 | 47 | - name: Run tests with coverage 48 | run: | 49 | go test -race -coverprofile=coverage.out $(go list ./... | grep -v /examples/) 50 | go tool cover -func=coverage.out 51 | 52 | - name: Check coverage threshold 53 | run: | 54 | COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print substr($3, 1, length($3)-1)}') 55 | THRESHOLD=80 56 | if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then 57 | echo "Code coverage $COVERAGE% is below threshold of $THRESHOLD%" 58 | exit 1 59 | fi 60 | echo "Code coverage $COVERAGE% is above threshold of $THRESHOLD%" 61 | 62 | - name: Upload coverage to Codecov 63 | uses: codecov/codecov-action@v3 64 | with: 65 | token: ${{ secrets.CODECOV_TOKEN }} 66 | test_success: 67 | # this aggregates success state of all jobs listed in `needs` 68 | # this is the only required check to pass CI 69 | name: "Test success" 70 | if: always() 71 | runs-on: ubuntu-latest 72 | needs: [ test ] 73 | steps: 74 | - name: "Success" 75 | if: needs.test.result == 'success' 76 | run: true 77 | shell: bash 78 | - name: "Failure" 79 | if: needs.test.result != 'success' 80 | run: false 81 | shell: bash 82 | 83 | draft: 84 | runs-on: ubuntu-latest 85 | needs: test_success 86 | if: github.ref == 'refs/heads/main' 87 | steps: 88 | - uses: release-drafter/release-drafter@v5 89 | env: 90 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/required-labels.yml: -------------------------------------------------------------------------------- 1 | name: Required Labels 2 | on: 3 | pull_request: 4 | types: [opened, labeled, unlabeled, synchronize] 5 | jobs: 6 | label: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: mheap/github-action-required-labels@v5 13 | with: 14 | mode: minimum 15 | count: 1 16 | labels: | 17 | feature 18 | enhancement 19 | fix 20 | bugfix 21 | bug 22 | chore 23 | documentation 24 | add_comment: true 25 | message: "Requires label: feature, enhancement, fix, bugfix, bug, chore, documentation." -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | /.idea 27 | /.vscode 28 | /output 29 | coverage.out -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting up the environment 2 | 3 | First, make sure you have Go installed (version 1.18 or higher). You can download it from [go.dev](https://go.dev/dl/). 4 | 5 | ## Dependencies Management 6 | 7 | We use Go modules for dependency management. 8 | 9 | Install dependencies: 10 | 11 | ```shell 12 | go mod tidy 13 | ``` 14 | 15 | ## Running Tests 16 | 17 | Run all tests: 18 | 19 | ```shell 20 | go test ./... 21 | ``` 22 | 23 | Run tests with coverage: 24 | 25 | ```shell 26 | go test -coverprofile=coverage.out ./... 27 | go tool cover -html=coverage.out # View coverage report in browser 28 | ``` 29 | 30 | Check code format: 31 | ```shell 32 | gofumpt -l -w . 33 | ``` 34 | 35 | ## Building the project 36 | 37 | To build the project: 38 | 39 | ```shell 40 | go build ./... 41 | ``` 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Spring (SG) Pte. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /audio.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | // AudioFormat represents the audio format type 4 | type AudioFormat string 5 | 6 | const ( 7 | AudioFormatWAV AudioFormat = "wav" 8 | AudioFormatPCM AudioFormat = "pcm" 9 | AudioFormatOGGOPUS AudioFormat = "ogg_opus" 10 | AudioFormatM4A AudioFormat = "m4a" 11 | AudioFormatAAC AudioFormat = "aac" 12 | AudioFormatMP3 AudioFormat = "mp3" 13 | ) 14 | 15 | func (f AudioFormat) String() string { 16 | return string(f) 17 | } 18 | 19 | func (f AudioFormat) Ptr() *AudioFormat { 20 | return &f 21 | } 22 | 23 | // LanguageCode represents the language code 24 | type LanguageCode string 25 | 26 | const ( 27 | LanguageCodeZH LanguageCode = "zh" 28 | LanguageCodeEN LanguageCode = "en" 29 | LanguageCodeJA LanguageCode = "ja" 30 | LanguageCodeES LanguageCode = "es" 31 | LanguageCodeID LanguageCode = "id" 32 | LanguageCodePT LanguageCode = "pt" 33 | ) 34 | 35 | func (l LanguageCode) String() string { 36 | return string(l) 37 | } 38 | 39 | type audio struct { 40 | Rooms *audioRooms 41 | Speech *audioSpeech 42 | Voices *audioVoices 43 | Transcriptions *audioTranscriptions 44 | } 45 | 46 | func newAudio(core *core) *audio { 47 | return &audio{ 48 | Rooms: newRooms(core), 49 | Speech: newSpeech(core), 50 | Voices: newVoice(core), 51 | Transcriptions: newTranscriptions(core), 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /audio_rooms.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | func (r *audioRooms) Create(ctx context.Context, req *CreateAudioRoomsReq) (*CreateAudioRoomsResp, error) { 9 | method := http.MethodPost 10 | uri := "/v1/audio/rooms" 11 | resp := &createAudioRoomsResp{} 12 | if err := r.core.Request(ctx, method, uri, req, resp); err != nil { 13 | return nil, err 14 | } 15 | resp.Data.setHTTPResponse(resp.HTTPResponse) 16 | return resp.Data, nil 17 | } 18 | 19 | type audioRooms struct { 20 | core *core 21 | } 22 | 23 | func newRooms(core *core) *audioRooms { 24 | return &audioRooms{core: core} 25 | } 26 | 27 | // AudioCodec represents the audio codec 28 | type AudioCodec string 29 | 30 | const ( 31 | AudioCodecAACLC AudioCodec = "AACLC" 32 | AudioCodecG711A AudioCodec = "G711A" 33 | AudioCodecOPUS AudioCodec = "OPUS" 34 | AudioCodecG722 AudioCodec = "G722" 35 | ) 36 | 37 | // CreateAudioRoomsReq represents the request for creating an audio room 38 | type CreateAudioRoomsReq struct { 39 | BotID string `json:"bot_id"` 40 | ConversationID string `json:"conversation_id,omitempty"` 41 | VoiceID string `json:"voice_id,omitempty"` 42 | UID string `json:"uid,omitempty"` 43 | WorkflowID string `json:"workflow_id,omitempty"` 44 | Config *RoomConfig `json:"config,omitempty"` 45 | } 46 | 47 | // RoomConfig represents the room configuration 48 | type RoomConfig struct { 49 | AudioConfig *RoomAudioConfig `json:"audio_config,omitempty"` 50 | VideoConfig *RoomVideoConfig `json:"video_config,omitempty"` 51 | PrologueContent string `json:"prologue_content,omitempty"` 52 | } 53 | 54 | // VideoCodec represents the video codec 55 | type VideoCodec string 56 | 57 | const ( 58 | VideoCodecH264 VideoCodec = "H264" 59 | VideoCodecBYTEVC1 VideoCodec = "BYTEVC1" 60 | ) 61 | 62 | // StreamVideoType represents the stream video type 63 | type StreamVideoType string 64 | 65 | const ( 66 | StreamVideoTypeMain StreamVideoType = "main" 67 | StreamVideoTypeScreen StreamVideoType = "screen" 68 | ) 69 | 70 | // RoomVideoConfig represents the room video configuration 71 | type RoomVideoConfig struct { 72 | Codec VideoCodec `json:"codec,omitempty"` 73 | StreamVideoType StreamVideoType `json:"stream_video_type,omitempty"` 74 | } 75 | 76 | // RoomAudioConfig represents the room audio configuration 77 | type RoomAudioConfig struct { 78 | Codec AudioCodec `json:"codec"` 79 | } 80 | 81 | // createAudioRoomsResp represents the response for creating an audio room 82 | type createAudioRoomsResp struct { 83 | baseResponse 84 | Data *CreateAudioRoomsResp `json:"data"` 85 | } 86 | 87 | // CreateAudioRoomsResp represents the response for creating an audio room 88 | type CreateAudioRoomsResp struct { 89 | baseModel 90 | RoomID string `json:"room_id"` 91 | AppID string `json:"app_id"` 92 | Token string `json:"token"` 93 | UID string `json:"uid"` 94 | } 95 | -------------------------------------------------------------------------------- /audio_rooms_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestAudioRooms(t *testing.T) { 13 | // Test Create method 14 | t.Run("Create audio room success", func(t *testing.T) { 15 | mockTransport := &mockTransport{ 16 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 17 | // Verify request method and path 18 | assert.Equal(t, http.MethodPost, req.Method) 19 | assert.Equal(t, "/v1/audio/rooms", req.URL.Path) 20 | 21 | // Return mock response 22 | return mockResponse(http.StatusOK, &createAudioRoomsResp{ 23 | Data: &CreateAudioRoomsResp{ 24 | RoomID: "room1", 25 | AppID: "app1", 26 | Token: "token1", 27 | UID: "uid1", 28 | }, 29 | }) 30 | }, 31 | } 32 | 33 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 34 | rooms := newRooms(core) 35 | 36 | // Test with all optional fields 37 | resp, err := rooms.Create(context.Background(), &CreateAudioRoomsReq{ 38 | BotID: "bot1", 39 | ConversationID: "conv1", 40 | VoiceID: "voice1", 41 | WorkflowID: "workflow1", 42 | Config: &RoomConfig{ 43 | AudioConfig: &RoomAudioConfig{ 44 | Codec: AudioCodecOPUS, 45 | }, 46 | VideoConfig: &RoomVideoConfig{ 47 | Codec: VideoCodecH264, 48 | StreamVideoType: StreamVideoTypeMain, 49 | }, 50 | PrologueContent: "Hello Coze", 51 | }, 52 | }) 53 | 54 | require.NoError(t, err) 55 | assert.Equal(t, "test_log_id", resp.LogID()) 56 | assert.Equal(t, "room1", resp.RoomID) 57 | assert.Equal(t, "app1", resp.AppID) 58 | assert.Equal(t, "token1", resp.Token) 59 | assert.Equal(t, "uid1", resp.UID) 60 | }) 61 | 62 | // Test Create method with minimal fields 63 | t.Run("Create audio room with minimal fields", func(t *testing.T) { 64 | mockTransport := &mockTransport{ 65 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 66 | // Return mock response 67 | return mockResponse(http.StatusOK, &createAudioRoomsResp{ 68 | Data: &CreateAudioRoomsResp{ 69 | RoomID: "room1", 70 | AppID: "app1", 71 | Token: "token1", 72 | UID: "uid1", 73 | }, 74 | }) 75 | }, 76 | } 77 | 78 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 79 | rooms := newRooms(core) 80 | 81 | // Test with only required fields 82 | resp, err := rooms.Create(context.Background(), &CreateAudioRoomsReq{ 83 | BotID: "bot1", 84 | }) 85 | 86 | require.NoError(t, err) 87 | assert.Equal(t, "test_log_id", resp.LogID()) 88 | assert.Equal(t, "room1", resp.RoomID) 89 | }) 90 | 91 | // Test Create method with error 92 | t.Run("Create audio room with error", func(t *testing.T) { 93 | mockTransport := &mockTransport{ 94 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 95 | // Return error response 96 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 97 | }, 98 | } 99 | 100 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 101 | rooms := newRooms(core) 102 | 103 | resp, err := rooms.Create(context.Background(), &CreateAudioRoomsReq{ 104 | BotID: "invalid_bot", 105 | }) 106 | 107 | require.Error(t, err) 108 | assert.Nil(t, resp) 109 | }) 110 | } 111 | 112 | func TestAudioCodec(t *testing.T) { 113 | t.Run("AudioCodec constants", func(t *testing.T) { 114 | assert.Equal(t, AudioCodec("AACLC"), AudioCodecAACLC) 115 | assert.Equal(t, AudioCodec("G711A"), AudioCodecG711A) 116 | assert.Equal(t, AudioCodec("OPUS"), AudioCodecOPUS) 117 | assert.Equal(t, AudioCodec("G722"), AudioCodecG722) 118 | }) 119 | } 120 | 121 | func TestVideoCodec(t *testing.T) { 122 | t.Run("VideoCodec constants", func(t *testing.T) { 123 | assert.Equal(t, VideoCodec("H264"), VideoCodecH264) 124 | assert.Equal(t, VideoCodec("BYTEVC1"), VideoCodecBYTEVC1) 125 | }) 126 | } 127 | 128 | func TestStreamVideoType(t *testing.T) { 129 | t.Run("StreamVideoType constants", func(t *testing.T) { 130 | assert.Equal(t, StreamVideoType("main"), StreamVideoTypeMain) 131 | assert.Equal(t, StreamVideoType("screen"), StreamVideoTypeScreen) 132 | }) 133 | } 134 | -------------------------------------------------------------------------------- /audio_speech.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | func (r *audioSpeech) Create(ctx context.Context, req *CreateAudioSpeechReq) (*CreateAudioSpeechResp, error) { 11 | uri := "/v1/audio/speech" 12 | resp, err := r.core.RawRequest(ctx, http.MethodPost, uri, req) 13 | if err != nil { 14 | return nil, err 15 | } 16 | res := &CreateAudioSpeechResp{ 17 | Data: resp.Body, 18 | } 19 | res.SetHTTPResponse(newHTTPResponse(resp)) 20 | return res, nil 21 | } 22 | 23 | type audioSpeech struct { 24 | core *core 25 | } 26 | 27 | func newSpeech(core *core) *audioSpeech { 28 | return &audioSpeech{core: core} 29 | } 30 | 31 | // CreateAudioSpeechReq represents the request for creating speech 32 | type CreateAudioSpeechReq struct { 33 | Input string `json:"input"` 34 | VoiceID string `json:"voice_id"` 35 | ResponseFormat *AudioFormat `json:"response_format"` 36 | Speed *float32 `json:"speed"` 37 | SampleRate *int `json:"sample_rate"` 38 | } 39 | 40 | // CreateAudioSpeechResp represents the response for creating speech 41 | type CreateAudioSpeechResp struct { 42 | baseResponse 43 | // TODO 没有 json tag? 44 | Data io.ReadCloser 45 | } 46 | 47 | func (c *CreateAudioSpeechResp) WriteToFile(path string) error { 48 | file, err := os.Create(path) 49 | if err != nil { 50 | return err 51 | } 52 | defer file.Close() 53 | defer c.Data.Close() 54 | 55 | _, err = io.Copy(file, c.Data) 56 | return err 57 | } 58 | -------------------------------------------------------------------------------- /audio_speech_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestAudioSpeech(t *testing.T) { 15 | // Test Create method 16 | t.Run("Create speech success", func(t *testing.T) { 17 | mockTransport := &mockTransport{ 18 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 19 | // Verify request method and path 20 | assert.Equal(t, http.MethodPost, req.Method) 21 | assert.Equal(t, "/v1/audio/speech", req.URL.Path) 22 | 23 | // Return mock response with audio data 24 | resp := &http.Response{ 25 | StatusCode: http.StatusOK, 26 | Header: http.Header{}, 27 | Body: io.NopCloser(strings.NewReader("mock audio data")), 28 | } 29 | resp.Header.Set(httpLogIDKey, "test_log_id") 30 | return resp, nil 31 | }, 32 | } 33 | 34 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 35 | speech := newSpeech(core) 36 | 37 | resp, err := speech.Create(context.Background(), &CreateAudioSpeechReq{ 38 | Input: "Hello, world!", 39 | VoiceID: "voice1", 40 | ResponseFormat: AudioFormatMP3.Ptr(), 41 | Speed: ptr[float32](1.0), 42 | }) 43 | 44 | require.NoError(t, err) 45 | assert.Equal(t, "test_log_id", resp.HTTPResponse.LogID()) 46 | 47 | // Read and verify response body 48 | data, err := io.ReadAll(resp.Data) 49 | require.NoError(t, err) 50 | assert.Equal(t, "mock audio data", string(data)) 51 | resp.Data.Close() 52 | }) 53 | 54 | // Test Create method with error 55 | t.Run("Create speech with error", func(t *testing.T) { 56 | mockTransport := &mockTransport{ 57 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 58 | // Return error response 59 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 60 | }, 61 | } 62 | 63 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 64 | speech := newSpeech(core) 65 | 66 | resp, err := speech.Create(context.Background(), &CreateAudioSpeechReq{ 67 | Input: "Hello, world!", 68 | VoiceID: "invalid_voice", 69 | ResponseFormat: AudioFormatMP3.Ptr(), 70 | Speed: ptr[float32](1.0), 71 | }) 72 | 73 | require.Error(t, err) 74 | assert.Nil(t, resp) 75 | }) 76 | 77 | // Test Create method with invalid speed 78 | t.Run("Create speech with invalid speed", func(t *testing.T) { 79 | mockTransport := &mockTransport{ 80 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 81 | // Return error response for invalid speed 82 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 83 | }, 84 | } 85 | 86 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 87 | speech := newSpeech(core) 88 | 89 | resp, err := speech.Create(context.Background(), &CreateAudioSpeechReq{ 90 | Input: "Hello, world!", 91 | VoiceID: "voice1", 92 | ResponseFormat: AudioFormatMP3.Ptr(), 93 | Speed: ptr[float32](-1.0), // Invalid speed 94 | }) 95 | 96 | require.Error(t, err) 97 | assert.Nil(t, resp) 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /audio_transcription.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | func (r *audioTranscriptions) Create(ctx context.Context, req *AudioSpeechTranscriptionsReq) (*CreateAudioTranscriptionsResp, error) { 9 | uri := "/v1/audio/transcriptions" 10 | resp := &CreateAudioTranscriptionsResp{} 11 | if err := r.core.UploadFile(ctx, uri, req.Audio, req.Filename, nil, resp); err != nil { 12 | return nil, err 13 | } 14 | return resp, nil 15 | } 16 | 17 | type AudioSpeechTranscriptionsReq struct { 18 | Filename string `json:"filename"` 19 | Audio io.Reader `json:"audio"` 20 | } 21 | 22 | type CreateAudioTranscriptionsResp struct { 23 | baseResponse 24 | Data AudioTranscriptionsData `json:"data"` 25 | } 26 | 27 | type AudioTranscriptionsData struct { 28 | Text string `json:"text"` 29 | } 30 | 31 | type audioTranscriptions struct { 32 | core *core 33 | } 34 | 35 | func newTranscriptions(core *core) *audioTranscriptions { 36 | return &audioTranscriptions{core: core} 37 | } 38 | -------------------------------------------------------------------------------- /audio_transcription_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestAudioTranscription(t *testing.T) { 16 | // Test Transcription method 17 | t.Run("Transcriptions with different text", func(t *testing.T) { 18 | mockTransport := &mockTransport{ 19 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 20 | // Verify request method and path 21 | assert.Equal(t, http.MethodPost, req.Method) 22 | assert.Equal(t, "/v1/audio/transcriptions", req.URL.Path) 23 | result := map[string]map[string]string{ 24 | "data": { 25 | "text": "this_test", 26 | }, 27 | } 28 | v, _ := json.Marshal(result) 29 | // Return mock response with audio data 30 | resp := &http.Response{ 31 | StatusCode: http.StatusOK, 32 | Header: http.Header{}, 33 | Body: io.NopCloser(strings.NewReader(string(v))), 34 | } 35 | resp.Header.Set(httpLogIDKey, "test_log_id") 36 | return resp, nil 37 | }, 38 | } 39 | 40 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 41 | transcription := newTranscriptions(core) 42 | reader := strings.NewReader("testmp3") 43 | resp, err := transcription.Create(context.Background(), &AudioSpeechTranscriptionsReq{ 44 | Filename: "testmp3", 45 | Audio: reader, 46 | }) 47 | 48 | require.NoError(t, err) 49 | assert.Equal(t, "test_log_id", resp.HTTPResponse.LogID()) 50 | 51 | // Read and verify response body 52 | assert.Equal(t, "this_test", resp.Data.Text) 53 | }) 54 | 55 | t.Run("Transcription error", func(t *testing.T) { 56 | mockTransport := &mockTransport{ 57 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 58 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 59 | }, 60 | } 61 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 62 | transcription := newTranscriptions(core) 63 | reader := strings.NewReader("testmp3") 64 | resp, err := transcription.Create(context.Background(), &AudioSpeechTranscriptionsReq{ 65 | Filename: "testmp3", 66 | Audio: reader, 67 | }) 68 | 69 | require.Error(t, err) 70 | assert.Nil(t, resp) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /audio_voices.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | ) 10 | 11 | func (r *audioVoices) Clone(ctx context.Context, req *CloneAudioVoicesReq) (*CloneAudioVoicesResp, error) { 12 | path := "/v1/audio/voices/clone" 13 | if req.File == nil { 14 | return nil, fmt.Errorf("file is required") 15 | } 16 | 17 | fields := map[string]string{ 18 | "voice_name": req.VoiceName, 19 | "audio_format": req.AudioFormat.String(), 20 | } 21 | 22 | // Add other fields 23 | if req.Language != nil { 24 | fields["language"] = req.Language.String() 25 | } 26 | if req.VoiceID != nil { 27 | fields["voice_id"] = *req.VoiceID 28 | } 29 | if req.PreviewText != nil { 30 | fields["preview_text"] = *req.PreviewText 31 | } 32 | if req.Text != nil { 33 | fields["text"] = *req.Text 34 | } 35 | if req.Description != nil { 36 | fields["description"] = *req.Description 37 | } 38 | if req.SpaceID != nil { 39 | fields["space_id"] = *req.SpaceID 40 | } 41 | resp := &cloneAudioVoicesResp{} 42 | if err := r.core.UploadFile(ctx, path, req.File, req.VoiceName, fields, resp); err != nil { 43 | return nil, err 44 | } 45 | resp.Data.setHTTPResponse(resp.HTTPResponse) 46 | return resp.Data, nil 47 | } 48 | 49 | func (r *audioVoices) List(ctx context.Context, req *ListAudioVoicesReq) (NumberPaged[Voice], error) { 50 | if req.PageSize == 0 { 51 | req.PageSize = 20 52 | } 53 | if req.PageNum == 0 { 54 | req.PageNum = 1 55 | } 56 | return NewNumberPaged[Voice]( 57 | func(request *pageRequest) (*pageResponse[Voice], error) { 58 | uri := "/v1/audio/voices" 59 | resp := &ListAudioVoicesResp{} 60 | err := r.core.Request(ctx, http.MethodGet, uri, nil, resp, 61 | withHTTPQuery("page_num", strconv.Itoa(request.PageNum)), 62 | withHTTPQuery("page_size", strconv.Itoa(request.PageSize)), 63 | withHTTPQuery("filter_system_voice", strconv.FormatBool(req.FilterSystemVoice))) 64 | if err != nil { 65 | return nil, err 66 | } 67 | return &pageResponse[Voice]{ 68 | HasMore: len(resp.Data.VoiceList) >= request.PageSize, 69 | Data: resp.Data.VoiceList, 70 | LogID: resp.HTTPResponse.LogID(), 71 | }, nil 72 | }, req.PageSize, req.PageNum) 73 | } 74 | 75 | type audioVoices struct { 76 | core *core 77 | } 78 | 79 | func newVoice(core *core) *audioVoices { 80 | return &audioVoices{core: core} 81 | } 82 | 83 | // Voice represents the voice model 84 | type Voice struct { 85 | VoiceID string `json:"voice_id"` 86 | Name string `json:"name"` 87 | IsSystemVoice bool `json:"is_system_voice"` 88 | LanguageCode string `json:"language_code"` 89 | LanguageName string `json:"language_name"` 90 | PreviewText string `json:"preview_text"` 91 | PreviewAudio string `json:"preview_audio"` 92 | AvailableTrainingTimes int `json:"available_training_times"` 93 | CreateTime int `json:"create_time"` 94 | UpdateTime int `json:"update_time"` 95 | } 96 | 97 | // CloneAudioVoicesReq represents the request for cloning a voice 98 | type CloneAudioVoicesReq struct { 99 | VoiceName string 100 | File io.Reader 101 | AudioFormat AudioFormat 102 | Language *LanguageCode 103 | VoiceID *string 104 | PreviewText *string 105 | Text *string 106 | SpaceID *string 107 | Description *string 108 | } 109 | 110 | // cloneAudioVoicesResp represents the response for cloning a voice 111 | type cloneAudioVoicesResp struct { 112 | baseResponse 113 | Data *CloneAudioVoicesResp `json:"data"` 114 | } 115 | 116 | // CloneAudioVoicesResp represents the response for cloning a voice 117 | type CloneAudioVoicesResp struct { 118 | baseModel 119 | VoiceID string `json:"voice_id"` 120 | } 121 | 122 | // ListAudioVoicesReq represents the request for listing voices 123 | type ListAudioVoicesReq struct { 124 | FilterSystemVoice bool `json:"filter_system_voice,omitempty"` 125 | PageNum int `json:"page_num"` 126 | PageSize int `json:"page_size"` 127 | } 128 | 129 | // ListAudioVoicesResp represents the response for listing voices 130 | type ListAudioVoicesResp struct { 131 | baseResponse 132 | Data struct { 133 | VoiceList []*Voice `json:"voice_list"` 134 | } `json:"data"` 135 | } 136 | -------------------------------------------------------------------------------- /auth_token.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | type Auth interface { 9 | Token(ctx context.Context) (string, error) 10 | } 11 | 12 | var ( 13 | _ Auth = &tokenAuthImpl{} 14 | _ Auth = &jwtOAuthImpl{} 15 | ) 16 | 17 | // tokenAuthImpl implements the Auth interface with fixed access token. 18 | type tokenAuthImpl struct { 19 | accessToken string 20 | } 21 | 22 | // NewTokenAuth creates a new token authentication instance. 23 | func NewTokenAuth(accessToken string) Auth { 24 | return &tokenAuthImpl{ 25 | accessToken: accessToken, 26 | } 27 | } 28 | 29 | func getRefreshBefore(ttl int) int64 { 30 | if ttl >= 600 { 31 | return 30 32 | } else if ttl >= 60 { 33 | return 10 34 | } else if ttl >= 30 { 35 | return 5 36 | } 37 | return 0 38 | } 39 | 40 | func NewJWTAuth(client *JWTOAuthClient, opt *GetJWTAccessTokenReq) Auth { 41 | ttl := 900 42 | if opt == nil { 43 | return &jwtOAuthImpl{ 44 | TTL: ttl, 45 | client: client, 46 | refreshBefore: getRefreshBefore(ttl), 47 | } 48 | } 49 | if opt.TTL > 0 { 50 | ttl = opt.TTL 51 | } 52 | 53 | return &jwtOAuthImpl{ 54 | TTL: ttl, 55 | Scope: opt.Scope, 56 | SessionName: opt.SessionName, 57 | refreshBefore: getRefreshBefore(ttl), 58 | client: client, 59 | accountID: opt.AccountID, 60 | } 61 | } 62 | 63 | // Token returns the access token. 64 | func (r *tokenAuthImpl) Token(ctx context.Context) (string, error) { 65 | return r.accessToken, nil 66 | } 67 | 68 | type jwtOAuthImpl struct { 69 | TTL int 70 | SessionName *string 71 | Scope *Scope 72 | client *JWTOAuthClient 73 | accessToken *string 74 | expireIn int64 75 | refreshBefore int64 // refresh moment before expireIn, unit second 76 | refreshAt int64 77 | accountID *int64 78 | } 79 | 80 | func (r *jwtOAuthImpl) needRefresh() bool { 81 | return r.accessToken == nil || time.Now().Unix() > r.refreshAt 82 | } 83 | 84 | func (r *jwtOAuthImpl) Token(ctx context.Context) (string, error) { 85 | if !r.needRefresh() { 86 | return ptrValue(r.accessToken), nil 87 | } 88 | resp, err := r.client.GetAccessToken(ctx, &GetJWTAccessTokenReq{ 89 | TTL: r.TTL, 90 | SessionName: r.SessionName, 91 | Scope: r.Scope, 92 | AccountID: r.accountID, 93 | }) 94 | if err != nil { 95 | return "", err 96 | } 97 | r.accessToken = ptr(resp.AccessToken) 98 | r.expireIn = resp.ExpiresIn 99 | r.refreshAt = resp.ExpiresIn - r.refreshBefore 100 | return resp.AccessToken, nil 101 | } 102 | -------------------------------------------------------------------------------- /base_model.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import "net/http" 4 | 5 | type Responser interface { 6 | Response() HTTPResponse 7 | } 8 | 9 | type HTTPResponse interface { 10 | LogID() string 11 | } 12 | 13 | type httpResponse struct { 14 | Status int 15 | Header http.Header 16 | ContentLength int64 17 | 18 | logid string 19 | } 20 | 21 | func (r *httpResponse) LogID() string { 22 | if r.logid == "" { 23 | r.logid = r.Header.Get(httpLogIDKey) 24 | } 25 | return r.logid 26 | } 27 | 28 | type baseResponse struct { 29 | Code int `json:"code"` 30 | Msg string `json:"msg"` 31 | HTTPResponse *httpResponse `json:"http_response"` 32 | } 33 | 34 | func (r *baseResponse) SetHTTPResponse(httpResponse *httpResponse) { 35 | r.HTTPResponse = httpResponse 36 | } 37 | 38 | func (r *baseResponse) SetCode(code int) { 39 | r.Code = code 40 | } 41 | 42 | func (r *baseResponse) SetMsg(msg string) { 43 | r.Msg = msg 44 | } 45 | 46 | func (r *baseResponse) GetCode() int { 47 | return r.Code 48 | } 49 | 50 | func (r *baseResponse) GetMsg() string { 51 | return r.Msg 52 | } 53 | 54 | func (r *baseResponse) LogID() string { 55 | return r.HTTPResponse.LogID() 56 | } 57 | 58 | type baseRespInterface interface { 59 | SetHTTPResponse(httpResponse *httpResponse) 60 | SetCode(code int) 61 | SetMsg(msg string) 62 | GetMsg() string 63 | GetCode() int 64 | } 65 | 66 | type baseModel struct { 67 | httpResponse *httpResponse 68 | } 69 | 70 | func (r *baseModel) setHTTPResponse(httpResponse *httpResponse) { 71 | r.httpResponse = httpResponse 72 | } 73 | 74 | func (r *baseModel) Response() HTTPResponse { 75 | return r.httpResponse 76 | } 77 | 78 | func (r *baseModel) LogID() string { 79 | return r.httpResponse.LogID() 80 | } 81 | 82 | func newHTTPResponse(resp *http.Response) *httpResponse { 83 | return &httpResponse{ 84 | Status: resp.StatusCode, 85 | Header: resp.Header, 86 | ContentLength: resp.ContentLength, 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /chats_messages.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | func (r *chatMessages) List(ctx context.Context, req *ListChatsMessagesReq) (*ListChatsMessagesResp, error) { 9 | method := http.MethodGet 10 | uri := "/v3/chat/message/list" 11 | resp := &listChatsMessagesResp{} 12 | err := r.core.Request(ctx, method, uri, nil, resp, 13 | withHTTPQuery("conversation_id", req.ConversationID), 14 | withHTTPQuery("chat_id", req.ChatID), 15 | ) 16 | if err != nil { 17 | return nil, err 18 | } 19 | result := &ListChatsMessagesResp{ 20 | baseModel: baseModel{ 21 | httpResponse: resp.HTTPResponse, 22 | }, 23 | Messages: resp.Messages, 24 | } 25 | return result, nil 26 | } 27 | 28 | type chatMessages struct { 29 | core *core 30 | } 31 | 32 | func newChatMessages(core *core) *chatMessages { 33 | return &chatMessages{core: core} 34 | } 35 | 36 | // ListChatsMessagesReq represents the request to list messages 37 | type ListChatsMessagesReq struct { 38 | // The Conversation ID can be viewed in the 'conversation_id' field of the Response when 39 | // initiating a conversation through the Chat API. 40 | ConversationID string `json:"conversation_id"` 41 | 42 | // The Chat ID can be viewed in the 'id' field of the Response when initiating a chat through the 43 | // Chat API. If it is a streaming response, check the 'id' field in the chat event of the Response. 44 | ChatID string `json:"chat_id"` 45 | } 46 | 47 | // ListChatsMessagesResp represents the response to list messages 48 | type listChatsMessagesResp struct { 49 | baseResponse 50 | *ListChatsMessagesResp 51 | } 52 | 53 | type ListChatsMessagesResp struct { 54 | baseModel 55 | Messages []*Message `json:"data"` 56 | } 57 | -------------------------------------------------------------------------------- /chats_messages_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestChatMessages(t *testing.T) { 13 | t.Run("List messages success", func(t *testing.T) { 14 | mockTransport := &mockTransport{ 15 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 16 | // 验证请求方法和路径 17 | assert.Equal(t, http.MethodGet, req.Method) 18 | assert.Equal(t, "/v3/chat/message/list", req.URL.Path) 19 | 20 | // 验证查询参数 21 | assert.Equal(t, "test_conversation_id", req.URL.Query().Get("conversation_id")) 22 | assert.Equal(t, "test_chat_id", req.URL.Query().Get("chat_id")) 23 | 24 | // 返回模拟响应 25 | return mockResponse(http.StatusOK, &listChatsMessagesResp{ 26 | ListChatsMessagesResp: &ListChatsMessagesResp{ 27 | Messages: []*Message{ 28 | { 29 | ID: "msg1", 30 | ConversationID: "test_conversation_id", 31 | Role: "user", 32 | Content: "Hello", 33 | }, 34 | { 35 | ID: "msg2", 36 | ConversationID: "test_conversation_id", 37 | Role: "assistant", 38 | Content: "Hi there!", 39 | }, 40 | }, 41 | }, 42 | }) 43 | }, 44 | } 45 | 46 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 47 | messages := newChatMessages(core) 48 | 49 | resp, err := messages.List(context.Background(), &ListChatsMessagesReq{ 50 | ConversationID: "test_conversation_id", 51 | ChatID: "test_chat_id", 52 | }) 53 | 54 | require.NoError(t, err) 55 | assert.Equal(t, "test_log_id", resp.LogID()) 56 | require.Len(t, resp.Messages, 2) 57 | 58 | // 验证第一条消息 59 | assert.Equal(t, "msg1", resp.Messages[0].ID) 60 | assert.Equal(t, "test_conversation_id", resp.Messages[0].ConversationID) 61 | assert.Equal(t, "user", resp.Messages[0].Role.String()) 62 | assert.Equal(t, "Hello", resp.Messages[0].Content) 63 | 64 | // 验证第二条消息 65 | assert.Equal(t, "msg2", resp.Messages[1].ID) 66 | assert.Equal(t, "test_conversation_id", resp.Messages[1].ConversationID) 67 | assert.Equal(t, "assistant", resp.Messages[1].Role.String()) 68 | assert.Equal(t, "Hi there!", resp.Messages[1].Content) 69 | }) 70 | 71 | t.Run("List messages with error", func(t *testing.T) { 72 | mockTransport := &mockTransport{ 73 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 74 | // 返回错误响应 75 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 76 | }, 77 | } 78 | 79 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 80 | messages := newChatMessages(core) 81 | 82 | resp, err := messages.List(context.Background(), &ListChatsMessagesReq{ 83 | ConversationID: "invalid_conversation_id", 84 | ChatID: "invalid_chat_id", 85 | }) 86 | 87 | require.Error(t, err) 88 | assert.Nil(t, resp) 89 | }) 90 | 91 | t.Run("List messages with empty response", func(t *testing.T) { 92 | mockTransport := &mockTransport{ 93 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 94 | return mockResponse(http.StatusOK, &listChatsMessagesResp{ 95 | ListChatsMessagesResp: &ListChatsMessagesResp{ 96 | Messages: []*Message{}, 97 | }, 98 | }) 99 | }, 100 | } 101 | 102 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 103 | messages := newChatMessages(core) 104 | 105 | resp, err := messages.List(context.Background(), &ListChatsMessagesReq{ 106 | ConversationID: "test_conversation_id", 107 | ChatID: "test_chat_id", 108 | }) 109 | 110 | require.NoError(t, err) 111 | assert.Equal(t, "test_log_id", resp.LogID()) 112 | assert.Empty(t, resp.Messages) 113 | }) 114 | 115 | t.Run("List messages with missing parameters", func(t *testing.T) { 116 | mockTransport := &mockTransport{ 117 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 118 | // 验证缺失的参数 119 | assert.Empty(t, req.URL.Query().Get("conversation_id")) 120 | assert.Empty(t, req.URL.Query().Get("chat_id")) 121 | 122 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 123 | }, 124 | } 125 | 126 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 127 | messages := newChatMessages(core) 128 | 129 | resp, err := messages.List(context.Background(), &ListChatsMessagesReq{}) 130 | 131 | require.Error(t, err) 132 | assert.Nil(t, resp) 133 | }) 134 | } 135 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | type CozeAPI struct { 9 | Audio *audio 10 | Bots *bots 11 | Chat *chat 12 | Conversations *conversations 13 | Workflows *workflows 14 | Workspaces *workspace 15 | Datasets *datasets 16 | Files *files 17 | Templates *templates 18 | Users *users 19 | Variables *variables 20 | baseURL string 21 | } 22 | 23 | type clientOption struct { 24 | baseURL string 25 | client HTTPClient 26 | logLevel LogLevel 27 | auth Auth 28 | enableLogID bool 29 | } 30 | 31 | type CozeAPIOption func(*clientOption) 32 | 33 | // WithBaseURL adds the base URL for the API 34 | func WithBaseURL(baseURL string) CozeAPIOption { 35 | return func(opt *clientOption) { 36 | opt.baseURL = baseURL 37 | } 38 | } 39 | 40 | // WithHttpClient sets a custom HTTP core 41 | func WithHttpClient(client HTTPClient) CozeAPIOption { 42 | return func(opt *clientOption) { 43 | opt.client = client 44 | } 45 | } 46 | 47 | // WithLogLevel sets the logging level 48 | func WithLogLevel(level LogLevel) CozeAPIOption { 49 | return func(opt *clientOption) { 50 | opt.logLevel = level 51 | } 52 | } 53 | 54 | func WithLogger(logger Logger) CozeAPIOption { 55 | return func(opt *clientOption) { 56 | setLogger(logger) 57 | } 58 | } 59 | 60 | func WithEnableLogID(enableLogID bool) CozeAPIOption { 61 | return func(opt *clientOption) { 62 | opt.enableLogID = enableLogID 63 | } 64 | } 65 | 66 | func NewCozeAPI(auth Auth, opts ...CozeAPIOption) CozeAPI { 67 | opt := &clientOption{ 68 | baseURL: ComBaseURL, 69 | client: nil, 70 | logLevel: LogLevelInfo, // Default log level is Info 71 | auth: auth, 72 | } 73 | for _, option := range opts { 74 | option(opt) 75 | } 76 | if opt.client == nil { 77 | opt.client = &http.Client{ 78 | Timeout: time.Second * 5, 79 | } 80 | } 81 | 82 | core := newCore(opt) 83 | setLevel(opt.logLevel) 84 | 85 | cozeClient := CozeAPI{ 86 | Audio: newAudio(core), 87 | Bots: newBots(core), 88 | Chat: newChats(core), 89 | Conversations: newConversations(core), 90 | Workflows: newWorkflows(core), 91 | Workspaces: newWorkspace(core), 92 | Datasets: newDatasets(core), 93 | Files: newFiles(core), 94 | Templates: newTemplates(core), 95 | Users: newUsers(core), 96 | Variables: newVariables(core), 97 | baseURL: opt.baseURL, 98 | } 99 | return cozeClient 100 | } 101 | 102 | type authTransport struct { 103 | auth Auth 104 | next http.RoundTripper 105 | } 106 | 107 | func (h *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { 108 | if isAuthContext(req.Context()) { 109 | return h.next.RoundTrip(req) 110 | } 111 | accessToken, err := h.auth.Token(req.Context()) 112 | if err != nil { 113 | logger.Errorf(req.Context(), "Failed to get access token: %v", err) 114 | return nil, err 115 | } 116 | req.Header.Set("Authorization", "Bearer "+accessToken) 117 | 118 | return h.next.RoundTrip(req) 119 | } 120 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | // mockAuth implements Auth interface for testing 13 | type mockAuth struct { 14 | token string 15 | err error 16 | } 17 | 18 | func (m *mockAuth) Token(ctx context.Context) (string, error) { 19 | return m.token, m.err 20 | } 21 | 22 | func TestNewCozeAPI(t *testing.T) { 23 | // Test default initialization 24 | t.Run("default initialization", func(t *testing.T) { 25 | auth := &mockAuth{token: "test_token"} 26 | api := NewCozeAPI(auth) 27 | 28 | assert.Equal(t, ComBaseURL, api.baseURL) 29 | assert.NotNil(t, api.Audio) 30 | assert.NotNil(t, api.Bots) 31 | assert.NotNil(t, api.Chat) 32 | assert.NotNil(t, api.Conversations) 33 | assert.NotNil(t, api.Workflows) 34 | assert.NotNil(t, api.Workspaces) 35 | assert.NotNil(t, api.Datasets) 36 | assert.NotNil(t, api.Files) 37 | }) 38 | 39 | // Test with custom base URL 40 | t.Run("custom base URL", func(t *testing.T) { 41 | auth := &mockAuth{token: "test_token"} 42 | customURL := "https://custom.api.coze.com" 43 | api := NewCozeAPI(auth, WithBaseURL(customURL)) 44 | 45 | assert.Equal(t, customURL, api.baseURL) 46 | }) 47 | 48 | // Test with custom HTTP core 49 | t.Run("custom HTTP core", func(t *testing.T) { 50 | auth := &mockAuth{token: "test_token"} 51 | customClient := &http.Client{ 52 | Timeout: 30, 53 | } 54 | api := NewCozeAPI(auth, WithHttpClient(customClient)) 55 | 56 | assert.NotNil(t, api) 57 | }) 58 | 59 | // Test with custom log level 60 | t.Run("custom log level", func(t *testing.T) { 61 | auth := &mockAuth{token: "test_token"} 62 | api := NewCozeAPI(auth, WithLogLevel(LogLevelDebug)) 63 | 64 | assert.NotNil(t, api) 65 | }) 66 | 67 | // Test with custom logger 68 | t.Run("custom logger", func(t *testing.T) { 69 | auth := &mockAuth{token: "test_token"} 70 | customLogger := &mockLogger{} 71 | api := NewCozeAPI(auth, WithLogger(customLogger)) 72 | 73 | assert.NotNil(t, api) 74 | }) 75 | 76 | // Test with multiple options 77 | t.Run("multiple options", func(t *testing.T) { 78 | auth := &mockAuth{token: "test_token"} 79 | customURL := "https://custom.api.coze.com" 80 | customClient := &http.Client{ 81 | Timeout: 30, 82 | } 83 | customLogger := &mockLogger{} 84 | 85 | api := NewCozeAPI(auth, 86 | WithBaseURL(customURL), 87 | WithHttpClient(customClient), 88 | WithLogLevel(LogLevelDebug), 89 | WithLogger(customLogger), 90 | ) 91 | 92 | assert.Equal(t, customURL, api.baseURL) 93 | assert.NotNil(t, api) 94 | }) 95 | } 96 | 97 | func TestAuthTransport(t *testing.T) { 98 | // Test successful authentication 99 | t.Run("successful authentication", func(t *testing.T) { 100 | auth := &mockAuth{token: "test_token"} 101 | transport := &authTransport{ 102 | auth: auth, 103 | next: &mockTransport{ 104 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 105 | // Verify authorization header 106 | assert.Equal(t, "Bearer test_token", req.Header.Get("Authorization")) 107 | return &http.Response{StatusCode: http.StatusOK}, nil 108 | }, 109 | }, 110 | } 111 | 112 | req, _ := http.NewRequest(http.MethodGet, ComBaseURL, nil) 113 | resp, err := transport.RoundTrip(req) 114 | 115 | require.NoError(t, err) 116 | assert.Equal(t, http.StatusOK, resp.StatusCode) 117 | }) 118 | 119 | // Test authentication error 120 | t.Run("authentication error", func(t *testing.T) { 121 | auth := &mockAuth{ 122 | token: "", 123 | err: assert.AnError, 124 | } 125 | transport := &authTransport{ 126 | auth: auth, 127 | next: http.DefaultTransport, 128 | } 129 | 130 | req, _ := http.NewRequest(http.MethodGet, ComBaseURL, nil) 131 | resp, err := transport.RoundTrip(req) 132 | 133 | require.Error(t, err) 134 | assert.Nil(t, resp) 135 | }) 136 | } 137 | 138 | // mockLogger implements log.Logger interface for testing 139 | type mockLogger struct{} 140 | 141 | func (m *mockLogger) Log(ctx context.Context, level LogLevel, message string, args ...interface{}) { 142 | } 143 | 144 | func (m *mockLogger) Errorf(format string, args ...interface{}) {} 145 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - ".github" 3 | - "examples" -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | const ( 4 | ComBaseURL = "https://api.coze.com" 5 | CnBaseURL = "https://api.coze.cn" 6 | ) 7 | 8 | const ( 9 | httpLogIDKey = "X-Tt-Logid" 10 | ctxLogIDKey = "K_LOGID" 11 | authorizeHeader = "Authorization" 12 | ) 13 | -------------------------------------------------------------------------------- /conversations.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | ) 9 | 10 | func (r *conversations) List(ctx context.Context, req *ListConversationsReq) (NumberPaged[Conversation], error) { 11 | if req.PageSize == 0 { 12 | req.PageSize = 20 13 | } 14 | if req.PageNum == 0 { 15 | req.PageNum = 1 16 | } 17 | return NewNumberPaged[Conversation]( 18 | func(request *pageRequest) (*pageResponse[Conversation], error) { 19 | uri := "/v1/conversations" 20 | resp := &listConversationsResp{} 21 | err := r.client.Request(ctx, http.MethodGet, uri, nil, resp, 22 | withHTTPQuery("bot_id", req.BotID), 23 | withHTTPQuery("page_num", strconv.Itoa(request.PageNum)), 24 | withHTTPQuery("page_size", strconv.Itoa(request.PageSize))) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &pageResponse[Conversation]{ 29 | HasMore: resp.Data.HasMore, 30 | Data: resp.Data.Conversations, 31 | LogID: resp.HTTPResponse.LogID(), 32 | }, nil 33 | }, req.PageSize, req.PageNum) 34 | } 35 | 36 | func (r *conversations) Create(ctx context.Context, req *CreateConversationsReq) (*CreateConversationsResp, error) { 37 | uri := "/v1/conversation/create" 38 | resp := &createConversationsResp{} 39 | err := r.client.Request(ctx, http.MethodPost, uri, req, resp) 40 | if err != nil { 41 | return nil, err 42 | } 43 | resp.Conversation.setHTTPResponse(resp.HTTPResponse) 44 | return resp.Conversation, nil 45 | } 46 | 47 | func (r *conversations) Retrieve(ctx context.Context, req *RetrieveConversationsReq) (*RetrieveConversationsResp, error) { 48 | uri := "/v1/conversation/retrieve" 49 | resp := &retrieveConversationsResp{} 50 | err := r.client.Request(ctx, http.MethodGet, uri, nil, resp, withHTTPQuery("conversation_id", req.ConversationID)) 51 | if err != nil { 52 | return nil, err 53 | } 54 | resp.Conversation.setHTTPResponse(resp.HTTPResponse) 55 | return resp.Conversation, nil 56 | } 57 | 58 | func (r *conversations) Clear(ctx context.Context, req *ClearConversationsReq) (*ClearConversationsResp, error) { 59 | uri := fmt.Sprintf("/v1/conversations/%s/clear", req.ConversationID) 60 | resp := &clearConversationsResp{} 61 | err := r.client.Request(ctx, http.MethodPost, uri, nil, resp) 62 | if err != nil { 63 | return nil, err 64 | } 65 | resp.Data.setHTTPResponse(resp.HTTPResponse) 66 | return resp.Data, nil 67 | } 68 | 69 | type conversations struct { 70 | client *core 71 | Messages *conversationsMessages 72 | } 73 | 74 | func newConversations(core *core) *conversations { 75 | return &conversations{ 76 | client: core, 77 | Messages: newConversationMessage(core), 78 | } 79 | } 80 | 81 | // Conversation represents conversation information 82 | type Conversation struct { 83 | // The ID of the conversation 84 | ID string `json:"id"` 85 | 86 | // Indicates the create time of the conversation. The value format is Unix timestamp in seconds. 87 | CreatedAt int `json:"created_at"` 88 | 89 | // Additional information when creating a message, and this additional information will also be 90 | // returned when retrieving messages. 91 | MetaData map[string]string `json:"meta_data,omitempty"` 92 | 93 | // section_id is used to distinguish the context sections of the session history. 94 | // The same section is one context. 95 | LastSectionID string `json:"last_section_id"` 96 | } 97 | 98 | // CreateConversationsReq represents request for creating conversation 99 | type CreateConversationsReq struct { 100 | // Messages in the conversation. For more information, see EnterMessage object. 101 | Messages []*Message `json:"messages,omitempty"` 102 | 103 | // Additional information when creating a message, and this additional information will also be 104 | // returned when retrieving messages. 105 | MetaData map[string]string `json:"meta_data,omitempty"` 106 | 107 | // Bind and isolate conversation on different bots. 108 | BotID string `json:"bot_id,omitempty"` 109 | 110 | // Optional: Specify a connector ID. Supports passing in 999 (Chat SDK) and 1024 (API). If not provided, the default is 1024 (API). 111 | ConnectorID string `json:"connector_id"` 112 | } 113 | 114 | // ListConversationsReq represents request for listing conversations 115 | type ListConversationsReq struct { 116 | // The ID of the bot. 117 | BotID string `json:"bot_id"` 118 | 119 | // The page number. 120 | PageNum int `json:"page_num,omitempty"` 121 | 122 | // The page size. 123 | PageSize int `json:"page_size,omitempty"` 124 | } 125 | 126 | // RetrieveConversationsReq represents request for retrieving conversation 127 | type RetrieveConversationsReq struct { 128 | // The ID of the conversation. 129 | ConversationID string `json:"conversation_id"` 130 | } 131 | 132 | // ClearConversationsReq represents request for clearing conversation 133 | type ClearConversationsReq struct { 134 | // The ID of the conversation. 135 | ConversationID string `json:"conversation_id"` 136 | } 137 | 138 | // CreateConversationsResp represents response for creating conversation 139 | type createConversationsResp struct { 140 | baseResponse 141 | Conversation *CreateConversationsResp `json:"data"` 142 | } 143 | 144 | type CreateConversationsResp struct { 145 | baseModel 146 | Conversation 147 | } 148 | 149 | // listConversationsResp represents response for listing conversations 150 | type listConversationsResp struct { 151 | baseResponse 152 | Data *ListConversationsResp `json:"data"` 153 | } 154 | 155 | // ListConversationsResp represents response for listing conversations 156 | type ListConversationsResp struct { 157 | baseModel 158 | HasMore bool `json:"has_more"` 159 | Conversations []*Conversation `json:"conversations"` 160 | } 161 | 162 | // RetrieveConversationsResp represents response for retrieving conversation 163 | type retrieveConversationsResp struct { 164 | baseResponse 165 | Conversation *RetrieveConversationsResp `json:"data"` 166 | } 167 | 168 | type RetrieveConversationsResp struct { 169 | baseModel 170 | Conversation 171 | } 172 | 173 | // ClearConversationsResp represents response for clearing conversation 174 | type clearConversationsResp struct { 175 | baseResponse 176 | Data *ClearConversationsResp `json:"data"` 177 | } 178 | 179 | type ClearConversationsResp struct { 180 | baseModel 181 | ID string `json:"id"` 182 | ConversationID string `json:"conversation_id"` 183 | } 184 | -------------------------------------------------------------------------------- /datasets_images.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | ) 9 | 10 | type datasetsImages struct { 11 | client *core 12 | } 13 | 14 | func newDatasetsImages(core *core) *datasetsImages { 15 | return &datasetsImages{ 16 | client: core, 17 | } 18 | } 19 | 20 | func (r *datasetsImages) Update(ctx context.Context, req *UpdateDatasetImageReq) (*UpdateDatasetImageResp, error) { 21 | method := http.MethodPut 22 | uri := fmt.Sprintf("/v1/datasets/%s/images/%s", req.DatasetID, req.DocumentID) 23 | resp := &updateImageResp{} 24 | err := r.client.Request(ctx, method, uri, req, resp) 25 | if err != nil { 26 | return nil, err 27 | } 28 | result := &UpdateDatasetImageResp{} 29 | result.setHTTPResponse(resp.HTTPResponse) 30 | return result, nil 31 | } 32 | 33 | func (r *datasetsImages) List(ctx context.Context, req *ListDatasetsImagesReq) (NumberPaged[Image], error) { 34 | if req.PageSize == 0 { 35 | req.PageSize = 10 36 | } 37 | if req.PageNum == 0 { 38 | req.PageNum = 1 39 | } 40 | 41 | return NewNumberPaged[Image]( 42 | func(request *pageRequest) (*pageResponse[Image], error) { 43 | uri := fmt.Sprintf("/v1/datasets/%s/images", req.DatasetID) 44 | resp := &listImagesResp{} 45 | var queries []RequestOption 46 | if req.Keyword != nil { 47 | queries = append(queries, withHTTPQuery("keyword", *req.Keyword)) 48 | } 49 | if req.HasCaption != nil { 50 | queries = append(queries, withHTTPQuery("has_caption", strconv.FormatBool(*req.HasCaption))) 51 | } 52 | queries = append(queries, 53 | withHTTPQuery("page_num", strconv.Itoa(request.PageNum)), 54 | withHTTPQuery("page_size", strconv.Itoa(request.PageSize)), 55 | ) 56 | err := r.client.Request(ctx, http.MethodGet, uri, nil, resp, queries...) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &pageResponse[Image]{ 61 | Total: resp.Data.TotalCount, 62 | HasMore: len(resp.Data.ImagesInfos) >= request.PageSize, 63 | Data: resp.Data.ImagesInfos, 64 | LogID: resp.HTTPResponse.LogID(), 65 | }, nil 66 | }, req.PageSize, req.PageNum) 67 | } 68 | 69 | // ImageStatus 表示图片状态 70 | type ImageStatus int 71 | 72 | const ( 73 | ImageStatusInProcessing ImageStatus = 0 // 处理中 74 | ImageStatusCompleted ImageStatus = 1 // 已完成 75 | ImageStatusProcessingFailed ImageStatus = 9 // 处理失败 76 | ) 77 | 78 | // Image 表示图片信息 79 | type Image struct { 80 | // The ID of the file. 81 | DocumentID string `json:"document_id"` 82 | 83 | // The total character count of the file content. 84 | CharCount int `json:"char_count"` 85 | 86 | // The chunking rules. For detailed instructions, refer to the ChunkStrategy object. 87 | ChunkStrategy *DocumentChunkStrategy `json:"chunk_strategy"` 88 | 89 | // The upload time of the file, in the format of a 10-digit Unix timestamp. 90 | CreateTime int `json:"create_time"` 91 | 92 | // The last modified time of the file, in the format of a 10-digit Unix timestamp. 93 | UpdateTime int `json:"update_time"` 94 | 95 | // The type of file format. Values include: 96 | // 0: Document type, such as txt, pdf, online web pages, etc. 97 | // 1: Spreadsheet type, such as xls spreadsheets, etc. 98 | // 2: Images type, such as png images, etc. 99 | FormatType DocumentFormatType `json:"format_type"` 100 | 101 | // The number of times the file has been hit in conversations. 102 | HitCount int `json:"hit_count"` 103 | 104 | // The name of the file. 105 | Name string `json:"name"` 106 | 107 | // The size of the file in bytes. 108 | Size int `json:"size"` 109 | 110 | // The number of slices the file has been divided into. 111 | SliceCount int `json:"slice_count"` 112 | 113 | // The method of uploading the file. Values include: 114 | // 0: Upload local files. 115 | // 1: Upload online web pages. 116 | SourceType DocumentSourceType `json:"source_type"` 117 | 118 | // The processing status of the file. Values include: 119 | // 0: Processing 120 | // 1: Completed 121 | // 9: Processing failed, it is recommended to re-upload 122 | Status ImageStatus `json:"status"` 123 | 124 | // The caption of the image. 125 | Caption string `json:"caption"` 126 | 127 | // The ID of the creator. 128 | CreatorID string `json:"creator_id"` 129 | } 130 | 131 | // UpdateDatasetImageReq 表示更新图片的请求 132 | type UpdateDatasetImageReq struct { 133 | DatasetID string `json:"-"` 134 | DocumentID string `json:"-"` 135 | Caption *string `json:"caption"` // 图片描述 136 | } 137 | 138 | // UpdateImageResp 表示更新图片的响应 139 | type updateImageResp struct { 140 | baseResponse 141 | Data *UpdateDatasetImageResp `json:"data"` 142 | } 143 | 144 | type UpdateDatasetImageResp struct { 145 | baseModel 146 | } 147 | 148 | // ListDatasetsImagesReq 表示列出图片的请求 149 | type ListDatasetsImagesReq struct { 150 | DatasetID string `json:"-"` 151 | Keyword *string `json:"keyword,omitempty"` 152 | HasCaption *bool `json:"has_caption,omitempty"` 153 | PageNum int `json:"page_num"` 154 | PageSize int `json:"page_size"` 155 | } 156 | 157 | // ListImagesResp 表示列出图片的响应 158 | type listImagesResp struct { 159 | baseResponse 160 | Data *ListImagesResp `json:"data"` 161 | } 162 | 163 | type ListImagesResp struct { 164 | baseModel 165 | ImagesInfos []*Image `json:"photo_infos"` 166 | TotalCount int `json:"total_count"` 167 | } 168 | -------------------------------------------------------------------------------- /datasets_images_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestDatasetsImages(t *testing.T) { 15 | t.Run("Update image success", func(t *testing.T) { 16 | mockTransport := &mockTransport{ 17 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 18 | // Verify request method and path 19 | assert.Equal(t, http.MethodPut, req.Method) 20 | assert.Equal(t, "/v1/datasets/123/images/456", req.URL.Path) 21 | 22 | // Return mock response 23 | return &http.Response{ 24 | StatusCode: http.StatusOK, 25 | Body: io.NopCloser(strings.NewReader(`{"data": {}}`)), 26 | Header: make(http.Header), 27 | }, nil 28 | }, 29 | } 30 | 31 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 32 | images := newDatasetsImages(core) 33 | 34 | // Create test request 35 | caption := "test caption" 36 | req := &UpdateDatasetImageReq{ 37 | DatasetID: "123", 38 | DocumentID: "456", 39 | Caption: &caption, 40 | } 41 | 42 | // Test image update 43 | resp, err := images.Update(context.Background(), req) 44 | require.NoError(t, err) 45 | require.NotNil(t, resp) 46 | }) 47 | 48 | t.Run("List images success", func(t *testing.T) { 49 | mockTransport := &mockTransport{ 50 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 51 | // Verify request method and path 52 | assert.Equal(t, http.MethodGet, req.Method) 53 | assert.Equal(t, "/v1/datasets/123/images", req.URL.Path) 54 | 55 | // Verify query parameters 56 | assert.Equal(t, "test", req.URL.Query().Get("keyword")) 57 | assert.Equal(t, "true", req.URL.Query().Get("has_caption")) 58 | assert.Equal(t, "1", req.URL.Query().Get("page_num")) 59 | assert.Equal(t, "10", req.URL.Query().Get("page_size")) 60 | 61 | // Return mock response 62 | return &http.Response{ 63 | StatusCode: http.StatusOK, 64 | Body: io.NopCloser(strings.NewReader(`{ 65 | "data": { 66 | "total_count": 2, 67 | "photo_infos": [ 68 | { 69 | "document_id": "img1", 70 | "name": "image1.png", 71 | "status": 1, 72 | "format_type": 2, 73 | "source_type": 0, 74 | "caption": "test image 1" 75 | }, 76 | { 77 | "document_id": "img2", 78 | "name": "image2.png", 79 | "status": 1, 80 | "format_type": 2, 81 | "source_type": 0, 82 | "caption": "test image 2" 83 | } 84 | ] 85 | } 86 | }`)), 87 | Header: make(http.Header), 88 | }, nil 89 | }, 90 | } 91 | 92 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 93 | images := newDatasetsImages(core) 94 | 95 | // Create test request 96 | keyword := "test" 97 | hasCaption := true 98 | req := &ListDatasetsImagesReq{ 99 | DatasetID: "123", 100 | Keyword: &keyword, 101 | HasCaption: &hasCaption, 102 | PageNum: 1, 103 | PageSize: 10, 104 | } 105 | 106 | // Test image listing 107 | pager, err := images.List(context.Background(), req) 108 | require.NoError(t, err) 109 | 110 | // Verify pagination results 111 | items := pager.Items() 112 | require.Len(t, items, 2) 113 | assert.Equal(t, "img1", items[0].DocumentID) 114 | assert.Equal(t, "image1.png", items[0].Name) 115 | assert.Equal(t, "test image 1", items[0].Caption) 116 | assert.Equal(t, ImageStatusCompleted, items[0].Status) 117 | assert.Equal(t, DocumentFormatTypeImage, items[0].FormatType) 118 | assert.Equal(t, DocumentSourceTypeLocalFile, items[0].SourceType) 119 | 120 | assert.Equal(t, "img2", items[1].DocumentID) 121 | assert.Equal(t, "image2.png", items[1].Name) 122 | assert.Equal(t, "test image 2", items[1].Caption) 123 | assert.Equal(t, ImageStatusCompleted, items[1].Status) 124 | assert.Equal(t, DocumentFormatTypeImage, items[1].FormatType) 125 | assert.Equal(t, DocumentSourceTypeLocalFile, items[1].SourceType) 126 | 127 | assert.Equal(t, 2, pager.Total()) 128 | assert.False(t, pager.HasMore()) 129 | }) 130 | } 131 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type Error struct { 9 | Code int 10 | Message string 11 | LogID string 12 | } 13 | 14 | func NewError(code int, msg, logID string) *Error { 15 | return &Error{ 16 | Code: code, 17 | Message: msg, 18 | LogID: logID, 19 | } 20 | } 21 | 22 | // Error implements the error interface 23 | func (e *Error) Error() string { 24 | return fmt.Sprintf("code=%d, message=%s, logid=%s", 25 | e.Code, 26 | e.Message, 27 | e.LogID) 28 | } 29 | 30 | // AsCozeError checks if the error is of type Error 31 | func AsCozeError(err error) (*Error, bool) { 32 | var cozeErr *Error 33 | if errors.As(err, &cozeErr) { 34 | return cozeErr, true 35 | } 36 | return nil, false 37 | } 38 | 39 | // authErrorFormat represents the error response from Coze API 40 | type authErrorFormat struct { 41 | ErrorMessage string `json:"error_message"` 42 | ErrorCode string `json:"error_code"` 43 | Error string `json:"error"` 44 | } 45 | 46 | // AuthErrorCode represents authentication error codes 47 | type AuthErrorCode string 48 | 49 | const ( 50 | /* 51 | * The user has not completed authorization yet, please try again later 52 | */ 53 | AuthorizationPending AuthErrorCode = "authorization_pending" 54 | /* 55 | * The request is too frequent, please try again later 56 | */ 57 | SlowDown AuthErrorCode = "slow_down" 58 | /* 59 | * The user has denied the authorization 60 | */ 61 | AccessDenied AuthErrorCode = "access_denied" 62 | /* 63 | * The token is expired 64 | */ 65 | ExpiredToken AuthErrorCode = "expired_token" 66 | ) 67 | 68 | // String implements the Stringer interface 69 | func (c AuthErrorCode) String() string { 70 | return string(c) 71 | } 72 | 73 | type AuthError struct { 74 | HttpCode int 75 | Code AuthErrorCode 76 | ErrorMessage string 77 | Param string 78 | LogID string 79 | parent error 80 | } 81 | 82 | func NewAuthError(error *authErrorFormat, statusCode int, logID string) *AuthError { 83 | return &AuthError{ 84 | HttpCode: statusCode, 85 | ErrorMessage: error.ErrorMessage, 86 | Code: AuthErrorCode(error.ErrorCode), 87 | Param: error.Error, 88 | LogID: logID, 89 | } 90 | } 91 | 92 | // Error implements the error interface 93 | func (e *AuthError) Error() string { 94 | return fmt.Sprintf("HttpCode: %d, Code: %s, Message: %s, Param: %s, LogID: %s", 95 | e.HttpCode, 96 | e.Code, 97 | e.ErrorMessage, 98 | e.Param, 99 | e.LogID) 100 | } 101 | 102 | // Unwrap returns the parent error 103 | func (e *AuthError) Unwrap() error { 104 | return e.parent 105 | } 106 | 107 | // AsAuthError 判断错误是否为 CozeAuthError 类型 108 | func AsAuthError(err error) (*AuthError, bool) { 109 | var authErr *AuthError 110 | if errors.As(err, &authErr) { 111 | return authErr, true 112 | } 113 | return nil, false 114 | } 115 | -------------------------------------------------------------------------------- /error_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewCozeError(t *testing.T) { 12 | // 测试创建新的 Error 13 | err := NewError(1001, "test error", "test-log-id") 14 | assert.NotNil(t, err) 15 | assert.Equal(t, 1001, err.Code) 16 | assert.Equal(t, "test error", err.Message) 17 | assert.Equal(t, "test-log-id", err.LogID) 18 | } 19 | 20 | func TestCozeError_Error(t *testing.T) { 21 | // 测试 Error() 方法 22 | err := NewError(1001, "test error", "test-log-id") 23 | expectedMsg := "code=1001, message=test error, logid=test-log-id" 24 | assert.Equal(t, expectedMsg, err.Error()) 25 | } 26 | 27 | func TestAsCozeError(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | err error 31 | wantErr *Error 32 | wantBool bool 33 | }{ 34 | { 35 | name: "nil error", 36 | err: nil, 37 | wantErr: nil, 38 | wantBool: false, 39 | }, 40 | { 41 | name: "non-Error", 42 | err: errors.New("standard error"), 43 | wantErr: nil, 44 | wantBool: false, 45 | }, 46 | { 47 | name: "Error", 48 | err: NewError(1001, "test error", "test-log-id"), 49 | wantErr: NewError(1001, "test error", "test-log-id"), 50 | wantBool: true, 51 | }, 52 | { 53 | name: "wrapped Error", 54 | err: fmt.Errorf("wrapped: %w", 55 | NewError(1001, "test error", "test-log-id")), 56 | wantErr: NewError(1001, "test error", "test-log-id"), 57 | wantBool: true, 58 | }, 59 | } 60 | 61 | for _, tt := range tests { 62 | t.Run(tt.name, func(t *testing.T) { 63 | gotErr, gotBool := AsCozeError(tt.err) 64 | assert.Equal(t, tt.wantBool, gotBool) 65 | if tt.wantErr != nil { 66 | assert.Equal(t, tt.wantErr.Code, gotErr.Code) 67 | assert.Equal(t, tt.wantErr.Message, gotErr.Message) 68 | assert.Equal(t, tt.wantErr.LogID, gotErr.LogID) 69 | } else { 70 | assert.Nil(t, gotErr) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func TestAuthErrorCode_String(t *testing.T) { 77 | tests := []struct { 78 | name string 79 | code AuthErrorCode 80 | want string 81 | }{ 82 | { 83 | name: "AuthorizationPending", 84 | code: AuthorizationPending, 85 | want: "authorization_pending", 86 | }, 87 | { 88 | name: "SlowDown", 89 | code: SlowDown, 90 | want: "slow_down", 91 | }, 92 | { 93 | name: "AccessDenied", 94 | code: AccessDenied, 95 | want: "access_denied", 96 | }, 97 | { 98 | name: "ExpiredToken", 99 | code: ExpiredToken, 100 | want: "expired_token", 101 | }, 102 | } 103 | 104 | for _, tt := range tests { 105 | t.Run(tt.name, func(t *testing.T) { 106 | assert.Equal(t, tt.want, tt.code.String()) 107 | }) 108 | } 109 | } 110 | 111 | func TestNewCozeAuthExceptionWithoutParent(t *testing.T) { 112 | // 测试创建新的认证错误 113 | errorFormat := &authErrorFormat{ 114 | ErrorMessage: "invalid token", 115 | ErrorCode: "invalid_token", 116 | Error: "token_error", 117 | } 118 | err := NewAuthError(errorFormat, 401, "test-log-id") 119 | 120 | assert.NotNil(t, err) 121 | assert.Equal(t, 401, err.HttpCode) 122 | assert.Equal(t, "invalid token", err.ErrorMessage) 123 | assert.Equal(t, AuthErrorCode("invalid_token"), err.Code) 124 | assert.Equal(t, "token_error", err.Param) 125 | assert.Equal(t, "test-log-id", err.LogID) 126 | assert.Nil(t, err.parent) 127 | } 128 | 129 | func TestAuthError_Error(t *testing.T) { 130 | // 测试 Error() 方法 131 | err := &AuthError{ 132 | HttpCode: 401, 133 | Code: AuthErrorCode("invalid_token"), 134 | ErrorMessage: "invalid token", 135 | Param: "token_error", 136 | LogID: "test-log-id", 137 | } 138 | 139 | expectedMsg := "HttpCode: 401, Code: invalid_token, Message: invalid token, Param: token_error, LogID: test-log-id" 140 | assert.Equal(t, expectedMsg, err.Error()) 141 | } 142 | 143 | func TestAuthError_Unwrap(t *testing.T) { 144 | // 测试无父错误的情况 145 | t.Run("No Parent", func(t *testing.T) { 146 | err := &AuthError{} 147 | assert.Nil(t, err.Unwrap()) 148 | }) 149 | 150 | // 测试有父错误的情况 151 | t.Run("With Parent", func(t *testing.T) { 152 | parentErr := errors.New("parent error") 153 | err := &AuthError{ 154 | parent: parentErr, 155 | } 156 | assert.Equal(t, parentErr, err.Unwrap()) 157 | }) 158 | } 159 | 160 | func TestAsAuthError(t *testing.T) { 161 | tests := []struct { 162 | name string 163 | err error 164 | wantErr *AuthError 165 | wantBool bool 166 | }{ 167 | { 168 | name: "nil error", 169 | err: nil, 170 | wantErr: nil, 171 | wantBool: false, 172 | }, 173 | { 174 | name: "non-AuthError", 175 | err: errors.New("standard error"), 176 | wantErr: nil, 177 | wantBool: false, 178 | }, 179 | { 180 | name: "AuthError", 181 | err: &AuthError{ 182 | HttpCode: 401, 183 | Code: AuthErrorCode("invalid_token"), 184 | ErrorMessage: "invalid token", 185 | Param: "token_error", 186 | LogID: "test-log-id", 187 | }, 188 | wantErr: &AuthError{ 189 | HttpCode: 401, 190 | Code: AuthErrorCode("invalid_token"), 191 | ErrorMessage: "invalid token", 192 | Param: "token_error", 193 | LogID: "test-log-id", 194 | }, 195 | wantBool: true, 196 | }, 197 | { 198 | name: "wrapped AuthError", 199 | err: fmt.Errorf("wrapped: %w", &AuthError{ 200 | HttpCode: 401, 201 | Code: AuthErrorCode("invalid_token"), 202 | ErrorMessage: "invalid token", 203 | Param: "token_error", 204 | LogID: "test-log-id", 205 | }), 206 | wantErr: &AuthError{ 207 | HttpCode: 401, 208 | Code: AuthErrorCode("invalid_token"), 209 | ErrorMessage: "invalid token", 210 | Param: "token_error", 211 | LogID: "test-log-id", 212 | }, 213 | wantBool: true, 214 | }, 215 | } 216 | 217 | for _, tt := range tests { 218 | t.Run(tt.name, func(t *testing.T) { 219 | gotErr, gotBool := AsAuthError(tt.err) 220 | assert.Equal(t, tt.wantBool, gotBool) 221 | if tt.wantErr != nil { 222 | assert.Equal(t, tt.wantErr.HttpCode, gotErr.HttpCode) 223 | assert.Equal(t, tt.wantErr.Code, gotErr.Code) 224 | assert.Equal(t, tt.wantErr.ErrorMessage, gotErr.ErrorMessage) 225 | assert.Equal(t, tt.wantErr.Param, gotErr.Param) 226 | assert.Equal(t, tt.wantErr.LogID, gotErr.LogID) 227 | } else { 228 | assert.Nil(t, gotErr) 229 | } 230 | }) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /examples/audio/rooms/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | 16 | // Init the Coze client through the access_token. 17 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 18 | 19 | botID := os.Getenv("COZE_BOT_ID") 20 | voiceID := os.Getenv("COZE_VOICE_ID") 21 | 22 | ctx := context.Background() 23 | resp, err := cozeCli.Audio.Rooms.Create(ctx, &coze.CreateAudioRoomsReq{ 24 | BotID: botID, 25 | VoiceID: voiceID, 26 | }) 27 | if err != nil { 28 | fmt.Println("Error creating rooms:", err) 29 | return 30 | } 31 | 32 | fmt.Println(resp) 33 | fmt.Println("Room ID:", resp.RoomID) 34 | fmt.Println("Log ID:", resp.Response().LogID()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/audio/speech/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | 16 | // Init the Coze client through the access_token. 17 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 18 | 19 | // saveFilePath := os.Getenv("SAVE_FILE_PATH") 20 | voiceID := os.Getenv("COZE_VOICE_ID") 21 | content := "Come and try it out" 22 | 23 | ctx := context.Background() 24 | resp, err := cozeCli.Audio.Speech.Create(ctx, &coze.CreateAudioSpeechReq{ 25 | Input: content, 26 | VoiceID: voiceID, 27 | }) 28 | if err != nil { 29 | fmt.Println("Error creating speech:", err) 30 | return 31 | } 32 | 33 | fmt.Println(resp) 34 | fmt.Println(resp.LogID()) 35 | 36 | if err := resp.WriteToFile(os.Getenv("SAVE_SPEECH_PATH")); err != nil { 37 | fmt.Println("Error writing to file:", err) 38 | return 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/audio/speech/transcription/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | 16 | // Init the Coze client through the access_token. 17 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 18 | 19 | // filename := "/Users/u/Downloads/hello.mp3" 20 | filename := os.Getenv("COZE_AUDIO_FILE") 21 | file, err := os.Open(filename) 22 | if err != nil { 23 | fmt.Println("open file error", err) 24 | return 25 | } 26 | defer file.Close() 27 | 28 | resp, err := cozeCli.Audio.Transcriptions.Create(context.Background(), &coze.AudioSpeechTranscriptionsReq{ 29 | Filename: filename, 30 | Audio: file, 31 | }) 32 | if err != nil { 33 | fmt.Println("Error creating speech:", err) 34 | return 35 | } 36 | 37 | fmt.Println(resp) 38 | fmt.Println(resp.LogID()) 39 | } 40 | -------------------------------------------------------------------------------- /examples/audio/voices/clone/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | 16 | // Init the Coze client through the access_token. 17 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 18 | 19 | file, err := os.Open(os.Getenv("VOICE_FILE_PATH")) 20 | if err != nil { 21 | fmt.Println("Error opening file:", err) 22 | return 23 | } 24 | defer file.Close() 25 | 26 | ctx := context.Background() 27 | resp, err := cozeCli.Audio.Voices.Clone(ctx, &coze.CloneAudioVoicesReq{ 28 | File: file, 29 | VoiceName: "your voices name", 30 | AudioFormat: coze.AudioFormatM4A, 31 | }) 32 | if err != nil { 33 | fmt.Println("Error cloning voices:", err) 34 | return 35 | } 36 | 37 | fmt.Println(resp) 38 | fmt.Println(resp.LogID()) 39 | } 40 | -------------------------------------------------------------------------------- /examples/audio/voices/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | 16 | // Init the Coze client through the access_token. 17 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 18 | 19 | ctx := context.Background() 20 | // you can use iterator to automatically retrieve next page 21 | voices, err := cozeCli.Audio.Voices.List(ctx, &coze.ListAudioVoicesReq{PageSize: 10}) 22 | if err != nil { 23 | fmt.Println("Error fetching voices:", err) 24 | return 25 | } 26 | for voices.Next() { 27 | fmt.Println(voices.Current()) 28 | } 29 | 30 | if voices.Err() != nil { 31 | fmt.Println("Error fetching voices:", voices.Err()) 32 | return 33 | } 34 | 35 | // the page result will return followed information 36 | fmt.Println("has_more:", voices.HasMore()) 37 | } 38 | -------------------------------------------------------------------------------- /examples/auth/device_oauth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // This examples is about how to use the device oauth process to acquire user authorization. 12 | // 13 | // Firstly, users need to access https://www.coze.com/open/oauth/apps. For the cn environment, 14 | // users need to access https://www.coze.cn/open/oauth/apps to create an OAuth App of the type 15 | // of TVs/Limited Input devices/Command line programs. 16 | // The specific creation process can be referred to in the document: 17 | // https://www.coze.com/docs/developer_guides/oauth_device_code. For the cn environment, it can be 18 | // accessed at https://www.coze.cn/docs/developer_guides/oauth_device_code. 19 | // After the creation is completed, the client ID can be obtained. 20 | 21 | func main() { 22 | clientID := os.Getenv("COZE_DEVICE_OAUTH_CLIENT_ID") 23 | 24 | // The default access is api.coze.com, but if you need to access api.coze.cn, 25 | // please use base_url to configure the api endpoint to access 26 | cozeAPIBase := os.Getenv("COZE_API_BASE") 27 | if cozeAPIBase == "" { 28 | cozeAPIBase = coze.ComBaseURL 29 | } 30 | ctx := context.Background() 31 | 32 | oauth, err := coze.NewDeviceOAuthClient(clientID, coze.WithAuthBaseURL(cozeAPIBase)) 33 | if err != nil { 34 | fmt.Printf("Failed to create OAuth client: %v\n", err) 35 | return 36 | } 37 | 38 | // In the device oauth authorization process, developers need to first call the interface 39 | // of Coze to generate the device code to obtain the user_code and device_code. Then generate 40 | // the authorization link through the user_code, guide the user to open the link, fill in the 41 | // user_code, and consent to the authorization. Developers need to call the interface of Coze 42 | // to generate the token through the device_code. When the user has not authorized or rejected 43 | // the authorization, the interface will throw an error and return a specific error code. 44 | // After the user consents to the authorization, the interface will succeed and return the 45 | // access_token. 46 | // 47 | // First, make a call to obtain 'getDeviceCode' 48 | 49 | codeResp, err := oauth.GetDeviceCode(ctx, nil) 50 | if err != nil { 51 | fmt.Printf("Failed to get device code: %v\n", err) 52 | return 53 | } 54 | fmt.Printf("%+v\n", codeResp) 55 | fmt.Println(codeResp.LogID()) 56 | 57 | // The space permissions for which the Access Token is granted can be specified. As following codes: 58 | // codeResp, err = oauth.GetDeviceCode(ctx, &coze.GetDeviceOAuthCodeReq{ 59 | // WorkspaceID: &workspaceID, 60 | // }) 61 | 62 | // The returned device_code contains an authorization link. Developers need to guide users 63 | // to open up this link. 64 | // open codeResp.getVerificationUri 65 | 66 | fmt.Printf("Please open url: %s\n", codeResp.VerificationURL) 67 | 68 | // The developers then need to use the device_code to poll Coze's interface to obtain the token. 69 | // The SDK has encapsulated this part of the code in and handled the different returned error 70 | // codes. The developers only need to invoke getAccessToken. 71 | 72 | // if the developers set poll as true, the sdk will automatically handle pending and slow down exception 73 | resp, err := oauth.GetAccessToken(ctx, &coze.GetDeviceOAuthAccessTokenReq{ 74 | DeviceCode: codeResp.DeviceCode, 75 | Poll: true, 76 | }) 77 | if err != nil { 78 | authErr, ok := coze.AsAuthError(err) 79 | if !ok { 80 | fmt.Printf("Failed to get access token: %v\n", err) 81 | return 82 | } 83 | switch authErr.Code { 84 | case coze.AccessDenied: 85 | // The user rejected the authorization. 86 | // Developers need to guide the user to open the authorization link again. 87 | fmt.Println("access denied") 88 | case coze.ExpiredToken: 89 | // The token has expired. Developers need to guide the user to open 90 | // the authorization link again. 91 | fmt.Println("expired token") 92 | default: 93 | fmt.Printf("Unexpected error: %v\n", err) 94 | 95 | return 96 | } 97 | } 98 | fmt.Printf("%+v\n", resp) 99 | 100 | // // use the access token to init Coze client 101 | // use the access token to init Coze client 102 | cozeCli := coze.NewCozeAPI(coze.NewTokenAuth(resp.AccessToken)) 103 | _ = cozeCli 104 | 105 | // When the token expires, you can also refresh and re-obtain the token 106 | resp, err = oauth.RefreshToken(ctx, resp.RefreshToken) 107 | if err != nil { 108 | fmt.Printf("Failed to refresh token: %v\n", err) 109 | return 110 | } 111 | fmt.Println(resp) 112 | fmt.Println(resp.LogID()) 113 | } 114 | -------------------------------------------------------------------------------- /examples/auth/error/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // Using web oauth as example, show how to handle the Auth exception 12 | func main() { 13 | redirectURI := os.Getenv("COZE_WEB_OAUTH_REDIRECT_URI") 14 | clientSecret := os.Getenv("COZE_WEB_OAUTH_CLIENT_SECRET") 15 | clientID := os.Getenv("COZE_WEB_OAUTH_CLIENT_ID") 16 | ctx := context.Background() 17 | 18 | // The default access is api.coze.com, but if you need to access api.coze.cn, 19 | // please use base_url to configure the api endpoint to access 20 | cozeAPIBase := os.Getenv("COZE_API_BASE") 21 | if cozeAPIBase == "" { 22 | cozeAPIBase = coze.ComBaseURL 23 | } 24 | 25 | // The sdk offers the WebOAuthClient class to establish an authorization for Web OAuth. 26 | // Firstly, it is required to initialize the WebOAuthApp with the client ID and client secret. 27 | oauth, err := coze.NewWebOAuthClient(clientID, clientSecret, coze.WithAuthBaseURL(cozeAPIBase)) 28 | if err != nil { 29 | fmt.Printf("Failed to create OAuth client: %v\n", err) 30 | return 31 | } 32 | 33 | // Generate the authorization link and direct the user to open it. 34 | oauthURL := oauth.GetOAuthURL(ctx, &coze.GetWebOAuthURLReq{ 35 | RedirectURI: redirectURI, 36 | State: "state", 37 | }) 38 | fmt.Println(oauthURL) 39 | 40 | // The space permissions for which the Access Token is granted can be specified. As following codes: 41 | // oauthURL := oauth.GetOAuthURL(&coze.GetWebOAuthURLReq{ 42 | // RedirectURI: redirectURI, 43 | // State: "state", 44 | // WorkspaceID: &workspaceID, 45 | // }) 46 | // fmt.Println(oauthURL) 47 | 48 | // After the user clicks the authorization consent button, the coze web page will redirect 49 | // to the redirect address configured in the authorization link and carry the authorization 50 | // code and state parameters in the address via the query string. 51 | // 52 | // Get from the query of the redirect interface: query.get('code') 53 | code := "mock code" 54 | 55 | // After obtaining the code after redirection, the interface to exchange the code for a 56 | // token can be invoked to generate the coze access_token of the authorized user. 57 | resp, err := oauth.GetAccessToken(ctx, &coze.GetWebOAuthAccessTokenReq{ 58 | Code: code, 59 | RedirectURI: redirectURI, 60 | }) 61 | if err != nil { 62 | fmt.Printf("Failed to get access token: %v\n", err) 63 | // The SDK has enumerated existing authentication error codes 64 | // You need to handle the exception and guide users to re-authenticate 65 | // For different oauth type, the error code may be different, 66 | // you should read document to get more information 67 | authErr, ok := coze.AsAuthError(err) 68 | if ok { 69 | switch authErr.Code { 70 | } 71 | } 72 | return 73 | } 74 | fmt.Println(resp) 75 | } 76 | -------------------------------------------------------------------------------- /examples/auth/jwt_oauth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // 12 | // This examples is about how to use the service jwt oauth process to acquire user authorization. 13 | // 14 | // Firstly, users need to access https://www.coze.com/open/oauth/apps. For the cn environment, 15 | // users need to access https://www.coze.cn/open/oauth/apps to create an OAuth App of the type 16 | // of Service application. 17 | // The specific creation process can be referred to in the document: 18 | // https://www.coze.com/docs/developer_guides/oauth_jwt. For the cn environment, it can be 19 | // accessed at https://www.coze.cn/docs/developer_guides/oauth_jwt. 20 | // After the creation is completed, the client ID, private key, and public key id, can be obtained. 21 | // For the client secret and public key id, users need to keep it securely to avoid leakage. 22 | // 23 | 24 | func main() { 25 | // The default access is api.coze.com, but if you need to access api.coze.cn, 26 | // please use base_url to configure the api endpoint to access 27 | cozeAPIBase := os.Getenv("COZE_API_BASE") 28 | jwtOauthClientID := os.Getenv("COZE_JWT_OAUTH_CLIENT_ID") 29 | jwtOauthPrivateKey := os.Getenv("COZE_JWT_OAUTH_PRIVATE_KEY") 30 | jwtOauthPrivateKeyFilePath := os.Getenv("COZE_JWT_OAUTH_PRIVATE_KEY_FILE_PATH") 31 | jwtOauthPublicKeyID := os.Getenv("COZE_JWT_OAUTH_PUBLIC_KEY_ID") 32 | 33 | // Read private key from file 34 | privateKeyBytes, err := os.ReadFile(jwtOauthPrivateKeyFilePath) 35 | if err != nil { 36 | fmt.Printf("Error reading private key file: %v\n", err) 37 | return 38 | } 39 | jwtOauthPrivateKey = string(privateKeyBytes) 40 | 41 | // The jwt oauth type requires using private to be able to issue a jwt token, and through the jwt token, 42 | // apply for an access_token from the coze service.The sdk encapsulates this procedure, 43 | // and only needs to use get_access_token to obtain the access_token under the jwt oauth process. 44 | // Generate the authorization token The default ttl is 900s, and developers can customize the expiration time, 45 | // which can be set up to 24 hours at most. 46 | oauth, err := coze.NewJWTOAuthClient(coze.NewJWTOAuthClientParam{ 47 | ClientID: jwtOauthClientID, PublicKey: jwtOauthPublicKeyID, PrivateKeyPEM: jwtOauthPrivateKey, 48 | }, coze.WithAuthBaseURL(cozeAPIBase)) 49 | if err != nil { 50 | fmt.Printf("Error creating JWT OAuth client: %v\n", err) 51 | return 52 | } 53 | ctx := context.Background() 54 | 55 | resp, err := oauth.GetAccessToken(ctx, nil) 56 | if err != nil { 57 | fmt.Printf("Error getting access token: %v\n", err) 58 | return 59 | } 60 | fmt.Printf("Access token response: %+v\n", resp) 61 | fmt.Println(resp.LogID()) 62 | 63 | // The jwt oauth process does not support refreshing tokens. When the token expires, 64 | // just directly call get_access_token to generate a new token. 65 | cozeCli := coze.NewCozeAPI( 66 | coze.NewJWTAuth(oauth, nil), 67 | coze.WithBaseURL(cozeAPIBase), 68 | ) 69 | _ = cozeCli 70 | } 71 | -------------------------------------------------------------------------------- /examples/auth/pkce_oauth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // How to effectuate OpenAPI authorization through the OAuth Proof Key for Code Exchange method. 12 | // 13 | // PKCE stands for Proof Key for Code Exchange, and it's an extension to the OAuth 2.0 authorization 14 | // code flow designed to enhance security for public clients, such as mobile and single-page 15 | // applications. 16 | // 17 | // Firstly, users need to access https://www.coze.com/open/oauth/apps. For the cn environment, 18 | // users need to access https://www.coze.cn/open/oauth/apps to create an OAuth App of the type 19 | // of Mobile/PC/Single-page application. 20 | // The specific creation process can be referred to in the document: 21 | // https://www.coze.com/docs/developer_guides/oauth_pkce. For the cn environment, it can be 22 | // accessed at https://www.coze.cn/docs/developer_guides/oauth_pkce. 23 | // After the creation is completed, the client ID can be obtained. 24 | 25 | func main() { 26 | redirectURI := os.Getenv("COZE_PKCE_OAUTH_REDIRECT_URI") 27 | clientID := os.Getenv("COZE_PKCE_OAUTH_CLIENT_ID") 28 | 29 | // 30 | // The default access is api.coze.com, but if you need to access api.coze.cn, 31 | // please use base_url to configure the api endpoint to access 32 | 33 | cozeAPIBase := os.Getenv("COZE_API_BASE") 34 | if cozeAPIBase == "" { 35 | cozeAPIBase = coze.ComBaseURL 36 | } 37 | ctx := context.Background() 38 | 39 | oauth, err := coze.NewPKCEOAuthClient(clientID, coze.WithAuthBaseURL(cozeAPIBase)) 40 | if err != nil { 41 | fmt.Printf("Failed to create OAuth client: %v\n", err) 42 | return 43 | } 44 | 45 | // In the SDK, we have wrapped up the code_challenge process of PKCE. 46 | // Developers only need to select the code_challenge_method. 47 | oauthURL, err := oauth.GetOAuthURL(ctx, &coze.GetPKCEOAuthURLReq{ 48 | RedirectURI: redirectURI, 49 | State: "state", 50 | Method: coze.CodeChallengeMethodS256.Ptr(), 51 | }) 52 | if err != nil { 53 | fmt.Printf("Failed to generate OAuth URL: %v\n", err) 54 | return 55 | } 56 | fmt.Println(oauthURL.AuthorizationURL) 57 | fmt.Println(oauthURL.CodeVerifier) 58 | 59 | // 60 | // The space permissions for which the Access Token is granted can be specified. As following codes: 61 | // oauthURL, err := oauth.GenOAuthURL(&coze.GetPKCEOAuthURLReq{ 62 | // RedirectURI: redirectURI, State: "state", 63 | // Method: coze.CodeChallengeMethodS256.Ptr(), 64 | // WorkspaceID: utils.ptr("workspace_id"), 65 | // }) 66 | // if err != nil { 67 | // fmt.Printf("Failed to generate OAuth URL with workspaces: %v\n", err) 68 | // return 69 | // } 70 | // fmt.Println(oauthURL.AuthorizationURL) 71 | // fmt.Println(oauthURL.CodeVerifier) 72 | 73 | // After the user clicks the authorization consent button, 74 | // the coze web page will redirect to the redirect address configured in the authorization link 75 | // and carry the authorization code and state parameters in the address via the query string. 76 | // Get from the query of the redirect interface : query.get('code') 77 | code := "mock code" 78 | codeVerifier := oauthURL.CodeVerifier 79 | // After obtaining the code after redirection, the interface to exchange the code for a 80 | // token can be invoked to generate the coze access_token of the authorized user. 81 | // The developer should use code verifier returned by genOAuthURL() method 82 | resp, err := oauth.GetAccessToken(ctx, &coze.GetPKCEAccessTokenReq{ 83 | Code: code, 84 | RedirectURI: redirectURI, 85 | CodeVerifier: codeVerifier, 86 | }) 87 | if err != nil { 88 | fmt.Printf("Failed to get access token: %v\n", err) 89 | return 90 | } 91 | fmt.Printf("%+v\n", resp) 92 | 93 | // use the access token to init Coze client 94 | cozeCli := coze.NewCozeAPI(coze.NewTokenAuth(resp.AccessToken), coze.WithBaseURL(cozeAPIBase)) 95 | fmt.Println(cozeCli) 96 | 97 | // When the token expires, you can also refresh and re-obtain the token 98 | resp, err = oauth.RefreshToken(ctx, resp.RefreshToken) 99 | if err != nil { 100 | fmt.Printf("Failed to refresh token: %v\n", err) 101 | return 102 | } 103 | fmt.Println(resp) 104 | } 105 | -------------------------------------------------------------------------------- /examples/auth/token/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/coze-dev/coze-go" 8 | ) 9 | 10 | // How to use personal access token to init Coze client. 11 | // 12 | // Firstly, you need to access https://www.coze.com/open/oauth/pats (for the cn environment, 13 | // visit https://www.coze.cn/open/oauth/pats). 14 | // 15 | // Click to add a new token. After setting the appropriate name, expiration time, and 16 | // permissions, click OK to generate your personal access token. Please store it in a 17 | // secure environment to prevent this personal access token from being disclosed. 18 | func main() { 19 | cozeAPIToken := os.Getenv("COZE_API_TOKEN") 20 | 21 | // 22 | // The default access is api.coze.com, but if you need to access api.coze.cn, 23 | // please use base_url to configure the api endpoint to access 24 | // 25 | cozeAPIBase := os.Getenv("COZE_API_BASE") 26 | 27 | // 28 | // The Coze SDK offers the TokenAuth class for constructing an Auth class based on a fixed 29 | // access token. Meanwhile, the Coze class enables the passing in of an Auth class to build 30 | // a coze client. 31 | // 32 | // Therefore, you can utilize the following code to initialize a coze client. 33 | // 34 | cozeCli := coze.NewCozeAPI(coze.NewTokenAuth(cozeAPIToken), coze.WithBaseURL(cozeAPIBase)) 35 | fmt.Println(cozeCli) 36 | } 37 | -------------------------------------------------------------------------------- /examples/auth/web_oauth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // How to effectuate OpenAPI authorization through the OAuth authorization code method. 12 | // 13 | // Firstly, users need to access https://www.coze.com/open/oauth/apps. For the cn environment, 14 | // users need to access https://www.coze.cn/open/oauth/apps to create an OAuth App of the type 15 | // of Web application. 16 | // The specific creation process can be referred to in the document: 17 | // https://www.coze.com/docs/developer_guides/oauth_code. For the cn environment, it can be 18 | // accessed at https://www.coze.cn/docs/developer_guides/oauth_code. 19 | // After the creation is completed, the client ID, client secret, and redirect link, can be 20 | // obtained. For the client secret, users need to keep it securely to avoid leakage. 21 | 22 | func main() { 23 | redirectURI := os.Getenv("COZE_WEB_OAUTH_REDIRECT_URI") 24 | clientSecret := os.Getenv("COZE_WEB_OAUTH_CLIENT_SECRET") 25 | clientID := os.Getenv("COZE_WEB_OAUTH_CLIENT_ID") 26 | ctx := context.Background() 27 | 28 | // The default access is api.coze.com, but if you need to access api.coze.cn, 29 | // please use base_url to configure the api endpoint to access 30 | cozeAPIBase := os.Getenv("COZE_API_BASE") 31 | if cozeAPIBase == "" { 32 | cozeAPIBase = coze.ComBaseURL 33 | } 34 | 35 | // The sdk offers the WebOAuthClient class to establish an authorization for Web OAuth. 36 | // Firstly, it is required to initialize the WebOAuthApp with the client ID and client secret. 37 | oauth, err := coze.NewWebOAuthClient(clientID, clientSecret, coze.WithAuthBaseURL(cozeAPIBase)) 38 | if err != nil { 39 | fmt.Printf("Failed to create OAuth client: %v\n", err) 40 | return 41 | } 42 | 43 | // Generate the authorization link and direct the user to open it. 44 | oauthURL := oauth.GetOAuthURL(ctx, &coze.GetWebOAuthURLReq{ 45 | RedirectURI: redirectURI, 46 | State: "state", 47 | }) 48 | fmt.Println(oauthURL) 49 | 50 | // The space permissions for which the Access Token is granted can be specified. As following codes: 51 | // oauthURL = oauth.GetOAuthURL(&coze.GetWebOAuthURLReq{ 52 | // RedirectURI: redirectURI, 53 | // State: "state", 54 | // WorkspaceID: &workspaceID, 55 | // }) 56 | // fmt.Println(oauthURL) 57 | 58 | // After the user clicks the authorization consent button, the coze web page will redirect 59 | // to the redirect address configured in the authorization link and carry the authorization 60 | // code and state parameters in the address via the query string. 61 | // 62 | // Get from the query of the redirect interface: query.get('code') 63 | code := "mock code" 64 | 65 | // After obtaining the code after redirection, the interface to exchange the code for a 66 | // token can be invoked to generate the coze access_token of the authorized user. 67 | resp, err := oauth.GetAccessToken(ctx, &coze.GetWebOAuthAccessTokenReq{ 68 | Code: code, 69 | RedirectURI: redirectURI, 70 | }) 71 | if err != nil { 72 | fmt.Printf("Failed to get access token: %v\n", err) 73 | return 74 | } 75 | fmt.Println(resp) 76 | 77 | // When the token expires, you can also refresh and re-obtain the token 78 | resp, err = oauth.RefreshToken(ctx, resp.RefreshToken) 79 | if err != nil { 80 | fmt.Printf("Failed to refresh token: %v\n", err) 81 | return 82 | } 83 | 84 | fmt.Printf("%+v\n", resp) 85 | 86 | // you can get request log by getLogID method 87 | fmt.Println(resp.LogID()) 88 | 89 | // use the access token to init Coze client 90 | cozeCli := coze.NewCozeAPI(coze.NewTokenAuth(resp.AccessToken)) 91 | _ = cozeCli 92 | } 93 | -------------------------------------------------------------------------------- /examples/bots/publish/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // This examples is for describing how to create a bot, update a bot and publish a bot to the API. 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | // step one, create a bot 21 | workspaceID := os.Getenv("WORKSPACE_ID") 22 | 23 | // Call the upload file interface to get the avatar id. 24 | avatarPath := os.Getenv("IMAGE_FILE_PATH") 25 | ctx := context.Background() 26 | file, err := os.Open(avatarPath) 27 | if err != nil { 28 | fmt.Println("Error opening file:", err) 29 | return 30 | } 31 | uploadReq := &coze.UploadFilesReq{ 32 | File: file, 33 | } 34 | avatarInfo, err := cozeCli.Files.Upload(ctx, uploadReq) 35 | if err != nil { 36 | fmt.Println("Error uploading avatar:", err) 37 | return 38 | } 39 | fmt.Println(avatarInfo) 40 | 41 | // build the request 42 | createResp, err := cozeCli.Bots.Create(ctx, &coze.CreateBotsReq{ 43 | SpaceID: workspaceID, 44 | Description: "the description of your bot", 45 | Name: "the name of your bot", 46 | PromptInfo: &coze.BotPromptInfo{ 47 | Prompt: "your prompt", 48 | }, 49 | OnboardingInfo: &coze.BotOnboardingInfo{ 50 | Prologue: "the prologue of your bot", 51 | SuggestedQuestions: []string{"question 1", "question 2"}, 52 | }, 53 | IconFileID: avatarInfo.FileInfo.ID, 54 | ModelInfoConfig: &coze.BotModelInfoConfig{ 55 | ModelID: "1738675210", 56 | }, 57 | WorkflowIDList: &coze.WorkflowIDList{ 58 | IDs: []coze.WorkflowIDInfo{ 59 | { 60 | ID: os.Getenv("WORKFLOW_ID"), // 工作流ID 61 | }, 62 | }, 63 | }, 64 | }) 65 | if err != nil { 66 | fmt.Println("Error creating bot:", err) 67 | return 68 | } 69 | botID := createResp.BotID 70 | fmt.Println(createResp) 71 | fmt.Println(createResp.LogID()) 72 | 73 | // 74 | // step two, update the bot, you can update the bot after being created 75 | // in this examples, we will update the avatar of the bot 76 | 77 | publishResp, err := cozeCli.Bots.Publish(ctx, &coze.PublishBotsReq{ 78 | BotID: botID, 79 | ConnectorIDs: []string{"1024"}, 80 | }) 81 | if err != nil { 82 | fmt.Println("Error publishing bot:", err) 83 | return 84 | } 85 | fmt.Println(publishResp) 86 | fmt.Println(publishResp.LogID()) 87 | 88 | // 89 | // step three, you can also modify the bot configuration and republish it. 90 | // in this examples, we will update the avatar of the bot 91 | 92 | newFile, err := os.Open(os.Getenv("IMAGE_FILE_PATH")) 93 | if err != nil { 94 | fmt.Println("Error opening file:", err) 95 | return 96 | } 97 | newUploadReq := &coze.UploadFilesReq{ 98 | File: newFile, 99 | } 100 | newAvatarInfo, err := cozeCli.Files.Upload(ctx, newUploadReq) 101 | if err != nil { 102 | fmt.Println("Error uploading new avatar:", err) 103 | return 104 | } 105 | fmt.Println(newAvatarInfo) 106 | fmt.Println(newAvatarInfo.LogID()) 107 | 108 | // Update bot 109 | updateResp, err := cozeCli.Bots.Update(ctx, &coze.UpdateBotsReq{ 110 | BotID: botID, 111 | Name: "the name of your bot", 112 | IconFileID: newAvatarInfo.FileInfo.ID, 113 | ModelInfoConfig: &coze.BotModelInfoConfig{ 114 | ModelID: "1738675210", 115 | }, 116 | WorkflowIDList: &coze.WorkflowIDList{ 117 | IDs: []coze.WorkflowIDInfo{ 118 | { 119 | ID: os.Getenv("WORKFLOW_ID"), 120 | }, 121 | }, 122 | }, 123 | }) 124 | if err != nil { 125 | fmt.Println("Error updating bot:", err) 126 | return 127 | } 128 | fmt.Println(updateResp.LogID()) 129 | 130 | // Republish bot 131 | publishResp, err = cozeCli.Bots.Publish(ctx, &coze.PublishBotsReq{ 132 | BotID: botID, 133 | ConnectorIDs: []string{"1024"}, 134 | }) 135 | if err != nil { 136 | fmt.Println("Error republishing bot:", err) 137 | return 138 | } 139 | fmt.Println(publishResp) 140 | fmt.Println(publishResp.LogID()) 141 | } 142 | -------------------------------------------------------------------------------- /examples/bots/retrieve/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // This examples is for describing how to retrieve a bot, fetch published bot list from the API. 12 | // The document for those interface: 13 | func main() { 14 | // Get an access_token through personal access token or oauth. 15 | token := os.Getenv("COZE_API_TOKEN") 16 | botID := os.Getenv("PUBLISHED_BOT_ID") 17 | authCli := coze.NewTokenAuth(token) 18 | 19 | // Init the Coze client through the access_token. 20 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 21 | 22 | ctx := context.Background() 23 | 24 | // 25 | // retrieve a bot 26 | 27 | botInfo, err := cozeCli.Bots.Retrieve(ctx, &coze.RetrieveBotsReq{ 28 | BotID: botID, 29 | }) 30 | if err != nil { 31 | fmt.Println("Error retrieving bot:", err) 32 | return 33 | } 34 | fmt.Println(botInfo.Bot) 35 | fmt.Println("Log ID:", botInfo.LogID()) 36 | 37 | // 38 | // get published bot list 39 | 40 | pageNum := 1 41 | workspaceID := os.Getenv("WORKSPACE_ID") 42 | botList, err := cozeCli.Bots.List(ctx, &coze.ListBotsReq{ 43 | SpaceID: workspaceID, 44 | PageNum: pageNum, 45 | PageSize: 4, 46 | }) 47 | if err != nil { 48 | fmt.Println("Error listing bots:", err) 49 | return 50 | } 51 | 52 | // you can use iterator to automatically retrieve next page 53 | for botList.Next() { 54 | fmt.Println(botList.Current()) 55 | } 56 | 57 | if botList.Err() != nil { 58 | fmt.Println("Error listing bots:", botList.Err()) 59 | return 60 | } 61 | 62 | // the page result will return followed information 63 | fmt.Println("total:", botList.Total()) 64 | fmt.Println("has_more:", botList.HasMore()) 65 | } 66 | -------------------------------------------------------------------------------- /examples/chats/chat/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/coze-dev/coze-go" 10 | ) 11 | 12 | // 13 | // This examples describes how to use the chats interface to initiate conversations, 14 | // poll the status of the conversation, and obtain the messages after the conversation is completed. 15 | // 16 | 17 | func main() { 18 | // Get an access_token through personal access token or oauth. 19 | token := os.Getenv("COZE_API_TOKEN") 20 | botID := os.Getenv("PUBLISHED_BOT_ID") 21 | uid := os.Getenv("USER_ID") 22 | 23 | authCli := coze.NewTokenAuth(token) 24 | 25 | // Init the Coze client through the access_token. 26 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 27 | 28 | ctx := context.Background() 29 | 30 | // 31 | // Step one, create chats 32 | // Call the coze.Create.Create() method to create a chats. The create method is a non-streaming 33 | // chats and will return a Create class. Developers should periodically check the status of the 34 | // chats and handle them separately according to different states. 35 | // 36 | req := &coze.CreateChatsReq{ 37 | BotID: botID, 38 | UserID: uid, 39 | Messages: []*coze.Message{ 40 | coze.BuildUserQuestionText("What can you do?", nil), 41 | }, 42 | } 43 | 44 | chatResp, err := cozeCli.Chat.Create(ctx, req) 45 | if err != nil { 46 | fmt.Println("Error creating chats:", err) 47 | return 48 | } 49 | fmt.Println(chatResp) 50 | fmt.Println(chatResp.LogID()) 51 | chat := chatResp.Chat 52 | chatID := chat.ID 53 | conversationID := chat.ConversationID 54 | 55 | // 56 | // Step two, poll the result of chats 57 | // Assume the development allows at most one chats to runs for 10 seconds. If it exceeds 10 seconds, 58 | // the chats will be cancelled. 59 | // And when the chats status is not completed, poll the status of the chats once every second. 60 | // After the chats is completed, retrieve all messages in the chats. 61 | // 62 | timeout := time.After(1) // time.Second 63 | ticker := time.NewTicker(time.Second) 64 | defer ticker.Stop() 65 | 66 | for chat.Status == coze.ChatStatusInProgress { 67 | select { 68 | case <-timeout: 69 | // The chats can be cancelled before its completed. 70 | cancelResp, err := cozeCli.Chat.Cancel(ctx, &coze.CancelChatsReq{ 71 | ConversationID: conversationID, 72 | ChatID: chatID, 73 | }) 74 | if err != nil { 75 | fmt.Println("Error cancelling chats:", err) 76 | } 77 | fmt.Println("cancel") 78 | fmt.Println(cancelResp) 79 | fmt.Println(cancelResp.LogID()) 80 | break 81 | case <-ticker.C: 82 | resp, err := cozeCli.Chat.Retrieve(ctx, &coze.RetrieveChatsReq{ 83 | ConversationID: conversationID, 84 | ChatID: chatID, 85 | }) 86 | if err != nil { 87 | fmt.Println("Error retrieving chats:", err) 88 | continue 89 | } 90 | fmt.Println("retrieve") 91 | fmt.Println(resp) 92 | fmt.Println(resp.LogID()) 93 | chat = resp.Chat 94 | if chat.Status == coze.ChatStatusCompleted { 95 | break 96 | } 97 | } 98 | } 99 | 100 | // The sdk provide an automatic polling method. 101 | chat2, err := cozeCli.Chat.CreateAndPoll(ctx, req, nil) 102 | if err != nil { 103 | fmt.Println("Error in CreateAndPoll:", err) 104 | return 105 | } 106 | fmt.Println(chat2) 107 | 108 | // the developer can also set the timeout. 109 | pollTimeout := 10 110 | chat3, err := cozeCli.Chat.CreateAndPoll(ctx, req, &pollTimeout) 111 | if err != nil { 112 | fmt.Println("Error in CreateAndPollWithTimeout:", err) 113 | return 114 | } 115 | fmt.Println(chat3) 116 | } 117 | -------------------------------------------------------------------------------- /examples/chats/chat_with_image/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "github.com/coze-dev/coze-go" 11 | ) 12 | 13 | // This examples is about how to use the streaming interface to start a chats request 14 | // with image upload and handle chats events 15 | func main() { 16 | // Get an access_token through personal access token or oauth. 17 | token := os.Getenv("COZE_API_TOKEN") 18 | botID := os.Getenv("PUBLISHED_BOT_ID") 19 | userID := os.Getenv("USER_ID") 20 | 21 | authCli := coze.NewTokenAuth(token) 22 | 23 | // Init the Coze client through the access_token. 24 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 25 | 26 | ctx := context.Background() 27 | 28 | // Call the upload file interface to get the image id. 29 | images, err := os.Open(os.Getenv("IMAGE_FILE_PATH")) 30 | if err != nil { 31 | fmt.Println("Error opening image:", err) 32 | return 33 | } 34 | imageInfo, err := cozeCli.Files.Upload(ctx, &coze.UploadFilesReq{File: images}) 35 | if err != nil { 36 | fmt.Println("Error uploading image:", err) 37 | return 38 | } 39 | fmt.Printf("upload image success, image id:%s\n", imageInfo.FileInfo.ID) 40 | 41 | // 42 | // Step one, create chats 43 | // Call the coze.Create.Stream() method to create a chats. The create method is a streaming 44 | // chats and will return a channel of ChatEvent. Developers should iterate the channel to get 45 | // chats events and handle them. 46 | 47 | req := &coze.CreateChatsReq{ 48 | BotID: botID, 49 | UserID: userID, 50 | Messages: []*coze.Message{ 51 | coze.BuildUserQuestionObjects([]*coze.MessageObjectString{ 52 | coze.NewTextMessageObject("Describe this picture"), 53 | coze.NewImageMessageObjectByID(imageInfo.FileInfo.ID), 54 | }, nil), 55 | }, 56 | } 57 | 58 | resp, err := cozeCli.Chat.Stream(ctx, req) 59 | if err != nil { 60 | fmt.Println("Error starting stream:", err) 61 | return 62 | } 63 | 64 | defer resp.Close() 65 | for { 66 | event, err := resp.Recv() 67 | if errors.Is(err, io.EOF) { 68 | fmt.Println("Stream finished") 69 | break 70 | } 71 | if err != nil { 72 | fmt.Println(err) 73 | break 74 | } 75 | if event.Event == coze.ChatEventConversationMessageDelta { 76 | fmt.Print(event.Message.Content) 77 | } else if event.Event == coze.ChatEventConversationChatCompleted { 78 | fmt.Printf("Token usage:%d\n", event.Chat.Usage.TokenCount) 79 | } else { 80 | fmt.Printf("\n") 81 | } 82 | } 83 | 84 | fmt.Printf("done, log:%s\n", resp.Response().LogID()) 85 | } 86 | -------------------------------------------------------------------------------- /examples/chats/stream/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | "time" 10 | 11 | "github.com/coze-dev/coze-go" 12 | ) 13 | 14 | func main() { 15 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 16 | defer cancel() 17 | 18 | // Get an access_token through personal access token or oauth. 19 | token := os.Getenv("COZE_API_TOKEN") 20 | botID := os.Getenv("PUBLISHED_BOT_ID") 21 | userID := os.Getenv("USER_ID") 22 | 23 | authCli := coze.NewTokenAuth(token) 24 | 25 | // Init the Coze client through the access_token. 26 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 27 | 28 | // 29 | // Step one, create chats 30 | req := &coze.CreateChatsReq{ 31 | BotID: botID, 32 | UserID: userID, 33 | Messages: []*coze.Message{ 34 | coze.BuildUserQuestionText("What can you do?", nil), 35 | }, 36 | } 37 | 38 | resp, err := cozeCli.Chat.Stream(ctx, req) 39 | if err != nil { 40 | fmt.Printf("Error starting chats: %v\n", err) 41 | return 42 | } 43 | 44 | defer resp.Close() 45 | for { 46 | event, err := resp.Recv() 47 | if errors.Is(err, io.EOF) { 48 | fmt.Println("Stream finished") 49 | break 50 | } 51 | if err != nil { 52 | fmt.Println(err) 53 | break 54 | } 55 | if event.Event == coze.ChatEventConversationMessageDelta { 56 | fmt.Print(event.Message.Content) 57 | } else if event.Event == coze.ChatEventConversationChatCompleted { 58 | fmt.Printf("Token usage:%d\n", event.Chat.Usage.TokenCount) 59 | } else { 60 | fmt.Printf("\n") 61 | } 62 | } 63 | 64 | fmt.Printf("done, log:%s\n", resp.Response().LogID()) 65 | } 66 | -------------------------------------------------------------------------------- /examples/chats/submit_tool_output/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "github.com/coze-dev/coze-go" 11 | ) 12 | 13 | // This use case teaches you how to use local plugin. 14 | func main() { 15 | // Get an access_token through personal access token or oauth. 16 | token := os.Getenv("COZE_API_TOKEN") 17 | botID := os.Getenv("PUBLISHED_BOT_ID") 18 | userID := os.Getenv("USER_ID") 19 | 20 | authCli := coze.NewTokenAuth(token) 21 | 22 | // Init the Coze client through the access_token. 23 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 24 | 25 | ctx := context.Background() 26 | 27 | req := &coze.CreateChatsReq{ 28 | BotID: botID, 29 | UserID: userID, 30 | Messages: []*coze.Message{ 31 | coze.BuildUserQuestionText("What's the weather like in Shenzhen today?", nil), 32 | }, 33 | } 34 | 35 | var pluginEvent *coze.ChatEvent 36 | var conversationID string 37 | 38 | resp, err := cozeCli.Chat.Stream(ctx, req) 39 | if err != nil { 40 | fmt.Println("Error starting stream:", err) 41 | return 42 | } 43 | 44 | defer resp.Close() 45 | for { 46 | event, err := resp.Recv() 47 | if errors.Is(err, io.EOF) { 48 | fmt.Println("Stream finished") 49 | break 50 | } 51 | if err != nil { 52 | fmt.Println(err) 53 | break 54 | } 55 | if event.Event == coze.ChatEventConversationMessageDelta { 56 | fmt.Print(event.Message.Content) 57 | } else if event.Event == coze.ChatEventConversationChatCompleted { 58 | fmt.Printf("Token usage:%d\n", event.Chat.Usage.TokenCount) 59 | } else if event.Event == coze.ChatEventConversationChatRequiresAction { 60 | fmt.Println("need action") 61 | pluginEvent = event 62 | conversationID = event.Chat.ConversationID 63 | break 64 | } else { 65 | fmt.Printf("\n") 66 | } 67 | } 68 | 69 | if pluginEvent == nil { 70 | return 71 | } 72 | 73 | var toolOutputs []*coze.ToolOutput 74 | for _, callInfo := range pluginEvent.Chat.RequiredAction.SubmitToolOutputs.ToolCalls { 75 | callID := callInfo.ID 76 | // you can handle different plugin by name. 77 | functionName := callInfo.Function.Name 78 | // you should unmarshal arguments if necessary. 79 | argsJSON := callInfo.Function.Arguments 80 | 81 | fmt.Printf("Function called: %s with args: %s\n", functionName, argsJSON) 82 | toolOutputs = append(toolOutputs, &coze.ToolOutput{ 83 | ToolCallID: callID, 84 | Output: "It is 18 to 21", 85 | }) 86 | } 87 | 88 | toolReq := &coze.SubmitToolOutputsChatReq{ 89 | ChatID: pluginEvent.Chat.ID, 90 | ConversationID: conversationID, 91 | ToolOutputs: toolOutputs, 92 | } 93 | 94 | resp2, err := cozeCli.Chat.StreamSubmitToolOutputs(ctx, toolReq) 95 | if err != nil { 96 | fmt.Println("Error submitting tool outputs:", err) 97 | return 98 | } 99 | 100 | defer resp2.Close() 101 | for { 102 | event, err := resp2.Recv() 103 | if errors.Is(err, io.EOF) { 104 | fmt.Println("Stream finished") 105 | break 106 | } 107 | if err != nil { 108 | fmt.Println(err) 109 | break 110 | } 111 | if event.Event == coze.ChatEventConversationMessageDelta { 112 | fmt.Print(event.Message.Content) 113 | } else { 114 | fmt.Printf("\n") 115 | } 116 | } 117 | 118 | fmt.Printf("done, log:%s\n", resp.Response().LogID()) 119 | } 120 | -------------------------------------------------------------------------------- /examples/client/error/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // This examples demonstrates how to handle different types of errors from the Coze API. 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | ctx := context.Background() 21 | 22 | // Example 1: Handle API error 23 | _, err := cozeCli.Bots.Retrieve(ctx, &coze.RetrieveBotsReq{ 24 | BotID: "invalid_bot_id", 25 | }) 26 | if err != nil { 27 | if cozeErr, ok := coze.AsCozeError(err); ok { 28 | // Handle Coze API error 29 | fmt.Printf("Coze API error: %s (code: %d)\n", cozeErr.Message, cozeErr.Code) 30 | return 31 | } 32 | // Handle other errors 33 | fmt.Printf("Other error: %v\n", err) 34 | return 35 | } 36 | 37 | // Example 2: Handle auth error 38 | invalidToken := "invalid_token" 39 | invalidAuthCli := coze.NewTokenAuth(invalidToken) 40 | invalidCozeCli := coze.NewCozeAPI(invalidAuthCli) 41 | 42 | _, err = invalidCozeCli.Bots.List(ctx, &coze.ListBotsReq{ 43 | PageNum: 1, 44 | PageSize: 10, 45 | }) 46 | if err != nil { 47 | if cozeErr, ok := coze.AsAuthError(err); ok { 48 | // Handle auth error 49 | if cozeErr.Code == "unauthorized" { 50 | fmt.Println("Authentication failed. Please check your token.") 51 | return 52 | } 53 | fmt.Printf("Coze API error: %s (code: %s)\n", cozeErr.ErrorMessage, cozeErr.Code) 54 | return 55 | } 56 | fmt.Printf("Other error: %v\n", err) 57 | return 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/client/init/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "time" 8 | 9 | "github.com/coze-dev/coze-go" 10 | ) 11 | 12 | // This examples demonstrates how to initialize the Coze client with different configurations. 13 | func main() { 14 | // Get an access_token through personal access token or oauth. 15 | token := os.Getenv("COZE_API_TOKEN") 16 | authCli := coze.NewTokenAuth(token) 17 | 18 | // 1. Initialize with default configuration 19 | cozeCli1 := coze.NewCozeAPI(authCli) 20 | fmt.Println("client 1:", cozeCli1) 21 | 22 | // 2. Initialize with custom base URL 23 | cozeAPIBase := os.Getenv("COZE_API_BASE") 24 | cozeCli2 := coze.NewCozeAPI(authCli, coze.WithBaseURL(cozeAPIBase)) 25 | fmt.Println("client 2:", cozeCli2) 26 | 27 | // 3. Initialize with custom HTTP client 28 | customClient := &http.Client{ 29 | Timeout: 30 * time.Second, 30 | Transport: &http.Transport{ 31 | MaxIdleConns: 100, 32 | MaxIdleConnsPerHost: 100, 33 | IdleConnTimeout: 90 * time.Second, 34 | }, 35 | } 36 | cozeCli3 := coze.NewCozeAPI(authCli, 37 | coze.WithBaseURL(cozeAPIBase), 38 | coze.WithHttpClient(customClient), 39 | ) 40 | fmt.Println("client 3:", cozeCli3) 41 | } 42 | -------------------------------------------------------------------------------- /examples/client/log/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // This examples demonstrates how to get response log ID from different API calls. 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) // coze.WithLogger() The developer can set the logger to print the log. 19 | // coze.WithLogLevel(log.LogDebug) The developer can set the log level. 20 | 21 | ctx := context.Background() 22 | botID := os.Getenv("COZE_BOT_ID") 23 | 24 | // Example 1: Get log ID from bot retrieve API 25 | botsResp, err := cozeCli.Bots.Retrieve(ctx, &coze.RetrieveBotsReq{ 26 | BotID: botID, 27 | }) 28 | if err != nil { 29 | fmt.Printf("Error retrieving bot: %v\n", err) 30 | return 31 | } 32 | fmt.Printf("Bot retrieve log ID: %s\n", botsResp.Response().LogID()) 33 | 34 | // Example 2: Get log ID from chats API 35 | chatResp, err := cozeCli.Chat.Create(ctx, &coze.CreateChatsReq{ 36 | BotID: botID, 37 | UserID: os.Getenv("USER_ID"), 38 | Messages: []*coze.Message{ 39 | coze.BuildUserQuestionText("What can you do?", nil), 40 | }, 41 | }) 42 | if err != nil { 43 | fmt.Printf("Error creating chats: %v\n", err) 44 | return 45 | } 46 | fmt.Printf("Create create log ID: %s\n", chatResp.Response().LogID()) 47 | 48 | // Example 3: Get log ID from file upload API 49 | file, err := os.Open(os.Getenv("FILE_PATH")) 50 | if err != nil { 51 | fmt.Printf("Error opening file: %v\n", err) 52 | return 53 | } 54 | defer file.Close() 55 | 56 | fileResp, err := cozeCli.Files.Upload(ctx, &coze.UploadFilesReq{File: file}) 57 | if err != nil { 58 | fmt.Printf("Error uploading file: %v\n", err) 59 | return 60 | } 61 | fmt.Printf("File upload log ID: %s\n", fileResp.Response().LogID()) 62 | } 63 | -------------------------------------------------------------------------------- /examples/conversations/crud/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/coze-dev/coze-go" 10 | ) 11 | 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | botID := os.Getenv("COZE_BOT_ID") 17 | 18 | // Init the Coze client through the access_token. 19 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 20 | 21 | ctx := context.Background() 22 | // Create a new conversation 23 | resp, err := cozeCli.Conversations.Create(ctx, &coze.CreateConversationsReq{BotID: botID}) 24 | if err != nil { 25 | fmt.Println("Error creating conversation:", err) 26 | return 27 | } 28 | fmt.Println("create conversations:", resp.Conversation) 29 | fmt.Println(resp.LogID()) 30 | 31 | conversationID := resp.Conversation.ID 32 | 33 | // Retrieve the conversation 34 | getResp, err := cozeCli.Conversations.Retrieve(ctx, &coze.RetrieveConversationsReq{ConversationID: conversationID}) 35 | if err != nil { 36 | fmt.Println("Error retrieving conversation:", err) 37 | return 38 | } 39 | fmt.Println("retrieve conversations:", getResp) 40 | fmt.Println(getResp.LogID()) 41 | 42 | // you can manually create message for conversation 43 | createMessageReq := &coze.CreateMessageReq{} 44 | createMessageReq.Role = coze.MessageRoleAssistant 45 | createMessageReq.ConversationID = conversationID 46 | createMessageReq.SetObjectContext([]*coze.MessageObjectString{ 47 | coze.NewFileMessageObjectByURL(os.Getenv("IMAGE_FILE_PATH")), 48 | coze.NewTextMessageObject("hello"), 49 | coze.NewImageMessageObjectByURL(os.Getenv("IMAGE_FILE_PATH")), 50 | }) 51 | time.Sleep(time.Second) 52 | 53 | msgs, err := cozeCli.Conversations.Messages.Create(ctx, createMessageReq) 54 | if err != nil { 55 | fmt.Println("Error creating message:", err) 56 | return 57 | } 58 | fmt.Println(msgs) 59 | fmt.Println(msgs.LogID()) 60 | 61 | // Clear the conversation 62 | clearResp, err := cozeCli.Conversations.Clear(ctx, &coze.ClearConversationsReq{ConversationID: conversationID}) 63 | if err != nil { 64 | fmt.Println("Error clearing conversation:", err) 65 | return 66 | } 67 | fmt.Println(clearResp) 68 | fmt.Println(clearResp.LogID()) 69 | } 70 | -------------------------------------------------------------------------------- /examples/conversations/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | botID := os.Getenv("PUBLISHED_BOT_ID") 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | ctx := context.Background() 21 | // you can use iterator to automatically retrieve next page 22 | conversations, err := cozeCli.Conversations.List(ctx, &coze.ListConversationsReq{PageSize: 2, BotID: botID}) 23 | if err != nil { 24 | fmt.Println("Error fetching conversations:", err) 25 | return 26 | } 27 | for conversations.Next() { 28 | fmt.Println(conversations.Current()) 29 | } 30 | if conversations.Err() != nil { 31 | fmt.Println("Error fetching conversations:", conversations.Err()) 32 | return 33 | } 34 | 35 | // the page result will return followed information 36 | fmt.Println("has_more:", conversations.HasMore()) 37 | } 38 | -------------------------------------------------------------------------------- /examples/conversations/messages/crud/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | 16 | // Init the Coze client through the access_token. 17 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 18 | 19 | ctx := context.Background() 20 | conversationID := os.Getenv("CONVERSATION_ID") 21 | 22 | // 23 | // create message to specific conversation 24 | 25 | createReq := &coze.CreateMessageReq{ 26 | ConversationID: conversationID, 27 | Role: coze.MessageRoleUser, 28 | Content: "message count", 29 | ContentType: coze.MessageContentTypeText, 30 | } 31 | messageResp, err := cozeCli.Conversations.Messages.Create(ctx, createReq) 32 | if err != nil { 33 | fmt.Println("Error creating message:", err) 34 | return 35 | } 36 | message := messageResp.Message 37 | fmt.Println(message) 38 | fmt.Println(messageResp.LogID()) 39 | 40 | // 41 | // retrieve message 42 | 43 | retrievedMsgResp, err := cozeCli.Conversations.Messages.Retrieve(ctx, &coze.RetrieveConversationsMessagesReq{ 44 | ConversationID: conversationID, 45 | MessageID: message.ID, 46 | }) 47 | if err != nil { 48 | fmt.Println("Error retrieving message:", err) 49 | return 50 | } 51 | retrievedMsg := retrievedMsgResp.Message 52 | fmt.Println(retrievedMsg) 53 | fmt.Println(retrievedMsgResp.LogID()) 54 | 55 | // 56 | // update message 57 | 58 | updateReq := &coze.UpdateConversationMessagesReq{ 59 | ConversationID: conversationID, 60 | MessageID: message.ID, 61 | Content: fmt.Sprintf("modified message content:%s", message.Content), 62 | ContentType: coze.MessageContentTypeText, 63 | } 64 | updateResp, err := cozeCli.Conversations.Messages.Update(ctx, updateReq) 65 | if err != nil { 66 | fmt.Println("Error updating message:", err) 67 | return 68 | } 69 | updatedMsg := updateResp.Message 70 | fmt.Println(updatedMsg) 71 | fmt.Println(updateResp.LogID()) 72 | 73 | // 74 | // delete message 75 | 76 | deletedMsgResp, err := cozeCli.Conversations.Messages.Delete(ctx, &coze.DeleteConversationsMessagesReq{ 77 | ConversationID: conversationID, 78 | MessageID: message.ID, 79 | }) 80 | if err != nil { 81 | fmt.Println("Error deleting message:", err) 82 | return 83 | } 84 | deletedMsg := deletedMsgResp.Message 85 | fmt.Println(deletedMsg) 86 | fmt.Println(deletedMsgResp.LogID()) 87 | } 88 | -------------------------------------------------------------------------------- /examples/conversations/messages/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | conversationID := os.Getenv("CONVERSATION_ID") 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | ctx := context.Background() 21 | // you can use iterator to automatically retrieve next page 22 | message, err := cozeCli.Conversations.Messages.List(ctx, &coze.ListConversationsMessagesReq{Limit: 2, ConversationID: conversationID}) 23 | if err != nil { 24 | fmt.Println("Error fetching message:", err) 25 | return 26 | } 27 | for message.Next() { 28 | fmt.Println(message.Current()) 29 | } 30 | if message.Err() != nil { 31 | fmt.Println("Error fetching message:", message.Err()) 32 | return 33 | } 34 | 35 | // the page result will return followed information 36 | fmt.Println("has_more:", message.HasMore()) 37 | } 38 | -------------------------------------------------------------------------------- /examples/datasets/crud/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/coze-dev/coze-go" 10 | ) 11 | 12 | func main() { 13 | // Get an access_token through personal access token or oauth 14 | token := os.Getenv("COZE_API_TOKEN") 15 | 16 | authCli := coze.NewTokenAuth(token) 17 | 18 | // Init the Coze client through the access_token. 19 | client := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 20 | 21 | spaceID := os.Getenv("WORKSPACE_ID") 22 | ctx := context.Background() 23 | 24 | /* 25 | * Create dataset 26 | */ 27 | createReq := &coze.CreateDatasetsReq{ 28 | Name: "test dataset", 29 | SpaceID: spaceID, 30 | FormatType: coze.DocumentFormatTypeDocument, 31 | Description: "test dataset description", 32 | } 33 | 34 | createResp, err := client.Datasets.Create(ctx, createReq) 35 | if err != nil { 36 | fmt.Printf("Failed to create dataset: %v\n", err) 37 | return 38 | } 39 | fmt.Printf("Created dataset ID: %s\n", createResp.DatasetID) 40 | 41 | datasetID := createResp.DatasetID 42 | 43 | // Wait for 5 seconds 44 | time.Sleep(5 * time.Second) 45 | 46 | /* 47 | * Update dataset 48 | */ 49 | updateReq := &coze.UpdateDatasetsReq{ 50 | DatasetID: datasetID, 51 | Name: "updated dataset name", 52 | Description: "updated dataset description", 53 | } 54 | 55 | updateResp, err := client.Datasets.Update(ctx, updateReq) 56 | if err != nil { 57 | fmt.Printf("Failed to update dataset: %v\n", err) 58 | return 59 | } 60 | fmt.Printf("Update dataset response: %+v\n", updateResp) 61 | 62 | /* 63 | * Delete dataset 64 | */ 65 | deleteReq := &coze.DeleteDatasetsReq{ 66 | DatasetID: datasetID, 67 | } 68 | 69 | deleteResp, err := client.Datasets.Delete(ctx, deleteReq) 70 | if err != nil { 71 | fmt.Printf("Failed to delete dataset: %v\n", err) 72 | return 73 | } 74 | fmt.Printf("Delete dataset response: %+v\n", deleteResp) 75 | } 76 | -------------------------------------------------------------------------------- /examples/datasets/documents/crud/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | 9 | "github.com/coze-dev/coze-go" 10 | ) 11 | 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | ctx := context.Background() 21 | datasetID, _ := strconv.ParseInt(os.Getenv("DATASET_ID"), 10, 64) 22 | 23 | // 24 | // create document in to specific dataset 25 | 26 | createReq := &coze.CreateDatasetsDocumentsReq{ 27 | DatasetID: datasetID, 28 | DocumentBases: []*coze.DocumentBase{ 29 | coze.DocumentBaseBuildLocalFile("file doc examples", "your file content", "txt"), 30 | }, 31 | } 32 | createResp, err := cozeCli.Datasets.Documents.Create(ctx, createReq) 33 | if err != nil { 34 | fmt.Println("Error creating documents:", err) 35 | return 36 | } 37 | fmt.Println(createResp) 38 | fmt.Println(createResp.LogID()) 39 | 40 | var documentIDs []int64 41 | for _, doc := range createResp.DocumentInfos { 42 | id, _ := strconv.ParseInt(doc.DocumentID, 10, 64) 43 | documentIDs = append(documentIDs, id) 44 | } 45 | 46 | // 47 | // update document. It means success that no exception has been thrown 48 | 49 | updateReq := &coze.UpdateDatasetsDocumentsReq{ 50 | DocumentID: documentIDs[0], 51 | DocumentName: "new name", 52 | } 53 | updateResp, err := cozeCli.Datasets.Documents.Update(ctx, updateReq) 54 | if err != nil { 55 | fmt.Println("Error updating document:", err) 56 | return 57 | } 58 | fmt.Println(updateResp) 59 | fmt.Println(updateResp.LogID()) 60 | 61 | // 62 | // delete document. It means success that no exception has been thrown 63 | 64 | deleteResp, err := cozeCli.Datasets.Documents.Delete(ctx, &coze.DeleteDatasetsDocumentsReq{ 65 | DocumentIDs: []int64{documentIDs[0]}, 66 | }) 67 | if err != nil { 68 | fmt.Println("Error deleting document:", err) 69 | return 70 | } 71 | fmt.Println(deleteResp) 72 | fmt.Println(deleteResp.LogID()) 73 | } 74 | -------------------------------------------------------------------------------- /examples/datasets/documents/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | 9 | "github.com/coze-dev/coze-go" 10 | ) 11 | 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | datasetID, _ := strconv.ParseInt(os.Getenv("DATASET_ID"), 10, 64) 17 | 18 | // Init the Coze client through the access_token. 19 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 20 | 21 | ctx := context.Background() 22 | // you can use iterator to automatically retrieve next page 23 | documents, err := cozeCli.Datasets.Documents.List(ctx, &coze.ListDatasetsDocumentsReq{Size: 2, DatasetID: datasetID}) 24 | if err != nil { 25 | fmt.Println("Error fetching documents:", err) 26 | return 27 | } 28 | for documents.Next() { 29 | fmt.Println(documents.Current()) 30 | } 31 | if documents.Err() != nil { 32 | fmt.Println("Error fetching documents:", documents.Err()) 33 | return 34 | } 35 | 36 | // the page result will return followed information 37 | fmt.Println("has_more:", documents.HasMore()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/datasets/image/crud/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/coze-dev/coze-go" 11 | ) 12 | 13 | func main() { 14 | // Get an access_token through personal access token or oauth 15 | token := os.Getenv("COZE_API_TOKEN") 16 | 17 | authCli := coze.NewTokenAuth(token) 18 | 19 | // Init the Coze client through the access_token. 20 | client := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 21 | 22 | datasetID := os.Getenv("DATASET_ID") 23 | ctx := context.Background() 24 | /* 25 | * Create image document 26 | */ 27 | 28 | /* 29 | * Step 1: Upload image to Coze 30 | */ 31 | imagePath := os.Getenv("IMAGE_FILE_PATH") 32 | fileInfo, err := os.Open(imagePath) 33 | if err != nil { 34 | fmt.Printf("Failed to open image file: %v\n", err) 35 | return 36 | } 37 | imageInfo, err := client.Files.Upload(ctx, &coze.UploadFilesReq{ 38 | File: fileInfo, 39 | }) 40 | if err != nil { 41 | fmt.Printf("Failed to upload image: %v\n", err) 42 | return 43 | } 44 | fmt.Printf("Image uploaded: %+v\n", imageInfo) 45 | 46 | /* 47 | * Step 2: Create document 48 | */ 49 | datasetIDInt, _ := strconv.ParseInt(datasetID, 10, 64) 50 | fileIDInt, _ := strconv.ParseInt(imageInfo.FileInfo.ID, 10, 64) 51 | 52 | createReq := &coze.CreateDatasetsDocumentsReq{ 53 | DatasetID: datasetIDInt, 54 | DocumentBases: []*coze.DocumentBase{ 55 | coze.DocumentBaseBuildImage("test image", fileIDInt), 56 | }, 57 | FormatType: coze.DocumentFormatTypeImage, 58 | } 59 | 60 | createResp, err := client.Datasets.Documents.Create(ctx, createReq) 61 | if err != nil { 62 | fmt.Printf("Failed to create document: %v\n", err) 63 | return 64 | } 65 | fmt.Printf("Document created: %+v\n", createResp) 66 | 67 | /* 68 | * Step 3: Make sure upload is completed 69 | */ 70 | documentID := createResp.DocumentInfos[0].DocumentID 71 | 72 | processReq := &coze.ProcessDocumentsReq{ 73 | DatasetID: datasetID, 74 | DocumentIDs: []string{documentID}, 75 | } 76 | 77 | // Poll until processing is complete 78 | for { 79 | processResp, err := client.Datasets.Process(ctx, processReq) 80 | if err != nil { 81 | fmt.Printf("Failed to check process status: %v\n", err) 82 | return 83 | } 84 | fmt.Printf("Process status: %+v\n", processResp) 85 | 86 | status := processResp.Data[0].Status 87 | if status == coze.DocumentStatusProcessing { 88 | fmt.Printf("Upload is not completed, please wait, process: %d\n", processResp.Data[0].Progress) 89 | time.Sleep(time.Second) 90 | } else if status == coze.DocumentStatusFailed { 91 | fmt.Println("Upload failed, please check") 92 | return 93 | } else { 94 | fmt.Println("Upload completed") 95 | break 96 | } 97 | } 98 | 99 | /* 100 | * Update image caption 101 | */ 102 | caption := "new image caption" 103 | updateReq := &coze.UpdateDatasetImageReq{ 104 | DatasetID: datasetID, 105 | DocumentID: documentID, 106 | Caption: &caption, 107 | } 108 | 109 | updateResp, err := client.Datasets.Images.Update(ctx, updateReq) 110 | if err != nil { 111 | fmt.Printf("Failed to update image: %v\n", err) 112 | return 113 | } 114 | fmt.Printf("Image updated: %+v\n", updateResp) 115 | } 116 | -------------------------------------------------------------------------------- /examples/datasets/image/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth 13 | token := os.Getenv("COZE_API_TOKEN") 14 | 15 | // Initialize the Coze client with custom configuration 16 | client := coze.NewCozeAPI( 17 | coze.NewTokenAuth(token), 18 | coze.WithBaseURL(os.Getenv("COZE_API_BASE")), 19 | ) 20 | ctx := context.Background() 21 | 22 | datasetID := os.Getenv("DATASET_ID") 23 | 24 | // Create list request with pagination parameters 25 | req := &coze.ListDatasetsImagesReq{ 26 | DatasetID: datasetID, 27 | PageSize: 2, 28 | PageNum: 1, 29 | } 30 | 31 | // Get paginated results 32 | images, err := client.Datasets.Images.List(ctx, req) 33 | if err != nil { 34 | fmt.Printf("Failed to list images: %v\n", err) 35 | return 36 | } 37 | 38 | for images.Next() { 39 | fmt.Println(images.Current()) 40 | } 41 | if images.Err() != nil { 42 | fmt.Println("Error fetching images:", images.Err()) 43 | return 44 | } 45 | 46 | // the page result will return followed information 47 | fmt.Println("total:", images.Total()) 48 | fmt.Println("has_more:", images.HasMore()) 49 | } 50 | -------------------------------------------------------------------------------- /examples/datasets/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | token := os.Getenv("COZE_API_TOKEN") 13 | authCli := coze.NewTokenAuth(token) 14 | 15 | client := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 16 | 17 | ctx := context.Background() 18 | 19 | spaceID := os.Getenv("WORKSPACE_ID") 20 | 21 | // Create list request with pagination parameters 22 | req := &coze.ListDatasetsReq{ 23 | SpaceID: spaceID, 24 | PageSize: 2, 25 | PageNum: 1, 26 | } 27 | 28 | // Get paginated results 29 | datasets, err := client.Datasets.List(ctx, req) 30 | if err != nil { 31 | fmt.Printf("Failed to list datasets: %v\n", err) 32 | return 33 | } 34 | 35 | for datasets.Next() { 36 | fmt.Println(datasets.Current()) 37 | } 38 | if datasets.Err() != nil { 39 | fmt.Println("Error fetching datasets:", datasets.Err()) 40 | return 41 | } 42 | 43 | // the page result will return followed information 44 | fmt.Println("total:", datasets.Total()) 45 | fmt.Println("has_more:", datasets.HasMore()) 46 | } 47 | -------------------------------------------------------------------------------- /examples/files/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/coze-dev/coze-go" 10 | ) 11 | 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | ctx := context.Background() 21 | filePath := os.Getenv("IMAGE_FILE_PATH") 22 | file, err := os.Open(filePath) 23 | if err != nil { 24 | fmt.Println("Error opening file:", err) 25 | return 26 | } 27 | 28 | // the developers can directly use file to upload 29 | uploadResp, err := cozeCli.Files.Upload(ctx, &coze.UploadFilesReq{ 30 | File: file, 31 | }) 32 | if err != nil { 33 | fmt.Println("Error uploading file:", err) 34 | return 35 | } 36 | fileInfo := uploadResp.FileInfo 37 | // The developers can also upload file by bytes stream 38 | // uploadResp, err = cozeCli.Files.Upload(ctx, coze.NewUploadFileReq(bytes.NewReader([]byte("hello world"))," your file name")) 39 | // if err != nil{ 40 | // fmt.Println("Error uploading file:", err) 41 | // return 42 | // } 43 | 44 | // wait the server to process the file 45 | time.Sleep(time.Second) 46 | 47 | // retrieve file 48 | retrievedResp, err := cozeCli.Files.Retrieve(ctx, &coze.RetrieveFilesReq{ 49 | FileID: fileInfo.ID, 50 | }) 51 | if err != nil { 52 | fmt.Println("Error retrieving file:", err) 53 | return 54 | } 55 | retrievedInfo := retrievedResp.FileInfo 56 | fmt.Println(retrievedInfo) 57 | fmt.Println(retrievedResp.LogID()) 58 | } 59 | -------------------------------------------------------------------------------- /examples/template/duplicate/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth 13 | token := os.Getenv("COZE_API_TOKEN") 14 | 15 | authCli := coze.NewTokenAuth(token) 16 | 17 | // Init the Coze client through the access_token. 18 | client := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | templateID := os.Getenv("COZE_TEMPLATE_ID") 21 | workspaceID := os.Getenv("WORKSPACE_ID") 22 | ctx := context.Background() 23 | 24 | /* 25 | * Duplicate template 26 | */ 27 | newName := "duplicated_template" 28 | req := &coze.DuplicateTemplateReq{ 29 | WorkspaceID: workspaceID, 30 | Name: &newName, 31 | } 32 | 33 | resp, err := client.Templates.Duplicate(ctx, templateID, req) 34 | if err != nil { 35 | fmt.Printf("Failed to duplicate template: %v\n", err) 36 | return 37 | } 38 | fmt.Printf("Duplicated template - Entity ID: %s, Entity Type: %s\n", resp.EntityID, resp.EntityType) 39 | } 40 | -------------------------------------------------------------------------------- /examples/workflow/chat/stream/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | "time" 10 | 11 | "github.com/coze-dev/coze-go" 12 | ) 13 | 14 | func main() { 15 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 16 | defer cancel() 17 | 18 | // Get an access_token through personal access token or oauth. 19 | token := os.Getenv("COZE_API_TOKEN") 20 | workflowID := os.Getenv("WORKFLOW_ID") 21 | botID := os.Getenv("PUBLISHED_BOT_ID") 22 | authCli := coze.NewTokenAuth(token) 23 | 24 | // Init the Coze client through the access_token. 25 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 26 | 27 | // 28 | // Step one, create chats 29 | req := &coze.WorkflowsChatStreamReq{ 30 | BotID: &botID, 31 | WorkflowID: workflowID, 32 | AdditionalMessages: []*coze.Message{ 33 | coze.BuildUserQuestionText("What can you do?", nil), 34 | }, 35 | Parameters: map[string]any{ 36 | "name": "John", 37 | }, 38 | } 39 | 40 | resp, err := cozeCli.Workflows.Chat.Stream(ctx, req) 41 | if err != nil { 42 | fmt.Printf("Error starting chats: %v\n", err) 43 | return 44 | } 45 | 46 | defer resp.Close() 47 | for { 48 | event, err := resp.Recv() 49 | if errors.Is(err, io.EOF) { 50 | fmt.Println("Stream finished") 51 | break 52 | } 53 | if err != nil { 54 | fmt.Println(err) 55 | break 56 | } 57 | if event.Event == coze.ChatEventConversationMessageDelta { 58 | fmt.Print(event.Message.Content) 59 | } else if event.Event == coze.ChatEventConversationChatCompleted { 60 | fmt.Printf("\n") 61 | fmt.Printf("Token usage:%d\n", event.Chat.Usage.TokenCount) 62 | } else if event.Event == coze.ChatEventDone { 63 | fmt.Printf("debug_url: %s\n", event.WorkflowDebug.DebugUrl) 64 | } 65 | } 66 | 67 | fmt.Printf("done, log:%s\n", resp.Response().LogID()) 68 | } 69 | -------------------------------------------------------------------------------- /examples/workflows/runs/async_run/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/coze-dev/coze-go" 10 | ) 11 | 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | ctx := context.Background() 21 | workflowID := os.Getenv("WORKFLOW_ID") 22 | 23 | // if your workflow need input params, you can send them by map 24 | data := map[string]interface{}{ 25 | "date": "param values", 26 | } 27 | 28 | req := &coze.RunWorkflowsReq{ 29 | WorkflowID: workflowID, 30 | Parameters: data, 31 | IsAsync: true, // if you want the workflow runs asynchronously, you must set isAsync to true. 32 | } 33 | 34 | resp, err := cozeCli.Workflows.Runs.Create(ctx, req) 35 | if err != nil { 36 | fmt.Println("Error running workflow:", err) 37 | return 38 | } 39 | fmt.Println("Start async workflow runs:", resp.ExecuteID) 40 | fmt.Println(resp.LogID()) 41 | 42 | executeID := resp.ExecuteID 43 | isFinished := false 44 | 45 | for !isFinished { 46 | historyResp, err := cozeCli.Workflows.Runs.Histories.Retrieve(ctx, &coze.RetrieveWorkflowsRunsHistoriesReq{ 47 | WorkflowID: workflowID, 48 | ExecuteID: executeID, 49 | }) 50 | if err != nil { 51 | fmt.Println("Error retrieving history:", err) 52 | return 53 | } 54 | fmt.Println(historyResp) 55 | fmt.Println(historyResp.LogID()) 56 | 57 | history := historyResp.Histories[0] 58 | switch history.ExecuteStatus { 59 | case coze.WorkflowExecuteStatusFail: 60 | fmt.Println("Workflow runs failed, reason:", history.ErrorMessage) 61 | isFinished = true 62 | case coze.WorkflowExecuteStatusRunning: 63 | fmt.Println("Workflow runs is running") 64 | time.Sleep(time.Second) 65 | default: 66 | fmt.Println("Workflow runs success:", history.Output) 67 | isFinished = true 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/workflows/runs/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | // This examples describes how to use the workflow interface to chats. 12 | func main() { 13 | // Get an access_token through personal access token or oauth. 14 | token := os.Getenv("COZE_API_TOKEN") 15 | authCli := coze.NewTokenAuth(token) 16 | 17 | // Init the Coze client through the access_token. 18 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 19 | 20 | ctx := context.Background() 21 | workflowID := os.Getenv("WORKFLOW_ID") 22 | 23 | // if your workflow need input params, you can send them by map 24 | data := map[string]interface{}{ 25 | "date": "param values", 26 | } 27 | 28 | req := &coze.RunWorkflowsReq{ 29 | WorkflowID: workflowID, 30 | Parameters: data, 31 | IsAsync: true, 32 | } 33 | 34 | resp, err := cozeCli.Workflows.Runs.Create(ctx, req) 35 | if err != nil { 36 | fmt.Println("Error running workflow:", err) 37 | return 38 | } 39 | fmt.Println(resp) 40 | fmt.Println(resp.LogID()) 41 | } 42 | -------------------------------------------------------------------------------- /examples/workflows/runs/stream/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "github.com/coze-dev/coze-go" 11 | ) 12 | 13 | // This examples describes how to use the workflow interface to stream chats. 14 | func main() { 15 | // Get an access_token through personal access token or oauth. 16 | token := os.Getenv("COZE_API_TOKEN") 17 | authCli := coze.NewTokenAuth(token) 18 | 19 | // Init the Coze client through the access_token. 20 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 21 | 22 | ctx := context.Background() 23 | workflowID := os.Getenv("WORKFLOW_ID") 24 | 25 | // if your workflow need input params, you can send them by map 26 | data := map[string]interface{}{ 27 | "date": "param values", 28 | } 29 | 30 | req := &coze.RunWorkflowsReq{ 31 | WorkflowID: workflowID, 32 | Parameters: data, 33 | } 34 | 35 | resp, err := cozeCli.Workflows.Runs.Stream(ctx, req) 36 | if err != nil { 37 | fmt.Println("Error starting stream:", err) 38 | return 39 | } 40 | 41 | handleEvents(ctx, resp, cozeCli, workflowID) 42 | } 43 | 44 | // The stream interface will return an iterator of WorkflowEvent. Developers should iterate 45 | // through this iterator to obtain WorkflowEvent and handle them separately according to 46 | // the type of WorkflowEvent. 47 | func handleEvents(ctx context.Context, resp coze.Stream[coze.WorkflowEvent], cozeCli coze.CozeAPI, workflowID string) { 48 | defer resp.Close() 49 | for { 50 | event, err := resp.Recv() 51 | if errors.Is(err, io.EOF) { 52 | fmt.Println("Stream finished") 53 | break 54 | } 55 | if err != nil { 56 | fmt.Println("Error receiving event:", err) 57 | break 58 | } 59 | 60 | switch event.Event { 61 | case coze.WorkflowEventTypeMessage: 62 | fmt.Println("Got message:", event.Message) 63 | case coze.WorkflowEventTypeError: 64 | fmt.Println("Got error:", event.Error) 65 | case coze.WorkflowEventTypeDone: 66 | fmt.Println("Got message:", event.Message) 67 | case coze.WorkflowEventTypeInterrupt: 68 | resumeReq := &coze.ResumeRunWorkflowsReq{ 69 | WorkflowID: workflowID, 70 | EventID: event.Interrupt.InterruptData.EventID, 71 | ResumeData: "your data", 72 | InterruptType: event.Interrupt.InterruptData.Type, 73 | } 74 | newResp, err := cozeCli.Workflows.Runs.Resume(ctx, resumeReq) 75 | if err != nil { 76 | fmt.Println("Error resuming workflow:", err) 77 | return 78 | } 79 | fmt.Println("start resume workflow") 80 | handleEvents(ctx, newResp, cozeCli, workflowID) 81 | } 82 | } 83 | fmt.Printf("done, log:%s\n", resp.Response().LogID()) 84 | } 85 | -------------------------------------------------------------------------------- /examples/workspaces/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/coze-dev/coze-go" 9 | ) 10 | 11 | func main() { 12 | // Get an access_token through personal access token or oauth. 13 | token := os.Getenv("COZE_API_TOKEN") 14 | authCli := coze.NewTokenAuth(token) 15 | 16 | // Init the Coze client through the access_token. 17 | cozeCli := coze.NewCozeAPI(authCli, coze.WithBaseURL(os.Getenv("COZE_API_BASE"))) 18 | 19 | ctx := context.Background() 20 | // you can use iterator to automatically retrieve next page 21 | workspaces, err := cozeCli.Workspaces.List(ctx, &coze.ListWorkspaceReq{PageSize: 2}) 22 | if err != nil { 23 | fmt.Println("Error fetching workspaces:", err) 24 | return 25 | } 26 | for workspaces.Next() { 27 | fmt.Println(workspaces.Current()) 28 | } 29 | if workspaces.Err() != nil { 30 | fmt.Println("Error fetching workspaces:", workspaces.Err()) 31 | return 32 | } 33 | 34 | // the page result will return followed information 35 | fmt.Println("total:", workspaces.Total()) 36 | fmt.Println("has_more:", workspaces.HasMore()) 37 | } 38 | -------------------------------------------------------------------------------- /files.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | func (r *files) Upload(ctx context.Context, req *UploadFilesReq) (*UploadFilesResp, error) { 10 | path := "/v1/files/upload" 11 | resp := &uploadFilesResp{} 12 | err := r.core.UploadFile(ctx, path, req.File, req.File.Name(), nil, resp) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | resp.FileInfo.setHTTPResponse(resp.HTTPResponse) 18 | return resp.FileInfo, nil 19 | } 20 | 21 | func (r *files) Retrieve(ctx context.Context, req *RetrieveFilesReq) (*RetrieveFilesResp, error) { 22 | method := http.MethodPost 23 | uri := "/v1/files/retrieve" 24 | resp := &retrieveFilesResp{} 25 | err := r.core.Request(ctx, method, uri, nil, resp, withHTTPQuery("file_id", req.FileID)) 26 | if err != nil { 27 | return nil, err 28 | } 29 | resp.FileInfo.setHTTPResponse(resp.HTTPResponse) 30 | return resp.FileInfo, nil 31 | } 32 | 33 | type files struct { 34 | core *core 35 | } 36 | 37 | func newFiles(core *core) *files { 38 | return &files{core: core} 39 | } 40 | 41 | // FileInfo represents information about a file 42 | type FileInfo struct { 43 | // The ID of the uploaded file. 44 | ID string `json:"id"` 45 | 46 | // The total byte size of the file. 47 | Bytes int `json:"bytes"` 48 | 49 | // The upload time of the file, in the format of a 10-digit Unix timestamp in seconds (s). 50 | CreatedAt int `json:"created_at"` 51 | 52 | // The name of the file. 53 | FileName string `json:"file_name"` 54 | } 55 | 56 | type FileTypes interface { 57 | io.Reader 58 | Name() string 59 | } 60 | 61 | type implFileInterface struct { 62 | io.Reader 63 | fileName string 64 | } 65 | 66 | func (r *implFileInterface) Name() string { 67 | return r.fileName 68 | } 69 | 70 | type UploadFilesReq struct { 71 | File FileTypes 72 | } 73 | 74 | func NewUploadFile(reader io.Reader, fileName string) FileTypes { 75 | return &implFileInterface{ 76 | Reader: reader, 77 | fileName: fileName, 78 | } 79 | } 80 | 81 | // RetrieveFilesReq represents request for retrieving file 82 | type RetrieveFilesReq struct { 83 | FileID string `json:"file_id"` 84 | } 85 | 86 | // uploadFilesResp represents response for uploading file 87 | type uploadFilesResp struct { 88 | baseResponse 89 | FileInfo *UploadFilesResp `json:"data"` 90 | } 91 | 92 | // UploadFilesResp represents response for uploading file 93 | type UploadFilesResp struct { 94 | baseModel 95 | FileInfo 96 | } 97 | 98 | // retrieveFilesResp represents response for retrieving file 99 | type retrieveFilesResp struct { 100 | baseResponse 101 | FileInfo *RetrieveFilesResp `json:"data"` 102 | } 103 | 104 | // RetrieveFilesResp represents response for retrieving file 105 | type RetrieveFilesResp struct { 106 | baseModel 107 | FileInfo 108 | } 109 | -------------------------------------------------------------------------------- /files_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestFiles(t *testing.T) { 14 | // Test Upload method 15 | t.Run("Upload file success", func(t *testing.T) { 16 | mockTransport := &mockTransport{ 17 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 18 | // Verify request method and path 19 | assert.Equal(t, http.MethodPost, req.Method) 20 | assert.Equal(t, "/v1/files/upload", req.URL.Path) 21 | 22 | // Return mock response 23 | return mockResponse(http.StatusOK, &uploadFilesResp{ 24 | FileInfo: &UploadFilesResp{ 25 | FileInfo: FileInfo{ 26 | ID: "file1", 27 | Bytes: 1024, 28 | CreatedAt: 1234567890, 29 | FileName: "test.txt", 30 | }, 31 | }, 32 | }) 33 | }, 34 | } 35 | 36 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 37 | files := newFiles(core) 38 | 39 | // Create a test file content 40 | content := []byte("test file content") 41 | fileReader := bytes.NewReader(content) 42 | uploadReq := &UploadFilesReq{ 43 | File: NewUploadFile(fileReader, "test.txt"), 44 | } 45 | 46 | resp, err := files.Upload(context.Background(), uploadReq) 47 | 48 | require.NoError(t, err) 49 | assert.Equal(t, "test_log_id", resp.LogID()) 50 | assert.Equal(t, "file1", resp.ID) 51 | assert.Equal(t, 1024, resp.Bytes) 52 | assert.Equal(t, 1234567890, resp.CreatedAt) 53 | assert.Equal(t, "test.txt", resp.FileName) 54 | }) 55 | 56 | // Test Retrieve method 57 | t.Run("Retrieve file success", func(t *testing.T) { 58 | mockTransport := &mockTransport{ 59 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 60 | // Verify request method and path 61 | assert.Equal(t, http.MethodPost, req.Method) 62 | assert.Equal(t, "/v1/files/retrieve", req.URL.Path) 63 | 64 | // Verify query parameters 65 | assert.Equal(t, "file1", req.URL.Query().Get("file_id")) 66 | 67 | // Return mock response 68 | return mockResponse(http.StatusOK, &retrieveFilesResp{ 69 | FileInfo: &RetrieveFilesResp{ 70 | FileInfo: FileInfo{ 71 | ID: "file1", 72 | Bytes: 1024, 73 | CreatedAt: 1234567890, 74 | FileName: "test.txt", 75 | }, 76 | }, 77 | }) 78 | }, 79 | } 80 | 81 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 82 | files := newFiles(core) 83 | 84 | resp, err := files.Retrieve(context.Background(), &RetrieveFilesReq{ 85 | FileID: "file1", 86 | }) 87 | 88 | require.NoError(t, err) 89 | assert.Equal(t, "test_log_id", resp.LogID()) 90 | assert.Equal(t, "file1", resp.ID) 91 | assert.Equal(t, 1024, resp.Bytes) 92 | assert.Equal(t, 1234567890, resp.CreatedAt) 93 | assert.Equal(t, "test.txt", resp.FileName) 94 | }) 95 | 96 | // Test Upload method with error 97 | t.Run("Upload file with error", func(t *testing.T) { 98 | mockTransport := &mockTransport{ 99 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 100 | // Return error response 101 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 102 | }, 103 | } 104 | 105 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 106 | files := newFiles(core) 107 | 108 | content := []byte("test file content") 109 | fileReader := bytes.NewReader(content) 110 | uploadReq := &UploadFilesReq{ 111 | File: NewUploadFile(fileReader, "test.txt"), 112 | } 113 | resp, err := files.Upload(context.Background(), uploadReq) 114 | 115 | require.Error(t, err) 116 | assert.Nil(t, resp) 117 | }) 118 | 119 | // Test Retrieve method with error 120 | t.Run("Retrieve file with error", func(t *testing.T) { 121 | mockTransport := &mockTransport{ 122 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 123 | // Return error response 124 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 125 | }, 126 | } 127 | 128 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 129 | files := newFiles(core) 130 | 131 | resp, err := files.Retrieve(context.Background(), &RetrieveFilesReq{ 132 | FileID: "invalid_file_id", 133 | }) 134 | 135 | require.Error(t, err) 136 | assert.Nil(t, resp) 137 | }) 138 | 139 | // Test UploadFilesReq 140 | t.Run("Test UploadFilesReq", func(t *testing.T) { 141 | content := []byte("test file content") 142 | fileReader := bytes.NewReader(content) 143 | uploadReq := NewUploadFile(fileReader, "test.txt") 144 | 145 | assert.Equal(t, "test.txt", uploadReq.Name()) 146 | 147 | // Test reading from the request 148 | buffer := make([]byte, len(content)) 149 | n, err := uploadReq.Read(buffer) 150 | require.NoError(t, err) 151 | assert.Equal(t, len(content), n) 152 | assert.Equal(t, content, buffer) 153 | }) 154 | } 155 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/coze-dev/coze-go 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang-jwt/jwt v3.2.2+incompatible 7 | github.com/jarcoal/httpmock v1.4.0 8 | github.com/stretchr/testify v1.10.0 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 4 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 5 | github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= 6 | github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= 7 | github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 11 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 14 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 15 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 16 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | ) 9 | 10 | // Logger ... 11 | type Logger interface { 12 | Log(ctx context.Context, level LogLevel, message string, args ...interface{}) 13 | } 14 | 15 | type LevelLogger interface { 16 | Logger 17 | SetLevel(level LogLevel) 18 | } 19 | 20 | type LogLevel int 21 | 22 | // LogLevelTrace ... 23 | const ( 24 | LogLevelTrace LogLevel = iota + 1 25 | LogLevelDebug 26 | LogLevelInfo 27 | LogLevelWarn 28 | LogLevelError 29 | ) 30 | 31 | // String ... 32 | func (r LogLevel) String() string { 33 | switch r { 34 | case LogLevelTrace: 35 | return "TRACE" 36 | case LogLevelDebug: 37 | return "DEBUG" 38 | case LogLevelInfo: 39 | return "INFO" 40 | case LogLevelWarn: 41 | return "WARN" 42 | case LogLevelError: 43 | return "ERROR" 44 | default: 45 | return "" 46 | } 47 | } 48 | 49 | type stdLogger struct { 50 | log *log.Logger 51 | } 52 | 53 | // newStdLogger ... 54 | func newStdLogger() Logger { 55 | return &stdLogger{ 56 | log: log.New(os.Stderr, "", log.LstdFlags), 57 | } 58 | } 59 | 60 | // Log ... 61 | func (l *stdLogger) Log(ctx context.Context, level LogLevel, message string, args ...interface{}) { 62 | if len(args) == 0 { 63 | _ = l.log.Output(2, "["+level.String()+"] "+message) 64 | } else { 65 | _ = l.log.Output(2, "["+level.String()+"] "+fmt.Sprintf(message, args...)) 66 | } 67 | } 68 | 69 | type levelLogger struct { 70 | Logger 71 | level LogLevel 72 | } 73 | 74 | // NewLevelLogger ... 75 | func NewLevelLogger(logger Logger, level LogLevel) LevelLogger { 76 | return &levelLogger{ 77 | Logger: logger, 78 | level: level, 79 | } 80 | } 81 | 82 | // SetLevel ... 83 | func (l *levelLogger) SetLevel(level LogLevel) { 84 | l.level = level 85 | } 86 | 87 | // Log ... 88 | func (l *levelLogger) Log(ctx context.Context, level LogLevel, message string, args ...interface{}) { 89 | if level >= l.level { 90 | l.Logger.Log(ctx, level, message, args...) 91 | } 92 | } 93 | 94 | func (l *levelLogger) Debugf(ctx context.Context, message string, args ...interface{}) { 95 | l.Log(ctx, LogLevelDebug, message, args...) 96 | } 97 | 98 | func (l *levelLogger) Infof(ctx context.Context, message string, args ...interface{}) { 99 | l.Log(ctx, LogLevelInfo, message, args...) 100 | } 101 | 102 | func (l *levelLogger) Warnf(ctx context.Context, message string, args ...interface{}) { 103 | l.Log(ctx, LogLevelWarn, message, args...) 104 | } 105 | 106 | func (l *levelLogger) Errorf(ctx context.Context, message string, args ...interface{}) { 107 | l.Log(ctx, LogLevelError, message, args...) 108 | } 109 | 110 | var logger = levelLogger{ 111 | Logger: newStdLogger(), 112 | level: LogLevelInfo, 113 | } 114 | 115 | func setLogger(l Logger) { 116 | logger.Logger = l 117 | } 118 | 119 | func setLevel(level LogLevel) { 120 | logger.level = level 121 | } 122 | -------------------------------------------------------------------------------- /pagination.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | type BasePaged[T any] interface { 4 | Err() error 5 | Items() []*T 6 | Current() *T 7 | Next() bool 8 | HasMore() bool 9 | } 10 | 11 | type NumberPaged[T any] interface { 12 | BasePaged[T] 13 | Total() int 14 | } 15 | type LastIDPaged[T any] interface { 16 | BasePaged[T] 17 | GetLastID() string 18 | } 19 | 20 | type pageRequest struct { 21 | PageToken string `json:"page_token,omitempty"` 22 | PageNum int `json:"page_num,omitempty"` 23 | PageSize int `json:"page_size,omitempty"` 24 | } 25 | 26 | type pageResponse[T any] struct { 27 | HasMore bool `json:"has_more"` 28 | Total int `json:"total"` 29 | Data []*T `json:"data"` 30 | LastID string `json:"last_id,omitempty"` 31 | NextID string `json:"next_id,omitempty"` 32 | LogID string `json:"log_id,omitempty"` 33 | } 34 | 35 | type basePager[T any] struct { 36 | pageFetcher PageFetcher[T] 37 | pageSize int 38 | currentPage *pageResponse[T] 39 | currentIndex int 40 | currentPageNum int 41 | cur *T 42 | err error 43 | } 44 | 45 | func (p *basePager[T]) Err() error { 46 | return p.err 47 | } 48 | 49 | func (p *basePager[T]) Items() []*T { 50 | return ptrValue(p.currentPage).Data 51 | } 52 | 53 | func (p *basePager[T]) Current() *T { 54 | return p.cur 55 | } 56 | 57 | func (p *basePager[T]) Total() int { 58 | return ptrValue(p.currentPage).Total 59 | } 60 | 61 | func (p *basePager[T]) HasMore() bool { 62 | return ptrValue(p.currentPage).HasMore 63 | } 64 | 65 | // PageFetcher interface 66 | type PageFetcher[T any] func(request *pageRequest) (*pageResponse[T], error) 67 | 68 | // NumberPaged implementation 69 | type implNumberPaged[T any] struct { 70 | basePager[T] 71 | } 72 | 73 | func NewNumberPaged[T any](fetcher PageFetcher[T], pageSize, pageNum int) (NumberPaged[T], error) { 74 | if pageNum <= 0 { 75 | pageNum = 1 76 | } 77 | paginator := &implNumberPaged[T]{basePager: basePager[T]{pageFetcher: fetcher, pageSize: pageSize, currentPageNum: pageNum}} 78 | err := paginator.fetchNextPage() 79 | if err != nil { 80 | return nil, err 81 | } 82 | return paginator, nil 83 | } 84 | 85 | func (p *implNumberPaged[T]) fetchNextPage() error { 86 | request := &pageRequest{PageNum: p.currentPageNum, PageSize: p.pageSize} 87 | var err error 88 | p.currentPage, err = p.pageFetcher(request) 89 | if err != nil { 90 | return err 91 | } 92 | p.currentIndex = 0 93 | p.currentPageNum++ 94 | return nil 95 | } 96 | 97 | func (p *implNumberPaged[T]) Next() bool { 98 | if p.currentIndex < len(ptrValue(p.currentPage).Data) { 99 | p.cur = p.currentPage.Data[p.currentIndex] 100 | p.currentIndex++ 101 | return true 102 | } 103 | if p.currentPage.HasMore { 104 | err := p.fetchNextPage() 105 | if err != nil { 106 | p.err = err 107 | return false 108 | } 109 | if len(p.currentPage.Data) == 0 { 110 | return false 111 | } 112 | p.cur = p.currentPage.Data[p.currentIndex] 113 | p.currentIndex++ 114 | return true 115 | } 116 | return false 117 | } 118 | 119 | // TokenPaged implementation 120 | type implLastIDPaged[T any] struct { 121 | basePager[T] 122 | pageToken *string 123 | } 124 | 125 | func NewLastIDPaged[T any](fetcher PageFetcher[T], pageSize int, nextID *string) (LastIDPaged[T], error) { 126 | paginator := &implLastIDPaged[T]{basePager: basePager[T]{pageFetcher: fetcher, pageSize: pageSize}, pageToken: nextID} 127 | err := paginator.fetchNextPage() 128 | if err != nil { 129 | return nil, err 130 | } 131 | return paginator, nil 132 | } 133 | 134 | func (p *implLastIDPaged[T]) fetchNextPage() error { 135 | request := &pageRequest{PageToken: ptrValue(p.pageToken), PageSize: p.pageSize} 136 | var err error 137 | p.currentPage, err = p.pageFetcher(request) 138 | if err != nil { 139 | return err 140 | } 141 | p.currentIndex = 0 142 | p.pageToken = &p.currentPage.NextID 143 | return nil 144 | } 145 | 146 | func (p *implLastIDPaged[T]) Next() bool { 147 | if p.currentIndex < len(ptrValue(p.currentPage).Data) { 148 | p.cur = p.currentPage.Data[p.currentIndex] 149 | p.currentIndex++ 150 | return true 151 | } 152 | if p.currentPage.HasMore { 153 | err := p.fetchNextPage() 154 | if err != nil { 155 | p.err = err 156 | return false 157 | } 158 | if len(p.currentPage.Data) == 0 { 159 | return false 160 | } 161 | p.cur = p.currentPage.Data[p.currentIndex] 162 | p.currentIndex++ 163 | return true 164 | } 165 | return false 166 | } 167 | 168 | func (p *implLastIDPaged[T]) GetLastID() string { 169 | return p.currentPage.LastID 170 | } 171 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "mime/multipart" 11 | "net/http" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | // HTTPClient an interface for making HTTP requests 17 | type HTTPClient interface { 18 | Do(*http.Request) (*http.Response, error) 19 | } 20 | 21 | type core struct { 22 | *clientOption 23 | } 24 | 25 | func newCore(opt *clientOption) *core { 26 | if opt.client == nil { 27 | opt.client = &http.Client{ 28 | Timeout: time.Second * 5, 29 | } 30 | } 31 | return &core{ 32 | clientOption: opt, 33 | } 34 | } 35 | 36 | // RequestOption 请求选项函数类型 37 | type RequestOption func(*http.Request) error 38 | 39 | // withHTTPHeader add http header 40 | func withHTTPHeader(key, value string) RequestOption { 41 | return func(req *http.Request) error { 42 | req.Header.Set(key, value) 43 | return nil 44 | } 45 | } 46 | 47 | // withHTTPQuery add http query 48 | func withHTTPQuery(key, value string) RequestOption { 49 | return func(req *http.Request) error { 50 | q := req.URL.Query() 51 | q.Add(key, value) 52 | req.URL.RawQuery = q.Encode() 53 | return nil 54 | } 55 | } 56 | 57 | // Request send http request 58 | func (c *core) Request(ctx context.Context, method, path string, body any, instance any, opts ...RequestOption) error { 59 | resp, err := c.RawRequest(ctx, method, path, body, opts...) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | return packInstance(ctx, instance, resp) 65 | } 66 | 67 | // UploadFile 上传文件 68 | func (c *core) UploadFile(ctx context.Context, path string, reader io.Reader, fileName string, fields map[string]string, instance any, opts ...RequestOption) error { 69 | body := &bytes.Buffer{} 70 | writer := multipart.NewWriter(body) 71 | 72 | part, err := writer.CreateFormFile("file", fileName) 73 | if err != nil { 74 | return fmt.Errorf("create form file: %w", err) 75 | } 76 | 77 | if _, err = io.Copy(part, reader); err != nil { 78 | return fmt.Errorf("copy file content: %w", err) 79 | } 80 | 81 | // 添加其他字段 82 | for key, value := range fields { 83 | if err := writer.WriteField(key, value); err != nil { 84 | return fmt.Errorf("write field %s: %w", key, err) 85 | } 86 | } 87 | 88 | if err := writer.Close(); err != nil { 89 | return fmt.Errorf("close multipart writer: %w", err) 90 | } 91 | 92 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s%s", c.baseURL, path), body) 93 | if err != nil { 94 | return fmt.Errorf("create request: %w", err) 95 | } 96 | 97 | req.Header.Set("Content-Type", writer.FormDataContentType()) 98 | 99 | // 应用请求选项 100 | for _, opt := range opts { 101 | if err := opt(req); err != nil { 102 | return fmt.Errorf("apply option: %w", err) 103 | } 104 | } 105 | 106 | if err := c.setCommonHeaders(req); err != nil { 107 | return err 108 | } 109 | 110 | resp, err := c.client.Do(req) 111 | if err != nil { 112 | return fmt.Errorf("do request: %w", err) 113 | } 114 | 115 | return packInstance(ctx, instance, resp) 116 | } 117 | 118 | func (c *core) RawRequest(ctx context.Context, method, path string, body any, opts ...RequestOption) (*http.Response, error) { 119 | urlInfo := fmt.Sprintf("%s%s", c.baseURL, path) 120 | 121 | var bodyReader io.Reader 122 | if body != nil { 123 | data, err := json.Marshal(body) 124 | if err != nil { 125 | return nil, fmt.Errorf("marshal request body: %w", err) 126 | } 127 | bodyReader = bytes.NewReader(data) 128 | } 129 | 130 | req, err := http.NewRequestWithContext(ctx, method, urlInfo, bodyReader) 131 | if err != nil { 132 | return nil, fmt.Errorf("create request: %w", err) 133 | } 134 | 135 | // 设置默认请求头 136 | req.Header.Set("Content-Type", "application/json") 137 | 138 | // 应用请求选项 139 | for _, opt := range opts { 140 | if err := opt(req); err != nil { 141 | return nil, fmt.Errorf("apply option: %w", err) 142 | } 143 | } 144 | 145 | if err := c.setCommonHeaders(req); err != nil { 146 | return nil, err 147 | } 148 | 149 | resp, err := c.client.Do(req) 150 | if err != nil { 151 | return nil, err 152 | } 153 | 154 | if err = checkHttpResp(ctx, resp); err != nil { 155 | return nil, err 156 | } 157 | return resp, nil 158 | } 159 | 160 | func (c *core) StreamRequest(ctx context.Context, method, path string, body any, opts ...RequestOption) (*http.Response, error) { 161 | resp, err := c.RawRequest(ctx, method, path, body, opts...) 162 | if err != nil { 163 | return nil, err 164 | } 165 | contentType := resp.Header.Get("Content-Type") 166 | if contentType != "" && strings.Contains(contentType, "application/json") { 167 | return nil, packInstance(ctx, &baseResponse{}, resp) 168 | } 169 | return resp, nil 170 | } 171 | 172 | func packInstance(ctx context.Context, instance any, resp *http.Response) error { 173 | err := checkHttpResp(ctx, resp) 174 | if err != nil { 175 | return err 176 | } 177 | bodyBytes, err := io.ReadAll(resp.Body) 178 | if err != nil { 179 | return fmt.Errorf("read response body: %w", err) 180 | } 181 | httpResponse := newHTTPResponse(resp) 182 | err = json.Unmarshal(bodyBytes, instance) 183 | if err != nil { 184 | logger.Errorf(ctx, fmt.Sprintf("unmarshal response body: %s", string(bodyBytes))) 185 | return err 186 | } 187 | if baseResp, ok := instance.(baseRespInterface); ok { 188 | return isResponseSuccess(ctx, baseResp, bodyBytes, httpResponse) 189 | } 190 | return nil 191 | } 192 | 193 | func isResponseSuccess(ctx context.Context, baseResp baseRespInterface, bodyBytes []byte, httpResponse *httpResponse) error { 194 | baseResp.SetHTTPResponse(httpResponse) 195 | if baseResp.GetCode() != 0 { 196 | logger.Warnf(ctx, "request failed, body=%s, log_id=%s", string(bodyBytes), httpResponse.LogID()) 197 | return NewError(baseResp.GetCode(), baseResp.GetMsg(), httpResponse.LogID()) 198 | } 199 | return nil 200 | } 201 | 202 | func checkHttpResp(ctx context.Context, resp *http.Response) error { 203 | logID := resp.Header.Get(httpLogIDKey) 204 | // 鉴权的情况,需要解析 205 | if resp.StatusCode != http.StatusOK { 206 | bodyBytes, err := io.ReadAll(resp.Body) 207 | if err != nil { 208 | return fmt.Errorf("coze read response body failed: %w, log_id: %s", err, logID) 209 | } 210 | errorInfo := authErrorFormat{} 211 | err = json.Unmarshal(bodyBytes, &errorInfo) 212 | if err != nil { 213 | logger.Errorf(ctx, fmt.Sprintf("unmarshal response body: %s", string(bodyBytes))) 214 | return errors.New(string(bodyBytes) + " log_id: " + logID) 215 | } 216 | return NewAuthError(&errorInfo, resp.StatusCode, logID) 217 | } 218 | return nil 219 | } 220 | -------------------------------------------------------------------------------- /stream_reader.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "io" 7 | "net/http" 8 | "strings" 9 | ) 10 | 11 | type streamable interface { 12 | ChatEvent | WorkflowEvent 13 | } 14 | 15 | type Stream[T streamable] interface { 16 | Responser 17 | Close() error 18 | Recv() (*T, error) 19 | } 20 | type eventProcessor[T streamable] func(line []byte, reader *bufio.Reader) (*T, bool, error) 21 | 22 | type streamReader[T streamable] struct { 23 | isFinished bool 24 | ctx context.Context 25 | 26 | reader *bufio.Reader 27 | response *http.Response 28 | processor eventProcessor[T] 29 | httpResponse *httpResponse 30 | } 31 | 32 | func (s *streamReader[T]) Recv() (response *T, err error) { 33 | return s.processLines() 34 | } 35 | 36 | func (s *streamReader[T]) processLines() (*T, error) { 37 | err := s.checkRespErr() 38 | if err != nil { 39 | return nil, err 40 | } 41 | for { 42 | line, _, readErr := s.reader.ReadLine() 43 | if readErr != nil { 44 | return nil, readErr 45 | } 46 | 47 | if line == nil { 48 | s.isFinished = true 49 | break 50 | } 51 | if len(line) == 0 { 52 | continue 53 | } 54 | event, isDone, err := s.processor(line, s.reader) 55 | if err != nil { 56 | return nil, err 57 | } 58 | s.isFinished = isDone 59 | if event == nil { 60 | continue 61 | } 62 | return event, nil 63 | } 64 | return nil, io.EOF 65 | } 66 | 67 | func (s *streamReader[T]) checkRespErr() error { 68 | contentType := s.response.Header.Get("Content-Type") 69 | if contentType != "" && strings.Contains(contentType, "application/json") { 70 | respStr, err := io.ReadAll(s.response.Body) 71 | if err != nil { 72 | logger.Warnf(s.ctx, "Error reading response body: ", err) 73 | return err 74 | } 75 | return isResponseSuccess(s.ctx, &baseResponse{}, respStr, s.httpResponse) 76 | } 77 | return nil 78 | } 79 | 80 | func (s *streamReader[T]) Close() error { 81 | return s.response.Body.Close() 82 | } 83 | 84 | func (s *streamReader[T]) Response() HTTPResponse { 85 | return s.httpResponse 86 | } 87 | -------------------------------------------------------------------------------- /stream_reader_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "io" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func mockHTTPResponse() *httpResponse { 17 | header := http.Header{} 18 | header.Set(httpLogIDKey, "test_log_id") 19 | return &httpResponse{ 20 | Header: header, 21 | } 22 | } 23 | 24 | // Mock event processor for testing 25 | func mockEventProcessor(line []byte, reader *bufio.Reader) (*WorkflowEvent, bool, error) { 26 | if len(line) == 0 { 27 | return nil, false, nil 28 | } 29 | 30 | // Parse event data 31 | event := &WorkflowEvent{ 32 | ID: 0, 33 | Event: WorkflowEventTypeMessage, 34 | Message: &WorkflowEventMessage{ 35 | Content: string(line), 36 | }, 37 | } 38 | 39 | // Check if this is the last event 40 | isDone := string(line) == "done" 41 | if isDone { 42 | event.Event = WorkflowEventTypeDone 43 | } 44 | return event, isDone, nil 45 | } 46 | 47 | func TestStreamReader(t *testing.T) { 48 | ctx := context.Background() 49 | t.Run("successful event processing", func(t *testing.T) { 50 | // Create mock response with multiple events 51 | events := []string{ 52 | "first", 53 | "second", 54 | "done", 55 | } 56 | resp := createMockResponse(events) 57 | 58 | // Create stream reader 59 | reader := &streamReader[WorkflowEvent]{ 60 | ctx: ctx, 61 | reader: bufio.NewReader(resp.Body), 62 | response: resp, 63 | processor: mockEventProcessor, 64 | httpResponse: mockHTTPResponse(), 65 | } 66 | defer reader.Close() 67 | 68 | // Read first event 69 | event, err := reader.Recv() 70 | require.NoError(t, err) 71 | assert.Equal(t, WorkflowEventTypeMessage, event.Event) 72 | assert.Equal(t, "first", event.Message.Content) 73 | assert.False(t, reader.isFinished) 74 | 75 | // Read second event 76 | event, err = reader.Recv() 77 | require.NoError(t, err) 78 | assert.Equal(t, WorkflowEventTypeMessage, event.Event) 79 | assert.Equal(t, "second", event.Message.Content) 80 | assert.False(t, reader.isFinished) 81 | 82 | // Read final event 83 | event, err = reader.Recv() 84 | require.NoError(t, err) 85 | assert.Equal(t, WorkflowEventTypeDone, event.Event) 86 | assert.True(t, reader.isFinished) 87 | 88 | // Try reading after done 89 | event, err = reader.Recv() 90 | assert.Equal(t, io.EOF, err) 91 | assert.Nil(t, event) 92 | }) 93 | 94 | t.Run("empty lines are skipped", func(t *testing.T) { 95 | events := []string{ 96 | "", 97 | "test", 98 | "", 99 | "done", 100 | } 101 | resp := createMockResponse(events) 102 | 103 | reader := &streamReader[WorkflowEvent]{ 104 | ctx: ctx, 105 | reader: bufio.NewReader(resp.Body), 106 | response: resp, 107 | processor: mockEventProcessor, 108 | httpResponse: mockHTTPResponse(), 109 | } 110 | defer reader.Close() 111 | 112 | // First non-empty event 113 | event, err := reader.Recv() 114 | require.NoError(t, err) 115 | assert.Equal(t, WorkflowEventTypeMessage, event.Event) 116 | assert.Equal(t, "test", event.Message.Content) 117 | 118 | // Second non-empty event 119 | event, err = reader.Recv() 120 | require.NoError(t, err) 121 | assert.Equal(t, WorkflowEventTypeDone, event.Event) 122 | }) 123 | 124 | t.Run("error response handling", func(t *testing.T) { 125 | // Create mock error response 126 | errorResp := &http.Response{ 127 | StatusCode: http.StatusBadRequest, 128 | Header: http.Header{ 129 | "Content-Type": []string{"application/json"}, 130 | }, 131 | Body: io.NopCloser(strings.NewReader(`{ 132 | "log_id": "error_log_id", 133 | "error": { 134 | "code": 400, 135 | "message": "Bad Request" 136 | } 137 | }`)), 138 | } 139 | 140 | reader := &streamReader[WorkflowEvent]{ 141 | ctx: ctx, 142 | reader: bufio.NewReader(errorResp.Body), 143 | response: errorResp, 144 | processor: mockEventProcessor, 145 | httpResponse: mockHTTPResponse(), 146 | } 147 | defer reader.Close() 148 | 149 | // Attempt to read should return error 150 | event, err := reader.Recv() 151 | assert.Error(t, err) 152 | assert.Nil(t, event) 153 | }) 154 | 155 | t.Run("LogID method", func(t *testing.T) { 156 | reader := &streamReader[WorkflowEvent]{ 157 | ctx: ctx, 158 | httpResponse: mockHTTPResponse(), 159 | } 160 | assert.Equal(t, "test_log_id", reader.httpResponse.LogID()) 161 | }) 162 | } 163 | 164 | // Helper function to create mock response with events 165 | func createMockResponse(events []string) *http.Response { 166 | // Join events with newlines 167 | body := strings.Join(events, "\n") 168 | 169 | return &http.Response{ 170 | StatusCode: http.StatusOK, 171 | Body: io.NopCloser(bytes.NewBufferString(body)), 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /templates.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | // templates provides access to template-related operations 10 | type templates struct { 11 | core *core 12 | } 13 | 14 | // newTemplates creates a new Templates client 15 | func newTemplates(core *core) *templates { 16 | return &templates{core: core} 17 | } 18 | 19 | // Duplicate creates a copy of an existing template 20 | func (c *templates) Duplicate(ctx context.Context, templateID string, req *DuplicateTemplateReq) (*TemplateDuplicateResp, error) { 21 | url := fmt.Sprintf("/v1/templates/%s/duplicate", templateID) 22 | 23 | var resp templateDuplicateResp 24 | err := c.core.Request(ctx, http.MethodPost, url, req, &resp) 25 | if err != nil { 26 | return nil, err 27 | } 28 | result := resp.Data 29 | result.setHTTPResponse(resp.HTTPResponse) 30 | return result, nil 31 | } 32 | 33 | // TemplateEntityType represents the type of template entity 34 | type TemplateEntityType string 35 | 36 | const ( 37 | // TemplateEntityTypeAgent represents an agent template 38 | TemplateEntityTypeAgent TemplateEntityType = "agent" 39 | ) 40 | 41 | // TemplateDuplicateResp represents the response from duplicating a template 42 | type TemplateDuplicateResp struct { 43 | baseModel 44 | EntityID string `json:"entity_id"` 45 | EntityType TemplateEntityType `json:"entity_type"` 46 | } 47 | 48 | // templateDuplicateResp represents response for creating document 49 | type templateDuplicateResp struct { 50 | baseResponse 51 | Data *TemplateDuplicateResp `json:"data"` 52 | } 53 | 54 | // DuplicateTemplateReq represents the request to duplicate a template 55 | type DuplicateTemplateReq struct { 56 | WorkspaceID string `json:"workspace_id"` 57 | Name *string `json:"name,omitempty"` 58 | } 59 | -------------------------------------------------------------------------------- /user_agent.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "os" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | const version = "0.1.0" 12 | 13 | var ( 14 | userAgentSDK = "cozego" 15 | userAgentLang = "go" 16 | userAgentLangVersion = strings.TrimPrefix(runtime.Version(), "go") 17 | userAgentOsName = runtime.GOOS 18 | userAgentOsVersion = os.Getenv("OSVERSION") 19 | userAgent = userAgentSDK + "/" + version + " " + userAgentLang + "/" + userAgentLangVersion + " " + userAgentOsName + "/" + userAgentOsVersion 20 | clientUserAgent string 21 | ) 22 | 23 | func (c *core) setCommonHeaders(req *http.Request) error { 24 | // agent 25 | req.Header.Set("User-Agent", userAgent) 26 | req.Header.Set("X-Coze-Client-User-Agent", clientUserAgent) 27 | 28 | // logid 29 | if c.enableLogID { 30 | v := req.Context().Value(ctxLogIDKey) 31 | if logid, ok := v.(string); ok && logid != "" { 32 | req.Header.Set(httpLogIDKey, logid) 33 | } 34 | } 35 | 36 | // auth 37 | if c.auth != nil { 38 | // auth 相关请求, c.auth 为 nil 39 | accessToken, err := c.auth.Token(req.Context()) 40 | if err != nil { 41 | logger.Errorf(req.Context(), "failed to get access_token: %s", err) 42 | return err 43 | } 44 | req.Header.Set("Authorization", "Bearer "+accessToken) 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func init() { 51 | clientUserAgent = getCozeClientUserAgent() 52 | } 53 | 54 | type userAgentInfo struct { 55 | Version string `json:"version"` 56 | Lang string `json:"lang"` 57 | LangVersion string `json:"lang_version"` 58 | OsName string `json:"os_name"` 59 | OsVersion string `json:"os_version"` 60 | } 61 | 62 | func getCozeClientUserAgent() string { 63 | data, _ := json.Marshal(userAgentInfo{ 64 | Version: version, 65 | Lang: userAgentSDK, 66 | LangVersion: userAgentLangVersion, 67 | OsName: userAgentOsName, 68 | OsVersion: userAgentOsVersion, 69 | }) 70 | return string(data) 71 | } 72 | -------------------------------------------------------------------------------- /users.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | // User represents a Coze user 9 | type User struct { 10 | baseModel 11 | UserID string `json:"user_id"` 12 | UserName string `json:"user_name"` 13 | NickName string `json:"nick_name"` 14 | AvatarURL string `json:"avatar_url"` 15 | } 16 | 17 | type meResp struct { 18 | baseResponse 19 | User *User `json:"data"` 20 | } 21 | 22 | type users struct { 23 | client *core 24 | } 25 | 26 | func newUsers(core *core) *users { 27 | return &users{ 28 | client: core, 29 | } 30 | } 31 | 32 | // Me retrieves the current user's information 33 | func (r *users) Me(ctx context.Context) (*User, error) { 34 | method := http.MethodGet 35 | uri := "/v1/users/me" 36 | resp := &meResp{} 37 | if err := r.client.Request(ctx, method, uri, nil, resp); err != nil { 38 | return nil, err 39 | } 40 | 41 | resp.User.setHTTPResponse(resp.HTTPResponse) 42 | return resp.User, nil 43 | } 44 | -------------------------------------------------------------------------------- /users_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestUsersClient_Me(t *testing.T) { 13 | mockTransport := &mockTransport{ 14 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 15 | expectedUser := &meResp{ 16 | User: &User{ 17 | UserID: "test_user_id", 18 | UserName: "test_user", 19 | NickName: "Test User", 20 | AvatarURL: "https://example.com/avatar.jpg", 21 | }, 22 | } 23 | return mockResponse(http.StatusOK, expectedUser) 24 | }, 25 | } 26 | 27 | client := NewCozeAPI(NewTokenAuth("test_token"), 28 | WithBaseURL(ComBaseURL), 29 | WithHttpClient(&http.Client{Transport: mockTransport}), 30 | ) 31 | 32 | user, err := client.Users.Me(context.Background()) 33 | require.NoError(t, err) 34 | assert.Equal(t, "test_user_id", user.UserID) 35 | assert.Equal(t, "test_user", user.UserName) 36 | assert.Equal(t, "Test User", user.NickName) 37 | assert.Equal(t, "https://example.com/avatar.jpg", user.AvatarURL) 38 | } 39 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/json" 7 | ) 8 | 9 | func ptrValue[T any](s *T) T { 10 | if s != nil { 11 | return *s 12 | } 13 | var empty T 14 | return empty 15 | } 16 | 17 | func ptr[T any](s T) *T { 18 | return &s 19 | } 20 | 21 | func generateRandomString(length int) (string, error) { 22 | bytes := make([]byte, length/2) 23 | if _, err := rand.Read(bytes); err != nil { 24 | return "", err 25 | } 26 | return bytesToHex(bytes), nil 27 | } 28 | 29 | func bytesToHex(bytes []byte) string { 30 | hex := make([]byte, len(bytes)*2) 31 | for i, b := range bytes { 32 | hex[i*2] = hexChar(b >> 4) 33 | hex[i*2+1] = hexChar(b & 0xF) 34 | } 35 | return string(hex) 36 | } 37 | 38 | func hexChar(b byte) byte { 39 | if b < 10 { 40 | return '0' + b 41 | } 42 | return 'a' + (b - 10) 43 | } 44 | 45 | func mustToJson(obj any) string { 46 | jsonArray, err := json.Marshal(obj) 47 | if err != nil { 48 | return "{}" 49 | } 50 | return string(jsonArray) 51 | } 52 | 53 | type contextKey string 54 | 55 | const ( 56 | authContextKey = contextKey("auth_context") 57 | authContextValue = "1" 58 | ) 59 | 60 | func genAuthContext(ctx context.Context) context.Context { 61 | return context.WithValue(ctx, authContextKey, authContextValue) 62 | } 63 | 64 | func isAuthContext(ctx context.Context) bool { 65 | v := ctx.Value(authContextKey) 66 | if v == nil { 67 | return false 68 | } 69 | strV, ok := v.(string) 70 | if !ok { 71 | return false 72 | } 73 | return strV == authContextValue 74 | } 75 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type mockHTTP struct { 11 | Response *http.Response 12 | Error error 13 | } 14 | 15 | func (m *mockHTTP) Do(*http.Request) (*http.Response, error) { 16 | return m.Response, m.Error 17 | } 18 | 19 | func Test_Ptr(t *testing.T) { 20 | as := assert.New(t) 21 | as.NotNil(ptr(1)) 22 | as.NotNil(ptr("2")) 23 | as.NotNil(ptr(int64(3))) 24 | as.NotNil(ptr(int32(4))) 25 | as.NotNil(ptr(int16(5))) 26 | as.NotNil(ptr(int8(6))) 27 | as.NotNil(ptr(uint(7))) 28 | as.NotNil(ptr(uint64(8))) 29 | as.NotNil(ptr(uint32(9))) 30 | as.NotNil(ptr(uint16(10))) 31 | as.NotNil(ptr(uint8(11))) 32 | as.NotNil(ptr(float32(12.1))) 33 | as.NotNil(ptr(float64(13.1))) 34 | as.NotNil(ptr(true)) 35 | as.NotNil(ptr(false)) 36 | 37 | as.Equal(1, ptrValue(ptr(1))) 38 | as.Equal("2", ptrValue(ptr("2"))) 39 | as.Equal(int64(3), ptrValue(ptr(int64(3)))) 40 | as.Equal(int32(4), ptrValue(ptr(int32(4)))) 41 | as.Equal(int16(5), ptrValue(ptr(int16(5)))) 42 | as.Equal(int8(6), ptrValue(ptr(int8(6)))) 43 | as.Equal(uint(7), ptrValue(ptr(uint(7)))) 44 | as.Equal(uint64(8), ptrValue(ptr(uint64(8)))) 45 | as.Equal(uint32(9), ptrValue(ptr(uint32(9)))) 46 | as.Equal(uint16(10), ptrValue(ptr(uint16(10)))) 47 | as.Equal(uint8(11), ptrValue(ptr(uint8(11)))) 48 | as.Equal(float32(12.1), ptrValue(ptr(float32(12.1)))) 49 | as.Equal(float64(13.1), ptrValue(ptr(float64(13.1)))) 50 | as.Equal(true, ptrValue(ptr(true))) 51 | as.Equal(false, ptrValue(ptr(false))) 52 | var s *string 53 | as.Equal("", ptrValue(s)) 54 | } 55 | 56 | func Test_GenerateRandomString(t *testing.T) { 57 | str1, err := generateRandomString(10) 58 | assert.Nil(t, err) 59 | str2, err := generateRandomString(10) 60 | assert.Nil(t, err) 61 | assert.NotEqual(t, str1, str2) 62 | } 63 | 64 | func Test_MustToJson(t *testing.T) { 65 | jsonStr := mustToJson(map[string]string{"test": "test"}) 66 | assert.Equal(t, jsonStr, `{"test":"test"}`) 67 | } 68 | -------------------------------------------------------------------------------- /variables.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | // variables manages variable-related API interactions. 11 | // 12 | // Update API docs: https://www.coze.cn/open/docs/developer_guides/update_variable 13 | // Retrieve API docs: https://www.coze.cn/open/docs/developer_guides/read_variable 14 | type variables struct { 15 | core *core 16 | } 17 | 18 | func newVariables(core *core) *variables { 19 | return &variables{core: core} 20 | } 21 | 22 | // VariableValue represents a single variable with its keyword and value. 23 | type VariableValue struct { 24 | Keyword string `json:"keyword"` 25 | Value string `json:"value"` 26 | UpdateTime int64 `json:"update_time,omitempty"` 27 | CreateTime int64 `json:"create_time,omitempty"` 28 | } 29 | 30 | // RetrieveVariablesReq represents the parameters for retrieving variables. 31 | type RetrieveVariablesReq struct { 32 | ConnectorUID string `json:"connector_uid"` // Required: Unique identifier for the connector 33 | Keywords []string `json:"keywords"` // Required: List of variable keywords to retrieve 34 | AppID *string `json:"app_id,omitempty"` // Optional: Application ID filter 35 | BotID *string `json:"bot_id,omitempty"` // Optional: Bot ID filter 36 | ConnectorID *string `json:"connector_id,omitempty"` // Optional: Connector ID filter 37 | } 38 | 39 | type retrieveVariablesResp struct { 40 | baseResponse 41 | Data *RetrieveVariablesResp `json:"data"` 42 | } 43 | 44 | type RetrieveVariablesResp struct { 45 | baseModel 46 | Items []*VariableValue `json:"items"` 47 | } 48 | 49 | // Retrieve retrieves variables matching the specified criteria. 50 | func (s *variables) Retrieve(ctx context.Context, req *RetrieveVariablesReq) (*RetrieveVariablesResp, error) { 51 | if req == nil { 52 | return nil, errors.New("invalid req") 53 | } 54 | method := http.MethodGet 55 | path := "/v1/variables" 56 | baseOpts := []RequestOption{ 57 | withHTTPQuery("connector_uid", req.ConnectorUID), 58 | withHTTPQuery("keywords", strings.Join(req.Keywords, ",")), 59 | } 60 | if req.AppID != nil { 61 | baseOpts = append(baseOpts, withHTTPQuery("app_id", *req.AppID)) 62 | } 63 | if req.BotID != nil { 64 | baseOpts = append(baseOpts, withHTTPQuery("bot_id", *req.BotID)) 65 | } 66 | if req.ConnectorID != nil { 67 | baseOpts = append(baseOpts, withHTTPQuery("connector_id", *req.ConnectorID)) 68 | } 69 | 70 | resp := &retrieveVariablesResp{} 71 | err := s.core.Request(ctx, method, path, nil, resp, baseOpts...) 72 | if err != nil { 73 | return nil, err 74 | } 75 | result := &RetrieveVariablesResp{ 76 | baseModel: baseModel{ 77 | httpResponse: resp.HTTPResponse, 78 | }, 79 | Items: resp.Data.Items, 80 | } 81 | return result, nil 82 | } 83 | 84 | // UpdateVariablesReq represents the request body for updating variables. 85 | type UpdateVariablesReq struct { 86 | ConnectorUID string `json:"connector_uid"` // Required: Unique identifier for the connector 87 | Data []VariableValue `json:"data"` // Required: List of variable values to update 88 | AppID *string `json:"app_id,omitempty"` // Optional: Application ID filter 89 | BotID *string `json:"bot_id,omitempty"` // Optional: Bot ID filter 90 | ConnectorID *string `json:"connector_id,omitempty"` // Optional: Connector ID filter 91 | } 92 | 93 | type updateVariablesResp struct { 94 | baseResponse 95 | Data *UpdateVariablesResp `json:"data"` 96 | } 97 | 98 | type UpdateVariablesResp struct { 99 | baseModel 100 | } 101 | 102 | // Update updates variables with the provided data. 103 | func (s *variables) Update(ctx context.Context, req *UpdateVariablesReq) (*UpdateVariablesResp, error) { 104 | if req == nil { 105 | return nil, errors.New("invalid req") 106 | } 107 | method := http.MethodPut 108 | uri := "/v1/variables" 109 | resp := &updateVariablesResp{} 110 | err := s.core.Request(ctx, method, uri, req, resp) 111 | if err != nil { 112 | return nil, err 113 | } 114 | if resp.Data == nil { 115 | resp.Data = new(UpdateVariablesResp) 116 | } 117 | resp.Data.setHTTPResponse(resp.HTTPResponse) 118 | return resp.Data, nil 119 | } 120 | -------------------------------------------------------------------------------- /variables_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/jarcoal/httpmock" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestVariablesService_Retrieve(t *testing.T) { 13 | mockClient := &http.Client{} 14 | httpmock.ActivateNonDefault(mockClient) 15 | defer httpmock.DeactivateAndReset() 16 | 17 | cli := NewCozeAPI(NewTokenAuth("test-token"), WithHttpClient(mockClient)) 18 | 19 | ctx := context.Background() 20 | 21 | t.Run("Test with valid req", func(t *testing.T) { 22 | mockResp := `{ 23 | "code": 0, 24 | "msg": "Success", 25 | "data": { 26 | "items": [ 27 | { 28 | "value": "val1", 29 | "create_time": 0, 30 | "update_time": 0, 31 | "keyword": "key1" 32 | }, 33 | { 34 | "update_time": 1744637812, 35 | "keyword": "key2", 36 | "value": "val2", 37 | "create_time": 1744637812 38 | } 39 | ] 40 | }, 41 | "detail": { 42 | "logid": "20241210152726467C48D89D6DB2****" 43 | } 44 | } 45 | ` 46 | 47 | httpmock.RegisterResponder("GET", "/v1/variables", 48 | func(req *http.Request) (*http.Response, error) { 49 | resp := httpmock.NewStringResponse(200, mockResp) 50 | return resp, nil 51 | }, 52 | ) 53 | 54 | req := &RetrieveVariablesReq{ 55 | ConnectorUID: "test-connector-uid", 56 | Keywords: []string{"key1", "key2"}, 57 | AppID: ptr("test-app-id"), 58 | } 59 | res, err := cli.Variables.Retrieve(ctx, req) 60 | assert.NoError(t, err) 61 | assert.NotNil(t, res) 62 | 63 | assert.Len(t, res.Items, 2) 64 | assert.Equal(t, "key1", res.Items[0].Keyword) 65 | assert.Equal(t, "val1", res.Items[0].Value) 66 | assert.Equal(t, int64(0), res.Items[0].CreateTime) 67 | assert.Equal(t, int64(0), res.Items[0].UpdateTime) 68 | 69 | assert.Equal(t, "key2", res.Items[1].Keyword) 70 | assert.Equal(t, "val2", res.Items[1].Value) 71 | assert.Equal(t, int64(1744637812), res.Items[1].CreateTime) 72 | assert.Equal(t, int64(1744637812), res.Items[1].UpdateTime) 73 | }) 74 | 75 | t.Run("Test with nil req", func(t *testing.T) { 76 | _, err := cli.Variables.Retrieve(ctx, nil) 77 | assert.Error(t, err) 78 | assert.Equal(t, "invalid req", err.Error()) 79 | }) 80 | } 81 | 82 | func TestVariablesService_Update(t *testing.T) { 83 | mockClient := &http.Client{} 84 | httpmock.ActivateNonDefault(mockClient) 85 | defer httpmock.DeactivateAndReset() 86 | 87 | cli := NewCozeAPI(NewTokenAuth("test-token"), WithHttpClient(mockClient)) 88 | 89 | ctx := context.Background() 90 | 91 | t.Run("Test with valid req", func(t *testing.T) { 92 | mockResp := `{ 93 | "code": 0, 94 | "msg": "", 95 | "detail": { 96 | "logid": "20250416125552EE59A23A87AD80CA7051" 97 | } 98 | } 99 | ` 100 | httpmock.RegisterResponder("PUT", "/v1/variables", 101 | func(req *http.Request) (*http.Response, error) { 102 | resp := httpmock.NewStringResponse(200, mockResp) 103 | return resp, nil 104 | }, 105 | ) 106 | 107 | req := &UpdateVariablesReq{ 108 | ConnectorUID: "test-connector-uid", 109 | Data: []VariableValue{ 110 | {Keyword: "key1", Value: "new_value1"}, 111 | {Keyword: "key2", Value: "new_value2"}, 112 | }, 113 | AppID: ptr("test-app-id"), 114 | } 115 | respData, err := cli.Variables.Update(ctx, req) 116 | assert.NoError(t, err) 117 | assert.NotNil(t, respData) 118 | }) 119 | 120 | t.Run("Test with nil req", func(t *testing.T) { 121 | _, err := cli.Variables.Update(ctx, nil) 122 | assert.Error(t, err) 123 | assert.Equal(t, "invalid req", err.Error()) 124 | }) 125 | } 126 | -------------------------------------------------------------------------------- /workflows.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | type workflows struct { 4 | Runs *workflowRuns 5 | Chat *workflowsChat 6 | } 7 | 8 | func newWorkflows(core *core) *workflows { 9 | return &workflows{ 10 | Runs: newWorkflowRun(core), 11 | Chat: newWorkflowsChat(core), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /workflows_chat.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "net/http" 7 | ) 8 | 9 | type workflowsChat struct { 10 | client *core 11 | } 12 | 13 | func (r *workflowsChat) Stream(ctx context.Context, req *WorkflowsChatStreamReq) (Stream[ChatEvent], error) { 14 | method := http.MethodPost 15 | uri := "/v1/workflows/chat" 16 | resp, err := r.client.StreamRequest(ctx, method, uri, req) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | return &streamReader[ChatEvent]{ 22 | ctx: ctx, 23 | response: resp, 24 | reader: bufio.NewReader(resp.Body), 25 | processor: parseChatEvent, 26 | httpResponse: newHTTPResponse(resp), 27 | }, nil 28 | } 29 | 30 | func newWorkflowsChat(core *core) *workflowsChat { 31 | return &workflowsChat{ 32 | client: core, 33 | } 34 | } 35 | 36 | // WorkflowsChatStreamReq 表示工作流聊天流式请求 37 | type WorkflowsChatStreamReq struct { 38 | WorkflowID string `json:"workflow_id"` // 工作流ID 39 | AdditionalMessages []*Message `json:"additional_messages"` // 额外的消息信息 40 | Parameters map[string]any `json:"parameters,omitempty"` // 工作流参数 41 | AppID *string `json:"app_id,omitempty"` // 应用ID 42 | BotID *string `json:"bot_id,omitempty"` // 机器人ID 43 | ConversationID *string `json:"conversation_id,omitempty"` // 会话ID 44 | Ext map[string]string `json:"ext,omitempty"` // 扩展信息 45 | } 46 | -------------------------------------------------------------------------------- /workflows_chat_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestWorkflowsChat(t *testing.T) { 15 | t.Run("Stream chat success", func(t *testing.T) { 16 | mockTransport := &mockTransport{ 17 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 18 | // Verify request method and path 19 | assert.Equal(t, http.MethodPost, req.Method) 20 | assert.Equal(t, "/v1/workflows/chat", req.URL.Path) 21 | 22 | // Return mock response with chat events 23 | events := []string{ 24 | `event: conversation.chat.created 25 | data: {"id":"chat1","conversation_id":"test_conversation_id","bot_id":"bot1","status":"created"} 26 | 27 | event: conversation.message.delta 28 | data: {"id":"msg1","conversation_id":"test_conversation_id","role":"assistant","content":"Hello"} 29 | 30 | event: done 31 | data: {} 32 | 33 | `, 34 | } 35 | return &http.Response{ 36 | StatusCode: http.StatusOK, 37 | Body: io.NopCloser(strings.NewReader(strings.Join(events, "\n"))), 38 | Header: make(http.Header), 39 | }, nil 40 | }, 41 | } 42 | 43 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 44 | chat := newWorkflowsChat(core) 45 | 46 | // Create test request 47 | req := &WorkflowsChatStreamReq{ 48 | WorkflowID: "test_workflow", 49 | AdditionalMessages: []*Message{ 50 | { 51 | Role: MessageRoleUser, 52 | Content: "Hello", 53 | }, 54 | }, 55 | Parameters: map[string]any{ 56 | "test": "value", 57 | }, 58 | } 59 | 60 | // Test streaming 61 | stream, err := chat.Stream(context.Background(), req) 62 | require.NoError(t, err) 63 | defer stream.Close() 64 | 65 | // Verify first event 66 | event1, err := stream.Recv() 67 | require.NoError(t, err) 68 | assert.Equal(t, ChatEventConversationChatCreated, event1.Event) 69 | 70 | // Verify second event 71 | event2, err := stream.Recv() 72 | require.NoError(t, err) 73 | assert.Equal(t, ChatEventConversationMessageDelta, event2.Event) 74 | 75 | // Verify completion event 76 | event3, err := stream.Recv() 77 | require.NoError(t, err) 78 | assert.Equal(t, ChatEventDone, event3.Event) 79 | 80 | // Verify stream end 81 | _, err = stream.Recv() 82 | assert.Equal(t, io.EOF, err) 83 | }) 84 | 85 | t.Run("Stream chat with error response", func(t *testing.T) { 86 | mockTransport := &mockTransport{ 87 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 88 | // Return error response 89 | mockResp := &http.Response{ 90 | StatusCode: http.StatusOK, 91 | Body: io.NopCloser(strings.NewReader(`{ 92 | "code": 100, 93 | "msg": "Invalid workflow ID" 94 | }`)), 95 | Header: make(http.Header), 96 | } 97 | mockResp.Header.Set("Content-Type", "application/json") 98 | mockResp.Header.Set(httpLogIDKey, "test_log_id") 99 | return mockResp, nil 100 | }, 101 | } 102 | 103 | core := newCore(&clientOption{ 104 | baseURL: ComBaseURL, 105 | client: &http.Client{Transport: mockTransport}, 106 | }) 107 | chat := newWorkflowsChat(core) 108 | 109 | req := &WorkflowsChatStreamReq{ 110 | WorkflowID: "invalid_workflow", 111 | } 112 | 113 | _, err := chat.Stream(context.Background(), req) 114 | require.Error(t, err) 115 | 116 | // Verify error details 117 | cozeErr, ok := AsCozeError(err) 118 | require.True(t, ok) 119 | assert.Equal(t, 100, cozeErr.Code) 120 | assert.Equal(t, "Invalid workflow ID", cozeErr.Message) 121 | assert.Equal(t, "test_log_id", cozeErr.LogID) 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /workflows_runs_histories.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | func (r *workflowRunsHistories) Retrieve(ctx context.Context, req *RetrieveWorkflowsRunsHistoriesReq) (*RetrieveWorkflowRunsHistoriesResp, error) { 10 | method := http.MethodGet 11 | uri := fmt.Sprintf("/v1/workflows/%s/run_histories/%s", req.WorkflowID, req.ExecuteID) 12 | resp := &retrieveWorkflowRunsHistoriesResp{} 13 | err := r.core.Request(ctx, method, uri, nil, resp) 14 | if err != nil { 15 | return nil, err 16 | } 17 | resp.RetrieveWorkflowRunsHistoriesResp.setHTTPResponse(resp.HTTPResponse) 18 | return resp.RetrieveWorkflowRunsHistoriesResp, nil 19 | } 20 | 21 | type workflowRunsHistories struct { 22 | core *core 23 | } 24 | 25 | func newWorkflowRunsHistories(core *core) *workflowRunsHistories { 26 | return &workflowRunsHistories{core: core} 27 | } 28 | 29 | // WorkflowRunMode represents how the workflow runs 30 | type WorkflowRunMode int 31 | 32 | const ( 33 | // WorkflowRunModeSynchronous Synchronous operation. 34 | WorkflowRunModeSynchronous WorkflowRunMode = 0 35 | 36 | // WorkflowRunModeStreaming Streaming operation. 37 | WorkflowRunModeStreaming WorkflowRunMode = 1 38 | 39 | // WorkflowRunModeAsynchronous Asynchronous operation. 40 | WorkflowRunModeAsynchronous WorkflowRunMode = 2 41 | ) 42 | 43 | // WorkflowExecuteStatus represents the execution status of a workflow 44 | type WorkflowExecuteStatus string 45 | 46 | const ( 47 | // WorkflowExecuteStatusSuccess Execution succeeded. 48 | WorkflowExecuteStatusSuccess WorkflowExecuteStatus = "Success" 49 | 50 | // WorkflowExecuteStatusRunning Execution in progress. 51 | WorkflowExecuteStatusRunning WorkflowExecuteStatus = "Running" 52 | 53 | // WorkflowExecuteStatusFail Execution failed. 54 | WorkflowExecuteStatusFail WorkflowExecuteStatus = "Fail" 55 | ) 56 | 57 | // RetrieveWorkflowsRunsHistoriesReq represents request for retrieving workflow runs history 58 | type RetrieveWorkflowsRunsHistoriesReq struct { 59 | // The ID of the workflow. 60 | ExecuteID string `json:"execute_id"` 61 | 62 | // The ID of the workflow async execute. 63 | WorkflowID string `json:"workflow_id"` 64 | } 65 | 66 | // runWorkflowsResp represents response for running workflow 67 | type runWorkflowsResp struct { 68 | baseResponse 69 | *RunWorkflowsResp 70 | } 71 | 72 | // RunWorkflowsResp represents response for running workflow 73 | type RunWorkflowsResp struct { 74 | baseModel 75 | // Execution ID of asynchronous execution. 76 | ExecuteID string `json:"execute_id,omitempty"` 77 | 78 | // Workflow execution result. 79 | Data string `json:"data,omitempty"` 80 | 81 | DebugURL string `json:"debug_url,omitempty"` 82 | Token int `json:"token,omitempty"` 83 | Cost string `json:"cost,omitempty"` 84 | } 85 | 86 | // retrieveWorkflowRunsHistoriesResp represents response for retrieving workflow runs history 87 | type retrieveWorkflowRunsHistoriesResp struct { 88 | baseResponse 89 | *RetrieveWorkflowRunsHistoriesResp 90 | } 91 | 92 | // RetrieveWorkflowRunsHistoriesResp represents response for retrieving workflow runs history 93 | type RetrieveWorkflowRunsHistoriesResp struct { 94 | baseModel 95 | Histories []*WorkflowRunHistory `json:"data"` 96 | } 97 | 98 | // WorkflowRunHistory represents the history of a workflow runs 99 | type WorkflowRunHistory struct { 100 | // The ID of execute. 101 | ExecuteID string `json:"execute_id"` 102 | 103 | // Execute status: success: Execution succeeded. running: Execution in progress. fail: Execution failed. 104 | ExecuteStatus WorkflowExecuteStatus `json:"execute_status"` 105 | 106 | // The Bot ID specified when executing the workflow. Returns 0 if no Bot ID was specified. 107 | BotID string `json:"bot_id"` 108 | 109 | // The release connector ID of the agent. By default, only the Agent as API connector is 110 | // displayed, and the connector ID is 1024. 111 | ConnectorID string `json:"connector_id"` 112 | 113 | // User ID, the user_id specified by the ext field when executing the workflow. If not specified, 114 | // the token applicant's button ID is returned. 115 | ConnectorUid string `json:"connector_uid"` 116 | 117 | // How the workflow runs: 0: Synchronous operation. 1: Streaming operation. 2: Asynchronous operation. 118 | RunMode WorkflowRunMode `json:"run_mode"` 119 | 120 | // The Log ID of the asynchronously running workflow. If the workflow is executed abnormally, you 121 | // can contact the service team to troubleshoot the problem through the Log ID. 122 | LogID string `json:"logid"` 123 | 124 | // The start time of the workflow, in Unix time timestamp format, in seconds. 125 | CreateTime int `json:"create_time"` 126 | 127 | // The workflow resume running time, in Unix time timestamp format, in seconds. 128 | UpdateTime int `json:"update_time"` 129 | 130 | // The output of the workflow is usually a JSON serialized string, but it may also be a non-JSON 131 | // structured string. 132 | Output string `json:"output"` 133 | 134 | // Status code. 0 represents a successful API call. Other values indicate that the call has 135 | // failed. You can determine the detailed reason for the error through the error_message field. 136 | ErrorCode string `json:"error_code"` 137 | 138 | // Status message. You can get detailed error information when the API call fails. 139 | ErrorMessage string `json:"error_message"` 140 | 141 | // Workflow trial runs debugging page. Visit this page to view the running results, input and 142 | // output information of each workflow node. 143 | DebugURL string `json:"debug_url"` 144 | } 145 | -------------------------------------------------------------------------------- /workflows_runs_histories_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestWorkflowRunsHistories(t *testing.T) { 13 | // Test Retrieve method 14 | t.Run("Retrieve workflow run history success", func(t *testing.T) { 15 | mockTransport := &mockTransport{ 16 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 17 | // Verify request method and path 18 | assert.Equal(t, http.MethodGet, req.Method) 19 | assert.Equal(t, "/v1/workflows/workflow1/run_histories/exec1", req.URL.Path) 20 | 21 | // Return mock response 22 | return mockResponse(http.StatusOK, &retrieveWorkflowRunsHistoriesResp{ 23 | RetrieveWorkflowRunsHistoriesResp: &RetrieveWorkflowRunsHistoriesResp{ 24 | Histories: []*WorkflowRunHistory{ 25 | { 26 | ExecuteID: "exec1", 27 | ExecuteStatus: WorkflowExecuteStatusSuccess, 28 | BotID: "bot1", 29 | ConnectorID: "1024", 30 | ConnectorUid: "user1", 31 | RunMode: WorkflowRunModeStreaming, 32 | LogID: "log1", 33 | CreateTime: 1234567890, 34 | UpdateTime: 1234567891, 35 | Output: `{"result": "success"}`, 36 | ErrorCode: "0", 37 | ErrorMessage: "", 38 | DebugURL: "https://debug.example.com", 39 | }, 40 | }, 41 | }, 42 | }) 43 | }, 44 | } 45 | 46 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 47 | histories := newWorkflowRunsHistories(core) 48 | 49 | resp, err := histories.Retrieve(context.Background(), &RetrieveWorkflowsRunsHistoriesReq{ 50 | WorkflowID: "workflow1", 51 | ExecuteID: "exec1", 52 | }) 53 | 54 | require.NoError(t, err) 55 | assert.Equal(t, "test_log_id", resp.LogID()) 56 | require.Len(t, resp.Histories, 1) 57 | 58 | history := resp.Histories[0] 59 | assert.Equal(t, "exec1", history.ExecuteID) 60 | assert.Equal(t, WorkflowExecuteStatusSuccess, history.ExecuteStatus) 61 | assert.Equal(t, "bot1", history.BotID) 62 | assert.Equal(t, "1024", history.ConnectorID) 63 | assert.Equal(t, "user1", history.ConnectorUid) 64 | assert.Equal(t, WorkflowRunModeStreaming, history.RunMode) 65 | assert.Equal(t, "log1", history.LogID) 66 | assert.Equal(t, 1234567890, history.CreateTime) 67 | assert.Equal(t, 1234567891, history.UpdateTime) 68 | assert.Equal(t, `{"result": "success"}`, history.Output) 69 | assert.Equal(t, "0", history.ErrorCode) 70 | assert.Empty(t, history.ErrorMessage) 71 | assert.Equal(t, "https://debug.example.com", history.DebugURL) 72 | }) 73 | 74 | // Test Retrieve method with error 75 | t.Run("Retrieve workflow run history with error", func(t *testing.T) { 76 | mockTransport := &mockTransport{ 77 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 78 | // Return error response 79 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 80 | }, 81 | } 82 | 83 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 84 | histories := newWorkflowRunsHistories(core) 85 | 86 | resp, err := histories.Retrieve(context.Background(), &RetrieveWorkflowsRunsHistoriesReq{ 87 | WorkflowID: "invalid_workflow", 88 | ExecuteID: "invalid_exec", 89 | }) 90 | 91 | require.Error(t, err) 92 | assert.Nil(t, resp) 93 | }) 94 | } 95 | 96 | func TestWorkflowRunMode(t *testing.T) { 97 | t.Run("WorkflowRunMode constants", func(t *testing.T) { 98 | assert.Equal(t, WorkflowRunMode(0), WorkflowRunModeSynchronous) 99 | assert.Equal(t, WorkflowRunMode(1), WorkflowRunModeStreaming) 100 | assert.Equal(t, WorkflowRunMode(2), WorkflowRunModeAsynchronous) 101 | }) 102 | } 103 | 104 | func TestWorkflowExecuteStatus(t *testing.T) { 105 | t.Run("WorkflowExecuteStatus constants", func(t *testing.T) { 106 | assert.Equal(t, WorkflowExecuteStatus("Success"), WorkflowExecuteStatusSuccess) 107 | assert.Equal(t, WorkflowExecuteStatus("Running"), WorkflowExecuteStatusRunning) 108 | assert.Equal(t, WorkflowExecuteStatus("Fail"), WorkflowExecuteStatusFail) 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /workspaces.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "strconv" 7 | ) 8 | 9 | func (r *workspace) List(ctx context.Context, req *ListWorkspaceReq) (NumberPaged[Workspace], error) { 10 | if req.PageSize == 0 { 11 | req.PageSize = 20 12 | } 13 | if req.PageNum == 0 { 14 | req.PageNum = 1 15 | } 16 | return NewNumberPaged[Workspace]( 17 | func(request *pageRequest) (*pageResponse[Workspace], error) { 18 | uri := "/v1/workspaces" 19 | resp := &listWorkspaceResp{} 20 | err := r.core.Request(ctx, http.MethodGet, uri, nil, resp, 21 | withHTTPQuery("page_num", strconv.Itoa(request.PageNum)), 22 | withHTTPQuery("page_size", strconv.Itoa(request.PageSize))) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &pageResponse[Workspace]{ 27 | Total: resp.Data.TotalCount, 28 | HasMore: len(resp.Data.Workspaces) >= request.PageSize, 29 | Data: resp.Data.Workspaces, 30 | LogID: resp.HTTPResponse.LogID(), 31 | }, nil 32 | }, req.PageSize, req.PageNum) 33 | } 34 | 35 | type workspace struct { 36 | core *core 37 | } 38 | 39 | func newWorkspace(core *core) *workspace { 40 | return &workspace{core: core} 41 | } 42 | 43 | // ListWorkspaceReq represents the request parameters for listing workspaces 44 | type ListWorkspaceReq struct { 45 | PageNum int `json:"page_num"` 46 | PageSize int `json:"page_size"` 47 | } 48 | 49 | func NewListWorkspaceReq() *ListWorkspaceReq { 50 | return &ListWorkspaceReq{ 51 | PageNum: 1, 52 | PageSize: 20, 53 | } 54 | } 55 | 56 | // listWorkspaceResp represents the response for listing workspaces 57 | type listWorkspaceResp struct { 58 | baseResponse 59 | Data *ListWorkspaceResp 60 | } 61 | 62 | // ListWorkspaceResp represents the response for listing workspaces 63 | type ListWorkspaceResp struct { 64 | baseModel 65 | TotalCount int `json:"total_count"` 66 | Workspaces []*Workspace `json:"workspaces"` 67 | } 68 | 69 | // Workspace represents workspace information 70 | type Workspace struct { 71 | ID string `json:"id"` 72 | Name string `json:"name"` 73 | IconUrl string `json:"icon_url"` 74 | RoleType WorkspaceRoleType `json:"role_type"` 75 | WorkspaceType WorkspaceType `json:"workspace_type"` 76 | } 77 | 78 | // WorkspaceRoleType represents the workspace role type 79 | type WorkspaceRoleType string 80 | 81 | const ( 82 | WorkspaceRoleTypeOwner WorkspaceRoleType = "owner" 83 | WorkspaceRoleTypeAdmin WorkspaceRoleType = "admin" 84 | WorkspaceRoleTypeMember WorkspaceRoleType = "member" 85 | ) 86 | 87 | // WorkspaceType represents the workspace type 88 | type WorkspaceType string 89 | 90 | const ( 91 | WorkspaceTypePersonal WorkspaceType = "personal" 92 | WorkspaceTypeTeam WorkspaceType = "team" 93 | ) 94 | -------------------------------------------------------------------------------- /workspaces_test.go: -------------------------------------------------------------------------------- 1 | package coze 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestWorkspaces(t *testing.T) { 13 | // Test List method 14 | t.Run("List workspaces success", func(t *testing.T) { 15 | mockTransport := &mockTransport{ 16 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 17 | // Verify request method and path 18 | assert.Equal(t, http.MethodGet, req.Method) 19 | assert.Equal(t, "/v1/workspaces", req.URL.Path) 20 | 21 | // Verify query parameters 22 | assert.Equal(t, "1", req.URL.Query().Get("page_num")) 23 | assert.Equal(t, "20", req.URL.Query().Get("page_size")) 24 | 25 | // Return mock response 26 | return mockResponse(http.StatusOK, &listWorkspaceResp{ 27 | Data: &ListWorkspaceResp{ 28 | TotalCount: 2, 29 | Workspaces: []*Workspace{ 30 | { 31 | ID: "ws1", 32 | Name: "Workspace 1", 33 | IconUrl: "https://example.com/icon1.png", 34 | RoleType: WorkspaceRoleTypeOwner, 35 | WorkspaceType: WorkspaceTypePersonal, 36 | }, 37 | { 38 | ID: "ws2", 39 | Name: "Workspace 2", 40 | IconUrl: "https://example.com/icon2.png", 41 | RoleType: WorkspaceRoleTypeAdmin, 42 | WorkspaceType: WorkspaceTypeTeam, 43 | }, 44 | }, 45 | }, 46 | }) 47 | }, 48 | } 49 | 50 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 51 | workspaces := newWorkspace(core) 52 | 53 | paged, err := workspaces.List(context.Background(), &ListWorkspaceReq{ 54 | PageNum: 1, 55 | PageSize: 20, 56 | }) 57 | 58 | require.NoError(t, err) 59 | assert.False(t, paged.HasMore()) 60 | items := paged.Items() 61 | require.Len(t, items, 2) 62 | 63 | // Verify first workspace 64 | assert.Equal(t, "ws1", items[0].ID) 65 | assert.Equal(t, "Workspace 1", items[0].Name) 66 | assert.Equal(t, "https://example.com/icon1.png", items[0].IconUrl) 67 | assert.Equal(t, WorkspaceRoleTypeOwner, items[0].RoleType) 68 | assert.Equal(t, WorkspaceTypePersonal, items[0].WorkspaceType) 69 | 70 | // Verify second workspace 71 | assert.Equal(t, "ws2", items[1].ID) 72 | assert.Equal(t, "Workspace 2", items[1].Name) 73 | assert.Equal(t, "https://example.com/icon2.png", items[1].IconUrl) 74 | assert.Equal(t, WorkspaceRoleTypeAdmin, items[1].RoleType) 75 | assert.Equal(t, WorkspaceTypeTeam, items[1].WorkspaceType) 76 | }) 77 | 78 | // Test List method with default pagination 79 | t.Run("List workspaces with default pagination", func(t *testing.T) { 80 | mockTransport := &mockTransport{ 81 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 82 | // Verify default pagination parameters 83 | assert.Equal(t, "1", req.URL.Query().Get("page_num")) 84 | assert.Equal(t, "20", req.URL.Query().Get("page_size")) 85 | 86 | // Return mock response 87 | return mockResponse(http.StatusOK, &listWorkspaceResp{ 88 | Data: &ListWorkspaceResp{ 89 | TotalCount: 0, 90 | Workspaces: []*Workspace{}, 91 | }, 92 | }) 93 | }, 94 | } 95 | 96 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 97 | workspaces := newWorkspace(core) 98 | 99 | paged, err := workspaces.List(context.Background(), NewListWorkspaceReq()) 100 | 101 | require.NoError(t, err) 102 | assert.False(t, paged.HasMore()) 103 | assert.Empty(t, paged.Items()) 104 | }) 105 | 106 | // Test List method with error 107 | t.Run("List workspaces with error", func(t *testing.T) { 108 | mockTransport := &mockTransport{ 109 | roundTripFunc: func(req *http.Request) (*http.Response, error) { 110 | // Return error response 111 | return mockResponse(http.StatusBadRequest, &baseResponse{}) 112 | }, 113 | } 114 | 115 | core := newCore(&clientOption{baseURL: ComBaseURL, client: &http.Client{Transport: mockTransport}}) 116 | workspaces := newWorkspace(core) 117 | 118 | paged, err := workspaces.List(context.Background(), &ListWorkspaceReq{ 119 | PageNum: 1, 120 | PageSize: 20, 121 | }) 122 | 123 | require.Error(t, err) 124 | assert.Nil(t, paged) 125 | }) 126 | } 127 | --------------------------------------------------------------------------------