Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
import { describe, expect, test } from "bun:test"
import { toolPartFromUpdate } from "./acp-client-support"
import {
requestUserInputFromOriginalEvent,
toolPartFromUpdate,
} from "./acp-client-support"

describe("ACP original event mapping", () => {
test("extracts request_user_input from the tagged server event wire shape", () => {
const originalEvent = {
kind: "request_user_input",
request: {
request_id: "rq1",
session_id: "s1",
turn_id: "t1",
item_id: null,
},
questions: [
{
id: "scope",
header: "Scope",
question: "Which scope?",
isOther: true,
isSecret: false,
options: [{ label: "Repo", description: "Current repository" }],
},
],
}

expect(requestUserInputFromOriginalEvent(originalEvent)).toEqual(originalEvent)
})
})

describe("ACP tool update mapping", () => {
test("infers command tools from raw input instead of exposing the tool call id", () => {
Expand Down
14 changes: 14 additions & 0 deletions apps/desktop/packages/devo-ai-sdk/src/v2/acp-client-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,23 @@ export function questionInfoFromAcp(question: unknown): any {
header: String(value.header ?? ""),
question: String(value.question ?? ""),
options,
isOther: Boolean(value.isOther ?? value.is_other ?? true),
isSecret: Boolean(value.isSecret ?? value.is_secret ?? false),
}
}

export function requestUserInputFromOriginalEvent(
original: unknown,
): Record<string, unknown> | undefined {
if (!original || typeof original !== "object") return undefined
const event = original as Record<string, unknown>
if (event.kind === "request_user_input") return event
const legacy = event.RequestUserInput
return legacy && typeof legacy === "object"
? (legacy as Record<string, unknown>)
: undefined
}

export function partTime(existingPart: any, now: number): { start: number; end?: number } {
return existingPart?.time ?? { start: now }
}
Expand Down
33 changes: 18 additions & 15 deletions apps/desktop/packages/devo-ai-sdk/src/v2/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1208,22 +1208,23 @@ describe("ACP desktop SDK session mapping", () => {
update: { sessionUpdate: "session_info_update" },
_meta: {
"devo/originalEvent": {
RequestUserInput: {
request: {
request_id: "rq1",
session_id: "s1",
turn_id: "t1",
item_id: null,
},
questions: [
{
id: "scope",
header: "Scope",
question: "Which scope?",
options: [{ label: "Repo", description: "Current repository" }],
},
],
kind: "request_user_input",
request: {
request_id: "rq1",
session_id: "s1",
turn_id: "t1",
item_id: null,
},
questions: [
{
id: "scope",
header: "Scope",
question: "Which scope?",
isOther: true,
isSecret: false,
options: [{ label: "Repo", description: "Current repository" }],
},
],
},
},
} satisfies AcpSessionNotification)
Expand Down Expand Up @@ -1255,6 +1256,8 @@ describe("ACP desktop SDK session mapping", () => {
header: "Scope",
question: "Which scope?",
options: [{ label: "Repo", description: "Current repository" }],
isOther: true,
isSecret: false,
},
],
},
Expand Down
51 changes: 43 additions & 8 deletions apps/desktop/packages/devo-ai-sdk/src/v2/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
partTime,
providerDataFromConfigOptions,
questionInfoFromAcp,
requestUserInputFromOriginalEvent,
sessionErrorEvent,
stableId,
statusFromDevo,
Expand Down Expand Up @@ -68,7 +69,10 @@ import {
type ReferenceSearchSnapshot,
} from "./reference-search-session"

export type { ReferenceSearchSnapshot } from "./reference-search-session"
export type {
ReferenceSearchResult,
ReferenceSearchSnapshot,
} from "./reference-search-session"

export type JsonRpcId = number | string

Expand Down Expand Up @@ -132,10 +136,28 @@ export type Project = any
export type Provider = any
export type ProviderAuthMethod = any
export type ProviderConfig = any
export type QuestionAnswer = any
export type QuestionInfo = any
export type QuestionOption = any
export type QuestionRequest = any
export type QuestionOption = {
label: string
description: string
}

export type QuestionInfo = {
id: string
header: string
question: string
options: QuestionOption[]
isOther: boolean
isSecret: boolean
}

export type QuestionRequest = {
id: string
requestID: string
sessionID: string
questions: QuestionInfo[]
}

export type QuestionAnswer = string[]
export type ReasoningPart = any
export type RetryPart = any
export type ServerConfig = any
Expand Down Expand Up @@ -636,7 +658,19 @@ class AcpClient {
model?: unknown
agent?: string
variant?: string
collaborationMode?: string
}) => {
// Plan mode uses turn/start which supports collaboration_mode
if (params.collaborationMode === "plan") {
const result = await this.turn.start({
sessionID: params.sessionID,
parts: params.parts,
model: params.model,
variant: params.variant,
collaborationMode: "plan",
})
return result
}
const model = params.model as { modelID?: string } | undefined
if (model?.modelID) await this.setSessionConfigOption(params.sessionID, "model", model.modelID)
if (params.variant) await this.setSessionConfigOption(params.sessionID, "thought_level", params.variant)
Expand Down Expand Up @@ -760,6 +794,7 @@ class AcpClient {
model?: unknown
variant?: string
cwd?: string | null
collaborationMode?: string
}) => {
const model = params.model as { modelID?: string } | undefined
if (model?.modelID) await this.setSessionConfigOption(params.sessionID, "model", model.modelID)
Expand All @@ -771,7 +806,7 @@ class AcpClient {
sandbox: null,
approval_policy: null,
cwd: params.cwd ?? null,
collaboration_mode: "build",
collaboration_mode: params.collaborationMode ?? "build",
})) as TurnStartResult
return { data: result }
},
Expand Down Expand Up @@ -1580,8 +1615,8 @@ class AcpClient {
})
return
}
if ("RequestUserInput" in original) {
const payload = (original as { RequestUserInput: Record<string, unknown> }).RequestUserInput
const payload = requestUserInputFromOriginalEvent(original)
if (payload) {
this.handleRequestUserInput(sessionId, directory, payload)
}
const workspaceChanges = workspaceChangesUpdatedFromOriginalEvent(original)
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/renderer/atoms/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,12 +436,13 @@ export const updateProjectPaginationAtom = atom(
directory: string
fetchedCount: number
limit: number
hasMore?: boolean
},
) => {
set(projectPaginationFamily(args.directory), {
loaded: true,
currentLimit: args.limit,
hasMore: args.fetchedCount >= args.limit,
hasMore: args.hasMore ?? args.fetchedCount >= args.limit,
loading: false,
})
},
Expand Down
11 changes: 5 additions & 6 deletions apps/desktop/src/renderer/components/chat/chat-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import { ContextItems } from "./context-items"
import { type MentionOption, MentionPopover, type MentionPopoverHandle } from "./mention-popover"
import { PromptAttachmentPreview } from "./prompt-attachments"
import {
createAgentMention,
createFileMention,
createMentionFromOption,
getMentionKey,
getMentionMarker,
insertMentionIntoText,
type PromptMention,
Expand Down Expand Up @@ -261,17 +261,16 @@ export function ChatInput({
const currentText = ctrl.getText()
const textarea = document.querySelector<HTMLTextAreaElement>("textarea[data-prompt-input]")
const cursorPos = textarea?.selectionStart ?? currentText.length
const mention =
option.type === "file" ? createFileMention(option.path) : createAgentMention(option.name)
const mention = createMentionFromOption(option)
const { text: newText, cursorPosition: newCursor } = insertMentionIntoText(
currentText,
cursorPos,
mention,
)
ctrl.setText(newText)
setMentions((prev) => {
const key = mention.type === "file" ? `file:${mention.path}` : `agent:${mention.name}`
if (prev.some((m) => (m.type === "file" ? `file:${m.path}` : `agent:${m.name}`) === key))
const key = getMentionKey(mention)
if (prev.some((candidate) => getMentionKey(candidate) === key))
return prev
return [...prev, mention]
})
Expand Down
Loading
Loading