diff --git a/.gitattributes b/.gitattributes index 7ec59d4a..09b2a6da 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,6 @@ .squad/agents/*/history.md merge=union .squad/log/** merge=union .squad/orchestration-log/** merge=union - # Set the default behavior, in case people don't have core.autocrlf set. * text=auto +*.zst filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index bf703978..da7a9f9b 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -27,6 +27,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + lfs: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index c164a158..a3509100 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -23,6 +23,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + lfs: true - name: Setup Go Environment uses: actions/setup-go@v5 @@ -111,6 +113,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + lfs: true - name: Setup Go Environment uses: actions/setup-go@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1520adfa..7d88d43b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,6 +74,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + lfs: true - name: Setup Go Environment uses: actions/setup-go@v5 @@ -170,6 +172,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + lfs: true - name: Setup Go Environment uses: actions/setup-go@v5 diff --git a/.github/workflows/waza-eval.yml b/.github/workflows/waza-eval.yml index b7f8ddec..567691ed 100644 --- a/.github/workflows/waza-eval.yml +++ b/.github/workflows/waza-eval.yml @@ -90,6 +90,8 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + lfs: true - name: Setup Go Environment uses: actions/setup-go@v5 diff --git a/README.md b/README.md index b31b3976..632b2f4c 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,18 @@ Or download binaries directly from the [latest release](https://github.com/micro Requires Go 1.26+: +NOTE, due to the use of LFS artifacts you cannot install waza using `go install`. To install waza, outside of a normal release, you'll need to clone the repository: + ```bash -go install github.com/microsoft/waza/cmd/waza@latest +git clone https://github.com/microsoft/waza.git +cd waza + +# ensure git LFS-based artifacts are available (for embedded copilot binaries) +git lfs install +git lfs pull + +go build -o waza ./cmd/waza +./waza ``` ### Azure Developer CLI (azd) Extension diff --git a/cmd/waza/cmd_new_task_test.go b/cmd/waza/cmd_new_task_test.go index 7b925df9..c5620585 100644 --- a/cmd/waza/cmd_new_task_test.go +++ b/cmd/waza/cmd_new_task_test.go @@ -173,10 +173,7 @@ func TestNewTaskFromPromptCommand_CopilotInitErrorReturned(t *testing.T) { func TestNewTaskFromPromptCommand_DiscoverErrorReturned(t *testing.T) { ctrl := gomock.NewController(t) - client := NewMockCopilotClient(ctrl) - - client.EXPECT().Start(gomock.Any()) - client.EXPECT().Stop().Return(nil) + client := newClientMock(ctrl) rootDir := t.TempDir() calledRoot := "" @@ -203,16 +200,14 @@ func TestNewTaskFromPromptCommand_DiscoverErrorReturned(t *testing.T) { func TestNewTaskFromPromptCommand_DiscoveredSkillsPassedToCopilotSession(t *testing.T) { ctrl := gomock.NewController(t) - client := NewMockCopilotClient(ctrl) + client := newClientMock(ctrl) skillDir := t.TempDir() - client.EXPECT().Start(gomock.Any()) client.EXPECT().CreateSession(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, cfg *copilot.SessionConfig) (execution.CopilotSession, error) { assert.Contains(t, cfg.SkillDirectories, skillDir) return nil, errors.New("create failed") }) - client.EXPECT().Stop().Return(nil) cmd := newTaskFromPromptCmd(&newTaskFromPromptCmdOptions{ NewTaskList: func(*ux.TaskListOptions) taskList { return &fakeTaskList{runAll: true} }, @@ -236,7 +231,7 @@ func TestNewTaskFromPromptCommand_DiscoveredSkillsPassedToCopilotSession(t *test func TestNewTaskFromPromptCommand_EndToEndCreatesTaskFile(t *testing.T) { ctrl := gomock.NewController(t) - client := NewMockCopilotClient(ctrl) + client := newClientMock(ctrl) session := NewMockCopilotSession(ctrl) sessionID := "session-end-to-end" @@ -251,7 +246,6 @@ func TestNewTaskFromPromptCommand_EndToEndCreatesTaskFile(t *testing.T) { require.NoError(t, os.MkdirAll(filepath.Dir(logPath), 0o755)) require.NoError(t, os.WriteFile(logPath, fixtureBytes, 0o644)) - client.EXPECT().Start(gomock.Any()) client.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(session, nil) session.EXPECT().On(gomock.Any()).Return(func() {}).Times(3) @@ -260,7 +254,6 @@ func TestNewTaskFromPromptCommand_EndToEndCreatesTaskFile(t *testing.T) { session.EXPECT().Disconnect().Return(nil) client.EXPECT().DeleteSession(gomock.Any(), sessionID).Return(nil) - client.EXPECT().Stop().Return(nil) outputPath := filepath.Join(t.TempDir(), "nested", "generated-task.yaml") @@ -316,3 +309,16 @@ func TestNewTaskFromPromptCommand_EndToEndCreatesTaskFile(t *testing.T) { require.Equal(t, expected, actual) } + +func newClientMock(ctrl *gomock.Controller) *MockCopilotClient { + clientMock := NewMockCopilotClient(ctrl) + + // This is the basic sequence of calls that occurs anytime a copilot engine is initialized + clientMock.EXPECT().Start(gomock.Any()).Times(1) + clientMock.EXPECT().Stop().Times(1) + clientMock.EXPECT().GetAuthStatus(gomock.Any()).Return(&copilot.GetAuthStatusResponse{ + IsAuthenticated: true, + }, nil).Times(1) + + return clientMock +} diff --git a/cmd/waza/cmd_run.go b/cmd/waza/cmd_run.go index a5af8207..ff440c86 100644 --- a/cmd/waza/cmd_run.go +++ b/cmd/waza/cmd_run.go @@ -91,8 +91,9 @@ With no arguments, uses workspace detection to find eval.yaml automatically: You can also specify a skill name to run its eval: waza run code-explainer`, - Args: cobra.MaximumNArgs(1), - RunE: runCommandE, + Args: cobra.MaximumNArgs(1), + RunE: runCommandE, + SilenceErrors: true, } cmd.Flags().StringVar(&contextDir, "context-dir", "", "Context directory for fixtures (default: ./fixtures relative to spec)") @@ -219,7 +220,7 @@ func runCommandE(cmd *cobra.Command, args []string) error { skillFolders = append(skillFolders, ds.Dir) } - slog.Info("Workspace skills added", "skills", skillFolders, "base", skillsPath) + slog.Debug("Workspace skills added", "skills", skillFolders, "base", skillsPath) } } diff --git a/cmd/waza/cmd_run_test.go b/cmd/waza/cmd_run_test.go index 066d9d43..16841f4e 100644 --- a/cmd/waza/cmd_run_test.go +++ b/cmd/waza/cmd_run_test.go @@ -2360,35 +2360,33 @@ func testWazaRun(t *testing.T, cwd string, args []string) (evalNames []string, s // This helper derives which evals were selected by inspecting EvaluationOutcome.BenchName in the output folder. ctrl := gomock.NewController(t) - client := NewMockCopilotClient(ctrl) - sess := NewMockCopilotSession(ctrl) - // currently, each run of an eval spec requires us a new copilot engine. - client.EXPECT().Start(gomock.Any()).AnyTimes() - client.EXPECT().Stop().AnyTimes() + newCopilotClientFn = func(clientOptions *copilot.ClientOptions) execution.CopilotClient { + client := newClientMock(ctrl) + sess := NewMockCopilotSession(ctrl) - client.EXPECT().CreateSession(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, config *copilot.SessionConfig) (execution.CopilotSession, error) { - require.NotEmpty(t, config.SkillDirectories, "all of our tests expect some skills to be found") + // currently, each run of an eval spec requires us a new copilot engine. + client.EXPECT().CreateSession(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, config *copilot.SessionConfig) (execution.CopilotSession, error) { + require.NotEmpty(t, config.SkillDirectories, "all of our tests expect some skills to be found") - for _, sp := range config.SkillDirectories { - skillsLoaded = append(skillsLoaded, filepath.Base(sp)) - } + for _, sp := range config.SkillDirectories { + skillsLoaded = append(skillsLoaded, filepath.Base(sp)) + } - return sess, nil - }).AnyTimes() + return sess, nil + }).AnyTimes() - sessionIDCounter := &atomic.Int32{} + sessionIDCounter := &atomic.Int32{} - sess.EXPECT().SessionID().DoAndReturn(func() string { - id := sessionIDCounter.Add(1) - return fmt.Sprintf("ID: %d", id) - }).AnyTimes() - sess.EXPECT().SendAndWait(gomock.Any(), gomock.Any()).AnyTimes() - sess.EXPECT().On(gomock.Any()).Return(func() {}).AnyTimes() - sess.EXPECT().Disconnect().AnyTimes() - client.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).AnyTimes() + sess.EXPECT().SessionID().DoAndReturn(func() string { + id := sessionIDCounter.Add(1) + return fmt.Sprintf("ID: %d", id) + }).AnyTimes() + sess.EXPECT().SendAndWait(gomock.Any(), gomock.Any()).AnyTimes() + sess.EXPECT().On(gomock.Any()).Return(func() {}).AnyTimes() + sess.EXPECT().Disconnect().AnyTimes() + client.EXPECT().DeleteSession(gomock.Any(), gomock.Any()).AnyTimes() - newCopilotClientFn = func(clientOptions *copilot.ClientOptions) execution.CopilotClient { return client } diff --git a/cmd/waza/copilot_client_wrapper_mocks_test.go b/cmd/waza/copilot_client_wrapper_mocks_test.go index 184d93e9..2a7076b8 100644 --- a/cmd/waza/copilot_client_wrapper_mocks_test.go +++ b/cmd/waza/copilot_client_wrapper_mocks_test.go @@ -152,6 +152,21 @@ func (mr *MockCopilotClientMockRecorder) DeleteSession(ctx, sessionID any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSession", reflect.TypeOf((*MockCopilotClient)(nil).DeleteSession), ctx, sessionID) } +// GetAuthStatus mocks base method. +func (m *MockCopilotClient) GetAuthStatus(ctx context.Context) (*copilot.GetAuthStatusResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAuthStatus", ctx) + ret0, _ := ret[0].(*copilot.GetAuthStatusResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAuthStatus indicates an expected call of GetAuthStatus. +func (mr *MockCopilotClientMockRecorder) GetAuthStatus(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthStatus", reflect.TypeOf((*MockCopilotClient)(nil).GetAuthStatus), ctx) +} + // ResumeSessionWithOptions mocks base method. func (m *MockCopilotClient) ResumeSessionWithOptions(ctx context.Context, sessionID string, config *copilot.ResumeSessionConfig) (execution.CopilotSession, error) { m.ctrl.T.Helper() diff --git a/go.mod b/go.mod index fc42c4be..a15d3d06 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/charmbracelet/huh v1.0.0 github.com/github/copilot-sdk/go v0.1.32 github.com/go-viper/mapstructure/v2 v2.5.0 + github.com/klauspost/compress v1.18.3 github.com/mattn/go-runewidth v0.0.21 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/spf13/cobra v1.10.2 @@ -111,4 +112,7 @@ require ( google.golang.org/protobuf v1.36.11 // indirect ) -tool go.uber.org/mock/mockgen +tool ( + github.com/github/copilot-sdk/go/cmd/bundler + go.uber.org/mock/mockgen +) diff --git a/go.sum b/go.sum index fb104a18..8fbf4406 100644 --- a/go.sum +++ b/go.sum @@ -159,6 +159,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= diff --git a/internal/embedded/generate/generate.go b/internal/embedded/generate/generate.go new file mode 100644 index 00000000..2ceff35d --- /dev/null +++ b/internal/embedded/generate/generate.go @@ -0,0 +1,93 @@ +// Generates all the proper copilot CLI SDK bundles, so we can use them in waza. +// The .zst, .license and generated .go files should all be checked in. When waza is built +// only the relevant copilot CLI package will be added. + +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "golang.org/x/sync/errgroup" +) + +//go:generate go run . windows/arm64 windows/amd64 linux/arm64 linux/amd64 darwin/arm64 darwin/amd64 + +func main() { + g := errgroup.Group{} + + platforms := os.Args[1:] + + fmt.Printf("Generating the following platforms:\n") + + for _, p := range platforms { + fmt.Println(p) + } + + fmt.Println("Starting...") + + outputDir := ".." + + for _, arg := range platforms { + g.Go(func() error { + cmd := exec.Command("go", "tool", "bundler", "-platform", arg, "-output", outputDir) + + fmt.Printf("Running %s\n", cmd.String()) + + output, err := cmd.CombinedOutput() + + if err != nil { + return fmt.Errorf("bundler failed (with output %s): %w", string(output), err) + } + + // once it's finished we just need to slap the proper package name on it. + platformParts := strings.Split(arg, "/") + + if len(platformParts) != 2 { + return fmt.Errorf("bad format for platform %q. Platforms should be / (ex: windows/amd64 )", arg) + } + + path := filepath.Join(outputDir, fmt.Sprintf("zcopilot_%s_%s.go", platformParts[0], platformParts[1])) + + fmt.Printf("Patching %s's package directive\n", path) + return fixCopilotPackageInGoFile(path) + }) + } + + if err := g.Wait(); err != nil { + fmt.Printf("Failed to generate bundles: %s\n", err) + os.Exit(1) + } else { + fmt.Println("Done, no errors") + fmt.Println("You must delete any older .zst or .license files, manually") + } +} + +func fixCopilotPackageInGoFile(goFile string) error { + contents, err := os.ReadFile(goFile) + + if err != nil { + return fmt.Errorf("failed to read %q to fix the package: %w", goFile, err) + } + + contents = bytes.Replace(contents, []byte("package main"), []byte("package embedded"), 1) + + err = os.WriteFile(goFile, contents, 0644) + + if err != nil { + return fmt.Errorf("failed to rewrite %q to fix the package: %w", goFile, err) + } + + cmd := exec.Command("gofmt", "-w", goFile) + stdout, err := cmd.CombinedOutput() + + if err != nil { + return fmt.Errorf("failed to gofmt %q. output: %s: %w", goFile, stdout, err) + } + + return nil +} diff --git a/internal/embedded/zcopilot_1.0.2_darwin_amd64.license b/internal/embedded/zcopilot_1.0.2_darwin_amd64.license new file mode 100644 index 00000000..325c3192 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_darwin_amd64.license @@ -0,0 +1,35 @@ +GitHub Copilot CLI License + +1. License Grant + Subject to the terms of this License, GitHub grants you a non‑exclusive, non‑transferable, royalty‑free license to install and run copies of the GitHub Copilot CLI (the “Software”). Subject to Section 2 below, GitHub also grants you the right to reproduce and redistribute unmodified copies of the Software as part of an application or service. + +2. Redistribution Rights and Conditions + You may reproduce and redistribute the Software only in accordance with all of the following conditions: + The Software is distributed only in unmodified form; + The Software is redistributed solely as part of an application or service that provides material functionality beyond the Software itself; + The Software is not distributed on a standalone basis or as a primary product; + You include a copy of this License and retain all applicable copyright, trademark, and attribution notices; and + Your application or service is licensed independently of the Software. + Nothing in this License restricts your choice of license for your application or service, including distribution under an open source license. This License applies solely to the Software and does not modify or supersede the license terms governing your application or its source code. + +3. Scope Limitations + This License does not grant you the right to: + Modify, adapt, translate, or create derivative works of the Software; + Redistribute the Software except as expressly permitted in Section 2; + Remove, alter, or obscure any proprietary notices included in the Software; or + Use GitHub trademarks, logos, or branding except as necessary to identify the Software. + +4. Reservation of Rights + GitHub and its licensors retain all right, title, and interest in and to the Software. All rights not expressly granted by this License are reserved. + +5. Disclaimer of Warranty + THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON‑INFRINGEMENT. THE ENTIRE RISK ARISING OUT OF USE OF THE SOFTWARE REMAINS WITH YOU. + +6. Limitation of Liability + TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL GITHUB OR ITS LICENSORS BE LIABLE FOR ANY DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR THE USE OR DISTRIBUTION OF THE SOFTWARE, WHETHER IN CONTRACT, TORT, OR OTHERWISE. + +7. Termination + This License terminates automatically if you fail to comply with its terms. Upon termination, you must cease all use and distribution of the Software. + +8. Notice Regarding GitHub Services (Informational Only) + Use of the Software may require access to GitHub services and is subject to the applicable GitHub Terms of Service and GitHub Copilot terms. This License governs only rights related to the Software and does not grant any rights to access or use GitHub services. diff --git a/internal/embedded/zcopilot_1.0.2_darwin_amd64.zst b/internal/embedded/zcopilot_1.0.2_darwin_amd64.zst new file mode 100644 index 00000000..d18bd41d --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_darwin_amd64.zst @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:525d451101584e018a481c1a27870a6f02ee3d3c1ef85d195d0eb6b1e03206bf +size 56213582 diff --git a/internal/embedded/zcopilot_1.0.2_darwin_arm64.license b/internal/embedded/zcopilot_1.0.2_darwin_arm64.license new file mode 100644 index 00000000..325c3192 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_darwin_arm64.license @@ -0,0 +1,35 @@ +GitHub Copilot CLI License + +1. License Grant + Subject to the terms of this License, GitHub grants you a non‑exclusive, non‑transferable, royalty‑free license to install and run copies of the GitHub Copilot CLI (the “Software”). Subject to Section 2 below, GitHub also grants you the right to reproduce and redistribute unmodified copies of the Software as part of an application or service. + +2. Redistribution Rights and Conditions + You may reproduce and redistribute the Software only in accordance with all of the following conditions: + The Software is distributed only in unmodified form; + The Software is redistributed solely as part of an application or service that provides material functionality beyond the Software itself; + The Software is not distributed on a standalone basis or as a primary product; + You include a copy of this License and retain all applicable copyright, trademark, and attribution notices; and + Your application or service is licensed independently of the Software. + Nothing in this License restricts your choice of license for your application or service, including distribution under an open source license. This License applies solely to the Software and does not modify or supersede the license terms governing your application or its source code. + +3. Scope Limitations + This License does not grant you the right to: + Modify, adapt, translate, or create derivative works of the Software; + Redistribute the Software except as expressly permitted in Section 2; + Remove, alter, or obscure any proprietary notices included in the Software; or + Use GitHub trademarks, logos, or branding except as necessary to identify the Software. + +4. Reservation of Rights + GitHub and its licensors retain all right, title, and interest in and to the Software. All rights not expressly granted by this License are reserved. + +5. Disclaimer of Warranty + THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON‑INFRINGEMENT. THE ENTIRE RISK ARISING OUT OF USE OF THE SOFTWARE REMAINS WITH YOU. + +6. Limitation of Liability + TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL GITHUB OR ITS LICENSORS BE LIABLE FOR ANY DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR THE USE OR DISTRIBUTION OF THE SOFTWARE, WHETHER IN CONTRACT, TORT, OR OTHERWISE. + +7. Termination + This License terminates automatically if you fail to comply with its terms. Upon termination, you must cease all use and distribution of the Software. + +8. Notice Regarding GitHub Services (Informational Only) + Use of the Software may require access to GitHub services and is subject to the applicable GitHub Terms of Service and GitHub Copilot terms. This License governs only rights related to the Software and does not grant any rights to access or use GitHub services. diff --git a/internal/embedded/zcopilot_1.0.2_darwin_arm64.zst b/internal/embedded/zcopilot_1.0.2_darwin_arm64.zst new file mode 100644 index 00000000..cf913aba --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_darwin_arm64.zst @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bd27494edd9698f017844095f078e8251fde7622c9e35b9b7721ee9faa4f20f +size 52830183 diff --git a/internal/embedded/zcopilot_1.0.2_linux_amd64.license b/internal/embedded/zcopilot_1.0.2_linux_amd64.license new file mode 100644 index 00000000..325c3192 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_linux_amd64.license @@ -0,0 +1,35 @@ +GitHub Copilot CLI License + +1. License Grant + Subject to the terms of this License, GitHub grants you a non‑exclusive, non‑transferable, royalty‑free license to install and run copies of the GitHub Copilot CLI (the “Software”). Subject to Section 2 below, GitHub also grants you the right to reproduce and redistribute unmodified copies of the Software as part of an application or service. + +2. Redistribution Rights and Conditions + You may reproduce and redistribute the Software only in accordance with all of the following conditions: + The Software is distributed only in unmodified form; + The Software is redistributed solely as part of an application or service that provides material functionality beyond the Software itself; + The Software is not distributed on a standalone basis or as a primary product; + You include a copy of this License and retain all applicable copyright, trademark, and attribution notices; and + Your application or service is licensed independently of the Software. + Nothing in this License restricts your choice of license for your application or service, including distribution under an open source license. This License applies solely to the Software and does not modify or supersede the license terms governing your application or its source code. + +3. Scope Limitations + This License does not grant you the right to: + Modify, adapt, translate, or create derivative works of the Software; + Redistribute the Software except as expressly permitted in Section 2; + Remove, alter, or obscure any proprietary notices included in the Software; or + Use GitHub trademarks, logos, or branding except as necessary to identify the Software. + +4. Reservation of Rights + GitHub and its licensors retain all right, title, and interest in and to the Software. All rights not expressly granted by this License are reserved. + +5. Disclaimer of Warranty + THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON‑INFRINGEMENT. THE ENTIRE RISK ARISING OUT OF USE OF THE SOFTWARE REMAINS WITH YOU. + +6. Limitation of Liability + TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL GITHUB OR ITS LICENSORS BE LIABLE FOR ANY DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR THE USE OR DISTRIBUTION OF THE SOFTWARE, WHETHER IN CONTRACT, TORT, OR OTHERWISE. + +7. Termination + This License terminates automatically if you fail to comply with its terms. Upon termination, you must cease all use and distribution of the Software. + +8. Notice Regarding GitHub Services (Informational Only) + Use of the Software may require access to GitHub services and is subject to the applicable GitHub Terms of Service and GitHub Copilot terms. This License governs only rights related to the Software and does not grant any rights to access or use GitHub services. diff --git a/internal/embedded/zcopilot_1.0.2_linux_amd64.zst b/internal/embedded/zcopilot_1.0.2_linux_amd64.zst new file mode 100644 index 00000000..6501d7d0 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_linux_amd64.zst @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0082e9bced9ba002ab1e32fb7dea1cdbcffe74dae9477007d4bf062dc2fff83 +size 58469156 diff --git a/internal/embedded/zcopilot_1.0.2_linux_arm64.license b/internal/embedded/zcopilot_1.0.2_linux_arm64.license new file mode 100644 index 00000000..325c3192 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_linux_arm64.license @@ -0,0 +1,35 @@ +GitHub Copilot CLI License + +1. License Grant + Subject to the terms of this License, GitHub grants you a non‑exclusive, non‑transferable, royalty‑free license to install and run copies of the GitHub Copilot CLI (the “Software”). Subject to Section 2 below, GitHub also grants you the right to reproduce and redistribute unmodified copies of the Software as part of an application or service. + +2. Redistribution Rights and Conditions + You may reproduce and redistribute the Software only in accordance with all of the following conditions: + The Software is distributed only in unmodified form; + The Software is redistributed solely as part of an application or service that provides material functionality beyond the Software itself; + The Software is not distributed on a standalone basis or as a primary product; + You include a copy of this License and retain all applicable copyright, trademark, and attribution notices; and + Your application or service is licensed independently of the Software. + Nothing in this License restricts your choice of license for your application or service, including distribution under an open source license. This License applies solely to the Software and does not modify or supersede the license terms governing your application or its source code. + +3. Scope Limitations + This License does not grant you the right to: + Modify, adapt, translate, or create derivative works of the Software; + Redistribute the Software except as expressly permitted in Section 2; + Remove, alter, or obscure any proprietary notices included in the Software; or + Use GitHub trademarks, logos, or branding except as necessary to identify the Software. + +4. Reservation of Rights + GitHub and its licensors retain all right, title, and interest in and to the Software. All rights not expressly granted by this License are reserved. + +5. Disclaimer of Warranty + THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON‑INFRINGEMENT. THE ENTIRE RISK ARISING OUT OF USE OF THE SOFTWARE REMAINS WITH YOU. + +6. Limitation of Liability + TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL GITHUB OR ITS LICENSORS BE LIABLE FOR ANY DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR THE USE OR DISTRIBUTION OF THE SOFTWARE, WHETHER IN CONTRACT, TORT, OR OTHERWISE. + +7. Termination + This License terminates automatically if you fail to comply with its terms. Upon termination, you must cease all use and distribution of the Software. + +8. Notice Regarding GitHub Services (Informational Only) + Use of the Software may require access to GitHub services and is subject to the applicable GitHub Terms of Service and GitHub Copilot terms. This License governs only rights related to the Software and does not grant any rights to access or use GitHub services. diff --git a/internal/embedded/zcopilot_1.0.2_linux_arm64.zst b/internal/embedded/zcopilot_1.0.2_linux_arm64.zst new file mode 100644 index 00000000..19001219 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_linux_arm64.zst @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:292d0d78b973d62ef3db9c10fcbae89463a07e9ed328061ee9b8db7b929363e9 +size 60491194 diff --git a/internal/embedded/zcopilot_1.0.2_windows_amd64.exe.license b/internal/embedded/zcopilot_1.0.2_windows_amd64.exe.license new file mode 100644 index 00000000..325c3192 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_windows_amd64.exe.license @@ -0,0 +1,35 @@ +GitHub Copilot CLI License + +1. License Grant + Subject to the terms of this License, GitHub grants you a non‑exclusive, non‑transferable, royalty‑free license to install and run copies of the GitHub Copilot CLI (the “Software”). Subject to Section 2 below, GitHub also grants you the right to reproduce and redistribute unmodified copies of the Software as part of an application or service. + +2. Redistribution Rights and Conditions + You may reproduce and redistribute the Software only in accordance with all of the following conditions: + The Software is distributed only in unmodified form; + The Software is redistributed solely as part of an application or service that provides material functionality beyond the Software itself; + The Software is not distributed on a standalone basis or as a primary product; + You include a copy of this License and retain all applicable copyright, trademark, and attribution notices; and + Your application or service is licensed independently of the Software. + Nothing in this License restricts your choice of license for your application or service, including distribution under an open source license. This License applies solely to the Software and does not modify or supersede the license terms governing your application or its source code. + +3. Scope Limitations + This License does not grant you the right to: + Modify, adapt, translate, or create derivative works of the Software; + Redistribute the Software except as expressly permitted in Section 2; + Remove, alter, or obscure any proprietary notices included in the Software; or + Use GitHub trademarks, logos, or branding except as necessary to identify the Software. + +4. Reservation of Rights + GitHub and its licensors retain all right, title, and interest in and to the Software. All rights not expressly granted by this License are reserved. + +5. Disclaimer of Warranty + THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON‑INFRINGEMENT. THE ENTIRE RISK ARISING OUT OF USE OF THE SOFTWARE REMAINS WITH YOU. + +6. Limitation of Liability + TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL GITHUB OR ITS LICENSORS BE LIABLE FOR ANY DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR THE USE OR DISTRIBUTION OF THE SOFTWARE, WHETHER IN CONTRACT, TORT, OR OTHERWISE. + +7. Termination + This License terminates automatically if you fail to comply with its terms. Upon termination, you must cease all use and distribution of the Software. + +8. Notice Regarding GitHub Services (Informational Only) + Use of the Software may require access to GitHub services and is subject to the applicable GitHub Terms of Service and GitHub Copilot terms. This License governs only rights related to the Software and does not grant any rights to access or use GitHub services. diff --git a/internal/embedded/zcopilot_1.0.2_windows_amd64.exe.zst b/internal/embedded/zcopilot_1.0.2_windows_amd64.exe.zst new file mode 100644 index 00000000..0d722b56 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_windows_amd64.exe.zst @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abc71a2305eaec5d8e1697644fe5f6775708b7034a43c9a4bc0a9d58787f21e8 +size 52604614 diff --git a/internal/embedded/zcopilot_1.0.2_windows_arm64.exe.license b/internal/embedded/zcopilot_1.0.2_windows_arm64.exe.license new file mode 100644 index 00000000..325c3192 --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_windows_arm64.exe.license @@ -0,0 +1,35 @@ +GitHub Copilot CLI License + +1. License Grant + Subject to the terms of this License, GitHub grants you a non‑exclusive, non‑transferable, royalty‑free license to install and run copies of the GitHub Copilot CLI (the “Software”). Subject to Section 2 below, GitHub also grants you the right to reproduce and redistribute unmodified copies of the Software as part of an application or service. + +2. Redistribution Rights and Conditions + You may reproduce and redistribute the Software only in accordance with all of the following conditions: + The Software is distributed only in unmodified form; + The Software is redistributed solely as part of an application or service that provides material functionality beyond the Software itself; + The Software is not distributed on a standalone basis or as a primary product; + You include a copy of this License and retain all applicable copyright, trademark, and attribution notices; and + Your application or service is licensed independently of the Software. + Nothing in this License restricts your choice of license for your application or service, including distribution under an open source license. This License applies solely to the Software and does not modify or supersede the license terms governing your application or its source code. + +3. Scope Limitations + This License does not grant you the right to: + Modify, adapt, translate, or create derivative works of the Software; + Redistribute the Software except as expressly permitted in Section 2; + Remove, alter, or obscure any proprietary notices included in the Software; or + Use GitHub trademarks, logos, or branding except as necessary to identify the Software. + +4. Reservation of Rights + GitHub and its licensors retain all right, title, and interest in and to the Software. All rights not expressly granted by this License are reserved. + +5. Disclaimer of Warranty + THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON‑INFRINGEMENT. THE ENTIRE RISK ARISING OUT OF USE OF THE SOFTWARE REMAINS WITH YOU. + +6. Limitation of Liability + TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL GITHUB OR ITS LICENSORS BE LIABLE FOR ANY DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR THE USE OR DISTRIBUTION OF THE SOFTWARE, WHETHER IN CONTRACT, TORT, OR OTHERWISE. + +7. Termination + This License terminates automatically if you fail to comply with its terms. Upon termination, you must cease all use and distribution of the Software. + +8. Notice Regarding GitHub Services (Informational Only) + Use of the Software may require access to GitHub services and is subject to the applicable GitHub Terms of Service and GitHub Copilot terms. This License governs only rights related to the Software and does not grant any rights to access or use GitHub services. diff --git a/internal/embedded/zcopilot_1.0.2_windows_arm64.exe.zst b/internal/embedded/zcopilot_1.0.2_windows_arm64.exe.zst new file mode 100644 index 00000000..07b75a4c --- /dev/null +++ b/internal/embedded/zcopilot_1.0.2_windows_arm64.exe.zst @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a8882948c2c199c1efa75af6cfaf800ec643314bf4e604d5bb9f6b78f71e154 +size 50979364 diff --git a/internal/embedded/zcopilot_darwin_amd64.go b/internal/embedded/zcopilot_darwin_amd64.go new file mode 100644 index 00000000..728a3873 --- /dev/null +++ b/internal/embedded/zcopilot_darwin_amd64.go @@ -0,0 +1,44 @@ +// Code generated by copilot-sdk bundler; DO NOT EDIT. + +package embedded + +import ( + "bytes" + _ "embed" + "encoding/base64" + "io" + + "github.com/github/copilot-sdk/go/embeddedcli" + "github.com/klauspost/compress/zstd" +) + +//go:embed zcopilot_1.0.2_darwin_amd64.zst +var localEmbeddedCopilotCLI []byte + +//go:embed zcopilot_1.0.2_darwin_amd64.license +var localEmbeddedCopilotCLILicense []byte + +func init() { + embeddedcli.Setup(embeddedcli.Config{ + Cli: cliReader(), + License: localEmbeddedCopilotCLILicense, + Version: "1.0.2", + CliHash: mustDecodeBase64("sF8drq6+D1apJuWgxXTe70WxLkKzs81mW8qfRS9ruEQ="), + }) +} + +func cliReader() io.Reader { + r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotCLI)) + if err != nil { + panic("failed to create zstd reader: " + err.Error()) + } + return r +} + +func mustDecodeBase64(s string) []byte { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic("failed to decode base64: " + err.Error()) + } + return b +} diff --git a/internal/embedded/zcopilot_darwin_arm64.go b/internal/embedded/zcopilot_darwin_arm64.go new file mode 100644 index 00000000..f3e8bc50 --- /dev/null +++ b/internal/embedded/zcopilot_darwin_arm64.go @@ -0,0 +1,44 @@ +// Code generated by copilot-sdk bundler; DO NOT EDIT. + +package embedded + +import ( + "bytes" + _ "embed" + "encoding/base64" + "io" + + "github.com/github/copilot-sdk/go/embeddedcli" + "github.com/klauspost/compress/zstd" +) + +//go:embed zcopilot_1.0.2_darwin_arm64.zst +var localEmbeddedCopilotCLI []byte + +//go:embed zcopilot_1.0.2_darwin_arm64.license +var localEmbeddedCopilotCLILicense []byte + +func init() { + embeddedcli.Setup(embeddedcli.Config{ + Cli: cliReader(), + License: localEmbeddedCopilotCLILicense, + Version: "1.0.2", + CliHash: mustDecodeBase64("T0l79xl4tSxnZH2SLVhXu3luA8S95YL2raA6S1LLC0c="), + }) +} + +func cliReader() io.Reader { + r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotCLI)) + if err != nil { + panic("failed to create zstd reader: " + err.Error()) + } + return r +} + +func mustDecodeBase64(s string) []byte { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic("failed to decode base64: " + err.Error()) + } + return b +} diff --git a/internal/embedded/zcopilot_linux_amd64.go b/internal/embedded/zcopilot_linux_amd64.go new file mode 100644 index 00000000..f7def2cf --- /dev/null +++ b/internal/embedded/zcopilot_linux_amd64.go @@ -0,0 +1,44 @@ +// Code generated by copilot-sdk bundler; DO NOT EDIT. + +package embedded + +import ( + "bytes" + _ "embed" + "encoding/base64" + "io" + + "github.com/github/copilot-sdk/go/embeddedcli" + "github.com/klauspost/compress/zstd" +) + +//go:embed zcopilot_1.0.2_linux_amd64.zst +var localEmbeddedCopilotCLI []byte + +//go:embed zcopilot_1.0.2_linux_amd64.license +var localEmbeddedCopilotCLILicense []byte + +func init() { + embeddedcli.Setup(embeddedcli.Config{ + Cli: cliReader(), + License: localEmbeddedCopilotCLILicense, + Version: "1.0.2", + CliHash: mustDecodeBase64("GaOaxJDiB/gA0U+Ovh619GXpk/9Cq6OaZeHN3kpNuBk="), + }) +} + +func cliReader() io.Reader { + r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotCLI)) + if err != nil { + panic("failed to create zstd reader: " + err.Error()) + } + return r +} + +func mustDecodeBase64(s string) []byte { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic("failed to decode base64: " + err.Error()) + } + return b +} diff --git a/internal/embedded/zcopilot_linux_arm64.go b/internal/embedded/zcopilot_linux_arm64.go new file mode 100644 index 00000000..62a4069e --- /dev/null +++ b/internal/embedded/zcopilot_linux_arm64.go @@ -0,0 +1,44 @@ +// Code generated by copilot-sdk bundler; DO NOT EDIT. + +package embedded + +import ( + "bytes" + _ "embed" + "encoding/base64" + "io" + + "github.com/github/copilot-sdk/go/embeddedcli" + "github.com/klauspost/compress/zstd" +) + +//go:embed zcopilot_1.0.2_linux_arm64.zst +var localEmbeddedCopilotCLI []byte + +//go:embed zcopilot_1.0.2_linux_arm64.license +var localEmbeddedCopilotCLILicense []byte + +func init() { + embeddedcli.Setup(embeddedcli.Config{ + Cli: cliReader(), + License: localEmbeddedCopilotCLILicense, + Version: "1.0.2", + CliHash: mustDecodeBase64("O7ie1jLb0yZ8jRS83oo+Wr4JoXKmH/ubsAR8hDZkPIA="), + }) +} + +func cliReader() io.Reader { + r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotCLI)) + if err != nil { + panic("failed to create zstd reader: " + err.Error()) + } + return r +} + +func mustDecodeBase64(s string) []byte { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic("failed to decode base64: " + err.Error()) + } + return b +} diff --git a/internal/embedded/zcopilot_windows_amd64.go b/internal/embedded/zcopilot_windows_amd64.go new file mode 100644 index 00000000..f34450a7 --- /dev/null +++ b/internal/embedded/zcopilot_windows_amd64.go @@ -0,0 +1,44 @@ +// Code generated by copilot-sdk bundler; DO NOT EDIT. + +package embedded + +import ( + "bytes" + _ "embed" + "encoding/base64" + "io" + + "github.com/github/copilot-sdk/go/embeddedcli" + "github.com/klauspost/compress/zstd" +) + +//go:embed zcopilot_1.0.2_windows_amd64.exe.zst +var localEmbeddedCopilotCLI []byte + +//go:embed zcopilot_1.0.2_windows_amd64.exe.license +var localEmbeddedCopilotCLILicense []byte + +func init() { + embeddedcli.Setup(embeddedcli.Config{ + Cli: cliReader(), + License: localEmbeddedCopilotCLILicense, + Version: "1.0.2", + CliHash: mustDecodeBase64("yjpBdMAvhtowOLMvs0o9mQJ5grYtURJq4AyBeQj1+uw="), + }) +} + +func cliReader() io.Reader { + r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotCLI)) + if err != nil { + panic("failed to create zstd reader: " + err.Error()) + } + return r +} + +func mustDecodeBase64(s string) []byte { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic("failed to decode base64: " + err.Error()) + } + return b +} diff --git a/internal/embedded/zcopilot_windows_arm64.go b/internal/embedded/zcopilot_windows_arm64.go new file mode 100644 index 00000000..1afd14be --- /dev/null +++ b/internal/embedded/zcopilot_windows_arm64.go @@ -0,0 +1,44 @@ +// Code generated by copilot-sdk bundler; DO NOT EDIT. + +package embedded + +import ( + "bytes" + _ "embed" + "encoding/base64" + "io" + + "github.com/github/copilot-sdk/go/embeddedcli" + "github.com/klauspost/compress/zstd" +) + +//go:embed zcopilot_1.0.2_windows_arm64.exe.zst +var localEmbeddedCopilotCLI []byte + +//go:embed zcopilot_1.0.2_windows_arm64.exe.license +var localEmbeddedCopilotCLILicense []byte + +func init() { + embeddedcli.Setup(embeddedcli.Config{ + Cli: cliReader(), + License: localEmbeddedCopilotCLILicense, + Version: "1.0.2", + CliHash: mustDecodeBase64("i1NCV1IxakgbZgtbFUIml0GC/7RS73uOsyf+HP9PnDY="), + }) +} + +func cliReader() io.Reader { + r, err := zstd.NewReader(bytes.NewReader(localEmbeddedCopilotCLI)) + if err != nil { + panic("failed to create zstd reader: " + err.Error()) + } + return r +} + +func mustDecodeBase64(s string) []byte { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic("failed to decode base64: " + err.Error()) + } + return b +} diff --git a/internal/execution/copilot.go b/internal/execution/copilot.go index fe2b8732..18f7e26b 100644 --- a/internal/execution/copilot.go +++ b/internal/execution/copilot.go @@ -12,6 +12,9 @@ import ( copilot "github.com/github/copilot-sdk/go" "github.com/microsoft/waza/internal/models" "github.com/microsoft/waza/internal/utils" + + // auto-loads the embedded copilot CLI, over using the copilot CLI on the machine. + _ "github.com/microsoft/waza/internal/embedded" ) // CopilotEngine integrates with GitHub Copilot SDK @@ -91,6 +94,26 @@ func (e *CopilotEngine) Initialize(ctx context.Context) error { // it'll kill the copilot process. // Tracking here: https://github.com/github/copilot-sdk/issues/668 startErr = e.client.Start(context.Background()) + + if startErr != nil { + return + } + + authStatusResp, err := e.client.GetAuthStatus(ctx) + + if err != nil { + _ = e.client.Stop() + + startErr = fmt.Errorf("failed to get copilot authentication status. Use any installed instance of copilot CLI and run \"copilot login\" before using this command: %w", err) + return + } + + if !authStatusResp.IsAuthenticated { + _ = e.client.Stop() + + startErr = fmt.Errorf("copilot is not authenticated. Use any installed instance of copilot CLI and run \"copilot login\" before using this command") + return + } }) if startErr != nil { diff --git a/internal/execution/copilot_client_wrapper_mocks_test.go b/internal/execution/copilot_client_wrapper_mocks_test.go index bf6bf78b..1e252116 100644 --- a/internal/execution/copilot_client_wrapper_mocks_test.go +++ b/internal/execution/copilot_client_wrapper_mocks_test.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/microsoft/waza/internal/execution (interfaces: copilotSession,copilotClient) +// Source: github.com/microsoft/waza/internal/execution (interfaces: CopilotSession,CopilotClient) // // Generated by this command: // -// mockgen -package execution -destination copilot_client_wrapper_mocks_test.go . copilotSession,copilotClient +// mockgen -package execution -destination copilot_client_wrapper_mocks_test.go . CopilotSession,CopilotClient // // Package execution is a generated GoMock package. @@ -17,32 +17,32 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockcopilotSession is a mock of copilotSession interface. -type MockcopilotSession struct { +// MockCopilotSession is a mock of CopilotSession interface. +type MockCopilotSession struct { ctrl *gomock.Controller - recorder *MockcopilotSessionMockRecorder + recorder *MockCopilotSessionMockRecorder isgomock struct{} } -// MockcopilotSessionMockRecorder is the mock recorder for MockcopilotSession. -type MockcopilotSessionMockRecorder struct { - mock *MockcopilotSession +// MockCopilotSessionMockRecorder is the mock recorder for MockCopilotSession. +type MockCopilotSessionMockRecorder struct { + mock *MockCopilotSession } -// NewMockcopilotSession creates a new mock instance. -func NewMockcopilotSession(ctrl *gomock.Controller) *MockcopilotSession { - mock := &MockcopilotSession{ctrl: ctrl} - mock.recorder = &MockcopilotSessionMockRecorder{mock} +// NewMockCopilotSession creates a new mock instance. +func NewMockCopilotSession(ctrl *gomock.Controller) *MockCopilotSession { + mock := &MockCopilotSession{ctrl: ctrl} + mock.recorder = &MockCopilotSessionMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockcopilotSession) EXPECT() *MockcopilotSessionMockRecorder { +func (m *MockCopilotSession) EXPECT() *MockCopilotSessionMockRecorder { return m.recorder } // Disconnect mocks base method. -func (m *MockcopilotSession) Disconnect() error { +func (m *MockCopilotSession) Disconnect() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Disconnect") ret0, _ := ret[0].(error) @@ -50,13 +50,13 @@ func (m *MockcopilotSession) Disconnect() error { } // Disconnect indicates an expected call of Disconnect. -func (mr *MockcopilotSessionMockRecorder) Disconnect() *gomock.Call { +func (mr *MockCopilotSessionMockRecorder) Disconnect() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*MockcopilotSession)(nil).Disconnect)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*MockCopilotSession)(nil).Disconnect)) } // On mocks base method. -func (m *MockcopilotSession) On(handler copilot.SessionEventHandler) func() { +func (m *MockCopilotSession) On(handler copilot.SessionEventHandler) func() { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "On", handler) ret0, _ := ret[0].(func()) @@ -64,13 +64,13 @@ func (m *MockcopilotSession) On(handler copilot.SessionEventHandler) func() { } // On indicates an expected call of On. -func (mr *MockcopilotSessionMockRecorder) On(handler any) *gomock.Call { +func (mr *MockCopilotSessionMockRecorder) On(handler any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "On", reflect.TypeOf((*MockcopilotSession)(nil).On), handler) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "On", reflect.TypeOf((*MockCopilotSession)(nil).On), handler) } // SendAndWait mocks base method. -func (m *MockcopilotSession) SendAndWait(ctx context.Context, options copilot.MessageOptions) (*copilot.SessionEvent, error) { +func (m *MockCopilotSession) SendAndWait(ctx context.Context, options copilot.MessageOptions) (*copilot.SessionEvent, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendAndWait", ctx, options) ret0, _ := ret[0].(*copilot.SessionEvent) @@ -79,13 +79,13 @@ func (m *MockcopilotSession) SendAndWait(ctx context.Context, options copilot.Me } // SendAndWait indicates an expected call of SendAndWait. -func (mr *MockcopilotSessionMockRecorder) SendAndWait(ctx, options any) *gomock.Call { +func (mr *MockCopilotSessionMockRecorder) SendAndWait(ctx, options any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAndWait", reflect.TypeOf((*MockcopilotSession)(nil).SendAndWait), ctx, options) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAndWait", reflect.TypeOf((*MockCopilotSession)(nil).SendAndWait), ctx, options) } // SessionID mocks base method. -func (m *MockcopilotSession) SessionID() string { +func (m *MockCopilotSession) SessionID() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SessionID") ret0, _ := ret[0].(string) @@ -93,37 +93,37 @@ func (m *MockcopilotSession) SessionID() string { } // SessionID indicates an expected call of SessionID. -func (mr *MockcopilotSessionMockRecorder) SessionID() *gomock.Call { +func (mr *MockCopilotSessionMockRecorder) SessionID() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SessionID", reflect.TypeOf((*MockcopilotSession)(nil).SessionID)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SessionID", reflect.TypeOf((*MockCopilotSession)(nil).SessionID)) } -// MockcopilotClient is a mock of copilotClient interface. -type MockcopilotClient struct { +// MockCopilotClient is a mock of CopilotClient interface. +type MockCopilotClient struct { ctrl *gomock.Controller - recorder *MockcopilotClientMockRecorder + recorder *MockCopilotClientMockRecorder isgomock struct{} } -// MockcopilotClientMockRecorder is the mock recorder for MockcopilotClient. -type MockcopilotClientMockRecorder struct { - mock *MockcopilotClient +// MockCopilotClientMockRecorder is the mock recorder for MockCopilotClient. +type MockCopilotClientMockRecorder struct { + mock *MockCopilotClient } -// NewMockcopilotClient creates a new mock instance. -func NewMockcopilotClient(ctrl *gomock.Controller) *MockcopilotClient { - mock := &MockcopilotClient{ctrl: ctrl} - mock.recorder = &MockcopilotClientMockRecorder{mock} +// NewMockCopilotClient creates a new mock instance. +func NewMockCopilotClient(ctrl *gomock.Controller) *MockCopilotClient { + mock := &MockCopilotClient{ctrl: ctrl} + mock.recorder = &MockCopilotClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockcopilotClient) EXPECT() *MockcopilotClientMockRecorder { +func (m *MockCopilotClient) EXPECT() *MockCopilotClientMockRecorder { return m.recorder } // CreateSession mocks base method. -func (m *MockcopilotClient) CreateSession(ctx context.Context, config *copilot.SessionConfig) (CopilotSession, error) { +func (m *MockCopilotClient) CreateSession(ctx context.Context, config *copilot.SessionConfig) (CopilotSession, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateSession", ctx, config) ret0, _ := ret[0].(CopilotSession) @@ -132,13 +132,13 @@ func (m *MockcopilotClient) CreateSession(ctx context.Context, config *copilot.S } // CreateSession indicates an expected call of CreateSession. -func (mr *MockcopilotClientMockRecorder) CreateSession(ctx, config any) *gomock.Call { +func (mr *MockCopilotClientMockRecorder) CreateSession(ctx, config any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockcopilotClient)(nil).CreateSession), ctx, config) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSession", reflect.TypeOf((*MockCopilotClient)(nil).CreateSession), ctx, config) } // DeleteSession mocks base method. -func (m *MockcopilotClient) DeleteSession(ctx context.Context, sessionID string) error { +func (m *MockCopilotClient) DeleteSession(ctx context.Context, sessionID string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteSession", ctx, sessionID) ret0, _ := ret[0].(error) @@ -146,13 +146,28 @@ func (m *MockcopilotClient) DeleteSession(ctx context.Context, sessionID string) } // DeleteSession indicates an expected call of DeleteSession. -func (mr *MockcopilotClientMockRecorder) DeleteSession(ctx, sessionID any) *gomock.Call { +func (mr *MockCopilotClientMockRecorder) DeleteSession(ctx, sessionID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSession", reflect.TypeOf((*MockcopilotClient)(nil).DeleteSession), ctx, sessionID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSession", reflect.TypeOf((*MockCopilotClient)(nil).DeleteSession), ctx, sessionID) +} + +// GetAuthStatus mocks base method. +func (m *MockCopilotClient) GetAuthStatus(ctx context.Context) (*copilot.GetAuthStatusResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAuthStatus", ctx) + ret0, _ := ret[0].(*copilot.GetAuthStatusResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAuthStatus indicates an expected call of GetAuthStatus. +func (mr *MockCopilotClientMockRecorder) GetAuthStatus(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthStatus", reflect.TypeOf((*MockCopilotClient)(nil).GetAuthStatus), ctx) } // ResumeSessionWithOptions mocks base method. -func (m *MockcopilotClient) ResumeSessionWithOptions(ctx context.Context, sessionID string, config *copilot.ResumeSessionConfig) (CopilotSession, error) { +func (m *MockCopilotClient) ResumeSessionWithOptions(ctx context.Context, sessionID string, config *copilot.ResumeSessionConfig) (CopilotSession, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResumeSessionWithOptions", ctx, sessionID, config) ret0, _ := ret[0].(CopilotSession) @@ -161,13 +176,13 @@ func (m *MockcopilotClient) ResumeSessionWithOptions(ctx context.Context, sessio } // ResumeSessionWithOptions indicates an expected call of ResumeSessionWithOptions. -func (mr *MockcopilotClientMockRecorder) ResumeSessionWithOptions(ctx, sessionID, config any) *gomock.Call { +func (mr *MockCopilotClientMockRecorder) ResumeSessionWithOptions(ctx, sessionID, config any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResumeSessionWithOptions", reflect.TypeOf((*MockcopilotClient)(nil).ResumeSessionWithOptions), ctx, sessionID, config) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResumeSessionWithOptions", reflect.TypeOf((*MockCopilotClient)(nil).ResumeSessionWithOptions), ctx, sessionID, config) } // Start mocks base method. -func (m *MockcopilotClient) Start(ctx context.Context) error { +func (m *MockCopilotClient) Start(ctx context.Context) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Start", ctx) ret0, _ := ret[0].(error) @@ -175,13 +190,13 @@ func (m *MockcopilotClient) Start(ctx context.Context) error { } // Start indicates an expected call of Start. -func (mr *MockcopilotClientMockRecorder) Start(ctx any) *gomock.Call { +func (mr *MockCopilotClientMockRecorder) Start(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockcopilotClient)(nil).Start), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockCopilotClient)(nil).Start), ctx) } // Stop mocks base method. -func (m *MockcopilotClient) Stop() error { +func (m *MockCopilotClient) Stop() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Stop") ret0, _ := ret[0].(error) @@ -189,7 +204,7 @@ func (m *MockcopilotClient) Stop() error { } // Stop indicates an expected call of Stop. -func (mr *MockcopilotClientMockRecorder) Stop() *gomock.Call { +func (mr *MockCopilotClientMockRecorder) Stop() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockcopilotClient)(nil).Stop)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockCopilotClient)(nil).Stop)) } diff --git a/internal/execution/copilot_client_wrappers.go b/internal/execution/copilot_client_wrappers.go index 920c5a77..afe2675b 100644 --- a/internal/execution/copilot_client_wrappers.go +++ b/internal/execution/copilot_client_wrappers.go @@ -27,6 +27,9 @@ type CopilotClient interface { // CreateSession maps to [copilot.Client.CreateSession] CreateSession(ctx context.Context, config *copilot.SessionConfig) (CopilotSession, error) + // GetAuthStatus maps to [copilot.Client.GetAuthStatus] + GetAuthStatus(ctx context.Context) (*copilot.GetAuthStatusResponse, error) + // Start maps to [copilot.Client.Start] Start(ctx context.Context) error @@ -78,6 +81,10 @@ func (w *copilotClientWrapper) Stop() error { return w.inner.Stop() } +func (w *copilotClientWrapper) GetAuthStatus(ctx context.Context) (*copilot.GetAuthStatusResponse, error) { + return w.inner.GetAuthStatus(ctx) +} + func (w *copilotClientWrapper) DeleteSession(ctx context.Context, sessionID string) error { return w.inner.DeleteSession(ctx, sessionID) } diff --git a/internal/execution/copilot_engine_test.go b/internal/execution/copilot_engine_test.go index a3a34fef..8a896c30 100644 --- a/internal/execution/copilot_engine_test.go +++ b/internal/execution/copilot_engine_test.go @@ -49,7 +49,7 @@ func TestCopilotEngine_Execute_StartRespectsTimeout(t *testing.T) { t.Skip("Skipping - passing a context to copilot.Start causes copilot CLI to exit") ctrl := gomock.NewController(t) - clientMock := NewMockcopilotClient(ctrl) + clientMock := NewMockCopilotClient(ctrl) // Simulate a Start() that blocks until its context is canceled (mimicking // the copilot SDK hanging on the JSON-RPC Ping during protocol negotiation). @@ -79,9 +79,8 @@ func TestCopilotEngine_Execute_StartRespectsTimeout(t *testing.T) { func TestCopilotEngine_Execute_CreateSessionError(t *testing.T) { ctrl := gomock.NewController(t) - clientMock := NewMockcopilotClient(ctrl) + clientMock := newClientMock(ctrl) - clientMock.EXPECT().Start(gomock.Any()) clientMock.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(nil, errors.New("session create failed")) engine := NewCopilotEngineBuilder("test", &CopilotEngineBuilderOptions{ @@ -92,6 +91,11 @@ func TestCopilotEngine_Execute_CreateSessionError(t *testing.T) { require.NoError(t, engine.Initialize(context.Background())) + t.Cleanup(func() { + err := engine.Shutdown(context.Background()) + require.NoError(t, err) + }) + resp, err := engine.Execute(context.Background(), &ExecutionRequest{Message: "hello", Timeout: time.Second}) require.Error(t, err) assert.Nil(t, resp) @@ -100,11 +104,11 @@ func TestCopilotEngine_Execute_CreateSessionError(t *testing.T) { func TestCopilotEngine_Execute_SendError(t *testing.T) { ctrl := gomock.NewController(t) - clientMock := NewMockcopilotClient(ctrl) - sessionMock := NewMockcopilotSession(ctrl) + clientMock := newClientMock(ctrl) + sessionMock := NewMockCopilotSession(ctrl) - clientMock.EXPECT().Start(gomock.Any()) clientMock.EXPECT().CreateSession(gomock.Any(), gomock.Any()).Return(sessionMock, nil) + clientMock.EXPECT().DeleteSession(gomock.Any(), "session-1") sessionMock.EXPECT().On(gomock.Any()).Return(func() {}).AnyTimes() sessionMock.EXPECT().SessionID().Return("session-1") @@ -120,6 +124,11 @@ func TestCopilotEngine_Execute_SendError(t *testing.T) { err := engine.Initialize(context.Background()) require.NoError(t, err) + t.Cleanup(func() { + err := engine.Shutdown(context.Background()) + require.NoError(t, err) + }) + resp, err := engine.Execute(context.Background(), &ExecutionRequest{Message: "hello", Timeout: time.Second}) require.NoError(t, err) @@ -129,7 +138,7 @@ func TestCopilotEngine_Execute_SendError(t *testing.T) { func TestCopilotEngine_Shutdown_StopsClientAndCleansWorkspaces(t *testing.T) { ctrl := gomock.NewController(t) - clientMock := NewMockcopilotClient(ctrl) + clientMock := NewMockCopilotClient(ctrl) engine := NewCopilotEngineBuilder("test-model", &CopilotEngineBuilderOptions{ NewCopilotClient: func(clientOptions *copilot.ClientOptions) CopilotClient { return clientMock }, diff --git a/internal/execution/copilot_test.go b/internal/execution/copilot_test.go index bdfac4f9..b9d18a43 100644 --- a/internal/execution/copilot_test.go +++ b/internal/execution/copilot_test.go @@ -3,7 +3,10 @@ package execution import ( "context" "errors" + "fmt" + "math/rand" "os" + "strconv" "testing" "time" @@ -13,12 +16,12 @@ import ( "golang.org/x/sync/errgroup" ) -var enableCopilotTests = os.Getenv("ENABLE_COPILOT_TESTS") == "true" +var enableLiveCopilotTests = os.Getenv("ENABLE_COPILOT_TESTS") == "true" func TestCopilotNoSessionID(t *testing.T) { ctrl := gomock.NewController(t) - clientMock := NewMockcopilotClient(ctrl) - sessionMock := NewMockcopilotSession(ctrl) + clientMock := newClientMock(ctrl) + sessionMock := NewMockCopilotSession(ctrl) const expectedModel = "this-model-wins" @@ -37,11 +40,9 @@ func TestCopilotNoSessionID(t *testing.T) { }, } - clientMock.EXPECT().Start(gomock.Any()) clientMock.EXPECT().CreateSession(gomock.Any(), expectedConfig).Return(sessionMock, nil) sessionMock.EXPECT().Disconnect() clientMock.EXPECT().DeleteSession(gomock.Any(), "session-1") - clientMock.EXPECT().Stop() sessionMock.EXPECT().On(gomock.Any()).Times(3).Return(unregister) sessionMock.EXPECT().SendAndWait(gomock.Any(), gomock.Any()).Return(&copilot.SessionEvent{}, nil) @@ -79,8 +80,8 @@ func TestCopilotNoSessionID(t *testing.T) { func TestCopilotResumeSessionID(t *testing.T) { ctrl := gomock.NewController(t) - clientMock := NewMockcopilotClient(ctrl) - sessionMock := NewMockcopilotSession(ctrl) + clientMock := newClientMock(ctrl) + sessionMock := NewMockCopilotSession(ctrl) sourceDir, err := os.Getwd() require.NoError(t, err) @@ -95,11 +96,9 @@ func TestCopilotResumeSessionID(t *testing.T) { }, } - clientMock.EXPECT().Start(gomock.Any()) clientMock.EXPECT().ResumeSessionWithOptions(gomock.Any(), "session-1", expectedConfig).Return(sessionMock, nil) sessionMock.EXPECT().Disconnect() clientMock.EXPECT().DeleteSession(gomock.Any(), "session-1") - clientMock.EXPECT().Stop() sessionMock.EXPECT().On(gomock.Any()).Times(3).Return(func() {}) sessionMock.EXPECT().SendAndWait(gomock.Any(), gomock.Any()).Return(&copilot.SessionEvent{}, nil) @@ -131,10 +130,46 @@ func TestCopilotResumeSessionID(t *testing.T) { require.True(t, resp.Success) } +func TestCopilotResumeSessionID_Live(t *testing.T) { + skipIfCopilotNotEnabled(t) + + engine := NewCopilotEngineBuilder("", nil).Build() + + err := engine.Initialize(context.Background()) + require.NoError(t, err) + + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + err := engine.Shutdown(ctx) + require.NoError(t, err) + }) + + randIntAsStr := strconv.FormatInt(rand.Int63(), 10) + const timeout = time.Minute + + resp, err := engine.Execute(context.Background(), &ExecutionRequest{ + Message: fmt.Sprintf("Memorize this integer and echo it back to me: %s", randIntAsStr), + Timeout: timeout, + }) + require.NoError(t, err) + require.NotEmpty(t, resp.SessionID) + require.Contains(t, resp.FinalOutput, randIntAsStr) + + resp, err = engine.Execute(context.Background(), &ExecutionRequest{ + SessionID: resp.SessionID, + Message: "What number did I ask you to memorize?", + Timeout: timeout, + }) + require.NoError(t, err) + require.Contains(t, resp.FinalOutput, randIntAsStr) +} + func TestCopilotSendAndWaitReturnsErrorInResult(t *testing.T) { ctrl := gomock.NewController(t) - clientMock := NewMockcopilotClient(ctrl) - sessionMock := NewMockcopilotSession(ctrl) + clientMock := newClientMock(ctrl) + sessionMock := NewMockCopilotSession(ctrl) sourceDir := t.TempDir() const sessionErrorMsg = "session error occurred" @@ -149,11 +184,9 @@ func TestCopilotSendAndWaitReturnsErrorInResult(t *testing.T) { }, } - clientMock.EXPECT().Start(gomock.Any()) clientMock.EXPECT().CreateSession(gomock.Any(), expectedConfig).Return(sessionMock, nil) sessionMock.EXPECT().Disconnect() clientMock.EXPECT().DeleteSession(gomock.Any(), "session-1") - clientMock.EXPECT().Stop() sessionMock.EXPECT().On(gomock.Any()).Times(3).Return(func() {}) sessionMock.EXPECT().SendAndWait(gomock.Any(), gomock.Any()).Return(nil, errors.New(sessionErrorMsg)) @@ -183,7 +216,7 @@ func TestCopilotSendAndWaitReturnsErrorInResult(t *testing.T) { func TestCopilotExecute_RequiredFields(t *testing.T) { ctrl := gomock.NewController(t) - client := NewMockcopilotClient(ctrl) + client := NewMockCopilotClient(ctrl) // Start() should NOT be called when the request is invalid (e.g. Timeout == 0), // because extractReqParams now runs before startOnce.Do. @@ -214,7 +247,7 @@ func TestCopilotInitialize_PropagatesStartError(t *testing.T) { // Regression test: Initialize() must propagate Start() errors so callers see // copilot CLI startup failures instead of hanging or proceeding silently. ctrl := gomock.NewController(t) - clientMock := NewMockcopilotClient(ctrl) + clientMock := NewMockCopilotClient(ctrl) // Start returns an error, simulating a copilot CLI that fails to start. clientMock.EXPECT().Start(gomock.Any()).Return(errors.New("context canceled")) @@ -230,10 +263,8 @@ func TestCopilotInitialize_PropagatesStartError(t *testing.T) { require.ErrorContains(t, err, "copilot failed to start") } -func TestCopilotExecuteParallel(t *testing.T) { - if !enableCopilotTests { - t.Skip("ENABLE_COPILOT_TESTS must be set in order to run live copilot tests") - } +func TestCopilotExecuteParallel_Live(t *testing.T) { + skipIfCopilotNotEnabled(t) for range 5 { engine := NewCopilotEngineBuilder("gpt-4o-mini", nil).Build() @@ -262,6 +293,53 @@ func TestCopilotExecuteParallel(t *testing.T) { } } +func TestCopilotNotAuthenticated(t *testing.T) { + t.Run("not authenticated", func(t *testing.T) { + ctrl := gomock.NewController(t) + clientMock := NewMockCopilotClient(ctrl) + + clientMock.EXPECT().Start(gomock.Any()) + clientMock.EXPECT().GetAuthStatus(gomock.Any()).Times(1).Return(&copilot.GetAuthStatusResponse{ + IsAuthenticated: false, + }, nil) + + engine := NewCopilotEngineBuilder("gpt-4o-mini", &CopilotEngineBuilderOptions{ + NewCopilotClient: func(clientOptions *copilot.ClientOptions) CopilotClient { return clientMock }, + }).Build() + defer func() { + clientMock.EXPECT().Stop() + require.NoError(t, engine.Shutdown(context.Background())) + }() + + clientMock.EXPECT().Stop() + err := engine.Initialize(context.Background()) + require.Error(t, err) + require.ErrorContains(t, err, "not authenticated") + }) + + t.Run("error checking authentication status", func(t *testing.T) { + ctrl := gomock.NewController(t) + clientMock := NewMockCopilotClient(ctrl) + + // Start returns an error, simulating a copilot CLI that fails to start. + clientMock.EXPECT().Start(gomock.Any()) + clientMock.EXPECT().GetAuthStatus(gomock.Any()).Times(1).Return(nil, errors.New("auth status not available or something")) + + engine := NewCopilotEngineBuilder("gpt-4o-mini", &CopilotEngineBuilderOptions{ + NewCopilotClient: func(clientOptions *copilot.ClientOptions) CopilotClient { return clientMock }, + }).Build() + defer func() { + clientMock.EXPECT().Stop() + require.NoError(t, engine.Shutdown(context.Background())) + }() + + clientMock.EXPECT().Stop() // we fail in our init + err := engine.Initialize(context.Background()) + require.Error(t, err) + require.ErrorContains(t, err, "failed to get copilot authentication status") + }) +} + type sessionConfigMatcher struct { expected any sourceDir string @@ -322,3 +400,22 @@ func (m sessionConfigMatcher) Matches(x any) bool { func (m sessionConfigMatcher) String() string { return "" } + +func newClientMock(ctrl *gomock.Controller) *MockCopilotClient { + clientMock := NewMockCopilotClient(ctrl) + + // This is the basic sequence of calls that occurs anytime a copilot engine is initialized + clientMock.EXPECT().Start(gomock.Any()).Times(1) + clientMock.EXPECT().Stop().Times(1) + clientMock.EXPECT().GetAuthStatus(gomock.Any()).Return(&copilot.GetAuthStatusResponse{ + IsAuthenticated: true, + }, nil).Times(1) + + return clientMock +} + +func skipIfCopilotNotEnabled(t *testing.T) { + if !enableLiveCopilotTests { + t.Skip("ENABLE_COPILOT_TESTS must be set in order to run live copilot tests") + } +} diff --git a/internal/execution/generate.go b/internal/execution/generate.go index 8b1672f5..b0179c86 100644 --- a/internal/execution/generate.go +++ b/internal/execution/generate.go @@ -1,3 +1,3 @@ package execution -//go:generate go tool mockgen -package execution -destination copilot_client_wrapper_mocks_test.go . copilotSession,copilotClient +//go:generate go tool mockgen -package execution -destination copilot_client_wrapper_mocks_test.go . CopilotSession,CopilotClient