rewind-mcp-server 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -11
- package/dist/__tests__/helpers.test.d.ts +1 -0
- package/dist/__tests__/helpers.test.js +131 -0
- package/dist/__tests__/helpers.test.js.map +1 -0
- package/dist/__tests__/image-block.live.d.ts +1 -0
- package/dist/__tests__/image-block.live.js +41 -0
- package/dist/__tests__/image-block.live.js.map +1 -0
- package/dist/__tests__/server.test.js +713 -16
- package/dist/__tests__/server.test.js.map +1 -1
- package/dist/__tests__/stdio-e2e.live.d.ts +1 -0
- package/dist/__tests__/stdio-e2e.live.js +60 -0
- package/dist/__tests__/stdio-e2e.live.js.map +1 -0
- package/dist/__tests__/ui-resource.live.d.ts +1 -0
- package/dist/__tests__/ui-resource.live.js +71 -0
- package/dist/__tests__/ui-resource.live.js.map +1 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.js +15 -0
- package/dist/client.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/prompts.js +132 -0
- package/dist/prompts.js.map +1 -1
- package/dist/resources/ui.d.ts +33 -0
- package/dist/resources/ui.js +14 -0
- package/dist/resources/ui.js.map +1 -0
- package/dist/resources.js +144 -0
- package/dist/resources.js.map +1 -1
- package/dist/server.js +55 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/collecting.js +78 -37
- package/dist/tools/collecting.js.map +1 -1
- package/dist/tools/cross-domain.js +147 -19
- package/dist/tools/cross-domain.js.map +1 -1
- package/dist/tools/debug.d.ts +6 -0
- package/dist/tools/debug.js +26 -0
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/helpers.d.ts +81 -5
- package/dist/tools/helpers.js +138 -4
- package/dist/tools/helpers.js.map +1 -1
- package/dist/tools/listening.js +223 -52
- package/dist/tools/listening.js.map +1 -1
- package/dist/tools/reading.js +122 -22
- package/dist/tools/reading.js.map +1 -1
- package/dist/tools/running.js +87 -43
- package/dist/tools/running.js.map +1 -1
- package/dist/tools/watching.js +199 -32
- package/dist/tools/watching.js.map +1 -1
- package/dist/ui-bundles.d.ts +3 -0
- package/dist/ui-bundles.js +8 -0
- package/dist/ui-bundles.js.map +1 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +55 -30
- package/dist/worker.js.map +1 -1
- package/package.json +13 -2
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { withRichResponse, text, resourceLink, imageBlock, timeAgo, fmt, READ_ONLY_ANNOTATIONS, LIST_IMAGE_PX, } from './helpers.js';
|
|
3
|
+
const SEARCH_TOP_N = 5;
|
|
4
|
+
/**
|
|
5
|
+
* Map a cross-domain entity reference to a Rewind resource URI.
|
|
6
|
+
* Returns null when the entity type has no registered resource template.
|
|
7
|
+
*/
|
|
8
|
+
function rewindUri(domain, entityType, entityId) {
|
|
9
|
+
if (!entityId)
|
|
10
|
+
return null;
|
|
11
|
+
const map = {
|
|
12
|
+
'listening:artist': 'artist',
|
|
13
|
+
'listening:album': 'album',
|
|
14
|
+
'watching:movie': 'movie',
|
|
15
|
+
'watching:show': 'show',
|
|
16
|
+
'collecting:vinyl': 'vinyl',
|
|
17
|
+
'collecting:release': 'vinyl',
|
|
18
|
+
'collecting:media': 'physical-media',
|
|
19
|
+
'reading:article': 'article',
|
|
20
|
+
'reading:highlight': 'highlight',
|
|
21
|
+
'running:activity': 'activity',
|
|
22
|
+
};
|
|
23
|
+
const kind = map[`${domain}:${entityType}`];
|
|
24
|
+
return kind ? `rewind://${kind}/${entityId}` : null;
|
|
25
|
+
}
|
|
3
26
|
export function registerCrossDomainTools(server, client) {
|
|
4
|
-
// search
|
|
5
|
-
server.tool('search', 'Search across all domains (listening, running, watching, collecting, reading)
|
|
27
|
+
// search ─────────────────────────────────────────────────────────
|
|
28
|
+
server.tool('search', 'Search across all domains (listening, running, watching, collecting, reading). Three ranking modes: keyword (default) uses FTS5 full-text search across all domains; semantic uses Voyage AI embeddings for paraphrased / meaning-based recall (reading domain only); hybrid fuses both (reading domain only). Use semantic or hybrid when the user describes what an article was ABOUT rather than quoting its words.', {
|
|
6
29
|
query: z.string().describe('Search query text'),
|
|
7
30
|
domain: z
|
|
8
31
|
.enum(['listening', 'running', 'watching', 'collecting', 'reading'])
|
|
9
32
|
.optional()
|
|
10
33
|
.describe('Optional: filter results to a single domain'),
|
|
34
|
+
mode: z
|
|
35
|
+
.enum(['keyword', 'semantic', 'hybrid'])
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Ranking mode. keyword = FTS (default). semantic = cosine-similarity over article embeddings (reading only). hybrid = FTS + semantic via reciprocal rank fusion (reading only).'),
|
|
11
38
|
limit: z
|
|
12
39
|
.number()
|
|
13
40
|
.min(1)
|
|
@@ -15,7 +42,7 @@ export function registerCrossDomainTools(server, client) {
|
|
|
15
42
|
.default(10)
|
|
16
43
|
.describe('Number of results to return'),
|
|
17
44
|
page: z.number().min(1).default(1).describe('Page number for pagination'),
|
|
18
|
-
}, READ_ONLY_ANNOTATIONS, async ({ query, domain, limit, page }) =>
|
|
45
|
+
}, READ_ONLY_ANNOTATIONS, async ({ query, domain, mode, limit, page }) => withRichResponse(async () => {
|
|
19
46
|
const params = {
|
|
20
47
|
q: query,
|
|
21
48
|
limit,
|
|
@@ -23,19 +50,106 @@ export function registerCrossDomainTools(server, client) {
|
|
|
23
50
|
};
|
|
24
51
|
if (domain)
|
|
25
52
|
params.domain = domain;
|
|
53
|
+
if (mode)
|
|
54
|
+
params.mode = mode;
|
|
26
55
|
const data = await client.get('/search', params);
|
|
27
|
-
if (!data.data.length)
|
|
28
|
-
return
|
|
56
|
+
if (!data.data.length) {
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
text(`No results found for "${query}"${domain ? ` in ${domain}` : ''}.`),
|
|
60
|
+
],
|
|
61
|
+
structuredContent: { items: [], pagination: data.pagination },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const modeLabel = mode && mode !== 'keyword' ? ` [${mode}]` : '';
|
|
65
|
+
const lines = [
|
|
66
|
+
`Search results for "${query}"${modeLabel} (${fmt(data.pagination.total)} total):`,
|
|
67
|
+
];
|
|
68
|
+
for (const [i, r] of data.data.entries()) {
|
|
69
|
+
const sub = r.subtitle ? ` -- ${r.subtitle}` : '';
|
|
70
|
+
const score = typeof r.score === 'number' ? ` (score=${r.score.toFixed(2)})` : '';
|
|
71
|
+
lines.push(`${i + 1}. [${r.domain}/${r.entity_type}] ${r.title}${sub}${score}`);
|
|
72
|
+
}
|
|
73
|
+
// Emit one resource_link per result that maps to a known entity.
|
|
74
|
+
const links = data.data
|
|
75
|
+
.map((r) => {
|
|
76
|
+
const uri = rewindUri(r.domain, r.entity_type, r.entity_id);
|
|
77
|
+
if (!uri)
|
|
78
|
+
return null;
|
|
79
|
+
return resourceLink(uri, r.title, {
|
|
80
|
+
mimeType: 'application/json',
|
|
81
|
+
description: r.subtitle ?? undefined,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
84
|
+
.filter((b) => b !== null);
|
|
85
|
+
// Emit image blocks for the top-N results that carry an image.
|
|
86
|
+
const topN = data.data.slice(0, SEARCH_TOP_N);
|
|
87
|
+
const images = await Promise.all(topN.map((r) => imageBlock(client, r.image, LIST_IMAGE_PX)));
|
|
88
|
+
const imageBlocks = images.filter((b) => b !== null);
|
|
89
|
+
const content = [
|
|
90
|
+
text(lines.join('\n')),
|
|
91
|
+
...imageBlocks,
|
|
92
|
+
...links,
|
|
93
|
+
];
|
|
94
|
+
return {
|
|
95
|
+
content,
|
|
96
|
+
structuredContent: { items: data.data, pagination: data.pagination },
|
|
97
|
+
};
|
|
98
|
+
}));
|
|
99
|
+
// semantic_search ────────────────────────────────────────────────
|
|
100
|
+
server.tool('semantic_search', 'Semantic search over the reading domain using Voyage AI embeddings. Use when the user describes the gist or topic of an article they remember rather than quoting exact words — e.g. "article about a former SNL writer" or "piece about tech layoffs". Returns articles ranked by cosine similarity with a score in [0,1]. Reading domain only.', {
|
|
101
|
+
query: z.string().describe('Natural-language description of the article'),
|
|
102
|
+
limit: z
|
|
103
|
+
.number()
|
|
104
|
+
.min(1)
|
|
105
|
+
.max(25)
|
|
106
|
+
.default(10)
|
|
107
|
+
.describe('Number of matches to return'),
|
|
108
|
+
}, READ_ONLY_ANNOTATIONS, async ({ query, limit }) => withRichResponse(async () => {
|
|
109
|
+
const data = await client.get('/search', {
|
|
110
|
+
q: query,
|
|
111
|
+
domain: 'reading',
|
|
112
|
+
mode: 'semantic',
|
|
113
|
+
limit,
|
|
114
|
+
});
|
|
115
|
+
if (!data.data.length) {
|
|
116
|
+
return {
|
|
117
|
+
content: [text(`No semantic matches found for "${query}".`)],
|
|
118
|
+
structuredContent: { items: [], pagination: data.pagination },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
29
121
|
const lines = [
|
|
30
|
-
`
|
|
122
|
+
`Semantic matches for "${query}" (${fmt(data.data.length)} shown):`,
|
|
31
123
|
];
|
|
32
124
|
for (const [i, r] of data.data.entries()) {
|
|
33
125
|
const sub = r.subtitle ? ` -- ${r.subtitle}` : '';
|
|
34
|
-
lines.push(`${i + 1}.
|
|
126
|
+
lines.push(`${i + 1}. ${r.title}${sub} (score=${r.score.toFixed(2)})`);
|
|
35
127
|
}
|
|
36
|
-
|
|
128
|
+
const links = data.data
|
|
129
|
+
.map((r) => {
|
|
130
|
+
const uri = rewindUri(r.domain, r.entity_type, r.entity_id);
|
|
131
|
+
if (!uri)
|
|
132
|
+
return null;
|
|
133
|
+
return resourceLink(uri, r.title, {
|
|
134
|
+
mimeType: 'application/json',
|
|
135
|
+
description: r.subtitle ?? undefined,
|
|
136
|
+
});
|
|
137
|
+
})
|
|
138
|
+
.filter((b) => b !== null);
|
|
139
|
+
const topN = data.data.slice(0, SEARCH_TOP_N);
|
|
140
|
+
const images = await Promise.all(topN.map((r) => imageBlock(client, r.image, LIST_IMAGE_PX)));
|
|
141
|
+
const imageBlocks = images.filter((b) => b !== null);
|
|
142
|
+
const content = [
|
|
143
|
+
text(lines.join('\n')),
|
|
144
|
+
...imageBlocks,
|
|
145
|
+
...links,
|
|
146
|
+
];
|
|
147
|
+
return {
|
|
148
|
+
content,
|
|
149
|
+
structuredContent: { items: data.data, pagination: data.pagination },
|
|
150
|
+
};
|
|
37
151
|
}));
|
|
38
|
-
// get_feed
|
|
152
|
+
// get_feed ───────────────────────────────────────────────────────
|
|
39
153
|
server.tool('get_feed', 'Get the unified activity feed across all domains. Returns a chronological list of recent activities (listens, runs, watches, reads, collection adds). Supports date filtering.', {
|
|
40
154
|
limit: z
|
|
41
155
|
.number()
|
|
@@ -59,7 +173,7 @@ export function registerCrossDomainTools(server, client) {
|
|
|
59
173
|
.string()
|
|
60
174
|
.optional()
|
|
61
175
|
.describe('Optional: end of date range (ISO 8601)'),
|
|
62
|
-
}, READ_ONLY_ANNOTATIONS, async ({ limit, domain, date, from, to }) =>
|
|
176
|
+
}, READ_ONLY_ANNOTATIONS, async ({ limit, domain, date, from, to }) => withRichResponse(async () => {
|
|
63
177
|
const params = {
|
|
64
178
|
limit,
|
|
65
179
|
date,
|
|
@@ -68,8 +182,12 @@ export function registerCrossDomainTools(server, client) {
|
|
|
68
182
|
};
|
|
69
183
|
const path = domain ? `/feed/domain/${domain}` : '/feed';
|
|
70
184
|
const data = await client.get(path, params);
|
|
71
|
-
if (!data.data.length)
|
|
72
|
-
return
|
|
185
|
+
if (!data.data.length) {
|
|
186
|
+
return {
|
|
187
|
+
content: [text('No feed activity found for the given filters.')],
|
|
188
|
+
structuredContent: { items: [], pagination: data.pagination },
|
|
189
|
+
};
|
|
190
|
+
}
|
|
73
191
|
const lines = ['Activity Feed:'];
|
|
74
192
|
for (const item of data.data) {
|
|
75
193
|
const sub = item.subtitle ? ` -- ${item.subtitle}` : '';
|
|
@@ -78,9 +196,12 @@ export function registerCrossDomainTools(server, client) {
|
|
|
78
196
|
if (data.pagination.has_more) {
|
|
79
197
|
lines.push('\nMore items available. Increase limit or narrow date range.');
|
|
80
198
|
}
|
|
81
|
-
return
|
|
199
|
+
return {
|
|
200
|
+
content: [text(lines.join('\n'))],
|
|
201
|
+
structuredContent: { items: data.data, pagination: data.pagination },
|
|
202
|
+
};
|
|
82
203
|
}));
|
|
83
|
-
// get_on_this_day
|
|
204
|
+
// get_on_this_day ────────────────────────────────────────────────
|
|
84
205
|
server.tool('get_on_this_day', "Get historical 'on this day' items -- what happened on a given date in previous years across all domains. Defaults to today. Great for nostalgia and reflection.", {
|
|
85
206
|
month: z
|
|
86
207
|
.number()
|
|
@@ -94,11 +215,15 @@ export function registerCrossDomainTools(server, client) {
|
|
|
94
215
|
.max(31)
|
|
95
216
|
.optional()
|
|
96
217
|
.describe('Optional: day (1-31). Defaults to current day.'),
|
|
97
|
-
}, READ_ONLY_ANNOTATIONS, async ({ month, day }) =>
|
|
218
|
+
}, READ_ONLY_ANNOTATIONS, async ({ month, day }) => withRichResponse(async () => {
|
|
98
219
|
const params = { month, day };
|
|
99
220
|
const data = await client.get('/feed/on-this-day', params);
|
|
100
|
-
if (!data.years.length)
|
|
101
|
-
return
|
|
221
|
+
if (!data.years.length) {
|
|
222
|
+
return {
|
|
223
|
+
content: [text("No 'on this day' history found.")],
|
|
224
|
+
structuredContent: data,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
102
227
|
const lines = [`On This Day (${data.month}/${data.day}):`];
|
|
103
228
|
for (const yearGroup of data.years) {
|
|
104
229
|
lines.push('');
|
|
@@ -108,7 +233,10 @@ export function registerCrossDomainTools(server, client) {
|
|
|
108
233
|
lines.push(` - [${item.domain}] ${item.title}${sub}`);
|
|
109
234
|
}
|
|
110
235
|
}
|
|
111
|
-
return
|
|
236
|
+
return {
|
|
237
|
+
content: [text(lines.join('\n'))],
|
|
238
|
+
structuredContent: data,
|
|
239
|
+
};
|
|
112
240
|
}));
|
|
113
241
|
}
|
|
114
242
|
//# sourceMappingURL=cross-domain.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-domain.js","sourceRoot":"","sources":["../../src/tools/cross-domain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"cross-domain.js","sourceRoot":"","sources":["../../src/tools/cross-domain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { READ_ONLY_ANNOTATIONS } from './helpers.js';
|
|
2
|
+
export function registerDebugTools(server) {
|
|
3
|
+
server.registerTool('ui_hello_debug', {
|
|
4
|
+
title: 'UI Hello (debug)',
|
|
5
|
+
description: 'Show the Rewind MCP Apps diagnostic UI. Invoke this to verify the MCP Apps pipeline end-to-end against a known-working minimal React app.',
|
|
6
|
+
inputSchema: {},
|
|
7
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
8
|
+
_meta: {
|
|
9
|
+
ui: { resourceUri: 'ui://rewind/hello.html' },
|
|
10
|
+
'ui/resourceUri': 'ui://rewind/hello.html',
|
|
11
|
+
},
|
|
12
|
+
}, async () => ({
|
|
13
|
+
content: [
|
|
14
|
+
{
|
|
15
|
+
type: 'text',
|
|
16
|
+
text: 'Rewind MCP Apps diagnostic. If the UI renders inline, the MCP Apps pipeline is working; if it fails with "Failed to set up MCP app", the issue is client-side (Anthropic sandbox).',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
structuredContent: {
|
|
20
|
+
greeting: 'hello',
|
|
21
|
+
server_time: new Date().toISOString(),
|
|
22
|
+
app: 'rewind-hello',
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/tools/debug.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,UAAU,kBAAkB,CAAC,MAAiB;IAClD,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,2IAA2I;QAC7I,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,qBAAqB;QAClC,KAAK,EAAE;YACL,EAAE,EAAE,EAAE,WAAW,EAAE,wBAAwB,EAAE;YAC7C,gBAAgB,EAAE,wBAAwB;SAC3C;KACF,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;QACX,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,oLAAoL;aAC3L;SACF;QACD,iBAAiB,EAAE;YACjB,QAAQ,EAAE,OAAO;YACjB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACrC,GAAG,EAAE,cAAc;SACpB;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
|
package/dist/tools/helpers.d.ts
CHANGED
|
@@ -1,32 +1,108 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { RewindClient } from '../client.js';
|
|
3
|
+
export type TextBlock = {
|
|
4
|
+
type: 'text';
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
export type ImageBlock = {
|
|
8
|
+
type: 'image';
|
|
9
|
+
data: string;
|
|
10
|
+
mimeType: string;
|
|
11
|
+
};
|
|
12
|
+
export type ResourceLinkBlock = {
|
|
13
|
+
type: 'resource_link';
|
|
14
|
+
uri: string;
|
|
15
|
+
name: string;
|
|
16
|
+
mimeType?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
};
|
|
19
|
+
export type ContentBlock = TextBlock | ImageBlock | ResourceLinkBlock;
|
|
20
|
+
export type ToolResult<S = unknown> = {
|
|
21
|
+
content: ContentBlock[];
|
|
22
|
+
structuredContent?: S;
|
|
23
|
+
isError?: boolean;
|
|
24
|
+
};
|
|
25
|
+
/** Build a text content block. */
|
|
26
|
+
export declare function text(value: string): TextBlock;
|
|
27
|
+
/**
|
|
28
|
+
* Build a resource_link content block.
|
|
29
|
+
* Returns null if the URI is missing/empty so callers can `.filter(Boolean)`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resourceLink(uri: string | null | undefined, name: string, opts?: {
|
|
32
|
+
mimeType?: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
}): ResourceLinkBlock | null;
|
|
35
|
+
/**
|
|
36
|
+
* An image attachment as returned by Rewind entity responses.
|
|
37
|
+
* Accepts both `cdn_url` (live API) and `url` (OpenAPI-schema-example field).
|
|
38
|
+
*/
|
|
39
|
+
export type ImageAttachment = {
|
|
40
|
+
cdn_url?: string | null;
|
|
41
|
+
url?: string | null;
|
|
42
|
+
thumbhash?: string | null;
|
|
43
|
+
dominant_color?: string | null;
|
|
44
|
+
accent_color?: string | null;
|
|
45
|
+
} | null | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Build an image content block from an entity's image attachment by fetching
|
|
48
|
+
* the public CDN URL directly. Returns null on any failure or missing URL --
|
|
49
|
+
* never throws. Image blocks are best-effort; we always want to return
|
|
50
|
+
* text/data even if the image fetch breaks.
|
|
51
|
+
*
|
|
52
|
+
* Pass `targetPx` to request a smaller transform -- overrides the `width` and
|
|
53
|
+
* `height` query params on the CDN URL. Useful for list tools that must stay
|
|
54
|
+
* under the client's per-response size budget. Omit for full-size (typically
|
|
55
|
+
* 300x300) detail-tool posters.
|
|
56
|
+
*/
|
|
57
|
+
export declare function imageBlock(client: RewindClient, image: ImageAttachment, targetPx?: number): Promise<ImageBlock | null>;
|
|
58
|
+
/** Target pixel size for list-tool image blocks. Keeps responses under ~250KB. */
|
|
59
|
+
export declare const LIST_IMAGE_PX = 150;
|
|
3
60
|
/**
|
|
4
|
-
* Wrap a tool handler
|
|
61
|
+
* Wrap a tool handler that returns only text.
|
|
5
62
|
* Returns { content, isError: true } on failure.
|
|
6
63
|
*/
|
|
7
64
|
export declare function withErrorHandling(fn: () => Promise<string>): Promise<{
|
|
8
|
-
content:
|
|
9
|
-
type: 'text';
|
|
10
|
-
text: string;
|
|
11
|
-
}[];
|
|
65
|
+
content: TextBlock[];
|
|
12
66
|
isError?: boolean;
|
|
13
67
|
}>;
|
|
68
|
+
/**
|
|
69
|
+
* Wrap a tool handler that returns a rich response (text + images + resource_links + structuredContent).
|
|
70
|
+
* Returns a well-formed error ToolResult on failure.
|
|
71
|
+
*
|
|
72
|
+
* Callers should construct content arrays via the builders above and filter nulls:
|
|
73
|
+
* return {
|
|
74
|
+
* content: [text(summary), ...blocks.filter(Boolean)],
|
|
75
|
+
* structuredContent: data,
|
|
76
|
+
* };
|
|
77
|
+
*/
|
|
78
|
+
export declare function withRichResponse<S = unknown>(fn: () => Promise<ToolResult<S>>): Promise<ToolResult<S>>;
|
|
14
79
|
/** Format a date string as "Jan 15, 2025" */
|
|
15
80
|
export declare function formatDate(iso: string | null | undefined): string;
|
|
16
81
|
/** Format a date string as relative time like "2h ago" */
|
|
17
82
|
export declare function timeAgo(iso: string): string;
|
|
18
83
|
/** Format a number with commas */
|
|
19
84
|
export declare function fmt(n: number | null | undefined): string;
|
|
85
|
+
/**
|
|
86
|
+
* Format a Letterboxd-style 0-5 star user rating. The Rewind API stores
|
|
87
|
+
* these in the 0-5 half-star range (e.g. 4.5 = four and a half stars).
|
|
88
|
+
* Renders as "4.5★" or "4★" -- never "4/10", which is a different scale.
|
|
89
|
+
*/
|
|
90
|
+
export declare function formatStars(r: number | null | undefined): string;
|
|
20
91
|
/** Common date filter params reused across tools. */
|
|
21
92
|
export declare const dateFilterParams: {
|
|
22
93
|
date: z.ZodOptional<z.ZodString>;
|
|
23
94
|
from: z.ZodOptional<z.ZodString>;
|
|
24
95
|
to: z.ZodOptional<z.ZodString>;
|
|
25
96
|
};
|
|
97
|
+
/** Shared optional input for tools that return image content blocks. */
|
|
98
|
+
export declare const includeImagesParam: {
|
|
99
|
+
include_images: z.ZodDefault<z.ZodBoolean>;
|
|
100
|
+
};
|
|
26
101
|
/** Standard annotations for all Rewind tools (read-only, open-world). */
|
|
27
102
|
export declare const READ_ONLY_ANNOTATIONS: {
|
|
28
103
|
readOnlyHint: true;
|
|
29
104
|
destructiveHint: false;
|
|
105
|
+
idempotentHint: true;
|
|
30
106
|
openWorldHint: true;
|
|
31
107
|
};
|
|
32
108
|
export type { RewindClient };
|
package/dist/tools/helpers.js
CHANGED
|
@@ -1,21 +1,136 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
// ─── Content block builders ──────────────────────────────────────────
|
|
3
|
+
/** Build a text content block. */
|
|
4
|
+
export function text(value) {
|
|
5
|
+
return { type: 'text', text: value };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Build a resource_link content block.
|
|
9
|
+
* Returns null if the URI is missing/empty so callers can `.filter(Boolean)`.
|
|
10
|
+
*/
|
|
11
|
+
export function resourceLink(uri, name, opts) {
|
|
12
|
+
if (!uri)
|
|
13
|
+
return null;
|
|
14
|
+
const block = { type: 'resource_link', uri, name };
|
|
15
|
+
if (opts?.mimeType)
|
|
16
|
+
block.mimeType = opts.mimeType;
|
|
17
|
+
if (opts?.description)
|
|
18
|
+
block.description = opts.description;
|
|
19
|
+
return block;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build an image content block from an entity's image attachment by fetching
|
|
23
|
+
* the public CDN URL directly. Returns null on any failure or missing URL --
|
|
24
|
+
* never throws. Image blocks are best-effort; we always want to return
|
|
25
|
+
* text/data even if the image fetch breaks.
|
|
26
|
+
*
|
|
27
|
+
* Pass `targetPx` to request a smaller transform -- overrides the `width` and
|
|
28
|
+
* `height` query params on the CDN URL. Useful for list tools that must stay
|
|
29
|
+
* under the client's per-response size budget. Omit for full-size (typically
|
|
30
|
+
* 300x300) detail-tool posters.
|
|
31
|
+
*/
|
|
32
|
+
export async function imageBlock(client, image, targetPx) {
|
|
33
|
+
if (!image)
|
|
34
|
+
return null;
|
|
35
|
+
let url = image.cdn_url ?? image.url ?? null;
|
|
36
|
+
if (!url)
|
|
37
|
+
return null;
|
|
38
|
+
if (targetPx)
|
|
39
|
+
url = resizeCdnUrl(url, targetPx);
|
|
40
|
+
try {
|
|
41
|
+
const { bytes, mimeType } = await client.getBinaryFromUrl(url);
|
|
42
|
+
return {
|
|
43
|
+
type: 'image',
|
|
44
|
+
data: bytesToBase64(bytes),
|
|
45
|
+
mimeType,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Rewrite a Rewind CDN URL to use the Cloudflare Images transform path
|
|
54
|
+
* (`/cdn-cgi/image/<opts>/<asset>`) at the requested pixel size. The raw
|
|
55
|
+
* `cdn_url` field returned by the API points at the original (full-res) R2
|
|
56
|
+
* object and does not honor width/height query params. Transform URLs do.
|
|
57
|
+
*
|
|
58
|
+
* Input: https://cdn.rewind.rest/watching/movies/707/original.jpg?width=300&height=300&...&v=1
|
|
59
|
+
* Output: https://cdn.rewind.rest/cdn-cgi/image/width=150,height=150,fit=cover,format=auto,quality=85/watching/movies/707/original.jpg?v=1
|
|
60
|
+
*
|
|
61
|
+
* Returns the input unchanged if it is not a valid URL or is already a
|
|
62
|
+
* transform URL.
|
|
63
|
+
*/
|
|
64
|
+
function resizeCdnUrl(url, targetPx) {
|
|
65
|
+
try {
|
|
66
|
+
const u = new URL(url);
|
|
67
|
+
if (u.pathname.startsWith('/cdn-cgi/image/'))
|
|
68
|
+
return url;
|
|
69
|
+
const opts = `width=${targetPx},height=${targetPx},fit=cover,format=auto,quality=85`;
|
|
70
|
+
const version = u.searchParams.get('v');
|
|
71
|
+
const versionSuffix = version ? `?v=${version}` : '';
|
|
72
|
+
return `${u.origin}/cdn-cgi/image/${opts}${u.pathname}${versionSuffix}`;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return url;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Target pixel size for list-tool image blocks. Keeps responses under ~250KB. */
|
|
79
|
+
export const LIST_IMAGE_PX = 150;
|
|
2
80
|
/**
|
|
3
|
-
*
|
|
81
|
+
* Convert bytes to base64. Uses globalThis.Buffer when available (Node / Workers),
|
|
82
|
+
* falls back to a manual encoding that works in any JS runtime.
|
|
83
|
+
*/
|
|
84
|
+
function bytesToBase64(bytes) {
|
|
85
|
+
const g = globalThis;
|
|
86
|
+
if (g.Buffer)
|
|
87
|
+
return g.Buffer.from(bytes).toString('base64');
|
|
88
|
+
let binary = '';
|
|
89
|
+
for (let i = 0; i < bytes.length; i++)
|
|
90
|
+
binary += String.fromCharCode(bytes[i]);
|
|
91
|
+
return btoa(binary);
|
|
92
|
+
}
|
|
93
|
+
// ─── Response wrappers ───────────────────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* Wrap a tool handler that returns only text.
|
|
4
96
|
* Returns { content, isError: true } on failure.
|
|
5
97
|
*/
|
|
6
98
|
export async function withErrorHandling(fn) {
|
|
7
99
|
try {
|
|
8
|
-
const
|
|
9
|
-
return { content: [
|
|
100
|
+
const textValue = await fn();
|
|
101
|
+
return { content: [text(textValue)] };
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
return {
|
|
106
|
+
content: [text(`Error: ${message}`)],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Wrap a tool handler that returns a rich response (text + images + resource_links + structuredContent).
|
|
113
|
+
* Returns a well-formed error ToolResult on failure.
|
|
114
|
+
*
|
|
115
|
+
* Callers should construct content arrays via the builders above and filter nulls:
|
|
116
|
+
* return {
|
|
117
|
+
* content: [text(summary), ...blocks.filter(Boolean)],
|
|
118
|
+
* structuredContent: data,
|
|
119
|
+
* };
|
|
120
|
+
*/
|
|
121
|
+
export async function withRichResponse(fn) {
|
|
122
|
+
try {
|
|
123
|
+
return await fn();
|
|
10
124
|
}
|
|
11
125
|
catch (error) {
|
|
12
126
|
const message = error instanceof Error ? error.message : String(error);
|
|
13
127
|
return {
|
|
14
|
-
content: [
|
|
128
|
+
content: [text(`Error: ${message}`)],
|
|
15
129
|
isError: true,
|
|
16
130
|
};
|
|
17
131
|
}
|
|
18
132
|
}
|
|
133
|
+
// ─── Existing utilities (unchanged) ──────────────────────────────────
|
|
19
134
|
/** Format a date string as "Jan 15, 2025" */
|
|
20
135
|
export function formatDate(iso) {
|
|
21
136
|
if (!iso)
|
|
@@ -48,6 +163,17 @@ export function fmt(n) {
|
|
|
48
163
|
return '0';
|
|
49
164
|
return n.toLocaleString('en-US');
|
|
50
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Format a Letterboxd-style 0-5 star user rating. The Rewind API stores
|
|
168
|
+
* these in the 0-5 half-star range (e.g. 4.5 = four and a half stars).
|
|
169
|
+
* Renders as "4.5★" or "4★" -- never "4/10", which is a different scale.
|
|
170
|
+
*/
|
|
171
|
+
export function formatStars(r) {
|
|
172
|
+
if (r === null || r === undefined)
|
|
173
|
+
return '';
|
|
174
|
+
const display = r % 1 === 0 ? r.toFixed(0) : r.toFixed(1);
|
|
175
|
+
return `${display}★`;
|
|
176
|
+
}
|
|
51
177
|
/** Common date filter params reused across tools. */
|
|
52
178
|
export const dateFilterParams = {
|
|
53
179
|
date: z
|
|
@@ -60,10 +186,18 @@ export const dateFilterParams = {
|
|
|
60
186
|
.describe('Optional: start of date range (ISO 8601)'),
|
|
61
187
|
to: z.string().optional().describe('Optional: end of date range (ISO 8601)'),
|
|
62
188
|
};
|
|
189
|
+
/** Shared optional input for tools that return image content blocks. */
|
|
190
|
+
export const includeImagesParam = {
|
|
191
|
+
include_images: z
|
|
192
|
+
.boolean()
|
|
193
|
+
.default(true)
|
|
194
|
+
.describe('Include artwork/poster image content blocks in the response. Default true. Set false to keep responses small.'),
|
|
195
|
+
};
|
|
63
196
|
/** Standard annotations for all Rewind tools (read-only, open-world). */
|
|
64
197
|
export const READ_ONLY_ANNOTATIONS = {
|
|
65
198
|
readOnlyHint: true,
|
|
66
199
|
destructiveHint: false,
|
|
200
|
+
idempotentHint: true,
|
|
67
201
|
openWorldHint: true,
|
|
68
202
|
};
|
|
69
203
|
//# sourceMappingURL=helpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/tools/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/tools/helpers.ts"],"names":[],"mappings":"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"}
|