| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { |
| writeStringField, writeMessageField, writeVarintField, writeBoolField, writeBytesField, |
| parseFields, getField, getAllFields, |
| } from './proto.js'; |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| export const CASCADE_STEP = { |
| |
| view_file: { typeEnum: 14, oneofField: 14 }, |
| list_directory: { typeEnum: 15, oneofField: 15 }, |
| write_to_file: { typeEnum: 23, oneofField: 23 }, |
| run_command: { typeEnum: 28, oneofField: 28 }, |
| propose_code: { typeEnum: 32, oneofField: 32 }, |
| find: { typeEnum: 34, oneofField: 34 }, |
| read_url_content:{ typeEnum: 40, oneofField: 40 }, |
| grep_search: { typeEnum: 13, oneofField: 13 }, |
| grep_search_v2: { typeEnum: 105, oneofField: 105 }, |
| |
| search_web: { typeEnum: 42, oneofField: 42 }, |
| }; |
|
|
| |
| |
| |
| export const CASCADE_STEP_STATUS_DONE = 3; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| function safeJsonParse(s) { |
| if (typeof s !== 'string' || !s) return {}; |
| try { const v = JSON.parse(s); return v && typeof v === 'object' ? v : {}; } |
| catch { return {}; } |
| } |
|
|
| function buildFileUri(absolutePath) { |
| if (typeof absolutePath !== 'string' || !absolutePath) return ''; |
| |
| |
| |
| if (/^file:\/\//.test(absolutePath)) return absolutePath; |
| if (/^[a-zA-Z]:[\\/]/.test(absolutePath) || absolutePath.startsWith('/')) { |
| return `file://${absolutePath.replace(/\\/g, '/')}`; |
| } |
| |
| |
| return absolutePath; |
| } |
|
|
| function stripFileUri(uri) { |
| if (typeof uri !== 'string') return ''; |
| return uri.replace(/^file:\/\//, ''); |
| } |
|
|
| |
| function forwardReadArgs(args) { |
| const file_path = args.file_path || args.path || args.absolute_path || ''; |
| const offset = Number(args.offset) || 0; |
| const limit = Number(args.limit) || 0; |
| return { |
| absolute_path_uri: buildFileUri(file_path), |
| offset, |
| limit, |
| }; |
| } |
| function reverseReadArgs(cascade) { |
| return { |
| file_path: stripFileUri(cascade.absolute_path_uri || ''), |
| ...(cascade.offset ? { offset: cascade.offset } : {}), |
| ...(cascade.limit ? { limit: cascade.limit } : {}), |
| }; |
| } |
|
|
| |
| function forwardBashArgs(args) { |
| const command = args.command || args.shell_command || ''; |
| return { |
| command_line: String(command), |
| cwd: typeof args.cwd === 'string' ? args.cwd : '', |
| blocking: true, |
| }; |
| } |
| function reverseBashArgs(cascade) { |
| return { |
| command: cascade.command_line || cascade.proposed_command_line || '', |
| ...(cascade.cwd ? { cwd: cascade.cwd } : {}), |
| }; |
| } |
|
|
| |
| function forwardGlobArgs(args) { |
| return { |
| pattern: args.pattern || '', |
| search_directory: args.path || args.cwd || '', |
| }; |
| } |
| function reverseGlobArgs(cascade) { |
| return { |
| pattern: cascade.pattern || '', |
| ...(cascade.search_directory ? { path: cascade.search_directory } : {}), |
| }; |
| } |
|
|
| |
| function forwardGrepArgs(args) { |
| return { |
| pattern: args.pattern || '', |
| path: args.path || '', |
| glob: args.glob || '', |
| output_mode: args.output_mode || 'files_with_matches', |
| case_insensitive: !!args['-i'], |
| multiline: !!args.multiline, |
| type: args.type || '', |
| head_limit: Number(args.head_limit) || 0, |
| lines_after: Number(args['-A']) || 0, |
| lines_before: Number(args['-B']) || 0, |
| lines_both: Number(args['-C'] ?? args.context) || 0, |
| }; |
| } |
| function reverseGrepArgs(cascade) { |
| const out = { pattern: cascade.pattern || '' }; |
| if (cascade.path) out.path = cascade.path; |
| if (cascade.glob) out.glob = cascade.glob; |
| if (cascade.output_mode) out.output_mode = cascade.output_mode; |
| if (cascade.case_insensitive) out['-i'] = true; |
| if (cascade.multiline) out.multiline = true; |
| if (cascade.type) out.type = cascade.type; |
| if (cascade.head_limit) out.head_limit = cascade.head_limit; |
| if (cascade.lines_after) out['-A'] = cascade.lines_after; |
| if (cascade.lines_before) out['-B'] = cascade.lines_before; |
| if (cascade.lines_both) out['-C'] = cascade.lines_both; |
| return out; |
| } |
|
|
| |
| function forwardWriteArgs(args) { |
| const file_path = args.file_path || args.path || ''; |
| const content = args.content || ''; |
| return { |
| target_file_uri: buildFileUri(file_path), |
| code_content: typeof content === 'string' ? [content] : Array.isArray(content) ? content : [String(content)], |
| }; |
| } |
| function reverseWriteArgs(cascade) { |
| const lines = Array.isArray(cascade.code_content) ? cascade.code_content : []; |
| return { |
| file_path: stripFileUri(cascade.target_file_uri || ''), |
| content: lines.join(''), |
| }; |
| } |
|
|
| |
| function forwardListDirArgs(args) { |
| return { |
| directory_path_uri: buildFileUri(args.path || args.directory_path || args.cwd || ''), |
| }; |
| } |
| function reverseListDirArgs(cascade) { |
| return { |
| path: stripFileUri(cascade.directory_path_uri || ''), |
| }; |
| } |
|
|
| |
| function identityArgs(x) { return { ...x }; } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function forwardClaudeEditArgs(args) { |
| const file_path = args.file_path || args.path || ''; |
| const chunks = []; |
| if (Array.isArray(args.edits)) { |
| for (const e of args.edits) { |
| chunks.push({ |
| target: typeof e?.old_string === 'string' ? e.old_string : '', |
| replacement: typeof e?.new_string === 'string' ? e.new_string : '', |
| allow_multiple: !!e?.replace_all, |
| }); |
| } |
| } else { |
| chunks.push({ |
| target: typeof args.old_string === 'string' ? args.old_string : '', |
| replacement: typeof args.new_string === 'string' ? args.new_string : '', |
| allow_multiple: !!args.replace_all, |
| }); |
| } |
| return { target_file_uri: buildFileUri(file_path), replacement_chunks: chunks, instruction: '' }; |
| } |
| function reverseClaudeEditArgs(cascade) { |
| if (cascade && typeof cascade.__raw_edit === 'string') return safeJsonParse(cascade.__raw_edit); |
| const file_path = stripFileUri(cascade.target_file_uri || ''); |
| const chunks = Array.isArray(cascade.replacement_chunks) ? cascade.replacement_chunks : []; |
| if (chunks.length <= 1) { |
| const c = chunks[0] || {}; |
| return { |
| file_path, |
| old_string: c.target || '', |
| new_string: c.replacement || '', |
| ...(c.allow_multiple ? { replace_all: true } : {}), |
| }; |
| } |
| return { |
| file_path, |
| edits: chunks.map(c => ({ |
| old_string: c.target || '', |
| new_string: c.replacement || '', |
| ...(c.allow_multiple ? { replace_all: true } : {}), |
| })), |
| }; |
| } |
|
|
| |
| |
| |
| |
| function forwardWebSearchArgs(args) { |
| return { |
| query: args.query || args.q || '', |
| domain: Array.isArray(args.domains) && args.domains.length ? args.domains[0] : (args.domain || ''), |
| }; |
| } |
| function reverseWebSearchArgs(cascade) { |
| return { |
| query: cascade.query || '', |
| ...(cascade.domain ? { domains: [cascade.domain] } : {}), |
| }; |
| } |
|
|
| |
| |
| function forwardWebFetchArgs(args) { |
| return { url: args.url || args.uri || args.link || '' }; |
| } |
| function reverseWebFetchArgs(cascade) { |
| return { url: cascade.url || '', summary: cascade.summary || '' }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function forwardApplyPatchArgs(args) { |
| |
| |
| return { __apply_patch_unmappable: true, raw: typeof args === 'string' ? args : (args.input || JSON.stringify(args || {})) }; |
| } |
| function reverseApplyPatchArgs(cascade) { |
| if (cascade && typeof cascade.raw === 'string') return { input: cascade.raw }; |
| return cascade || {}; |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| export const TOOL_MAP = { |
| |
| Read: { kind: 'view_file', forward: forwardReadArgs, reverse: reverseReadArgs }, |
| Bash: { kind: 'run_command', forward: forwardBashArgs, reverse: reverseBashArgs }, |
| Glob: { kind: 'find', forward: forwardGlobArgs, reverse: reverseGlobArgs }, |
| Grep: { kind: 'grep_search_v2', forward: forwardGrepArgs, reverse: reverseGrepArgs }, |
| Write: { kind: 'write_to_file', forward: forwardWriteArgs, reverse: reverseWriteArgs }, |
| |
| |
| |
| |
| Edit: { kind: 'propose_code', forward: forwardClaudeEditArgs, reverse: reverseClaudeEditArgs }, |
| MultiEdit: { kind: 'propose_code', forward: forwardClaudeEditArgs, reverse: reverseClaudeEditArgs }, |
| WebSearch: { kind: 'search_web', forward: forwardWebSearchArgs, reverse: reverseWebSearchArgs }, |
| |
| ToolSearch: { kind: 'search_web', forward: forwardWebSearchArgs, reverse: reverseWebSearchArgs }, |
| |
| WebFetch: { kind: 'read_url_content', forward: forwardWebFetchArgs, reverse: reverseWebFetchArgs }, |
|
|
| |
| view_file: { kind: 'view_file', forward: identityArgs, reverse: identityArgs }, |
| run_command: { kind: 'run_command', forward: forwardRunCommandPassThrough, reverse: reverseRunCommandPassThrough }, |
| grep_search: { kind: 'grep_search_v2', forward: identityArgs, reverse: identityArgs }, |
| grep_search_v2: { kind: 'grep_search_v2', forward: identityArgs, reverse: identityArgs }, |
| find: { kind: 'find', forward: identityArgs, reverse: identityArgs }, |
| list_dir: { kind: 'list_directory', forward: forwardListDirArgs, reverse: reverseListDirArgs }, |
| list_directory: { kind: 'list_directory', forward: forwardListDirArgs, reverse: reverseListDirArgs }, |
| write_to_file: { kind: 'write_to_file', forward: identityArgs, reverse: identityArgs }, |
|
|
| |
| read_file: { kind: 'view_file', forward: forwardReadArgs, reverse: reverseReadArgs }, |
| shell: { kind: 'run_command', forward: forwardBashArgs, reverse: reverseBashArgs }, |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| shell_command: { kind: 'run_command', forward: forwardCodexShellArgs, reverse: reverseCodexShellArgs }, |
| |
| |
| |
| web_search: { kind: 'search_web', forward: forwardWebSearchArgs, reverse: reverseWebSearchArgs }, |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| function forwardEditArgs(args) { |
| return { __raw_edit: JSON.stringify(args || {}) }; |
| } |
| function reverseEditArgs(cascade) { |
| if (cascade && typeof cascade.__raw_edit === 'string') return safeJsonParse(cascade.__raw_edit); |
| return cascade || {}; |
| } |
|
|
| |
| |
| function forwardRunCommandPassThrough(args) { |
| return { |
| command_line: args.command_line || args.command || '', |
| cwd: args.cwd || '', |
| blocking: true, |
| }; |
| } |
| function reverseRunCommandPassThrough(cascade) { |
| return { |
| command_line: cascade.command_line || cascade.proposed_command_line || '', |
| ...(cascade.cwd ? { cwd: cascade.cwd } : {}), |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function forwardCodexShellArgs(args) { |
| return { |
| command_line: args.command || args.command_line || '', |
| cwd: args.workdir || args.cwd || '', |
| blocking: true, |
| }; |
| } |
| function reverseCodexShellArgs(cascade) { |
| return { |
| command: cascade.command_line || cascade.proposed_command_line || '', |
| ...(cascade.cwd ? { workdir: cascade.cwd } : {}), |
| }; |
| } |
|
|
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function canMapAllTools(tools) { |
| if (!Array.isArray(tools) || tools.length === 0) return false; |
| for (const t of tools) { |
| if (t?.type !== 'function') return false; |
| const name = t.function?.name; |
| if (!name || !TOOL_MAP[name]) return false; |
| } |
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function partitionTools(tools) { |
| const mapped = []; |
| const unmapped = []; |
| if (Array.isArray(tools)) { |
| for (const t of tools) { |
| if (t?.type !== 'function' || !t.function?.name) continue; |
| if (TOOL_MAP[t.function.name]) mapped.push(t); |
| else unmapped.push(t); |
| } |
| } |
| return { mapped, unmapped, hasAny: mapped.length > 0 }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function buildReverseLookup(callerTools) { |
| const out = new Map(); |
| if (!Array.isArray(callerTools)) return out; |
| for (const t of callerTools) { |
| if (t?.type !== 'function' || !t.function?.name) continue; |
| const name = t.function.name; |
| const entry = TOOL_MAP[name]; |
| if (!entry) continue; |
| if (!out.has(entry.kind)) out.set(entry.kind, []); |
| out.get(entry.kind).push(name); |
| } |
| return out; |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| function buildViewFileBody(args) { |
| const parts = []; |
| if (args.absolute_path_uri) parts.push(writeStringField(1, args.absolute_path_uri)); |
| if (args.offset) parts.push(writeVarintField(11, args.offset)); |
| if (args.limit) parts.push(writeVarintField(12, args.limit)); |
| if (typeof args.content === 'string') parts.push(writeStringField(4, args.content)); |
| return Buffer.concat(parts); |
| } |
|
|
| function buildRunCommandBody(args) { |
| const parts = []; |
| if (args.command_line) parts.push(writeStringField(23, args.command_line)); |
| if (args.cwd) parts.push(writeStringField(2, args.cwd)); |
| if (args.blocking) parts.push(writeBoolField(11, true)); |
| if (typeof args.stdout === 'string') parts.push(writeStringField(4, args.stdout)); |
| if (typeof args.stderr === 'string') parts.push(writeStringField(5, args.stderr)); |
| if (Number.isFinite(args.exit_code)) parts.push(writeVarintField(6, args.exit_code)); |
| |
| |
| |
| if (typeof args.full_output === 'string') { |
| const inner = writeStringField(1, args.full_output); |
| parts.push(writeMessageField(21, inner)); |
| } |
| return Buffer.concat(parts); |
| } |
|
|
| function buildGrepSearchV2Body(args) { |
| const parts = []; |
| if (args.pattern) parts.push(writeStringField(2, args.pattern)); |
| if (args.path) parts.push(writeStringField(3, args.path)); |
| if (args.glob) parts.push(writeStringField(4, args.glob)); |
| if (args.output_mode) parts.push(writeStringField(5, args.output_mode)); |
| if (args.case_insensitive) parts.push(writeBoolField(10, true)); |
| if (args.multiline) parts.push(writeBoolField(13, true)); |
| if (args.type) parts.push(writeStringField(11, args.type)); |
| if (args.head_limit) parts.push(writeVarintField(12, args.head_limit)); |
| if (args.lines_after) parts.push(writeVarintField(6, args.lines_after)); |
| if (args.lines_before) parts.push(writeVarintField(7, args.lines_before)); |
| if (args.lines_both) parts.push(writeVarintField(8, args.lines_both)); |
| if (typeof args.raw_output === 'string') parts.push(writeStringField(15, args.raw_output)); |
| return Buffer.concat(parts); |
| } |
|
|
| function buildFindBody(args) { |
| const parts = []; |
| if (args.search_directory) parts.push(writeStringField(10, args.search_directory)); |
| if (args.pattern) parts.push(writeStringField(1, args.pattern)); |
| if (typeof args.raw_output === 'string') parts.push(writeStringField(11, args.raw_output)); |
| return Buffer.concat(parts); |
| } |
|
|
| function buildListDirectoryBody(args) { |
| const parts = []; |
| if (args.directory_path_uri) parts.push(writeStringField(1, args.directory_path_uri)); |
| |
| |
| if (Array.isArray(args.children)) { |
| for (const c of args.children) parts.push(writeStringField(2, String(c))); |
| } |
| return Buffer.concat(parts); |
| } |
|
|
| function buildWriteToFileBody(args) { |
| const parts = []; |
| if (args.target_file_uri) parts.push(writeStringField(1, args.target_file_uri)); |
| if (Array.isArray(args.code_content)) { |
| for (const line of args.code_content) parts.push(writeStringField(2, String(line))); |
| } |
| if (args.file_created) parts.push(writeBoolField(4, true)); |
| return Buffer.concat(parts); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function buildProposeCodeBody(args) { |
| const parts = []; |
| |
| const cmdParts = []; |
| if (args.instruction) cmdParts.push(writeStringField(1, args.instruction)); |
| cmdParts.push(writeBoolField(2, true)); |
| if (Array.isArray(args.replacement_chunks)) { |
| for (const c of args.replacement_chunks) { |
| const chunkParts = []; |
| if (typeof c.target === 'string') chunkParts.push(writeStringField(1, c.target)); |
| if (typeof c.replacement === 'string') chunkParts.push(writeStringField(2, c.replacement)); |
| if (c.allow_multiple) chunkParts.push(writeBoolField(3, true)); |
| cmdParts.push(writeMessageField(9, Buffer.concat(chunkParts))); |
| } |
| } |
| if (args.target_file_uri) { |
| |
| const psi = writeStringField(5, args.target_file_uri); |
| cmdParts.push(writeMessageField(4, psi)); |
| } |
| const command = Buffer.concat(cmdParts); |
| |
| const actionSpec = writeMessageField(1, command); |
| parts.push(writeMessageField(1, actionSpec)); |
| if (args.instruction) parts.push(writeStringField(3, args.instruction)); |
| return Buffer.concat(parts); |
| } |
|
|
| |
| |
| |
| |
| function buildSearchWebBody(args) { |
| const parts = []; |
| if (args.query) parts.push(writeStringField(1, args.query)); |
| if (args.domain) parts.push(writeStringField(3, args.domain)); |
| if (typeof args.summary === 'string') parts.push(writeStringField(5, args.summary)); |
| return Buffer.concat(parts); |
| } |
|
|
| |
| |
| |
| function buildReadUrlContentBody(args) { |
| const parts = []; |
| if (args.url) parts.push(writeStringField(1, args.url)); |
| if (typeof args.summary === 'string') parts.push(writeStringField(5, args.summary)); |
| return Buffer.concat(parts); |
| } |
|
|
| const STEP_BODY_BUILDER = { |
| view_file: buildViewFileBody, |
| run_command: buildRunCommandBody, |
| grep_search_v2: buildGrepSearchV2Body, |
| grep_search: buildGrepSearchV2Body, |
| find: buildFindBody, |
| list_directory: buildListDirectoryBody, |
| write_to_file: buildWriteToFileBody, |
| propose_code: buildProposeCodeBody, |
| search_web: buildSearchWebBody, |
| read_url_content: buildReadUrlContentBody, |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function buildAdditionalStep(kind, args) { |
| const meta = CASCADE_STEP[kind]; |
| const builder = STEP_BODY_BUILDER[kind]; |
| if (!meta || !builder) return null; |
| const body = builder(args || {}); |
| |
| return Buffer.concat([ |
| writeVarintField(1, meta.typeEnum), |
| writeVarintField(4, CASCADE_STEP_STATUS_DONE), |
| writeMessageField(meta.oneofField, body), |
| ]); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| function decodeViewFileStep(buf) { |
| const f = parseFields(buf); |
| return { |
| absolute_path_uri: getField(f, 1, 2)?.value?.toString('utf8') || '', |
| start_line: Number(getField(f, 2, 0)?.value || 0), |
| end_line: Number(getField(f, 3, 0)?.value || 0), |
| offset: Number(getField(f, 11, 0)?.value || 0), |
| limit: Number(getField(f, 12, 0)?.value || 0), |
| content: getField(f, 4, 2)?.value?.toString('utf8') || '', |
| raw_content: getField(f, 9, 2)?.value?.toString('utf8') || '', |
| }; |
| } |
|
|
| function decodeRunCommandStep(buf) { |
| const f = parseFields(buf); |
| const decoded = { |
| command_line: getField(f, 23, 2)?.value?.toString('utf8') |
| || getField(f, 1, 2)?.value?.toString('utf8') || '', |
| proposed_command_line: getField(f, 25, 2)?.value?.toString('utf8') || '', |
| cwd: getField(f, 2, 2)?.value?.toString('utf8') || '', |
| stdout: getField(f, 4, 2)?.value?.toString('utf8') || '', |
| stderr: getField(f, 5, 2)?.value?.toString('utf8') || '', |
| exit_code: getField(f, 6, 0)?.value, |
| }; |
| |
| const combined = getField(f, 21, 2); |
| if (combined) { |
| const c = parseFields(combined.value); |
| const full = getField(c, 1, 2)?.value?.toString('utf8') || ''; |
| if (full) decoded.full_output = full; |
| } |
| return decoded; |
| } |
|
|
| function decodeGrepSearchV2Step(buf) { |
| const f = parseFields(buf); |
| return { |
| pattern: getField(f, 2, 2)?.value?.toString('utf8') || '', |
| path: getField(f, 3, 2)?.value?.toString('utf8') || '', |
| glob: getField(f, 4, 2)?.value?.toString('utf8') || '', |
| output_mode: getField(f, 5, 2)?.value?.toString('utf8') || '', |
| lines_after: Number(getField(f, 6, 0)?.value || 0), |
| lines_before: Number(getField(f, 7, 0)?.value || 0), |
| lines_both: Number(getField(f, 8, 0)?.value || 0), |
| case_insensitive: !!getField(f, 10, 0)?.value, |
| multiline: !!getField(f, 13, 0)?.value, |
| type: getField(f, 11, 2)?.value?.toString('utf8') || '', |
| head_limit: Number(getField(f, 12, 0)?.value || 0), |
| raw_output: getField(f, 15, 2)?.value?.toString('utf8') || '', |
| }; |
| } |
|
|
| function decodeFindStep(buf) { |
| const f = parseFields(buf); |
| return { |
| search_directory: getField(f, 10, 2)?.value?.toString('utf8') || '', |
| pattern: getField(f, 1, 2)?.value?.toString('utf8') || '', |
| raw_output: getField(f, 11, 2)?.value?.toString('utf8') || '', |
| }; |
| } |
|
|
| function decodeListDirectoryStep(buf) { |
| const f = parseFields(buf); |
| return { |
| directory_path_uri: getField(f, 1, 2)?.value?.toString('utf8') || '', |
| children: getAllFields(f, 2) |
| .filter(x => x.wireType === 2) |
| .map(x => x.value.toString('utf8')), |
| }; |
| } |
|
|
| function decodeWriteToFileStep(buf) { |
| const f = parseFields(buf); |
| return { |
| target_file_uri: getField(f, 1, 2)?.value?.toString('utf8') || '', |
| code_content: getAllFields(f, 2) |
| .filter(x => x.wireType === 2) |
| .map(x => x.value.toString('utf8')), |
| file_created: !!getField(f, 4, 0)?.value, |
| }; |
| } |
|
|
| |
| |
| function decodeProposeCodeStep(buf) { |
| const f = parseFields(buf); |
| const actionSpec = getField(f, 1, 2); |
| const out = { target_file_uri: '', replacement_chunks: [], instruction: '' }; |
| if (!actionSpec) return out; |
| const asFields = parseFields(actionSpec.value); |
| const command = getField(asFields, 1, 2); |
| if (!command) return out; |
| const cmdFields = parseFields(command.value); |
| const instr = getField(cmdFields, 1, 2); |
| if (instr) out.instruction = instr.value.toString('utf8'); |
| const fileTarget = getField(cmdFields, 4, 2); |
| if (fileTarget) { |
| const psi = parseFields(fileTarget.value); |
| const uri = getField(psi, 5, 2)?.value?.toString('utf8') |
| || getField(psi, 1, 2)?.value?.toString('utf8') || ''; |
| out.target_file_uri = uri; |
| } |
| for (const chunkField of getAllFields(cmdFields, 9)) { |
| if (chunkField.wireType !== 2) continue; |
| const cp = parseFields(chunkField.value); |
| out.replacement_chunks.push({ |
| target: getField(cp, 1, 2)?.value?.toString('utf8') || '', |
| replacement: getField(cp, 2, 2)?.value?.toString('utf8') || '', |
| allow_multiple: !!getField(cp, 3, 0)?.value, |
| }); |
| } |
| return out; |
| } |
|
|
| function decodeSearchWebStep(buf) { |
| const f = parseFields(buf); |
| return { |
| query: getField(f, 1, 2)?.value?.toString('utf8') || '', |
| domain: getField(f, 3, 2)?.value?.toString('utf8') || '', |
| summary: getField(f, 5, 2)?.value?.toString('utf8') || '', |
| }; |
| } |
|
|
| function decodeReadUrlContentStep(buf) { |
| const f = parseFields(buf); |
| return { |
| url: getField(f, 1, 2)?.value?.toString('utf8') || '', |
| summary: getField(f, 5, 2)?.value?.toString('utf8') || '', |
| }; |
| } |
|
|
| const STEP_BODY_DECODER = { |
| view_file: decodeViewFileStep, |
| run_command: decodeRunCommandStep, |
| grep_search_v2: decodeGrepSearchV2Step, |
| grep_search: decodeGrepSearchV2Step, |
| find: decodeFindStep, |
| list_directory: decodeListDirectoryStep, |
| write_to_file: decodeWriteToFileStep, |
| propose_code: decodeProposeCodeStep, |
| search_web: decodeSearchWebStep, |
| read_url_content: decodeReadUrlContentStep, |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function decodeCascadeStepToToolCall(stepFields, kind, callerLookup) { |
| const decoder = STEP_BODY_DECODER[kind]; |
| const meta = CASCADE_STEP[kind]; |
| if (!decoder || !meta) return null; |
| const oneof = getField(stepFields, meta.oneofField, 2); |
| if (!oneof) return null; |
| const decoded = decoder(oneof.value); |
| const candidates = callerLookup?.get(kind) || []; |
| |
| |
| |
| |
| const callerName = candidates[0] || kind; |
| const reverseFn = TOOL_MAP[callerName]?.reverse || identityArgs; |
| let args; |
| try { |
| args = reverseFn(decoded); |
| } catch { |
| args = decoded; |
| } |
| return { |
| name: callerName, |
| arguments: args, |
| cascade_kind: kind, |
| observation: decoded, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function buildAdditionalStepsFromHistory(messages) { |
| if (!Array.isArray(messages) || !messages.length) return []; |
| const out = []; |
|
|
| |
| const toolResultById = new Map(); |
| for (const m of messages) { |
| if (m?.role !== 'tool' || !m.tool_call_id) continue; |
| const content = typeof m.content === 'string' |
| ? m.content |
| : (Array.isArray(m.content) |
| ? m.content.filter(p => typeof p?.text === 'string').map(p => p.text).join('\n') |
| : JSON.stringify(m.content ?? '')); |
| toolResultById.set(m.tool_call_id, content); |
| } |
|
|
| for (const m of messages) { |
| if (m?.role !== 'assistant' || !Array.isArray(m.tool_calls)) continue; |
| for (const tc of m.tool_calls) { |
| const name = tc.function?.name; |
| const entry = TOOL_MAP[name]; |
| if (!entry) continue; |
| const args = safeJsonParse(tc.function?.arguments); |
| let cascadeArgs; |
| try { cascadeArgs = entry.forward(args); } catch { continue; } |
| const observation = toolResultById.get(tc.id); |
| |
| |
| |
| |
| if (cascadeArgs?.__apply_patch_unmappable) continue; |
| if (typeof observation === 'string') { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (entry.kind === 'view_file') cascadeArgs.content = observation; |
| else if (entry.kind === 'run_command') { |
| cascadeArgs.full_output = observation; |
| cascadeArgs.stdout = observation; |
| cascadeArgs.exit_code = 0; |
| } else if (entry.kind === 'grep_search_v2' || entry.kind === 'grep_search') { |
| cascadeArgs.raw_output = observation; |
| } else if (entry.kind === 'find') { |
| cascadeArgs.raw_output = observation; |
| } else if (entry.kind === 'list_directory') { |
| cascadeArgs.children = observation |
| .split(/\r?\n/) |
| .map(s => s.trim()) |
| .filter(Boolean); |
| } else if (entry.kind === 'search_web') { |
| cascadeArgs.summary = observation; |
| } else if (entry.kind === 'read_url_content') { |
| cascadeArgs.summary = observation; |
| } |
| } |
| const buf = buildAdditionalStep(entry.kind, cascadeArgs); |
| if (buf) out.push(buf); |
| } |
| } |
| return out; |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function shouldUseNativeBridge(tools, { modelKey = '', provider = '', route = '' } = {}) { |
| if (process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_OFF === '1') return false; |
| const explicitOn = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE === '1'; |
| const part = partitionTools(tools); |
| if (!part.hasAny) return false; |
| return explicitOn; |
| } |
|
|