Skip to content

Commit 4c232cd

Browse files
stephentoubCopilot
andcommitted
Adapt hand-written SDK code to CLI 1.0.76-0 wire changes
CI was red across all six SDKs after the @github/copilot bump. All failures were build/typecheck breakages from the regenerated wire types. 1. New `sessionFs.sqliteTransaction` RPC The CLI added `sessionFs.sqliteTransaction` to the SessionFs client handler interface, so every hand-written provider adapter stopped satisfying it (TS2741 / Go interface mismatch / ty invalid-return-type / CS0535). Rust dispatches by method name so it still compiled, but silently had no handler. Added a required `transaction` member to the SQLite provider interface in each language plus the adapter plumbing: - nodejs: `SessionFsSqliteStatement`, `SessionFsSqliteTransactionFailure`, `SessionFsSqliteProvider.transaction`, adapter `sqliteTransaction` - go: `SessionFSSqliteProvider.SqliteTransaction`, `SessionFSSqliteTransactionFailure` - python: `SessionFsSqliteProvider.sqlite_transaction`, `SessionFsSqliteTransactionFailure` - dotnet: `ISessionFsSqliteProvider.TransactionAsync`, `SessionFsSqliteStatement`, `SessionFsSqliteTransactionException` - rust: `SessionFsSqliteProvider::sqlite_transaction`, `SessionFsSqliteTransactionError`, dispatch arm Provider errors and "sqlite not supported" both surface as a *result-level* classified error (busyOrLocked / fatal / postCommitAmbiguous) rather than a transport error, since the runtime uses the class for retry decisions. In-repo test providers were updated with real BEGIN IMMEDIATE / COMMIT / ROLLBACK implementations. 2. Rust codegen bug for primitive RPC results `session.cancelAllBackgroundAgents` has an inline `{"type":"integer"}` result. `getResultTypeName()` named it `SessionCancelAllBackgroundAgentsResult`, but the type-emitting loops in scripts/codegen/rust.ts only handled enum/array/map/object schemas, so no alias was emitted (E0425). Added `rustScalarType` / `emitRustScalarAlias` and regenerated. 3. Java records gained components - `SessionOptionsUpdateParams`: `shell`, `eventsLogIncludesSubagents` - `SessionHistoryTruncateResult`: `checkpointCleanupFailed`, `checkpointCleanupError` - `AssistantMessageEventData`: `rte` Fixed the positional constructor call sites in CopilotClient and two tests. 4. Collateral wire changes - `permissions.resetSessionApprovals` now takes a params object (Go + Rust e2e call sites) - `UIExitPlanModeResponse` gained `defer_implementation` (Rust struct literal) Generated by Copilot Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e615d062-bcb7-431e-aa9c-d3e47405723a
1 parent ca24d1a commit 4c232cd

29 files changed

Lines changed: 910 additions & 97 deletions

dotnet/src/SessionFsProvider.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*--------------------------------------------------------------------------------------------*/
44

55
using GitHub.Copilot.Rpc;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Text.Json;
78

89
namespace GitHub.Copilot;
@@ -27,6 +28,23 @@ public sealed class SessionFsSqliteResult
2728
public long? LastInsertRowid { get; set; }
2829
}
2930

31+
/// <summary>
32+
/// One statement in an atomic SQLite transaction passed to
33+
/// <see cref="ISessionFsSqliteProvider.TransactionAsync"/>.
34+
/// </summary>
35+
[Experimental(Diagnostics.Experimental)]
36+
public sealed class SessionFsSqliteStatement
37+
{
38+
/// <summary>How to execute: <c>"exec"</c>, <c>"query"</c>, or <c>"run"</c>.</summary>
39+
public SessionFsSqliteQueryType QueryType { get; set; }
40+
41+
/// <summary>SQL statement to execute.</summary>
42+
public string Query { get; set; } = string.Empty;
43+
44+
/// <summary>Optional named bind parameters.</summary>
45+
public IDictionary<string, object?>? Params { get; set; }
46+
}
47+
3048
/// <summary>
3149
/// Optional interface for <see cref="SessionFsProvider"/> subclasses that support
3250
/// per-session SQLite databases. Implement this interface on your provider to enable
@@ -48,13 +66,53 @@ public interface ISessionFsSqliteProvider
4866
IDictionary<string, object?>? bindParams,
4967
CancellationToken cancellationToken);
5068

