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
16 changes: 8 additions & 8 deletions dotnet/src/Generated/Rpc.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2736,6 +2736,10 @@ public sealed class SessionMetadata
/// </summary>
public string? Summary { get; set; }
/// <summary>
/// Identifier of the client driving the session.
/// </summary>
public string? ClientName { get; set; }
/// <summary>
/// Whether the session is running on a remote server.
/// </summary>
public bool IsRemote { get; set; }
Expand Down
24 changes: 24 additions & 0 deletions dotnet/test/Unit/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,30 @@ public void ResumeSessionRequest_CanSerializeInstructionDirectories_WithSdkOptio
Assert.Equal("C:\\resume-instructions", root.GetProperty("instructionDirectories")[0].GetString());
}

[Fact]
public void SessionMetadata_CanRoundTripClientName_WithSdkOptions()
{
var options = GetSerializerOptions();
var original = new SessionMetadata
{
SessionId = "session-1",
StartTime = DateTimeOffset.Parse("2025-01-01T00:00:00Z"),
ModifiedTime = DateTimeOffset.Parse("2025-01-01T01:00:00Z"),
Summary = "loaded session",
ClientName = "my-app",
IsRemote = false
};

var json = JsonSerializer.Serialize(original, options);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
Assert.Equal("my-app", root.GetProperty("clientName").GetString());

var deserialized = JsonSerializer.Deserialize<SessionMetadata>(json, options);
Assert.NotNull(deserialized);
Assert.Equal("my-app", deserialized.ClientName);
}

[Fact]
public void CreateSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptions()
{
Expand Down
27 changes: 27 additions & 0 deletions go/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,33 @@ func TestResumeSessionRequest_ClientName(t *testing.T) {
})
}

func TestSessionMetadata_ClientName(t *testing.T) {
t.Run("round-trips clientName in JSON", func(t *testing.T) {
var metadata SessionMetadata
if err := json.Unmarshal([]byte(`{
"sessionId":"s1",
"startTime":"2025-01-01T00:00:00Z",
"modifiedTime":"2025-01-01T01:00:00Z",
"summary":"loaded session",
"clientName":"my-app",
"isRemote":false
}`), &metadata); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if metadata.ClientName == nil || *metadata.ClientName != "my-app" {
t.Fatalf("Expected clientName to be my-app, got %v", metadata.ClientName)
}

data, err := json.Marshal(metadata)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
if !strings.Contains(string(data), `"clientName":"my-app"`) {
t.Fatalf("Expected marshaled JSON to include clientName, got %s", string(data))
}
})
}

