devin-oauth-opencode 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/bin/setup.js +199 -0
- package/dist/auth.d.ts +17 -0
- package/dist/auth.js +88 -0
- package/dist/browser-auth.d.ts +34 -0
- package/dist/browser-auth.js +126 -0
- package/dist/cloud.d.ts +21 -0
- package/dist/cloud.js +332 -0
- package/dist/devin.d.ts +34 -0
- package/dist/devin.js +9 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +108 -0
- package/dist/model-discovery.d.ts +37 -0
- package/dist/model-discovery.js +340 -0
- package/dist/models.d.ts +27 -0
- package/dist/models.js +150 -0
- package/dist/proxy.d.ts +10 -0
- package/dist/proxy.js +405 -0
- package/package.json +64 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { estimateDevinModelCost, getFallbackDevinModels } from "./models";
|
|
3
|
+
// Required picker models. They must remain visible even if live discovery
|
|
4
|
+
// omits them or returns a partial list.
|
|
5
|
+
const REQUIRED_MODEL_IDS = ["glm-5-2", "swe-1.6-fast", "swe-1.6", "swe-1.5-fast", "adaptive"];
|
|
6
|
+
function mergeRequiredDevinModels(discovered) {
|
|
7
|
+
const byId = new Map();
|
|
8
|
+
const fallbackById = new Map(getFallbackDevinModels().map((model) => [model.id, model]));
|
|
9
|
+
for (const model of discovered) {
|
|
10
|
+
const cost = model.cost ?? estimateDevinModelCost(model.id);
|
|
11
|
+
byId.set(model.id, { ...model, cost });
|
|
12
|
+
}
|
|
13
|
+
for (const requiredId of REQUIRED_MODEL_IDS) {
|
|
14
|
+
if (byId.has(requiredId))
|
|
15
|
+
continue;
|
|
16
|
+
const fallback = fallbackById.get(requiredId);
|
|
17
|
+
if (fallback) {
|
|
18
|
+
byId.set(requiredId, {
|
|
19
|
+
...fallback,
|
|
20
|
+
cost: fallback.cost ?? estimateDevinModelCost(fallback.id),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [...byId.values()];
|
|
25
|
+
}
|
|
26
|
+
export function getRequiredDevinModelIds() {
|
|
27
|
+
return REQUIRED_MODEL_IDS;
|
|
28
|
+
}
|
|
29
|
+
const CODEIUM_API_ORIGIN = "https://server.codeium.com";
|
|
30
|
+
const GET_CLI_MODEL_CONFIGS_PATH = "/exa.api_server_pb.ApiServerService/GetCliModelConfigs";
|
|
31
|
+
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
32
|
+
const DEFAULT_MAX_TOKENS = 64_000;
|
|
33
|
+
const MODEL_CACHE_TTL_MS = 10 * 60_000;
|
|
34
|
+
const DEFAULT_DISCOVERY_TIMEOUT_MS = 2_000;
|
|
35
|
+
function encodeVarint(value) {
|
|
36
|
+
const bytes = [];
|
|
37
|
+
let current = BigInt(value);
|
|
38
|
+
while (current > 127n) {
|
|
39
|
+
bytes.push(Number(current & 0x7fn) | 0x80);
|
|
40
|
+
current >>= 7n;
|
|
41
|
+
}
|
|
42
|
+
bytes.push(Number(current));
|
|
43
|
+
return bytes;
|
|
44
|
+
}
|
|
45
|
+
function encodeString(fieldNum, value) {
|
|
46
|
+
const bytes = Buffer.from(value, "utf8");
|
|
47
|
+
return Buffer.from([
|
|
48
|
+
...encodeVarint((fieldNum << 3) | 2),
|
|
49
|
+
...encodeVarint(bytes.length),
|
|
50
|
+
...bytes,
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
function connectEnvelope(payload) {
|
|
54
|
+
const frame = Buffer.alloc(5);
|
|
55
|
+
frame[0] = 0;
|
|
56
|
+
frame.writeUInt32BE(payload.length, 1);
|
|
57
|
+
return Buffer.concat([frame, payload]);
|
|
58
|
+
}
|
|
59
|
+
function encodeBytes(fieldNum, value) {
|
|
60
|
+
return Buffer.from([
|
|
61
|
+
...encodeVarint((fieldNum << 3) | 2),
|
|
62
|
+
...encodeVarint(value.length),
|
|
63
|
+
...value,
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
function buildGetCliModelConfigsRequest(apiKey, version) {
|
|
67
|
+
// Captured Devin CLI traffic sends a field-1 metadata message over application/proto.
|
|
68
|
+
return encodeBytes(1, Buffer.concat([
|
|
69
|
+
encodeString(1, "chisel"),
|
|
70
|
+
encodeString(2, version),
|
|
71
|
+
encodeString(3, apiKey),
|
|
72
|
+
encodeString(4, "en"),
|
|
73
|
+
encodeString(5, process.platform === "darwin" ? "mac" : process.platform),
|
|
74
|
+
encodeString(7, version),
|
|
75
|
+
encodeString(12, "chisel"),
|
|
76
|
+
encodeBytes(30, Buffer.from([3, 4, 6])),
|
|
77
|
+
encodeString(31, crypto.randomBytes(366).toString("hex")),
|
|
78
|
+
]));
|
|
79
|
+
}
|
|
80
|
+
function readVarint(buffer, offset) {
|
|
81
|
+
let value = 0n;
|
|
82
|
+
let shift = 0n;
|
|
83
|
+
let next = offset;
|
|
84
|
+
while (next < buffer.length) {
|
|
85
|
+
const byte = buffer[next];
|
|
86
|
+
value |= BigInt(byte & 0x7f) << shift;
|
|
87
|
+
next++;
|
|
88
|
+
if (byte < 0x80)
|
|
89
|
+
return { value, next };
|
|
90
|
+
shift += 7n;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
function parseFields(buffer) {
|
|
95
|
+
const fields = [];
|
|
96
|
+
let offset = 0;
|
|
97
|
+
while (offset < buffer.length) {
|
|
98
|
+
const key = readVarint(buffer, offset);
|
|
99
|
+
if (!key)
|
|
100
|
+
break;
|
|
101
|
+
offset = key.next;
|
|
102
|
+
const fieldNum = Number(key.value >> 3n);
|
|
103
|
+
const wireType = Number(key.value & 7n);
|
|
104
|
+
if (wireType === 0) {
|
|
105
|
+
const value = readVarint(buffer, offset);
|
|
106
|
+
if (!value)
|
|
107
|
+
break;
|
|
108
|
+
fields.push({ fieldNum, wireType, value: value.value });
|
|
109
|
+
offset = value.next;
|
|
110
|
+
}
|
|
111
|
+
else if (wireType === 1) {
|
|
112
|
+
const end = offset + 8;
|
|
113
|
+
if (end > buffer.length)
|
|
114
|
+
break;
|
|
115
|
+
fields.push({ fieldNum, wireType, value: buffer.subarray(offset, end) });
|
|
116
|
+
offset = end;
|
|
117
|
+
}
|
|
118
|
+
else if (wireType === 2) {
|
|
119
|
+
const length = readVarint(buffer, offset);
|
|
120
|
+
if (!length)
|
|
121
|
+
break;
|
|
122
|
+
offset = length.next;
|
|
123
|
+
const end = offset + Number(length.value);
|
|
124
|
+
if (end > buffer.length)
|
|
125
|
+
break;
|
|
126
|
+
fields.push({ fieldNum, wireType, value: buffer.subarray(offset, end) });
|
|
127
|
+
offset = end;
|
|
128
|
+
}
|
|
129
|
+
else if (wireType === 5) {
|
|
130
|
+
const end = offset + 4;
|
|
131
|
+
if (end > buffer.length)
|
|
132
|
+
break;
|
|
133
|
+
fields.push({ fieldNum, wireType, value: buffer.subarray(offset, end) });
|
|
134
|
+
offset = end;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return fields;
|
|
141
|
+
}
|
|
142
|
+
function parseConnectEnvelopes(buffer) {
|
|
143
|
+
const envelopes = [];
|
|
144
|
+
let offset = 0;
|
|
145
|
+
while (offset + 5 <= buffer.length) {
|
|
146
|
+
const flags = buffer[offset];
|
|
147
|
+
const length = buffer.readUInt32BE(offset + 1);
|
|
148
|
+
offset += 5;
|
|
149
|
+
const end = offset + length;
|
|
150
|
+
if (end > buffer.length)
|
|
151
|
+
break;
|
|
152
|
+
envelopes.push({ flags, body: buffer.subarray(offset, end) });
|
|
153
|
+
offset = end;
|
|
154
|
+
}
|
|
155
|
+
return envelopes;
|
|
156
|
+
}
|
|
157
|
+
function readFloat32(value) {
|
|
158
|
+
if (value.length < 4)
|
|
159
|
+
return undefined;
|
|
160
|
+
return Number(value.readFloatLE(0).toPrecision(6));
|
|
161
|
+
}
|
|
162
|
+
function normalizeModelId(value) {
|
|
163
|
+
const normalized = value
|
|
164
|
+
.trim()
|
|
165
|
+
.replace(/^MODEL_/i, "")
|
|
166
|
+
.replace(/_/g, "-")
|
|
167
|
+
.replace(/SWE-(\d+)-(\d+)/i, "swe-$1.$2")
|
|
168
|
+
.toLowerCase();
|
|
169
|
+
if (normalized === "swe-1.5")
|
|
170
|
+
return "swe-1.5-fast";
|
|
171
|
+
if (normalized === "swe-1.5-slow")
|
|
172
|
+
return "swe-1.5";
|
|
173
|
+
return normalized;
|
|
174
|
+
}
|
|
175
|
+
function firstString(fields, fieldNum) {
|
|
176
|
+
const value = fields.find((field) => field.fieldNum === fieldNum && Buffer.isBuffer(field.value))?.value;
|
|
177
|
+
return Buffer.isBuffer(value) ? value.toString("utf8").trim() : undefined;
|
|
178
|
+
}
|
|
179
|
+
function parseCostEntry(buffer) {
|
|
180
|
+
const fields = parseFields(buffer);
|
|
181
|
+
const label = firstString(fields, 1);
|
|
182
|
+
const rawValue = fields.find((field) => field.fieldNum === 4 && Buffer.isBuffer(field.value))?.value;
|
|
183
|
+
const value = Buffer.isBuffer(rawValue) ? readFloat32(rawValue) : undefined;
|
|
184
|
+
if (!label || value === undefined)
|
|
185
|
+
return null;
|
|
186
|
+
return { label, value };
|
|
187
|
+
}
|
|
188
|
+
function parseModelConfig(buffer) {
|
|
189
|
+
const fields = parseFields(buffer);
|
|
190
|
+
const name = firstString(fields, 1);
|
|
191
|
+
const uid = firstString(fields, 22) ?? firstString(fields, 18) ?? firstString(fields, 2);
|
|
192
|
+
if (!name || !uid)
|
|
193
|
+
return null;
|
|
194
|
+
const costEntries = new Map();
|
|
195
|
+
for (const field of fields) {
|
|
196
|
+
if (![16, 32].includes(field.fieldNum) || !Buffer.isBuffer(field.value))
|
|
197
|
+
continue;
|
|
198
|
+
const entry = parseCostEntry(field.value);
|
|
199
|
+
if (entry)
|
|
200
|
+
costEntries.set(entry.label.toLowerCase(), entry.value);
|
|
201
|
+
}
|
|
202
|
+
const input = costEntries.get("input");
|
|
203
|
+
const cachedInput = costEntries.get("cached input");
|
|
204
|
+
const output = costEntries.get("output");
|
|
205
|
+
const cost = input !== undefined || cachedInput !== undefined || output !== undefined
|
|
206
|
+
? {
|
|
207
|
+
input: input ?? 0,
|
|
208
|
+
output: output ?? 0,
|
|
209
|
+
cache: { read: cachedInput ?? 0, write: 0 },
|
|
210
|
+
}
|
|
211
|
+
: undefined;
|
|
212
|
+
return {
|
|
213
|
+
id: normalizeModelId(uid),
|
|
214
|
+
modelUid: uid,
|
|
215
|
+
name,
|
|
216
|
+
cost,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function parseModelConfigs(buffer) {
|
|
220
|
+
const configs = [];
|
|
221
|
+
const envelopes = parseConnectEnvelopes(buffer);
|
|
222
|
+
const bodies = envelopes.length > 0
|
|
223
|
+
? envelopes.filter((envelope) => envelope.flags !== 2).map((envelope) => envelope.body)
|
|
224
|
+
: [buffer];
|
|
225
|
+
for (const body of bodies) {
|
|
226
|
+
for (const field of parseFields(body)) {
|
|
227
|
+
if (field.fieldNum !== 1 || !Buffer.isBuffer(field.value))
|
|
228
|
+
continue;
|
|
229
|
+
const config = parseModelConfig(field.value);
|
|
230
|
+
if (config)
|
|
231
|
+
configs.push(config);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return configs;
|
|
235
|
+
}
|
|
236
|
+
function toDevinModel(config) {
|
|
237
|
+
return {
|
|
238
|
+
id: config.id,
|
|
239
|
+
name: config.name,
|
|
240
|
+
enumValue: 0,
|
|
241
|
+
modelUid: config.modelUid,
|
|
242
|
+
reasoning: true,
|
|
243
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
244
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
245
|
+
cost: config.cost,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
async function callGetCliModelConfigs(apiKey, version, timeoutMs = DEFAULT_DISCOVERY_TIMEOUT_MS) {
|
|
249
|
+
const controller = new AbortController();
|
|
250
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
251
|
+
let response;
|
|
252
|
+
try {
|
|
253
|
+
response = await fetch(`${CODEIUM_API_ORIGIN}${GET_CLI_MODEL_CONFIGS_PATH}`, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers: {
|
|
256
|
+
"content-type": "application/proto",
|
|
257
|
+
"connect-protocol-version": "1",
|
|
258
|
+
authorization: `Basic ${apiKey}-${apiKey}`,
|
|
259
|
+
},
|
|
260
|
+
body: new Uint8Array(buildGetCliModelConfigsRequest(apiKey, version)),
|
|
261
|
+
signal: controller.signal,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
clearTimeout(timeout);
|
|
266
|
+
}
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
throw new Error(`Model config request failed with ${response.status}`);
|
|
269
|
+
}
|
|
270
|
+
return Buffer.from(await response.arrayBuffer());
|
|
271
|
+
}
|
|
272
|
+
let cachedModelKey = "";
|
|
273
|
+
let cachedModels = null;
|
|
274
|
+
let cachedModelTime = 0;
|
|
275
|
+
let inFlightModelKey = "";
|
|
276
|
+
let inFlightModels = null;
|
|
277
|
+
function discoveryCacheKey(apiKey, version) {
|
|
278
|
+
return `${crypto.createHash("sha256").update(apiKey).digest("hex").slice(0, 16)}:${version}`;
|
|
279
|
+
}
|
|
280
|
+
function isCacheFresh(cacheKey, now = Date.now()) {
|
|
281
|
+
return Boolean(cachedModels && cachedModelKey === cacheKey && now - cachedModelTime < MODEL_CACHE_TTL_MS);
|
|
282
|
+
}
|
|
283
|
+
export function getCachedDevinModels(apiKey, version = "3000.1.23") {
|
|
284
|
+
if (!apiKey || apiKey === "devin-local") {
|
|
285
|
+
return mergeRequiredDevinModels(getFallbackDevinModels());
|
|
286
|
+
}
|
|
287
|
+
const cacheKey = discoveryCacheKey(apiKey, version);
|
|
288
|
+
if (cachedModels && cachedModelKey === cacheKey)
|
|
289
|
+
return cachedModels;
|
|
290
|
+
return mergeRequiredDevinModels(getFallbackDevinModels());
|
|
291
|
+
}
|
|
292
|
+
export async function getDiscoveredDevinModels(apiKey, version = "3000.1.23", options = {}) {
|
|
293
|
+
if (!apiKey || apiKey === "devin-local") {
|
|
294
|
+
return mergeRequiredDevinModels(getFallbackDevinModels());
|
|
295
|
+
}
|
|
296
|
+
const cacheKey = discoveryCacheKey(apiKey, version);
|
|
297
|
+
if (isCacheFresh(cacheKey))
|
|
298
|
+
return cachedModels;
|
|
299
|
+
if (inFlightModels && inFlightModelKey === cacheKey)
|
|
300
|
+
return inFlightModels;
|
|
301
|
+
inFlightModelKey = cacheKey;
|
|
302
|
+
inFlightModels = (async () => {
|
|
303
|
+
try {
|
|
304
|
+
const response = await callGetCliModelConfigs(apiKey, version, options.timeoutMs);
|
|
305
|
+
const discovered = parseModelConfigs(response).map(toDevinModel);
|
|
306
|
+
const base = discovered.length > 0 ? discovered : getFallbackDevinModels();
|
|
307
|
+
cachedModels = mergeRequiredDevinModels(base);
|
|
308
|
+
cachedModelKey = cacheKey;
|
|
309
|
+
cachedModelTime = Date.now();
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
if (!(options.allowStale ?? true) || cachedModelKey !== cacheKey || !cachedModels) {
|
|
313
|
+
cachedModels = mergeRequiredDevinModels(getFallbackDevinModels());
|
|
314
|
+
cachedModelKey = cacheKey;
|
|
315
|
+
cachedModelTime = Date.now();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
inFlightModelKey = "";
|
|
320
|
+
inFlightModels = null;
|
|
321
|
+
}
|
|
322
|
+
return cachedModels;
|
|
323
|
+
})();
|
|
324
|
+
return inFlightModels;
|
|
325
|
+
}
|
|
326
|
+
export function clearDiscoveredModelCache() {
|
|
327
|
+
cachedModelKey = "";
|
|
328
|
+
cachedModels = null;
|
|
329
|
+
cachedModelTime = 0;
|
|
330
|
+
inFlightModelKey = "";
|
|
331
|
+
inFlightModels = null;
|
|
332
|
+
}
|
|
333
|
+
export const __test = {
|
|
334
|
+
clearDiscoveredModelCache,
|
|
335
|
+
connectEnvelope,
|
|
336
|
+
encodeString,
|
|
337
|
+
parseConnectEnvelopes,
|
|
338
|
+
parseFields,
|
|
339
|
+
parseModelConfigs,
|
|
340
|
+
};
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const DEVIN_PROVIDER_ID = "devin";
|
|
2
|
+
export declare const DEVIN_PROVIDER_NPM = "@ai-sdk/openai-compatible";
|
|
3
|
+
export interface DevinModel {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
enumValue: number;
|
|
7
|
+
modelUid: string;
|
|
8
|
+
reasoning: boolean;
|
|
9
|
+
contextWindow: number;
|
|
10
|
+
maxTokens: number;
|
|
11
|
+
cost?: ModelCost;
|
|
12
|
+
}
|
|
13
|
+
export interface ModelCost {
|
|
14
|
+
input: number;
|
|
15
|
+
output: number;
|
|
16
|
+
cache: {
|
|
17
|
+
read: number;
|
|
18
|
+
write: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare function estimateDevinModelCost(modelId: string): ModelCost;
|
|
22
|
+
export declare function getDefaultModel(): DevinModel;
|
|
23
|
+
export declare function getFallbackDevinModels(): DevinModel[];
|
|
24
|
+
export declare function setDevinModels(models: readonly DevinModel[]): void;
|
|
25
|
+
export declare function getDevinModels(): DevinModel[];
|
|
26
|
+
export declare function resolveDevinModel(modelId?: string): DevinModel;
|
|
27
|
+
export declare function buildDevinProviderModels(port: number, models?: readonly DevinModel[]): Record<string, any>;
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export const DEVIN_PROVIDER_ID = "devin";
|
|
2
|
+
export const DEVIN_PROVIDER_NPM = "@ai-sdk/openai-compatible";
|
|
3
|
+
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
4
|
+
const DEFAULT_MAX_TOKENS = 64_000;
|
|
5
|
+
const FALLBACK_MODELS = [
|
|
6
|
+
{
|
|
7
|
+
id: "swe-1.6-fast",
|
|
8
|
+
name: "SWE 1.6 Fast",
|
|
9
|
+
enumValue: 421,
|
|
10
|
+
modelUid: "swe-1-6-fast",
|
|
11
|
+
reasoning: true,
|
|
12
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
13
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "glm-5-2",
|
|
17
|
+
name: "GLM-5.2 High",
|
|
18
|
+
enumValue: 0,
|
|
19
|
+
modelUid: "glm-5-2",
|
|
20
|
+
reasoning: true,
|
|
21
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
22
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "swe-1.6",
|
|
26
|
+
name: "SWE 1.6",
|
|
27
|
+
enumValue: 420,
|
|
28
|
+
modelUid: "swe-1-6",
|
|
29
|
+
reasoning: true,
|
|
30
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
31
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "swe-1.5-fast",
|
|
35
|
+
name: "SWE 1.5 Fast",
|
|
36
|
+
enumValue: 359,
|
|
37
|
+
modelUid: "swe-1-5",
|
|
38
|
+
reasoning: true,
|
|
39
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
40
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "adaptive",
|
|
44
|
+
name: "Adaptive",
|
|
45
|
+
enumValue: 0,
|
|
46
|
+
modelUid: "adaptive",
|
|
47
|
+
reasoning: true,
|
|
48
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
49
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
const ALIASES = {
|
|
53
|
+
"glm-5.2": "glm-5-2",
|
|
54
|
+
"glm-5-2-high": "glm-5-2",
|
|
55
|
+
"swe-1-6-fast": "swe-1.6-fast",
|
|
56
|
+
"swe-1.6:fast": "swe-1.6-fast",
|
|
57
|
+
"swe-1-6": "swe-1.6",
|
|
58
|
+
"swe-1-5-fast": "swe-1.5-fast",
|
|
59
|
+
"swe-1.5:fast": "swe-1.5-fast",
|
|
60
|
+
"MODEL_SWE_1_6_FAST": "swe-1.6-fast",
|
|
61
|
+
"MODEL_SWE_1_6": "swe-1.6",
|
|
62
|
+
"MODEL_SWE_1_5": "swe-1.5-fast",
|
|
63
|
+
MODEL_ADAPTIVE: "adaptive",
|
|
64
|
+
};
|
|
65
|
+
const MODEL_BY_ID = new Map(FALLBACK_MODELS.map((model) => [model.id, model]));
|
|
66
|
+
// $/M token rates from Devin model docs/API. Dynamic discovery overrides these when available.
|
|
67
|
+
const MODEL_COST_TABLE = {
|
|
68
|
+
"glm-5-2": { input: 0.3, output: 1.5, cache: { read: 0.03, write: 0 } },
|
|
69
|
+
"swe-1.6-fast": { input: 0.3, output: 1.5, cache: { read: 0.03, write: 0 } },
|
|
70
|
+
"swe-1.6": { input: 0.3, output: 1.5, cache: { read: 0.03, write: 0 } },
|
|
71
|
+
"swe-1.5-fast": { input: 0.3, output: 1.5, cache: { read: 0.03, write: 0 } },
|
|
72
|
+
"adaptive": { input: 0.3, output: 1.5, cache: { read: 0.03, write: 0 } },
|
|
73
|
+
};
|
|
74
|
+
const DEFAULT_COST = { input: 0, output: 0, cache: { read: 0, write: 0 } };
|
|
75
|
+
let activeModels = [...FALLBACK_MODELS];
|
|
76
|
+
function canonicalModelId(modelId) {
|
|
77
|
+
const normalized = modelId.trim().replace(/^devin\//i, "").replace(/_/g, "-").toLowerCase();
|
|
78
|
+
return ALIASES[modelId.trim()] ?? ALIASES[normalized] ?? normalized;
|
|
79
|
+
}
|
|
80
|
+
export function estimateDevinModelCost(modelId) {
|
|
81
|
+
return MODEL_COST_TABLE[canonicalModelId(modelId)] ?? DEFAULT_COST;
|
|
82
|
+
}
|
|
83
|
+
export function getDefaultModel() {
|
|
84
|
+
return FALLBACK_MODELS[0];
|
|
85
|
+
}
|
|
86
|
+
export function getFallbackDevinModels() {
|
|
87
|
+
return [...FALLBACK_MODELS];
|
|
88
|
+
}
|
|
89
|
+
export function setDevinModels(models) {
|
|
90
|
+
activeModels = models.length > 0 ? [...models] : [...FALLBACK_MODELS];
|
|
91
|
+
}
|
|
92
|
+
export function getDevinModels() {
|
|
93
|
+
return [...activeModels];
|
|
94
|
+
}
|
|
95
|
+
export function resolveDevinModel(modelId) {
|
|
96
|
+
if (!modelId)
|
|
97
|
+
return getDefaultModel();
|
|
98
|
+
const resolvedId = canonicalModelId(modelId);
|
|
99
|
+
return activeModels.find((model) => model.id === resolvedId || model.modelUid === resolvedId)
|
|
100
|
+
?? MODEL_BY_ID.get(resolvedId)
|
|
101
|
+
?? getDefaultModel();
|
|
102
|
+
}
|
|
103
|
+
export function buildDevinProviderModels(port, models = activeModels) {
|
|
104
|
+
setDevinModels(models);
|
|
105
|
+
return Object.fromEntries(models.map((model) => [
|
|
106
|
+
model.id,
|
|
107
|
+
{
|
|
108
|
+
id: model.id,
|
|
109
|
+
providerID: DEVIN_PROVIDER_ID,
|
|
110
|
+
api: {
|
|
111
|
+
id: model.id,
|
|
112
|
+
url: `http://localhost:${port}/v1`,
|
|
113
|
+
npm: DEVIN_PROVIDER_NPM,
|
|
114
|
+
},
|
|
115
|
+
name: model.name,
|
|
116
|
+
capabilities: {
|
|
117
|
+
temperature: true,
|
|
118
|
+
reasoning: model.reasoning,
|
|
119
|
+
attachment: false,
|
|
120
|
+
toolcall: true,
|
|
121
|
+
input: {
|
|
122
|
+
text: true,
|
|
123
|
+
audio: false,
|
|
124
|
+
image: false,
|
|
125
|
+
video: false,
|
|
126
|
+
pdf: false,
|
|
127
|
+
},
|
|
128
|
+
output: {
|
|
129
|
+
text: true,
|
|
130
|
+
audio: false,
|
|
131
|
+
image: false,
|
|
132
|
+
video: false,
|
|
133
|
+
pdf: false,
|
|
134
|
+
},
|
|
135
|
+
interleaved: false,
|
|
136
|
+
},
|
|
137
|
+
cost: model.cost ?? estimateDevinModelCost(model.id),
|
|
138
|
+
limit: {
|
|
139
|
+
context: model.contextWindow,
|
|
140
|
+
input: model.contextWindow,
|
|
141
|
+
output: model.maxTokens,
|
|
142
|
+
},
|
|
143
|
+
status: "active",
|
|
144
|
+
options: {},
|
|
145
|
+
headers: {},
|
|
146
|
+
release_date: "",
|
|
147
|
+
variants: {},
|
|
148
|
+
},
|
|
149
|
+
]));
|
|
150
|
+
}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { buildDevinProviderModels } from "./models";
|
|
2
|
+
import { type ChatCompletionRequest } from "./devin";
|
|
3
|
+
export declare const DEVIN_PROXY_DEFAULT_PORT = 65534;
|
|
4
|
+
type ChatTransport = (request: ChatCompletionRequest) => AsyncIterable<string> | Promise<AsyncIterable<string> | string> | string;
|
|
5
|
+
export declare function setChatTransportForTests(transport?: ChatTransport): void;
|
|
6
|
+
export declare function getProxyPort(): number | undefined;
|
|
7
|
+
export declare function stopProxy(): void;
|
|
8
|
+
export declare function startProxy(forceLocal?: boolean): Promise<number>;
|
|
9
|
+
export declare function proxyBaseURL(port?: number): string;
|
|
10
|
+
export { buildDevinProviderModels };
|