69+
/// <summary>
70+
/// Executes <paramref name="statements"/> atomically against the per-session database.
71+
/// </summary>
72+
/// <param name="statements">Statements to execute in order, inside a single transaction.</param>
73+
/// <param name="cancellationToken">Cancellation token.</param>
74+
/// <returns>One result per statement, in the same order as <paramref name="statements"/>.</returns>
75+
/// <exception cref="SessionFsSqliteTransactionException">
76+
/// Thrown to tell the runtime how the failure should be classified. Any other exception
77+
/// is reported as <see cref="SessionFsSqliteTransactionErrorClass.Fatal"/>.
78+
/// </exception>
79+
Task<IList<SessionFsSqliteResult>> TransactionAsync(
80+
IList<SessionFsSqliteStatement> statements,
81+
CancellationToken cancellationToken);
82+
5183
/// <summary>
5284
/// Checks whether the per-session SQLite database already exists, without creating it.
5385
/// </summary>
5486
/// <param name="cancellationToken">Cancellation token.</param>
5587
Task<bool> ExistsAsync(CancellationToken cancellationToken);
5688
}
5789

90+
/// <summary>
91+
/// Thrown by an <see cref="ISessionFsSqliteProvider"/> to classify a failed SQLite transaction.
92+
/// <see cref="SessionFsSqliteTransactionErrorClass.BusyOrLocked"/> guarantees the transaction
93+
/// rolled back and is safe to retry; <see cref="SessionFsSqliteTransactionErrorClass.PostCommitAmbiguous"/>
94+
/// must never be retried.
95+
/// </summary>
96+
[Experimental(Diagnostics.Experimental)]
97+
public sealed class SessionFsSqliteTransactionException : Exception
98+
{
99+
/// <summary>Initializes a new instance of the <see cref="SessionFsSqliteTransactionException"/> class.</summary>
100+
/// <param name="message">Human-readable failure description.</param>
101+
/// <param name="errorClass">How the runtime should classify the failure.</param>
102+
/// <param name="innerException">Optional underlying exception.</param>
103+
public SessionFsSqliteTransactionException(
104+
string message,
105+
SessionFsSqliteTransactionErrorClass errorClass,
106+
Exception? innerException = null)
107+
: base(message, innerException)
108+
{
109+
ErrorClass = errorClass;
110+
}
111+
112+
/// <summary>Gets the failure classification reported to the runtime.</summary>
113+
public SessionFsSqliteTransactionErrorClass ErrorClass { get; }
114+
}
115+
58116
/// <summary>
59117
/// Base class for session filesystem providers. Subclasses override the
60118
/// virtual methods and use normal C# patterns (return values, throw exceptions).
@@ -309,6 +367,64 @@ async Task<SessionFsSqliteQueryResult> ISessionFsHandler.SqliteQueryAsync(Sessio
309367
}
310368
}
311369

