Skip to content

feat: support unified prompt file attachments#107

Merged
Astro-Han merged 6 commits into
devfrom
codex/feat-unified-file-attach
Apr 22, 2026
Merged

feat: support unified prompt file attachments#107
Astro-Han merged 6 commits into
devfrom
codex/feat-unified-file-attach

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Apr 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Route prompt file attachments by model input support, preserving original paths for Office, unknown, and unsupported PDF files.
  • Use the desktop native picker with broad filters and add a minimal data URL bridge for supported image and PDF direct attachments.
  • Guard backend prompt resolution so unsupported media returned by Read stays as a path reference.

Why

Closes #100. PawWork users need to attach files from outside the worktree without hidden copies, while keeping media direct only when the selected model can read it.

Related Issue

Closes #100

How To Verify

cd packages/app
bun test ./src/components/prompt-input/attachment-routing.test.ts ./src/components/prompt-input/pick-attachments.test.ts ./src/components/prompt-input/attachments.test.ts ./src/components/prompt-input/build-request-parts.test.ts

cd packages/opencode
bun test test/session/prompt.test.ts test/session/prompt-effect.test.ts

bun turbo typecheck
bun turbo test:ci

Fresh-eyes review found no P0/P1/P2/P3 findings.

Screenshots or Recordings

Not captured. This change is covered by unit tests and backend prompt tests. Manual desktop picker smoke was not run in this headless session.

Checklist

  • I ran the relevant verification steps
  • I tested visible changes manually when needed
  • I am targeting the dev branch

Summary by CodeRabbit

  • New Features

    • Model-aware attachment routing, native file-picker flow, and a desktop API to read local files as data URLs.
  • Improvements

    • Per-file routing for mixed uploads, stricter data-URL validation, clearer toasts for unsupported images and path-required flows, preservation of Office/absolute paths, WSL path handling, and per-sender approved attachment reads.
  • Tests

    • Expanded coverage for routing, picker fallback, ingestion flows, path/URL handling, and request-part generation.

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 90857c0e-1a68-42e7-8a5e-a0f98043a2d9

📥 Commits

Reviewing files that changed from the base of the PR and between 2325e22 and 745411b.

📒 Files selected for processing (4)
  • packages/desktop-electron/src/main/server.test.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/prompt.test.ts

📝 Walkthrough

Walkthrough

Adds model-aware attachment routing and a native picker orchestration, surfaces approved local-file data-URL reads via platform/IPC, reworks ingestion to route/embed/reject based on model capabilities, preserves workspace path parts for office/text files, and adds comprehensive tests plus i18n keys.

Changes

