Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds multi-item turn inputs and image-attachment end-to-end support: client UI for attaching/previewing images, types and store updates, persistence/hydration changes, provider contract and validation for attachments, server-side multi-item turn construction, and new tests covering image scenarios. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ChatView as ChatView (Client)
participant Store as State Store
participant Codex as CodexAppServerManager (Server)
participant Backend as Backend Service
User->>ChatView: Attach image(s) and/or enter text
ChatView->>ChatView: Validate attachments, read files as data URLs, create previews
User->>ChatView: Click Send
ChatView->>Store: PUSH_USER_MESSAGE (text + attachments metadata)
Store-->>ChatView: State updated
ChatView->>Codex: sendTurn(threadId, { input?, attachments? })
Codex->>Codex: Build turnInput array (text + image items), validate non-empty
Codex->>Backend: POST turn/start (includes text + image data URLs)
Backend-->>Codex: Return threadId/turnId
Codex->>Store: Update session (running, activeTurnId)
Codex-->>ChatView: Resolve send result
ChatView->>ChatView: Revoke previews and clear attachments
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
Add image attachments support to
|
dac20ef to
3cf58f8
Compare
Greptile OverviewGreptile SummaryAdded image paste/drag-and-drop support and expanded image preview dialog to the chat interface. Images can now be attached via paste or drag-and-drop without clicking a button, and clicking on images opens them in a full-screen dialog with proper close button hit area. Key Changes:
Issue Found:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant ChatView
participant Composer
participant Store
participant API
participant Server
participant Codex
User->>Composer: Paste/Drag image
Composer->>Composer: Create blob URL
Composer->>ChatView: addComposerImages()
ChatView->>ChatView: Validate size/type/count
ChatView->>Composer: Display preview thumbnails
User->>Composer: Click send
Composer->>ChatView: onSend()
ChatView->>Store: PUSH_USER_MESSAGE (with images)
ChatView->>ChatView: readFileAsDataUrl()
ChatView->>API: providers.sendTurn()
API->>Server: WebSocket message
Server->>Codex: turn/start (text + image dataUrls)
Codex-->>Server: turn response
Server-->>API: WebSocket push events
API-->>ChatView: Provider events
ChatView->>ChatView: Clear composer
User->>ChatView: Click image thumbnail
ChatView->>ChatView: openImageDialog()
ChatView->>User: Show expanded dialog
User->>ChatView: Click backdrop/Escape
ChatView->>ChatView: setExpandedImage(null)
Last reviewed commit: 3cf58f8 |
| text: trimmed, | ||
| ...(messageImages.length > 0 ? { images: messageImages } : {}), |
There was a problem hiding this comment.
Memory leak: blob URLs not revoked when clearing composer images after send
| text: trimmed, | |
| ...(messageImages.length > 0 ? { images: messageImages } : {}), | |
| setPrompt(""); | |
| revokePreviewUrls(composerImages); | |
| setComposerImages([]); |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/server/src/codexAppServerManager.ts`:
- Around line 260-292: The code builds turnInput in sendTurn without validating
image count or size; add server-side validation before constructing turnInput
using a Zod schema from packages/contracts (or create a local schema that
imports shared types) to enforce maximum image count (e.g. MAX_IMAGES) and
per-image size (e.g. MAX_IMAGE_SIZE_BYTES), and reject the request (throw an
Error or return a 4xx) when limits are exceeded; validate input.images and each
image.dataUrl length/size, then only map validated images into turnInput (refer
to sendTurn, turnInput, turnStartParams and input.images to locate the logic) so
oversized/excessive payloads are blocked before calling codex.
🧹 Nitpick comments (1)
packages/contracts/src/provider.ts (1)
62-82: Validate dataUrl media type against mimeType (case‑insensitive).
Keeps metadata consistent and avoids mismatched content handling.♻️ Suggested tweak
.superRefine((value, ctx) => { - if (!value.dataUrl.startsWith("data:image/")) { + const dataUrlLower = value.dataUrl.toLowerCase(); + if (!dataUrlLower.startsWith("data:image/")) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Image dataUrl must start with data:image/", path: ["dataUrl"], }); } + const match = value.dataUrl.match(/^data:([^;]+);/i); + const dataMime = match?.[1]?.toLowerCase(); + if (dataMime && dataMime !== value.mimeType.toLowerCase()) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "mimeType must match dataUrl media type", + path: ["mimeType"], + }); + } });
| const turnInput: Array< | ||
| | { type: "text"; text: string; text_elements: [] } | ||
| | { type: "image"; url: string } | ||
| > = []; | ||
| if (input.input) { | ||
| turnInput.push({ | ||
| type: "text", | ||
| text: input.input, | ||
| text_elements: [], | ||
| }); | ||
| } | ||
| for (const image of input.images ?? []) { | ||
| turnInput.push({ | ||
| type: "image", | ||
| url: image.dataUrl, | ||
| }); | ||
| } | ||
| if (turnInput.length === 0) { | ||
| throw new Error("Turn input must include text or image attachments."); | ||
| } | ||
|
|
||
| const turnStartParams: { | ||
| threadId: string; | ||
| input: Array<{ type: "text"; text: string; text_elements: [] }>; | ||
| input: Array< | ||
| | { type: "text"; text: string; text_elements: [] } | ||
| | { type: "image"; url: string } | ||
| >; | ||
| model?: string; | ||
| effort?: string; | ||
| } = { | ||
| threadId: context.session.threadId, | ||
| input: [ | ||
| { | ||
| type: "text", | ||
| text: input.input, | ||
| text_elements: [], | ||
| }, | ||
| ], | ||
| input: turnInput, | ||
| }; |
There was a problem hiding this comment.
Add server-side validation for image limits before calling codex.
sendTurn now forwards image data URLs but does not enforce max image count/size. A single oversized or excessive payload can destabilize the app-server and breaks “predictable behavior under load.” Validate against shared contract limits (or a Zod schema from packages/contracts) before building turnInput.
🔧 Suggested guardrails
- const turnInput: Array<
+ const turnInput: Array<
| { type: "text"; text: string; text_elements: [] }
| { type: "image"; url: string }
> = [];
+ const images = input.images ?? [];
+ if (images.length > PROVIDER_SEND_TURN_MAX_IMAGES) {
+ throw new Error(`Too many images (max ${PROVIDER_SEND_TURN_MAX_IMAGES}).`);
+ }
if (input.input) {
turnInput.push({
type: "text",
text: input.input,
text_elements: [],
});
}
- for (const image of input.images ?? []) {
+ for (const image of images) {
+ if (image.sizeBytes > PROVIDER_SEND_TURN_MAX_IMAGE_BYTES) {
+ throw new Error(`Image "${image.name}" exceeds size limit.`);
+ }
turnInput.push({
type: "image",
url: image.dataUrl,
});
}As per coding guidelines: “apps/server/src//*.{ts,js}: Maintain predictable behavior under load and during failures (session restarts, reconnects, partial streams)” and “apps//*.{ts,tsx}: Use Zod schemas from packages/contracts for shared type contracts covering provider events, WebSocket protocol, and model/session types.”
🤖 Prompt for AI Agents
In `@apps/server/src/codexAppServerManager.ts` around lines 260 - 292, The code
builds turnInput in sendTurn without validating image count or size; add
server-side validation before constructing turnInput using a Zod schema from
packages/contracts (or create a local schema that imports shared types) to
enforce maximum image count (e.g. MAX_IMAGES) and per-image size (e.g.
MAX_IMAGE_SIZE_BYTES), and reject the request (throw an Error or return a 4xx)
when limits are exceeded; validate input.images and each image.dataUrl
length/size, then only map validated images into turnInput (refer to sendTurn,
turnInput, turnStartParams and input.images to locate the logic) so
oversized/excessive payloads are blocked before calling codex.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/components/ChatView.tsx (1)
696-807:⚠️ Potential issue | 🟠 MajorPreserve attachments for retry on send failure.
Line 767/768 clears the composer beforereadFileAsDataUrlandsendTurn. If either fails, users must reattach images to retry. Consider restoring the draft on error (or clearing only after success).💡 Suggested fix (restore draft on error)
- const trimmed = prompt.trim(); + const trimmed = prompt.trim(); + const promptSnapshot = prompt; if (!trimmed && composerImages.length === 0) return; @@ setPrompt(""); setComposerImages([]); @@ } catch (err) { + setPrompt(promptSnapshot); + setComposerImages(composerImagesSnapshot); setThreadError( activeThread.id, err instanceof Error ? err.message : "Failed to send message.", ); } finally {
🧹 Nitpick comments (2)
packages/contracts/src/provider.ts (1)
72-94: Consider potential mimeType/dataUrl validation mismatch.The schema validates
mimeTypewith a regex (/^image\//i) and separately validatesdataUrlstarts with"data:image/". However, there's no cross-validation ensuring the mimeType in the schema matches the actual MIME type embedded in the dataUrl. For example, a payload withmimeType: "image/png"butdataUrl: "data:image/jpeg;base64,..."would pass validation.If strict consistency is desired, consider adding a refinement that extracts and compares the MIME type from the dataUrl prefix against the declared mimeType field.
packages/contracts/src/provider.test.ts (1)
91-104: Consider adding a boundary test for exactly max attachments.The test correctly rejects
PROVIDER_SEND_TURN_MAX_ATTACHMENTS + 1attachments. Consider also adding a test that verifies exactlyPROVIDER_SEND_TURN_MAX_ATTACHMENTSattachments are accepted, to fully cover the boundary condition.📝 Suggested boundary test
it("accepts exactly the max attachment count", () => { const parsed = providerSendTurnInputSchema.parse({ sessionId: "sess_1", attachments: Array.from({ length: PROVIDER_SEND_TURN_MAX_ATTACHMENTS }, (_, index) => ({ type: "image" as const, name: `image-${index}.png`, mimeType: "image/png", sizeBytes: 1_024, dataUrl: "data:image/png;base64,AAAA", })), }); expect(parsed.attachments).toHaveLength(PROVIDER_SEND_TURN_MAX_ATTACHMENTS); });
- Update `ChatView` so Electron uses a fixed 52px drag titlebar, including empty-thread state. - Make `DiffPanel` header use the same Electron drag-region sizing. - Consolidate Sidebar drag-region into branding row and remove separate spacer div. - Co-authored-by: codex <codex@users.noreply.github.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/historyBootstrap.ts (1)
93-109:⚠️ Potential issue | 🔴 CriticalUse
slice().reverse()instead oftoReversed()for broader browser support.
Array.prototype.toReversed()is not supported in Firefox 114, which is within your build target range (Vite 8'sbaseline-widely-availabledefaults to Chrome 111, Edge 111, Firefox 114, and Safari 16.4). This will cause runtime errors for Firefox users. Useslice().reverse()as a compatible fallback on lines 97 and 109.Suggested fix
- const nextChronological = nextNewestFirst.toReversed(); + const nextChronological = nextNewestFirst.slice().reverse(); ... - let includedChronological = includedNewestFirst.toReversed(); + let includedChronological = includedNewestFirst.slice().reverse();
| }); | ||
| const previousMessages = activeThread.messages; | ||
| setPrompt(""); | ||
| setComposerImages([]); |
There was a problem hiding this comment.
🟢 Low
components/ChatView.tsx:770 Blob URLs created via URL.createObjectURL in addComposerImages are copied to messageAttachments when sending, but setComposerImages([]) clears the state without revoking them. Consider calling revokePreviewUrls(composerImagesSnapshot) before clearing the state.
| setComposerImages([]); | |
| revokePreviewUrls(composerImagesSnapshot); | |
| setComposerImages([]); |
🚀 Want me to fix this? Reply ex: "fix it for me".
🤖 Prompt for AI
In file apps/web/src/components/ChatView.tsx around line 770:
Blob URLs created via `URL.createObjectURL` in `addComposerImages` are copied to `messageAttachments` when sending, but `setComposerImages([])` clears the state without revoking them. Consider calling `revokePreviewUrls(composerImagesSnapshot)` before clearing the state.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@apps/web/src/components/ChatView.tsx`:
- Around line 752-800: The persisted messageAttachments are using
image.previewUrl (a blob URL) which breaks hydration and safe revocation; change
the flow so composerImagesSnapshot is converted to a dataUrl (via
readFileAsDataUrl) once and use that dataUrl for both the UI preview and the
Provider send payload (turnAttachments), update the creation of
messageAttachments to set previewUrl to the dataUrl instead of the blob URL, and
ensure any blob URLs are revoked immediately after creating the dataUrl; locate
symbols composerImagesSnapshot, messageAttachments, turnAttachments,
readFileAsDataUrl, and previewUrl to make these changes and clear blob URLs
after conversion.
In `@packages/contracts/src/provider.ts`:
- Around line 62-73: The providerSendTurnImageAttachmentSchema currently only
length-validates dataUrl allowing invalid or mismatched MIME headers; update
providerSendTurnImageAttachmentSchema to add a z.superRefine that (1) verifies
dataUrl matches the base64 data URL pattern
/^data:([^;]+);base64,([A-Za-z0-9+/=]+)$/ and extracts the MIME type and
payload, (2) checks the extracted MIME type equals the mimeType field, and (3)
fails with clear errors via ctx.addIssue when the format is invalid or MIME
types mismatch; reference providerSendTurnImageAttachmentSchema and its mimeType
and dataUrl fields when making the change.
| setThreadError(activeThread.id, null); | ||
| const messageAttachments: ChatImageAttachment[] = composerImagesSnapshot.map((image) => ({ | ||
| type: "image", | ||
| id: image.id, | ||
| name: image.name, | ||
| mimeType: image.mimeType, | ||
| sizeBytes: image.sizeBytes, | ||
| previewUrl: image.previewUrl, | ||
| })); | ||
| dispatch({ | ||
| type: "PUSH_USER_MESSAGE", | ||
| threadId: activeThread.id, | ||
| id: crypto.randomUUID(), | ||
| text: trimmed, | ||
| ...(messageAttachments.length > 0 ? { attachments: messageAttachments } : {}), | ||
| }); | ||
| const previousMessages = activeThread.messages; | ||
| setPrompt(""); | ||
| setComposerImages([]); | ||
|
|
||
| const sessionInfo = await ensureSession(sessionCwd); | ||
| if (!sessionInfo) return; | ||
|
|
||
| setIsSending(true); | ||
| try { | ||
| const turnAttachments = await Promise.all( | ||
| composerImagesSnapshot.map(async (image): Promise<ProviderSendTurnAttachmentInput> => ({ | ||
| type: "image", | ||
| name: image.name, | ||
| mimeType: image.mimeType, | ||
| sizeBytes: image.sizeBytes, | ||
| dataUrl: await readFileAsDataUrl(image.file), | ||
| })), | ||
| ); | ||
| const shouldBootstrap = | ||
| previousMessages.length > 0 && | ||
| (sessionInfo.continuityState === "new" || sessionInfo.continuityState === "fallback_new"); | ||
| const latestPromptForBootstrap = trimmed || IMAGE_ONLY_BOOTSTRAP_PROMPT; | ||
| const input = shouldBootstrap | ||
| ? buildBootstrapInput(previousMessages, trimmed, PROVIDER_SEND_TURN_MAX_INPUT_CHARS).text | ||
| : trimmed; | ||
| ? buildBootstrapInput( | ||
| previousMessages, | ||
| latestPromptForBootstrap, | ||
| PROVIDER_SEND_TURN_MAX_INPUT_CHARS, | ||
| ).text | ||
| : (trimmed || undefined); | ||
| await api.providers.sendTurn({ | ||
| sessionId: sessionInfo.sessionId, | ||
| input, | ||
| ...(input ? { input } : {}), | ||
| ...(turnAttachments.length > 0 ? { attachments: turnAttachments } : {}), |
There was a problem hiding this comment.
Persisting blob previewUrl will break hydration and prevents safe revocation.
Blob URLs are session-scoped; if messages are persisted/hydrated, previews will be invalid after reload. It also blocks revoking without losing previews, which can leak memory across many sends. Consider using the already-read dataUrl for message previews and revoking blob URLs right after.
💡 Suggested approach (reuse dataUrl for UI + send)
- const messageAttachments: ChatImageAttachment[] = composerImagesSnapshot.map((image) => ({
- type: "image",
- id: image.id,
- name: image.name,
- mimeType: image.mimeType,
- sizeBytes: image.sizeBytes,
- previewUrl: image.previewUrl,
- }));
+ const turnAttachments = await Promise.all(
+ composerImagesSnapshot.map(async (image): Promise<ProviderSendTurnAttachmentInput> => ({
+ type: "image",
+ name: image.name,
+ mimeType: image.mimeType,
+ sizeBytes: image.sizeBytes,
+ dataUrl: await readFileAsDataUrl(image.file),
+ })),
+ );
+ const messageAttachments: ChatImageAttachment[] = composerImagesSnapshot.map((image, index) => ({
+ type: "image",
+ id: image.id,
+ name: image.name,
+ mimeType: image.mimeType,
+ sizeBytes: image.sizeBytes,
+ previewUrl: turnAttachments[index]?.dataUrl,
+ }));
+ revokePreviewUrls(composerImagesSnapshot);🤖 Prompt for AI Agents
In `@apps/web/src/components/ChatView.tsx` around lines 752 - 800, The persisted
messageAttachments are using image.previewUrl (a blob URL) which breaks
hydration and safe revocation; change the flow so composerImagesSnapshot is
converted to a dataUrl (via readFileAsDataUrl) once and use that dataUrl for
both the UI preview and the Provider send payload (turnAttachments), update the
creation of messageAttachments to set previewUrl to the dataUrl instead of the
blob URL, and ensure any blob URLs are revoked immediately after creating the
dataUrl; locate symbols composerImagesSnapshot, messageAttachments,
turnAttachments, readFileAsDataUrl, and previewUrl to make these changes and
clear blob URLs after conversion.
| export const providerSendTurnImageAttachmentSchema = z.object({ | ||
| type: z.literal("image"), | ||
| name: z.string().trim().min(1).max(255), | ||
| mimeType: z | ||
| .string() | ||
| .trim() | ||
| .min(1) | ||
| .max(100) | ||
| .regex(/^image\//i, "mimeType must be an image/* MIME type"), | ||
| sizeBytes: z.number().int().min(1).max(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES), | ||
| dataUrl: z.string().trim().min(1).max(PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS), | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let me check the file structure and find the provider.ts file
find . -type f -name "provider.ts" -path "*/packages/contracts/*" 2>/dev/null | head -5Repository: pingdotgg/codething-mvp
Length of output: 103
🏁 Script executed:
# Read the provider.ts file to see the current state
wc -l packages/contracts/src/provider.tsRepository: pingdotgg/codething-mvp
Length of output: 105
🏁 Script executed:
# Get the full context around lines 62-73 and 90-98
head -n 110 packages/contracts/src/provider.ts | tail -n +50Repository: pingdotgg/codething-mvp
Length of output: 2168
🏁 Script executed:
# Also check what constants are defined for the max values
grep -n "PROVIDER_SEND_TURN_MAX_IMAGE" packages/contracts/src/provider.tsRepository: pingdotgg/codething-mvp
Length of output: 381
🏁 Script executed:
# Search for how dataUrl is used downstream to understand importance of validation
rg "dataUrl" --type ts packages/contracts/src/ -A 2 -B 2Repository: pingdotgg/codething-mvp
Length of output: 1487
🏁 Script executed:
# Check how the schema is used - look for imports or usage of providerSendTurnImageAttachmentSchema
rg "providerSendTurnImageAttachmentSchema|providerSendTurnInputSchema" --type ts -B 2 -A 2Repository: pingdotgg/codething-mvp
Length of output: 5133
🏁 Script executed:
# Check provider.test.ts more thoroughly to see all test cases
cat -n packages/contracts/src/provider.test.ts | head -100Repository: pingdotgg/codething-mvp
Length of output: 3675
🏁 Script executed:
# Look for any code that processes/parses dataUrl to see if validation is critical
rg "dataUrl" --type ts packages/ -B 3 -A 3 | head -60Repository: pingdotgg/codething-mvp
Length of output: 2034
🏁 Script executed:
# Check if there are any validation utilities for data URLs elsewhere
fd -e ts -e js | xargs rg "data:" -l --type ts 2>/dev/null | head -10Repository: pingdotgg/codething-mvp
Length of output: 391
🏁 Script executed:
# Check lines 75-98 to understand "Also applies to" reference
sed -n '75,98p' packages/contracts/src/provider.tsRepository: pingdotgg/codething-mvp
Length of output: 888
Validate data URL format and MIME type consistency in attachment schema.
dataUrl is only length-validated, allowing non-data URLs or mismatched MIME headers to pass while downstream trusts mimeType. Add a superRefine check to ensure dataUrl is a base64 data URL with the correct MIME type prefix.
🔧 Suggested fix
-export const providerSendTurnImageAttachmentSchema = z.object({
+export const providerSendTurnImageAttachmentSchema = z
+ .object({
type: z.literal("image"),
name: z.string().trim().min(1).max(255),
mimeType: z
.string()
.trim()
.min(1)
.max(100)
.regex(/^image\//i, "mimeType must be an image/* MIME type"),
sizeBytes: z.number().int().min(1).max(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES),
dataUrl: z.string().trim().min(1).max(PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS),
-});
+ })
+ .superRefine((value, ctx) => {
+ const header = value.dataUrl.split(",", 1)[0]?.toLowerCase() ?? "";
+ const expected = `data:${value.mimeType.toLowerCase()}`;
+ if (!header.startsWith(expected) || !header.includes(";base64")) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "dataUrl must be a base64 data URL matching mimeType",
+ path: ["dataUrl"],
+ });
+ }
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const providerSendTurnImageAttachmentSchema = z.object({ | |
| type: z.literal("image"), | |
| name: z.string().trim().min(1).max(255), | |
| mimeType: z | |
| .string() | |
| .trim() | |
| .min(1) | |
| .max(100) | |
| .regex(/^image\//i, "mimeType must be an image/* MIME type"), | |
| sizeBytes: z.number().int().min(1).max(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES), | |
| dataUrl: z.string().trim().min(1).max(PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS), | |
| }); | |
| export const providerSendTurnImageAttachmentSchema = z | |
| .object({ | |
| type: z.literal("image"), | |
| name: z.string().trim().min(1).max(255), | |
| mimeType: z | |
| .string() | |
| .trim() | |
| .min(1) | |
| .max(100) | |
| .regex(/^image\//i, "mimeType must be an image/* MIME type"), | |
| sizeBytes: z.number().int().min(1).max(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES), | |
| dataUrl: z.string().trim().min(1).max(PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS), | |
| }) | |
| .superRefine((value, ctx) => { | |
| const header = value.dataUrl.split(",", 1)[0]?.toLowerCase() ?? ""; | |
| const expected = `data:${value.mimeType.toLowerCase()}`; | |
| if (!header.startsWith(expected) || !header.includes(";base64")) { | |
| ctx.addIssue({ | |
| code: z.ZodIssueCode.custom, | |
| message: "dataUrl must be a base64 data URL matching mimeType", | |
| path: ["dataUrl"], | |
| }); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In `@packages/contracts/src/provider.ts` around lines 62 - 73, The
providerSendTurnImageAttachmentSchema currently only length-validates dataUrl
allowing invalid or mismatched MIME headers; update
providerSendTurnImageAttachmentSchema to add a z.superRefine that (1) verifies
dataUrl matches the base64 data URL pattern
/^data:([^;]+);base64,([A-Za-z0-9+/=]+)$/ and extracts the MIME type and
payload, (2) checks the extracted MIME type equals the mimeType field, and (3)
fails with clear errors via ctx.addIssue when the format is invalid or MIME
types mismatch; reference providerSendTurnImageAttachmentSchema and its mimeType
and dataUrl fields when making the change.
Summary
Testing
Summary by CodeRabbit
New Features
Bug Fixes / UX
Tests