-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathdevinProvider.js
More file actions
139 lines (117 loc) · 4.53 KB
/
Copy pathdevinProvider.js
File metadata and controls
139 lines (117 loc) · 4.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
const { spawn } = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
class DevinProvider {
constructor(options) {
this.providerId = options.id || 'devin-custom';
this.config = options.config || {};
}
id() {
return this.providerId;
}
async callApi(prompt, context, options) {
const model = this.config.model || 'SWE-1.6';
let isGraderMode = false;
try {
const parsed = JSON.parse(prompt);
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0].role) {
isGraderMode = true;
}
} catch (e) {
// Not JSON, provider mode
}
let fullPrompt;
if (isGraderMode) {
let systemMsg, userMsg;
try {
const messages = JSON.parse(prompt);
const systemMessage = messages.find(m => m.role === 'system');
const userMessage = messages.find(m => m.role === 'user');
if (systemMessage && userMessage) {
systemMsg = systemMessage.content;
userMsg = userMessage.content;
} else {
throw new Error('Missing system or user message');
}
} catch (e) {
systemMsg = 'You are an evaluator. Respond with only valid JSON: {"pass": bool, "score": 0.0-1.0, "reason": "string"}';
userMsg = prompt;
}
fullPrompt = `${systemMsg}\n\n${userMsg}`;
} else {
fullPrompt = prompt;
}
const tmpFile = path.join(os.tmpdir(), `devin-prompt-${Date.now()}-${process.pid}.txt`);
const exportFile = path.join(os.tmpdir(), `devin-export-${Date.now()}-${process.pid}.json`);
try {
fs.writeFileSync(tmpFile, fullPrompt, 'utf8');
const args = ['-p', '--prompt-file', tmpFile, '--model', model, '--export', exportFile];
if (!isGraderMode) {
args.push('--permission-mode', 'auto');
}
const result = await new Promise((resolve, reject) => {
const child = spawn('devin', args, {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => { stdout += data; });
child.stderr.on('data', (data) => { stderr += data; });
child.on('error', (err) => {
resolve({ error: err.message, tokenUsage: parseTokenUsage(exportFile) });
});
child.on('close', (code) => {
if (code !== 0) {
resolve({ error: stdout || stderr || `devin exited with code ${code}`, tokenUsage: parseTokenUsage(exportFile) });
} else {
resolve({ output: stdout, tokenUsage: parseTokenUsage(exportFile) });
}
});
});
return result;
} finally {
try {
fs.unlinkSync(tmpFile);
fs.unlinkSync(exportFile);
} catch (e) {
// Ignore cleanup errors
}
}
}
}
function parseTokenUsage(exportFilePath) {
try {
const data = JSON.parse(fs.readFileSync(exportFilePath, 'utf8'));
// Devin exports ATIF v1.7 transcripts. Token metrics live in step.metrics
// (standard ATIF) with prompt_tokens/completion_tokens, but older exports
// put them in step.metadata.metrics as input_tokens/output_tokens.
const allSteps = (data.steps || []);
const candidateSteps = allSteps.filter(s => s.source === 'agent' || s.source === 'model' || s.source === 'assistant');
const steps = candidateSteps.length > 0 ? candidateSteps : allSteps.filter(s => !s.metadata?.is_user_input);
let promptTokens = 0, completionTokens = 0, cachedTokens = 0, cacheCreationTokens = 0;
for (const step of steps) {
const m = step.metrics || step.metadata?.metrics || {};
if (m && Object.keys(m).length > 0) {
promptTokens += (m.prompt_tokens ?? m.input_tokens ?? 0) + (m.cache_read_input_tokens ?? m.cache_read_tokens ?? 0);
completionTokens += (m.completion_tokens ?? m.output_tokens ?? 0);
cachedTokens += (m.cached_tokens ?? m.cache_read_input_tokens ?? m.cache_read_tokens ?? 0);
cacheCreationTokens += (m.cache_creation_input_tokens ?? m.cache_creation_tokens ?? 0);
}
}
// Legacy fallback: include legacy metadata.metrics cache creation in prompt
// if no ATIF cache_creation_input_tokens was present.
promptTokens += cacheCreationTokens;
return {
total: promptTokens + completionTokens,
prompt: promptTokens,
completion: completionTokens,
cached: cachedTokens,
numRequests: 1,
};
} catch {
return { total: 0, prompt: 0, completion: 0, cached: 0, numRequests: 1 };
}
}
module.exports = DevinProvider;