Cohort / File(s) Summary
Attachment routing & tests
packages/app/src/components/prompt-input/attachment-routing.ts, packages/app/src/components/prompt-input/attachment-routing.test.ts
New routing primitives/types to classify browser Files and picked filesystem paths against model input capabilities; includes unit tests for routing logic and suffix/MIME heuristics.
Pick attachments helper & tests
packages/app/src/components/prompt-input/pick-attachments.ts, packages/app/src/components/prompt-input/pick-attachments.test.ts
New pickAttachments flow favoring native picker, normalizes results, validates, calls addPickedPaths, falls back to hidden input click on absence/errors; tests cover success, cancel, fallback, and error flows.
Prompt input ingestion & tests
packages/app/src/components/prompt-input.tsx, packages/app/src/components/prompt-input/attachments.ts, packages/app/src/components/prompt-input/attachments.test.ts
Wire routing into ingestion (replace MIME sniffing): handle direct/reject-image/path outcomes, add addPickedPath(s), require model/openModelSelector, optional readFileDataUrl, strict data-URL validation, and route-aware toasts; tests updated/expanded.
Platform, IPC, preload & renderer
packages/app/src/context/platform.tsx, packages/desktop-electron/src/main/ipc.ts, packages/desktop-electron/src/preload/index.ts, packages/desktop-electron/src/preload/types.ts, packages/desktop-electron/src/renderer/index.tsx
Add optional Platform.readFileDataUrl; IPC adds per-sender approved-path tracking and read-file-data-url handler validating approval, MIME, size and returning base64 data URL; preload/renderer expose and use API with WSL-aware path resolution.
Request parts & path handling
packages/app/src/components/prompt-input/build-request-parts.ts, packages/app/src/components/prompt-input/build-request-parts.test.ts
Introduce fileURL(path, selection?) helper for consistent file:// URL construction (UNC handling, encoding) and tests validating macOS absolute and Windows UNC paths.
Files helper & utilities
packages/app/src/components/prompt-input/files.ts, packages/util/src/file-extensions.ts
Export textMime; add centralized extension helpers and constants (OFFICE_EXTS, IMAGE_EXTS, TEXT_EXTS, pathBasename, pathSuffix) used by routing and MIME fallback.
Session prompt handling & tests
packages/opencode/src/session/prompt.ts, packages/opencode/test/session/prompt-effect.test.ts, packages/opencode/test/session/prompt.test.ts
Model-capability gating for Read-tool outputs and media-kind mapping; special-case office-like local file paths to skip Read and preserve original file parts; update tests to assert preserved/filtered parts.
Server / desktop tests & package
packages/desktop-electron/src/main/server.test.ts, packages/desktop-electron/package.json
Test mocks extended/restored; add dependency @opencode-ai/util to desktop package.json.
Files ingestion tests
packages/app/src/components/prompt-input/attachments.test.ts, packages/app/src/components/prompt-input/pick-attachments.test.ts
Expanded test scaffolding covering routing classification, data-URL validation, toast emission, prompt-part insertion, picker behavior, and FileReader mocks.
i18n
packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
Add English and Chinese toast strings for image-unsupported and path-required scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant UI as "PromptInput UI"
    participant PickerFlow as "pickAttachments"
    participant Picker as "Platform.openFilePickerDialog"
    participant Fallback as "HiddenInputFallback"
    participant Router as "attachment-routing"
    participant PlatformRead as "Platform.readFileDataUrl"
    participant ModelSel as "openModelSelector"

    UI->>PickerFlow: start pick (fallbackInputClick, addPickedPaths)
    PickerFlow->>Picker: openFilePickerDialog({multiple:true,extensions:[]})
    alt picker throws or not provided
        Picker-->>PickerFlow: error / undefined
        PickerFlow->>Fallback: fallbackInputClick()
        PickerFlow-->>UI: return false
    else picker returns paths
        Picker-->>PickerFlow: [paths]
        loop for each path
            PickerFlow->>Router: routePickedPath(path, model)
            alt route == direct
                Router-->>PickerFlow: direct (mime)
                PickerFlow->>PlatformRead: readFileDataUrl(path,mime)
                alt dataUrl returned
                    PlatformRead-->>PickerFlow: dataUrl
                    PickerFlow-->>UI: add direct attachment
                else null
                    PlatformRead-->>PickerFlow: null
                    PickerFlow-->>UI: insert file path part
                end
            else route == reject-image
                Router-->>PickerFlow: reject-image
                PickerFlow->>ModelSel: openModelSelector() (toast action)
                PickerFlow-->>UI: show image-unsupported toast
            else route == path
                Router-->>PickerFlow: path (office/text/unknown/unsupported-pdf)
                PickerFlow-->>UI: insert file path part (e.g., "@path")
            end
        end
        PickerFlow-->>UI: return addPickedPaths result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

ui

Poem

🐰 I hopped to the picker with curious paws,

Paths and pixels, obeying new laws,
Router asked models what they could read,
Toasts fluttered warnings, data-URLs fed the need,
I nibbled bytes and left the prompt with applause.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: support unified prompt file attachments' accurately reflects the main objective of routing file attachments based on model capabilities.
Description check ✅ Passed The PR description comprehensively addresses the required template sections with clear summary, rationale, related issue link, verification steps, and checklist confirmation.
Linked Issues check ✅ Passed The PR implementation fully satisfies issue #100 objectives: routes files by model capability (vision-capable models embed directly, non-multimodal models reject images with actionable feedback, all other files save to workspace with @ insertion).
Out of Scope Changes check ✅ Passed All code changes are directly aligned with the unified file attachment routing objectives; no extraneous refactoring or unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/feat-unified-file-attach

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive attachment routing system that handles files based on model capabilities, including support for images, PDFs, and Office documents. It introduces a native file picker for the desktop application and adds IPC handlers to read local files as data URLs. The backend prompt logic is also updated to filter tool-produced attachments and handle path-only fallbacks for unsupported formats. Feedback highlights opportunities to improve error reporting for bulk file uploads, expand media support to include audio and video, and prevent information loss when filtering tool outputs.

