Skip to content

Commit 94c4791

Browse files
authored
chore: adds digestId to client responses (#8283)
## Explanation The `/asset-summary` and `/market-overview` API endpoints always return responses wrapped in an `{ id, digest }` / `{ id, report }` envelope where `id` is a unique UUID identifying the digest. This PR updates `AiDigestService` to require that envelope shape (removing the bare-response fallback path), extracts `id` and exposes it as `digestId` on both `MarketInsightsReport` and `MarketOverview`, and makes `digestId` a required field on both types. This enables downstream consumers like MM app to include a `digest_id` property in analytics events. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces a breaking response-shape/type change by no longer accepting bare `/asset-summary` payloads and making `digestId` required, which can break downstream consumers and tests that still expect the old shape. > > **Overview** > **AI digest client responses now surface the API-provided digest UUID.** `AiDigestService.searchDigest` now only accepts the `{ id, digest }` envelope for `/asset-summary`, validates `id`, and returns the digest with `id` exposed as a required `digestId`. > > Tests are updated to mock enveloped responses and assert `digestId` is present, and the `ai-controllers` changelog documents the new required field and the removal of the bare-response fallback. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit c0f3977. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 29c005a commit 94c4791

File tree

4 files changed

+168
-106
lines changed

4 files changed

+168
-106
lines changed

packages/ai-controllers/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- `MarketInsightsReport` now includes a required `digestId` field containing the unique UUID returned in the `/asset-summary` API response envelope ([#8283](https://github.com/MetaMask/core/pull/8283)).
13+
- `AiDigestService` now requires the `{ id, digest }` / `{ id, report }` envelope shape and exposes `id` as `digestId` on the returned report objects; bare (non-enveloped) responses are no longer accepted ([#8283](https://github.com/MetaMask/core/pull/8283)).
14+
1015
## [0.5.0]
1116

1217
### Changed

packages/ai-controllers/src/AiDigestService.test.ts

Lines changed: 159 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,16 @@ describe('AiDigestService', () => {
5353
],
5454
};
5555

56+
const mockEnvelope = {
57+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
58+
digest: mockMarketInsightsReport,
59+
};
60+
5661
it('fetches market insights using universal asset= param for CAIP-19 identifiers', async () => {
5762
mockFetch.mockResolvedValue({
5863
ok: true,
5964
status: 200,
60-
json: () => Promise.resolve(mockMarketInsightsReport),
65+
json: () => Promise.resolve(mockEnvelope),
6166
});
6267

6368
const service = new AiDigestService({
@@ -67,7 +72,10 @@ describe('AiDigestService', () => {
6772
'eip155:1/erc20:0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
6873
);
6974

70-
expect(result).toStrictEqual(mockMarketInsightsReport);
75+
expect(result).toStrictEqual({
76+
...mockMarketInsightsReport,
77+
digestId: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
78+
});
7179
expect(mockFetch).toHaveBeenCalledWith(
7280
'http://test.com/api/v1/asset-summary?asset=eip155%3A1%2Ferc20%3A0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
7381
);
@@ -77,15 +85,18 @@ describe('AiDigestService', () => {
7785
mockFetch.mockResolvedValue({
7886
ok: true,
7987
status: 200,
80-
json: () => Promise.resolve(mockMarketInsightsReport),
88+
json: () => Promise.resolve(mockEnvelope),
8189
});
8290

8391
const service = new AiDigestService({
8492
baseUrl: 'http://test.com/api/v1',
8593
});
8694
const result = await service.searchDigest('ETH');
8795

88-
expect(result).toStrictEqual(mockMarketInsightsReport);
96+
expect(result).toStrictEqual({
97+
...mockMarketInsightsReport,
98+
digestId: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
99+
});
89100
expect(mockFetch).toHaveBeenCalledWith(
90101
'http://test.com/api/v1/asset-summary?asset=ETH',
91102
);
@@ -95,7 +106,7 @@ describe('AiDigestService', () => {
95106
mockFetch.mockResolvedValue({
96107
ok: true,
97108
status: 200,
98-
json: () => Promise.resolve(mockMarketInsightsReport),
109+
json: () => Promise.resolve(mockEnvelope),
99110
});
100111

101112
const service = new AiDigestService({
@@ -108,7 +119,27 @@ describe('AiDigestService', () => {
108119
);
109120
});
110121

111-
it('accepts digest envelope responses', async () => {
122+
it('extracts digestId from the envelope id field', async () => {
123+
mockFetch.mockResolvedValue({
124+
ok: true,
125+
status: 200,
126+
json: () => Promise.resolve(mockEnvelope),
127+
});
128+
129+
const service = new AiDigestService({
130+
baseUrl: 'http://test.com/api/v1',
131+
});
132+
const result = await service.searchDigest(
133+
'eip155:1/erc20:0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
134+
);
135+
136+
expect(result).toStrictEqual({
137+
...mockMarketInsightsReport,
138+
digestId: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
139+
});
140+
});
141+
142+
it('throws when envelope id is missing', async () => {
112143
mockFetch.mockResolvedValue({
113144
ok: true,
114145
status: 200,
@@ -121,11 +152,12 @@ describe('AiDigestService', () => {
121152
const service = new AiDigestService({
122153
baseUrl: 'http://test.com/api/v1',
123154
});
124-
const result = await service.searchDigest(
125-
'eip155:1/erc20:0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
126-
);
127155

128-
expect(result).toStrictEqual(mockMarketInsightsReport);
156+
await expect(
157+
service.searchDigest(
158+
'eip155:1/erc20:0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
159+
),
160+
).rejects.toThrow(AiDigestControllerErrorMessage.API_INVALID_RESPONSE);
129161
});
130162

131163
it('accepts report responses with top-level social items', async () => {
@@ -134,15 +166,18 @@ describe('AiDigestService', () => {
134166
status: 200,
135167
json: () =>
136168
Promise.resolve({
137-
...mockMarketInsightsReport,
138-
social: [
139-
{
140-
contentSummary: 'BTC remains under macro pressure.',
141-
url: 'https://x.com/example/status/456',
142-
author: 'example',
143-
date: '2026-02-17',
144-
},
145-
],
169+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
170+
digest: {
171+
...mockMarketInsightsReport,
172+
social: [
173+
{
174+
contentSummary: 'BTC remains under macro pressure.',
175+
url: 'https://x.com/example/status/456',
176+
author: 'example',
177+
date: '2026-02-17',
178+
},
179+
],
180+
},
146181
}),
147182
});
148183

@@ -155,6 +190,7 @@ describe('AiDigestService', () => {
155190

156191
expect(result).toStrictEqual({
157192
...mockMarketInsightsReport,
193+
digestId: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
158194
social: [
159195
{
160196
contentSummary: 'BTC remains under macro pressure.',
@@ -172,26 +208,29 @@ describe('AiDigestService', () => {
172208
status: 200,
173209
json: () =>
174210
Promise.resolve({
175-
...mockMarketInsightsReport,
176-
extraTopLevelField: true,
177-
trends: [
178-
{
179-
...mockMarketInsightsReport.trends[0],
180-
extraTrendField: 'ignored',
181-
articles: [
182-
{
183-
...mockMarketInsightsReport.trends[0].articles[0],
184-
extraArticleField: 'ignored',
185-
},
186-
],
187-
},
188-
],
189-
sources: [
190-
{
191-
...mockMarketInsightsReport.sources[0],
192-
extraSourceField: 'ignored',
193-
},
194-
],
211+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
212+
digest: {
213+
...mockMarketInsightsReport,
214+
extraTopLevelField: true,
215+
trends: [
216+
{
217+
...mockMarketInsightsReport.trends[0],
218+
extraTrendField: 'ignored',
219+
articles: [
220+
{
221+
...mockMarketInsightsReport.trends[0].articles[0],
222+
extraArticleField: 'ignored',
223+
},
224+
],
225+
},
226+
],
227+
sources: [
228+
{
229+
...mockMarketInsightsReport.sources[0],
230+
extraSourceField: 'ignored',
231+
},
232+
],
233+
},
195234
}),
196235
});
197236

@@ -204,6 +243,7 @@ describe('AiDigestService', () => {
204243

205244
expect(result).toStrictEqual({
206245
...mockMarketInsightsReport,
246+
digestId: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
207247
extraTopLevelField: true,
208248
trends: [
209249
{
@@ -255,12 +295,15 @@ describe('AiDigestService', () => {
255295
status: 200,
256296
json: () =>
257297
Promise.resolve({
258-
asset: 'btc',
259-
generatedAt: '2026-02-16T10:00:00.000Z',
260-
headline: 'BTC market update',
261-
summary: 'Momentum is positive across major venues.',
262-
trends: 'invalid-trends',
263-
sources: [],
298+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
299+
digest: {
300+
asset: 'btc',
301+
generatedAt: '2026-02-16T10:00:00.000Z',
302+
headline: 'BTC market update',
303+
summary: 'Momentum is positive across major venues.',
304+
trends: 'invalid-trends',
305+
sources: [],
306+
},
264307
}),
265308
});
266309

@@ -279,29 +322,32 @@ describe('AiDigestService', () => {
279322
status: 200,
280323
json: () =>
281324
Promise.resolve({
282-
asset: 'btc',
283-
generatedAt: '2026-02-16T10:00:00.000Z',
284-
headline: 'BTC market update',
285-
summary: 'Momentum is positive across major venues.',
286-
trends: [
287-
{
288-
title: 'Institutions continue buying',
289-
description:
290-
'Large holders have increased accumulation activity.',
291-
category: 'macro',
292-
impact: 'positive',
293-
articles: [
294-
{
295-
title: 'Institutional demand grows',
296-
url: 'https://example.com/news/institutional-demand-grows',
297-
source: 'example.com',
298-
date: 1234,
299-
},
300-
],
301-
tweets: [],
302-
},
303-
],
304-
sources: [],
325+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
326+
digest: {
327+
asset: 'btc',
328+
generatedAt: '2026-02-16T10:00:00.000Z',
329+
headline: 'BTC market update',
330+
summary: 'Momentum is positive across major venues.',
331+
trends: [
332+
{
333+
title: 'Institutions continue buying',
334+
description:
335+
'Large holders have increased accumulation activity.',
336+
category: 'macro',
337+
impact: 'positive',
338+
articles: [
339+
{
340+
title: 'Institutional demand grows',
341+
url: 'https://example.com/news/institutional-demand-grows',
342+
source: 'example.com',
343+
date: 1234,
344+
},
345+
],
346+
tweets: [],
347+
},
348+
],
349+
sources: [],
350+
},
305351
}),
306352
});
307353

@@ -320,18 +366,21 @@ describe('AiDigestService', () => {
320366
status: 200,
321367
json: () =>
322368
Promise.resolve({
323-
asset: 'btc',
324-
generatedAt: '2026-02-16T10:00:00.000Z',
325-
headline: 'BTC market update',
326-
summary: 'Momentum is positive across major venues.',
327-
trends: [],
328-
sources: [
329-
{
330-
name: 'Example News',
331-
url: 'https://example.com',
332-
type: null,
333-
},
334-
],
369+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
370+
digest: {
371+
asset: 'btc',
372+
generatedAt: '2026-02-16T10:00:00.000Z',
373+
headline: 'BTC market update',
374+
summary: 'Momentum is positive across major venues.',
375+
trends: [],
376+
sources: [
377+
{
378+
name: 'Example News',
379+
url: 'https://example.com',
380+
type: null,
381+
},
382+
],
383+
},
335384
}),
336385
});
337386

@@ -350,13 +399,16 @@ describe('AiDigestService', () => {
350399
status: 200,
351400
json: () =>
352401
Promise.resolve({
353-
version: 1,
354-
asset: 'btc',
355-
generatedAt: '2026-02-16T10:00:00.000Z',
356-
headline: 'BTC market update',
357-
summary: 'Momentum is positive across major venues.',
358-
trends: [],
359-
sources: [],
402+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
403+
digest: {
404+
version: 1,
405+
asset: 'btc',
406+
generatedAt: '2026-02-16T10:00:00.000Z',
407+
headline: 'BTC market update',
408+
summary: 'Momentum is positive across major venues.',
409+
trends: [],
410+
sources: [],
411+
},
360412
}),
361413
});
362414

@@ -391,13 +443,16 @@ describe('AiDigestService', () => {
391443
status: 200,
392444
json: () =>
393445
Promise.resolve({
394-
...mockMarketInsightsReport,
395-
trends: [
396-
{
397-
...mockMarketInsightsReport.trends[0],
398-
category: 'unknown-category',
399-
},
400-
],
446+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
447+
digest: {
448+
...mockMarketInsightsReport,
449+
trends: [
450+
{
451+
...mockMarketInsightsReport.trends[0],
452+
category: 'unknown-category',
453+
},
454+
],
455+
},
401456
}),
402457
});
403458

@@ -416,13 +471,16 @@ describe('AiDigestService', () => {
416471
status: 200,
417472
json: () =>
418473
Promise.resolve({
419-
...mockMarketInsightsReport,
420-
trends: [
421-
{
422-
...mockMarketInsightsReport.trends[0],
423-
impact: 'unknown-impact',
424-
},
425-
],
474+
id: 'a8154c57-c665-449c-8bb5-fcaae96ef922',
475+
digest: {
476+
...mockMarketInsightsReport,
477+
trends: [
478+
{
479+
...mockMarketInsightsReport.trends[0],
480+
impact: 'unknown-impact',
481+
},
482+
],
483+
},
426484
}),
427485
});
428486

0 commit comments

Comments
 (0)