Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions .github/workflows/dotnet-sdk-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,9 @@ jobs:
- os: windows-latest
transport: default
shard: full
# TODO(cli-1.0.81-2): @github/copilot 1.0.81-2 never completes a model-driven
# turn when the runtime is hosted in-process against the CAPI backend. A full
# run reported 241 failures across 43 classes and took 150-225 minutes, which
# also starved the rest of the matrix. The defect is specific to this pairing:
# the in-process BYOK cells below and the stdio capi cells are unaffected.
# Drop these two entries once a fixed CLI build is picked up.
# TODO(cli-1.0.81-4): in-process CAPI model turns eventually stop
# completing and poison the shared runtime until the job times out.
# Stdio CAPI and in-process BYOK cells remain enabled.
- os: ubuntu-latest
transport: inprocess
- os: macos-latest
Expand Down
25,774 changes: 14,506 additions & 11,268 deletions dotnet/src/Generated/Rpc.cs

Large diffs are not rendered by default.

271 changes: 135 additions & 136 deletions dotnet/src/Generated/SessionEvents.cs

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions dotnet/test/E2E/RewindE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ await TestHelper.WaitForConditionAsync(
async () =>
{
rewindPoints = await session.Rpc.History.ListRewindPointsAsync();
return rewindPoints.UnavailableReason is null;
return rewindPoints.UnavailableReason is null
&& rewindPoints.Points.Count == 1
&& rewindPoints.Points[0].CanRestoreFiles
&& rewindPoints.Points[0].FileCount == 1;
},
timeout: TimeSpan.FromSeconds(10),
timeoutMessage: "Timed out waiting for rewind points to become available.",
timeoutMessage: "Timed out waiting for a restorable file rewind point.",
pollInterval: TimeSpan.FromMilliseconds(100));

Assert.NotNull(rewindPoints);
Expand Down
20 changes: 10 additions & 10 deletions dotnet/test/E2E/RpcSessionStateExtrasE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace GitHub.Copilot.Test.E2E;
/// <summary>
/// E2E coverage for session-scoped RPC methods that were previously untested:
/// completions, model.list, metadata.activity/context attribution/heaviest messages,
/// permissions.getAllowAll/setAllowAll, plan.readSqlTodos, provider.add,
/// permissions.getMode/setMode, plan.readSqlTodos, provider.add,
/// telemetry.getEngagementId, tools.getCurrentMetadata/updateSubagentSettings,
/// session visibility, and the session-scoped plugins.reload.
/// </summary>
Expand Down Expand Up @@ -158,22 +158,22 @@ public async Task Should_Get_And_Set_AllowAll_Permissions()

try
{
var initial = await session.Rpc.Permissions.GetAllowAllAsync();
Assert.False(initial.Enabled, "Allow-all should be disabled on a fresh session.");
var initial = await session.Rpc.Permissions.GetModeAsync();
Assert.Equal(PermissionMode.Manual, initial.Mode);

var enable = await session.Rpc.Permissions.SetAllowAllAsync(enabled: true);
var enable = await session.Rpc.Permissions.SetModeAsync(PermissionMode.AllowAll);
Assert.True(enable.Success);
Assert.True(enable.Enabled);
Assert.True((await session.Rpc.Permissions.GetAllowAllAsync()).Enabled);
Assert.Equal(PermissionMode.AllowAll, enable.Mode);
Assert.Equal(PermissionMode.AllowAll, (await session.Rpc.Permissions.GetModeAsync()).Mode);

var disable = await session.Rpc.Permissions.SetAllowAllAsync(enabled: false);
var disable = await session.Rpc.Permissions.SetModeAsync(PermissionMode.Manual);
Assert.True(disable.Success);
Assert.False(disable.Enabled);
Assert.False((await session.Rpc.Permissions.GetAllowAllAsync()).Enabled);
Assert.Equal(PermissionMode.Manual, disable.Mode);
Assert.Equal(PermissionMode.Manual, (await session.Rpc.Permissions.GetModeAsync()).Mode);
}
finally
{
await session.Rpc.Permissions.SetAllowAllAsync(enabled: false);
await session.Rpc.Permissions.SetModeAsync(PermissionMode.Manual);
}
}

