@agent-native/core 0.78.0 → 0.78.2
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +15 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +269 -237
- package/corpus/core/src/agent/types.ts +22 -0
- package/corpus/core/src/client/AgentPanel.tsx +8 -0
- package/corpus/core/src/client/AssistantChat.tsx +40 -0
- package/corpus/core/src/client/agent-chat.ts +173 -0
- package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
- package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
- package/corpus/core/src/client/composer/types.ts +22 -0
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
- package/corpus/core/src/server/request-context.ts +8 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +13 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
- package/corpus/templates/assets/AGENTS.md +18 -3
- package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
- package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
- package/corpus/templates/assets/actions/generate-asset.ts +30 -23
- package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
- package/corpus/templates/assets/actions/generate-image.ts +46 -30
- package/corpus/templates/assets/actions/generate-video.ts +18 -3
- package/corpus/templates/assets/actions/list-assets.ts +39 -8
- package/corpus/templates/assets/actions/list-libraries.ts +24 -2
- package/corpus/templates/assets/actions/navigate.ts +1 -1
- package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
- package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
- package/corpus/templates/assets/actions/variant-slots.ts +131 -28
- package/corpus/templates/assets/actions/view-screen.ts +9 -0
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
- package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
- package/corpus/templates/assets/app/i18n-data.ts +1815 -0
- package/corpus/templates/assets/app/lib/libraries.ts +6 -0
- package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/audit.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
- package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
- package/corpus/templates/assets/app/routes/library.tsx +1454 -175
- package/corpus/templates/assets/server/db/index.ts +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +86 -15
- package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
- package/corpus/templates/assets/server/plugins/db.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +6 -3
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +50 -29
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +21 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +15 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +32 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +113 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +2 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +228 -56
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/MentionReference.js +3 -0
- package/dist/client/composer/extensions/MentionReference.js.map +1 -1
- package/dist/client/composer/types.d.ts +21 -0
- package/dist/client/composer/types.d.ts.map +1 -1
- package/dist/client/composer/types.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/request-context.d.ts +8 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getOrgContext } from "@agent-native/core/org";
|
|
2
2
|
import {
|
|
3
3
|
createAgentChatPlugin,
|
|
4
|
+
getRequestRunContext,
|
|
4
5
|
loadActionsFromStaticRegistry,
|
|
5
6
|
type AgentLoopFinalResponseGuardContext,
|
|
6
7
|
} from "@agent-native/core/server";
|
|
@@ -197,6 +198,18 @@ export default createAgentChatPlugin({
|
|
|
197
198
|
"After creating the extension, briefly tell the user that the request needed bespoke UI/code beyond the native Analytics dashboard or analysis format, so you built it as an extension.\n" +
|
|
198
199
|
"</analytics-artifact-guidance>";
|
|
199
200
|
|
|
201
|
+
// In the durable background-function worker, skip the data-dictionary read
|
|
202
|
+
// + render. That settings read + synchronous render is the heaviest, most
|
|
203
|
+
// hang-prone part of worker setup on a cold bg-fn instance, and it runs
|
|
204
|
+
// EAGERLY while the system prompt is built (before any pre-send timeout can
|
|
205
|
+
// arm), so a stall here is what kept the analytics worker from ever claiming
|
|
206
|
+
// its run. The agent can still pull dictionary entries on demand via
|
|
207
|
+
// `list-data-dictionary` / `search-bigquery-schema`; the static guidance is
|
|
208
|
+
// what it needs to know they exist. Foreground requests keep the full dict.
|
|
209
|
+
if (getRequestRunContext()?.isBackgroundWorker) {
|
|
210
|
+
return `${sourceGuidance}\n\n${artifactGuidance}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
200
213
|
try {
|
|
201
214
|
const scope = await resolveSettingsScope(event);
|
|
202
215
|
const all = await listScopedSettingRecords(scope, DATA_DICT_PREFIX);
|
|
@@ -21,12 +21,19 @@ or generated image/video assets that another app can reference by ID and URL.
|
|
|
21
21
|
when `libraryId` is omitted, generates candidates, returns the picker filtered
|
|
22
22
|
to those run IDs, and works in in-app chat plus external MCP hosts.
|
|
23
23
|
- Use `open-asset-picker` when a person should browse, search, or select an
|
|
24
|
-
existing asset
|
|
24
|
+
existing asset inside an embedded picker, or when you want the picker to
|
|
25
|
+
handle generation itself. It still opens `/library` with the iframe/bridge
|
|
26
|
+
contract. The normal human Library workspace is `/library` and `/library/:id`.
|
|
25
27
|
Pass `mediaType: "image"` by default, or `mediaType: "video"` for video
|
|
26
28
|
libraries.
|
|
27
29
|
- Use unattended actions when the agent already knows what to do:
|
|
28
30
|
`search-assets`, `list-assets`, `generate-image`, `generate-image-batch`,
|
|
29
31
|
`generate-video`, `refresh-generation-run`, and `export-asset`.
|
|
32
|
+
- In chat, consume composer `@` references as structured generation inputs:
|
|
33
|
+
`brand-kit` maps to `libraryId`, `preset` maps to `presetId`, and
|
|
34
|
+
`media-type` chooses image generation versus video generation. If no mention
|
|
35
|
+
is available, use `view-screen`, `list-libraries`, and
|
|
36
|
+
`list-generation-presets` to choose explicit args.
|
|
30
37
|
- Use generation presets when the user asks for a repeatable output format
|
|
31
38
|
like social image, blog hero, or diagram. Call `list-generation-presets` for
|
|
32
39
|
the library and pass `presetId` through generation/refinement actions.
|
|
@@ -58,7 +65,8 @@ or generated image/video assets that another app can reference by ID and URL.
|
|
|
58
65
|
`generate-image-batch` returns, use its returned `images` / asset fields
|
|
59
66
|
directly; do not call `get-generation-run`, `refresh-generation-run`, or
|
|
60
67
|
regenerate just to verify image runs.
|
|
61
|
-
4. For preset-backed work, pass `presetId`; for handoff
|
|
68
|
+
4. For preset-backed work, pass a mentioned or selected `presetId`; for handoff
|
|
69
|
+
work, pass `sessionId`.
|
|
62
70
|
5. Let the server choose a small deterministic reference set unless the user
|
|
63
71
|
named exact assets. Canonical style anchors come from
|
|
64
72
|
`assetLibraries.settings.canonicalStyleAssetIds` and
|
|
@@ -10,8 +10,13 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
|
|
|
10
10
|
|
|
11
11
|
## Rules
|
|
12
12
|
|
|
13
|
-
- Start from
|
|
14
|
-
|
|
13
|
+
- Start from composer `@` mentions when the user tags generation inputs.
|
|
14
|
+
`brand-kit` references map to `libraryId`, `preset` references map to
|
|
15
|
+
`presetId`, and `media-type` references choose image generation versus video
|
|
16
|
+
generation. Call `view-screen` when the user says "this library" or "this
|
|
17
|
+
image" and you need fresh IDs. The image model may default from the composer
|
|
18
|
+
image-model picker; all other generation settings should be explicit action
|
|
19
|
+
args, preset values, or action schema defaults.
|
|
15
20
|
- Use category-tagged references. Blog heroes should prefer `hero`; diagrams
|
|
16
21
|
should prefer `diagram`; product imagery should include `product` and `logo`
|
|
17
22
|
references.
|
|
@@ -20,16 +25,18 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
|
|
|
20
25
|
`assets.metadata.isStyleAnchor` before sampling other relevant references.
|
|
21
26
|
- Honor library custom instructions. They are persistent prompt guidance and
|
|
22
27
|
should be updated when the user wants durable generation behavior.
|
|
23
|
-
- Generate
|
|
24
|
-
with stable `slotId`s so the
|
|
28
|
+
- Generate the selected candidate count for open-ended requests, usually 2-4.
|
|
29
|
+
Use `generate-image-batch` with stable `slotId`s so the shared generation tray
|
|
30
|
+
can show live slots.
|
|
25
31
|
- `generate-image` and `generate-image-batch` are synchronous for images. One
|
|
26
32
|
batch call should produce the requested candidates and return their asset
|
|
27
33
|
IDs/URLs; do not follow it with `get-generation-run`,
|
|
28
34
|
`refresh-generation-run`, or more generation unless the user asks for another
|
|
29
35
|
direction or the returned slot has `ok: false`.
|
|
30
|
-
- For repeatable deliverables,
|
|
31
|
-
|
|
32
|
-
`
|
|
36
|
+
- For repeatable deliverables, honor a `preset` @mention as `presetId` or call
|
|
37
|
+
`list-generation-presets` when choosing one. Pass the preset through
|
|
38
|
+
`generate-image`, `generate-image-batch`, `refine-image`, or
|
|
39
|
+
`rerun-generation-run`.
|
|
33
40
|
- For designer handoff, preserve `sessionId` and call
|
|
34
41
|
`update-generation-session` after each new candidate so the active asset,
|
|
35
42
|
feedback, and run lineage stay resumable.
|
|
@@ -7,6 +7,14 @@ description: Schema, CRUD, sharing, and cascade-delete patterns for asset librar
|
|
|
7
7
|
|
|
8
8
|
Use this skill before adding fields, changing access checks, or modifying delete behavior.
|
|
9
9
|
|
|
10
|
+
## User surface
|
|
11
|
+
|
|
12
|
+
The human Library workspace is canonical at `/library`. The root selects
|
|
13
|
+
"All assets" and browses assets across every accessible brand kit; `/library/:id`
|
|
14
|
+
opens one kit's management detail. Legacy `/brand-kits` URLs redirect here.
|
|
15
|
+
Embedded picker hosts still load `/library` in an iframe and keep the existing
|
|
16
|
+
bridge contract.
|
|
17
|
+
|
|
10
18
|
## Schema overview
|
|
11
19
|
|
|
12
20
|
```
|
|
@@ -24,6 +32,8 @@ image_libraries — top-level library, has ownableColumns + shares
|
|
|
24
32
|
Every action that touches an ownable resource must scope its queries:
|
|
25
33
|
|
|
26
34
|
- **List queries**: `accessFilter(schema.assetLibraries, schema.assetLibraryShares)` in WHERE.
|
|
35
|
+
Cross-kit asset lists must first resolve the accessible library IDs, then
|
|
36
|
+
query `image_assets` by those IDs and include the parent kit title for UI chips.
|
|
27
37
|
- **Read by id**: `await resolveAccess("asset-library", libraryId)`. The `requireLibrary(id)` helper in `_helpers.ts` wraps this.
|
|
28
38
|
- **Write**: `await assertAccess("asset-library", libraryId, "editor")` for updates / inserts; `"admin"` for deletes.
|
|
29
39
|
|
|
@@ -52,6 +62,7 @@ Example: adding `image_libraries.icon`:
|
|
|
52
62
|
## Sharing
|
|
53
63
|
|
|
54
64
|
Libraries follow the standard framework sharing model:
|
|
65
|
+
|
|
55
66
|
- `visibility: "private" | "org" | "public"`
|
|
56
67
|
- Per-user / per-org grants in `image_library_shares` with `viewer | editor | admin` roles.
|
|
57
68
|
- Use the framework actions `share-resource`, `unshare-resource`, `set-resource-visibility` with `--resourceType=asset-library`. The legacy `image-library` alias remains registered for existing grants.
|
|
@@ -61,6 +72,7 @@ Generated assets and references inherit the parent library's visibility. v1 does
|
|
|
61
72
|
## Cascade delete
|
|
62
73
|
|
|
63
74
|
`delete-library` deletes in order:
|
|
75
|
+
|
|
64
76
|
1. `image_assets WHERE library_id = ?`
|
|
65
77
|
2. `image_generation_runs WHERE library_id = ?`
|
|
66
78
|
3. `image_collections WHERE library_id = ?`
|
|
@@ -21,7 +21,9 @@ Detailed library, generation, image, embed, and engine rules live in
|
|
|
21
21
|
ad hoc provider calls when the app has an action/engine abstraction.
|
|
22
22
|
- Preserve provenance and metadata for generated or imported assets.
|
|
23
23
|
- Use `view-screen` when the active library, selected asset, picker, generation,
|
|
24
|
-
or embed target is unclear. The
|
|
24
|
+
or embed target is unclear. The human Library surface is `/library` for
|
|
25
|
+
cross-kit browsing and `/library/:libraryId` for a single brand kit; embedded
|
|
26
|
+
picker hosts still use `/library` with their iframe/auth bridge params.
|
|
25
27
|
- The Create tab (`/`) is the full-page Assets chat surface. Use the shared
|
|
26
28
|
`assets` chat thread storage there, keep past chats in the left sidebar, and
|
|
27
29
|
use the right agent sidebar only on non-Create routes with view-transition
|
|
@@ -33,8 +35,21 @@ Detailed library, generation, image, embed, and engine rules live in
|
|
|
33
35
|
## Application State
|
|
34
36
|
|
|
35
37
|
- `navigation` exposes library, asset, generation, picker, embed, and selection
|
|
36
|
-
context.
|
|
37
|
-
|
|
38
|
+
context. Human Library state uses
|
|
39
|
+
`{ view: "library", selection: "all" | libraryId, tab, scope, folderId, search }`.
|
|
40
|
+
Embedded picker state keeps `{ view: "picker", mediaType, libraryId, query,
|
|
41
|
+
prompt, aspectRatio }`.
|
|
42
|
+
- Composer `@` mentions are the source of generation inputs. Map
|
|
43
|
+
`brand-kit` references to `libraryId`, `preset` references to `presetId`, and
|
|
44
|
+
`media-type` references to choosing image (`generate-image` /
|
|
45
|
+
`generate-image-batch`) or video (`generate-video`) generation. The current
|
|
46
|
+
library view auto-tags its brand kit as a visible removable chip when the
|
|
47
|
+
composer is empty. The image model is the only remaining composer-side
|
|
48
|
+
default; the image-model picker writes `imageGenerationModel`, which image
|
|
49
|
+
generation actions may use when `model` is omitted.
|
|
50
|
+
- `asset-variants` is the shared live generation tray state. New image
|
|
51
|
+
candidates should appear there through `generate-image` or
|
|
52
|
+
`generate-image-batch`; do not invent page-local progress surfaces.
|
|
38
53
|
- `navigate` moves the UI to picker, library, generation, asset, and settings
|
|
39
54
|
surfaces.
|
|
40
55
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readAppState } from "@agent-native/core/application-state";
|
|
2
|
+
|
|
3
|
+
import { IMAGE_MODELS, type ImageModel } from "../shared/api.js";
|
|
4
|
+
|
|
5
|
+
export const IMAGE_MODEL_STATE_KEY = "imageGenerationModel";
|
|
6
|
+
|
|
7
|
+
function imageModelValue(value: unknown): ImageModel | undefined {
|
|
8
|
+
return typeof value === "string" &&
|
|
9
|
+
(IMAGE_MODELS as readonly string[]).includes(value)
|
|
10
|
+
? (value as ImageModel)
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function recordValue(value: unknown): Record<string, unknown> {
|
|
15
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
16
|
+
? (value as Record<string, unknown>)
|
|
17
|
+
: {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function readImageModelDefault(): Promise<ImageModel | undefined> {
|
|
21
|
+
const raw = await readAppState(IMAGE_MODEL_STATE_KEY).catch(() => null);
|
|
22
|
+
const state = recordValue(raw);
|
|
23
|
+
return imageModelValue(state.model) ?? imageModelValue(raw);
|
|
24
|
+
}
|
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
3
|
-
deleteAppState,
|
|
4
|
-
readAppState,
|
|
5
|
-
writeAppState,
|
|
6
|
-
} from "@agent-native/core/application-state";
|
|
2
|
+
import type { ActionRunContext } from "@agent-native/core/action";
|
|
7
3
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
8
4
|
import { eq } from "drizzle-orm";
|
|
9
5
|
import { z } from "zod";
|
|
10
6
|
|
|
11
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
12
|
-
import
|
|
8
|
+
import {
|
|
9
|
+
deleteVariantState,
|
|
10
|
+
readVariantState,
|
|
11
|
+
writeVariantState,
|
|
12
|
+
} from "./variant-slots.js";
|
|
13
13
|
|
|
14
14
|
export default defineAction({
|
|
15
15
|
description:
|
|
16
|
-
"Clear one or more live candidate slots from application_state.asset-variants. Use slotId for a single slot, scope='failed' to drop every failed slot, or scope='all' to clear the panel. Any underlying asset rows for cleared slots are deleted (requires editor access on the library).",
|
|
16
|
+
"Clear one or more live candidate slots from application_state.asset-variants. Uses the current chat thread when available. Use slotId for a single slot, scope='failed' to drop every failed slot, or scope='all' to clear the panel. Any underlying asset rows for cleared slots are deleted (requires editor access on the library).",
|
|
17
17
|
schema: z
|
|
18
18
|
.object({
|
|
19
19
|
slotId: z.string().optional(),
|
|
20
20
|
scope: z.enum(["failed", "all"]).optional(),
|
|
21
|
+
threadId: z.string().nullable().optional(),
|
|
21
22
|
})
|
|
22
23
|
.refine((v) => Boolean(v.slotId) !== Boolean(v.scope), {
|
|
23
24
|
message: "Provide exactly one of `slotId` or `scope`.",
|
|
24
25
|
}),
|
|
25
|
-
run: async ({ slotId, scope }) => {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
raw ??
|
|
29
|
-
((await readAppState("image-variants").catch(() => null)) as
|
|
30
|
-
| unknown
|
|
31
|
-
| null);
|
|
32
|
-
const state = (legacyRaw ?? null) as AssetVariantState | null;
|
|
26
|
+
run: async ({ slotId, scope, threadId }, context?: ActionRunContext) => {
|
|
27
|
+
const effectiveThreadId = threadId ?? context?.threadId ?? null;
|
|
28
|
+
const state = await readVariantState(effectiveThreadId);
|
|
33
29
|
if (!state || !Array.isArray(state.slots) || state.slots.length === 0) {
|
|
34
30
|
return { dismissed: 0, assetsDeleted: 0, cleared: true };
|
|
35
31
|
}
|
|
32
|
+
const stateThreadId =
|
|
33
|
+
effectiveThreadId ?? state.variantScopeId ?? state.threadId ?? null;
|
|
36
34
|
|
|
37
35
|
await assertAccess("asset-library", state.libraryId, "editor");
|
|
38
36
|
|
|
@@ -64,8 +62,7 @@ export default defineAction({
|
|
|
64
62
|
const remaining = state.slots.filter((s) => !removed.has(s.slotId));
|
|
65
63
|
|
|
66
64
|
if (remaining.length === 0) {
|
|
67
|
-
await
|
|
68
|
-
await deleteAppState("image-variants").catch(() => {});
|
|
65
|
+
await deleteVariantState(stateThreadId);
|
|
69
66
|
return {
|
|
70
67
|
dismissed: toRemove.length,
|
|
71
68
|
assetsDeleted,
|
|
@@ -75,11 +72,7 @@ export default defineAction({
|
|
|
75
72
|
|
|
76
73
|
state.slots = remaining;
|
|
77
74
|
state.updatedAt = new Date().toISOString();
|
|
78
|
-
await
|
|
79
|
-
"asset-variants",
|
|
80
|
-
state as unknown as Record<string, unknown>,
|
|
81
|
-
);
|
|
82
|
-
await deleteAppState("image-variants").catch(() => {});
|
|
75
|
+
await writeVariantState(state, stateThreadId);
|
|
83
76
|
return {
|
|
84
77
|
dismissed: toRemove.length,
|
|
85
78
|
assetsDeleted,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineAction, embedApp } from "@agent-native/core";
|
|
2
|
+
import type { ActionRunContext } from "@agent-native/core/action";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -116,16 +117,19 @@ const action = defineAction({
|
|
|
116
117
|
view: "picker",
|
|
117
118
|
};
|
|
118
119
|
},
|
|
119
|
-
run: async (args) => {
|
|
120
|
+
run: async (args, context?: ActionRunContext) => {
|
|
120
121
|
if (args.mediaType === "video") {
|
|
121
|
-
const picker = (await openAssetPicker.run(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
const picker = (await openAssetPicker.run(
|
|
123
|
+
{
|
|
124
|
+
mediaType: "video",
|
|
125
|
+
prompt: args.prompt,
|
|
126
|
+
libraryId: args.libraryId,
|
|
127
|
+
libraryHint: args.libraryHint,
|
|
128
|
+
count: args.count,
|
|
129
|
+
callerAppId: args.callerAppId,
|
|
130
|
+
},
|
|
131
|
+
context,
|
|
132
|
+
)) as Record<string, unknown>;
|
|
129
133
|
return {
|
|
130
134
|
...picker,
|
|
131
135
|
generated: false,
|
|
@@ -134,20 +138,23 @@ const action = defineAction({
|
|
|
134
138
|
};
|
|
135
139
|
}
|
|
136
140
|
|
|
137
|
-
const picker = (await openAssetPicker.run(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
141
|
+
const picker = (await openAssetPicker.run(
|
|
142
|
+
{
|
|
143
|
+
mediaType: "image",
|
|
144
|
+
prompt: args.prompt,
|
|
145
|
+
libraryId: args.libraryId,
|
|
146
|
+
libraryHint: args.libraryHint,
|
|
147
|
+
aspectRatio: args.aspectRatio,
|
|
148
|
+
presetId: args.presetId,
|
|
149
|
+
count: args.count,
|
|
150
|
+
autoGenerate: true,
|
|
151
|
+
tier: args.tier,
|
|
152
|
+
styleStrength: args.styleStrength,
|
|
153
|
+
includeLogo: args.includeLogo,
|
|
154
|
+
callerAppId: args.callerAppId,
|
|
155
|
+
},
|
|
156
|
+
context,
|
|
157
|
+
)) as Record<string, unknown>;
|
|
151
158
|
return {
|
|
152
159
|
...picker,
|
|
153
160
|
generated: false,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import type { ActionRunContext } from "@agent-native/core/action";
|
|
2
3
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
4
|
import { eq } from "drizzle-orm";
|
|
4
5
|
import { nanoid } from "nanoid";
|
|
@@ -17,13 +18,20 @@ import {
|
|
|
17
18
|
STYLE_STRENGTHS,
|
|
18
19
|
} from "../shared/api.js";
|
|
19
20
|
import { requireGenerationSessionInLibrary } from "./_helpers.js";
|
|
21
|
+
import { readImageModelDefault } from "./_image-model-default.js";
|
|
20
22
|
import generateImage from "./generate-image.js";
|
|
23
|
+
import { upsertVariantSlot } from "./variant-slots.js";
|
|
21
24
|
|
|
22
25
|
export default defineAction({
|
|
23
26
|
description:
|
|
24
|
-
"Generate several brand-consistent images in parallel from one library. This is synchronous for images: one call waits for every slot and returns final image artifacts. Use this for slide decks, landing pages, and multi-slot design work. Do not call get-generation-run or refresh-generation-run after a normal image batch result.",
|
|
27
|
+
"Generate several brand-consistent images in parallel from one brand kit/library. Use @brand-kit mentions as libraryId and @preset mentions as presetId when present. This is synchronous for images: one call waits for every slot and returns final image artifacts. Use this for slide decks, landing pages, and multi-slot design work. Do not call get-generation-run or refresh-generation-run after a normal image batch result.",
|
|
25
28
|
schema: z.object({
|
|
26
|
-
libraryId: z
|
|
29
|
+
libraryId: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe(
|
|
33
|
+
"Brand kit/library ID. Pass the refId from a brand-kit @mention, or choose a kit from view-screen/list-libraries.",
|
|
34
|
+
),
|
|
27
35
|
collectionId: z.string().optional(),
|
|
28
36
|
presetId: z.string().optional(),
|
|
29
37
|
sessionId: z.string().optional(),
|
|
@@ -45,6 +53,12 @@ export default defineAction({
|
|
|
45
53
|
)
|
|
46
54
|
.min(1)
|
|
47
55
|
.max(12),
|
|
56
|
+
variantScopeId: z
|
|
57
|
+
.string()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe(
|
|
60
|
+
"Internal UI state scope for live candidate slots. Usually omitted; embedded picker UIs pass a browser-tab scope.",
|
|
61
|
+
),
|
|
48
62
|
model: z.enum(IMAGE_MODELS).optional(),
|
|
49
63
|
tier: z.enum(IMAGE_QUALITY_TIERS).optional(),
|
|
50
64
|
intent: z.enum(GENERATION_INTENTS).default("generate"),
|
|
@@ -60,41 +74,74 @@ export default defineAction({
|
|
|
60
74
|
),
|
|
61
75
|
}),
|
|
62
76
|
parallelSafe: true,
|
|
63
|
-
run: async ({ slots, ...
|
|
77
|
+
run: async ({ slots, ...inputBase }, context?: ActionRunContext) => {
|
|
78
|
+
const imageModelDefault = await readImageModelDefault();
|
|
79
|
+
const libraryId = inputBase.libraryId;
|
|
80
|
+
if (!libraryId) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
"No brand kit selected. Tag a brand kit with @ or pass libraryId.",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const base = {
|
|
86
|
+
...inputBase,
|
|
87
|
+
libraryId,
|
|
88
|
+
model: inputBase.model ?? imageModelDefault,
|
|
89
|
+
};
|
|
64
90
|
await assertAccess("asset-library", base.libraryId, "editor");
|
|
65
91
|
if (base.sessionId) {
|
|
66
92
|
await requireGenerationSessionInLibrary(base.sessionId, base.libraryId);
|
|
67
93
|
}
|
|
68
|
-
const limit = pLimit(4);
|
|
69
94
|
const variantBatchId = nanoid();
|
|
95
|
+
await Promise.all(
|
|
96
|
+
slots.map((slot, index) =>
|
|
97
|
+
upsertVariantSlot({
|
|
98
|
+
runId: `pending-${variantBatchId}-${index + 1}`,
|
|
99
|
+
batchId: variantBatchId,
|
|
100
|
+
libraryId: base.libraryId,
|
|
101
|
+
collectionId: base.collectionId ?? null,
|
|
102
|
+
presetId: base.presetId ?? null,
|
|
103
|
+
sessionId: base.sessionId ?? null,
|
|
104
|
+
threadId: context?.threadId ?? null,
|
|
105
|
+
variantScopeId: base.variantScopeId ?? null,
|
|
106
|
+
prompt: slot.prompt,
|
|
107
|
+
slotId: slot.slotId,
|
|
108
|
+
status: "pending",
|
|
109
|
+
}),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
const limit = pLimit(4);
|
|
70
113
|
const results = await Promise.allSettled(
|
|
71
114
|
slots.map((slot) =>
|
|
72
115
|
limit(() =>
|
|
73
|
-
generateImage.run(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
generateImage.run(
|
|
117
|
+
{
|
|
118
|
+
libraryId: base.libraryId,
|
|
119
|
+
collectionId: base.collectionId,
|
|
120
|
+
presetId: base.presetId,
|
|
121
|
+
sessionId: base.sessionId,
|
|
122
|
+
prompt: slot.prompt,
|
|
123
|
+
aspectRatio: slot.aspectRatio,
|
|
124
|
+
imageSize: slot.imageSize,
|
|
125
|
+
model: base.model,
|
|
126
|
+
tier: base.tier,
|
|
127
|
+
intent: slot.intent ?? base.intent,
|
|
128
|
+
styleStrength: slot.styleStrength ?? base.styleStrength,
|
|
129
|
+
categories: slot.categories,
|
|
130
|
+
referenceAssetIds: slot.referenceAssetIds,
|
|
131
|
+
includeLogo: base.includeLogo,
|
|
132
|
+
groundingMode: base.groundingMode,
|
|
133
|
+
slotId: slot.slotId,
|
|
134
|
+
variantBatchId,
|
|
135
|
+
variantScopeId: base.variantScopeId,
|
|
136
|
+
dismissible: slot.dismissible,
|
|
137
|
+
sourceAssetId: slot.sourceAssetId,
|
|
138
|
+
subjectAssetId: slot.subjectAssetId,
|
|
139
|
+
source: base.source,
|
|
140
|
+
callerAppId: base.callerAppId,
|
|
141
|
+
activateSessionAsset: false,
|
|
142
|
+
},
|
|
143
|
+
context,
|
|
144
|
+
),
|
|
98
145
|
),
|
|
99
146
|
),
|
|
100
147
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import type { ActionRunContext } from "@agent-native/core/action";
|
|
2
3
|
import {
|
|
3
|
-
readAppState,
|
|
4
4
|
writeAppState,
|
|
5
5
|
deleteAppState,
|
|
6
6
|
} from "@agent-native/core/application-state";
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
requireGenerationSessionInLibrary,
|
|
44
44
|
serializeAsset,
|
|
45
45
|
} from "./_helpers.js";
|
|
46
|
+
import { readImageModelDefault } from "./_image-model-default.js";
|
|
46
47
|
import { upsertVariantSlot, wasVariantSlotDismissed } from "./variant-slots.js";
|
|
47
48
|
|
|
48
49
|
function resolveModelForTier(
|
|
@@ -57,33 +58,16 @@ function resolveModelForTier(
|
|
|
57
58
|
: "gemini-3.1-flash-image";
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
/**
|
|
61
|
-
* The user's default image model, chosen from the composer's model picker and
|
|
62
|
-
* persisted in per-user application state. Used as a fallback when no explicit
|
|
63
|
-
* model, tier, or preset model is supplied. Returns undefined when unset or
|
|
64
|
-
* invalid so the hardcoded default still applies.
|
|
65
|
-
*/
|
|
66
|
-
async function readUserDefaultImageModel(): Promise<ImageModel | undefined> {
|
|
67
|
-
try {
|
|
68
|
-
const stored = await readAppState("imageGenerationModel");
|
|
69
|
-
const model = stored?.model;
|
|
70
|
-
if (
|
|
71
|
-
typeof model === "string" &&
|
|
72
|
-
(IMAGE_MODELS as readonly string[]).includes(model)
|
|
73
|
-
) {
|
|
74
|
-
return model as ImageModel;
|
|
75
|
-
}
|
|
76
|
-
} catch {
|
|
77
|
-
// No request context or read failure — fall back to defaults below.
|
|
78
|
-
}
|
|
79
|
-
return undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
61
|
export default defineAction({
|
|
83
62
|
description:
|
|
84
|
-
"Generate one brand-consistent image from a library. This is synchronous for images and returns the final asset with preview/download/embed URLs. Use generate-image-batch for multiple independent slots; do not poll image runs after this action returns.",
|
|
63
|
+
"Generate one brand-consistent image from a brand kit/library. This is synchronous for images and returns the final asset with preview/download/embed URLs. Use @brand-kit mentions as libraryId and @preset mentions as presetId when present. Use generate-image-batch for multiple independent slots; do not poll image runs after this action returns.",
|
|
85
64
|
schema: z.object({
|
|
86
|
-
libraryId: z
|
|
65
|
+
libraryId: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe(
|
|
69
|
+
"Brand kit/library ID. Pass the refId from a brand-kit @mention, or choose a kit from view-screen/list-libraries.",
|
|
70
|
+
),
|
|
87
71
|
collectionId: z.string().optional(),
|
|
88
72
|
presetId: z.string().optional(),
|
|
89
73
|
sessionId: z.string().optional(),
|
|
@@ -104,6 +88,12 @@ export default defineAction({
|
|
|
104
88
|
includeLogo: z.coerce.boolean().default(false),
|
|
105
89
|
slotId: z.string().optional(),
|
|
106
90
|
variantBatchId: z.string().optional(),
|
|
91
|
+
variantScopeId: z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe(
|
|
95
|
+
"Internal UI state scope for live candidate slots. Usually omitted; embedded picker UIs pass a browser-tab scope.",
|
|
96
|
+
),
|
|
107
97
|
dismissible: z.coerce
|
|
108
98
|
.boolean()
|
|
109
99
|
.default(true)
|
|
@@ -135,7 +125,18 @@ export default defineAction({
|
|
|
135
125
|
),
|
|
136
126
|
}),
|
|
137
127
|
parallelSafe: true,
|
|
138
|
-
run: async (
|
|
128
|
+
run: async (input, context?: ActionRunContext) => {
|
|
129
|
+
const imageModelDefault = await readImageModelDefault();
|
|
130
|
+
const libraryId = input.libraryId;
|
|
131
|
+
if (!libraryId) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
"No brand kit selected. Tag a brand kit with @ or pass libraryId.",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const args = {
|
|
137
|
+
...input,
|
|
138
|
+
libraryId,
|
|
139
|
+
};
|
|
139
140
|
await assertAccess("asset-library", args.libraryId, "editor");
|
|
140
141
|
const db = getDb();
|
|
141
142
|
const [library] = await db
|
|
@@ -248,9 +249,9 @@ export default defineAction({
|
|
|
248
249
|
preset?.category ??
|
|
249
250
|
collection?.category) as ImageCategory | undefined;
|
|
250
251
|
const resolvedModel = (args.model ??
|
|
252
|
+
imageModelDefault ??
|
|
251
253
|
resolveModelForTier(resolvedTier, category) ??
|
|
252
254
|
preset?.model ??
|
|
253
|
-
(await readUserDefaultImageModel()) ??
|
|
254
255
|
"gemini-3.1-flash-image") as (typeof IMAGE_MODELS)[number];
|
|
255
256
|
const resolvedCategories =
|
|
256
257
|
args.categories ??
|
|
@@ -303,6 +304,8 @@ export default defineAction({
|
|
|
303
304
|
});
|
|
304
305
|
const runId = nanoid();
|
|
305
306
|
const now = nowIso();
|
|
307
|
+
const slotId = args.slotId ?? runId;
|
|
308
|
+
const variantScopeId = args.variantScopeId ?? context?.threadId ?? null;
|
|
306
309
|
// Capture identity at insert time so the org-admin audit log can filter
|
|
307
310
|
// by owner / org without re-resolving who triggered the run later.
|
|
308
311
|
const ownerEmail = getRequestUserEmail() ?? null;
|
|
@@ -342,11 +345,12 @@ export default defineAction({
|
|
|
342
345
|
sessionId: session?.id ?? null,
|
|
343
346
|
customInstructions: library.customInstructions ?? "",
|
|
344
347
|
};
|
|
345
|
-
const slotId = args.slotId ?? runId;
|
|
346
348
|
const dismissibleSlot = args.dismissible !== false && Boolean(slotId);
|
|
347
349
|
const baseMetadata = {
|
|
348
350
|
slotId,
|
|
349
351
|
variantBatchId: args.variantBatchId ?? null,
|
|
352
|
+
threadId: context?.threadId ?? null,
|
|
353
|
+
variantScopeId,
|
|
350
354
|
dismissible: dismissibleSlot,
|
|
351
355
|
sourceAssetId: args.sourceAssetId,
|
|
352
356
|
subjectAssetId: args.subjectAssetId,
|
|
@@ -389,6 +393,8 @@ export default defineAction({
|
|
|
389
393
|
collectionId: resolvedCollectionId ?? null,
|
|
390
394
|
presetId: preset?.id ?? null,
|
|
391
395
|
sessionId: session?.id ?? null,
|
|
396
|
+
threadId: context?.threadId ?? null,
|
|
397
|
+
variantScopeId,
|
|
392
398
|
prompt: args.prompt,
|
|
393
399
|
slotId,
|
|
394
400
|
status: "pending",
|
|
@@ -430,7 +436,10 @@ export default defineAction({
|
|
|
430
436
|
}
|
|
431
437
|
if (
|
|
432
438
|
dismissibleSlot &&
|
|
433
|
-
(await wasVariantSlotDismissed(args.libraryId, slotId
|
|
439
|
+
(await wasVariantSlotDismissed(args.libraryId, slotId, {
|
|
440
|
+
threadId: context?.threadId ?? null,
|
|
441
|
+
variantScopeId,
|
|
442
|
+
}))
|
|
434
443
|
) {
|
|
435
444
|
await db
|
|
436
445
|
.update(schema.assetGenerationRuns)
|
|
@@ -539,6 +548,8 @@ export default defineAction({
|
|
|
539
548
|
collectionId: resolvedCollectionId ?? null,
|
|
540
549
|
presetId: preset?.id ?? null,
|
|
541
550
|
sessionId: session?.id ?? null,
|
|
551
|
+
threadId: context?.threadId ?? null,
|
|
552
|
+
variantScopeId,
|
|
542
553
|
prompt: args.prompt,
|
|
543
554
|
slotId,
|
|
544
555
|
status: "ready",
|
|
@@ -570,7 +581,10 @@ export default defineAction({
|
|
|
570
581
|
.where(eq(schema.assetGenerationRuns.id, runId));
|
|
571
582
|
if (
|
|
572
583
|
dismissibleSlot &&
|
|
573
|
-
(await wasVariantSlotDismissed(args.libraryId, slotId
|
|
584
|
+
(await wasVariantSlotDismissed(args.libraryId, slotId, {
|
|
585
|
+
threadId: context?.threadId ?? null,
|
|
586
|
+
variantScopeId,
|
|
587
|
+
}))
|
|
574
588
|
) {
|
|
575
589
|
throw err;
|
|
576
590
|
}
|
|
@@ -581,6 +595,8 @@ export default defineAction({
|
|
|
581
595
|
collectionId: resolvedCollectionId ?? null,
|
|
582
596
|
presetId: preset?.id ?? null,
|
|
583
597
|
sessionId: session?.id ?? null,
|
|
598
|
+
threadId: context?.threadId ?? null,
|
|
599
|
+
variantScopeId,
|
|
584
600
|
prompt: args.prompt,
|
|
585
601
|
slotId,
|
|
586
602
|
status: "failed",
|