@avocadostudio-ai/orchestrator-core 0.14.0 → 0.16.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.
@@ -1,6 +1,21 @@
1
1
  import type { Logger } from "../logger.ts";
2
- import { type EditPlan, type PageDoc } from "@avocadostudio-ai/shared";
2
+ import { type EditPlan, type Operation, type PageDoc } from "@avocadostudio-ai/shared";
3
3
  import type { PendingImageGeneration } from "../state/session-state.ts";
4
+ import { resolveUnsplashImage } from "../image/image-helpers.ts";
5
+ export declare function setChatImageResolverForTests(fn?: typeof resolveUnsplashImage): void;
6
+ /**
7
+ * Which prop on this block an image belongs in, or null if none does.
8
+ *
9
+ * Used by the last-resort path: the message asked for a photo, the planner
10
+ * produced no image op, so find somewhere on the page to put one. It looked
11
+ * for a literal `imageUrl` prop, which meant that on a site whose hero image
12
+ * field is `backgroundImage` the fallback found nothing and the request
13
+ * quietly did nothing at all.
14
+ *
15
+ * Chrome blocks are excluded: the header and the footer own the logo, and
16
+ * "add a photo" has never meant "replace the logo".
17
+ */
18
+ export declare function primaryImageKeyFor(block: PageDoc["blocks"][number] | null | undefined): string | null;
4
19
  export declare function blockHasImageUrlProp(block: PageDoc["blocks"][number] | null | undefined): block is PageDoc["blocks"][number];
5
20
  export declare function parsePath(path: string): Array<string | number>;
6
21
  export declare function getValueAtPath(root: unknown, path: string): unknown;
@@ -13,11 +28,113 @@ export declare function extractReferencedItemIndices(message: string): {
13
28
  hasConstraint: boolean;
14
29
  };
15
30
  /**
16
- * Check whether a block type's schema supports imageUrl at a given path.
31
+ * Check whether a block type's schema declares an image at a given path.
17
32
  * E.g. "imageUrl" → true for Hero, "features[0].imageUrl" → false for FeatureGrid.
33
+ *
34
+ * The final key is read against the block's own field metadata rather than
35
+ * compared to the literal `imageUrl`, so a block whose image field is called
36
+ * `backgroundImage` or `photo` answers for it too.
18
37
  */
19
38
  export declare function blockSupportsImageAtPath(blockType: string, imagePath: string): boolean;
