@gavana.ai/cli 0.2.2 → 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/CHANGELOG.md +28 -0
- package/README.md +27 -3
- package/guides/creative-canvas.md +16 -3
- package/guides/generated-assets.md +38 -5
- package/guides/getting-started.md +16 -1
- package/guides/paid-action-safety.md +2 -2
- package/guides/product-imports.md +45 -0
- package/guides/untrusted-content.md +51 -0
- package/guides/video-generation.md +52 -0
- package/guides/workflows.md +43 -0
- package/package.json +1 -1
- package/src/canvas-agent-guide.mjs +65 -13
- package/src/canvas-agent-validation.mjs +113 -17
- package/src/capabilities.mjs +1 -1
- package/src/client.mjs +16 -0
- package/src/commands.mjs +3 -3
- package/src/guide-sources.mjs +36 -4
- package/src/mcp-targets.mjs +1 -1
- package/src/runner.mjs +63 -7
- package/src/tools/definitions.mjs +2 -0
- package/src/tools/element_create.mjs +1 -1
- package/src/tools/element_update.mjs +2 -1
- package/src/tools/guide_get.mjs +1 -1
- package/src/tools/guide_search.mjs +1 -1
- package/src/tools/image_tool.mjs +9 -3
- package/src/tools/registry.mjs +72 -26
- package/src/tools/run_list.mjs +20 -0
- package/src/tools/schemas.mjs +37 -3
- package/src/tools/surface-names.mjs +120 -0
- package/src/version.mjs +1 -1
|
@@ -3,7 +3,7 @@ import { elementInput } from "./schemas.mjs";
|
|
|
3
3
|
export function defineElementCreate(client) {
|
|
4
4
|
return {
|
|
5
5
|
title: "Create a Gavana Element",
|
|
6
|
-
description: "Create a reusable visual Element
|
|
6
|
+
description: "Create a reusable visual Element. creative Elements inspire, identity Elements guide recognizable subjects, and exact Elements require one PNG source for deterministic composition.",
|
|
7
7
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
8
8
|
inputSchema: elementInput,
|
|
9
9
|
handler: (input) => client.createElement(input),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { elementType, mutableElementReference, assetReference } from "./schemas.mjs";
|
|
2
|
+
import { elementApplicationMode, elementType, mutableElementReference, assetReference } from "./schemas.mjs";
|
|
3
3
|
|
|
4
4
|
export function defineElementUpdate(client) {
|
|
5
5
|
return {
|
|
@@ -13,6 +13,7 @@ export function defineElementUpdate(client) {
|
|
|
13
13
|
type: elementType,
|
|
14
14
|
sourceAssetIds: z.array(assetReference).max(8).default([]),
|
|
15
15
|
guidelines: z.string().max(2_000).optional(),
|
|
16
|
+
applicationMode: elementApplicationMode.optional(),
|
|
16
17
|
})
|
|
17
18
|
.strict()
|
|
18
19
|
.refine((value) => value.sourceAssetIds.length > 0 || Boolean(value.guidelines?.trim()), "Add at least one source asset or guideline."),
|
package/src/tools/guide_get.mjs
CHANGED
|
@@ -11,6 +11,6 @@ export function defineGuideGet(client) {
|
|
|
11
11
|
description: `Read one canonical Gavana Canvas Agent Guide topic. Start at ${GAVANA_CANVAS_GUIDE_INDEX_URI} or use guide_search to find an exact topic ID.`,
|
|
12
12
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
13
13
|
inputSchema: z.object({ guideId: z.string().min(1).max(600).describe("Guide topic ID or exact gavana:// guide URI returned by guide_search or resources/list.") }),
|
|
14
|
-
handler: ({ guideId }) => getCanvasGuide(guideId),
|
|
14
|
+
handler: ({ guideId }) => getCanvasGuide(guideId, "local"),
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -11,6 +11,6 @@ export function defineGuideSearch(client) {
|
|
|
11
11
|
description: `Search the canonical Gavana Canvas Agent Guide v${GAVANA_CANVAS_GUIDE_VERSION}. Use this before the first canvas mutation in a session or whenever an operation is unfamiliar.`,
|
|
12
12
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
13
13
|
inputSchema: z.object({ query: z.string().max(240).default(""), limit: z.number().int().min(1).max(10).default(10) }),
|
|
14
|
-
handler: ({ query, limit }) => searchCanvasGuides(query, limit),
|
|
14
|
+
handler: ({ query, limit }) => searchCanvasGuides(query, limit, "local"),
|
|
15
15
|
};
|
|
16
16
|
}
|
package/src/tools/image_tool.mjs
CHANGED
|
@@ -11,16 +11,21 @@ export function imageToolDefinition(operation, client) {
|
|
|
11
11
|
title: `${operation === "generate" ? "Generate" : operation === "edit" ? "Edit" : "Vary"} canvas images`,
|
|
12
12
|
description:
|
|
13
13
|
operation === "generate"
|
|
14
|
-
? "Queue image generation into existing target image nodes. Campaign work needs a visible reference-led concept before output; reuse existing Canvas node:/asset: handles for product identity, brand-world, and typography/layout references.
|
|
14
|
+
? "Queue image generation into existing target image nodes. Campaign work needs a visible reference-led concept before output; reuse existing Canvas node:/asset: handles for product identity, brand-world, and typography/layout references. identity Elements guide preservation but do not guarantee identical pixels. exact Elements require normalized placements and are deterministically composited from original source pixels. Set preflight true to inspect the compiled reference plan before paid generation. For several images set count (1-4): Gavana writes one image node per output. Never describe multiple panels, frames, or a collage in one prompt, and when you pass targetNodeIds pass exactly one target node per output."
|
|
15
15
|
: operation === "edit"
|
|
16
|
-
? "Queue an image edit using stable node: or asset: references. Reuse a Canvas image by its existing handle rather than re-uploading it.
|
|
17
|
-
: "Queue variations using an existing node: or asset: source.",
|
|
16
|
+
? "Queue an image edit using stable node: or asset: references. Reuse a Canvas image by its existing handle rather than re-uploading it. For a local image the user explicitly supplied, call asset_upload first. identity guidance is not pixel-exact; use an exact Element with placements for deterministic logo or wordmark composition. Set preflight true to inspect the compiled reference plan before paid generation. For several images set count (1-4): Gavana writes one image node per output. Never describe multiple panels, frames, or a collage in one prompt, and when you pass targetNodeIds pass exactly one target node per output."
|
|
17
|
+
: "Queue variations using an existing node: or asset: source. For several images set count (1-4): Gavana writes one image node per output. Never describe multiple panels, frames, or a collage in one prompt, and when you pass targetNodeIds pass exactly one target node per output.",
|
|
18
18
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
19
19
|
inputSchema: imageSchema(),
|
|
20
20
|
markdown: true,
|
|
21
21
|
handler: async (input, extra) => {
|
|
22
22
|
validateImageInput(operation, input);
|
|
23
23
|
const destination = input.destination || input.canvasId;
|
|
24
|
+
if (input.preflight === true) {
|
|
25
|
+
const { destination: _destination, canvasId: _canvasId, canvasTitle: _canvasTitle, preflight: _preflight, wait: _wait, timeoutSeconds: _timeoutSeconds, ...preflightInput } = input;
|
|
26
|
+
const plan = await client.preflightImage(operation, { ...preflightInput, canvasId: destination });
|
|
27
|
+
return { ...plan, destination: { requested: destination, canvasId: destination, targetNodeIds: input.targetNodeIds } };
|
|
28
|
+
}
|
|
24
29
|
const prepared = await client.prepareImageDestination({
|
|
25
30
|
...input,
|
|
26
31
|
operation,
|
|
@@ -36,6 +41,7 @@ export function imageToolDefinition(operation, client) {
|
|
|
36
41
|
targetY: _targetY,
|
|
37
42
|
targetWidth: _targetWidth,
|
|
38
43
|
targetHeight: _targetHeight,
|
|
44
|
+
preflight: _preflight,
|
|
39
45
|
wait: _wait,
|
|
40
46
|
timeoutSeconds: _timeoutSeconds,
|
|
41
47
|
...jobInput
|
package/src/tools/registry.mjs
CHANGED
|
@@ -144,7 +144,11 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
144
144
|
{
|
|
145
145
|
name: "canvas_create",
|
|
146
146
|
toolset: "canvas",
|
|
147
|
-
surfaces: ["local"],
|
|
147
|
+
surfaces: ["hosted","local"],
|
|
148
|
+
scopes: ["canvas:read","canvas:write"],
|
|
149
|
+
hostedOrder: 6,
|
|
150
|
+
hostedReadOnly: false,
|
|
151
|
+
hostedToolset: "canvas",
|
|
148
152
|
localOrder: 15,
|
|
149
153
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},
|
|
150
154
|
},
|
|
@@ -260,6 +264,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
260
264
|
scopes: ["canvas:read","job:manage"],
|
|
261
265
|
hostedOrder: 12,
|
|
262
266
|
hostedReadOnly: false,
|
|
267
|
+
hostedCategory: "read",
|
|
263
268
|
hostedToolset: "runs",
|
|
264
269
|
embedImage: true,
|
|
265
270
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
@@ -284,7 +289,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
284
289
|
toolset: "elements",
|
|
285
290
|
surfaces: ["hosted","local"],
|
|
286
291
|
scopes: ["element:read","element:write"],
|
|
287
|
-
hostedOrder:
|
|
292
|
+
hostedOrder: 18,
|
|
288
293
|
hostedReadOnly: false,
|
|
289
294
|
hostedToolset: "elements",
|
|
290
295
|
localOrder: 48,
|
|
@@ -296,7 +301,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
296
301
|
toolset: "elements",
|
|
297
302
|
surfaces: ["hosted","local"],
|
|
298
303
|
scopes: ["element:read","element:write"],
|
|
299
|
-
hostedOrder:
|
|
304
|
+
hostedOrder: 19,
|
|
300
305
|
hostedReadOnly: false,
|
|
301
306
|
hostedToolset: "elements",
|
|
302
307
|
localOrder: 50,
|
|
@@ -307,7 +312,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
307
312
|
toolset: "elements",
|
|
308
313
|
surfaces: ["hosted","local"],
|
|
309
314
|
scopes: ["element:read","element:write"],
|
|
310
|
-
hostedOrder:
|
|
315
|
+
hostedOrder: 20,
|
|
311
316
|
hostedReadOnly: false,
|
|
312
317
|
hostedToolset: "elements",
|
|
313
318
|
localOrder: 53,
|
|
@@ -320,7 +325,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
320
325
|
surfaces: ["hosted","local"],
|
|
321
326
|
scopes: ["element:read"],
|
|
322
327
|
readOnlySurface: true,
|
|
323
|
-
hostedOrder:
|
|
328
|
+
hostedOrder: 21,
|
|
324
329
|
hostedReadOnly: true,
|
|
325
330
|
hostedToolset: "elements",
|
|
326
331
|
localOrder: 49,
|
|
@@ -331,7 +336,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
331
336
|
toolset: "elements",
|
|
332
337
|
surfaces: ["hosted","local"],
|
|
333
338
|
scopes: ["element:read","element:write"],
|
|
334
|
-
hostedOrder:
|
|
339
|
+
hostedOrder: 22,
|
|
335
340
|
hostedReadOnly: false,
|
|
336
341
|
hostedToolset: "elements",
|
|
337
342
|
localOrder: 52,
|
|
@@ -342,7 +347,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
342
347
|
toolset: "elements",
|
|
343
348
|
surfaces: ["hosted","local"],
|
|
344
349
|
scopes: ["element:read","element:write"],
|
|
345
|
-
hostedOrder:
|
|
350
|
+
hostedOrder: 23,
|
|
346
351
|
hostedReadOnly: false,
|
|
347
352
|
hostedToolset: "elements",
|
|
348
353
|
localOrder: 45,
|
|
@@ -354,7 +359,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
354
359
|
surfaces: ["hosted","local"],
|
|
355
360
|
scopes: ["element:read"],
|
|
356
361
|
readOnlySurface: true,
|
|
357
|
-
hostedOrder:
|
|
362
|
+
hostedOrder: 24,
|
|
358
363
|
hostedReadOnly: true,
|
|
359
364
|
hostedToolset: "elements",
|
|
360
365
|
localOrder: 43,
|
|
@@ -366,7 +371,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
366
371
|
surfaces: ["hosted","local"],
|
|
367
372
|
scopes: ["element:read"],
|
|
368
373
|
readOnlySurface: true,
|
|
369
|
-
hostedOrder:
|
|
374
|
+
hostedOrder: 25,
|
|
370
375
|
hostedReadOnly: true,
|
|
371
376
|
hostedToolset: "elements",
|
|
372
377
|
localOrder: 44,
|
|
@@ -378,7 +383,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
378
383
|
surfaces: ["hosted","local"],
|
|
379
384
|
scopes: ["element:read"],
|
|
380
385
|
readOnlySurface: true,
|
|
381
|
-
hostedOrder:
|
|
386
|
+
hostedOrder: 26,
|
|
382
387
|
hostedReadOnly: true,
|
|
383
388
|
hostedToolset: "elements",
|
|
384
389
|
localOrder: 42,
|
|
@@ -389,7 +394,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
389
394
|
toolset: "elements",
|
|
390
395
|
surfaces: ["hosted","local"],
|
|
391
396
|
scopes: ["element:read","element:write"],
|
|
392
|
-
hostedOrder:
|
|
397
|
+
hostedOrder: 27,
|
|
393
398
|
hostedReadOnly: false,
|
|
394
399
|
hostedToolset: "elements",
|
|
395
400
|
localOrder: 47,
|
|
@@ -400,7 +405,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
400
405
|
toolset: "elements",
|
|
401
406
|
surfaces: ["hosted","local"],
|
|
402
407
|
scopes: ["element:read","element:write"],
|
|
403
|
-
hostedOrder:
|
|
408
|
+
hostedOrder: 28,
|
|
404
409
|
hostedReadOnly: false,
|
|
405
410
|
hostedToolset: "elements",
|
|
406
411
|
localOrder: 46,
|
|
@@ -411,7 +416,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
411
416
|
toolset: "elements",
|
|
412
417
|
surfaces: ["hosted","local"],
|
|
413
418
|
scopes: ["element:read","element:write"],
|
|
414
|
-
hostedOrder:
|
|
419
|
+
hostedOrder: 29,
|
|
415
420
|
hostedReadOnly: false,
|
|
416
421
|
hostedToolset: "elements",
|
|
417
422
|
localOrder: 51,
|
|
@@ -455,7 +460,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
455
460
|
hostedAlias: "generate_image_in_canvas",
|
|
456
461
|
scopes: ["canvas:read","canvas:write","asset:read","image:generate","job:manage"],
|
|
457
462
|
conditionalScopes: { elements: ["element:read"] },
|
|
458
|
-
hostedOrder:
|
|
463
|
+
hostedOrder: 14,
|
|
459
464
|
paid: true,
|
|
460
465
|
hostedReadOnly: false,
|
|
461
466
|
hostedToolset: "images",
|
|
@@ -551,6 +556,18 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
551
556
|
localOrder: 20,
|
|
552
557
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
553
558
|
},
|
|
559
|
+
{
|
|
560
|
+
name: "product_photoshoot_generate",
|
|
561
|
+
toolset: "images",
|
|
562
|
+
surfaces: ["hosted"],
|
|
563
|
+
hostedAlias: "generate_product_photoshoot_in_canvas",
|
|
564
|
+
scopes: ["canvas:read","canvas:write","asset:read","image:generate","job:manage"],
|
|
565
|
+
hostedOrder: 13,
|
|
566
|
+
paid: true,
|
|
567
|
+
hostedReadOnly: false,
|
|
568
|
+
hostedToolset: "images",
|
|
569
|
+
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},
|
|
570
|
+
},
|
|
554
571
|
{
|
|
555
572
|
name: "product_reference_pack_import",
|
|
556
573
|
toolset: "assets",
|
|
@@ -611,10 +628,30 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
611
628
|
{
|
|
612
629
|
name: "run_get",
|
|
613
630
|
toolset: "runs",
|
|
614
|
-
surfaces: ["local"],
|
|
631
|
+
surfaces: ["hosted","local"],
|
|
632
|
+
hostedAlias: "get_image_run",
|
|
633
|
+
scopes: ["job:manage"],
|
|
634
|
+
embedImage: true,
|
|
635
|
+
hostedOrder: 35,
|
|
636
|
+
hostedReadOnly: false,
|
|
637
|
+
hostedCategory: "read",
|
|
638
|
+
hostedToolset: "runs",
|
|
615
639
|
localOrder: 42,
|
|
616
640
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
617
641
|
},
|
|
642
|
+
{
|
|
643
|
+
name: "run_list",
|
|
644
|
+
toolset: "jobs",
|
|
645
|
+
surfaces: ["hosted","local"],
|
|
646
|
+
scopes: ["job:manage"],
|
|
647
|
+
hostedAlias: "list_image_runs",
|
|
648
|
+
hostedOrder: 36,
|
|
649
|
+
hostedReadOnly: false,
|
|
650
|
+
hostedToolset: "jobs",
|
|
651
|
+
localOrder: 41,
|
|
652
|
+
readOnlySurface: true,
|
|
653
|
+
annotations: {"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
654
|
+
},
|
|
618
655
|
{
|
|
619
656
|
name: "run_wait",
|
|
620
657
|
toolset: "runs",
|
|
@@ -627,8 +664,8 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
627
664
|
toolset: "videos",
|
|
628
665
|
surfaces: ["hosted","local"],
|
|
629
666
|
hostedAlias: "generate_video",
|
|
630
|
-
scopes: ["canvas:read","asset:read","video:generate","job:manage"],
|
|
631
|
-
hostedOrder:
|
|
667
|
+
scopes: ["canvas:read","canvas:write","asset:read","video:generate","job:manage"],
|
|
668
|
+
hostedOrder: 16,
|
|
632
669
|
paid: true,
|
|
633
670
|
hostedReadOnly: false,
|
|
634
671
|
hostedToolset: "videos",
|
|
@@ -641,8 +678,9 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
641
678
|
surfaces: ["hosted"],
|
|
642
679
|
hostedAlias: "get_video_job",
|
|
643
680
|
scopes: ["job:manage"],
|
|
644
|
-
hostedOrder:
|
|
681
|
+
hostedOrder: 17,
|
|
645
682
|
hostedReadOnly: false,
|
|
683
|
+
hostedCategory: "read",
|
|
646
684
|
hostedToolset: "runs",
|
|
647
685
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
648
686
|
},
|
|
@@ -652,8 +690,8 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
652
690
|
surfaces: ["hosted"],
|
|
653
691
|
hostedAlias: "find_video_models",
|
|
654
692
|
scopes: ["video:generate"],
|
|
655
|
-
hostedOrder:
|
|
656
|
-
hostedReadOnly:
|
|
693
|
+
hostedOrder: 15,
|
|
694
|
+
hostedReadOnly: false,
|
|
657
695
|
hostedToolset: "models",
|
|
658
696
|
annotations: {"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
659
697
|
},
|
|
@@ -662,7 +700,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
662
700
|
toolset: "canvas",
|
|
663
701
|
surfaces: ["hosted","local"],
|
|
664
702
|
scopes: ["canvas:read","canvas:write"],
|
|
665
|
-
hostedOrder:
|
|
703
|
+
hostedOrder: 32,
|
|
666
704
|
hostedReadOnly: false,
|
|
667
705
|
hostedToolset: "canvas",
|
|
668
706
|
localOrder: 56,
|
|
@@ -674,7 +712,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
674
712
|
toolset: "canvas",
|
|
675
713
|
surfaces: ["hosted","local"],
|
|
676
714
|
scopes: ["canvas:read","canvas:write","asset:read","image:generate"],
|
|
677
|
-
hostedOrder:
|
|
715
|
+
hostedOrder: 33,
|
|
678
716
|
paid: true,
|
|
679
717
|
hostedReadOnly: false,
|
|
680
718
|
hostedToolset: "canvas",
|
|
@@ -688,7 +726,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
688
726
|
surfaces: ["hosted","local"],
|
|
689
727
|
scopes: ["canvas:read"],
|
|
690
728
|
readOnlySurface: true,
|
|
691
|
-
hostedOrder:
|
|
729
|
+
hostedOrder: 31,
|
|
692
730
|
hostedReadOnly: true,
|
|
693
731
|
hostedToolset: "canvas",
|
|
694
732
|
localOrder: 55,
|
|
@@ -700,7 +738,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
700
738
|
toolset: "canvas",
|
|
701
739
|
surfaces: ["hosted","local"],
|
|
702
740
|
scopes: ["canvas:read","canvas:write","asset:read"],
|
|
703
|
-
hostedOrder:
|
|
741
|
+
hostedOrder: 30,
|
|
704
742
|
hostedReadOnly: false,
|
|
705
743
|
hostedToolset: "canvas",
|
|
706
744
|
localOrder: 54,
|
|
@@ -712,7 +750,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
712
750
|
toolset: "canvas",
|
|
713
751
|
surfaces: ["hosted","local"],
|
|
714
752
|
scopes: ["canvas:read","canvas:write","image:generate"],
|
|
715
|
-
hostedOrder:
|
|
753
|
+
hostedOrder: 34,
|
|
716
754
|
hostedReadOnly: false,
|
|
717
755
|
hostedToolset: "canvas",
|
|
718
756
|
localOrder: 58,
|
|
@@ -744,8 +782,16 @@ export function gavanaToolsForSurface(surface, { includeLegacyCampaign = false,
|
|
|
744
782
|
// each surface keeps the order it shipped with rather than falling out of the
|
|
745
783
|
// registry's alphabetical layout.
|
|
746
784
|
const key = surface === "hosted" ? "hostedOrder" : "localOrder";
|
|
785
|
+
// The two read-only flags are not the same question, and `run_list` is the
|
|
786
|
+
// first tool where they part. Local read-only serves whatever declares
|
|
787
|
+
// `readOnlyHint`, so a tool that only reads belongs there. Hosted read-only
|
|
788
|
+
// additionally has to grant the tool's OAuth scopes out of a read-only set
|
|
789
|
+
// that does not contain `job:manage`, so the same tool cannot be served
|
|
790
|
+
// there at all. Filtering both surfaces through the local flag advertised a
|
|
791
|
+
// hosted tool the hosted server would never serve.
|
|
792
|
+
const readOnlyKey = surface === "hosted" ? "hostedReadOnly" : "readOnlySurface";
|
|
747
793
|
return GAVANA_TOOL_REGISTRY.filter(
|
|
748
|
-
(tool) => tool.surfaces.includes(surface) && (includeLegacyCampaign || !tool.legacyCampaign) && (!readOnly || tool
|
|
794
|
+
(tool) => tool.surfaces.includes(surface) && (includeLegacyCampaign || !tool.legacyCampaign) && (!readOnly || tool[readOnlyKey]),
|
|
749
795
|
)
|
|
750
796
|
.slice()
|
|
751
797
|
.sort((left, right) => (left[key] ?? 0) - (right[key] ?? 0));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Tool definition: run_list
|
|
2
|
+
//
|
|
3
|
+
// Registered through ./definitions.mjs. ./registry.mjs is the catalog that says
|
|
4
|
+
// this tool exists and on which surfaces; this file is what it does.
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
export function defineRunList(client) {
|
|
8
|
+
return {
|
|
9
|
+
title: "List Gavana image runs",
|
|
10
|
+
description:
|
|
11
|
+
"List image and Action runs this account has started, newest first, with their status and canvas. Use this to answer questions about work already in flight, to recover a run handle you no longer have, and to see which runs are holding the concurrent-job limit.",
|
|
12
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
status: z.enum(["active", "all"]).default("active").describe("active returns only runs still queued or running; all includes finished and failed runs."),
|
|
15
|
+
canvasId: z.string().min(1).max(240).optional().describe("Restrict to runs targeting one canvas."),
|
|
16
|
+
limit: z.number().int().min(1).max(50).default(20),
|
|
17
|
+
}),
|
|
18
|
+
handler: (input) => client.listRuns(input),
|
|
19
|
+
};
|
|
20
|
+
}
|
package/src/tools/schemas.mjs
CHANGED
|
@@ -26,13 +26,17 @@ const elementCollectionReference = z
|
|
|
26
26
|
.string()
|
|
27
27
|
.regex(/^element-collection:[A-Za-z0-9_-]{1,180}$/)
|
|
28
28
|
.describe("Stable Element collection handle element-collection:<id>.");
|
|
29
|
-
const elementType = z.enum(["character", "product/object", "environment", "style", "material/texture", "lighting"]);
|
|
29
|
+
const elementType = z.enum(["character", "product/object", "environment", "style", "material/texture", "lighting", "brand/mark"]);
|
|
30
|
+
const elementApplicationMode = z
|
|
31
|
+
.enum(["creative", "identity", "exact"])
|
|
32
|
+
.describe("creative permits visual reinterpretation; identity guides preservation but is not pixel-exact; exact composites original Element pixels after generation.");
|
|
30
33
|
const elementInput = z
|
|
31
34
|
.object({
|
|
32
35
|
name: z.string().min(1).max(160),
|
|
33
36
|
type: elementType,
|
|
34
37
|
sourceAssetIds: z.array(assetReference).max(8).default([]),
|
|
35
38
|
guidelines: z.string().max(2_000).optional(),
|
|
39
|
+
applicationMode: elementApplicationMode.optional().describe("creative uses an Element as inspiration; identity guides recognizable subjects; exact preserves an original PNG through deterministic composition."),
|
|
36
40
|
collectionIds: z.array(elementCollectionReference).max(24).optional(),
|
|
37
41
|
})
|
|
38
42
|
.strict()
|
|
@@ -119,6 +123,15 @@ const approvedOutputReferences = z
|
|
|
119
123
|
const campaignIdempotencyKey = z.string().min(8).max(200).describe("Required caller-stable retry key. Reuse it only for the exact same campaign action.");
|
|
120
124
|
const recipeForkIdempotencyKey = z.string().min(8).max(200).describe("Required caller-stable retry key. Reuse it only for the exact same Recipe fork.");
|
|
121
125
|
const campaignAspectRatio = z.enum(["1:1", "4:5", "3:4", "16:9", "9:16"]);
|
|
126
|
+
const exactElementPlacement = z
|
|
127
|
+
.object({
|
|
128
|
+
x: z.number().min(0).max(1).describe("Normalized left coordinate in the output, from 0 through 1."),
|
|
129
|
+
y: z.number().min(0).max(1).describe("Normalized top coordinate in the output, from 0 through 1."),
|
|
130
|
+
width: z.number().gt(0).max(1).describe("Normalized width in the output, greater than 0 through 1."),
|
|
131
|
+
height: z.number().gt(0).max(1).describe("Normalized height in the output, greater than 0 through 1."),
|
|
132
|
+
})
|
|
133
|
+
.strict()
|
|
134
|
+
.refine((placement) => placement.x + placement.width <= 1 && placement.y + placement.height <= 1, "Exact Element placement must remain inside the normalized output bounds.");
|
|
122
135
|
const elementGenerationInput = z
|
|
123
136
|
.union([
|
|
124
137
|
pinnedElementReference,
|
|
@@ -127,10 +140,16 @@ const elementGenerationInput = z
|
|
|
127
140
|
handle: pinnedElementReference,
|
|
128
141
|
role: z.enum(["identity", "construction", "texture", "fit", "style"]).optional(),
|
|
129
142
|
influence: z.number().min(0).max(1).optional(),
|
|
143
|
+
applicationMode: elementApplicationMode.optional(),
|
|
144
|
+
currentTurnUserModeOverride: z
|
|
145
|
+
.literal(true)
|
|
146
|
+
.optional()
|
|
147
|
+
.describe("Required only when the current user explicitly asks to override this Element version's stored application mode."),
|
|
148
|
+
placements: z.array(exactElementPlacement).min(1).max(16).optional().describe("Required for exact Elements. Repeat placements to composite unchanged source pixels more than once."),
|
|
130
149
|
})
|
|
131
150
|
.strict(),
|
|
132
151
|
])
|
|
133
|
-
.describe("A version-pinned Element,
|
|
152
|
+
.describe("A version-pinned Element. identity is guided preservation, not pixel-exact. exact requires placements and returns exact-composed only when original Element pixels are deterministically composited.");
|
|
134
153
|
const MAX_LOCAL_REFERENCE_IMAGE_BYTES = 50 * 1024 * 1024;
|
|
135
154
|
const revisionFields = {
|
|
136
155
|
baseRevision: z.string().min(1).optional().describe("Revision returned by canvas_get. Omit to read the latest revision immediately before the write."),
|
|
@@ -165,7 +184,16 @@ function imageSchema() {
|
|
|
165
184
|
model: z.union([modelReference, rawModelId]).optional(),
|
|
166
185
|
size: z.string().max(80).optional(),
|
|
167
186
|
quality: z.string().max(80).optional(),
|
|
168
|
-
count: z
|
|
187
|
+
count: z
|
|
188
|
+
.number()
|
|
189
|
+
.int()
|
|
190
|
+
.min(1)
|
|
191
|
+
.max(4)
|
|
192
|
+
.optional()
|
|
193
|
+
.describe(
|
|
194
|
+
"Number of separate images to generate, 1-4. Gavana creates one image node per output, and this is the only way to produce more than one image. Never ask for several panels, frames, or a collage inside one prompt. When you pass targetNodeIds, count must equal the number of targetNodeIds; when you omit count, Gavana uses the number of targets.",
|
|
195
|
+
),
|
|
196
|
+
preflight: z.boolean().optional().describe("Compile and validate the final reference plan without creating targets, starting a provider request, or consuming generation credits. Requires an existing canvas and explicit targetNodeIds."),
|
|
169
197
|
wait: z.boolean().default(false).describe("Return after queueing by default. Set true to wait for the completed Run."),
|
|
170
198
|
timeoutSeconds: z.number().min(1).max(3_600).default(900),
|
|
171
199
|
});
|
|
@@ -182,6 +210,11 @@ function validateImageInput(operation, value) {
|
|
|
182
210
|
// the job, and the blank node would be left behind as an orphan.
|
|
183
211
|
if (operation !== "variations" && !value.prompt && !value.promptNodeId && !value.targetNodeIds?.length) issues.push("Provide prompt or promptNodeId, or pass targetNodeIds whose nodes have a prompt connection.");
|
|
184
212
|
if (operation !== "generate" && !value.source && !value.references?.length) issues.push(`${operation} requires source or references.`);
|
|
213
|
+
if (value.preflight) {
|
|
214
|
+
const destination = value.destination || value.canvasId;
|
|
215
|
+
if (destination === "agent-canvas" || destination === "new-canvas") issues.push("preflight requires an existing canvas handle, not agent-canvas or new-canvas.");
|
|
216
|
+
if (!value.targetNodeIds?.length) issues.push("preflight requires explicit targetNodeIds; it never creates target nodes.");
|
|
217
|
+
}
|
|
185
218
|
if (!issues.length) return;
|
|
186
219
|
const error = new Error(`Input validation error: ${issues.join(" ")}`);
|
|
187
220
|
error.code = "invalid_image_input";
|
|
@@ -195,6 +228,7 @@ export {
|
|
|
195
228
|
assetReference,
|
|
196
229
|
elementCollectionReference,
|
|
197
230
|
elementGenerationInput,
|
|
231
|
+
elementApplicationMode,
|
|
198
232
|
elementInput,
|
|
199
233
|
elementType,
|
|
200
234
|
campaignAspectRatio,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Tool vocabulary per surface.
|
|
2
|
+
//
|
|
3
|
+
// The hosted and local servers advertise different catalogs under different
|
|
4
|
+
// naming conventions (see ./registry.mjs). The Canvas Agent Guide is shared
|
|
5
|
+
// prose, so a sentence written in one surface's vocabulary reaches agents on
|
|
6
|
+
// the other surface naming a tool they cannot call. That is not a hypothetical:
|
|
7
|
+
// the guide told every agent to "call `model_list` before image or video
|
|
8
|
+
// generation", and `model_list` is local-only, so hosted agents either guessed
|
|
9
|
+
// a substitute or gave up on a mandatory pre-flight step.
|
|
10
|
+
//
|
|
11
|
+
// This module is the one place that answers "what is this tool called here, and
|
|
12
|
+
// does it exist here at all". scripts/canvas-agent-guide.test.mjs asserts that
|
|
13
|
+
// no served guide text names a tool the reading surface cannot call, so the
|
|
14
|
+
// drift fails the build instead of reaching an agent.
|
|
15
|
+
|
|
16
|
+
import { GAVANA_TOOL_REGISTRY } from "./registry.mjs";
|
|
17
|
+
|
|
18
|
+
export const GAVANA_TOOL_SURFACES = Object.freeze(["hosted", "local"]);
|
|
19
|
+
|
|
20
|
+
/** Name a surface advertises for one registry entry, or undefined when it does not advertise it. */
|
|
21
|
+
function advertisedNameFor(tool, surface) {
|
|
22
|
+
if (!tool.surfaces.includes(surface)) return undefined;
|
|
23
|
+
return surface === "hosted" ? (tool.hostedAlias ?? tool.name) : tool.name;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Every spelling of every tool, mapped to what `surface` calls it.
|
|
28
|
+
*
|
|
29
|
+
* `rename` covers both directions: a canonical name written in the guide maps to
|
|
30
|
+
* the hosted alias when hosted advertises it, and a hosted alias maps back to the
|
|
31
|
+
* canonical name for the local surface. `absent` holds tools no spelling of which
|
|
32
|
+
* the surface can call.
|
|
33
|
+
*/
|
|
34
|
+
export function surfaceToolVocabulary(surface) {
|
|
35
|
+
if (!GAVANA_TOOL_SURFACES.includes(surface)) {
|
|
36
|
+
throw new Error(`Unknown tool surface: ${surface}. Expected one of ${GAVANA_TOOL_SURFACES.join(", ")}.`);
|
|
37
|
+
}
|
|
38
|
+
const advertised = new Set();
|
|
39
|
+
const rename = new Map();
|
|
40
|
+
const absent = new Set();
|
|
41
|
+
|
|
42
|
+
for (const tool of GAVANA_TOOL_REGISTRY) {
|
|
43
|
+
const spellings = [tool.name, tool.hostedAlias].filter(Boolean);
|
|
44
|
+
const here = advertisedNameFor(tool, surface);
|
|
45
|
+
if (!here) {
|
|
46
|
+
for (const spelling of spellings) absent.add(spelling);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
advertised.add(here);
|
|
50
|
+
for (const spelling of spellings) {
|
|
51
|
+
if (spelling !== here) rename.set(spelling, here);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// A spelling this surface advertises is never "absent", even when another
|
|
56
|
+
// registry entry shares the word. Advertised always wins.
|
|
57
|
+
for (const name of advertised) absent.delete(name);
|
|
58
|
+
for (const spelling of rename.keys()) absent.delete(spelling);
|
|
59
|
+
|
|
60
|
+
return { surface, advertised, rename, absent };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const VOCABULARIES = new Map(GAVANA_TOOL_SURFACES.map((surface) => [surface, surfaceToolVocabulary(surface)]));
|
|
64
|
+
|
|
65
|
+
/** Cached vocabulary. Callers must not mutate the returned sets. */
|
|
66
|
+
export function toolVocabulary(surface) {
|
|
67
|
+
const vocabulary = VOCABULARIES.get(surface);
|
|
68
|
+
if (!vocabulary) throw new Error(`Unknown tool surface: ${surface}. Expected one of ${GAVANA_TOOL_SURFACES.join(", ")}.`);
|
|
69
|
+
return vocabulary;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Word boundary that keeps `run_get` from matching inside `canvas_workflow_run_get`.
|
|
73
|
+
const identifier = (name) => new RegExp(`(?<![A-Za-z0-9_])${name}(?![A-Za-z0-9_])`, "g");
|
|
74
|
+
|
|
75
|
+
const CONDITIONAL = /\{\{#(hosted|local)\}\}([\s\S]*?)\{\{\/\1\}\}/g;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolve `{{#hosted}}…{{/hosted}}` / `{{#local}}…{{/local}}` spans for one surface.
|
|
79
|
+
*
|
|
80
|
+
* Guide prose uses these where the two surfaces genuinely differ in capability
|
|
81
|
+
* rather than only in spelling — an instruction that cannot be satisfied at all
|
|
82
|
+
* on one surface must not merely be renamed there.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveSurfaceConditionals(text, surface) {
|
|
85
|
+
return String(text ?? "").replace(CONDITIONAL, (_match, branch, body) => (branch === surface ? body : ""));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Rewrite every tool name in `text` to the spelling `surface` advertises. */
|
|
89
|
+
export function renameToolsForSurface(text, surface) {
|
|
90
|
+
const { rename } = toolVocabulary(surface);
|
|
91
|
+
let output = String(text ?? "");
|
|
92
|
+
for (const [from, to] of rename) output = output.replace(identifier(from), to);
|
|
93
|
+
return output;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Guide prose as one surface should read it: conditionals resolved, then names
|
|
98
|
+
* translated. Order matters — a conditional branch may name a tool that only
|
|
99
|
+
* exists on its own surface, so the other branch must be dropped before renaming.
|
|
100
|
+
*/
|
|
101
|
+
export function renderForSurface(text, surface) {
|
|
102
|
+
return renameToolsForSurface(resolveSurfaceConditionals(text, surface), surface);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Tool names `text` mentions that `surface` cannot call. Empty is the contract.
|
|
107
|
+
*
|
|
108
|
+
* Only registry-known spellings count, so ordinary prose is never flagged. Call
|
|
109
|
+
* this on already-rendered text: an unresolved conditional would report its
|
|
110
|
+
* other branch as a violation.
|
|
111
|
+
*/
|
|
112
|
+
export function unavailableToolMentions(text, surface) {
|
|
113
|
+
const { absent } = toolVocabulary(surface);
|
|
114
|
+
const value = String(text ?? "");
|
|
115
|
+
const found = new Set();
|
|
116
|
+
for (const name of absent) {
|
|
117
|
+
if (identifier(name).test(value)) found.add(name);
|
|
118
|
+
}
|
|
119
|
+
return Array.from(found).sort();
|
|
120
|
+
}
|
package/src/version.mjs
CHANGED