func TestCreateSessionRequest_Agent(t *testing.T) {
t.Run("includes agent in JSON when set", func(t *testing.T) {
req := createSessionRequest{Agent: "test-agent"}
Expand Down
5 changes: 5 additions & 0 deletions go/rpc/zrpc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,7 @@ type SessionMetadata struct {
StartTime time.Time `json:"startTime"`
ModifiedTime time.Time `json:"modifiedTime"`
Summary *string `json:"summary,omitempty"`
ClientName *string `json:"clientName,omitempty"`
IsRemote bool `json:"isRemote"`
Context *SessionContext `json:"context,omitempty"`
}
Expand Down
22 changes: 22 additions & 0 deletions java/src/main/java/com/github/copilot/rpc/SessionMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class SessionMetadata {
@JsonProperty("summary")
private String summary;

@JsonProperty("clientName")
private String clientName;
Comment thread
Davsterl marked this conversation as resolved.

@JsonProperty("isRemote")
private boolean isRemote;

Expand Down Expand Up @@ -130,6 +133,25 @@ public void setSummary(String summary) {
this.summary = summary;
}

/**
* Gets the identifier of the client driving the session.
*
* @return the client name, or {@code null} if not available
*/
public String getClientName() {
return clientName;
}

/**
* Sets the client identifier for the session.
*
* @param clientName
* the client name
*/
public void setClientName(String clientName) {
this.clientName = clientName;
}

/**
* Returns whether this session is stored remotely.
*
Expand Down
3 changes: 3 additions & 0 deletions java/src/test/java/com/github/copilot/ModelInfoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ void sessionMetadataGettersAndSetters() {
assertNull(meta.getStartTime());
assertNull(meta.getModifiedTime());
assertNull(meta.getSummary());
assertNull(meta.getClientName());
assertFalse(meta.isRemote());

meta.setClientName("my-app");
assertEquals("my-app", meta.getClientName());
meta.setRemote(true);
assertTrue(meta.isRemote());
}
Expand Down
4 changes: 4 additions & 0 deletions nodejs/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,7 @@ export class CopilotClient {
startTime: string;
modifiedTime: string;
summary?: string;
clientName?: string;
isRemote: boolean;
context?: { cwd: string; gitRoot?: string; repository?: string; branch?: string };
}>;
Expand Down Expand Up @@ -1354,6 +1355,7 @@ export class CopilotClient {
startTime: string;
modifiedTime: string;
summary?: string;
clientName?: string;
isRemote: boolean;
context?: { cwd: string; gitRoot?: string; repository?: string; branch?: string };
};
Expand All @@ -1371,6 +1373,7 @@ export class CopilotClient {
startTime: string;
modifiedTime: string;
summary?: string;
clientName?: string;
isRemote: boolean;
context?: { cwd: string; gitRoot?: string; repository?: string; branch?: string };
}): SessionMetadata {
Expand All @@ -1380,6 +1383,7 @@ export class CopilotClient {
startTime: new Date(raw.startTime),
modifiedTime: new Date(raw.modifiedTime),
summary: raw.summary,
clientName: raw.clientName,
isRemote: raw.isRemote,
context: context
? {
Expand Down
9 changes: 9 additions & 0 deletions nodejs/src/generated/rpc.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions nodejs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,8 @@ export interface SessionMetadata {
startTime: Date;
modifiedTime: Date;
summary?: string;
/** Identifier of the client driving the session */
clientName?: string;
isRemote: boolean;
/** Working directory context (working directory, git info) from session creation */
context?: SessionContext;
Expand Down
74 changes: 74 additions & 0 deletions nodejs/test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,80 @@ describe("CopilotClient", () => {
spy.mockRestore();
});

it("maps clientName from session.list responses", async () => {
const client = new CopilotClient();
await client.start();
onTestFinished(() => client.forceStop());

vi.spyOn((client as any).connection!, "sendRequest").mockImplementation(
async (method: string) => {
if (method === "session.list") {
return {
sessions: [
{
sessionId: "session-1",
startTime: "2025-01-01T00:00:00Z",
modifiedTime: "2025-01-01T01:00:00Z",
summary: "test session",
clientName: "my-app",
isRemote: false,
},
],
};
}
throw new Error(`Unexpected method: ${method}`);
}
);

const sessions = await client.listSessions();

expect(sessions).toEqual([
expect.objectContaining({
sessionId: "session-1",
clientName: "my-app",
summary: "test session",
}),
]);
expect(sessions[0].startTime).toBeInstanceOf(Date);
expect(sessions[0].modifiedTime).toBeInstanceOf(Date);
});

it("maps clientName from session.getMetadata responses", async () => {
const client = new CopilotClient();
await client.start();
onTestFinished(() => client.forceStop());

vi.spyOn((client as any).connection!, "sendRequest").mockImplementation(
async (method: string) => {
if (method === "session.getMetadata") {
return {
session: {
sessionId: "session-1",
startTime: "2025-01-01T00:00:00Z",
modifiedTime: "2025-01-01T01:00:00Z",
summary: "loaded session",
clientName: "my-app",
isRemote: false,
},
};
}
throw new Error(`Unexpected method: ${method}`);
}
);

const metadata = await client.getSessionMetadata("session-1");

expect(metadata).toEqual(
expect.objectContaining({
sessionId: "session-1",
clientName: "my-app",
summary: "loaded session",
})
);
expect(metadata?.startTime).toBeInstanceOf(Date);
expect(metadata?.modifiedTime).toBeInstanceOf(Date);
});

it("forwards enableSessionTelemetry in session.create request", async () => {
const client = new CopilotClient();
await client.start();
Expand Down
5 changes: 5 additions & 0 deletions python/copilot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ class SessionMetadata:
modified_time: datetime # Timestamp when session was last modified
is_remote: bool # Whether the session is remote
summary: str | None = None # Optional summary of the session
client_name: str | None = None # Identifier of the client driving the session
context: SessionContext | None = None # Working directory context

@staticmethod
Expand All @@ -795,6 +796,7 @@ def from_dict(obj: Any) -> SessionMetadata:
f"startTime={start_time}, modifiedTime={modified_time}, isRemote={is_remote}"
)
summary = obj.get("summary")
client_name = obj.get("clientName")
context_dict = obj.get("context")
context = SessionContext.from_dict(context_dict) if context_dict else None
return SessionMetadata(
Expand All @@ -803,6 +805,7 @@ def from_dict(obj: Any) -> SessionMetadata:
modified_time=_parse_session_timestamp(modified_time),
is_remote=bool(is_remote),
summary=summary,
client_name=client_name,
context=context,
)

Expand All @@ -814,6 +817,8 @@ def to_dict(self) -> dict:
result["isRemote"] = self.is_remote
if self.summary is not None:
result["summary"] = self.summary
if self.client_name is not None:
result["clientName"] = self.client_name
if self.context is not None:
result["context"] = self.context.to_dict()
return result
Expand Down
Loading
Loading