mirror of
https://github.com/getpaseo/paseo.git
synced 2026-08-15 04:42:45 +00:00
Add image attachments to agent creation
This commit is contained in:
@@ -573,12 +573,6 @@ export default function DraftAgentScreen() {
|
|||||||
...(modeId ? { modeId } : {}),
|
...(modeId ? { modeId } : {}),
|
||||||
...(trimmedModel ? { model: trimmedModel } : {}),
|
...(trimmedModel ? { model: trimmedModel } : {}),
|
||||||
};
|
};
|
||||||
// TODO: Images in initial agent creation are not yet supported by the server API.
|
|
||||||
// For now we log a warning. Images can be sent after agent creation via sendAgentMessage.
|
|
||||||
if (images && images.length > 0) {
|
|
||||||
console.warn("[DraftAgentScreen] Image attachments on agent creation not yet supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
const trimmedBaseBranch = baseBranch.trim();
|
const trimmedBaseBranch = baseBranch.trim();
|
||||||
const shouldIncludeBase =
|
const shouldIncludeBase =
|
||||||
trimmedBaseBranch.length > 0 ||
|
trimmedBaseBranch.length > 0 ||
|
||||||
@@ -608,6 +602,7 @@ export default function DraftAgentScreen() {
|
|||||||
createAgent({
|
createAgent({
|
||||||
config,
|
config,
|
||||||
initialPrompt: trimmedPrompt,
|
initialPrompt: trimmedPrompt,
|
||||||
|
images,
|
||||||
git: gitOptions,
|
git: gitOptions,
|
||||||
requestId,
|
requestId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1284,6 +1284,34 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
ws.send(msg);
|
ws.send(msg);
|
||||||
}, [serverId, ws, setProviderModels]);
|
}, [serverId, ws, setProviderModels]);
|
||||||
|
|
||||||
|
const encodeImages = useCallback(async (
|
||||||
|
images?: Array<{ uri: string; mimeType?: string }>
|
||||||
|
) => {
|
||||||
|
if (!images || images.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const encodedImages = await Promise.all(
|
||||||
|
images.map(async ({ uri, mimeType }) => {
|
||||||
|
try {
|
||||||
|
const data = await FileSystem.readAsStringAsync(uri, {
|
||||||
|
encoding: "base64",
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
mimeType: mimeType ?? "image/jpeg",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Session] Failed to convert image:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const validImages = encodedImages.filter(
|
||||||
|
(entry): entry is { data: string; mimeType: string } => entry !== null
|
||||||
|
);
|
||||||
|
return validImages.length > 0 ? validImages : undefined;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const sendAgentMessage = useCallback(async (
|
const sendAgentMessage = useCallback(async (
|
||||||
agentId: string,
|
agentId: string,
|
||||||
message: string,
|
message: string,
|
||||||
@@ -1304,31 +1332,7 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
|
|
||||||
let imagesData: Array<{ data: string; mimeType: string }> | undefined;
|
const imagesData = await encodeImages(images);
|
||||||
if (images && images.length > 0) {
|
|
||||||
const encodedImages = await Promise.all(
|
|
||||||
images.map(async ({ uri, mimeType }) => {
|
|
||||||
try {
|
|
||||||
const data = await FileSystem.readAsStringAsync(uri, {
|
|
||||||
encoding: "base64",
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
mimeType: mimeType ?? "image/jpeg",
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[Session] Failed to convert image:", error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const validImages = encodedImages.filter(
|
|
||||||
(entry): entry is { data: string; mimeType: string } => entry !== null
|
|
||||||
);
|
|
||||||
if (validImages.length > 0) {
|
|
||||||
imagesData = validImages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const msg: WSInboundMessage = {
|
const msg: WSInboundMessage = {
|
||||||
type: "session",
|
type: "session",
|
||||||
@@ -1341,7 +1345,7 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
ws.send(msg);
|
ws.send(msg);
|
||||||
}, [serverId, ws, setAgentStreamState]);
|
}, [encodeImages, serverId, ws, setAgentStreamState]);
|
||||||
|
|
||||||
const cancelAgentRun = useCallback((agentId: string) => {
|
const cancelAgentRun = useCallback((agentId: string) => {
|
||||||
const msg: WSInboundMessage = {
|
const msg: WSInboundMessage = {
|
||||||
@@ -1431,21 +1435,28 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
}
|
}
|
||||||
}, [ws]);
|
}, [ws]);
|
||||||
|
|
||||||
const createAgent = useCallback(({ config, initialPrompt, git, worktreeName, requestId }: { config: any; initialPrompt: string; git?: any; worktreeName?: string; requestId?: string }) => {
|
const createAgent = useCallback(async ({ config, initialPrompt, images, git, worktreeName, requestId }: { config: any; initialPrompt: string; images?: Array<{ uri: string; mimeType?: string }>; git?: any; worktreeName?: string; requestId?: string }) => {
|
||||||
const trimmedPrompt = initialPrompt.trim();
|
const trimmedPrompt = initialPrompt.trim();
|
||||||
|
let imagesData: Array<{ data: string; mimeType: string }> | undefined;
|
||||||
|
try {
|
||||||
|
imagesData = await encodeImages(images);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Session] Failed to prepare images for agent creation:", error);
|
||||||
|
}
|
||||||
const msg: WSInboundMessage = {
|
const msg: WSInboundMessage = {
|
||||||
type: "session",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
type: "create_agent_request",
|
type: "create_agent_request",
|
||||||
config,
|
config,
|
||||||
...(trimmedPrompt ? { initialPrompt: trimmedPrompt } : {}),
|
...(trimmedPrompt ? { initialPrompt: trimmedPrompt } : {}),
|
||||||
|
...(imagesData && imagesData.length > 0 ? { images: imagesData } : {}),
|
||||||
...(git ? { git } : {}),
|
...(git ? { git } : {}),
|
||||||
...(worktreeName ? { worktreeName } : {}),
|
...(worktreeName ? { worktreeName } : {}),
|
||||||
...(requestId ? { requestId } : {}),
|
...(requestId ? { requestId } : {}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
ws.send(msg);
|
ws.send(msg);
|
||||||
}, [ws]);
|
}, [encodeImages, ws]);
|
||||||
|
|
||||||
const resumeAgent = useCallback(({ handle, overrides, requestId }: { handle: any; overrides?: any; requestId?: string }) => {
|
const resumeAgent = useCallback(({ handle, overrides, requestId }: { handle: any; overrides?: any; requestId?: string }) => {
|
||||||
const msg: WSInboundMessage = {
|
const msg: WSInboundMessage = {
|
||||||
|
|||||||
@@ -199,10 +199,11 @@ export interface SessionState {
|
|||||||
createAgent: (options: {
|
createAgent: (options: {
|
||||||
config: any;
|
config: any;
|
||||||
initialPrompt: string;
|
initialPrompt: string;
|
||||||
|
images?: Array<{ uri: string; mimeType?: string }>;
|
||||||
git?: any;
|
git?: any;
|
||||||
worktreeName?: string;
|
worktreeName?: string;
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
}) => void;
|
}) => Promise<void>;
|
||||||
resumeAgent: (options: { handle: any; overrides?: any; requestId?: string }) => void;
|
resumeAgent: (options: { handle: any; overrides?: any; requestId?: string }) => void;
|
||||||
setAgentMode: (agentId: string, modeId: string) => void;
|
setAgentMode: (agentId: string, modeId: string) => void;
|
||||||
respondToPermission: (agentId: string, requestId: string, response: any) => void;
|
respondToPermission: (agentId: string, requestId: string, response: any) => void;
|
||||||
|
|||||||
@@ -365,6 +365,10 @@ export const CreateAgentRequestMessageSchema = z.object({
|
|||||||
config: AgentSessionConfigSchema,
|
config: AgentSessionConfigSchema,
|
||||||
worktreeName: z.string().optional(),
|
worktreeName: z.string().optional(),
|
||||||
initialPrompt: z.string().optional(),
|
initialPrompt: z.string().optional(),
|
||||||
|
images: z.array(z.object({
|
||||||
|
data: z.string(), // base64 encoded image
|
||||||
|
mimeType: z.string(), // e.g., "image/jpeg", "image/png"
|
||||||
|
})).optional(),
|
||||||
git: GitSetupOptionsSchema.optional(),
|
git: GitSetupOptionsSchema.optional(),
|
||||||
requestId: z.string().optional(),
|
requestId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1265,7 +1265,7 @@ export class Session {
|
|||||||
private async handleCreateAgentRequest(
|
private async handleCreateAgentRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "create_agent_request" }>
|
msg: Extract<SessionInboundMessage, { type: "create_agent_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { config, worktreeName, requestId, initialPrompt, git } = msg;
|
const { config, worktreeName, requestId, initialPrompt, git, images } = msg;
|
||||||
console.log(
|
console.log(
|
||||||
`[Session ${this.clientId}] Creating agent in ${config.cwd} (${config.provider})${
|
`[Session ${this.clientId}] Creating agent in ${config.cwd} (${config.provider})${
|
||||||
worktreeName ? ` with worktree ${worktreeName}` : ""
|
worktreeName ? ` with worktree ${worktreeName}` : ""
|
||||||
@@ -1306,7 +1306,8 @@ export class Session {
|
|||||||
await this.handleSendAgentMessage(
|
await this.handleSendAgentMessage(
|
||||||
snapshot.id,
|
snapshot.id,
|
||||||
trimmedPrompt,
|
trimmedPrompt,
|
||||||
uuidv4()
|
uuidv4(),
|
||||||
|
images
|
||||||
);
|
);
|
||||||
} catch (promptError) {
|
} catch (promptError) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
3
plan.md
3
plan.md
@@ -134,10 +134,11 @@ Improvements to the new agent screen in the app.
|
|||||||
- If broken, add fix tasks with debugging details.
|
- If broken, add fix tasks with debugging details.
|
||||||
- **Done (2025-12-29 09:13)**: WHAT: Tested image attachments via Playwright MCP; traced code in `packages/app/src/app/agent/new.tsx:576-580`, `packages/app/src/contexts/session-context.tsx:1307-1331`, `packages/server/src/server/session.ts:316-334`. RESULT: Image attachments are BROKEN - three issues found: (1) Agent creation silently drops images with console warning, (2) Web platform fails to base64 encode images due to `expo-file-system.readAsStringAsync` not working, (3) Server `buildAgentPrompt` only creates text summary, never passes actual image bytes to LLM. EVIDENCE: Console logs `[WARNING] [DraftAgentScreen] Image attachments on agent creation not yet supported` and `[ERROR] [Session] Failed to convert image: Error: Method readAsStringAsync imported from "expo-file-system"...`; agent responses consistently state "I don't see any image attached". Full report in `REPORT-image-attachment-investigation.md`.
|
- **Done (2025-12-29 09:13)**: WHAT: Tested image attachments via Playwright MCP; traced code in `packages/app/src/app/agent/new.tsx:576-580`, `packages/app/src/contexts/session-context.tsx:1307-1331`, `packages/server/src/server/session.ts:316-334`. RESULT: Image attachments are BROKEN - three issues found: (1) Agent creation silently drops images with console warning, (2) Web platform fails to base64 encode images due to `expo-file-system.readAsStringAsync` not working, (3) Server `buildAgentPrompt` only creates text summary, never passes actual image bytes to LLM. EVIDENCE: Console logs `[WARNING] [DraftAgentScreen] Image attachments on agent creation not yet supported` and `[ERROR] [Session] Failed to convert image: Error: Method readAsStringAsync imported from "expo-file-system"...`; agent responses consistently state "I don't see any image attached". Full report in `REPORT-image-attachment-investigation.md`.
|
||||||
|
|
||||||
- [ ] **Fix**: Add image support to agent creation API
|
- [x] **Fix**: Add image support to agent creation API
|
||||||
- Extend `create_agent_request` to accept images array
|
- Extend `create_agent_request` to accept images array
|
||||||
- Pass images to `handleSendAgentMessage` after agent creation
|
- Pass images to `handleSendAgentMessage` after agent creation
|
||||||
- Remove warning in `new.tsx:576-580` once implemented
|
- Remove warning in `new.tsx:576-580` once implemented
|
||||||
|
- **Done (2025-12-29 09:07)**: WHAT: `packages/server/src/server/messages.ts:363-373` adds optional `images` to `create_agent_request`; `packages/server/src/server/session.ts:1268-1311` forwards images into `handleSendAgentMessage`; `packages/app/src/contexts/session-context.tsx:1287-1459` encodes image attachments and includes them in create-agent websocket payloads; `packages/app/src/stores/session-store.ts:175-206` updates createAgent type to accept images/async; `packages/app/src/app/agent/new.tsx:560-606` forwards images to createAgent and removes the warning. RESULT: agent creation requests now carry image attachments through to the initial prompt path. EVIDENCE: Not run (not requested).
|
||||||
|
|
||||||
- [ ] **Fix**: Fix web platform image base64 encoding
|
- [ ] **Fix**: Fix web platform image base64 encoding
|
||||||
- Replace `FileSystem.readAsStringAsync` in `session-context.tsx:1312` with cross-platform solution
|
- Replace `FileSystem.readAsStringAsync` in `session-context.tsx:1312` with cross-platform solution
|
||||||
|
|||||||
Reference in New Issue
Block a user