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
38 changes: 36 additions & 2 deletions src/main/presenter/agentRuntimePresenter/contextBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,14 @@ export function normalizeUserInput(input: string | SendMessageInput): SendMessag
)
)
: []
const inlineItems = Array.isArray(input.inlineItems) ? input.inlineItems : []
return {
text: typeof input.text === 'string' ? input.text : '',
files: Array.isArray(input.files)
? (input.files.filter((file): file is MessageFile => Boolean(file)) as MessageFile[])
: [],
...(activeSkills.length > 0 ? { activeSkills } : {})
...(activeSkills.length > 0 ? { activeSkills } : {}),
...(inlineItems.length > 0 ? { inlineItems } : {})
}
}

Expand Down Expand Up @@ -402,13 +404,45 @@ function buildImageMetadataContext(files: MessageFile[]): string {
.join('\n\n')
}

function buildInlineDisplayText(input: SendMessageInput): string {
const text = input.text ?? ''
const inlineItems = Array.isArray(input.inlineItems) ? input.inlineItems : []
if (inlineItems.length === 0) {
return text
}

const validItems = inlineItems
.map((item, index) => ({ item, index }))
.filter(
({ item }) => Number.isInteger(item.offset) && item.offset >= 0 && item.offset <= text.length
)
.sort((left, right) => left.item.offset - right.item.offset || left.index - right.index)
if (validItems.length === 0) {
return text
}

const parts: string[] = []
let cursor = 0
for (const { item } of validItems) {
if (item.offset > cursor) {
parts.push(text.slice(cursor, item.offset))
}
parts.push(item.type === 'skill' ? `[Skill: ${item.skillName}]` : `[File: ${item.fileName}]`)
cursor = item.offset
}
if (cursor < text.length) {
parts.push(text.slice(cursor))
}
return parts.join('')
}

export function buildUserMessageContent(
input: SendMessageInput,
supportsVision: boolean,
supportsAudioInput: boolean = false,
options: UserMessageContentBuildOptions = {}
): ChatMessage['content'] {
const text = input.text ?? ''
const text = buildInlineDisplayText(input)
const files = Array.isArray(input.files) ? input.files : []
const includeImageData = options.includeImageData !== false
const includeAudioData = options.includeAudioData !== false
Expand Down
4 changes: 3 additions & 1 deletion src/main/presenter/agentRuntimePresenter/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1191,7 +1191,9 @@ async function runToolCall(params: {
onProgress: applyProgressUpdate,
signal: io.abortSignal,
permissionMode: toolPermissionMode,
activeSkillNames: hooks?.getActiveSkillNames?.()
activeSkillNames: hooks?.getActiveSkillNames?.(),
enabledSkillNames: hooks?.getEnabledSkillNames?.(),
enabledPluginIds: hooks?.getEnabledPluginIds?.()
})

let toolCallResult = await callTool()
Expand Down
28 changes: 25 additions & 3 deletions src/main/presenter/agentRuntimePresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1330,7 +1330,8 @@ export class AgentRuntimePresenter implements IAgentImplementation {
think: false,
...(normalizedInput.activeSkills?.length
? { activeSkills: normalizedInput.activeSkills }
: {})
: {}),
...(normalizedInput.inlineItems?.length ? { inlineItems: normalizedInput.inlineItems } : {})
}

