Skip to content

feat(agent-toolkit): support asset_id for image blocks in update_doc#280

Merged
RoniLandau merged 11 commits intomasterfrom
feat/update-doc-image-asset-support
Mar 25, 2026
Merged

feat(agent-toolkit): support asset_id for image blocks in update_doc#280
RoniLandau merged 11 commits intomasterfrom
feat/update-doc-image-asset-support

Conversation

@RoniLandau
Copy link
Collaborator

Summary

  • Add asset_id as an alternative to public_url for image blocks in create_block and replace_block operations
  • asset_id accepts both number and string (coerced to number) since agents may send it as either type
  • Update buildCreateBlockInput helper to pass asset_id to the image_block GraphQL input when provided
  • Update tool description to guide agents on splitting mixed content (text + asset images) into alternating add_markdown_content and create_block operations, since add_markdown_content does not support asset-based images

Test plan

  • Image block with asset_id (number) creates block with asset_id in GraphQL input, no public_url
  • Image block with asset_id (string) coerces to number correctly
  • replace_block with asset_id image works (delete + create)
  • Existing public_url image block tests still pass
  • All 27 tests passing

🤖 Generated with Claude Code

RoniLandau and others added 4 commits March 24, 2026 22:26
Allow image blocks in create_block/replace_block operations to use
asset_id instead of public_url. The create_doc_blocks API handles
asset-based images internally.

Updated tool description to guide agents on splitting mixed content
(text + asset images) into alternating add_markdown_content and
create_block operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoids internal server error when creating asset-based image blocks,
since ImageContent.public_url is non-nullable but unavailable for
asset-based images.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@LinoyMargan
Copy link
Collaborator

PR Review: feat(agent-toolkit): support asset_id for image blocks

Critical Issues (must fix before merge)

1. Verify asset_id is in the live API schema

ImageBlockInput in the generated types (src/monday-graphql/generated/graphql/graphql.ts) only has { parent_block_id, public_url, width } — no asset_id. This is the most important question: does the live monday.com GraphQL API actually accept asset_id on image block input?

Run npm run fetch:generate and check if ImageBlockInput gains asset_id. If it doesn't appear, the feature can't work as written. The as CreateBlockInput cast currently hides this mismatch entirely.

2. No validation when neither public_url nor asset_id is provided

Both fields are .optional() in CreateBlockImageSchema with no .refine() enforcement, and there's no guard in the helper. An agent can pass { block_type: 'image' } with nothing else — it will pass Zod validation and the helper will silently send { image_block: { width: undefined, public_url: undefined } } to the API. The resulting error will be either a raw GraphQL message or the unhelpful "No blocks returned from create_doc_blocks".

Suggested fix — add an explicit guard in buildCreateBlockInput:

case 'image': {
  if (block.asset_id == null && !block.public_url) {
    throw new Error('image block requires either asset_id or public_url');
  }
  // ...
}

(Note: .refine() on a discriminated union member requires using it at the CreateBlockSchema level or in the helper — not on the member schema directly.)


Important Issues (should fix)

3. if (block.asset_id) is a falsy check on an ID field

update-doc-tool.helpers.ts — image case

If asset_id is 0 (falsy, but valid per z.number().int()), the condition is false, the code falls through to the else branch, and public_url = undefined gets sent to the API silently. Use block.asset_id != null instead.

4. as CreateBlockInput cast bypasses TypeScript type safety

update-doc-tool.helpers.ts — image case

All other block types return properly typed objects. This is the only case using Record<string, unknown> + a type assertion, meaning TypeScript won't catch regressions if ImageBlockInput gains new required fields after a future schema regeneration. If/when asset_id is added to the generated types, replace with a typed conditional:

const imageBlockInput: ImageBlockInput = block.asset_id != null
  ? { asset_id: block.asset_id, width: block.width }
  : { public_url: block.public_url!, width: block.width };
return { image_block: imageBlockInput };

5. replace_block test doesn't assert the GraphQL payload

update-doc-tool.test.tshandles replace_block with asset_id image

The test only checks [OK] replace_block and the block ID. It doesn't verify blocksInput[0].image_block.asset_id === 5555 or that public_url is absent. The existing public_url replace_block test does assert the payload — this should match that pattern.


Missing Tests

Case Priority
asset_id: 0 — should fail clearly, not silently fall back to public_url path High
Neither public_url nor asset_id provided — should fail with a clear message High
Both public_url and asset_id provided — document which wins, assert it in the payload Medium
Invalid string asset_id (e.g. "abc") — assert schema validation error Medium
replace_block GraphQL payload assertions (see #5 above) Medium

What's Good ✓

  • String coercion via z.union([z.number().int(), z.string().regex(/^\d+$/).transform(Number)]) is exactly right for agents sending IDs as either type.
  • The first asset_id test correctly asserts both presence of asset_id AND absence of public_url — the right mutual-exclusion pattern.
  • Removing public_url from the GraphQL response fragment is safe — it's the response selection set only, not the mutation input.
  • Tool description update in getDescription() and the schema operation guide are clear and actionable for agents.
  • Version bump follows CLAUDE.md conventions.
  • Tests are behaviorally focused (asserting GraphQL inputs, not internal method calls).

RoniLandau and others added 7 commits March 25, 2026 09:49
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds guard in buildCreateBlockInput to throw a clear error when an
image block is missing both asset_id and public_url, instead of
sending an invalid request to the API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use `block.asset_id != null` instead of falsy check (handles edge
  case where asset_id is 0)
- Replace Record<string, unknown> spread with typed conditional to
  preserve TypeScript safety

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify blocksInput contains asset_id and no public_url, matching the
assertion pattern used by the public_url replace_block test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The generated ImageBlockInput requires public_url (non-nullable) and
doesn't include asset_id yet. Use separate branches with a type
assertion for the asset_id path to satisfy TypeScript while keeping
the public_url path fully typed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run codegen to pick up asset_id and optional public_url from the
schema. ImageBlockInput now has asset_id natively, so the
`as unknown as CreateBlockInput` cast is no longer needed.

Also revert accidental rename of getDocByObjectId query.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@RoniLandau RoniLandau merged commit c6d9ca0 into master Mar 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants