diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index e6446d3..80f8368 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -39,7 +39,7 @@ export type ApiClientOptions = { }; export type ApiRequestOptions = { - method?: 'GET' | 'POST'; + method?: 'GET' | 'POST' | 'DELETE'; body?: Record; query?: Record; headers?: Record; diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 41d8163..c43b307 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -370,6 +370,37 @@ export class SessionClientImpl implements SessionClient { } } + /** + * Deletes the session permanently. + * This removes the session from both the Jules API and the local cache. + * Once deleted, the session cannot be recovered. + * + * **Side Effects:** + * - Sends a DELETE request to `sessions/{id}`. + * - Removes the session from the local cache. + * + * **Error Handling:** + * - If the API call fails, the local cache is NOT modified. + * - If the session is not found (404), the local cache is still cleaned up. + */ + async delete(): Promise { + // Step 1: Call the API to delete the session + try { + await this.request(`sessions/${this.id}`, { + method: 'DELETE', + }); + } catch (error: any) { + // Re-throw all errors except 404 (session already deleted) + if (error.status !== 404) { + throw error; + } + // If 404, continue to clean up local cache + } + + // Step 2: Clean up local cache (only if API succeeded or returned 404) + await this.sessionStorage.delete(this.id); + } + /** * Retrieves the latest state of the underlying session resource. * Implements "Iceberg" Read-Through caching. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 5f9c7cb..de31e5b 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1202,6 +1202,24 @@ export interface SessionClient { */ unarchive(): Promise; + /** + * Deletes the session permanently. + * This removes the session from both the Jules API and the local cache. + * Once deleted, the session cannot be recovered. + * + * **Side Effects:** + * - Sends a DELETE request to `sessions/{id}`. + * - Removes the session from the local cache. + * + * **Error Handling:** + * - If the API call fails, the local cache is NOT modified. + * - If the session is not found (404), the local cache is still cleaned up. + * + * @example + * await session.delete(); + */ + delete(): Promise; + /** * Creates a point-in-time snapshot of the session with all activities loaded and derived analytics computed. * This is a network operation with cache heuristics. diff --git a/packages/core/tests/session.test.ts b/packages/core/tests/session.test.ts index 0439ef0..5634ace 100644 --- a/packages/core/tests/session.test.ts +++ b/packages/core/tests/session.test.ts @@ -338,6 +338,90 @@ describe('SessionClient', () => { }); }); + describe('delete()', () => { + it('should call the delete API endpoint and remove from cache', async () => { + let localDeleteCalled = false; + let cacheDeleted = false; + + server.use( + http.delete( + 'https://jules.googleapis.com/v1alpha/sessions/SESSION_123', + () => { + localDeleteCalled = true; + return HttpResponse.json({}); + }, + ), + ); + + // Mock storage delete to verify it's called + const storage = (session as any).sessionStorage; + const originalDelete = storage.delete; + storage.delete = async (sessionId: string) => { + cacheDeleted = true; + return originalDelete.call(storage, sessionId); + }; + + await session.delete(); + + expect(localDeleteCalled).toBe(true); + expect(cacheDeleted).toBe(true); + }); + + it('should clean up local cache even if API returns 404', async () => { + let cacheDeleted = false; + + server.use( + http.delete( + 'https://jules.googleapis.com/v1alpha/sessions/SESSION_123', + () => { + return HttpResponse.json( + { error: { message: 'Session not found' } }, + { status: 404 }, + ); + }, + ), + ); + + const storage = (session as any).sessionStorage; + const originalDelete = storage.delete; + storage.delete = async (sessionId: string) => { + cacheDeleted = true; + return originalDelete.call(storage, sessionId); + }; + + // Should not throw + await session.delete(); + + expect(cacheDeleted).toBe(true); + }); + + it('should throw error and NOT clean up cache if API fails with non-404 status', async () => { + let cacheDeleted = false; + + server.use( + http.delete( + 'https://jules.googleapis.com/v1alpha/sessions/SESSION_123', + () => { + return HttpResponse.json( + { error: { message: 'Permission denied' } }, + { status: 403 }, + ); + }, + ), + ); + + const storage = (session as any).sessionStorage; + const originalDelete = storage.delete; + storage.delete = async (sessionId: string) => { + cacheDeleted = true; + return originalDelete.call(storage, sessionId); + }; + + await expect(session.delete()).rejects.toThrow(); + expect(cacheDeleted).toBe(false); + }); + }); + describe('ask()', () => { it('should send a message and return the corresponding reply', async () => { const startTime = new Date();