Comment thread packages/app/src/components/prompt-input/attachments.ts Outdated
Comment thread packages/opencode/src/session/prompt.ts Outdated
Comment thread packages/opencode/src/session/prompt.ts
@Astro-Han Astro-Han force-pushed the codex/feat-unified-file-attach branch 2 times, most recently from c68b60f to 9f3c761 Compare April 21, 2026 16:05

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 910e233c3b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/app/src/components/prompt-input/pick-attachments.ts Outdated
@Astro-Han Astro-Han force-pushed the codex/feat-unified-file-attach branch from 9f3c761 to e955612 Compare April 21, 2026 16:28
@Astro-Han

Copy link
Copy Markdown
Owner Author

Addressed the review comments in the latest force-push:\n\n- Batch attachment drops now still show feedback when some files are skipped after other files attach successfully.\n- Native picker failures now fall back to the browser file input instead of surfacing an unhandled rejection.\n- Backend media filtering now covers audio and video modalities consistently with provider capabilities.\n- Mixed supported and unsupported Read attachments now preserve the original path part when any attachment is filtered out.\n\nVerification rerun:\n- Focused prompt attachment tests\n- Focused session prompt tests\n- bun turbo typecheck\n- bun turbo test:ci

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicks

Overall implementation is solid. A few minor observations for consistency and edge cases:

