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
2 changes: 1 addition & 1 deletion packages/core/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type ApiClientOptions = {
};

export type ApiRequestOptions = {
method?: 'GET' | 'POST';
method?: 'GET' | 'POST' | 'DELETE';
body?: Record<string, unknown>;
query?: Record<string, any>;
headers?: Record<string, string>;
Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
// 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.
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,24 @@ export interface SessionClient {
*/
unarchive(): Promise<void>;

/**
* 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<void>;

/**
* 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.
Expand Down
84 changes: 84 additions & 0 deletions packages/core/tests/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down