Skip to content
Open
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
4 changes: 2 additions & 2 deletions docs/features/image-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func main() {
Prompt: "Describe what you see in this image",
Attachments: []copilot.Attachment{
{
Type: copilot.File,
Type: copilot.AttachmentTypeFile,
Path: &path,
},
},
Expand All @@ -143,7 +143,7 @@ session.Send(ctx, copilot.MessageOptions{
Prompt: "Describe what you see in this image",
Attachments: []copilot.Attachment{
{
Type: copilot.File,
Type: copilot.AttachmentTypeFile,
Path: &path,
},
},
Expand Down
5 changes: 3 additions & 2 deletions dotnet/src/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,7 @@ public Task SetModelAsync(string model, CancellationToken cancellationToken = de
/// <param name="message">The message to log.</param>
/// <param name="level">Log level (default: info).</param>
/// <param name="ephemeral">When <c>true</c>, the message is not persisted to disk.</param>
/// <param name="url">Optional URL to associate with the log entry.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
/// <example>
/// <code>
Expand All @@ -758,9 +759,9 @@ public Task SetModelAsync(string model, CancellationToken cancellationToken = de
/// await session.LogAsync("Temporary status", ephemeral: true);
/// </code>
/// </example>
public async Task LogAsync(string message, SessionLogRequestLevel? level = null, bool? ephemeral = null, CancellationToken cancellationToken = default)
public async Task LogAsync(string message, SessionLogRequestLevel? level = null, bool? ephemeral = null, string? url = null, CancellationToken cancellationToken = default)
{
await Rpc.LogAsync(message, level, ephemeral, cancellationToken);
await Rpc.LogAsync(message, level, ephemeral, url, cancellationToken);
}

/// <summary>
Expand Down
260 changes: 130 additions & 130 deletions go/generated_session_events.go

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions go/internal/e2e/agent_and_compact_rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func TestAgentSelectionRpc(t *testing.T) {
}
})

t.Run("should return empty list when no custom agents configured", func(t *testing.T) {
t.Run("should return no custom agents when none configured", func(t *testing.T) {
client := copilot.NewClient(&copilot.ClientOptions{
CLIPath: cliPath,
UseStdio: copilot.Bool(true),
Expand All @@ -238,8 +238,13 @@ func TestAgentSelectionRpc(t *testing.T) {
t.Fatalf("Failed to list agents: %v", err)
}

if len(result.Agents) != 0 {
t.Errorf("Expected empty agent list, got %d agents", len(result.Agents))
// The CLI may return built-in/default agents even when no custom agents
// are configured, so just verify none of the known custom agent names appear.
customNames := map[string]bool{"test-agent": true, "another-agent": true}
for _, agent := range result.Agents {
if customNames[agent.Name] {
t.Errorf("Expected no custom agents, but found %q", agent.Name)
}
}

if err := client.Stop(); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions go/internal/e2e/compaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ func TestCompaction(t *testing.T) {
var compactionCompleteEvents []copilot.SessionEvent

session.On(func(event copilot.SessionEvent) {
if event.Type == copilot.SessionCompactionStart {
if event.Type == copilot.SessionEventTypeSessionCompactionStart {
compactionStartEvents = append(compactionStartEvents, event)
}
if event.Type == copilot.SessionCompactionComplete {
if event.Type == copilot.SessionEventTypeSessionCompactionComplete {
compactionCompleteEvents = append(compactionCompleteEvents, event)
}
})
Expand Down Expand Up @@ -105,7 +105,7 @@ func TestCompaction(t *testing.T) {

var compactionEvents []copilot.SessionEvent
session.On(func(event copilot.SessionEvent) {
if event.Type == copilot.SessionCompactionStart || event.Type == copilot.SessionCompactionComplete {
if event.Type == copilot.SessionEventTypeSessionCompactionStart || event.Type == copilot.SessionEventTypeSessionCompactionComplete {
compactionEvents = append(compactionEvents, event)
}
})
Expand Down
26 changes: 13 additions & 13 deletions go/internal/e2e/multi_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,27 +79,27 @@ func TestMultiClient(t *testing.T) {
client2Completed := make(chan struct{}, 1)

session1.On(func(event copilot.SessionEvent) {
if event.Type == copilot.ExternalToolRequested {
if event.Type == copilot.SessionEventTypeExternalToolRequested {
select {
case client1Requested <- struct{}{}:
default:
}
}
if event.Type == copilot.ExternalToolCompleted {
if event.Type == copilot.SessionEventTypeExternalToolCompleted {
select {
case client1Completed <- struct{}{}:
default:
}
}
})
session2.On(func(event copilot.SessionEvent) {
if event.Type == copilot.ExternalToolRequested {
if event.Type == copilot.SessionEventTypeExternalToolRequested {
select {
case client2Requested <- struct{}{}:
default:
}
}
if event.Type == copilot.ExternalToolCompleted {
if event.Type == copilot.SessionEventTypeExternalToolCompleted {
select {
case client2Completed <- struct{}{}:
default:
Expand All @@ -120,7 +120,7 @@ func TestMultiClient(t *testing.T) {
}

// Wait for all broadcast events to arrive on both clients
timeout := time.After(10 * time.Second)
timeout := time.After(30 * time.Second)
for _, ch := range []chan struct{}{client1Requested, client2Requested, client1Completed, client2Completed} {
select {
case <-ch:
Expand Down Expand Up @@ -197,10 +197,10 @@ func TestMultiClient(t *testing.T) {

// Both clients should have seen permission.requested events
mu1.Lock()
c1PermRequested := filterEventsByType(client1Events, copilot.PermissionRequested)
c1PermRequested := filterEventsByType(client1Events, copilot.SessionEventTypePermissionRequested)
mu1.Unlock()
mu2.Lock()
c2PermRequested := filterEventsByType(client2Events, copilot.PermissionRequested)
c2PermRequested := filterEventsByType(client2Events, copilot.SessionEventTypePermissionRequested)
mu2.Unlock()

if len(c1PermRequested) == 0 {
Expand All @@ -212,10 +212,10 @@ func TestMultiClient(t *testing.T) {

// Both clients should have seen permission.completed events with approved result
mu1.Lock()
c1PermCompleted := filterEventsByType(client1Events, copilot.PermissionCompleted)
c1PermCompleted := filterEventsByType(client1Events, copilot.SessionEventTypePermissionCompleted)
mu1.Unlock()
mu2.Lock()
c2PermCompleted := filterEventsByType(client2Events, copilot.PermissionCompleted)
c2PermCompleted := filterEventsByType(client2Events, copilot.SessionEventTypePermissionCompleted)
mu2.Unlock()

if len(c1PermCompleted) == 0 {
Expand Down Expand Up @@ -293,10 +293,10 @@ func TestMultiClient(t *testing.T) {

// Both clients should have seen permission.requested events
mu1.Lock()
c1PermRequested := filterEventsByType(client1Events, copilot.PermissionRequested)
c1PermRequested := filterEventsByType(client1Events, copilot.SessionEventTypePermissionRequested)
mu1.Unlock()
mu2.Lock()
c2PermRequested := filterEventsByType(client2Events, copilot.PermissionRequested)
c2PermRequested := filterEventsByType(client2Events, copilot.SessionEventTypePermissionRequested)
mu2.Unlock()

if len(c1PermRequested) == 0 {
Expand All @@ -308,10 +308,10 @@ func TestMultiClient(t *testing.T) {

// Both clients should see the denial in the completed event
mu1.Lock()
c1PermCompleted := filterEventsByType(client1Events, copilot.PermissionCompleted)
c1PermCompleted := filterEventsByType(client1Events, copilot.SessionEventTypePermissionCompleted)
mu1.Unlock()
mu2.Lock()
c2PermCompleted := filterEventsByType(client2Events, copilot.PermissionCompleted)
c2PermCompleted := filterEventsByType(client2Events, copilot.SessionEventTypePermissionCompleted)
mu2.Unlock()

if len(c1PermCompleted) == 0 {
Expand Down
4 changes: 2 additions & 2 deletions go/internal/e2e/permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestPermissions(t *testing.T) {
permissionDenied := false

session.On(func(event copilot.SessionEvent) {
if event.Type == copilot.ToolExecutionComplete &&
if event.Type == copilot.SessionEventTypeToolExecutionComplete &&
event.Data.Success != nil && !*event.Data.Success &&
event.Data.Error != nil && event.Data.Error.ErrorClass != nil &&
strings.Contains(event.Data.Error.ErrorClass.Message, "Permission denied") {
Expand Down Expand Up @@ -223,7 +223,7 @@ func TestPermissions(t *testing.T) {
permissionDenied := false

session2.On(func(event copilot.SessionEvent) {
if event.Type == copilot.ToolExecutionComplete &&
if event.Type == copilot.SessionEventTypeToolExecutionComplete &&
event.Data.Success != nil && !*event.Data.Success &&
event.Data.Error != nil && event.Data.Error.ErrorClass != nil &&
strings.Contains(event.Data.Error.ErrorClass.Message, "Permission denied") {
Expand Down
12 changes: 6 additions & 6 deletions go/internal/e2e/rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,16 @@ func TestSessionRpc(t *testing.T) {
if err != nil {
t.Fatalf("Failed to get mode: %v", err)
}
if initial.Mode != rpc.Interactive {
if initial.Mode != rpc.ModeInteractive {
t.Errorf("Expected initial mode 'interactive', got %q", initial.Mode)
}

// Switch to plan mode
planResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.Plan})
planResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.ModePlan})
if err != nil {
t.Fatalf("Failed to set mode to plan: %v", err)
}
if planResult.Mode != rpc.Plan {
if planResult.Mode != rpc.ModePlan {
t.Errorf("Expected mode 'plan', got %q", planResult.Mode)
}

Expand All @@ -237,16 +237,16 @@ func TestSessionRpc(t *testing.T) {
if err != nil {
t.Fatalf("Failed to get mode after plan: %v", err)
}
if afterPlan.Mode != rpc.Plan {
if afterPlan.Mode != rpc.ModePlan {
t.Errorf("Expected mode 'plan' after set, got %q", afterPlan.Mode)
}

// Switch back to interactive
interactiveResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.Interactive})
interactiveResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.ModeInteractive})
if err != nil {
t.Fatalf("Failed to set mode to interactive: %v", err)
}
if interactiveResult.Mode != rpc.Interactive {
if interactiveResult.Mode != rpc.ModeInteractive {
t.Errorf("Expected mode 'interactive', got %q", interactiveResult.Mode)
}
})
Expand Down
20 changes: 10 additions & 10 deletions go/internal/e2e/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ func TestSession(t *testing.T) {
toolStartCh := make(chan *copilot.SessionEvent, 1)
toolStartErrCh := make(chan error, 1)
go func() {
evt, err := testharness.GetNextEventOfType(session, copilot.ToolExecutionStart, 60*time.Second)
evt, err := testharness.GetNextEventOfType(session, copilot.SessionEventTypeToolExecutionStart, 60*time.Second)
if err != nil {
toolStartErrCh <- err
} else {
Expand All @@ -517,7 +517,7 @@ func TestSession(t *testing.T) {
sessionIdleCh := make(chan *copilot.SessionEvent, 1)
sessionIdleErrCh := make(chan error, 1)
go func() {
evt, err := testharness.GetNextEventOfType(session, copilot.SessionIdle, 60*time.Second)
evt, err := testharness.GetNextEventOfType(session, copilot.SessionEventTypeSessionIdle, 60*time.Second)
if err != nil {
sessionIdleErrCh <- err
} else {
Expand Down Expand Up @@ -565,7 +565,7 @@ func TestSession(t *testing.T) {
// Verify messages contain an abort event
hasAbortEvent := false
for _, msg := range messages {
if msg.Type == copilot.Abort {
if msg.Type == copilot.SessionEventTypeAbort {
hasAbortEvent = true
break
}
Expand Down Expand Up @@ -913,7 +913,7 @@ func TestSetModelWithReasoningEffort(t *testing.T) {

modelChanged := make(chan copilot.SessionEvent, 1)
session.On(func(event copilot.SessionEvent) {
if event.Type == copilot.SessionModelChange {
if event.Type == copilot.SessionEventTypeSessionModelChange {
select {
case modelChanged <- event:
default:
Expand Down Expand Up @@ -986,7 +986,7 @@ func TestSessionLog(t *testing.T) {
t.Fatalf("Log failed: %v", err)
}

evt := waitForEvent(t, &mu, &events, copilot.SessionInfo, "Info message", 5*time.Second)
evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionInfo, "Info message", 5*time.Second)
if evt.Data.InfoType == nil || *evt.Data.InfoType != "notification" {
t.Errorf("Expected infoType 'notification', got %v", evt.Data.InfoType)
}
Expand All @@ -996,11 +996,11 @@ func TestSessionLog(t *testing.T) {
})

t.Run("should log warning message", func(t *testing.T) {
if err := session.Log(t.Context(), "Warning message", &copilot.LogOptions{Level: rpc.Warning}); err != nil {
if err := session.Log(t.Context(), "Warning message", &copilot.LogOptions{Level: rpc.LevelWarning}); err != nil {
t.Fatalf("Log failed: %v", err)
}

evt := waitForEvent(t, &mu, &events, copilot.SessionWarning, "Warning message", 5*time.Second)
evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionWarning, "Warning message", 5*time.Second)
if evt.Data.WarningType == nil || *evt.Data.WarningType != "notification" {
t.Errorf("Expected warningType 'notification', got %v", evt.Data.WarningType)
}
Expand All @@ -1010,11 +1010,11 @@ func TestSessionLog(t *testing.T) {
})

t.Run("should log error message", func(t *testing.T) {
if err := session.Log(t.Context(), "Error message", &copilot.LogOptions{Level: rpc.Error}); err != nil {
if err := session.Log(t.Context(), "Error message", &copilot.LogOptions{Level: rpc.LevelError}); err != nil {
t.Fatalf("Log failed: %v", err)
}

evt := waitForEvent(t, &mu, &events, copilot.SessionError, "Error message", 5*time.Second)
evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionError, "Error message", 5*time.Second)
if evt.Data.ErrorType == nil || *evt.Data.ErrorType != "notification" {
t.Errorf("Expected errorType 'notification', got %v", evt.Data.ErrorType)
}
Expand All @@ -1028,7 +1028,7 @@ func TestSessionLog(t *testing.T) {
t.Fatalf("Log failed: %v", err)
}

evt := waitForEvent(t, &mu, &events, copilot.SessionInfo, "Ephemeral message", 5*time.Second)
evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionInfo, "Ephemeral message", 5*time.Second)
if evt.Data.InfoType == nil || *evt.Data.InfoType != "notification" {
t.Errorf("Expected infoType 'notification', got %v", evt.Data.InfoType)
}
Expand Down
2 changes: 1 addition & 1 deletion go/internal/e2e/testharness/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func GetNextEventOfType(session *copilot.Session, eventType copilot.SessionEvent
case result <- &event:
default:
}
case copilot.SessionError:
case copilot.SessionEventTypeSessionError:
msg := "session error"
if event.Data.Message != nil {
msg = *event.Data.Message
Expand Down
Loading
Loading