Comment thread packages/app/src/components/prompt-input/attachment-routing.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachment-routing.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachments.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachments.ts
Comment thread packages/opencode/src/session/prompt.ts Outdated
@Astro-Han Astro-Han force-pushed the codex/feat-unified-file-attach branch from e955612 to 9e82f88 Compare April 21, 2026 17:50
@Astro-Han

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/components/prompt-input/attachment-routing.ts`:
- Around line 87-89: routeBrowserFile currently only checks OFFICE_EXTS and
falls back to reason "unknown", causing inconsistent routing versus
routePickedPath which checks TEXT_EXTS; update routeBrowserFile to compute the
file extension (using ext(file.name) as already done), then if
TEXT_EXTS.has(suffix) return { type: "path", reason: "text" } before the office
check or alongside it so .html/.txt/etc. are routed as text consistently with
routePickedPath; keep the existing OFFICE_EXTS check and fallback for unknowns.

In `@packages/app/src/components/prompt-input/attachments.ts`:
- Around line 107-116: The add function redundantly calls attachmentMime(file)
for route.type === "direct" even though routeBrowserFile already returns
route.mime; replace the async attachmentMime call with route.mime: after
obtaining route from routeBrowserFile (via routeBrowserFile and inspecting
route.type), use const mime = route.mime (or route.mime directly), check if mime
is falsy and call warn() and return false as before, then call await
dataUrl(file, mime) and pass file.name, mime into addDirect; remove the
attachmentMime(file) invocation and keep calls to routeBrowserFile, addDirect,
dataUrl, and warn unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 13ce2d01-93df-4dd7-a59c-74ecb91eae7d

📥 Commits

Reviewing files that changed from the base of the PR and between 63a8a52 and 9e82f88.

📒 Files selected for processing (18)
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/attachment-routing.test.ts
  • packages/app/src/components/prompt-input/attachment-routing.ts
  • packages/app/src/components/prompt-input/attachments.test.ts
  • packages/app/src/components/prompt-input/attachments.ts
  • packages/app/src/components/prompt-input/build-request-parts.test.ts
  • packages/app/src/components/prompt-input/pick-attachments.test.ts
  • packages/app/src/components/prompt-input/pick-attachments.ts
  • packages/app/src/context/platform.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/renderer/index.tsx
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/prompt.test.ts

Comment thread packages/app/src/components/prompt-input/attachment-routing.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachments.ts Outdated
@Astro-Han Astro-Han force-pushed the codex/feat-unified-file-attach branch from 9e82f88 to 1a9dcf6 Compare April 22, 2026 01:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/src/session/prompt.ts (1)

1094-1175: ⚠️ Potential issue | 🟠 Major

Capability gating is still bypassed for file: parts with real MIME types.

This new downgrade logic only runs when the incoming local part is labeled text/plain. A caller that sends file://.../report.docx with its actual Office MIME, or file://.../foo.pdf with application/pdf, still falls through to the raw fsys.readFile(...)->data: branch below and gets attached directly to the model. That undermines the new routing contract and can reintroduce unsupported-media failures. Apply the same path-preservation/capability check before any local file: part is converted to a data URL, not just after Read or for text/plain inputs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/session/prompt.ts` around lines 1094 - 1175, The code
only applies the path-preservation/capability gating for local parts when
part.mime === "text/plain"; update the logic to detect any local file part
(e.g., part.url starting with "file:" or using officePathOnly(filepath)) before
the code path that converts local files to data URLs and apply the same
capability checks (use mediaInputKind(attachment.mime) and
modelCanReadMedia(model, kind)) and path-preservation behavior: if the
destination model cannot read the media, keep the file-as-path synthetic message
and attach the original part instead of converting to a data: URL. Locate and
modify the branch that currently handles text/plain and the later raw
fsys.readFile(...)->data: conversion so the gating runs once earlier (using
filepath, part.url, officePathOnly, execRead/provider.getModel/Exit handling)
for all local file parts regardless of MIME.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/components/prompt-input/attachment-routing.ts`:
- Around line 3-4: InputKind currently only lists "image" | "pdf" while
ModelInputMap (and the backend's MediaInputKind) also include "audio" and
"video", causing a type mismatch and future confusion; update InputKind to
"image" | "pdf" | "audio" | "video" to match ModelInputMap and backend
MediaInputKind (or if you intentionally want to limit supported types, add a
clear comment above InputKind referencing that it's intentionally limited and
link to backend MediaInputKind), ensuring the symbols InputKind and
ModelInputMap remain consistent across frontend and backend.

In `@packages/app/src/components/prompt-input/attachments.ts`:
- Around line 71-81: warnRouteFailure contains a fallback warn() that is
theoretically unreachable because AttachRoute.direct denotes success and should
never be passed in; make this explicit by replacing the generic fallback with an
exhaustive check that signals a programmer error: convert the if-chain into a
switch or if/else blocks over route.type (handling "reject-image" ->
warnImageUnsupported, "path" -> pathRequired) and for any other case (e.g.,
route.type === "direct" or unexpected values) throw an explicit Error or call an
assertUnreachable helper mentioning warnRouteFailure and route.type so the
unreachable state is obvious at runtime and during typechecking (reference
warnRouteFailure, AttachRoute, warnImageUnsupported, pathRequired, warn, and
consider adding/asserting an assertUnreachable(route) or throw new
Error(`Unexpected AttachRoute in warnRouteFailure: ${route.type}`)).
- Around line 127-142: The addAttachments function redundantly calls
routeBrowserFile after add fails; change add(file, ...) to return the failure
AttachRoute (e.g., Promise<boolean | AttachRoute>) instead of just false so it
captures the route before returning, then update addAttachments to check the
returned value from add and set failure = returned AttachRoute when present (no
second routeBrowserFile call), preserving use of warnRouteFailure(failure) and
input.model() where needed.

In `@packages/desktop-electron/src/main/ipc.ts`:
- Around line 122-129: The "read-file-data-url" IPC handler currently reads any
path directly with fs.readFile, allowing arbitrary and oversized files; update
the handler to first validate that the supplied path was produced by your
approved picker flow (e.g. check a secure allowlist or token via a new helper
like isApprovedAttachment(path) or lookup in the picker-provided Map) and then
call fs.stat(path) to enforce the attachment size limit (compare size to
MAX_ATTACHMENT_BYTES) before reading; only if both checks pass call
fs.readFile(path) and return the base64 data URL, otherwise reject/return null
or throw a clear error.

---

Outside diff comments:
In `@packages/opencode/src/session/prompt.ts`:
- Around line 1094-1175: The code only applies the path-preservation/capability
gating for local parts when part.mime === "text/plain"; update the logic to
detect any local file part (e.g., part.url starting with "file:" or using
officePathOnly(filepath)) before the code path that converts local files to data
URLs and apply the same capability checks (use mediaInputKind(attachment.mime)
and modelCanReadMedia(model, kind)) and path-preservation behavior: if the
destination model cannot read the media, keep the file-as-path synthetic message
and attach the original part instead of converting to a data: URL. Locate and
modify the branch that currently handles text/plain and the later raw
fsys.readFile(...)->data: conversion so the gating runs once earlier (using
filepath, part.url, officePathOnly, execRead/provider.getModel/Exit handling)
for all local file parts regardless of MIME.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d36aaf73-62fe-4da1-aceb-8d90764ce1dc

📥 Commits

Reviewing files that changed from the base of the PR and between 9e82f88 and 1a9dcf6.

📒 Files selected for processing (18)
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/attachment-routing.test.ts
  • packages/app/src/components/prompt-input/attachment-routing.ts
  • packages/app/src/components/prompt-input/attachments.test.ts
  • packages/app/src/components/prompt-input/attachments.ts
  • packages/app/src/components/prompt-input/build-request-parts.test.ts
  • packages/app/src/components/prompt-input/pick-attachments.test.ts
  • packages/app/src/components/prompt-input/pick-attachments.ts
  • packages/app/src/context/platform.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/renderer/index.tsx
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/prompt.test.ts

Comment thread packages/app/src/components/prompt-input/attachment-routing.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachments.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachments.ts
Comment thread packages/desktop-electron/src/main/ipc.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachments.ts
Comment thread packages/app/src/components/prompt-input/attachments.ts
Comment thread packages/app/src/components/prompt-input/attachment-routing.ts Outdated
Comment thread packages/app/src/components/prompt-input/pick-attachments.ts
Comment thread packages/app/src/components/prompt-input/attachments.ts
Comment thread packages/app/src/components/prompt-input/attachment-routing.ts
Comment thread packages/app/src/components/prompt-input/attachment-routing.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachments.ts Outdated
Comment thread packages/opencode/src/session/prompt.ts
Comment thread packages/opencode/src/session/prompt.ts
Comment thread packages/desktop-electron/src/renderer/index.tsx Outdated
@Astro-Han Astro-Han force-pushed the codex/feat-unified-file-attach branch from 1a9dcf6 to ce662ed Compare April 22, 2026 05:44
Comment thread packages/desktop-electron/src/main/ipc.ts
Comment thread packages/opencode/src/session/prompt.ts
Comment thread packages/desktop-electron/src/preload/types.ts
Comment thread packages/app/src/i18n/en.ts
Comment thread packages/desktop-electron/src/main/ipc.ts
Comment thread packages/app/src/components/prompt-input/pick-attachments.ts
Comment thread packages/desktop-electron/src/main/ipc.ts
@Astro-Han Astro-Han force-pushed the codex/feat-unified-file-attach branch 2 times, most recently from 722f3c2 to 7be033f Compare April 22, 2026 11:16
Comment thread packages/app/src/components/prompt-input/attachment-routing.ts Outdated
Comment thread packages/util/src/file-extensions.ts
Comment thread packages/opencode/src/session/prompt.ts Outdated
Comment thread packages/app/src/components/prompt-input/attachments.ts
Comment thread packages/app/src/components/prompt-input/attachments.ts
Comment thread packages/opencode/src/session/prompt.ts
@Astro-Han Astro-Han force-pushed the codex/feat-unified-file-attach branch from 9fc49f6 to 2325e22 Compare April 22, 2026 11:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/opencode/test/session/prompt.test.ts (1)

121-145: ⚠️ Potential issue | 🟠 Major

Use pathToFileURL() for the missing-file case too.

Line 130 and Line 142 still use file://${missing}, which is not a portable file URL on Windows and mis-encodes reserved characters like # and %. The new Office-path test below already uses pathToFileURL(...).href; this case should match it.

♻️ Proposed fix
             const missing = path.join(tmp.path, "does-not-exist.ts")
+            const missingUrl = pathToFileURL(missing).href
             const msg = yield* prompt.prompt({
               sessionID: session.id,
               agent: "build",
               noReply: true,
               parts: [
                 { type: "text", text: "please review `@does-not-exist.ts`" },
                 {
                   type: "file",
                   mime: "text/plain",
-                  url: `file://${missing}`,
+                  url: missingUrl,
                   filename: "does-not-exist.ts",
                 },
               ],
             })