let compactionIntent: CompactionIntent | null = null
Expand Down Expand Up @@ -3447,6 +3448,7 @@ export class AgentRuntimePresenter implements IAgentImplementation {

const streamSessionActiveSkillNames =
await this.resolveActiveSkillNamesForToolProfile(sessionId)
const streamExtensionPolicy = await this.resolveAgentExtensionPolicy(sessionId)
const getEffectiveRuntimeSkillNames = (baseSkillNames = streamSessionActiveSkillNames) =>
this.resolveEffectiveActiveSkillNames(baseSkillNames, sessionId)
const tools =
Expand Down Expand Up @@ -3853,6 +3855,10 @@ export class AgentRuntimePresenter implements IAgentImplementation {
Boolean(this.pendingInputCoordinator.getNextSteerInput(sessionId)),
hooks: {
getActiveSkillNames: () => getEffectiveRuntimeSkillNames(),
getEnabledSkillNames: () =>
this.normalizeNullablePolicyList(streamExtensionPolicy.enabledSkillNames),
getEnabledPluginIds: () =>
this.normalizeNullablePolicyList(streamExtensionPolicy.enabledPluginIds),
activateSkill: async (skillName) => {
const policy = await this.resolveAgentExtensionPolicy(sessionId)
if (this.filterSkillNamesByPolicy([skillName], policy).length === 0) {
Expand Down Expand Up @@ -5851,10 +5857,18 @@ export class AgentRuntimePresenter implements IAgentImplementation {
? ((parsed as { activeSkills?: unknown }).activeSkills as string[])
: []
)
const inlineItems: NonNullable<SendMessageInput['inlineItems']> = Array.isArray(
(parsed as { inlineItems?: unknown }).inlineItems
)
? ((parsed as { inlineItems?: unknown }).inlineItems as NonNullable<
SendMessageInput['inlineItems']
>)
: []
return {
text,
files,
...(activeSkills.length > 0 ? { activeSkills } : {})
...(activeSkills.length > 0 ? { activeSkills } : {}),
...(inlineItems.length > 0 ? { inlineItems } : {})
}
} catch {
return { text: content, files: [] }
Expand All @@ -5875,10 +5889,12 @@ export class AgentRuntimePresenter implements IAgentImplementation {
const activeSkills = this.normalizeSkillNames(
Array.isArray(input.activeSkills) ? input.activeSkills : []
)
const inlineItems = Array.isArray(input.inlineItems) ? input.inlineItems : []
return {
text,
files,
...(activeSkills.length > 0 ? { activeSkills } : {})
...(activeSkills.length > 0 ? { activeSkills } : {}),
...(inlineItems.length > 0 ? { inlineItems } : {})
}
}

Expand Down Expand Up @@ -5928,6 +5944,7 @@ export class AgentRuntimePresenter implements IAgentImplementation {
}

const next = { ...parsed, text } as Record<string, unknown>
delete next.inlineItems

if (!Array.isArray(next.files)) {
next.files = []
Expand Down Expand Up @@ -5964,6 +5981,10 @@ export class AgentRuntimePresenter implements IAgentImplementation {
next.content = mapped
}

if (Array.isArray(next.inlineItems)) {
delete next.inlineItems
}

return JSON.stringify(next)
} catch {
return JSON.stringify(fallback)
Expand Down Expand Up @@ -6497,6 +6518,7 @@ export class AgentRuntimePresenter implements IAgentImplementation {
const extensionPolicy = await this.resolveAgentExtensionPolicy(sessionId)
const result = await this.toolPresenter.callTool(request, {
agentId: this.getSessionAgentId(sessionId) ?? 'deepchat',
enabledSkillNames: extensionPolicy.enabledSkillNames ?? undefined,
enabledPluginIds: extensionPolicy.enabledPluginIds ?? undefined,
onProgress: (update) => {
if (
Expand Down
7 changes: 5 additions & 2 deletions src/main/presenter/agentRuntimePresenter/messageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,13 +742,15 @@ export class DeepChatMessageStore {

const rawUserContent = this.parseUserContent(row.content)
const activeSkills = rawUserContent?.activeSkills ?? []
const inlineItems = rawUserContent?.inlineItems ?? []
return JSON.stringify({
text: userRow.text,
files: fileRows.map((fileRow) => this.toMessageFile(fileRow)),
links: linkRows.map((linkRow) => linkRow.url),
search: userRow.search_enabled === 1,
think: userRow.think_enabled === 1,
...(activeSkills.length > 0 ? { activeSkills } : {})
...(activeSkills.length > 0 ? { activeSkills } : {}),
...(inlineItems.length > 0 ? { inlineItems } : {})
} satisfies UserMessageContent)
}

Expand Down Expand Up @@ -786,7 +788,8 @@ export class DeepChatMessageStore {
: [],
search: parsed.search === true,
think: parsed.think === true,
activeSkills: this.normalizeActiveSkills(parsed.activeSkills)
activeSkills: this.normalizeActiveSkills(parsed.activeSkills),
inlineItems: Array.isArray(parsed.inlineItems) ? parsed.inlineItems : []
}
} catch {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ function normalizeInput(input: string | SendMessageInput): SendMessageInput {
)
: []

const inlineItems = Array.isArray(input?.inlineItems) ? input.inlineItems : []
return {
text: typeof input?.text === 'string' ? input.text : '',
files: Array.isArray(input?.files) ? input.files.filter(Boolean) : [],
...(activeSkills.length > 0 ? { activeSkills } : {})
...(activeSkills.length > 0 ? { activeSkills } : {}),
...(inlineItems.length > 0 ? { inlineItems } : {})
}
}

Expand Down
33 changes: 30 additions & 3 deletions src/main/presenter/agentRuntimePresenter/pendingInputStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import type {
import type { SQLitePresenter } from '../sqlitePresenter'
import type { DeepChatPendingInputRow } from '../sqlitePresenter/tables/deepchatPendingInputs'

type InlineItem = NonNullable<SendMessageInput['inlineItems']>[number]

function normalizeInput(input: string | SendMessageInput): SendMessageInput {
if (typeof input === 'string') {
return { text: input, files: [] }
Expand All @@ -22,13 +24,29 @@ function normalizeInput(input: string | SendMessageInput): SendMessageInput {
)
: []

const inlineItems = Array.isArray(input?.inlineItems) ? input.inlineItems : []
return {
text: typeof input?.text === 'string' ? input.text : '',
files: Array.isArray(input?.files) ? input.files.filter(Boolean) : [],
...(activeSkills.length > 0 ? { activeSkills } : {})
...(activeSkills.length > 0 ? { activeSkills } : {}),
...(inlineItems.length > 0 ? { inlineItems } : {})
}
}

function shiftInlineItems(
inlineItems: SendMessageInput['inlineItems'],
offset: number
): InlineItem[] {
if (!Array.isArray(inlineItems) || inlineItems.length === 0) {
return []
}

return inlineItems.map((item) => ({
...item,
offset: Math.max(0, item.offset + offset)
}))
}

export class DeepChatPendingInputStore {
private readonly sqlitePresenter: SQLitePresenter

Expand Down Expand Up @@ -117,16 +135,25 @@ export class DeepChatPendingInputStore {

const existing = this.parsePayload(row.payload_json)
const next = normalizeInput(input)
const text = [existing.text.trim(), next.text.trim()].filter(Boolean).join('\n\n')
const existingText = existing.text.trim()
const nextText = next.text.trim()
const separator = existingText && nextText ? '\n\n' : ''
const text = [existingText, nextText].filter(Boolean).join(separator)
const nextOffset = existingText.length + separator.length
const files = [...(existing.files ?? []), ...(next.files ?? [])].filter(Boolean)
const activeSkills = Array.from(
new Set([...(existing.activeSkills ?? []), ...(next.activeSkills ?? [])])
)
const inlineItems = [
...(existing.inlineItems ?? []),
...shiftInlineItems(next.inlineItems, nextOffset)
]
this.sqlitePresenter.deepchatPendingInputsTable.update(itemId, {
payload_json: JSON.stringify({
text,
files,
...(activeSkills.length > 0 ? { activeSkills } : {})
...(activeSkills.length > 0 ? { activeSkills } : {}),
...(inlineItems.length > 0 ? { inlineItems } : {})
})
})
return this.toRecord(this.requireRow(itemId, row.session_id))
Expand Down
2 changes: 2 additions & 0 deletions src/main/presenter/agentRuntimePresenter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export interface ProcessHooks {
commitDecision: (granted: boolean) => void
) => void
getActiveSkillNames?: () => string[]
getEnabledSkillNames?: () => string[] | null | undefined
getEnabledPluginIds?: () => string[] | null | undefined
activateSkill?: (skillName: string) => Promise<string[]>
normalizeToolResult?: (tool: {
sessionId: string
Expand Down
14 changes: 12 additions & 2 deletions src/main/presenter/agentSessionPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4126,10 +4126,12 @@ export class AgentSessionPresenter {
? content.files.filter((file): file is MessageFile => Boolean(file))
: []
const activeSkills = this.normalizeActiveSkills(content.activeSkills)
const inlineItems = Array.isArray(content.inlineItems) ? content.inlineItems : []
return {
text,
files,
...(activeSkills.length > 0 ? { activeSkills } : {})
...(activeSkills.length > 0 ? { activeSkills } : {}),
...(inlineItems.length > 0 ? { inlineItems } : {})
}
}

Expand All @@ -4138,7 +4140,15 @@ export class AgentSessionPresenter {
const files = Array.isArray(input.files)
? input.files.filter((file): file is MessageFile => Boolean(file))
: []
return this.withInitialMessageActiveSkills({ text, files }, input.activeSkills)
const inlineItems = Array.isArray(input.inlineItems) ? input.inlineItems : []
return this.withInitialMessageActiveSkills(
{
text,
files,
...(inlineItems.length > 0 ? { inlineItems } : {})
},
input.activeSkills
)
}

private withInitialMessageActiveSkills(
Expand Down
Loading