| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { randomUUID } from 'crypto'; |
| import { |
| writeVarintField, writeStringField, writeMessageField, writeBytesField, |
| writeBoolField, parseFields, getField, getAllFields, |
| } from './proto.js'; |
| import { getSystemPrompts } from './runtime-config.js'; |
|
|
| |
|
|
| export const SOURCE = { |
| USER: 1, |
| SYSTEM: 2, |
| ASSISTANT: 3, |
| TOOL: 4, |
| }; |
|
|
| |
|
|
| function encodeTimestamp() { |
| const now = Date.now(); |
| const secs = Math.floor(now / 1000); |
| const nanos = (now % 1000) * 1_000_000; |
| const parts = [writeVarintField(1, secs)]; |
| if (nanos > 0) parts.push(writeVarintField(2, nanos)); |
| return Buffer.concat(parts); |
| } |
|
|
| |
|
|
| import { platform, arch } from 'os'; |
| const _os = platform() === 'darwin' ? 'macos' : platform() === 'win32' ? 'windows' : 'linux'; |
| const _hw = arch() === 'arm64' ? 'arm64' : 'x86_64'; |
| const DEFAULT_CLIENT_VERSION = process.env.WINDSURF_CLIENT_VERSION || '2.0.67'; |
|
|
| export function buildMetadata(apiKey, version = DEFAULT_CLIENT_VERSION, sessionId = null) { |
| return Buffer.concat([ |
| writeStringField(1, 'windsurf'), |
| writeStringField(2, version), |
| writeStringField(3, apiKey), |
| writeStringField(4, 'en'), |
| writeStringField(5, _os), |
| writeStringField(7, version), |
| writeStringField(8, _hw), |
| writeVarintField(9, Math.floor(Math.random() * 2**48)), |
| writeStringField(10, sessionId || randomUUID()), |
| writeStringField(12, 'windsurf'), |
| ]); |
| } |
|
|
| |
|
|
| function buildChatMessage(content, source, conversationId) { |
| const parts = [ |
| writeStringField(1, randomUUID()), |
| writeVarintField(2, source), |
| writeMessageField(3, encodeTimestamp()), |
| writeStringField(4, conversationId), |
| ]; |
|
|
| if (source === SOURCE.ASSISTANT) { |
| |
| |
| |
| |
| |
| |
| |
| const actionGeneric = writeStringField(1, content); |
| const action = writeMessageField(1, actionGeneric); |
| parts.push(writeMessageField(6, action)); |
| } else { |
| |
| const intentGeneric = writeStringField(1, content); |
| const intent = writeMessageField(1, intentGeneric); |
| parts.push(writeMessageField(5, intent)); |
| } |
|
|
| return Buffer.concat(parts); |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function buildRawGetChatMessageRequest(apiKey, messages, modelEnum, modelName, sessionId = null) { |
| const parts = []; |
| const conversationId = randomUUID(); |
|
|
| |
| |
| |
| parts.push(writeMessageField(1, buildMetadata(apiKey, undefined, sessionId))); |
|
|
| |
| |
| |
| |
| |
| |
| |
| let systemPrompt = ''; |
| for (const msg of messages) { |
| if (msg.role === 'system') { |
| systemPrompt += (systemPrompt ? '\n' : '') + |
| (typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)); |
| continue; |
| } |
|
|
| let source; |
| let text; |
| const baseText = typeof msg.content === 'string' ? msg.content |
| : Array.isArray(msg.content) ? msg.content.filter(c => c.type === 'text').map(c => c.text).join('\n') |
| : msg.content == null ? '' : JSON.stringify(msg.content); |
|
|
| switch (msg.role) { |
| case 'user': |
| source = SOURCE.USER; |
| text = baseText; |
| break; |
| case 'assistant': |
| source = SOURCE.ASSISTANT; |
| |
| |
| if (Array.isArray(msg.tool_calls) && msg.tool_calls.length) { |
| const tcLines = msg.tool_calls.map(tc => |
| `[called tool ${tc.function?.name || 'unknown'} with ${tc.function?.arguments || '{}'}]` |
| ).join('\n'); |
| text = baseText ? `${baseText}\n${tcLines}` : tcLines; |
| } else { |
| text = baseText; |
| } |
| break; |
| case 'tool': |
| |
| |
| source = SOURCE.USER; |
| text = `[tool result${msg.tool_call_id ? ` for ${msg.tool_call_id}` : ''}]: ${baseText}`; |
| break; |
| default: |
| source = SOURCE.USER; |
| text = baseText; |
| } |
|
|
| parts.push(writeMessageField(2, buildChatMessage(text, source, conversationId))); |
| } |
|
|
| |
| if (systemPrompt) { |
| parts.push(writeStringField(3, systemPrompt)); |
| } |
|
|
| |
| parts.push(writeVarintField(4, modelEnum)); |
|
|
| |
| if (modelName) { |
| parts.push(writeStringField(5, modelName)); |
| } |
|
|
| return Buffer.concat(parts); |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| export function parseRawResponse(buf) { |
| const fields = parseFields(buf); |
| const f1 = getField(fields, 1, 2); |
| if (!f1) return { text: '' }; |
|
|
| const inner = parseFields(f1.value); |
| const text = getField(inner, 5, 2); |
| const inProgress = getField(inner, 6, 0); |
| const isError = getField(inner, 7, 0); |
|
|
| return { |
| text: text ? text.value.toString('utf8') : '', |
| inProgress: inProgress ? !!inProgress.value : false, |
| isError: isError ? !!isError.value : false, |
| }; |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function buildInitializePanelStateRequest(apiKey, sessionId, trusted = true) { |
| return Buffer.concat([ |
| writeMessageField(1, buildMetadata(apiKey, undefined, sessionId)), |
| writeBoolField(3, trusted), |
| ]); |
| } |
|
|
| |
| export function buildHeartbeatRequest(apiKey, sessionId) { |
| return writeMessageField(1, buildMetadata(apiKey, undefined, sessionId)); |
| } |
|
|
| |
| export function buildAddTrackedWorkspaceRequest(workspacePath) { |
| return writeStringField(1, workspacePath); |
| } |
|
|
| |
| export function buildUpdateWorkspaceTrustRequest(apiKey, _ignored, trusted = true, sessionId) { |
| return Buffer.concat([ |
| writeMessageField(1, buildMetadata(apiKey, undefined, sessionId)), |
| writeBoolField(2, trusted), |
| ]); |
| } |
|
|
| export function buildUpdatePanelStateWithUserStatusRequest(apiKey, sessionId, userStatusBytes) { |
| const parts = [ |
| writeMessageField(1, buildMetadata(apiKey, undefined, sessionId)), |
| ]; |
| if (userStatusBytes?.length) { |
| parts.push(writeMessageField(2, userStatusBytes)); |
| } |
| return Buffer.concat(parts); |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| export function buildStartCascadeRequest(apiKey, sessionId) { |
| return Buffer.concat([ |
| writeMessageField(1, buildMetadata(apiKey, undefined, sessionId)), |
| writeVarintField(4, 1), |
| writeVarintField(5, 1), |
| ]); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function buildSendCascadeMessageRequest(apiKey, cascadeId, text, modelEnum, modelUid, sessionId, { toolPreamble, images, additionalSteps, nativeMode, nativeAllowlist } = {}) { |
| const parts = []; |
|
|
| |
| parts.push(writeStringField(1, cascadeId)); |
|
|
| |
| parts.push(writeMessageField(2, writeStringField(1, text))); |
|
|
| |
| parts.push(writeMessageField(3, buildMetadata(apiKey, undefined, sessionId))); |
|
|
| |
| |
| |
| |
| |
| |
| |
| const forceDefault = !!nativeMode || (!!images?.length && !toolPreamble); |
| |
| |
| |
| |
| |
| const cascadeConfig = buildCascadeConfig(modelEnum, modelUid, { |
| toolPreamble: toolPreamble || '', |
| forceDefault, |
| nativeMode: !!nativeMode, |
| nativeAllowlist: nativeAllowlist || null, |
| }); |
| parts.push(writeMessageField(5, cascadeConfig)); |
|
|
| |
| if (images?.length) { |
| for (const img of images) { |
| const imgMsg = Buffer.concat([ |
| writeStringField(1, img.base64_data), |
| writeStringField(2, img.mime_type || 'image/png'), |
| ]); |
| parts.push(writeMessageField(6, imgMsg)); |
| } |
| } |
|
|
| |
| |
| |
| |
| if (Array.isArray(additionalSteps) && additionalSteps.length) { |
| for (const stepBuf of additionalSteps) { |
| if (!stepBuf || !Buffer.isBuffer(stepBuf) || stepBuf.length === 0) continue; |
| parts.push(writeMessageField(9, stepBuf)); |
| } |
| } |
|
|
| return Buffer.concat(parts); |
| } |
|
|
| function buildCascadeConfig(modelEnum, modelUid, { toolPreamble, forceDefault, nativeMode, nativeAllowlist } = {}) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const mode = (nativeMode || forceDefault) ? 1 : 3; |
| const convParts = [writeVarintField(4, mode)]; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (toolPreamble) { |
| |
| |
| |
| const sp = getSystemPrompts(); |
| const reinforcement = '\n\n' + sp.toolReinforcement; |
| const fullSection = toolPreamble + reinforcement; |
| const additionalSection = Buffer.concat([ |
| writeVarintField(1, 1), |
| writeStringField(2, fullSection), |
| ]); |
| convParts.push(writeMessageField(12, additionalSection)); |
| |
| |
| |
| |
| |
| |
| if (process.env.WINDSURFAPI_DUMP_SYSTEM_PROMPT === '1') { |
| try { |
| |
| |
| import('fs').then(fs => { |
| const ts = new Date().toISOString().replace(/[:.]/g, '-'); |
| const path = `/tmp/windsurf-sp-dump-${ts}.txt`; |
| fs.writeFileSync(path, fullSection.slice(0, 4096) + '\n--- end ---\n'); |
| }).catch(() => {}); |
| } catch {} |
| } |
|
|
| |
| |
| |
| |
| |
| |
| const toolCommOverride = Buffer.concat([ |
| writeVarintField(1, 1), |
| writeStringField(2, |
| sp.communicationWithTools), |
| ]); |
| convParts.push(writeMessageField(13, toolCommOverride)); |
| } else { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| const noToolSection = Buffer.concat([ |
| writeVarintField(1, 1), |
| writeStringField(2, 'No tools are available.'), |
| ]); |
| convParts.push(writeMessageField(10, noToolSection)); |
|
|
| |
| |
| |
| |
| |
| |
| |
| const noToolAdditional = Buffer.concat([ |
| writeVarintField(1, 1), |
| writeStringField(2, |
| 'CRITICAL OPERATING CONSTRAINT — READ BEFORE ANY RESPONSE:\n' + |
| 'You are being accessed as a plain chat API. You have NO tools, NO file access, NO shell, NO code execution, NO repository awareness, NO ability to list or read anything on the user\'s machine or any sandbox. You cannot "check", "look at", "open", "view", "inspect", "run", "glob", "grep", "list", or "edit" anything.\n' + |
| '\n' + |
| 'OUTPUT RULES:\n' + |
| '1. Never narrate tool-like actions ("Let me check X", "I\'ll look at Y", "Looking at the file...", "I see in main.py...", "Based on the codebase...").\n' + |
| '2. Never reference file paths, directory structures, line numbers, or repository contents that were not explicitly pasted into the current conversation by the user.\n' + |
| '3. If the user asks about their code or project but hasn\'t pasted the relevant file content, respond: "I don\'t see that file in our conversation — please paste it and I\'ll help." Do NOT invent file contents.\n' + |
| '4. For general questions, answer directly from your training knowledge. No preambles.\n' + |
| '5. Match the user\'s language (Chinese → Chinese, English → English; never switch mid-conversation).\n' + |
| '\n' + |
| 'Violating these rules will produce broken output for the end user. Stay in chat-API mode at all times.'), |
| ]); |
| convParts.push(writeMessageField(12, noToolAdditional)); |
|
|
| |
| const spNoTools = getSystemPrompts(); |
| const communicationOverride = Buffer.concat([ |
| writeVarintField(1, 1), |
| writeStringField(2, spNoTools.communicationNoTools), |
| ]); |
| convParts.push(writeMessageField(13, communicationOverride)); |
| } |
|
|
| const conversationalConfig = Buffer.concat(convParts); |
| const plannerParts = [ |
| writeMessageField(2, conversationalConfig), |
| ]; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| if (modelUid) { |
| plannerParts.push(writeStringField(35, modelUid)); |
| plannerParts.push(writeStringField(34, modelUid)); |
| } |
| if (modelEnum && modelEnum > 0) { |
| |
| plannerParts.push(writeMessageField(15, writeVarintField(1, modelEnum))); |
| |
| plannerParts.push(writeVarintField(1, modelEnum)); |
| } |
| if (!modelUid && !modelEnum) { |
| throw new Error('buildCascadeConfig: at least one of modelUid or modelEnum must be provided'); |
| } |
|
|
| |
| |
| plannerParts.push(writeVarintField(6, 32768)); |
|
|
| |
| if (!toolPreamble) { |
| const emptySection = Buffer.concat([writeVarintField(1, 1), writeStringField(2, '')]); |
| plannerParts.push(writeMessageField(11, emptySection)); |
| } |
|
|
| |
| |
| |
| |
| if (nativeMode) { |
| plannerParts.push(writeMessageField(13, buildNativeCascadeToolConfig(nativeAllowlist))); |
| } |
|
|
| const plannerConfig = Buffer.concat(plannerParts); |
|
|
| const brainConfig = Buffer.concat([ |
| writeVarintField(1, 1), |
| writeMessageField(6, writeMessageField(6, Buffer.alloc(0))), |
| ]); |
|
|
| |
| |
| const memoryConfig = Buffer.concat([writeBoolField(1, false)]); |
|
|
| return Buffer.concat([ |
| writeMessageField(1, plannerConfig), |
| writeMessageField(5, memoryConfig), |
| writeMessageField(7, brainConfig), |
| ]); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function buildNativeCascadeToolConfig(allowlist = null) { |
| const list = Array.isArray(allowlist) && allowlist.length |
| ? allowlist |
| : ['view_file', 'run_command', 'grep_search_v2', 'find', 'list_dir']; |
| const parts = []; |
| |
| |
| if (list.includes('run_command')) { |
| parts.push(writeMessageField(8, Buffer.alloc(0))); |
| } |
| if (list.includes('view_file')) { |
| parts.push(writeMessageField(10, Buffer.alloc(0))); |
| } |
| if (list.includes('list_dir') || list.includes('list_directory')) { |
| parts.push(writeMessageField(19, Buffer.alloc(0))); |
| } |
| if (list.includes('grep_search_v2') || list.includes('grep_search')) { |
| parts.push(writeMessageField(33, Buffer.alloc(0))); |
| } |
| if (list.includes('find')) { |
| parts.push(writeMessageField(5, Buffer.alloc(0))); |
| } |
| |
| for (const name of list) { |
| parts.push(writeStringField(32, name)); |
| } |
| return Buffer.concat(parts); |
| } |
|
|
| |
| |
| |
| |
| export function buildGetTrajectoryStepsRequest(cascadeId, stepOffset = 0) { |
| const parts = [writeStringField(1, cascadeId)]; |
| if (stepOffset > 0) parts.push(writeVarintField(2, stepOffset)); |
| return Buffer.concat(parts); |
| } |
|
|
| |
| |
| |
| |
| export function buildGetTrajectoryRequest(cascadeId) { |
| return writeStringField(1, cascadeId); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function buildGetGeneratorMetadataRequest(cascadeId, offset = 0) { |
| const parts = [writeStringField(1, cascadeId)]; |
| if (offset > 0) parts.push(writeVarintField(2, offset)); |
| return Buffer.concat(parts); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function parseGeneratorMetadata(buf) { |
| const fields = parseFields(buf); |
| const metaEntries = getAllFields(fields, 1).filter(f => f.wireType === 2); |
| if (metaEntries.length === 0) return null; |
|
|
| let inputTokens = 0, outputTokens = 0, cacheReadTokens = 0, cacheWriteTokens = 0; |
| let found = false; |
|
|
| for (const entry of metaEntries) { |
| const gm = parseFields(entry.value); |
| const chatModelField = getField(gm, 1, 2); |
| if (!chatModelField) continue; |
| const cm = parseFields(chatModelField.value); |
| const usageField = getField(cm, 4, 2); |
| if (!usageField) continue; |
| const us = parseFields(usageField.value); |
| const readUint = (fn) => { |
| const f = getField(us, fn, 0); |
| return f ? Number(f.value) : 0; |
| }; |
| const inT = readUint(2); |
| const outT = readUint(3); |
| const cacheW = readUint(4); |
| const cacheR = readUint(5); |
| if (inT || outT || cacheW || cacheR) { |
| inputTokens += inT; |
| outputTokens += outT; |
| cacheWriteTokens += cacheW; |
| cacheReadTokens += cacheR; |
| found = true; |
| } |
| } |
| if (!found) return null; |
| return { |
| inputTokens, |
| outputTokens, |
| cacheReadTokens, |
| cacheWriteTokens, |
| entryCount: metaEntries.length, |
| }; |
| } |
|
|
| |
|
|
| |
| export function parseStartCascadeResponse(buf) { |
| const fields = parseFields(buf); |
| const f1 = getField(fields, 1, 2); |
| return f1 ? f1.value.toString('utf8') : ''; |
| } |
|
|
| |
| export function parseTrajectoryStatus(buf) { |
| const fields = parseFields(buf); |
| const f2 = getField(fields, 2, 0); |
| return f2 ? f2.value : 0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function parseTrajectorySteps(buf) { |
| const fields = parseFields(buf); |
| const steps = getAllFields(fields, 1).filter(f => f.wireType === 2); |
| const results = []; |
|
|
| for (const step of steps) { |
| const sf = parseFields(step.value); |
| const typeField = getField(sf, 1, 0); |
| const statusField = getField(sf, 4, 0); |
| |
| |
| const plannerField = getField(sf, 20, 2); |
|
|
| const entry = { |
| type: typeField ? typeField.value : 0, |
| status: statusField ? statusField.value : 0, |
| text: '', |
| thinking: '', |
| errorText: '', |
| toolCalls: [], |
| usage: null, |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const stepMetaField = getField(sf, 5, 2); |
| if (stepMetaField) { |
| const meta = parseFields(stepMetaField.value); |
| const usageField = getField(meta, 9, 2); |
| if (usageField) { |
| const us = parseFields(usageField.value); |
| const readUint = (fn) => { |
| const f = getField(us, fn, 0); |
| return f ? Number(f.value) : 0; |
| }; |
| const inputTokens = readUint(2); |
| const outputTokens = readUint(3); |
| const cacheWriteTokens = readUint(4); |
| const cacheReadTokens = readUint(5); |
| if (inputTokens || outputTokens || cacheReadTokens || cacheWriteTokens) { |
| entry.usage = { inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens }; |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| const parseChatToolCall = (buf) => { |
| const f = parseFields(buf); |
| const id = getField(f, 1, 2); |
| const name = getField(f, 2, 2); |
| const args = getField(f, 3, 2); |
| return { |
| id: id ? id.value.toString('utf8') : '', |
| name: name ? name.value.toString('utf8') : '', |
| argumentsJson: args ? args.value.toString('utf8') : '', |
| }; |
| }; |
| const customField = getField(sf, 45, 2); |
| if (customField) { |
| const cf = parseFields(customField.value); |
| const recipeId = getField(cf, 1, 2); |
| const argsF = getField(cf, 2, 2); |
| const outF = getField(cf, 3, 2); |
| const nameF = getField(cf, 4, 2); |
| entry.toolCalls.push({ |
| id: recipeId ? recipeId.value.toString('utf8') : '', |
| name: nameF ? nameF.value.toString('utf8') : (recipeId ? recipeId.value.toString('utf8') : 'custom_tool'), |
| argumentsJson: argsF ? argsF.value.toString('utf8') : '', |
| result: outF ? outF.value.toString('utf8') : '', |
| }); |
| } |
| const mcpField = getField(sf, 47, 2); |
| if (mcpField) { |
| const mf = parseFields(mcpField.value); |
| const serverF = getField(mf, 1, 2); |
| const callF = getField(mf, 2, 2); |
| const resultF = getField(mf, 3, 2); |
| if (callF) { |
| const tc = parseChatToolCall(callF.value); |
| tc.serverName = serverF ? serverF.value.toString('utf8') : ''; |
| tc.result = resultF ? resultF.value.toString('utf8') : ''; |
| entry.toolCalls.push(tc); |
| } |
| } |
| const proposalField = getField(sf, 49, 2); |
| if (proposalField) { |
| const pf = parseFields(proposalField.value); |
| const callF = getField(pf, 1, 2); |
| if (callF) entry.toolCalls.push(parseChatToolCall(callF.value)); |
| } |
| const choiceField = getField(sf, 50, 2); |
| if (choiceField) { |
| const cf = parseFields(choiceField.value); |
| const chosenIdx = getField(cf, 2, 0); |
| const calls = getAllFields(cf, 1).filter(x => x.wireType === 2).map(x => parseChatToolCall(x.value)); |
| if (calls.length) { |
| const idx = chosenIdx ? Number(chosenIdx.value) : 0; |
| entry.toolCalls.push(calls[idx] || calls[0]); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const NATIVE_STEP_FIELDS = [ |
| [14, 'view_file'], |
| [15, 'list_directory'], |
| [23, 'write_to_file'], |
| [28, 'run_command'], |
| [13, 'grep_search'], |
| [34, 'find'], |
| [105, 'grep_search_v2'], |
| ]; |
| for (const [fieldNum, kind] of NATIVE_STEP_FIELDS) { |
| const oneof = getField(sf, fieldNum, 2); |
| if (!oneof) continue; |
| const body = parseFields(oneof.value); |
| let argumentsJson = ''; |
| let result = ''; |
| try { |
| if (kind === 'view_file') { |
| const args = { |
| absolute_path_uri: getField(body, 1, 2)?.value?.toString('utf8') || '', |
| offset: Number(getField(body, 11, 0)?.value || 0), |
| limit: Number(getField(body, 12, 0)?.value || 0), |
| start_line: Number(getField(body, 2, 0)?.value || 0), |
| end_line: Number(getField(body, 3, 0)?.value || 0), |
| }; |
| argumentsJson = JSON.stringify(args); |
| result = getField(body, 4, 2)?.value?.toString('utf8') || ''; |
| } else if (kind === 'run_command') { |
| const args = { |
| command_line: getField(body, 23, 2)?.value?.toString('utf8') |
| || getField(body, 1, 2)?.value?.toString('utf8') || '', |
| cwd: getField(body, 2, 2)?.value?.toString('utf8') || '', |
| }; |
| argumentsJson = JSON.stringify(args); |
| |
| const combined = getField(body, 21, 2); |
| if (combined) { |
| const c = parseFields(combined.value); |
| result = getField(c, 1, 2)?.value?.toString('utf8') || ''; |
| } |
| if (!result) { |
| |
| const stdout = getField(body, 4, 2)?.value?.toString('utf8') || ''; |
| const stderr = getField(body, 5, 2)?.value?.toString('utf8') || ''; |
| result = stdout + (stderr ? `\n[stderr]\n${stderr}` : ''); |
| } |
| } else if (kind === 'grep_search_v2') { |
| const args = { |
| pattern: getField(body, 2, 2)?.value?.toString('utf8') || '', |
| path: getField(body, 3, 2)?.value?.toString('utf8') || '', |
| glob: getField(body, 4, 2)?.value?.toString('utf8') || '', |
| output_mode: getField(body, 5, 2)?.value?.toString('utf8') || '', |
| head_limit: Number(getField(body, 12, 0)?.value || 0), |
| }; |
| argumentsJson = JSON.stringify(args); |
| result = getField(body, 15, 2)?.value?.toString('utf8') || ''; |
| } else if (kind === 'grep_search') { |
| const args = { |
| query: getField(body, 1, 2)?.value?.toString('utf8') || '', |
| search_path_uri: getField(body, 11, 2)?.value?.toString('utf8') || '', |
| }; |
| argumentsJson = JSON.stringify(args); |
| result = getField(body, 3, 2)?.value?.toString('utf8') || ''; |
| } else if (kind === 'find') { |
| const args = { |
| pattern: getField(body, 1, 2)?.value?.toString('utf8') || '', |
| search_directory: getField(body, 10, 2)?.value?.toString('utf8') || '', |
| }; |
| argumentsJson = JSON.stringify(args); |
| result = getField(body, 11, 2)?.value?.toString('utf8') || ''; |
| } else if (kind === 'list_directory') { |
| const children = getAllFields(body, 2) |
| .filter(x => x.wireType === 2) |
| .map(x => x.value.toString('utf8')); |
| const args = { |
| directory_path_uri: getField(body, 1, 2)?.value?.toString('utf8') || '', |
| }; |
| argumentsJson = JSON.stringify(args); |
| result = children.join('\n'); |
| } else if (kind === 'write_to_file') { |
| const lines = getAllFields(body, 2) |
| .filter(x => x.wireType === 2) |
| .map(x => x.value.toString('utf8')); |
| const args = { |
| target_file_uri: getField(body, 1, 2)?.value?.toString('utf8') || '', |
| code_content: lines, |
| }; |
| argumentsJson = JSON.stringify(args); |
| } |
| } catch { |
| argumentsJson = argumentsJson || '{}'; |
| } |
| |
| |
| |
| |
| entry.toolCalls.push({ |
| id: `native:${kind}:${results.length}`, |
| name: kind, |
| argumentsJson, |
| result, |
| cascade_native: true, |
| }); |
| } |
|
|
| if (plannerField) { |
| const pf = parseFields(plannerField.value); |
| const textField = getField(pf, 1, 2); |
| const modifiedField = getField(pf, 8, 2); |
| const thinkField = getField(pf, 3, 2); |
| const responseText = textField ? textField.value.toString('utf8') : ''; |
| const modifiedText = modifiedField ? modifiedField.value.toString('utf8') : ''; |
| |
| |
| |
| |
| |
| entry.text = modifiedText || responseText; |
| entry.responseText = responseText; |
| entry.modifiedText = modifiedText; |
| if (thinkField) entry.thinking = thinkField.value.toString('utf8'); |
| } |
|
|
| |
| |
| const readErrorDetails = (buf) => { |
| const ed = parseFields(buf); |
| for (const fnum of [1, 2, 3]) { |
| const f = getField(ed, fnum, 2); |
| if (f) { |
| const s = f.value.toString('utf8').trim(); |
| if (s) return s.split('\n')[0].slice(0, 300); |
| } |
| } |
| return ''; |
| }; |
|
|
| |
| |
| |
| const errMsgField = getField(sf, 24, 2); |
| if (errMsgField) { |
| const inner = getField(parseFields(errMsgField.value), 3, 2); |
| if (inner) entry.errorText = readErrorDetails(inner.value); |
| } |
| if (!entry.errorText) { |
| const errField = getField(sf, 31, 2); |
| if (errField) entry.errorText = readErrorDetails(errField.value); |
| } |
|
|
|
|
| results.push(entry); |
| } |
|
|
| return results; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export function buildGetUserStatusRequest(apiKey) { |
| return writeMessageField(1, buildMetadata(apiKey)); |
| } |
|
|
| export function extractUserStatusBytes(getUserStatusResponseBuf) { |
| if (!getUserStatusResponseBuf || getUserStatusResponseBuf.length === 0) return null; |
| const top = parseFields(getUserStatusResponseBuf); |
| return getField(top, 1, 2)?.value || null; |
| } |
|
|
| |
| |
| |
| |
| export function mapTeamsTier(t) { |
| if (t === 0 || t === 6 || t === 19) return 'free'; |
| if (t > 0) return 'pro'; |
| return 'unknown'; |
| } |
|
|
| |
| export function teamsTierLabel(t) { |
| return ({ |
| 0: 'Unspecified', 1: 'Teams', 2: 'Pro', 3: 'Enterprise (SaaS)', |
| 4: 'Hybrid', 5: 'Enterprise (Self-Hosted)', 6: 'Waitlist Pro', |
| 7: 'Teams Ultimate', 8: 'Pro Ultimate', 9: 'Trial', |
| 10: 'Enterprise (Self-Serve)', 11: 'Enterprise (SaaS Pooled)', |
| 12: 'Devin Enterprise', 14: 'Devin Teams', 15: 'Devin Teams V2', |
| 16: 'Devin Pro', 17: 'Devin Max', 18: 'Max', |
| 19: 'Devin Free', 20: 'Devin Trial', |
| })[t] || `Tier ${t}`; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function parseGetUserStatusResponse(buf) { |
| const out = { |
| pro: false, |
| teamsTier: 0, |
| tierName: '', |
| email: '', |
| displayName: '', |
| teamId: '', |
| userUsedPromptCredits: 0, |
| userUsedFlowCredits: 0, |
| trialEndMs: 0, |
| maxPremiumChatMessages: 0, |
| planName: '', |
| monthlyPromptCredits: 0, |
| monthlyFlowCredits: 0, |
| hasPaidFeatures: false, |
| isTeams: false, |
| isEnterprise: false, |
| allowedModels: [], |
| }; |
|
|
| if (!buf || buf.length === 0) { |
| out.tierName = mapTeamsTier(out.teamsTier); |
| return out; |
| } |
| const top = parseFields(buf); |
| const usBuf = getField(top, 1, 2)?.value; |
| const piBuf = getField(top, 2, 2)?.value; |
|
|
| if (usBuf && usBuf.length) { |
| const us = parseFields(usBuf); |
| out.pro = (getField(us, 1, 0)?.value ?? 0) === 1; |
| out.displayName = getField(us, 3, 2)?.value?.toString('utf8') || ''; |
| out.teamId = getField(us, 5, 2)?.value?.toString('utf8') || ''; |
| out.email = getField(us, 7, 2)?.value?.toString('utf8') || ''; |
| out.teamsTier = getField(us, 10, 0)?.value ?? 0; |
| out.userUsedPromptCredits = Number(getField(us, 28, 0)?.value ?? 0); |
| out.userUsedFlowCredits = Number(getField(us, 29, 0)?.value ?? 0); |
| out.maxPremiumChatMessages = Number(getField(us, 35, 0)?.value ?? 0); |
| const tsBuf = getField(us, 34, 2)?.value; |
| if (tsBuf && tsBuf.length) { |
| const tsFields = parseFields(tsBuf); |
| const secs = Number(getField(tsFields, 1, 0)?.value ?? 0); |
| out.trialEndMs = secs * 1000; |
| } |
| } |
|
|
| if (piBuf && piBuf.length) { |
| const pi = parseFields(piBuf); |
| if (!out.teamsTier) out.teamsTier = getField(pi, 1, 0)?.value ?? 0; |
| out.planName = getField(pi, 2, 2)?.value?.toString('utf8') || ''; |
| out.monthlyPromptCredits = Number(getField(pi, 12, 0)?.value ?? 0); |
| out.monthlyFlowCredits = Number(getField(pi, 13, 0)?.value ?? 0); |
| out.isEnterprise = (getField(pi, 16, 0)?.value ?? 0) === 1; |
| out.isTeams = (getField(pi, 17, 0)?.value ?? 0) === 1; |
| out.hasPaidFeatures = (getField(pi, 32, 0)?.value ?? 0) === 1; |
|
|
| |
| for (const entry of getAllFields(pi, 21)) { |
| if (entry.wireType !== 2) continue; |
| const ac = parseFields(entry.value); |
| const moaBuf = getField(ac, 1, 2)?.value; |
| |
| const cmField = getField(ac, 2, 5); |
| let multiplier = 1.0; |
| if (cmField && cmField.value.length === 4) { |
| multiplier = cmField.value.readFloatLE(0); |
| } |
| let modelEnum = 0; |
| let alias = 0; |
| if (moaBuf && moaBuf.length) { |
| const moa = parseFields(moaBuf); |
| modelEnum = getField(moa, 1, 0)?.value ?? 0; |
| alias = getField(moa, 2, 0)?.value ?? 0; |
| } |
| out.allowedModels.push({ modelEnum, alias, multiplier }); |
| } |
| } |
|
|
| out.tierName = mapTeamsTier(out.teamsTier); |
| return out; |
| } |
|
|