@agent-native/creative-context 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-native.package.json +11 -0
- package/dist/actions/continue-context-import.d.ts +16 -1
- package/dist/actions/continue-context-import.js +4 -1
- package/dist/actions/continue-context-import.js.map +1 -1
- package/dist/actions/get-creative-context.d.ts +38 -0
- package/dist/actions/get-creative-context.d.ts.map +1 -1
- package/dist/actions/get-creative-context.js +59 -23
- package/dist/actions/get-creative-context.js.map +1 -1
- package/dist/actions/get-google-picker-session.d.ts +10 -0
- package/dist/actions/get-google-picker-session.d.ts.map +1 -0
- package/dist/actions/get-google-picker-session.js +38 -0
- package/dist/actions/get-google-picker-session.js.map +1 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +2 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/start-context-enrichment.d.ts +16 -1
- package/dist/actions/start-context-enrichment.js +6 -4
- package/dist/actions/start-context-enrichment.js.map +1 -1
- package/dist/actions/start-context-import.d.ts +16 -1
- package/dist/actions/start-context-import.js +2 -1
- package/dist/actions/start-context-import.js.map +1 -1
- package/dist/client/CreativeContextPanel.d.ts +1 -59
- package/dist/client/CreativeContextPanel.d.ts.map +1 -1
- package/dist/client/CreativeContextPanel.js +111 -36
- package/dist/client/CreativeContextPanel.js.map +1 -1
- package/dist/client/actions.d.ts +8 -0
- package/dist/client/actions.d.ts.map +1 -1
- package/dist/client/actions.js +4 -0
- package/dist/client/actions.js.map +1 -1
- package/dist/client/google-slides-picker.d.ts +19 -0
- package/dist/client/google-slides-picker.d.ts.map +1 -0
- package/dist/client/google-slides-picker.js +106 -0
- package/dist/client/google-slides-picker.js.map +1 -0
- 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/client/messages.d.ts +1 -1
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +22 -0
- package/dist/client/messages.js.map +1 -1
- package/dist/connectors/google-slides.d.ts +1 -1
- package/dist/connectors/google-slides.d.ts.map +1 -1
- package/dist/connectors/google-slides.js +1 -2
- package/dist/connectors/google-slides.js.map +1 -1
- package/dist/connectors/normalize.d.ts +7 -0
- package/dist/connectors/normalize.d.ts.map +1 -1
- package/dist/connectors/normalize.js +122 -3
- package/dist/connectors/normalize.js.map +1 -1
- package/dist/schema/index.d.ts +30 -30
- package/dist/server/enrichment.d.ts +1 -0
- package/dist/server/enrichment.d.ts.map +1 -1
- package/dist/server/enrichment.js +26 -24
- package/dist/server/enrichment.js.map +1 -1
- package/dist/server/generation-context.d.ts +1 -1
- package/dist/server/generation-context.d.ts.map +1 -1
- package/dist/server/generation-context.js +1 -1
- package/dist/server/generation-context.js.map +1 -1
- package/dist/store/content.d.ts +13 -0
- package/dist/store/content.d.ts.map +1 -1
- package/dist/store/content.js +172 -1
- package/dist/store/content.js.map +1 -1
- package/dist/store/jobs.d.ts.map +1 -1
- package/dist/store/jobs.js +2 -2
- package/dist/store/jobs.js.map +1 -1
- package/dist/store/purge.d.ts.map +1 -1
- package/dist/store/purge.js +9 -8
- package/dist/store/purge.js.map +1 -1
- package/dist/store/suggestions.d.ts +1 -0
- package/dist/store/suggestions.d.ts.map +1 -1
- package/dist/store/suggestions.js +56 -38
- package/dist/store/suggestions.js.map +1 -1
- package/docs/skills/creative-context/SKILL.md +8 -0
- package/package.json +3 -3
- package/src/actions/continue-context-import.ts +4 -1
- package/src/actions/creative-context-a2a.spec.ts +98 -0
- package/src/actions/get-creative-context.spec.ts +121 -1
- package/src/actions/get-creative-context.ts +78 -29
- package/src/actions/get-google-picker-session.spec.ts +62 -0
- package/src/actions/get-google-picker-session.ts +45 -0
- package/src/actions/index.ts +2 -0
- package/src/actions/job-public-serialization.spec.ts +122 -0
- package/src/actions/start-context-enrichment.ts +6 -4
- package/src/actions/start-context-import.ts +2 -1
- package/src/client/CreativeContextPanel.tsx +163 -64
- package/src/client/actions.ts +18 -0
- package/src/client/google-slides-picker.spec.ts +36 -0
- package/src/client/google-slides-picker.ts +146 -0
- package/src/client/index.ts +2 -0
- package/src/client/messages.ts +35 -0
- package/src/client/source-config.spec.ts +24 -10
- package/src/connectors/google-slides.ts +1 -2
- package/src/connectors/normalize.spec.ts +144 -0
- package/src/connectors/normalize.ts +201 -3
- package/src/server/enrichment.spec.ts +94 -0
- package/src/server/enrichment.ts +27 -24
- package/src/server/generation-artifact-access.spec.ts +104 -0
- package/src/server/generation-context-a2a.spec.ts +20 -2
- package/src/server/generation-context.ts +6 -6
- package/src/server/isolated-a2a.spec.ts +21 -1
- package/src/store/access.integration.spec.ts +511 -26
- package/src/store/content.ts +213 -1
- package/src/store/jobs.ts +2 -0
- package/src/store/purge.ts +18 -9
- package/src/store/suggestions.ts +67 -41
|
@@ -248,6 +248,18 @@ describe("get-context-item public-agent boundary", () => {
|
|
|
248
248
|
},
|
|
249
249
|
},
|
|
250
250
|
},
|
|
251
|
+
edges: [
|
|
252
|
+
{
|
|
253
|
+
id: "edge-child-1",
|
|
254
|
+
fromItemId: "item-1",
|
|
255
|
+
fromItemVersionId: "version-1",
|
|
256
|
+
toItemId: "item-child-1",
|
|
257
|
+
toItemVersionId: "version-child-1",
|
|
258
|
+
toExternalId: "figma-file:child-1",
|
|
259
|
+
relation: "contains-native-child",
|
|
260
|
+
metadata: {},
|
|
261
|
+
},
|
|
262
|
+
],
|
|
251
263
|
});
|
|
252
264
|
|
|
253
265
|
const result = (await action.run({
|
|
@@ -259,6 +271,18 @@ describe("get-context-item public-agent boundary", () => {
|
|
|
259
271
|
dataRole: "untrusted-reference",
|
|
260
272
|
format: "design-html",
|
|
261
273
|
content: html,
|
|
274
|
+
retrieval: {
|
|
275
|
+
mode: "manifest-parts",
|
|
276
|
+
root: { itemId: "item-1", itemVersionId: "version-1" },
|
|
277
|
+
cloneAction: "clone-creative-context-design",
|
|
278
|
+
parts: [
|
|
279
|
+
{
|
|
280
|
+
externalId: "figma-file:child-1",
|
|
281
|
+
itemId: "item-child-1",
|
|
282
|
+
itemVersionId: "version-child-1",
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
262
286
|
});
|
|
263
287
|
expect(mocks.getCreativeContextItemByExternalId).not.toHaveBeenCalled();
|
|
264
288
|
});
|
|
@@ -305,11 +329,107 @@ describe("get-context-item public-agent boundary", () => {
|
|
|
305
329
|
},
|
|
306
330
|
},
|
|
307
331
|
},
|
|
332
|
+
edges: [
|
|
333
|
+
{
|
|
334
|
+
id: "edge-child-1",
|
|
335
|
+
fromItemId: "item-1",
|
|
336
|
+
fromItemVersionId: "version-1",
|
|
337
|
+
toItemId: "item-child-1",
|
|
338
|
+
toItemVersionId: "version-child-1",
|
|
339
|
+
toExternalId: "figma-file:child-1",
|
|
340
|
+
relation: "contains-native-child",
|
|
341
|
+
metadata: {},
|
|
342
|
+
},
|
|
343
|
+
],
|
|
308
344
|
});
|
|
309
345
|
|
|
310
346
|
const result = (await action.run({ itemId: "item-1" })) as any;
|
|
311
347
|
|
|
312
|
-
expect(result.version.nativeCode).
|
|
348
|
+
expect(result.version.nativeCode).toEqual({
|
|
349
|
+
dataRole: "untrusted-reference",
|
|
350
|
+
format: "design-html",
|
|
351
|
+
content: null,
|
|
352
|
+
oversized: true,
|
|
353
|
+
byteLength: Buffer.byteLength(html, "utf8"),
|
|
354
|
+
maxInlineBytes: 128 * 1024,
|
|
355
|
+
retrieval: {
|
|
356
|
+
mode: "manifest-parts",
|
|
357
|
+
root: { itemId: "item-1", itemVersionId: "version-1" },
|
|
358
|
+
cloneAction: "clone-creative-context-design",
|
|
359
|
+
parts: [
|
|
360
|
+
{
|
|
361
|
+
externalId: "figma-file:child-1",
|
|
362
|
+
itemId: "item-child-1",
|
|
363
|
+
itemVersionId: "version-child-1",
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
instruction: expect.stringContaining("exact clone action"),
|
|
368
|
+
});
|
|
369
|
+
expect(result.version.content).toContain(
|
|
370
|
+
"Oversized native code is omitted",
|
|
371
|
+
);
|
|
372
|
+
expect(JSON.stringify(result)).not.toContain("x".repeat(1_000));
|
|
313
373
|
expect(mocks.getCreativeContextItemByExternalId).not.toHaveBeenCalled();
|
|
314
374
|
});
|
|
375
|
+
|
|
376
|
+
it("returns an explicit exact-clone contract instead of oversized flat HTML", async () => {
|
|
377
|
+
const context = await mocks.getCreativeContextItem();
|
|
378
|
+
const html = `<div class="fmd-slide google-slides-native" data-source-slide-id="slide-1" style="position:relative;width:960px;height:540px"><p>${"x".repeat(128 * 1024)}</p></div>`;
|
|
379
|
+
mocks.getCreativeContextItem.mockResolvedValue({
|
|
380
|
+
...context,
|
|
381
|
+
item: {
|
|
382
|
+
...context.item,
|
|
383
|
+
mimeType: "text/html",
|
|
384
|
+
provenance: {
|
|
385
|
+
compiler: "@agent-native/creative-context:google-slides-native",
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
version: {
|
|
389
|
+
...context.version,
|
|
390
|
+
mimeType: "text/html",
|
|
391
|
+
content: html,
|
|
392
|
+
metadata: {
|
|
393
|
+
nativeArtifact: {
|
|
394
|
+
schemaVersion: 1,
|
|
395
|
+
app: "slides",
|
|
396
|
+
format: "slides-html",
|
|
397
|
+
rootExternalId: "deck-1:slide-1",
|
|
398
|
+
fidelityReport: {
|
|
399
|
+
exact: { count: 1 },
|
|
400
|
+
approximated: { count: 0, reasons: [] },
|
|
401
|
+
imageFallback: { count: 0, reasons: [] },
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
edges: [],
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const result = (await action.run({
|
|
410
|
+
itemId: "item-1",
|
|
411
|
+
itemVersionId: "version-1",
|
|
412
|
+
})) as any;
|
|
413
|
+
|
|
414
|
+
expect(result.version.nativeCode).toMatchObject({
|
|
415
|
+
dataRole: "untrusted-reference",
|
|
416
|
+
format: "slides-html",
|
|
417
|
+
content: null,
|
|
418
|
+
oversized: true,
|
|
419
|
+
byteLength: Buffer.byteLength(html, "utf8"),
|
|
420
|
+
maxInlineBytes: 128 * 1024,
|
|
421
|
+
retrieval: {
|
|
422
|
+
mode: "exact-clone-only",
|
|
423
|
+
root: { itemId: "item-1", itemVersionId: "version-1" },
|
|
424
|
+
cloneAction: "clone-context-slide",
|
|
425
|
+
parts: [],
|
|
426
|
+
},
|
|
427
|
+
instruction: expect.stringContaining("never concatenate"),
|
|
428
|
+
});
|
|
429
|
+
expect(result.version.nativeCode.content).toBeNull();
|
|
430
|
+
expect(result.version.content).toContain(
|
|
431
|
+
"Oversized native code is omitted",
|
|
432
|
+
);
|
|
433
|
+
expect(JSON.stringify(result)).not.toContain("x".repeat(1_000));
|
|
434
|
+
});
|
|
315
435
|
});
|
|
@@ -5,7 +5,10 @@ import {
|
|
|
5
5
|
reassembleNativeCreativeArtifact,
|
|
6
6
|
validateCompiledNativeHtml,
|
|
7
7
|
} from "../native-artifact-reassembly.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
nativeCreativeArtifactFromMetadata,
|
|
10
|
+
type NativeCreativeArtifact,
|
|
11
|
+
} from "../native-artifact.js";
|
|
9
12
|
import {
|
|
10
13
|
sanitizePublicString,
|
|
11
14
|
serializePublicContextDetail,
|
|
@@ -21,7 +24,7 @@ import {
|
|
|
21
24
|
getCreativeContextItemByExternalId,
|
|
22
25
|
} from "../store/index.js";
|
|
23
26
|
|
|
24
|
-
const
|
|
27
|
+
const MAX_PUBLIC_NATIVE_CODE_BYTES = 128 * 1024;
|
|
25
28
|
|
|
26
29
|
export default defineAction({
|
|
27
30
|
description:
|
|
@@ -44,17 +47,11 @@ export default defineAction({
|
|
|
44
47
|
context.version.metadata,
|
|
45
48
|
);
|
|
46
49
|
const nativeCode = nativeArtifact
|
|
47
|
-
? nativeArtifact
|
|
48
|
-
? boundedStoredNativeCode(context.version.content, nativeArtifact)
|
|
49
|
-
: ((
|
|
50
|
-
await reassembleNativeCreativeArtifact({
|
|
51
|
-
root: context,
|
|
52
|
-
app: nativeArtifact.app,
|
|
53
|
-
format: nativeArtifact.format,
|
|
54
|
-
resolveChild: getCreativeContextItemByExternalId,
|
|
55
|
-
}).catch(() => null)
|
|
56
|
-
)?.html ?? null)
|
|
50
|
+
? await publicNativeCode(context, nativeArtifact)
|
|
57
51
|
: null;
|
|
52
|
+
const nativeCodeOversized = Boolean(
|
|
53
|
+
nativeCode && "oversized" in nativeCode && nativeCode.oversized,
|
|
54
|
+
);
|
|
58
55
|
return {
|
|
59
56
|
...publicContext,
|
|
60
57
|
pendingJobId: await ensureContextItemHydration(context.item.id),
|
|
@@ -75,16 +72,11 @@ export default defineAction({
|
|
|
75
72
|
sanitizePublicString(context.version.title),
|
|
76
73
|
),
|
|
77
74
|
content: delimitUntrustedReference(
|
|
78
|
-
|
|
75
|
+
nativeCodeOversized
|
|
76
|
+
? "Oversized native code is omitted from this public result. Use version.nativeCode.retrieval."
|
|
77
|
+
: sanitizePublicString(context.version.content),
|
|
79
78
|
),
|
|
80
|
-
nativeCode:
|
|
81
|
-
nativeArtifact && nativeCode
|
|
82
|
-
? {
|
|
83
|
-
dataRole: UNTRUSTED_REFERENCE_ROLE,
|
|
84
|
-
format: nativeArtifact.format,
|
|
85
|
-
content: nativeCode,
|
|
86
|
-
}
|
|
87
|
-
: null,
|
|
79
|
+
nativeCode: nativeArtifact && nativeCode ? nativeCode : null,
|
|
88
80
|
summary: context.version.summary
|
|
89
81
|
? delimitUntrustedReference(
|
|
90
82
|
sanitizePublicString(context.version.summary),
|
|
@@ -132,17 +124,74 @@ export default defineAction({
|
|
|
132
124
|
},
|
|
133
125
|
});
|
|
134
126
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
artifact:
|
|
138
|
-
)
|
|
139
|
-
if (Buffer.byteLength(content, "utf8") > MAX_STORED_NATIVE_CODE_BYTES) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
127
|
+
async function publicNativeCode(
|
|
128
|
+
context: NonNullable<Awaited<ReturnType<typeof getCreativeContextItem>>>,
|
|
129
|
+
artifact: NativeCreativeArtifact,
|
|
130
|
+
) {
|
|
142
131
|
try {
|
|
132
|
+
const content = artifact.manifest
|
|
133
|
+
? context.version.content
|
|
134
|
+
: (
|
|
135
|
+
await reassembleNativeCreativeArtifact({
|
|
136
|
+
root: context,
|
|
137
|
+
app: artifact.app,
|
|
138
|
+
format: artifact.format,
|
|
139
|
+
resolveChild: getCreativeContextItemByExternalId,
|
|
140
|
+
})
|
|
141
|
+
).html;
|
|
143
142
|
validateCompiledNativeHtml(content, artifact);
|
|
144
|
-
|
|
143
|
+
const byteLength = Buffer.byteLength(content, "utf8");
|
|
144
|
+
if (byteLength <= MAX_PUBLIC_NATIVE_CODE_BYTES) {
|
|
145
|
+
return {
|
|
146
|
+
dataRole: UNTRUSTED_REFERENCE_ROLE,
|
|
147
|
+
format: artifact.format,
|
|
148
|
+
content,
|
|
149
|
+
...(artifact.manifest
|
|
150
|
+
? { retrieval: nativeCodeRetrieval(context, artifact) }
|
|
151
|
+
: {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
dataRole: UNTRUSTED_REFERENCE_ROLE,
|
|
156
|
+
format: artifact.format,
|
|
157
|
+
content: null,
|
|
158
|
+
oversized: true,
|
|
159
|
+
byteLength,
|
|
160
|
+
maxInlineBytes: MAX_PUBLIC_NATIVE_CODE_BYTES,
|
|
161
|
+
retrieval: nativeCodeRetrieval(context, artifact),
|
|
162
|
+
instruction:
|
|
163
|
+
"Use the exact clone action for the complete artifact. For a manifest artifact, inspect individually pinned parts with get-context-item; never concatenate a truncated HTML fragment.",
|
|
164
|
+
};
|
|
145
165
|
} catch {
|
|
146
166
|
return null;
|
|
147
167
|
}
|
|
148
168
|
}
|
|
169
|
+
|
|
170
|
+
function nativeCodeRetrieval(
|
|
171
|
+
context: NonNullable<Awaited<ReturnType<typeof getCreativeContextItem>>>,
|
|
172
|
+
artifact: NativeCreativeArtifact,
|
|
173
|
+
) {
|
|
174
|
+
return {
|
|
175
|
+
mode: artifact.manifest ? "manifest-parts" : "exact-clone-only",
|
|
176
|
+
root: {
|
|
177
|
+
itemId: context.item.id,
|
|
178
|
+
itemVersionId: context.version.id,
|
|
179
|
+
},
|
|
180
|
+
cloneAction:
|
|
181
|
+
artifact.app === "slides"
|
|
182
|
+
? "clone-context-slide"
|
|
183
|
+
: "clone-creative-context-design",
|
|
184
|
+
parts: (artifact.manifest?.children ?? []).map((child) => {
|
|
185
|
+
const edge = context.edges.find(
|
|
186
|
+
(candidate) =>
|
|
187
|
+
candidate.relation === "contains-native-child" &&
|
|
188
|
+
candidate.toExternalId === child.externalId,
|
|
189
|
+
);
|
|
190
|
+
return {
|
|
191
|
+
externalId: sanitizePublicString(child.externalId),
|
|
192
|
+
itemId: edge?.toItemId ?? null,
|
|
193
|
+
itemVersionId: edge?.toItemVersionId ?? null,
|
|
194
|
+
};
|
|
195
|
+
}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const resolveProviderApiOAuthAccessToken = vi.fn();
|
|
4
|
+
const resolveSecret = vi.fn();
|
|
5
|
+
|
|
6
|
+
vi.mock("@agent-native/core/provider-api", () => ({
|
|
7
|
+
resolveProviderApiOAuthAccessToken,
|
|
8
|
+
}));
|
|
9
|
+
vi.mock("@agent-native/core/server", () => ({ resolveSecret }));
|
|
10
|
+
vi.mock("../server/context.js", () => ({
|
|
11
|
+
getCreativeContext: () => ({ connectorContext: { appId: "slides" } }),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const { default: action } = await import("./get-google-picker-session.js");
|
|
15
|
+
|
|
16
|
+
describe("get-google-picker-session", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
resolveProviderApiOAuthAccessToken.mockReset();
|
|
19
|
+
resolveSecret.mockReset();
|
|
20
|
+
resolveProviderApiOAuthAccessToken.mockResolvedValue({
|
|
21
|
+
accessToken: "short-lived-access-token",
|
|
22
|
+
accountLabel: "Work Google",
|
|
23
|
+
});
|
|
24
|
+
resolveSecret.mockImplementation(async (key: string) =>
|
|
25
|
+
key === "GOOGLE_PICKER_API_KEY"
|
|
26
|
+
? "browser-restricted-key"
|
|
27
|
+
: key === "GOOGLE_PICKER_APP_ID"
|
|
28
|
+
? "123456789"
|
|
29
|
+
: null,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("is UI-only and returns a Picker session for an app-scoped connection", async () => {
|
|
34
|
+
expect(action.agentTool).toBe(false);
|
|
35
|
+
expect(action.toolCallable).toBe(false);
|
|
36
|
+
expect(action.requiresAuth).toBe(true);
|
|
37
|
+
|
|
38
|
+
await expect(
|
|
39
|
+
action.run({ connectionId: "drive-connection" }),
|
|
40
|
+
).resolves.toEqual({
|
|
41
|
+
accessToken: "short-lived-access-token",
|
|
42
|
+
accountLabel: "Work Google",
|
|
43
|
+
apiKey: "browser-restricted-key",
|
|
44
|
+
appId: "123456789",
|
|
45
|
+
});
|
|
46
|
+
expect(resolveProviderApiOAuthAccessToken).toHaveBeenCalledWith(
|
|
47
|
+
{ provider: "google_drive", connectionId: "drive-connection" },
|
|
48
|
+
expect.objectContaining({
|
|
49
|
+
appId: "slides",
|
|
50
|
+
providerIds: ["google_drive"],
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("fails closed when the browser-restricted Picker config is absent", async () => {
|
|
56
|
+
resolveSecret.mockResolvedValue(null);
|
|
57
|
+
|
|
58
|
+
await expect(
|
|
59
|
+
action.run({ connectionId: "drive-connection" }),
|
|
60
|
+
).rejects.toThrow(/Google Picker is not configured/);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import { resolveProviderApiOAuthAccessToken } from "@agent-native/core/provider-api";
|
|
3
|
+
import { resolveSecret } from "@agent-native/core/server";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getCreativeContext } from "../server/context.js";
|
|
7
|
+
|
|
8
|
+
export default defineAction({
|
|
9
|
+
description:
|
|
10
|
+
"Return a short-lived Google Picker session for the signed-in Library UI.",
|
|
11
|
+
schema: z.object({
|
|
12
|
+
connectionId: z.string().min(1),
|
|
13
|
+
}),
|
|
14
|
+
http: { method: "GET" },
|
|
15
|
+
readOnly: true,
|
|
16
|
+
requiresAuth: true,
|
|
17
|
+
agentTool: false,
|
|
18
|
+
toolCallable: false,
|
|
19
|
+
run: async ({ connectionId }) => {
|
|
20
|
+
const { connectorContext } = getCreativeContext();
|
|
21
|
+
const [oauth, apiKey, appId] = await Promise.all([
|
|
22
|
+
resolveProviderApiOAuthAccessToken(
|
|
23
|
+
{ provider: "google_drive", connectionId },
|
|
24
|
+
{
|
|
25
|
+
appId: connectorContext.appId,
|
|
26
|
+
providerIds: ["google_drive"],
|
|
27
|
+
localCredentialSource: "creative_context_picker",
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
resolveSecret("GOOGLE_PICKER_API_KEY"),
|
|
31
|
+
resolveSecret("GOOGLE_PICKER_APP_ID"),
|
|
32
|
+
]);
|
|
33
|
+
if (!apiKey || !appId) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"Google Picker is not configured. Set GOOGLE_PICKER_API_KEY and GOOGLE_PICKER_APP_ID.",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
accessToken: oauth.accessToken,
|
|
40
|
+
accountLabel: oauth.accountLabel,
|
|
41
|
+
apiKey,
|
|
42
|
+
appId,
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
});
|
package/src/actions/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import getBrandProfile from "./get-brand-profile.js";
|
|
|
7
7
|
import getContextImportStatus from "./get-context-import-status.js";
|
|
8
8
|
import getContextItem from "./get-context-item.js";
|
|
9
9
|
import getContextPack from "./get-context-pack.js";
|
|
10
|
+
import getGooglePickerSession from "./get-google-picker-session.js";
|
|
10
11
|
import inferBrandDna from "./infer-brand-dna.js";
|
|
11
12
|
import listCanonicalLogoCandidates from "./list-canonical-logo-candidates.js";
|
|
12
13
|
import listContextConnections from "./list-context-connections.js";
|
|
@@ -39,6 +40,7 @@ export const creativeContextActions: Record<string, ActionEntry> = {
|
|
|
39
40
|
"get-context-import-status": getContextImportStatus,
|
|
40
41
|
"get-context-item": getContextItem,
|
|
41
42
|
"get-context-pack": getContextPack,
|
|
43
|
+
"get-google-picker-session": getGooglePickerSession,
|
|
42
44
|
"infer-brand-dna": inferBrandDna,
|
|
43
45
|
"list-canonical-logo-candidates": listCanonicalLogoCandidates,
|
|
44
46
|
"list-context-packs": listContextPacks,
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
continueJob: vi.fn(),
|
|
5
|
+
createJob: vi.fn(),
|
|
6
|
+
getContextSource: vi.fn(),
|
|
7
|
+
dispatchCreativeContextImportJob: vi.fn(async () => undefined),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("../store/index.js", () => ({
|
|
11
|
+
continueJob: mocks.continueJob,
|
|
12
|
+
createJob: mocks.createJob,
|
|
13
|
+
getContextSource: mocks.getContextSource,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("../jobs/index.js", () => ({
|
|
17
|
+
dispatchCreativeContextImportJob: mocks.dispatchCreativeContextImportJob,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock("../server/context.js", () => ({
|
|
21
|
+
getCreativeContext: () => ({ connectorContext: { appId: "slides" } }),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
import continueImport from "./continue-context-import.js";
|
|
25
|
+
import startEnrichment from "./start-context-enrichment.js";
|
|
26
|
+
import startImport from "./start-context-import.js";
|
|
27
|
+
|
|
28
|
+
const privateJob = {
|
|
29
|
+
id: "job-1",
|
|
30
|
+
ownerEmail: "owner@example.test",
|
|
31
|
+
orgId: "org-secret",
|
|
32
|
+
sourceId: "source-1",
|
|
33
|
+
kind: "import",
|
|
34
|
+
status: "running",
|
|
35
|
+
mode: "incremental",
|
|
36
|
+
progressCurrent: 2,
|
|
37
|
+
progressTotal: 4,
|
|
38
|
+
attempts: 1,
|
|
39
|
+
leaseOwner: "worker-private",
|
|
40
|
+
leaseToken: "lease-private",
|
|
41
|
+
leaseExpiresAt: "2026-07-16T02:00:00.000Z",
|
|
42
|
+
nextResumeAt: null,
|
|
43
|
+
budget: { token: "budget-private" },
|
|
44
|
+
checkpoint: {
|
|
45
|
+
blobRef: "creative-context-blob:v1:checkpoint-private",
|
|
46
|
+
warning:
|
|
47
|
+
"Fetched https://provider.example/private?X-Amz-Signature=checkpoint-private",
|
|
48
|
+
},
|
|
49
|
+
request: {
|
|
50
|
+
providerUrl:
|
|
51
|
+
"https://provider.example/private?X-Amz-Signature=request-private",
|
|
52
|
+
},
|
|
53
|
+
result: {
|
|
54
|
+
processed: 2,
|
|
55
|
+
warning:
|
|
56
|
+
"Failed https://provider.example/private?X-Amz-Signature=result-private",
|
|
57
|
+
},
|
|
58
|
+
error:
|
|
59
|
+
"Provider https://provider.example/private?X-Amz-Signature=error-private failed",
|
|
60
|
+
createdAt: "2026-07-16T00:00:00.000Z",
|
|
61
|
+
startedAt: "2026-07-16T00:01:00.000Z",
|
|
62
|
+
completedAt: null,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function expectPublicJob(result: unknown) {
|
|
66
|
+
expect(result).toMatchObject({
|
|
67
|
+
job: {
|
|
68
|
+
id: "job-1",
|
|
69
|
+
sourceId: "source-1",
|
|
70
|
+
result: { processed: 2, warning: "Failed [redacted]" },
|
|
71
|
+
error: "Provider [redacted] failed",
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const serialized = JSON.stringify(result);
|
|
75
|
+
expect(serialized).not.toMatch(
|
|
76
|
+
/owner@example|org-secret|worker-private|lease-private|checkpoint-private|request-private|result-private|error-private|creative-context-blob|X-Amz-Signature/i,
|
|
77
|
+
);
|
|
78
|
+
expect(serialized).not.toMatch(
|
|
79
|
+
/ownerEmail|orgId|leaseOwner|leaseToken|leaseExpiresAt|checkpoint|request|budget/i,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe("creative context job action serialization", () => {
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
vi.clearAllMocks();
|
|
86
|
+
mocks.continueJob.mockResolvedValue(privateJob);
|
|
87
|
+
mocks.createJob.mockResolvedValue(privateJob);
|
|
88
|
+
mocks.getContextSource.mockResolvedValue({
|
|
89
|
+
id: "source-1",
|
|
90
|
+
ownerEmail: "owner@example.test",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("redacts continuation job capabilities and provider URLs", async () => {
|
|
95
|
+
expectPublicJob(await continueImport.run({ jobId: "job-1" }));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("redacts newly queued import job capabilities and provider URLs", async () => {
|
|
99
|
+
expectPublicJob(
|
|
100
|
+
await startImport.run({
|
|
101
|
+
sourceId: "source-1",
|
|
102
|
+
mode: "incremental",
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
expect(mocks.dispatchCreativeContextImportJob).toHaveBeenCalledWith({
|
|
106
|
+
jobId: "job-1",
|
|
107
|
+
ownerEmail: "owner@example.test",
|
|
108
|
+
orgId: "org-secret",
|
|
109
|
+
appId: "slides",
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("redacts newly queued enrichment job capabilities and provider URLs", async () => {
|
|
114
|
+
expectPublicJob(
|
|
115
|
+
await startEnrichment.run({
|
|
116
|
+
sourceId: "source-1",
|
|
117
|
+
operation: "enrich-media",
|
|
118
|
+
eagerLimit: 25,
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { serializePublicJob } from "../server/public-serialization.js";
|
|
4
5
|
import { createJob } from "../store/index.js";
|
|
5
6
|
|
|
6
7
|
const schema = z.object({
|
|
@@ -24,8 +25,8 @@ export default defineAction({
|
|
|
24
25
|
schema,
|
|
25
26
|
needsApproval: true,
|
|
26
27
|
publicAgent: { expose: true, readOnly: false, requiresAuth: true },
|
|
27
|
-
run: async (args) =>
|
|
28
|
-
job
|
|
28
|
+
run: async (args) => {
|
|
29
|
+
const job = await createJob({
|
|
29
30
|
sourceId: args.sourceId,
|
|
30
31
|
kind:
|
|
31
32
|
args.operation === "enrich-media"
|
|
@@ -45,6 +46,7 @@ export default defineAction({
|
|
|
45
46
|
eagerLimit: args.eagerLimit,
|
|
46
47
|
remainingMode: "pending-on-demand",
|
|
47
48
|
},
|
|
48
|
-
})
|
|
49
|
-
|
|
49
|
+
});
|
|
50
|
+
return { job: serializePublicJob(job) };
|
|
51
|
+
},
|
|
50
52
|
});
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
|
|
4
4
|
import { dispatchCreativeContextImportJob } from "../jobs/index.js";
|
|
5
5
|
import { getCreativeContext } from "../server/context.js";
|
|
6
|
+
import { serializePublicJob } from "../server/public-serialization.js";
|
|
6
7
|
import { createJob } from "../store/index.js";
|
|
7
8
|
import { getContextSource } from "../store/index.js";
|
|
8
9
|
|
|
@@ -32,6 +33,6 @@ export default defineAction({
|
|
|
32
33
|
orgId: job.orgId,
|
|
33
34
|
appId: getCreativeContext().connectorContext.appId,
|
|
34
35
|
});
|
|
35
|
-
return { job };
|
|
36
|
+
return { job: serializePublicJob(job) };
|
|
36
37
|
},
|
|
37
38
|
});
|