370+
async Task<SessionFsSqliteTransactionResult> ISessionFsHandler.SqliteTransactionAsync(SessionFsSqliteTransactionRequest request, CancellationToken cancellationToken)
371+
{
372+
if (this is not ISessionFsSqliteProvider sqliteProvider)
373+
{
374+
return new SessionFsSqliteTransactionResult
375+
{
376+
Error = new SessionFsSqliteTransactionError
377+
{
378+
ErrorClass = SessionFsSqliteTransactionErrorClass.Fatal,
379+
Message = "SQLite is not supported by this provider.",
380+
},
381+
};
382+
}
383+
384+
IList<SessionFsSqliteResult> results;
385+
try
386+
{
387+
var statements = request.Statements.Select(statement => new SessionFsSqliteStatement
388+
{
389+
QueryType = statement.QueryType,
390+
Query = statement.Query,
391+
Params = statement.Params?.ToDictionary(kvp => kvp.Key, kvp => JsonElementToValue(kvp.Value)),
392+
}).ToList();
393+
results = await sqliteProvider.TransactionAsync(statements, cancellationToken).ConfigureAwait(false);
394+
}
395+
catch (SessionFsSqliteTransactionException ex)
396+
{
397+
return new SessionFsSqliteTransactionResult
398+
{
399+
Error = new SessionFsSqliteTransactionError { ErrorClass = ex.ErrorClass, Message = ex.Message },
400+
};
401+
}
402+
catch (Exception ex)
403+
{
404+
return new SessionFsSqliteTransactionResult
405+
{
406+
Error = new SessionFsSqliteTransactionError
407+
{
408+
ErrorClass = SessionFsSqliteTransactionErrorClass.Fatal,
409+
Message = ex.Message,
410+
},
411+
};
412+
}
413+
414+
return new SessionFsSqliteTransactionResult
415+
{
416+
Results = results.Select(result => new SessionFsSqliteQueryResult
417+
{
418+
Rows = result.Rows?.Select(row => (IDictionary<string, JsonElement>)row.ToDictionary(
419+
kvp => kvp.Key,
420+
kvp => CopilotClient.ToJsonElementForWire(kvp.Value)!.Value)).ToList() ?? [],
421+
Columns = result.Columns ?? [],
422+
RowsAffected = result.RowsAffected,
423+
LastInsertRowid = result.LastInsertRowid,
424+
}).ToList(),
425+
};
426+
}
427+
312428
async Task<SessionFsSqliteExistsResult> ISessionFsHandler.SqliteExistsAsync(SessionFsSqliteExistsRequest request, CancellationToken cancellationToken)
313429
{
314430
if (this is not ISessionFsSqliteProvider sqliteProvider)

dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,68 @@ private SqliteConnection GetOrCreateDb()
4545
string query,
4646
IDictionary<string, object?>? bindParams,
4747
CancellationToken cancellationToken)
48+
{
49+
return Task.FromResult(RunStatement(GetOrCreateDb(), null, queryType, query, bindParams));
50+
}
51+
52+
public Task<IList<SessionFsSqliteResult>> TransactionAsync(
53+
IList<SessionFsSqliteStatement> statements,
54+
CancellationToken cancellationToken)
55+
{
56+
var db = GetOrCreateDb();
57+
using var transaction = db.BeginTransaction();
58+
try
59+
{
60+
IList<SessionFsSqliteResult> results = statements
61+
.Select(statement => RunStatement(db, transaction, statement.QueryType, statement.Query, statement.Params)
62+
?? new SessionFsSqliteResult())
63+
.ToList();
64+
transaction.Commit();
65+
return Task.FromResult(results);
66+
}
67+
catch (SqliteException ex)
68+
{
69+
transaction.Rollback();
70+
var errorClass = ex.SqliteErrorCode is 5 or 6
71+
? SessionFsSqliteTransactionErrorClass.BusyOrLocked
72+
: SessionFsSqliteTransactionErrorClass.Fatal;
73+
throw new SessionFsSqliteTransactionException(ex.Message, errorClass, ex);
74+
}
75+
catch (Exception ex)
76+
{
77+
transaction.Rollback();
78+
throw new SessionFsSqliteTransactionException(ex.Message, SessionFsSqliteTransactionErrorClass.Fatal, ex);
79+
}
80+
}
81+
82+
private SessionFsSqliteResult? RunStatement(
83+
SqliteConnection db,
84+
SqliteTransaction? transaction,
85+
SessionFsSqliteQueryType queryType,
86+
string query,
87+
IDictionary<string, object?>? bindParams)
4888
{
4989
sqliteCalls.Add(new SqliteCall(sessionId, queryType.Value, query));
5090

5191
var trimmed = query.Trim();
5292
if (trimmed.Length == 0)
5393
{
54-
return Task.FromResult<SessionFsSqliteResult?>(null);
94+
return null;
5595
}
5696

57-
var db = GetOrCreateDb();
58-
5997
if (queryType == SessionFsSqliteQueryType.Exec)
6098
{
6199
using var cmd = db.CreateCommand();
100+
cmd.Transaction = transaction;
62101
cmd.CommandText = trimmed;
63102
cmd.ExecuteNonQuery();
64-
return Task.FromResult<SessionFsSqliteResult?>(null);
103+
return null;
65104
}
66105

67106
if (queryType == SessionFsSqliteQueryType.Query)
68107
{
69108
using var cmd = db.CreateCommand();
109+
cmd.Transaction = transaction;
70110
cmd.CommandText = trimmed;
71111
AddParams(cmd, bindParams);
72112

@@ -88,33 +128,35 @@ private SqliteConnection GetOrCreateDb()
88128
rows.Add(row);
89129
}
90130

91-
return Task.FromResult<SessionFsSqliteResult?>(new SessionFsSqliteResult
131+
return new SessionFsSqliteResult
92132
{
93133
Columns = columns,
94134
Rows = rows,
95135
RowsAffected = 0,
96-
});
136+
};
97137
}
98138

99139
if (queryType == SessionFsSqliteQueryType.Run)
100140
{
101141
using var cmd = db.CreateCommand();
142+
cmd.Transaction = transaction;
102143
cmd.CommandText = trimmed;
103144
AddParams(cmd, bindParams);
104145

105146
var rowsAffected = cmd.ExecuteNonQuery();
106147

107148
using var rowidCmd = db.CreateCommand();
149+
rowidCmd.Transaction = transaction;
108150
rowidCmd.CommandText = "SELECT last_insert_rowid()";
109151
var lastRowid = rowidCmd.ExecuteScalar();
110152

111-
return Task.FromResult<SessionFsSqliteResult?>(new SessionFsSqliteResult
153+
return new SessionFsSqliteResult
112154
{
113155
Columns = [],
114156
Rows = [],
115157
RowsAffected = rowsAffected,
116158
LastInsertRowid = lastRowid is long l ? l : null,
117-
});
159+
};
118160
}
119161

120162
throw new ArgumentException($"Unknown queryType: {queryType}");