@@
-            expect(msg.parts.some((part) => part.type === "file" && part.url === `file://${missing}`)).toBe(true)
+            expect(msg.parts.some((part) => part.type === "file" && part.url === missingUrl)).toBe(true)
#!/bin/bash
rg -n 'file://\$\{' packages/opencode/test/session/prompt.test.ts packages/opencode/test/session/prompt-effect.test.ts

Expected: no interpolated file:// URLs remain in the session prompt tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/session/prompt.test.ts` around lines 121 - 145,
Replace the literal interpolated file URL strings with a proper file URL via
pathToFileURL(missing).href: in the prompt.prompt call (the parts array) use
pathToFileURL(missing).href instead of `file://${missing}`, and update the
assertion that checks for the file part URL (the msg.parts.some(...) check) to
compare against pathToFileURL(missing).href as well; import pathToFileURL from
'url' if not already present and keep the rest of the logic (prompt.prompt call
and msg.parts checks) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/session/prompt.ts`:
- Around line 1163-1181: The current filter keeps attachments when
mediaInputKind(attachment.mime) is undefined, allowing unknown/office types
(e.g., .docx) to be re-emitted as synthetic attachments; change the predicate in
the attachments creation so it only keeps attachments with a defined kind that
the model can read (i.e., require kind !== undefined && modelCanReadMedia(model,
kind)) before mapping to synthetic pieces; keep the existing fallback that
pushes the original { ...part, messageID: info.id, sessionID: input.sessionID }
when filtered attachments.length < result.attachments.length so unmapped/unknown
Read attachments preserve the original file: part.

---

Duplicate comments:
In `@packages/opencode/test/session/prompt.test.ts`:
- Around line 121-145: Replace the literal interpolated file URL strings with a
proper file URL via pathToFileURL(missing).href: in the prompt.prompt call (the
parts array) use pathToFileURL(missing).href instead of `file://${missing}`, and
update the assertion that checks for the file part URL (the msg.parts.some(...)
check) to compare against pathToFileURL(missing).href as well; import
pathToFileURL from 'url' if not already present and keep the rest of the logic
(prompt.prompt call and msg.parts checks) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 82e88811-0700-4b7b-a41e-2d4f6109020c

