From a936e2859546ed7b87a5439aff6a21e20a262a30 Mon Sep 17 00:00:00 2001 From: AgentSeal Date: Thu, 2 Jul 2026 05:46:59 +0200 Subject: [PATCH] fix(zed): read legacy uncompressed json thread rows Zed's DataType enum carries both zstd (the current save path) and json; verified against crates/agent/src/db.rs, along with the exact TokenUsage field names, the SerializedLanguageModel {provider, model} shape, and RFC3339 updated_at serialization. --- src/providers/zed.ts | 9 +++++++-- tests/providers/zed.test.ts | 20 +++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/providers/zed.ts b/src/providers/zed.ts index bcbf143..1457bbd 100644 --- a/src/providers/zed.ts +++ b/src/providers/zed.ts @@ -112,7 +112,9 @@ function parseThreads(db: SqliteDatabase, seenKeys: Set): ParsedProvider for (const row of rows) { try { - if (!row.id || !row.data || row.data_type !== 'zstd') { + // Zed's DataType enum is "zstd" (current save path) or "json" (legacy + // uncompressed rows); anything else is unknown. + if (!row.id || !row.data || (row.data_type !== 'zstd' && row.data_type !== 'json')) { if (row.data != null) skipped++ continue } @@ -120,7 +122,10 @@ function parseThreads(db: SqliteDatabase, seenKeys: Set): ParsedProvider if (Number.isNaN(parsedAt.getTime())) continue const timestamp = parsedAt.toISOString() - const thread = JSON.parse(zstdDecompress!(Buffer.from(row.data)).toString('utf-8')) as ThreadJson + const jsonText = row.data_type === 'zstd' + ? zstdDecompress!(Buffer.from(row.data)).toString('utf-8') + : Buffer.from(row.data).toString('utf-8') + const thread = JSON.parse(jsonText) as ThreadJson const model = thread.model?.model || 'unknown' const userMessage = row.summary ?? '' diff --git a/tests/providers/zed.test.ts b/tests/providers/zed.test.ts index 7a80e8a..569a11f 100644 --- a/tests/providers/zed.test.ts +++ b/tests/providers/zed.test.ts @@ -138,7 +138,7 @@ describe.skipIf(skipReason !== null)('zed provider (#480)', () => { it('skips non-zstd rows and malformed blobs without dropping healthy threads', async () => { const dbPath = buildDb((db) => { - insertThread(db, { id: 'bad-type', dataType: 'json', rawData: Buffer.from('{}') }) + insertThread(db, { id: 'bad-type', dataType: 'protobuf', rawData: Buffer.from('{}') }) insertThread(db, { id: 'bad-blob', rawData: Buffer.from('not zstd at all') }) insertThread(db, { id: 'good', @@ -154,6 +154,24 @@ describe.skipIf(skipReason !== null)('zed provider (#480)', () => { expect(calls[0]!.sessionId).toBe('good') }) + it('reads legacy uncompressed json rows alongside zstd rows', async () => { + const dbPath = buildDb((db) => { + insertThread(db, { + id: 'legacy', + dataType: 'json', + rawData: Buffer.from(JSON.stringify({ + model: { model: 'claude-sonnet-4-6' }, + request_token_usage: { 'req-1': { input_tokens: 40, output_tokens: 8 } }, + })), + }) + }) + + const calls = await collectCalls(dbPath) + expect(calls.length).toBe(1) + expect(calls[0]!.inputTokens).toBe(40) + expect(calls[0]!.model).toBe('claude-sonnet-4-6') + }) + it('dedupes across repeat parses via the shared seenKeys set', async () => { const dbPath = buildDb((db) => { insertThread(db, {