dotnet/test/E2E/SessionFsE2ETests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,9 @@ protected override Task RenameAsync(string src, string dest, CancellationToken c
616616
Task<SessionFsSqliteResult?> ISessionFsSqliteProvider.QueryAsync(SessionFsSqliteQueryType queryType, string query, IDictionary<string, object?>? bindParams, CancellationToken cancellationToken) =>
617617
Task.FromException<SessionFsSqliteResult?>(exception);
618618

619+
Task<IList<SessionFsSqliteResult>> ISessionFsSqliteProvider.TransactionAsync(IList<SessionFsSqliteStatement> statements, CancellationToken cancellationToken) =>
620+
Task.FromException<IList<SessionFsSqliteResult>>(exception);
621+
619622
Task<bool> ISessionFsSqliteProvider.ExistsAsync(CancellationToken cancellationToken) =>
620623
Task.FromException<bool>(exception);
621624
}

go/internal/e2e/rpc_session_state_e2e_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,7 @@ func TestRPCSessionStateE2E(t *testing.T) {
10831083
t.Errorf("Expected SetApproveAll(true) to succeed, got %+v", approve)
10841084
}
10851085

1086-
reset, err := session.RPC.Permissions.ResetSessionApprovals(t.Context())
1086+
reset, err := session.RPC.Permissions.ResetSessionApprovals(t.Context(), &rpc.PermissionsResetSessionApprovalsRequest{})
10871087
if err != nil {
10881088
t.Fatalf("Failed to call ResetSessionApprovals: %v", err)
10891089
}

go/internal/e2e/session_fs_sqlite_e2e_test.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -198,39 +198,55 @@ func (p *inMemorySqliteProvider) Rename(src string, dest string) error {
198198
func (p *inMemorySqliteProvider) SqliteQuery(queryType rpc.SessionFSSqliteQueryType, query string, params map[string]any) (*copilot.SessionFSSqliteQueryResult, error) {
199199
p.mu.Lock()
200200
defer p.mu.Unlock()
201+
return p.runQueryLocked(queryType, query), nil
202+
}
203+
204+
func (p *inMemorySqliteProvider) SqliteTransaction(statements []rpc.SessionFSSqliteTransactionStatement) ([]copilot.SessionFSSqliteQueryResult, error) {
205+
p.mu.Lock()
206+
defer p.mu.Unlock()
207+
results := make([]copilot.SessionFSSqliteQueryResult, 0, len(statements))
208+
for _, statement := range statements {
209+
results = append(results, *p.runQueryLocked(statement.QueryType, statement.Query))
210+
}
211+
return results, nil
212+
}
213+
214+
// runQueryLocked returns canned results based on query type. The agent doesn't
215+
// know or care whether a real SQLite database is behind this — it just receives
216+
// SQL tool results. These stubs return plausible responses so the agent can
217+
// proceed normally without pulling in a real SQLite dependency.
218+
//
219+
// Callers must hold p.mu.
220+
func (p *inMemorySqliteProvider) runQueryLocked(queryType rpc.SessionFSSqliteQueryType, query string) *copilot.SessionFSSqliteQueryResult {
201221
p.hadQuery = true
202222
*p.sqliteCalls = append(*p.sqliteCalls, sqliteCall{
203223
SessionID: p.sessionID,
204224
QueryType: string(queryType),
205225
Query: query,
206226
})
207227

208-
// Return canned results based on query type. The agent doesn't know or care
209-
// whether a real SQLite database is behind this — it just receives SQL tool
210-
// results. These stubs return plausible responses so the agent can proceed
211-
// normally without pulling in a real SQLite dependency.
212228
upper := strings.ToUpper(strings.TrimSpace(query))
213229
switch queryType {
214230
case rpc.SessionFSSqliteQueryTypeExec:
215-
return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}, nil
231+
return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}
216232
case rpc.SessionFSSqliteQueryTypeRun:
217233
lastID := int64(1)
218234
return &copilot.SessionFSSqliteQueryResult{
219235
Columns: []string{},
220236
Rows: []map[string]any{},
221237
RowsAffected: 1,
222238
LastInsertRowid: &lastID,
223-
}, nil
239+
}
224240
case rpc.SessionFSSqliteQueryTypeQuery:
225241
if strings.Contains(upper, "SELECT") {
226242
return &copilot.SessionFSSqliteQueryResult{
227243
Columns: []string{"id", "name"},
228244
Rows: []map[string]any{{"id": "a1", "name": "Widget"}},
229-
}, nil
245+
}
230246
}
231-
return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}, nil
247+
return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}
232248
}
233-
return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}, nil
249+
return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}
234250
}
235251

236252
func (p *inMemorySqliteProvider) SqliteExists() (bool, error) {

0 commit comments

Comments
 (0)