Expand Down
8 changes: 4 additions & 4 deletions dotnet/test/E2E/RpcUiEphemeralQueryE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ namespace GitHub.Copilot.Test.E2E;
public class RpcUiEphemeralQueryE2ETests(E2ETestFixture fixture, ITestOutputHelper output)
: E2ETestBase(fixture, "rpc_ui_ephemeral_query", output)
{
// TODO(cli-1.0.81-2): CLI 1.0.81-2 fails session.ui.ephemeralQuery against the recorded
// snapshot ("Failed to get response from the AI model"). Re-enable once the runtime
// fix ships.
[Fact(Skip = "Blocked on CLI 1.0.81-2 session.ui.ephemeralQuery regression")]
// TODO(cli-1.0.81-2): CLI 1.0.81-4 still fails session.ui.ephemeralQuery against the
// recorded snapshot ("Failed to get response from the AI model"). Re-enable once the
// runtime fix ships.
[Fact(Skip = "Blocked on CLI 1.0.81-4 session.ui.ephemeralQuery regression")]
public async Task Should_Answer_Ephemeral_Query()
{
await using var session = await CreateSessionAsync();
Expand Down
4 changes: 3 additions & 1 deletion go/inprocess_disabled.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import "errors"

const inProcessAvailable = false

var errInProcessUnavailable = errors.New("in-process transport unavailable")

func createInProcessHost(string, inProcessHostConfig) (inProcessHost, error) {
return nil, errors.New("in-process transport unavailable")
return nil, errInProcessUnavailable
}
94 changes: 43 additions & 51 deletions go/internal/e2e/abort_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func TestAbortE2E(t *testing.T) {
var mu sync.Mutex
var events []copilot.SessionEvent
firstDelta := make(chan *copilot.AssistantMessageDeltaData, 1)
sessionIdle := make(chan struct{}, 1)

session.On(func(event copilot.SessionEvent) {
mu.Lock()
Expand All @@ -43,6 +44,12 @@ func TestAbortE2E(t *testing.T) {
default:
}
}
if _, ok := event.Data.(*copilot.SessionIdleData); ok {
select {
case sessionIdle <- struct{}{}:
default:
}
}
})

// Fire-and-forget — we'll abort before it finishes
Expand All @@ -67,6 +74,11 @@ func TestAbortE2E(t *testing.T) {
if err := session.Abort(t.Context()); err != nil {
t.Fatalf("Abort failed: %v", err)
}
select {
case <-sessionIdle:
case <-time.After(60 * time.Second):
t.Fatal("Timed out waiting for session to become idle after abort")
}

mu.Lock()
snapshot := make([]copilot.SessionEvent, len(events))
Expand All @@ -85,33 +97,15 @@ func TestAbortE2E(t *testing.T) {
t.Error("Expected at least one assistant.message_delta event before abort")
}

// Session should be usable after abort. Wait for the specific recovery
// message rather than racing against a late idle from the aborted turn.
recoveryReceived := make(chan *copilot.AssistantMessageData, 1)
session.On(func(event copilot.SessionEvent) {
if d, ok := event.Data.(*copilot.AssistantMessageData); ok {
if strings.Contains(strings.ToLower(d.Content), "abort_recovery_ok") {
select {
case recoveryReceived <- d:
default:
}
}
}
// Session should be usable after abort.
msg, err := session.SendAndWait(t.Context(), copilot.MessageOptions{
Prompt: "Say 'abort_recovery_ok'.",
})

go func() {
_, _ = session.Send(t.Context(), copilot.MessageOptions{
Prompt: "Say 'abort_recovery_ok'.",
})
}()

select {
case msg := <-recoveryReceived:
if !strings.Contains(strings.ToLower(msg.Content), "abort_recovery_ok") {
t.Errorf("Expected recovery message to contain 'abort_recovery_ok', got %q", msg.Content)
}
case <-time.After(60 * time.Second):
t.Fatal("Timed out waiting for recovery message after abort")
if err != nil {
t.Fatalf("Recovery SendAndWait failed after abort: %v", err)
}
if content := assistantContent(t, msg); !strings.Contains(strings.ToLower(content), "abort_recovery_ok") {
t.Errorf("Expected recovery message to contain 'abort_recovery_ok', got %q", content)
}
})

Expand Down Expand Up @@ -144,6 +138,16 @@ func TestAbortE2E(t *testing.T) {
}
t.Cleanup(func() { _ = session.Disconnect() })

sessionIdle := make(chan struct{}, 1)
session.On(func(event copilot.SessionEvent) {
if _, ok := event.Data.(*copilot.SessionIdleData); ok {
select {
case sessionIdle <- struct{}{}:
default:
}
}
})

// Fire-and-forget
go func() {
_, _ = session.Send(t.Context(), copilot.MessageOptions{
Expand Down Expand Up @@ -172,33 +176,21 @@ func TestAbortE2E(t *testing.T) {
case releaseTool <- "RELEASED_AFTER_ABORT":
default:
}

// Session should be usable after abort
recoveryReceived := make(chan *copilot.AssistantMessageData, 1)
session.On(func(event copilot.SessionEvent) {
if d, ok := event.Data.(*copilot.AssistantMessageData); ok {
if strings.Contains(d.Content, "tool_abort_recovery_ok") {
select {
case recoveryReceived <- d:
default:
}
}
}
})

go func() {
_, _ = session.Send(t.Context(), copilot.MessageOptions{
Prompt: "Say 'tool_abort_recovery_ok'.",
})
}()

select {
case msg := <-recoveryReceived:
if !strings.Contains(msg.Content, "tool_abort_recovery_ok") {
t.Errorf("Expected recovery message to contain 'tool_abort_recovery_ok', got %q", msg.Content)
}
case <-sessionIdle:
case <-time.After(60 * time.Second):
t.Fatal("Timed out waiting for recovery message after abort")
t.Fatal("Timed out waiting for session to become idle after tool abort")
}

// Session should be usable after abort.
msg, err := session.SendAndWait(t.Context(), copilot.MessageOptions{
Prompt: "Say 'tool_abort_recovery_ok'.",
})
if err != nil {
t.Fatalf("Recovery SendAndWait failed after tool abort: %v", err)
}
if content := assistantContent(t, msg); !strings.Contains(content, "tool_abort_recovery_ok") {
t.Errorf("Expected recovery message to contain 'tool_abort_recovery_ok', got %q", content)
}
})
}
42 changes: 21 additions & 21 deletions go/internal/e2e/rpc_session_state_extras_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,45 +70,45 @@ func TestRpcSessionStateExtras(t *testing.T) {
session := createPortedSession(t, client, nil)
defer session.Disconnect()
defer func() {
_, _ = session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(false)})
_, _ = session.RPC.Permissions.SetMode(t.Context(), &rpc.PermissionsSetModeRequest{Mode: rpc.PermissionModeManual})
}()

initial, err := session.RPC.Permissions.GetAllowAll(t.Context())
initial, err := session.RPC.Permissions.GetMode(t.Context())
if err != nil {
t.Fatalf("Permissions.GetAllowAll initial failed: %v", err)
t.Fatalf("Permissions.GetMode initial failed: %v", err)
}
if initial.Enabled {
t.Fatal("Allow-all should be disabled on a fresh session")
if initial.Mode != rpc.PermissionModeManual {
t.Fatalf("Expected manual mode on a fresh session, got %q", initial.Mode)
}

enable, err := session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(true)})
enable, err := session.RPC.Permissions.SetMode(t.Context(), &rpc.PermissionsSetModeRequest{Mode: rpc.PermissionModeAllowAll})
if err != nil {
t.Fatalf("Permissions.SetAllowAll(true) failed: %v", err)
t.Fatalf("Permissions.SetMode(allow-all) failed: %v", err)
}
if !enable.Success || !enable.Enabled {
t.Fatalf("Expected successful enable, got %+v", enable)
if !enable.Success || enable.Mode != rpc.PermissionModeAllowAll {
t.Fatalf("Expected successful allow-all mode change, got %+v", enable)
}
afterEnable, err := session.RPC.Permissions.GetAllowAll(t.Context())
afterEnable, err := session.RPC.Permissions.GetMode(t.Context())
if err != nil {
t.Fatalf("Permissions.GetAllowAll after enable failed: %v", err)
t.Fatalf("Permissions.GetMode after allow-all failed: %v", err)
}
if !afterEnable.Enabled {
t.Fatal("Expected allow-all to be enabled")
if afterEnable.Mode != rpc.PermissionModeAllowAll {
t.Fatalf("Expected allow-all mode, got %q", afterEnable.Mode)
}

disable, err := session.RPC.Permissions.SetAllowAll(t.Context(), &rpc.PermissionsSetAllowAllRequest{Enabled: copilot.Bool(false)})
disable, err := session.RPC.Permissions.SetMode(t.Context(), &rpc.PermissionsSetModeRequest{Mode: rpc.PermissionModeManual})
if err != nil {
t.Fatalf("Permissions.SetAllowAll(false) failed: %v", err)
t.Fatalf("Permissions.SetMode(manual) failed: %v", err)
}
if !disable.Success || disable.Enabled {
t.Fatalf("Expected successful disable, got %+v", disable)
if !disable.Success || disable.Mode != rpc.PermissionModeManual {
t.Fatalf("Expected successful manual mode change, got %+v", disable)
}
afterDisable, err := session.RPC.Permissions.GetAllowAll(t.Context())
afterDisable, err := session.RPC.Permissions.GetMode(t.Context())
if err != nil {
t.Fatalf("Permissions.GetAllowAll after disable failed: %v", err)
t.Fatalf("Permissions.GetMode after manual failed: %v", err)
}
if afterDisable.Enabled {
t.Fatal("Expected allow-all to be disabled")
if afterDisable.Mode != rpc.PermissionModeManual {
t.Fatalf("Expected manual mode, got %q", afterDisable.Mode)
}
})

Expand Down
4 changes: 2 additions & 2 deletions go/internal/e2e/rpc_ui_ephemeral_query_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ func TestRpcUiEphemeralQuery(t *testing.T) {
t.Cleanup(func() { client.ForceStop() })

t.Run("should_answer_ephemeral_query", func(t *testing.T) {
// TODO(cli-1.0.81-2): CLI 1.0.81-2 fails session.ui.ephemeralQuery against the
// TODO(cli-1.0.81-2): CLI 1.0.81-4 still fails session.ui.ephemeralQuery against the
// recorded snapshot ("Failed to get response from the AI model"). Re-enable once
// the runtime fix ships.
t.Skip("blocked on CLI 1.0.81-2 session.ui.ephemeralQuery regression")
t.Skip("blocked on CLI 1.0.81-4 session.ui.ephemeralQuery regression")

ctx.ConfigureForTest(t)
session := createPortedSession(t, client, nil)
Expand Down
Loading
Loading