@contenthero/mcp 0.4.7 → 0.4.9

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/dist/client.js CHANGED
@@ -33,7 +33,7 @@ export function getClient() {
33
33
  }
34
34
  // Announced once, on stderr so it never corrupts the stdio protocol stream. Which ACCOUNT an MCP server is
35
35
  // acting as is the single most consequential thing about it and was previously invisible: the only way to
36
- // find out was to call get_balance and recognise the number.
36
+ // find out was to call get_balance and recognize the number.
37
37
  console.error(`[contenthero-mcp] authenticated from ${keySource} (config dir: ${configDir()})`);
38
38
  cached = new ContentHero({
39
39
  apiKey,
package/dist/format.d.ts CHANGED
@@ -6,8 +6,121 @@
6
6
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
7
7
  import type { Avatar, AvatarSummary, Balance, BrandKit, BrandKitSummary, BrandKitSectionRecord, BrandKnowledgeItem, BrandKnowledgeDetail, BrandKnowledgeListResult, BrandKnowledgeMatch, ConnectedAccount, CostEstimate, CreateAvatarResult, Element, Generation, GenerateResult, EditAudioResult, MediaItem, MediaSummary, SearchMediaResult, Folder, DerivedFolder, FolderItem, MediaBatchResult, CreateMediaUploadResult, ImportedMedia, UploadedMedia, ModelInfo, PlatformSummary, PlatformSchema, Stage, Space, CardAsset, Post, CardDetail, CardListResult, StageListResult, CardSummary, Tag, PublishResult, TrackedAccount, ContentDetail, ContentListResult, AccountDetail, Transcription, Voice, VoiceSummary, ApplyEditorOpsResult, ProjectSummary, ProjectDetail, LiveContextResult, LayerTypeCatalog, TimelineTypeCatalog, TranscriptResult, ExportJob, ExportFormatCatalog, ExtractionOutcome } from '@contenthero/sdk';
8
8
  export declare function text(body: string, isError?: boolean): CallToolResult;
9
- /** A finished image/video generation: list the asset URLs, plus the placement outcome when placed on a project. */
10
- export declare function completedResult(gen: Generation): CallToolResult;
9
+ /**
10
+ * An attachment for a finished generation: the bytes to inline, or a link for the host to render.
11
+ *
12
+ * ⭐⭐⭐ **THE SHAPE FOLLOWS THE MEDIUM, NOT OUR PREFERENCE.** MCP's `ContentBlock` union is
13
+ * `text | image | audio | resource_link | resource`. Images and audio have first-class blocks and are small
14
+ * enough to carry as bytes, so they are EMBEDDED: that is what makes them render in the chat and what makes
15
+ * them outlive any URL. Video has no block of its own and would be megabytes of base64 inside a transcript,
16
+ * so it travels as a `resource_link`, which is exactly what a working implementation does (verified against
17
+ * the Higgsfield MCP, whose `job_display` returns `[Resource link: ....mp4]`).
18
+ */
19
+ export type GeneratedAttachment = {
20
+ kind: 'bytes';
21
+ type: 'image' | 'audio';
22
+ data: string;
23
+ mimeType: string;
24
+ } | {
25
+ kind: 'link';
26
+ uri: string;
27
+ mimeType: string;
28
+ name: string;
29
+ };
30
+ /**
31
+ * A finished generation: the asset URLs and placement outcome, plus the asset ITSELF.
32
+ *
33
+ * ## Why this used to be text only
34
+ *
35
+ * 🚨 **AN AGENT THAT GENERATES AN IMAGE COULD NOT SEE IT.** This returned a header, an outputId and a
36
+ * numbered list of urls, so the model had metadata and nothing else. Measured 2026-09-19 in a real ChatGPT
37
+ * and Claude session: Claude said "I can't see the image myself, only the metadata", and the link it
38
+ * surfaced was dead on arrival because ChatGPT had appended `utm_source=chatgpt.com` to a presigned URL and
39
+ * broken its signature.
40
+ *
41
+ * ⭐ The machinery already existed and was wired to the wrong tools: `mediaBatchResult` and
42
+ * `liveContextResult` have pushed image blocks for a while. Generation, the surface where a user most wants
43
+ * to SEE the result, was the one that did not.
44
+ *
45
+ * ## ⛔ THE AGENT CANNOT SEE WHAT IT MADE FROM THIS RESULT, AND THAT IS DELIBERATE
46
+ *
47
+ * An `image` block feeds the MODEL's vision; a `resource_link` gives the HOST something to render for the
48
+ * human. So the user sees every variation inline, and the model has a name and a url.
49
+ *
50
+ * ⭐ **`get_media` IS HOW A MODEL ACTUALLY LOOKS AT SOMETHING.** It embeds bytes as image blocks for exactly
51
+ * that purpose, so an agent that needs to judge a result (is the hand wrong, is the text legible, which of
52
+ * these four is best) calls it with the outputId. Embedding bytes HERE instead would spend the user's
53
+ * context on every generation to answer a question they may never ask, and a four-image batch would pay
54
+ * that cost four times over, repeated in every later turn.
55
+ *
56
+ * ⚠️ ATTACHMENTS ARE BUILT BY THE CALLER, not here. This module stays pure, the same split
57
+ * `mediaBatchResult` already uses: the handler decides, the formatter assembles.
58
+ */
59
+ /**
60
+ * What the generation WIDGET reads.
61
+ *
62
+ * ⭐⭐ `structuredContent` IS THE WIDGET'S ONLY INPUT. It is a separate channel from `content`: the blocks
63
+ * feed the model and any host without app support, this feeds the UI. Both are emitted, so nothing regresses
64
+ * where widgets are unsupported and nothing is duplicated where they are.
65
+ *
66
+ * ⚠️ URLS ONLY, NEVER BYTES. The widget runs in the host's frame and fetches media itself, and our
67
+ * capability urls carry their token in the QUERY STRING, so `<video src>` loads one directly with no header
68
+ * to set. Embedding base64 here would pay the context cost twice over.
69
+ */
70
+ /**
71
+ * Where one output lives in the product.
72
+ *
73
+ * ## ⭐⭐⭐ COMPUTED HERE, NOT IN THE WIDGET, BECAUSE HERE THE INDEX IS ALREADY A NUMBER
74
+ *
75
+ * The widget's first version parsed the slot back out of the output's NAME (`<id>-3` means slot 2), which
76
+ * is a derivation of something this function has in its hand. Parsing a number out of a string we formatted
77
+ * two lines earlier is how an off-by-one gets in, and the symptom would be "Open shows the wrong picture",
78
+ * which reads as a broken link rather than an index bug.
79
+ *
80
+ * ⚠️ **ONE-BASED IN THE NAME, ZERO-BASED IN THE URL.** `<id>-3` is what a person reads as "variation 3",
81
+ * and `variation=2` is the studio's `imageIndex`, which is slot space. Both conventions are correct in
82
+ * their own place and the conversion belongs at exactly one boundary, which is this one.
83
+ *
84
+ * ⚠️ A single-output generation gets NO `variation`. There is no variation to name, and passing 0 would
85
+ * imply there was a choice.
86
+ */
87
+ /** Where a deep link points when no client base url was threaded through. */
88
+ export declare const DEFAULT_APP_URL = "https://app.contenthero.ai";
89
+ export declare function studioUrlFor(baseUrl: string, outputId: string, index: number, total: number): string;
90
+ export declare function generationWidgetData(gen: Generation, posterUrls?: readonly (string | null)[], baseUrl?: string): {
91
+ outputId: string;
92
+ contentType: "image" | "audio" | "video";
93
+ modelId: string;
94
+ /**
95
+ * ⛔⛔ **NULL WHEN THERE IS NOTHING TO NAME, AND THE WIDGET MUST RENDER NO CHIP.**
96
+ *
97
+ * This used to be `displayName ?? gen.modelId`, fed by a catalog fetch in the server with a `catch`
98
+ * that returned the id. The id reads like a label, so a failed fetch showed as a chip flickering
99
+ * between kebab case and title case rather than as a failure. The name now arrives on the row.
100
+ *
101
+ * ⚠️ It is not always this generation's own model: an upload names itself, and a look names the model
102
+ * that produced the image it was assembled from.
103
+ */
104
+ modelName: string | null;
105
+ /** Hex brand accent from the registry, or null. The widget colors its chip with it. */
106
+ modelBrandColor: string | null;
107
+ /** Brand family key (e.g. `"openai"`) for choosing a glyph. NOT the model id. */
108
+ modelIconKey: string | null;
109
+ /** `"W:H"`, or null for audio. Drives the column count and the tile's own shape. */
110
+ displayAspect: string | null;
111
+ /** Verbatim. Some prompts are JSON-shaped because the person authored one; that object IS the prompt. */
112
+ prompt: string | null;
113
+ outputs: {
114
+ url: string;
115
+ posterUrl: string | null;
116
+ name: string;
117
+ /** Where the Open button goes: this asset, in the studio, with its siblings and every action. */
118
+ studioUrl: string;
119
+ }[];
120
+ };
121
+ export declare function completedResult(gen: Generation, attachments?: GeneratedAttachment[], posterUrls?: readonly (string | null)[],
122
+ /** The server this client talks to, so Open deep-links to it rather than always to production. */
123
+ baseUrl?: string): CallToolResult;
11
124
  /** Suggested seconds to wait before re-polling a job, by content type. */
12
125
  export declare function pollAfterSecondsFor(contentType: string): number;
13
126
  /**
@@ -19,8 +132,39 @@ export declare function pollAfterSecondsFor(contentType: string): number;
19
132
  * is what drifted, so these messages now print the call instead, and every site shares this one function.
20
133
  */
21
134
  export declare function getStatusCall(outputIds: readonly string[]): string;
22
- /** A slow job that did not finish within the smart-wait window. */
23
- export declare function pendingResult(outputId: string, pollAfterSeconds?: number): CallToolResult;
135
+ /** What a still-running generation already knows about the shape of its own result. */
136
+ export interface PendingShape {
137
+ contentType: 'image' | 'video' | 'audio';
138
+ modelId: string;
139
+ /** `"W:H"` as requested. Absent for audio and for `auto`/`adaptive`. */
140
+ displayAspect?: string | null;
141
+ /** How many outputs were asked for, so the widget draws that many placeholders. */
142
+ expected?: number;
143
+ }
144
+ /**
145
+ * A slow job that did not finish within the smart-wait window.
146
+ *
147
+ * ## ⭐⭐⭐ THIS IS WHERE THE SKELETONS COME FROM, AND WHY IT IS THE ONLY PLACE THEY COULD
148
+ *
149
+ * A generation returns one of two ways: it finished inside the smart wait, in which case there is nothing
150
+ * to show a spinner for, or it did not, and until now that produced ONE SENTENCE of prose asking the agent
151
+ * to poll. Every video takes that path. So the person who waited longest got the least: a paragraph, while
152
+ * the same job in the studio shows placeholder cards filling in.
153
+ *
154
+ * ⛔ **THE FIX IS NOT TO MAKE `generate_*` RETURN EARLY.** That would hand every host the pending path,
155
+ * including hosts with no MCP Apps support, which would lose the inline image they get today. The pending
156
+ * path already exists and already reaches exactly the people who are waiting.
157
+ *
158
+ * ⚠️ **THE TEXT STAYS, WORD FOR WORD.** It is what a host without app support renders, and it is what the
159
+ * AGENT reads to know it must poll. The widget is added ALONGSIDE it, not instead of it: an agent that
160
+ * stopped polling because the prose was replaced by a payload it cannot see would leave the generation
161
+ * unclaimed.
162
+ *
163
+ * ⚠️ No model NAME here, and that is deliberate. Resolving one would mean a second network call from
164
+ * inside a `catch`, which is the exact shape that produced a chip flickering between kebab case and title
165
+ * case. The widget polls `get_generation_status`, and the name arrives with the first response.
166
+ */
167
+ export declare function pendingResult(outputId: string, pollAfterSeconds?: number, shape?: PendingShape): CallToolResult;
24
168
  /** Synchronous audio result (already complete on submit). */
25
169
  export declare function audioResult(result: GenerateResult | EditAudioResult): CallToolResult;
26
170
  /**
@@ -46,9 +190,9 @@ export declare function costResult(est: CostEstimate): CallToolResult;
46
190
  * caller that stopped at a partial result believing it was complete would silently lose images,
47
191
  * which is the failure this is meant to prevent, not cause.
48
192
  */
49
- export declare function generationStatusResult(gen: Generation): CallToolResult;
193
+ export declare function generationStatusResult(gen: Generation, attachments?: GeneratedAttachment[], baseUrl?: string): CallToolResult;
50
194
  /** One or more generations (snapshot or post-wait). Falls through to the single form for one id. */
51
- export declare function generationBatchResult(gens: Generation[]): CallToolResult;
195
+ export declare function generationBatchResult(gens: Generation[], attachmentsByOutputId?: Record<string, GeneratedAttachment[]>, baseUrl?: string): CallToolResult;
52
196
  /** A finished transcription: header line plus the transcript body. */
53
197
  export declare function transcriptResult(t: Transcription): CallToolResult;
54
198
  /** List of avatars, each with the fields an agent needs to drive lip-sync. */
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
package/dist/format.js CHANGED
@@ -3,15 +3,138 @@
3
3
  * Tool errors are returned as `isError` results (not thrown) so the agent sees
4
4
  * a readable message instead of a transport failure.
5
5
  */
6
+ // The spec's own spelling of the key that binds a result to its widget. See `server.ts` for why only the
7
+ // constants come from this package and not its server helpers.
8
+ import { RESOURCE_URI_META_KEY } from '@modelcontextprotocol/ext-apps';
9
+ import { GENERATION_WIDGET_URI } from './widget-uri.js';
6
10
  import { ContentHeroError, InsufficientCreditsError, RateLimitError } from '@contenthero/sdk';
7
11
  export function text(body, isError = false) {
8
12
  return { content: [{ type: 'text', text: body }], isError };
9
13
  }
10
- /** A finished image/video generation: list the asset URLs, plus the placement outcome when placed on a project. */
11
- export function completedResult(gen) {
14
+ /**
15
+ * A finished generation: the asset URLs and placement outcome, plus the asset ITSELF.
16
+ *
17
+ * ## Why this used to be text only
18
+ *
19
+ * 🚨 **AN AGENT THAT GENERATES AN IMAGE COULD NOT SEE IT.** This returned a header, an outputId and a
20
+ * numbered list of urls, so the model had metadata and nothing else. Measured 2026-09-19 in a real ChatGPT
21
+ * and Claude session: Claude said "I can't see the image myself, only the metadata", and the link it
22
+ * surfaced was dead on arrival because ChatGPT had appended `utm_source=chatgpt.com` to a presigned URL and
23
+ * broken its signature.
24
+ *
25
+ * ⭐ The machinery already existed and was wired to the wrong tools: `mediaBatchResult` and
26
+ * `liveContextResult` have pushed image blocks for a while. Generation, the surface where a user most wants
27
+ * to SEE the result, was the one that did not.
28
+ *
29
+ * ## ⛔ THE AGENT CANNOT SEE WHAT IT MADE FROM THIS RESULT, AND THAT IS DELIBERATE
30
+ *
31
+ * An `image` block feeds the MODEL's vision; a `resource_link` gives the HOST something to render for the
32
+ * human. So the user sees every variation inline, and the model has a name and a url.
33
+ *
34
+ * ⭐ **`get_media` IS HOW A MODEL ACTUALLY LOOKS AT SOMETHING.** It embeds bytes as image blocks for exactly
35
+ * that purpose, so an agent that needs to judge a result (is the hand wrong, is the text legible, which of
36
+ * these four is best) calls it with the outputId. Embedding bytes HERE instead would spend the user's
37
+ * context on every generation to answer a question they may never ask, and a four-image batch would pay
38
+ * that cost four times over, repeated in every later turn.
39
+ *
40
+ * ⚠️ ATTACHMENTS ARE BUILT BY THE CALLER, not here. This module stays pure, the same split
41
+ * `mediaBatchResult` already uses: the handler decides, the formatter assembles.
42
+ */
43
+ /**
44
+ * What the generation WIDGET reads.
45
+ *
46
+ * ⭐⭐ `structuredContent` IS THE WIDGET'S ONLY INPUT. It is a separate channel from `content`: the blocks
47
+ * feed the model and any host without app support, this feeds the UI. Both are emitted, so nothing regresses
48
+ * where widgets are unsupported and nothing is duplicated where they are.
49
+ *
50
+ * ⚠️ URLS ONLY, NEVER BYTES. The widget runs in the host's frame and fetches media itself, and our
51
+ * capability urls carry their token in the QUERY STRING, so `<video src>` loads one directly with no header
52
+ * to set. Embedding base64 here would pay the context cost twice over.
53
+ */
54
+ /**
55
+ * Where one output lives in the product.
56
+ *
57
+ * ## ⭐⭐⭐ COMPUTED HERE, NOT IN THE WIDGET, BECAUSE HERE THE INDEX IS ALREADY A NUMBER
58
+ *
59
+ * The widget's first version parsed the slot back out of the output's NAME (`<id>-3` means slot 2), which
60
+ * is a derivation of something this function has in its hand. Parsing a number out of a string we formatted
61
+ * two lines earlier is how an off-by-one gets in, and the symptom would be "Open shows the wrong picture",
62
+ * which reads as a broken link rather than an index bug.
63
+ *
64
+ * ⚠️ **ONE-BASED IN THE NAME, ZERO-BASED IN THE URL.** `<id>-3` is what a person reads as "variation 3",
65
+ * and `variation=2` is the studio's `imageIndex`, which is slot space. Both conventions are correct in
66
+ * their own place and the conversion belongs at exactly one boundary, which is this one.
67
+ *
68
+ * ⚠️ A single-output generation gets NO `variation`. There is no variation to name, and passing 0 would
69
+ * imply there was a choice.
70
+ */
71
+ /** Where a deep link points when no client base url was threaded through. */
72
+ export const DEFAULT_APP_URL = 'https://app.contenthero.ai';
73
+ export function studioUrlFor(baseUrl, outputId, index, total) {
74
+ const root = baseUrl.replace(/\/+$/, '');
75
+ const variation = total > 1 ? `&variation=${index}` : '';
76
+ return `${root}/studio?output=${encodeURIComponent(outputId)}${variation}`;
77
+ }
78
+ export function generationWidgetData(gen, posterUrls = [], baseUrl = DEFAULT_APP_URL) {
79
+ const urls = gen.outputUrls ?? [];
80
+ return {
81
+ outputId: gen.outputId,
82
+ contentType: gen.contentType,
83
+ modelId: gen.modelId,
84
+ /**
85
+ * ⛔⛔ **NULL WHEN THERE IS NOTHING TO NAME, AND THE WIDGET MUST RENDER NO CHIP.**
86
+ *
87
+ * This used to be `displayName ?? gen.modelId`, fed by a catalog fetch in the server with a `catch`
88
+ * that returned the id. The id reads like a label, so a failed fetch showed as a chip flickering
89
+ * between kebab case and title case rather than as a failure. The name now arrives on the row.
90
+ *
91
+ * ⚠️ It is not always this generation's own model: an upload names itself, and a look names the model
92
+ * that produced the image it was assembled from.
93
+ */
94
+ modelName: gen.modelDisplayName ?? null,
95
+ /** Hex brand accent from the registry, or null. The widget colors its chip with it. */
96
+ modelBrandColor: gen.modelBrandColor ?? null,
97
+ /** Brand family key (e.g. `"openai"`) for choosing a glyph. NOT the model id. */
98
+ modelIconKey: gen.modelIconKey ?? null,
99
+ /** `"W:H"`, or null for audio. Drives the column count and the tile's own shape. */
100
+ displayAspect: gen.displayAspect ?? null,
101
+ /** Verbatim. Some prompts are JSON-shaped because the person authored one; that object IS the prompt. */
102
+ prompt: gen.prompt ?? null,
103
+ outputs: urls.map((url, i) => ({
104
+ url,
105
+ posterUrl: posterUrls[i] ?? null,
106
+ name: `${gen.outputId}${urls.length > 1 ? `-${i + 1}` : ''}`,
107
+ /** Where the Open button goes: this asset, in the studio, with its siblings and every action. */
108
+ studioUrl: studioUrlFor(baseUrl, gen.outputId, i, urls.length),
109
+ })),
110
+ };
111
+ }
112
+ export function completedResult(gen, attachments = [], posterUrls = [],
113
+ /** The server this client talks to, so Open deep-links to it rather than always to production. */
114
+ baseUrl) {
12
115
  const urls = gen.outputUrls ?? [];
13
116
  const noun = urls.length === 1 ? gen.contentType : `${gen.contentType}s`;
14
- const header = `Done. ${urls.length} ${noun} from ${gen.modelId} (outputId ${gen.outputId}):`;
117
+ /**
118
+ * ⭐⭐ **ONE TEXT LIST, AND NO `resource_link` BLOCKS. THREE REPRESENTATIONS OF ONE URL WAS TWO TOO MANY.**
119
+ *
120
+ * This went through both extremes before landing here. Listing the urls in prose AND attaching a link per
121
+ * output printed every url twice. Suppressing the prose left the links alone, and a host renders those as
122
+ * `name: uri` with NO SEPARATOR BETWEEN THEM, so url 1 ended flush against filename 2 and anything
123
+ * splitting on whitespace read a corrupted token. Measured at all three boundaries of a four-image batch.
124
+ *
125
+ * ⛔ THE SEPARATOR WAS NEVER OURS TO ADD. Our text block ends with a newline; the run-together is the host
126
+ * concatenating sibling blocks, and no content we emit can put a break between two of them.
127
+ *
128
+ * ⭐ So the fallback is the thing we fully control: one newline-delimited list. The widget is the surface
129
+ * that renders, and this is what a host without app support (or a model reading the transcript) gets. It
130
+ * costs less than the links did and it cannot be run together by anybody.
131
+ */
132
+ /**
133
+ * ⭐ **THE ID IS AN ACCEPTABLE FALLBACK HERE AND NOWHERE ELSE.** This block's reader is the model, for
134
+ * whom `gpt-image-2` is a true and directly useful token. The widget's chip has a human reader, for whom
135
+ * the same string is an unexplained failure wearing a label's clothes, so there it renders as nothing.
136
+ */
137
+ const header = `Done. ${urls.length} ${noun} from ${gen.modelDisplayName ?? gen.modelId} (outputId ${gen.outputId}):`;
15
138
  const lines = [header, ...urls.map((u, i) => `${i + 1}. ${u}`)];
16
139
  const p = gen.placement;
17
140
  if (p) {
@@ -24,7 +147,34 @@ export function completedResult(gen) {
24
147
  if (p.warnings?.length)
25
148
  lines.push(`Placement notes: ${p.warnings.join('; ')}`);
26
149
  }
27
- return text(lines.join('\n'));
150
+ // ⚠️ A TRAILING NEWLINE, because a host concatenates blocks without inserting one. Without it the last
151
+ // url ran straight into the next block's rendering, producing `...MBCo_Kkcfe3bafb-...-1.png: https://...`
152
+ // and a token that anything splitting on whitespace would read as part of the filename.
153
+ const content = [{ type: 'text', text: lines.join('\n') + '\n' }];
154
+ for (const a of attachments) {
155
+ if (a.kind === 'bytes') {
156
+ content.push({ type: a.type, data: a.data, mimeType: a.mimeType });
157
+ }
158
+ else {
159
+ // A `resource_link` names the bytes without carrying them. The uri is a capability url, so it does not
160
+ // expire and appended query parameters cannot invalidate it.
161
+ content.push({ type: 'resource_link', uri: a.uri, name: a.name, mimeType: a.mimeType });
162
+ }
163
+ }
164
+ /**
165
+ * ⭐⭐⭐ **THE `_meta` KEY IS WHAT MAKES A WIDGET APPEAR.** Without it the host has an HTML resource it was
166
+ * never told to mount, and the result renders as blocks alone. With it, a host that supports MCP Apps
167
+ * shows the widget and a host that does not ignores the key entirely.
168
+ *
169
+ * ⚠️ Emitted ALONGSIDE the blocks, never instead of them. Two mechanisms, one of which is better where it
170
+ * exists: ChatGPT and Claude get the widget, anything else still gets an image it can draw.
171
+ */
172
+ return {
173
+ content,
174
+ isError: false,
175
+ structuredContent: generationWidgetData(gen, posterUrls, baseUrl),
176
+ _meta: { [RESOURCE_URI_META_KEY]: GENERATION_WIDGET_URI, ui: { resourceUri: GENERATION_WIDGET_URI } },
177
+ };
28
178
  }
29
179
  /** Suggested seconds to wait before re-polling a job, by content type. */
30
180
  export function pollAfterSecondsFor(contentType) {
@@ -41,9 +191,52 @@ export function pollAfterSecondsFor(contentType) {
41
191
  export function getStatusCall(outputIds) {
42
192
  return `get_generation_status { outputIds: [${outputIds.map((id) => `"${id}"`).join(', ')}] }`;
43
193
  }
44
- /** A slow job that did not finish within the smart-wait window. */
45
- export function pendingResult(outputId, pollAfterSeconds = 15) {
46
- return text(`Still rendering (outputId ${outputId}). This is normal for video. Call ${getStatusCall([outputId])} in ~${pollAfterSeconds}s [poll_after_seconds: ${pollAfterSeconds}] to get the final URLs.`);
194
+ /**
195
+ * A slow job that did not finish within the smart-wait window.
196
+ *
197
+ * ## ⭐⭐⭐ THIS IS WHERE THE SKELETONS COME FROM, AND WHY IT IS THE ONLY PLACE THEY COULD
198
+ *
199
+ * A generation returns one of two ways: it finished inside the smart wait, in which case there is nothing
200
+ * to show a spinner for, or it did not, and until now that produced ONE SENTENCE of prose asking the agent
201
+ * to poll. Every video takes that path. So the person who waited longest got the least: a paragraph, while
202
+ * the same job in the studio shows placeholder cards filling in.
203
+ *
204
+ * ⛔ **THE FIX IS NOT TO MAKE `generate_*` RETURN EARLY.** That would hand every host the pending path,
205
+ * including hosts with no MCP Apps support, which would lose the inline image they get today. The pending
206
+ * path already exists and already reaches exactly the people who are waiting.
207
+ *
208
+ * ⚠️ **THE TEXT STAYS, WORD FOR WORD.** It is what a host without app support renders, and it is what the
209
+ * AGENT reads to know it must poll. The widget is added ALONGSIDE it, not instead of it: an agent that
210
+ * stopped polling because the prose was replaced by a payload it cannot see would leave the generation
211
+ * unclaimed.
212
+ *
213
+ * ⚠️ No model NAME here, and that is deliberate. Resolving one would mean a second network call from
214
+ * inside a `catch`, which is the exact shape that produced a chip flickering between kebab case and title
215
+ * case. The widget polls `get_generation_status`, and the name arrives with the first response.
216
+ */
217
+ export function pendingResult(outputId, pollAfterSeconds = 15, shape) {
218
+ const prose = `Still rendering (outputId ${outputId}). This is normal for video. Call ${getStatusCall([outputId])} in ~${pollAfterSeconds}s [poll_after_seconds: ${pollAfterSeconds}] to get the final URLs.`;
219
+ if (!shape)
220
+ return text(prose);
221
+ return {
222
+ content: [{ type: 'text', text: prose }],
223
+ structuredContent: {
224
+ outputId,
225
+ status: 'processing',
226
+ contentType: shape.contentType,
227
+ modelId: shape.modelId,
228
+ modelName: null,
229
+ modelBrandColor: null,
230
+ modelIconKey: null,
231
+ displayAspect: shape.displayAspect ?? null,
232
+ prompt: null,
233
+ /** At least one, or the widget renders a grid with nothing in it and looks broken rather than busy. */
234
+ expected: Math.max(1, shape.expected ?? 1),
235
+ pollAfterSeconds,
236
+ outputs: [],
237
+ },
238
+ _meta: { [RESOURCE_URI_META_KEY]: GENERATION_WIDGET_URI, ui: { resourceUri: GENERATION_WIDGET_URI } },
239
+ };
47
240
  }
48
241
  /** Synchronous audio result (already complete on submit). */
49
242
  export function audioResult(result) {
@@ -96,9 +289,18 @@ export function costResult(est) {
96
289
  * caller that stopped at a partial result believing it was complete would silently lose images,
97
290
  * which is the failure this is meant to prevent, not cause.
98
291
  */
99
- export function generationStatusResult(gen) {
292
+ export function generationStatusResult(gen, attachments = [], baseUrl) {
293
+ /**
294
+ * ⛔⛔ **THIS DROPPED THE ATTACHMENTS AND THEREFORE RENDERED NOTHING.** It called `completedResult(gen)`
295
+ * with no second argument, so POLLING returned text alone even after the generate handlers started
296
+ * attaching blocks. That is the path EVERY async generation takes, which is every video, so the common
297
+ * case stayed blank while the synchronous one worked.
298
+ *
299
+ * ⭐ Found by the `verify:inline` harness in its first run, minutes after it existed. The unit tests could
300
+ * not see it: they call `completedResult` directly and never go through here.
301
+ */
100
302
  if (gen.status === 'completed')
101
- return completedResult(gen);
303
+ return completedResult(gen, attachments, [], baseUrl);
102
304
  if (gen.status === 'failed') {
103
305
  return text(`Generation ${gen.outputId} failed: ${gen.error ?? 'unknown error'}`, true);
104
306
  }
@@ -116,9 +318,12 @@ export function generationStatusResult(gen) {
116
318
  ].join('\n'));
117
319
  }
118
320
  /** One or more generations (snapshot or post-wait). Falls through to the single form for one id. */
119
- export function generationBatchResult(gens) {
321
+ export function generationBatchResult(gens, attachmentsByOutputId = {}, baseUrl) {
322
+ // ⚠️ ONLY THE SINGLE FORM ATTACHES. A batch status covering ten generations would embed ten sets of
323
+ // bytes into one result, which is the context blow-up the link design was originally protecting against.
324
+ // The single form is what a caller polling one generation hits, and that is the case worth rendering.
120
325
  if (gens.length === 1)
121
- return generationStatusResult(gens[0]);
326
+ return generationStatusResult(gens[0], attachmentsByOutputId[gens[0].outputId] ?? [], baseUrl);
122
327
  const rows = gens.map((gen) => {
123
328
  if (gen.status === 'completed') {
124
329
  const urls = gen.outputUrls ?? [];
@@ -1133,7 +1338,7 @@ export function projectDetailResult(p) {
1133
1338
  return text(`Project ${p.id}: "${p.title}" (${p.kind}, ${p.orientation} ${p.width}x${p.height}), revision ${p.revision}.\n` +
1134
1339
  `Pass this revision back as expectedRevision when you edit.\n` +
1135
1340
  // The output resolution above is NOT the coordinate space layer geometry uses. Stating both, adjacent
1136
- // and labelled, is the point: an agent that read only "2168x1152" sized every layer 2.26x too large
1341
+ // and labeled, is the point: an agent that read only "2168x1152" sized every layer 2.26x too large
1137
1342
  // and got no error for it, because an oversized box is valid input.
1138
1343
  (p.compositionSpace
1139
1344
  ? `Layer geometry is in composition space ${p.compositionSpace.width}x${p.compositionSpace.height} ` +