📥 Commits

Reviewing files that changed from the base of the PR and between 91cc04d and 2325e22.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • packages/app/src/components/prompt-input.tsx
  • packages/app/src/components/prompt-input/attachment-routing.test.ts
  • packages/app/src/components/prompt-input/attachment-routing.ts
  • packages/app/src/components/prompt-input/attachments.test.ts
  • packages/app/src/components/prompt-input/attachments.ts
  • packages/app/src/components/prompt-input/build-request-parts.test.ts
  • packages/app/src/components/prompt-input/build-request-parts.ts
  • packages/app/src/components/prompt-input/files.ts
  • packages/app/src/components/prompt-input/pick-attachments.test.ts
  • packages/app/src/components/prompt-input/pick-attachments.ts
  • packages/app/src/context/platform.tsx
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/desktop-electron/package.json
  • packages/desktop-electron/src/main/ipc.ts
  • packages/desktop-electron/src/main/server.test.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/renderer/index.tsx
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/session/prompt-effect.test.ts
  • packages/opencode/test/session/prompt.test.ts
  • packages/util/src/file-extensions.ts

Comment thread packages/opencode/src/session/prompt.ts
@Astro-Han Astro-Han force-pushed the codex/feat-unified-file-attach branch from 2325e22 to 745411b Compare April 22, 2026 12:06
@Astro-Han Astro-Han merged commit c2f8fd5 into dev Apr 22, 2026
23 checks passed
@Astro-Han Astro-Han deleted the codex/feat-unified-file-attach branch April 22, 2026 12:13
@coderabbitai coderabbitai Bot mentioned this pull request May 28, 2026
13 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request P2 Medium priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Unified file attach that routes to vision, workspace, or reject based on model capability

1 participant