@contenthero/mcp 0.4.8 → 0.4.10
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/format.d.ts +100 -5
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +177 -11
- package/dist/format.js.map +1 -1
- package/dist/server.d.ts +68 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +387 -26
- package/dist/server.js.map +1 -1
- package/dist/widget/generation.html +516 -0
- package/dist/widget-uri.d.ts +12 -0
- package/dist/widget-uri.d.ts.map +1 -0
- package/dist/widget-uri.js +12 -0
- package/dist/widget-uri.js.map +1 -0
- package/package.json +13 -4
package/dist/format.d.ts
CHANGED
|
@@ -56,7 +56,71 @@ export type GeneratedAttachment = {
|
|
|
56
56
|
* ⚠️ ATTACHMENTS ARE BUILT BY THE CALLER, not here. This module stays pure, the same split
|
|
57
57
|
* `mediaBatchResult` already uses: the handler decides, the formatter assembles.
|
|
58
58
|
*/
|
|
59
|
-
|
|
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;
|
|
60
124
|
/** Suggested seconds to wait before re-polling a job, by content type. */
|
|
61
125
|
export declare function pollAfterSecondsFor(contentType: string): number;
|
|
62
126
|
/**
|
|
@@ -68,8 +132,39 @@ export declare function pollAfterSecondsFor(contentType: string): number;
|
|
|
68
132
|
* is what drifted, so these messages now print the call instead, and every site shares this one function.
|
|
69
133
|
*/
|
|
70
134
|
export declare function getStatusCall(outputIds: readonly string[]): string;
|
|
71
|
-
/**
|
|
72
|
-
export
|
|
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;
|
|
73
168
|
/** Synchronous audio result (already complete on submit). */
|
|
74
169
|
export declare function audioResult(result: GenerateResult | EditAudioResult): CallToolResult;
|
|
75
170
|
/**
|
|
@@ -95,9 +190,9 @@ export declare function costResult(est: CostEstimate): CallToolResult;
|
|
|
95
190
|
* caller that stopped at a partial result believing it was complete would silently lose images,
|
|
96
191
|
* which is the failure this is meant to prevent, not cause.
|
|
97
192
|
*/
|
|
98
|
-
export declare function generationStatusResult(gen: Generation): CallToolResult;
|
|
193
|
+
export declare function generationStatusResult(gen: Generation, attachments?: GeneratedAttachment[], baseUrl?: string): CallToolResult;
|
|
99
194
|
/** One or more generations (snapshot or post-wait). Falls through to the single form for one id. */
|
|
100
|
-
export declare function generationBatchResult(gens: Generation[]): CallToolResult;
|
|
195
|
+
export declare function generationBatchResult(gens: Generation[], attachmentsByOutputId?: Record<string, GeneratedAttachment[]>, baseUrl?: string): CallToolResult;
|
|
101
196
|
/** A finished transcription: header line plus the transcript body. */
|
|
102
197
|
export declare function transcriptResult(t: Transcription): CallToolResult;
|
|
103
198
|
/** List of avatars, each with the fields an agent needs to drive lip-sync. */
|
package/dist/format.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
|
package/dist/format.js
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
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 };
|
|
@@ -36,10 +40,101 @@ export function text(body, isError = false) {
|
|
|
36
40
|
* ⚠️ ATTACHMENTS ARE BUILT BY THE CALLER, not here. This module stays pure, the same split
|
|
37
41
|
* `mediaBatchResult` already uses: the handler decides, the formatter assembles.
|
|
38
42
|
*/
|
|
39
|
-
|
|
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) {
|
|
40
115
|
const urls = gen.outputUrls ?? [];
|
|
41
116
|
const noun = urls.length === 1 ? gen.contentType : `${gen.contentType}s`;
|
|
42
|
-
|
|
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}):`;
|
|
43
138
|
const lines = [header, ...urls.map((u, i) => `${i + 1}. ${u}`)];
|
|
44
139
|
const p = gen.placement;
|
|
45
140
|
if (p) {
|
|
@@ -52,7 +147,10 @@ export function completedResult(gen, attachments = []) {
|
|
|
52
147
|
if (p.warnings?.length)
|
|
53
148
|
lines.push(`Placement notes: ${p.warnings.join('; ')}`);
|
|
54
149
|
}
|
|
55
|
-
|
|
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' }];
|
|
56
154
|
for (const a of attachments) {
|
|
57
155
|
if (a.kind === 'bytes') {
|
|
58
156
|
content.push({ type: a.type, data: a.data, mimeType: a.mimeType });
|
|
@@ -63,7 +161,20 @@ export function completedResult(gen, attachments = []) {
|
|
|
63
161
|
content.push({ type: 'resource_link', uri: a.uri, name: a.name, mimeType: a.mimeType });
|
|
64
162
|
}
|
|
65
163
|
}
|
|
66
|
-
|
|
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
|
+
};
|
|
67
178
|
}
|
|
68
179
|
/** Suggested seconds to wait before re-polling a job, by content type. */
|
|
69
180
|
export function pollAfterSecondsFor(contentType) {
|
|
@@ -80,9 +191,52 @@ export function pollAfterSecondsFor(contentType) {
|
|
|
80
191
|
export function getStatusCall(outputIds) {
|
|
81
192
|
return `get_generation_status { outputIds: [${outputIds.map((id) => `"${id}"`).join(', ')}] }`;
|
|
82
193
|
}
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
};
|
|
86
240
|
}
|
|
87
241
|
/** Synchronous audio result (already complete on submit). */
|
|
88
242
|
export function audioResult(result) {
|
|
@@ -135,9 +289,18 @@ export function costResult(est) {
|
|
|
135
289
|
* caller that stopped at a partial result believing it was complete would silently lose images,
|
|
136
290
|
* which is the failure this is meant to prevent, not cause.
|
|
137
291
|
*/
|
|
138
|
-
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
|
+
*/
|
|
139
302
|
if (gen.status === 'completed')
|
|
140
|
-
return completedResult(gen);
|
|
303
|
+
return completedResult(gen, attachments, [], baseUrl);
|
|
141
304
|
if (gen.status === 'failed') {
|
|
142
305
|
return text(`Generation ${gen.outputId} failed: ${gen.error ?? 'unknown error'}`, true);
|
|
143
306
|
}
|
|
@@ -155,9 +318,12 @@ export function generationStatusResult(gen) {
|
|
|
155
318
|
].join('\n'));
|
|
156
319
|
}
|
|
157
320
|
/** One or more generations (snapshot or post-wait). Falls through to the single form for one id. */
|
|
158
|
-
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.
|
|
159
325
|
if (gens.length === 1)
|
|
160
|
-
return generationStatusResult(gens[0]);
|
|
326
|
+
return generationStatusResult(gens[0], attachmentsByOutputId[gens[0].outputId] ?? [], baseUrl);
|
|
161
327
|
const rows = gens.map((gen) => {
|
|
162
328
|
if (gen.status === 'completed') {
|
|
163
329
|
const urls = gen.outputUrls ?? [];
|