20
- export declare function detectImagePaths(value: unknown, basePath?: string, acc?: Set<string>): Set<string>;
39
+ /**
40
+ * Every path inside `value` that holds an image URL.
41
+ *
42
+ * Pass `blockType` and the block's declared `kind: "image"` fields are what
43
+ * counts; without it — a patch whose target is unknown, or a block that ships
44
+ * no field metadata — only the built-in `imageUrl` name is recognised, which
45
+ * is what this did for every caller before the block type was threaded in.
46
+ */
47
+ export declare function detectImagePaths(value: unknown, opts?: {
48
+ blockType?: string;
49
+ }): Set<string>;
50
+ /**
51
+ * Is the field the user has selected in the preview an image field?
52
+ *
53
+ * The overlay reports the path it marked — `imageUrl`, `cards[2].photo` — and
54
+ * the pipeline used to compare it to the string "imageUrl", so selecting the
55
+ * image on any block that names the field differently read as "the user is not
56
+ * looking at an image".
57
+ */
58
+ export declare function isImageEditablePath(blockType: string | undefined, editablePath: string | undefined): boolean;
59
+ /**
60
+ * Where the alt text for the image at `imagePath` belongs.
61
+ *
62
+ * Built-in blocks pair `imageUrl` with `imageAlt`, which is why the pairing
63
+ * used to be a string replace — but a Gallery item pairs `imageUrl` with
64
+ * `alt`, so that replace produced a key the Gallery does not have. The
65
+ * registry knows the pairing; ask it, and keep the old replace for blocks that
66
+ * declare nothing.
67
+ */
68
+ export declare function imageAltPathFor(blockType: string | undefined, imagePath: string): string;
69
+ /**
70
+ * A props patch that writes `value` at `path` without collapsing the list the
71
+ * path runs through.
72
+ *
73
+ * `update_props` merges a list entry by entry and keeps the *patch's* length,
74
+ * so the obvious construction — `setValueAtPath({}, "cards[0].imageUrl", url)`
75
+ * → `{ cards: [{ imageUrl: url }] }` — takes a three-card grid down to one
76
+ * card. The list has to be rebuilt at the length it currently has, with every
77
+ * entry but the addressed one left empty for the merge to fill back in.
78
+ */
79
+ export declare function buildImagePatchProps(blockProps: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>;
80
+ /**
81
+ * Write `value` at `path` in a patch that is being edited in place, without
82
+ * shortening the list the path runs through.
83
+ *
84
+ * `setValueAtPath` alone creates the list it needs and no more, so writing
85
+ * `cards[0].imageUrl` into a patch that says nothing about `cards` produces a
86
+ * one-element array — and update_props keeps the patch's length, so applying it
87
+ * deletes every other card. That is not hypothetical: a turn asking for the
88
+ * first card's image planned an unrelated `{ title }` patch, the image pass
89
+ * added `cards[0].imageUrl` to it, and a three-card grid came back with one
90
+ * card in it.
91
+ *
92
+ * A patch that already carries the list is left at the length it states — a
93
+ * plan replacing three cards with two means it.
94
+ */
95
+ export declare function setImageInPatch(patch: Record<string, unknown>, blockProps: Record<string, unknown>, path: string, value: unknown): void;
96
+ /**
97
+ * Every place one op writes an image, and how to write a different image there.
98
+ *
99
+ * The deferred image passes used to ask a plan a single question — "is
100
+ * `patch.imageUrl` a placeholder?" — which is only ever true of an
101
+ * `update_props` op on a block whose image is a top-level prop. A card's image
102
+ * is neither of those things. The planner addresses one entry of a list with
103
+ * `update_item`, and the `update_props` form of the same edit nests the path
104
+ * (`cards[0].imageUrl`); both were invisible to those passes. So a turn that
105
+ * searched Unsplash twice, got two photos back and reported that it had swapped
106
+ * one in had nowhere to put either of them, and the placeholder the planner is
107
+ * told to write stayed on the page.
108
+ *
109
+ * `rewrite` is part of the site rather than left to the caller because the op
110
+ * that writes the image back is not the same shape as the op that asked for it
111
+ * — see `buildImagePatchProps` for the list-length trap it avoids.
112
+ */
113
+ export type ImagePatchSite = {
114
+ blockId: string;
115
+ pageSlug: string;
116
+ /** The block being patched, when the page state knows it. */
117
+ blockType: string | undefined;
118
+ /** The image's path, relative to this op's patch. */
119
+ path: string;
120
+ /** Where this image's alt text belongs, relative to this op's patch. */
121
+ altPath: string;
122
+ /** What the op writes at `path` today. */
123
+ value: string;
124
+ /** What the op writes at `altPath` today, when it writes anything. */
125
+ alt: string | undefined;
126
+ /** An op writing `url` (and `alt`, when given) to the same place. */
127
+ rewrite: (url: string, alt?: string) => Operation;
128
+ };
129
+ export declare function findImagePatchSites(op: Operation,
130
+ /** The page as it stands now — a lookup, so the caller needs no op narrowing of its own. */
131
+ pageAt: (slug: string) => PageDoc | null | undefined): ImagePatchSite[];
132
+ /**
133
+ * Do two ops address the same image? Block identity is enough for
134
+ * `update_props`; an item op also has to agree on which entry of which list,
135
+ * or a plan touching two cards would patch the wrong one.
136
+ */
137
+ export declare function opsAddressSameTarget(a: Operation, b: Operation): boolean;
21
138
  export declare function imageQueryFromItem(item: Record<string, unknown>, sectionContext?: string): string;
22
139
  export declare function shouldPopulateAllChildImages(message: string): boolean;
23
140
  export declare function findImageTargets(args: {
@@ -25,11 +142,29 @@ export declare function findImageTargets(args: {
25
142
  currentPage: PageDoc;
26
143
  targetBlock: PageDoc["blocks"][number];
27
144
  patchCandidate: Record<string, unknown>;
145
+ /**
146
+ * The op's `imageQuery` — what the planner said the picture should show.
147
+ * It becomes this block's default subject; a list item that names its own
148
+ * subject (a card's title, an indexed "third one: a dog") still wins, because
149
+ * that is more specific than one phrase for the whole block.
150
+ */
151
+ plannerQuery?: string;
28
152
  }): {
29
153
  path: string;
30
154
  altPath: string;
31
155
  query: string;
32
156
  }[];
157
+ /**
158
+ * The image subject the planner stated anywhere in this plan.
159
+ *
160
+ * The two hero fallbacks below fire when no op yielded an image target — the
161
+ * plan named a block this page does not have, or patched nothing an image
162
+ * could hang on. The request was still about an image and the planner still
163
+ * said what it should show, so that answer beats re-deriving one from the
164
+ * sentence. Ops are checked in order and the first stated subject wins; a plan
165
+ * that resolves several images by this route is not a case worth guessing at.
166
+ */
167
+ export declare function statedImageQuery(plan: EditPlan): string | undefined;
33
168
  export declare function rewriteAddBlockToChildImageUpdate(args: {
34
169
  plan: EditPlan;
35
170
  message: string;
@@ -76,6 +211,37 @@ export declare function resolveHeroImageForCreatePage(args: {
76
211
  alt: string;
77
212
  source: "ai-generated" | "gdrive" | "unsplash";
78
213
  } | null>;
214
+ /**
215
+ * Is this patch all structure and no content — nothing but empty objects and
216
+ * empty lists of them?
217
+ *
218
+ * `{}` is obviously nothing. `{ cards: [{}, {}, {}] }` is the same nothing
219
+ * wearing the shape a list patch has to wear to keep its length, and the ops
220
+ * engine skips it as an unchanged value. Callers that need to know whether
221
+ * stripping a patch left anything behind have to ask the second question too.
222
+ */
223
+ export declare function isVacuousPatch(value: unknown): boolean;
224
+ /**
225
+ * Rewrite an `update_item` that sets an image into the nested `update_props`
226
+ * form, so the image passes can see it at all.
227
+ *
228
+ * `detectImageOps` and `withUnsplashHeroImage` both walk `update_props` ops and
229
+ * read image paths out of the block's merged props — `cards[0].imageUrl`. An
230
+ * item op carries exactly that edit in a shape neither reads. For a planner
231
+ * with image tools to call that only costs a detour; for one without them —
232
+ * OpenAI defers no image tools, and it is the default planner — those two
233
+ * passes are the *only* route to a real photo, so the stand-in the prompt tells
234
+ * the model to write was the final answer. A card kept `/hero-generated.svg`
235
+ * under a summary saying a photo had been found.
236
+ *
237
+ * Only item ops that actually set an image are rewritten. An item op changing
238
+ * text keeps its shape, which addresses the entry far more precisely.
239
+ */
240
+ export declare function rewriteItemImageUpdateToProps(args: {
241
+ plan: EditPlan;
242
+ currentPage: PageDoc;
243
+ slug: string;
244
+ }): EditPlan;
79
245
  export declare function detectImageOps(args: {
80
246
  plan: EditPlan;
81
247
  message: string;