add tool use, update system prompt

This commit is contained in:
duanfuxiang
2025-03-12 21:39:29 +08:00
parent cabf2d5fa4
commit b0fbbb22d3
36 changed files with 7149 additions and 430 deletions

View File

@@ -1,128 +1,433 @@
import JSON5 from 'json5'
import { parseFragment } from 'parse5'
export enum InfioBlockAction {
Edit = 'edit',
New = 'new',
Reference = 'reference'
}
export type ParsedInfioBlock =
| { type: 'string'; content: string }
export type ParsedMsgBlock =
| {
type: 'infio_block'
type: 'string'
content: string
language?: string
filename?: string
startLine?: number
endLine?: number
action?: InfioBlockAction
}
| { type: 'think'; content: string }
function isInfioBlockAction(value: string): value is InfioBlockAction {
return Object.values<string>(InfioBlockAction).includes(value)
}
export function parseinfioBlocks(input: string): ParsedInfioBlock[] {
const parsedResult: ParsedInfioBlock[] = []
const fragment = parseFragment(input, {
sourceCodeLocationInfo: true,
})
let lastEndOffset = 0
for (const node of fragment.childNodes) {
if (node.nodeName === 'infio_block') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
const language = node.attrs.find((attr) => attr.name === 'language')?.value
const filename = node.attrs.find((attr) => attr.name === 'filename')?.value
const startLine = node.attrs.find((attr) => attr.name === 'startline')?.value
const endLine = node.attrs.find((attr) => attr.name === 'endline')?.value
const actionValue = node.attrs.find((attr) => attr.name === 'type')?.value
const action = actionValue && isInfioBlockAction(actionValue)
? actionValue
: undefined
const children = node.childNodes
if (children.length === 0) {
parsedResult.push({
type: 'infio_block',
content: '',
language,
filename,
startLine: startLine ? parseInt(startLine) : undefined,
endLine: endLine ? parseInt(endLine) : undefined,
action: action,
})
} else {
const innerContentStartOffset =
children[0].sourceCodeLocation?.startOffset
const innerContentEndOffset =
children[children.length - 1].sourceCodeLocation?.endOffset
if (!innerContentStartOffset || !innerContentEndOffset) {
throw new Error('sourceCodeLocation is undefined')
}
parsedResult.push({
type: 'infio_block',
content: input.slice(innerContentStartOffset, innerContentEndOffset),
language,
filename,
startLine: startLine ? parseInt(startLine) : undefined,
endLine: endLine ? parseInt(endLine) : undefined,
action: action,
})
}
lastEndOffset = endOffset
} else if (node.nodeName === 'think') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
const children = node.childNodes
if (children.length === 0) {
parsedResult.push({
type: 'think',
content: '',
})
} else {
const innerContentStartOffset =
children[0].sourceCodeLocation?.startOffset
const innerContentEndOffset =
children[children.length - 1].sourceCodeLocation?.endOffset
if (!innerContentStartOffset || !innerContentEndOffset) {
throw new Error('sourceCodeLocation is undefined')
}
parsedResult.push({
type: 'think',
content: input.slice(innerContentStartOffset, innerContentEndOffset),
})
}
lastEndOffset = endOffset
}
| {
type: 'think'
content: string
} | {
type: 'thinking'
content: string
} | {
type: 'write_to_file'
path: string
content: string
lineCount?: number
} | {
type: 'insert_content'
path: string
startLine: number
content: string
} | {
type: 'read_file'
path: string
finish: boolean
} | {
type: 'attempt_completion'
result: string
} | {
type: 'search_and_replace'
path: string
operations: {
search: string
replace: string
start_line?: number
end_line?: number
use_regex?: boolean
ignore_case?: boolean
regex_flags?: string
}[]
finish: boolean
} | {
type: 'ask_followup_question'
question: string
} | {
type: 'list_files'
path: string
recursive?: boolean
finish: boolean
} | {
type: 'regex_search_files'
path: string
regex: string
finish: boolean
} | {
type: 'semantic_search_files'
path: string
query: string
finish: boolean
}
if (lastEndOffset < input.length) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset),
export function parseMsgBlocks(
input: string,
): ParsedMsgBlock[] {
try {
const parsedResult: ParsedMsgBlock[] = []
const fragment = parseFragment(input, {
sourceCodeLocationInfo: true,
})
let lastEndOffset = 0
for (const node of fragment.childNodes) {
if (node.nodeName === 'thinking') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
const children = node.childNodes
if (children.length === 0) {
parsedResult.push({
type: 'thinking',
content: '',
})
} else {
const innerContentStartOffset =
children[0].sourceCodeLocation?.startOffset
const innerContentEndOffset =
children[children.length - 1].sourceCodeLocation?.endOffset
if (!innerContentStartOffset || !innerContentEndOffset) {
throw new Error('sourceCodeLocation is undefined')
}
parsedResult.push({
type: 'thinking',
content: input.slice(innerContentStartOffset, innerContentEndOffset),
})
}
lastEndOffset = endOffset
} else if (node.nodeName === 'think') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
const children = node.childNodes
if (children.length === 0) {
parsedResult.push({
type: 'think',
content: '',
})
} else {
const innerContentStartOffset =
children[0].sourceCodeLocation?.startOffset
const innerContentEndOffset =
children[children.length - 1].sourceCodeLocation?.endOffset
if (!innerContentStartOffset || !innerContentEndOffset) {
throw new Error('sourceCodeLocation is undefined')
}
parsedResult.push({
type: 'think',
content: input.slice(innerContentStartOffset, innerContentEndOffset),
})
}
lastEndOffset = endOffset
} else if (node.nodeName === 'list_files') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let path: string | undefined
let recursive: boolean | undefined
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'path' && childNode.childNodes.length > 0) {
path = childNode.childNodes[0].value
} else if (childNode.nodeName === 'recursive' && childNode.childNodes.length > 0) {
const recursiveValue = childNode.childNodes[0].value
recursive = recursiveValue ? recursiveValue.toLowerCase() === 'true' : false
}
}
parsedResult.push({
type: 'list_files',
path: path || '/',
recursive,
finish: node.sourceCodeLocation.endTag !== undefined
})
lastEndOffset = endOffset
} else if (node.nodeName === 'read_file') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let path: string | undefined
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'path' && childNode.childNodes.length > 0) {
path = childNode.childNodes[0].value
}
}
parsedResult.push({
type: 'read_file',
path,
// Check if the tag is completely parsed with proper closing tag
// In parse5, when a tag is properly closed, its sourceCodeLocation will include endTag
finish: node.sourceCodeLocation.endTag !== undefined
})
lastEndOffset = endOffset
} else if (node.nodeName === 'regex_search_files') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let path: string | undefined
let regex: string | undefined
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'path' && childNode.childNodes.length > 0) {
path = childNode.childNodes[0].value
} else if (childNode.nodeName === 'regex' && childNode.childNodes.length > 0) {
regex = childNode.childNodes[0].value
}
}
parsedResult.push({
type: 'regex_search_files',
path: path,
regex: regex,
finish: node.sourceCodeLocation.endTag !== undefined
})
lastEndOffset = endOffset
} else if (node.nodeName === 'semantic_search_files') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let path: string | undefined
let query: string | undefined
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'path' && childNode.childNodes.length > 0) {
path = childNode.childNodes[0].value
} else if (childNode.nodeName === 'query' && childNode.childNodes.length > 0) {
query = childNode.childNodes[0].value
}
}
parsedResult.push({
type: 'semantic_search_files',
path: path,
query: query,
finish: node.sourceCodeLocation.endTag !== undefined
})
lastEndOffset = endOffset
} else if (node.nodeName === 'write_to_file') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let path: string | undefined
let content: string = ''
let lineCount: number | undefined
// 处理子标签
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'path' && childNode.childNodes.length > 0) {
path = childNode.childNodes[0].value
} else if (childNode.nodeName === 'content' && childNode.childNodes.length > 0) {
// 如果内容有多个子节点,需要合并它们
content = childNode.childNodes.map(n => n.value || '').join('')
} else if (childNode.nodeName === 'line_count' && childNode.childNodes.length > 0) {
const lineCountStr = childNode.childNodes[0].value
lineCount = lineCountStr ? parseInt(lineCountStr) : undefined
}
}
parsedResult.push({
type: 'write_to_file',
content,
path,
lineCount
})
lastEndOffset = endOffset
} else if (node.nodeName === 'insert_content') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let path: string | undefined
let content: string = ''
let startLine: number = 0
// 处理子标签
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'path' && childNode.childNodes.length > 0) {
path = childNode.childNodes[0].value
} else if (childNode.nodeName === 'operations' && childNode.childNodes.length > 0) {
try {
const operationsJson = childNode.childNodes[0].value
const operations = JSON5.parse(operationsJson)
if (Array.isArray(operations) && operations.length > 0) {
const operation = operations[0]
startLine = operation.start_line || 1
content = operation.content || ''
}
} catch (error) {
console.error('Failed to parse operations JSON', error)
}
}
}
parsedResult.push({
type: 'insert_content',
path,
startLine,
content
})
lastEndOffset = endOffset
} else if (node.nodeName === 'search_and_replace') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let path: string | undefined
let operations = []
// 处理子标签
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'path' && childNode.childNodes.length > 0) {
path = childNode.childNodes[0].value
} else if (childNode.nodeName === 'operations' && childNode.childNodes.length > 0) {
try {
const operationsJson = childNode.childNodes[0].value
operations = JSON5.parse(operationsJson)
} catch (error) {
console.error('Failed to parse operations JSON', error)
}
}
}
parsedResult.push({
type: 'search_and_replace',
path,
operations,
finish: node.sourceCodeLocation.endTag !== undefined
})
lastEndOffset = endOffset
} else if (node.nodeName === 'attempt_completion') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let result: string | undefined
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'result' && childNode.childNodes.length > 0) {
result = childNode.childNodes[0].value
}
}
parsedResult.push({
type: 'attempt_completion',
result,
})
lastEndOffset = endOffset
} else if (node.nodeName === 'ask_followup_question') {
if (!node.sourceCodeLocation) {
throw new Error('sourceCodeLocation is undefined')
}
const startOffset = node.sourceCodeLocation.startOffset
const endOffset = node.sourceCodeLocation.endOffset
if (startOffset > lastEndOffset) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset, startOffset),
})
}
let question: string | undefined
for (const childNode of node.childNodes) {
if (childNode.nodeName === 'question' && childNode.childNodes.length > 0) {
question = childNode.childNodes[0].value
}
}
parsedResult.push({
type: 'ask_followup_question',
question,
})
lastEndOffset = endOffset
}
}
// handle the last part of the input
if (lastEndOffset < input.length) {
parsedResult.push({
type: 'string',
content: input.slice(lastEndOffset),
})
}
return parsedResult
} catch (error) {
console.error('Failed to parse infio block', error)
throw error
}
return parsedResult
}