@ai-devkit/agent-manager 0.39.0 → 0.40.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/dist/__tests__/capacity/devin.test.js +239 -0
- package/dist/__tests__/capacity/devin.test.js.map +1 -0
- package/dist/capacity/index.d.ts +3 -0
- package/dist/capacity/index.d.ts.map +1 -1
- package/dist/capacity/index.js +8 -0
- package/dist/capacity/index.js.map +1 -1
- package/dist/capacity/providers/devin.d.ts +13 -0
- package/dist/capacity/providers/devin.d.ts.map +1 -0
- package/dist/capacity/providers/devin.js +92 -0
- package/dist/capacity/providers/devin.js.map +1 -0
- package/dist/capacity/sources/devin.d.ts +12 -0
- package/dist/capacity/sources/devin.d.ts.map +1 -0
- package/dist/capacity/sources/devin.js +29 -0
- package/dist/capacity/sources/devin.js.map +1 -0
- package/dist/harnesses/devin/credentials.d.ts +10 -0
- package/dist/harnesses/devin/credentials.d.ts.map +1 -0
- package/dist/harnesses/devin/credentials.js +66 -0
- package/dist/harnesses/devin/credentials.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/capacity/devin.test.ts +210 -0
- package/src/__tests__/capacity/fixtures/devin-config.json +6 -0
- package/src/__tests__/capacity/fixtures/devin-credentials.toml +4 -0
- package/src/__tests__/capacity/fixtures/devin-quota-nonplan.json +5 -0
- package/src/__tests__/capacity/fixtures/devin-quota.json +10 -0
- package/src/capacity/index.ts +12 -0
- package/src/capacity/providers/devin.ts +121 -0
- package/src/capacity/sources/devin.ts +49 -0
- package/src/harnesses/devin/credentials.ts +88 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { parseDevinCapacity } from "../../capacity/providers/devin.js";
|
|
5
|
+
import { probeDevinCapacity } from "../../capacity/sources/devin.js";
|
|
6
|
+
import { resolveDevinCredential } from "../../harnesses/devin/credentials.js";
|
|
7
|
+
const checkedAt = "2026-10-05T10:00:00.000Z";
|
|
8
|
+
const fixture = (name)=>readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8");
|
|
9
|
+
const credentialFiles = async (path)=>{
|
|
10
|
+
if (path === "/users/test/.local/share/devin/credentials.toml") {
|
|
11
|
+
return fixture("devin-credentials.toml");
|
|
12
|
+
}
|
|
13
|
+
if (path === "/users/test/.config/devin/config.json") {
|
|
14
|
+
return fixture("devin-config.json");
|
|
15
|
+
}
|
|
16
|
+
throw Object.assign(new Error("missing"), {
|
|
17
|
+
code: "ENOENT"
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
describe("devin credential resolution", ()=>{
|
|
21
|
+
it("prefers DEVIN_API_KEY and DEVIN_ORG_ID without reading files", async ()=>{
|
|
22
|
+
const reader = vi.fn(credentialFiles);
|
|
23
|
+
await expect(resolveDevinCredential({
|
|
24
|
+
env: {
|
|
25
|
+
DEVIN_API_KEY: "env-key",
|
|
26
|
+
DEVIN_ORG_ID: "org-env"
|
|
27
|
+
},
|
|
28
|
+
readFile: reader
|
|
29
|
+
})).resolves.toEqual({
|
|
30
|
+
key: "env-key",
|
|
31
|
+
orgId: "org-env"
|
|
32
|
+
});
|
|
33
|
+
expect(reader).not.toHaveBeenCalled();
|
|
34
|
+
});
|
|
35
|
+
it("reads the key from credentials.toml and org from config.json", async ()=>{
|
|
36
|
+
const reader = vi.fn(credentialFiles);
|
|
37
|
+
await expect(resolveDevinCredential({
|
|
38
|
+
env: {
|
|
39
|
+
HOME: "/users/test"
|
|
40
|
+
},
|
|
41
|
+
readFile: reader
|
|
42
|
+
})).resolves.toEqual({
|
|
43
|
+
key: "devin-test-key",
|
|
44
|
+
orgId: "org-test1234"
|
|
45
|
+
});
|
|
46
|
+
expect(reader).toHaveBeenCalledWith("/users/test/.local/share/devin/credentials.toml", "utf8");
|
|
47
|
+
expect(reader).toHaveBeenCalledWith("/users/test/.config/devin/config.json", "utf8");
|
|
48
|
+
});
|
|
49
|
+
it.each([
|
|
50
|
+
[
|
|
51
|
+
"missing credentials file",
|
|
52
|
+
async (path)=>{
|
|
53
|
+
if (path.endsWith("config.json")) return fixture("devin-config.json");
|
|
54
|
+
throw Object.assign(new Error("missing"), {
|
|
55
|
+
code: "ENOENT"
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
/Devin API key not found/
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"malformed credentials file",
|
|
62
|
+
async (path)=>{
|
|
63
|
+
if (path.endsWith("credentials.toml")) return "not = [valid";
|
|
64
|
+
return fixture("devin-config.json");
|
|
65
|
+
},
|
|
66
|
+
/credentials file is malformed/
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"credentials without key",
|
|
70
|
+
async (path)=>{
|
|
71
|
+
if (path.endsWith("credentials.toml")) return 'api_server_url = "https://x"';
|
|
72
|
+
return fixture("devin-config.json");
|
|
73
|
+
},
|
|
74
|
+
/Devin API key not found/
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
"missing config file",
|
|
78
|
+
async (path)=>{
|
|
79
|
+
if (path.endsWith("credentials.toml")) return fixture("devin-credentials.toml");
|
|
80
|
+
throw Object.assign(new Error("missing"), {
|
|
81
|
+
code: "ENOENT"
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
/Devin organization id not found/
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
"config without org_id",
|
|
88
|
+
async (path)=>{
|
|
89
|
+
if (path.endsWith("credentials.toml")) return fixture("devin-credentials.toml");
|
|
90
|
+
return JSON.stringify({
|
|
91
|
+
devin: {}
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
/Devin organization id not found/
|
|
95
|
+
]
|
|
96
|
+
])("fails clearly for %s without exposing file content", async (_name, readFileImpl, pattern)=>{
|
|
97
|
+
await expect(resolveDevinCredential({
|
|
98
|
+
env: {
|
|
99
|
+
HOME: "/users/test"
|
|
100
|
+
},
|
|
101
|
+
readFile: readFileImpl
|
|
102
|
+
})).rejects.toThrow(pattern);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe("devin quota mapping", ()=>{
|
|
106
|
+
it("maps daily and weekly windows with credits", async ()=>{
|
|
107
|
+
const report = parseDevinCapacity(JSON.parse(await fixture("devin-quota.json")));
|
|
108
|
+
expect(report).toMatchObject({
|
|
109
|
+
authenticated: true,
|
|
110
|
+
available: "yes",
|
|
111
|
+
creditsRemaining: 12.5
|
|
112
|
+
});
|
|
113
|
+
expect(report.windows).toEqual([
|
|
114
|
+
{
|
|
115
|
+
id: "devin:daily",
|
|
116
|
+
label: "Daily",
|
|
117
|
+
durationMinutes: 1440,
|
|
118
|
+
usedPercent: 25,
|
|
119
|
+
resetsAt: "2026-10-06T08:00:00.000Z"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "devin:weekly",
|
|
123
|
+
label: "Weekly",
|
|
124
|
+
durationMinutes: 10080,
|
|
125
|
+
usedPercent: 60,
|
|
126
|
+
resetsAt: "2026-10-11T08:00:00.000Z"
|
|
127
|
+
}
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
it("omits the daily window when hide_daily_quota is true", ()=>{
|
|
131
|
+
const report = parseDevinCapacity({
|
|
132
|
+
is_quota_plan: true,
|
|
133
|
+
has_quota_allocation: true,
|
|
134
|
+
daily_percentage: 90,
|
|
135
|
+
weekly_percentage: 10,
|
|
136
|
+
hide_daily_quota: true
|
|
137
|
+
});
|
|
138
|
+
expect(report.windows.map((window)=>window.id)).toEqual([
|
|
139
|
+
"devin:weekly"
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
it("reports authenticated unknown availability for non-quota plans", async ()=>{
|
|
143
|
+
const report = parseDevinCapacity(JSON.parse(await fixture("devin-quota-nonplan.json")));
|
|
144
|
+
expect(report).toEqual({
|
|
145
|
+
authenticated: true,
|
|
146
|
+
available: "unknown",
|
|
147
|
+
windows: [],
|
|
148
|
+
creditsRemaining: null
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
it("clamps percentages and degrades missing fields honestly", ()=>{
|
|
152
|
+
const report = parseDevinCapacity({
|
|
153
|
+
is_quota_plan: true,
|
|
154
|
+
has_quota_allocation: true,
|
|
155
|
+
daily_percentage: 140,
|
|
156
|
+
weekly_reset_at: "not-a-date",
|
|
157
|
+
overage_balance: "unknown"
|
|
158
|
+
});
|
|
159
|
+
const [daily, weekly] = report.windows;
|
|
160
|
+
expect(daily.usedPercent).toBe(100);
|
|
161
|
+
expect(weekly.usedPercent).toBeNull();
|
|
162
|
+
expect(weekly.resetsAt).toBeNull();
|
|
163
|
+
expect(report.creditsRemaining).toBeNull();
|
|
164
|
+
expect(report.available).toBe("no");
|
|
165
|
+
});
|
|
166
|
+
it("rejects non-object responses", ()=>{
|
|
167
|
+
expect(()=>parseDevinCapacity("nope")).toThrow(/Devin quota/);
|
|
168
|
+
expect(()=>parseDevinCapacity(null)).toThrow(/Devin quota/);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe("devin request", ()=>{
|
|
172
|
+
it("GETs the org quota endpoint with only Bearer authentication", async ()=>{
|
|
173
|
+
const body = await fixture("devin-quota.json");
|
|
174
|
+
const fetch = vi.fn(async ()=>new Response(body, {
|
|
175
|
+
status: 200
|
|
176
|
+
}));
|
|
177
|
+
const report = await probeDevinCapacity({
|
|
178
|
+
checkedAt,
|
|
179
|
+
env: {
|
|
180
|
+
DEVIN_API_KEY: "request-test-key",
|
|
181
|
+
DEVIN_ORG_ID: "org-abc"
|
|
182
|
+
},
|
|
183
|
+
fetch
|
|
184
|
+
});
|
|
185
|
+
expect(fetch).toHaveBeenCalledWith("https://app.devin.ai/api/org-abc/billing/quota/usage", expect.objectContaining({
|
|
186
|
+
method: "GET",
|
|
187
|
+
headers: {
|
|
188
|
+
Authorization: "Bearer request-test-key"
|
|
189
|
+
}
|
|
190
|
+
}));
|
|
191
|
+
expect(report.harness).toBe("devin");
|
|
192
|
+
expect(report.provider).toBe("devin");
|
|
193
|
+
expect(JSON.stringify(report)).not.toContain("request-test-key");
|
|
194
|
+
});
|
|
195
|
+
it.each([
|
|
196
|
+
401,
|
|
197
|
+
403
|
|
198
|
+
])("reports HTTP %i as unauthenticated", async (status)=>{
|
|
199
|
+
const report = await probeDevinCapacity({
|
|
200
|
+
checkedAt,
|
|
201
|
+
env: {
|
|
202
|
+
DEVIN_API_KEY: "request-test-key",
|
|
203
|
+
DEVIN_ORG_ID: "org-abc"
|
|
204
|
+
},
|
|
205
|
+
fetch: vi.fn(async ()=>new Response("denied", {
|
|
206
|
+
status
|
|
207
|
+
}))
|
|
208
|
+
});
|
|
209
|
+
expect(report).toMatchObject({
|
|
210
|
+
authenticated: false,
|
|
211
|
+
available: "unknown",
|
|
212
|
+
windows: []
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
it("rejects other non-200 and malformed JSON responses with sanitized errors", async ()=>{
|
|
216
|
+
await expect(probeDevinCapacity({
|
|
217
|
+
checkedAt,
|
|
218
|
+
env: {
|
|
219
|
+
DEVIN_API_KEY: "never-expose-this-key",
|
|
220
|
+
DEVIN_ORG_ID: "org-abc"
|
|
221
|
+
},
|
|
222
|
+
fetch: vi.fn(async ()=>new Response("private body", {
|
|
223
|
+
status: 503
|
|
224
|
+
}))
|
|
225
|
+
})).rejects.toThrow("Devin quota request failed: HTTP 503");
|
|
226
|
+
await expect(probeDevinCapacity({
|
|
227
|
+
checkedAt,
|
|
228
|
+
env: {
|
|
229
|
+
DEVIN_API_KEY: "never-expose-this-key",
|
|
230
|
+
DEVIN_ORG_ID: "org-abc"
|
|
231
|
+
},
|
|
232
|
+
fetch: vi.fn(async ()=>new Response("not-json", {
|
|
233
|
+
status: 200
|
|
234
|
+
}))
|
|
235
|
+
})).rejects.toThrow("Devin quota response is not valid JSON");
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
//# sourceMappingURL=devin.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/capacity/devin.test.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { describe, expect, it, vi } from \"vitest\";\nimport { parseDevinCapacity } from \"../../capacity/providers/devin.js\";\nimport { probeDevinCapacity } from \"../../capacity/sources/devin.js\";\nimport { resolveDevinCredential } from \"../../harnesses/devin/credentials.js\";\n\nconst checkedAt = \"2026-10-05T10:00:00.000Z\";\nconst fixture = (name: string) =>\n readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), \"utf8\");\n\nconst credentialFiles = async (path: string): Promise<string> => {\n if (path === \"/users/test/.local/share/devin/credentials.toml\") {\n return fixture(\"devin-credentials.toml\");\n }\n if (path === \"/users/test/.config/devin/config.json\") {\n return fixture(\"devin-config.json\");\n }\n throw Object.assign(new Error(\"missing\"), { code: \"ENOENT\" });\n};\n\ndescribe(\"devin credential resolution\", () => {\n it(\"prefers DEVIN_API_KEY and DEVIN_ORG_ID without reading files\", async () => {\n const reader = vi.fn(credentialFiles);\n await expect(\n resolveDevinCredential({\n env: { DEVIN_API_KEY: \"env-key\", DEVIN_ORG_ID: \"org-env\" },\n readFile: reader,\n }),\n ).resolves.toEqual({ key: \"env-key\", orgId: \"org-env\" });\n expect(reader).not.toHaveBeenCalled();\n });\n\n it(\"reads the key from credentials.toml and org from config.json\", async () => {\n const reader = vi.fn(credentialFiles);\n await expect(\n resolveDevinCredential({ env: { HOME: \"/users/test\" }, readFile: reader }),\n ).resolves.toEqual({ key: \"devin-test-key\", orgId: \"org-test1234\" });\n expect(reader).toHaveBeenCalledWith(\"/users/test/.local/share/devin/credentials.toml\", \"utf8\");\n expect(reader).toHaveBeenCalledWith(\"/users/test/.config/devin/config.json\", \"utf8\");\n });\n\n it.each([\n [\n \"missing credentials file\",\n async (path: string) => {\n if (path.endsWith(\"config.json\")) return fixture(\"devin-config.json\");\n throw Object.assign(new Error(\"missing\"), { code: \"ENOENT\" });\n },\n /Devin API key not found/,\n ],\n [\n \"malformed credentials file\",\n async (path: string) => {\n if (path.endsWith(\"credentials.toml\")) return \"not = [valid\";\n return fixture(\"devin-config.json\");\n },\n /credentials file is malformed/,\n ],\n [\n \"credentials without key\",\n async (path: string) => {\n if (path.endsWith(\"credentials.toml\")) return 'api_server_url = \"https://x\"';\n return fixture(\"devin-config.json\");\n },\n /Devin API key not found/,\n ],\n [\n \"missing config file\",\n async (path: string) => {\n if (path.endsWith(\"credentials.toml\")) return fixture(\"devin-credentials.toml\");\n throw Object.assign(new Error(\"missing\"), { code: \"ENOENT\" });\n },\n /Devin organization id not found/,\n ],\n [\n \"config without org_id\",\n async (path: string) => {\n if (path.endsWith(\"credentials.toml\")) return fixture(\"devin-credentials.toml\");\n return JSON.stringify({ devin: {} });\n },\n /Devin organization id not found/,\n ],\n ])(\"fails clearly for %s without exposing file content\", async (_name, readFileImpl, pattern) => {\n await expect(\n resolveDevinCredential({ env: { HOME: \"/users/test\" }, readFile: readFileImpl }),\n ).rejects.toThrow(pattern);\n });\n});\n\ndescribe(\"devin quota mapping\", () => {\n it(\"maps daily and weekly windows with credits\", async () => {\n const report = parseDevinCapacity(JSON.parse(await fixture(\"devin-quota.json\")));\n expect(report).toMatchObject({\n authenticated: true,\n available: \"yes\",\n creditsRemaining: 12.5,\n });\n expect(report.windows).toEqual([\n {\n id: \"devin:daily\",\n label: \"Daily\",\n durationMinutes: 1440,\n usedPercent: 25,\n resetsAt: \"2026-10-06T08:00:00.000Z\",\n },\n {\n id: \"devin:weekly\",\n label: \"Weekly\",\n durationMinutes: 10080,\n usedPercent: 60,\n resetsAt: \"2026-10-11T08:00:00.000Z\",\n },\n ]);\n });\n\n it(\"omits the daily window when hide_daily_quota is true\", () => {\n const report = parseDevinCapacity({\n is_quota_plan: true,\n has_quota_allocation: true,\n daily_percentage: 90,\n weekly_percentage: 10,\n hide_daily_quota: true,\n });\n expect(report.windows.map((window) => window.id)).toEqual([\"devin:weekly\"]);\n });\n\n it(\"reports authenticated unknown availability for non-quota plans\", async () => {\n const report = parseDevinCapacity(JSON.parse(await fixture(\"devin-quota-nonplan.json\")));\n expect(report).toEqual({\n authenticated: true,\n available: \"unknown\",\n windows: [],\n creditsRemaining: null,\n });\n });\n\n it(\"clamps percentages and degrades missing fields honestly\", () => {\n const report = parseDevinCapacity({\n is_quota_plan: true,\n has_quota_allocation: true,\n daily_percentage: 140,\n weekly_reset_at: \"not-a-date\",\n overage_balance: \"unknown\",\n });\n const [daily, weekly] = report.windows;\n expect(daily.usedPercent).toBe(100);\n expect(weekly.usedPercent).toBeNull();\n expect(weekly.resetsAt).toBeNull();\n expect(report.creditsRemaining).toBeNull();\n expect(report.available).toBe(\"no\");\n });\n\n it(\"rejects non-object responses\", () => {\n expect(() => parseDevinCapacity(\"nope\")).toThrow(/Devin quota/);\n expect(() => parseDevinCapacity(null)).toThrow(/Devin quota/);\n });\n});\n\ndescribe(\"devin request\", () => {\n it(\"GETs the org quota endpoint with only Bearer authentication\", async () => {\n const body = await fixture(\"devin-quota.json\");\n const fetch = vi.fn(async () => new Response(body, { status: 200 }));\n const report = await probeDevinCapacity({\n checkedAt,\n env: { DEVIN_API_KEY: \"request-test-key\", DEVIN_ORG_ID: \"org-abc\" },\n fetch,\n });\n expect(fetch).toHaveBeenCalledWith(\n \"https://app.devin.ai/api/org-abc/billing/quota/usage\",\n expect.objectContaining({\n method: \"GET\",\n headers: { Authorization: \"Bearer request-test-key\" },\n }),\n );\n expect(report.harness).toBe(\"devin\");\n expect(report.provider).toBe(\"devin\");\n expect(JSON.stringify(report)).not.toContain(\"request-test-key\");\n });\n\n it.each([401, 403])(\"reports HTTP %i as unauthenticated\", async (status) => {\n const report = await probeDevinCapacity({\n checkedAt,\n env: { DEVIN_API_KEY: \"request-test-key\", DEVIN_ORG_ID: \"org-abc\" },\n fetch: vi.fn(async () => new Response(\"denied\", { status })),\n });\n expect(report).toMatchObject({\n authenticated: false,\n available: \"unknown\",\n windows: [],\n });\n });\n\n it(\"rejects other non-200 and malformed JSON responses with sanitized errors\", async () => {\n await expect(\n probeDevinCapacity({\n checkedAt,\n env: { DEVIN_API_KEY: \"never-expose-this-key\", DEVIN_ORG_ID: \"org-abc\" },\n fetch: vi.fn(async () => new Response(\"private body\", { status: 503 })),\n }),\n ).rejects.toThrow(\"Devin quota request failed: HTTP 503\");\n await expect(\n probeDevinCapacity({\n checkedAt,\n env: { DEVIN_API_KEY: \"never-expose-this-key\", DEVIN_ORG_ID: \"org-abc\" },\n fetch: vi.fn(async () => new Response(\"not-json\", { status: 200 })),\n }),\n ).rejects.toThrow(\"Devin quota response is not valid JSON\");\n });\n});\n"],"names":["readFile","fileURLToPath","describe","expect","it","vi","parseDevinCapacity","probeDevinCapacity","resolveDevinCredential","checkedAt","fixture","name","URL","url","credentialFiles","path","Object","assign","Error","code","reader","fn","env","DEVIN_API_KEY","DEVIN_ORG_ID","resolves","toEqual","key","orgId","not","toHaveBeenCalled","HOME","toHaveBeenCalledWith","each","endsWith","JSON","stringify","devin","_name","readFileImpl","pattern","rejects","toThrow","report","parse","toMatchObject","authenticated","available","creditsRemaining","windows","id","label","durationMinutes","usedPercent","resetsAt","is_quota_plan","has_quota_allocation","daily_percentage","weekly_percentage","hide_daily_quota","map","window","weekly_reset_at","overage_balance","daily","weekly","toBe","toBeNull","body","fetch","Response","status","objectContaining","method","headers","Authorization","harness","provider","toContain"],"mappings":"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"}
|
package/dist/capacity/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { probeCodexCapacity } from "./sources/codex.js";
|
|
2
2
|
import { type ClaudeCapacityOptions } from "./sources/claude.js";
|
|
3
|
+
import { type DevinCapacityOptions } from "./sources/devin.js";
|
|
3
4
|
import { type PiAnthropicCapacityOptions, type PiOpenAiCapacityOptions, type PiZaiCapacityOptions } from "./sources/pi.js";
|
|
4
5
|
import type { CapacityReport } from "./types.js";
|
|
5
6
|
export type { CapacityReport, CapacityWindow } from "./types.js";
|
|
6
7
|
export type { ClaudeCapacityOptions } from "./sources/claude.js";
|
|
8
|
+
export type { DevinCapacityOptions } from "./sources/devin.js";
|
|
7
9
|
export type { PiAnthropicCapacityOptions as AnthropicCapacityOptions } from "./sources/pi.js";
|
|
8
10
|
export type { PiOpenAiCapacityOptions as OpenAiCapacityOptions, PiZaiCapacityOptions as ZaiCapacityOptions, } from "./sources/pi.js";
|
|
9
11
|
export type CapacityProbeOptions = {
|
|
@@ -16,5 +18,6 @@ export declare function getCodexCapacityReport(options?: CapacityProbeOptions):
|
|
|
16
18
|
export declare function getZaiCapacityReport(options?: PiZaiCapacityOptions): Promise<CapacityReport>;
|
|
17
19
|
export declare function getOpenAiCapacityReport(options?: PiOpenAiCapacityOptions): Promise<CapacityReport>;
|
|
18
20
|
export declare function getClaudeCapacityReport(options?: ClaudeCapacityOptions): Promise<CapacityReport>;
|
|
21
|
+
export declare function getDevinCapacityReport(options?: DevinCapacityOptions): Promise<CapacityReport>;
|
|
19
22
|
export declare function getAnthropicCapacityReport(options?: PiAnthropicCapacityOptions): Promise<CapacityReport>;
|
|
20
23
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/capacity/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAIL,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjE,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAE,0BAA0B,IAAI,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC9F,YAAY,EACV,uBAAuB,IAAI,qBAAqB,EAChD,oBAAoB,IAAI,kBAAkB,GAC3C,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,OAAO,kBAAkB,CAAC;CACnC,CAAC;AA+BF,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,cAAc,CAAC,CAWzB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,cAAc,CAAC,CAMzB;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,cAAc,CAAC,CAMzB;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,cAAc,CAAC,CAOzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,cAAc,CAAC,CAOzB"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/capacity/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAIL,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjE,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,YAAY,EAAE,0BAA0B,IAAI,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC9F,YAAY,EACV,uBAAuB,IAAI,qBAAqB,EAChD,oBAAoB,IAAI,kBAAkB,GAC3C,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,OAAO,kBAAkB,CAAC;CACnC,CAAC;AA+BF,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,cAAc,CAAC,CAWzB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,cAAc,CAAC,CAMzB;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,cAAc,CAAC,CAMzB;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,cAAc,CAAC,CAOzB;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,cAAc,CAAC,CAMzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,cAAc,CAAC,CAOzB"}
|
package/dist/capacity/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { access as fsAccess } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { codexUnavailableReport, probeCodexCapacity } from "./sources/codex.js";
|
|
5
5
|
import { probeClaudeCapacity } from "./sources/claude.js";
|
|
6
|
+
import { probeDevinCapacity } from "./sources/devin.js";
|
|
6
7
|
import { probeOpenAiCapacity, probePiAnthropicCapacity, probeZaiCapacity } from "./sources/pi.js";
|
|
7
8
|
async function canAccess(target, mode) {
|
|
8
9
|
try {
|
|
@@ -62,6 +63,13 @@ export async function getClaudeCapacityReport(options = {}) {
|
|
|
62
63
|
now
|
|
63
64
|
});
|
|
64
65
|
}
|
|
66
|
+
export async function getDevinCapacityReport(options = {}) {
|
|
67
|
+
const { now, ...probeOptions } = options;
|
|
68
|
+
return probeDevinCapacity({
|
|
69
|
+
...probeOptions,
|
|
70
|
+
checkedAt: (now?.() ?? new Date()).toISOString()
|
|
71
|
+
});
|
|
72
|
+
}
|
|
65
73
|
export async function getAnthropicCapacityReport(options = {}) {
|
|
66
74
|
const { now, ...probeOptions } = options;
|
|
67
75
|
return probePiAnthropicCapacity({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/capacity/index.ts"],"sourcesContent":["import { constants } from \"node:fs\";\nimport { access as fsAccess } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { codexUnavailableReport, probeCodexCapacity } from \"./sources/codex.js\";\nimport { probeClaudeCapacity, type ClaudeCapacityOptions } from \"./sources/claude.js\";\nimport {\n probeOpenAiCapacity,\n probePiAnthropicCapacity,\n probeZaiCapacity,\n type PiAnthropicCapacityOptions,\n type PiOpenAiCapacityOptions,\n type PiZaiCapacityOptions,\n} from \"./sources/pi.js\";\nimport type { CapacityReport } from \"./types.js\";\n\nexport type { CapacityReport, CapacityWindow } from \"./types.js\";\nexport type { ClaudeCapacityOptions } from \"./sources/claude.js\";\nexport type { PiAnthropicCapacityOptions as AnthropicCapacityOptions } from \"./sources/pi.js\";\nexport type {\n PiOpenAiCapacityOptions as OpenAiCapacityOptions,\n PiZaiCapacityOptions as ZaiCapacityOptions,\n} from \"./sources/pi.js\";\n\nexport type CapacityProbeOptions = {\n now?: () => Date;\n path?: string;\n access?: (target: string) => Promise<void>;\n probe?: typeof probeCodexCapacity;\n};\n\nasync function canAccess(target: string, mode: number): Promise<boolean> {\n try {\n await fsAccess(target, mode);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function isCodexInstalled(\n pathValue: string,\n checkAccess?: (target: string) => Promise<void>,\n): Promise<boolean> {\n const directories = pathValue.split(path.delimiter).filter(Boolean);\n for (const directory of directories) {\n const executable = path.join(directory, \"codex\");\n if (checkAccess) {\n try {\n await checkAccess(executable);\n return true;\n } catch {\n continue;\n }\n }\n if (await canAccess(executable, constants.X_OK)) return true;\n }\n return false;\n}\n\nexport async function getCodexCapacityReport(\n options: CapacityProbeOptions = {},\n): Promise<CapacityReport> {\n const generatedAt = (options.now?.() ?? new Date()).toISOString();\n const installed = await isCodexInstalled(options.path ?? process.env.PATH ?? \"\", options.access);\n try {\n return await (options.probe ?? probeCodexCapacity)({\n installed,\n checkedAt: generatedAt,\n });\n } catch {\n return codexUnavailableReport(generatedAt);\n }\n}\n\nexport async function getZaiCapacityReport(\n options: PiZaiCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probeZaiCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n });\n}\n\nexport async function getOpenAiCapacityReport(\n options: PiOpenAiCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probeOpenAiCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n });\n}\n\nexport async function getClaudeCapacityReport(\n options: ClaudeCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probeClaudeCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n now,\n });\n}\n\nexport async function getAnthropicCapacityReport(\n options: PiAnthropicCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probePiAnthropicCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n now,\n });\n}\n"],"names":["constants","access","fsAccess","path","codexUnavailableReport","probeCodexCapacity","probeClaudeCapacity","probeOpenAiCapacity","probePiAnthropicCapacity","probeZaiCapacity","canAccess","target","mode","isCodexInstalled","pathValue","checkAccess","directories","split","delimiter","filter","Boolean","directory","executable","join","X_OK","getCodexCapacityReport","options","generatedAt","now","Date","toISOString","installed","process","env","PATH","probe","checkedAt","getZaiCapacityReport","probeOptions","getOpenAiCapacityReport","getClaudeCapacityReport","getAnthropicCapacityReport"],"mappings":"AAAA,SAASA,SAAS,QAAQ,UAAU;AACpC,SAASC,UAAUC,QAAQ,QAAQ,mBAAmB;AACtD,OAAOC,UAAU,YAAY;AAC7B,SAASC,sBAAsB,EAAEC,kBAAkB,QAAQ,qBAAqB;AAChF,SAASC,mBAAmB,QAAoC,sBAAsB;AACtF,SACEC,mBAAmB,EACnBC,wBAAwB,EACxBC,gBAAgB,QAIX,kBAAkB;
|
|
1
|
+
{"version":3,"sources":["../../src/capacity/index.ts"],"sourcesContent":["import { constants } from \"node:fs\";\nimport { access as fsAccess } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { codexUnavailableReport, probeCodexCapacity } from \"./sources/codex.js\";\nimport { probeClaudeCapacity, type ClaudeCapacityOptions } from \"./sources/claude.js\";\nimport { probeDevinCapacity, type DevinCapacityOptions } from \"./sources/devin.js\";\nimport {\n probeOpenAiCapacity,\n probePiAnthropicCapacity,\n probeZaiCapacity,\n type PiAnthropicCapacityOptions,\n type PiOpenAiCapacityOptions,\n type PiZaiCapacityOptions,\n} from \"./sources/pi.js\";\nimport type { CapacityReport } from \"./types.js\";\n\nexport type { CapacityReport, CapacityWindow } from \"./types.js\";\nexport type { ClaudeCapacityOptions } from \"./sources/claude.js\";\nexport type { DevinCapacityOptions } from \"./sources/devin.js\";\nexport type { PiAnthropicCapacityOptions as AnthropicCapacityOptions } from \"./sources/pi.js\";\nexport type {\n PiOpenAiCapacityOptions as OpenAiCapacityOptions,\n PiZaiCapacityOptions as ZaiCapacityOptions,\n} from \"./sources/pi.js\";\n\nexport type CapacityProbeOptions = {\n now?: () => Date;\n path?: string;\n access?: (target: string) => Promise<void>;\n probe?: typeof probeCodexCapacity;\n};\n\nasync function canAccess(target: string, mode: number): Promise<boolean> {\n try {\n await fsAccess(target, mode);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function isCodexInstalled(\n pathValue: string,\n checkAccess?: (target: string) => Promise<void>,\n): Promise<boolean> {\n const directories = pathValue.split(path.delimiter).filter(Boolean);\n for (const directory of directories) {\n const executable = path.join(directory, \"codex\");\n if (checkAccess) {\n try {\n await checkAccess(executable);\n return true;\n } catch {\n continue;\n }\n }\n if (await canAccess(executable, constants.X_OK)) return true;\n }\n return false;\n}\n\nexport async function getCodexCapacityReport(\n options: CapacityProbeOptions = {},\n): Promise<CapacityReport> {\n const generatedAt = (options.now?.() ?? new Date()).toISOString();\n const installed = await isCodexInstalled(options.path ?? process.env.PATH ?? \"\", options.access);\n try {\n return await (options.probe ?? probeCodexCapacity)({\n installed,\n checkedAt: generatedAt,\n });\n } catch {\n return codexUnavailableReport(generatedAt);\n }\n}\n\nexport async function getZaiCapacityReport(\n options: PiZaiCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probeZaiCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n });\n}\n\nexport async function getOpenAiCapacityReport(\n options: PiOpenAiCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probeOpenAiCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n });\n}\n\nexport async function getClaudeCapacityReport(\n options: ClaudeCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probeClaudeCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n now,\n });\n}\n\nexport async function getDevinCapacityReport(\n options: DevinCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probeDevinCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n });\n}\n\nexport async function getAnthropicCapacityReport(\n options: PiAnthropicCapacityOptions = {},\n): Promise<CapacityReport> {\n const { now, ...probeOptions } = options;\n return probePiAnthropicCapacity({\n ...probeOptions,\n checkedAt: (now?.() ?? new Date()).toISOString(),\n now,\n });\n}\n"],"names":["constants","access","fsAccess","path","codexUnavailableReport","probeCodexCapacity","probeClaudeCapacity","probeDevinCapacity","probeOpenAiCapacity","probePiAnthropicCapacity","probeZaiCapacity","canAccess","target","mode","isCodexInstalled","pathValue","checkAccess","directories","split","delimiter","filter","Boolean","directory","executable","join","X_OK","getCodexCapacityReport","options","generatedAt","now","Date","toISOString","installed","process","env","PATH","probe","checkedAt","getZaiCapacityReport","probeOptions","getOpenAiCapacityReport","getClaudeCapacityReport","getDevinCapacityReport","getAnthropicCapacityReport"],"mappings":"AAAA,SAASA,SAAS,QAAQ,UAAU;AACpC,SAASC,UAAUC,QAAQ,QAAQ,mBAAmB;AACtD,OAAOC,UAAU,YAAY;AAC7B,SAASC,sBAAsB,EAAEC,kBAAkB,QAAQ,qBAAqB;AAChF,SAASC,mBAAmB,QAAoC,sBAAsB;AACtF,SAASC,kBAAkB,QAAmC,qBAAqB;AACnF,SACEC,mBAAmB,EACnBC,wBAAwB,EACxBC,gBAAgB,QAIX,kBAAkB;AAmBzB,eAAeC,UAAUC,MAAc,EAAEC,IAAY;IACnD,IAAI;QACF,MAAMX,SAASU,QAAQC;QACvB,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,eAAeC,iBACbC,SAAiB,EACjBC,WAA+C;IAE/C,MAAMC,cAAcF,UAAUG,KAAK,CAACf,KAAKgB,SAAS,EAAEC,MAAM,CAACC;IAC3D,KAAK,MAAMC,aAAaL,YAAa;QACnC,MAAMM,aAAapB,KAAKqB,IAAI,CAACF,WAAW;QACxC,IAAIN,aAAa;YACf,IAAI;gBACF,MAAMA,YAAYO;gBAClB,OAAO;YACT,EAAE,OAAM;gBACN;YACF;QACF;QACA,IAAI,MAAMZ,UAAUY,YAAYvB,UAAUyB,IAAI,GAAG,OAAO;IAC1D;IACA,OAAO;AACT;AAEA,OAAO,eAAeC,uBACpBC,UAAgC,CAAC,CAAC;IAElC,MAAMC,cAAc,AAACD,CAAAA,QAAQE,GAAG,QAAQ,IAAIC,MAAK,EAAGC,WAAW;IAC/D,MAAMC,YAAY,MAAMlB,iBAAiBa,QAAQxB,IAAI,IAAI8B,QAAQC,GAAG,CAACC,IAAI,IAAI,IAAIR,QAAQ1B,MAAM;IAC/F,IAAI;QACF,OAAO,MAAM,AAAC0B,CAAAA,QAAQS,KAAK,IAAI/B,kBAAiB,EAAG;YACjD2B;YACAK,WAAWT;QACb;IACF,EAAE,OAAM;QACN,OAAOxB,uBAAuBwB;IAChC;AACF;AAEA,OAAO,eAAeU,qBACpBX,UAAgC,CAAC,CAAC;IAElC,MAAM,EAAEE,GAAG,EAAE,GAAGU,cAAc,GAAGZ;IACjC,OAAOjB,iBAAiB;QACtB,GAAG6B,YAAY;QACfF,WAAW,AAACR,CAAAA,WAAW,IAAIC,MAAK,EAAGC,WAAW;IAChD;AACF;AAEA,OAAO,eAAeS,wBACpBb,UAAmC,CAAC,CAAC;IAErC,MAAM,EAAEE,GAAG,EAAE,GAAGU,cAAc,GAAGZ;IACjC,OAAOnB,oBAAoB;QACzB,GAAG+B,YAAY;QACfF,WAAW,AAACR,CAAAA,WAAW,IAAIC,MAAK,EAAGC,WAAW;IAChD;AACF;AAEA,OAAO,eAAeU,wBACpBd,UAAiC,CAAC,CAAC;IAEnC,MAAM,EAAEE,GAAG,EAAE,GAAGU,cAAc,GAAGZ;IACjC,OAAOrB,oBAAoB;QACzB,GAAGiC,YAAY;QACfF,WAAW,AAACR,CAAAA,WAAW,IAAIC,MAAK,EAAGC,WAAW;QAC9CF;IACF;AACF;AAEA,OAAO,eAAea,uBACpBf,UAAgC,CAAC,CAAC;IAElC,MAAM,EAAEE,GAAG,EAAE,GAAGU,cAAc,GAAGZ;IACjC,OAAOpB,mBAAmB;QACxB,GAAGgC,YAAY;QACfF,WAAW,AAACR,CAAAA,WAAW,IAAIC,MAAK,EAAGC,WAAW;IAChD;AACF;AAEA,OAAO,eAAeY,2BACpBhB,UAAsC,CAAC,CAAC;IAExC,MAAM,EAAEE,GAAG,EAAE,GAAGU,cAAc,GAAGZ;IACjC,OAAOlB,yBAAyB;QAC9B,GAAG8B,YAAY;QACfF,WAAW,AAACR,CAAAA,WAAW,IAAIC,MAAK,EAAGC,WAAW;QAC9CF;IACF;AACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProviderCapacitySnapshot } from "../types.js";
|
|
2
|
+
import type { DevinCredential } from "../../harnesses/devin/credentials.js";
|
|
3
|
+
export type DevinRequestOptions = {
|
|
4
|
+
fetch?: typeof globalThis.fetch;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare class DevinHttpError extends Error {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
constructor(status: number, message: string);
|
|
10
|
+
}
|
|
11
|
+
export declare function parseDevinCapacity(raw: unknown): ProviderCapacitySnapshot;
|
|
12
|
+
export declare function fetchDevinCapacity(credential: DevinCredential, options?: DevinRequestOptions): Promise<ProviderCapacitySnapshot>;
|
|
13
|
+
//# sourceMappingURL=devin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devin.d.ts","sourceRoot":"","sources":["../../../src/capacity/providers/devin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgC,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAQ5E,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,cAAe,SAAQ,KAAK;aAErB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;CAKlB;AA8BD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,wBAAwB,CAqCzE;AAED,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,eAAe,EAC3B,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,wBAAwB,CAAC,CA0BnC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const DEVIN_WEBAPP_HOST = "https://app.devin.ai";
|
|
2
|
+
const DAY_MINUTES = 24 * 60;
|
|
3
|
+
const WEEK_MINUTES = 7 * 24 * 60;
|
|
4
|
+
export class DevinHttpError extends Error {
|
|
5
|
+
status;
|
|
6
|
+
constructor(status, message){
|
|
7
|
+
super(message), this.status = status;
|
|
8
|
+
this.name = "DevinHttpError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function record(value) {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13
|
+
}
|
|
14
|
+
function percent(value) {
|
|
15
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
16
|
+
return Math.max(0, Math.min(100, value));
|
|
17
|
+
}
|
|
18
|
+
function timestamp(value) {
|
|
19
|
+
if (typeof value !== "string" || !value) return null;
|
|
20
|
+
const ms = Date.parse(value);
|
|
21
|
+
return Number.isNaN(ms) ? null : new Date(ms).toISOString();
|
|
22
|
+
}
|
|
23
|
+
function finiteNumber(value) {
|
|
24
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
25
|
+
}
|
|
26
|
+
function availability(windows) {
|
|
27
|
+
if (windows.length === 0) return "unknown";
|
|
28
|
+
return windows.some((window)=>window.usedPercent !== null && window.usedPercent < 100) ? "yes" : "no";
|
|
29
|
+
}
|
|
30
|
+
export function parseDevinCapacity(raw) {
|
|
31
|
+
const root = record(raw);
|
|
32
|
+
if (!root) throw new Error("Invalid Devin quota response");
|
|
33
|
+
if (root.is_quota_plan !== true || root.has_quota_allocation !== true) {
|
|
34
|
+
return {
|
|
35
|
+
authenticated: true,
|
|
36
|
+
available: "unknown",
|
|
37
|
+
windows: [],
|
|
38
|
+
creditsRemaining: null
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const windows = [];
|
|
42
|
+
if (root.hide_daily_quota !== true) {
|
|
43
|
+
windows.push({
|
|
44
|
+
id: "devin:daily",
|
|
45
|
+
label: "Daily",
|
|
46
|
+
durationMinutes: DAY_MINUTES,
|
|
47
|
+
usedPercent: percent(root.daily_percentage),
|
|
48
|
+
resetsAt: timestamp(root.daily_reset_at)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
windows.push({
|
|
52
|
+
id: "devin:weekly",
|
|
53
|
+
label: "Weekly",
|
|
54
|
+
durationMinutes: WEEK_MINUTES,
|
|
55
|
+
usedPercent: percent(root.weekly_percentage),
|
|
56
|
+
resetsAt: timestamp(root.weekly_reset_at)
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
authenticated: true,
|
|
60
|
+
available: availability(windows),
|
|
61
|
+
windows,
|
|
62
|
+
creditsRemaining: finiteNumber(root.overage_balance)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function fetchDevinCapacity(credential, options = {}) {
|
|
66
|
+
const url = `${DEVIN_WEBAPP_HOST}/api/${encodeURIComponent(credential.orgId)}/billing/quota/usage`;
|
|
67
|
+
let response;
|
|
68
|
+
try {
|
|
69
|
+
response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
70
|
+
method: "GET",
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: `Bearer ${credential.key}`
|
|
73
|
+
},
|
|
74
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 5000)
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error("Devin quota request failed");
|
|
78
|
+
}
|
|
79
|
+
if (response.status !== 200) {
|
|
80
|
+
throw new DevinHttpError(response.status, `Devin quota request failed: HTTP ${response.status}`);
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return parseDevinCapacity(await response.json());
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof SyntaxError) {
|
|
86
|
+
throw new Error("Devin quota response is not valid JSON");
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=devin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/capacity/providers/devin.ts"],"sourcesContent":["import type { Availability, CapacityWindow, ProviderCapacitySnapshot } from \"../types.js\";\nimport type { DevinCredential } from \"../../harnesses/devin/credentials.js\";\n\nconst DEVIN_WEBAPP_HOST = \"https://app.devin.ai\";\nconst DAY_MINUTES = 24 * 60;\nconst WEEK_MINUTES = 7 * 24 * 60;\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type DevinRequestOptions = {\n fetch?: typeof globalThis.fetch;\n timeoutMs?: number;\n};\n\nexport class DevinHttpError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n ) {\n super(message);\n this.name = \"DevinHttpError\";\n }\n}\n\nfunction record(value: unknown): UnknownRecord | null {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? (value as UnknownRecord)\n : null;\n}\n\nfunction percent(value: unknown): number | null {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return null;\n return Math.max(0, Math.min(100, value));\n}\n\nfunction timestamp(value: unknown): string | null {\n if (typeof value !== \"string\" || !value) return null;\n const ms = Date.parse(value);\n return Number.isNaN(ms) ? null : new Date(ms).toISOString();\n}\n\nfunction finiteNumber(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction availability(windows: CapacityWindow[]): Availability {\n if (windows.length === 0) return \"unknown\";\n return windows.some((window) => window.usedPercent !== null && window.usedPercent < 100)\n ? \"yes\"\n : \"no\";\n}\n\nexport function parseDevinCapacity(raw: unknown): ProviderCapacitySnapshot {\n const root = record(raw);\n if (!root) throw new Error(\"Invalid Devin quota response\");\n\n if (root.is_quota_plan !== true || root.has_quota_allocation !== true) {\n return {\n authenticated: true,\n available: \"unknown\",\n windows: [],\n creditsRemaining: null,\n };\n }\n\n const windows: CapacityWindow[] = [];\n if (root.hide_daily_quota !== true) {\n windows.push({\n id: \"devin:daily\",\n label: \"Daily\",\n durationMinutes: DAY_MINUTES,\n usedPercent: percent(root.daily_percentage),\n resetsAt: timestamp(root.daily_reset_at),\n });\n }\n windows.push({\n id: \"devin:weekly\",\n label: \"Weekly\",\n durationMinutes: WEEK_MINUTES,\n usedPercent: percent(root.weekly_percentage),\n resetsAt: timestamp(root.weekly_reset_at),\n });\n\n return {\n authenticated: true,\n available: availability(windows),\n windows,\n creditsRemaining: finiteNumber(root.overage_balance),\n };\n}\n\nexport async function fetchDevinCapacity(\n credential: DevinCredential,\n options: DevinRequestOptions = {},\n): Promise<ProviderCapacitySnapshot> {\n const url = `${DEVIN_WEBAPP_HOST}/api/${encodeURIComponent(credential.orgId)}/billing/quota/usage`;\n let response: Response;\n try {\n response = await (options.fetch ?? globalThis.fetch)(url, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${credential.key}` },\n signal: AbortSignal.timeout(options.timeoutMs ?? 5000),\n });\n } catch {\n throw new Error(\"Devin quota request failed\");\n }\n if (response.status !== 200) {\n throw new DevinHttpError(\n response.status,\n `Devin quota request failed: HTTP ${response.status}`,\n );\n }\n try {\n return parseDevinCapacity(await response.json());\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(\"Devin quota response is not valid JSON\");\n }\n throw error;\n }\n}\n"],"names":["DEVIN_WEBAPP_HOST","DAY_MINUTES","WEEK_MINUTES","DevinHttpError","Error","status","message","name","record","value","Array","isArray","percent","Number","isFinite","Math","max","min","timestamp","ms","Date","parse","isNaN","toISOString","finiteNumber","availability","windows","length","some","window","usedPercent","parseDevinCapacity","raw","root","is_quota_plan","has_quota_allocation","authenticated","available","creditsRemaining","hide_daily_quota","push","id","label","durationMinutes","daily_percentage","resetsAt","daily_reset_at","weekly_percentage","weekly_reset_at","overage_balance","fetchDevinCapacity","credential","options","url","encodeURIComponent","orgId","response","fetch","globalThis","method","headers","Authorization","key","signal","AbortSignal","timeout","timeoutMs","json","error","SyntaxError"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CapacityReport } from "../types.js";
|
|
2
|
+
import { type DevinCredentialOptions } from "../../harnesses/devin/credentials.js";
|
|
3
|
+
export type DevinCapacityOptions = DevinCredentialOptions & {
|
|
4
|
+
now?: () => Date;
|
|
5
|
+
fetch?: typeof globalThis.fetch;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
};
|
|
8
|
+
export type DevinProbeOptions = DevinCapacityOptions & {
|
|
9
|
+
checkedAt: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function probeDevinCapacity(options: DevinProbeOptions): Promise<CapacityReport>;
|
|
12
|
+
//# sourceMappingURL=devin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devin.d.ts","sourceRoot":"","sources":["../../../src/capacity/sources/devin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,sCAAsC,CAAC;AAE9C,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,GAAG;IAC1D,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,GAAG;IACrD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAcF,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CAmB5F"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DevinHttpError, fetchDevinCapacity } from "../providers/devin.js";
|
|
2
|
+
import { resolveDevinCredential } from "../../harnesses/devin/credentials.js";
|
|
3
|
+
function devinReport(snapshot, checkedAt) {
|
|
4
|
+
return {
|
|
5
|
+
harness: "devin",
|
|
6
|
+
provider: "devin",
|
|
7
|
+
generatedAt: checkedAt,
|
|
8
|
+
...snapshot
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export async function probeDevinCapacity(options) {
|
|
12
|
+
const credential = await resolveDevinCredential(options);
|
|
13
|
+
try {
|
|
14
|
+
const snapshot = await fetchDevinCapacity(credential, options);
|
|
15
|
+
return devinReport(snapshot, options.checkedAt);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error instanceof DevinHttpError && (error.status === 401 || error.status === 403)) {
|
|
18
|
+
return devinReport({
|
|
19
|
+
authenticated: false,
|
|
20
|
+
available: "unknown",
|
|
21
|
+
windows: [],
|
|
22
|
+
creditsRemaining: null
|
|
23
|
+
}, options.checkedAt);
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=devin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/capacity/sources/devin.ts"],"sourcesContent":["import { DevinHttpError, fetchDevinCapacity } from \"../providers/devin.js\";\nimport type { CapacityReport } from \"../types.js\";\nimport {\n resolveDevinCredential,\n type DevinCredentialOptions,\n} from \"../../harnesses/devin/credentials.js\";\n\nexport type DevinCapacityOptions = DevinCredentialOptions & {\n now?: () => Date;\n fetch?: typeof globalThis.fetch;\n timeoutMs?: number;\n};\n\nexport type DevinProbeOptions = DevinCapacityOptions & {\n checkedAt: string;\n};\n\nfunction devinReport(\n snapshot: Omit<CapacityReport, \"harness\" | \"provider\" | \"generatedAt\">,\n checkedAt: string,\n): CapacityReport {\n return {\n harness: \"devin\",\n provider: \"devin\",\n generatedAt: checkedAt,\n ...snapshot,\n };\n}\n\nexport async function probeDevinCapacity(options: DevinProbeOptions): Promise<CapacityReport> {\n const credential = await resolveDevinCredential(options);\n try {\n const snapshot = await fetchDevinCapacity(credential, options);\n return devinReport(snapshot, options.checkedAt);\n } catch (error) {\n if (error instanceof DevinHttpError && (error.status === 401 || error.status === 403)) {\n return devinReport(\n {\n authenticated: false,\n available: \"unknown\",\n windows: [],\n creditsRemaining: null,\n },\n options.checkedAt,\n );\n }\n throw error;\n }\n}\n"],"names":["DevinHttpError","fetchDevinCapacity","resolveDevinCredential","devinReport","snapshot","checkedAt","harness","provider","generatedAt","probeDevinCapacity","options","credential","error","status","authenticated","available","windows","creditsRemaining"],"mappings":"AAAA,SAASA,cAAc,EAAEC,kBAAkB,QAAQ,wBAAwB;AAE3E,SACEC,sBAAsB,QAEjB,uCAAuC;AAY9C,SAASC,YACPC,QAAsE,EACtEC,SAAiB;IAEjB,OAAO;QACLC,SAAS;QACTC,UAAU;QACVC,aAAaH;QACb,GAAGD,QAAQ;IACb;AACF;AAEA,OAAO,eAAeK,mBAAmBC,OAA0B;IACjE,MAAMC,aAAa,MAAMT,uBAAuBQ;IAChD,IAAI;QACF,MAAMN,WAAW,MAAMH,mBAAmBU,YAAYD;QACtD,OAAOP,YAAYC,UAAUM,QAAQL,SAAS;IAChD,EAAE,OAAOO,OAAO;QACd,IAAIA,iBAAiBZ,kBAAmBY,CAAAA,MAAMC,MAAM,KAAK,OAAOD,MAAMC,MAAM,KAAK,GAAE,GAAI;YACrF,OAAOV,YACL;gBACEW,eAAe;gBACfC,WAAW;gBACXC,SAAS,EAAE;gBACXC,kBAAkB;YACpB,GACAP,QAAQL,SAAS;QAErB;QACA,MAAMO;IACR;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type DevinCredential = {
|
|
2
|
+
key: string;
|
|
3
|
+
orgId: string;
|
|
4
|
+
};
|
|
5
|
+
export type DevinCredentialOptions = {
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
|
+
readFile?: (path: string, encoding: BufferEncoding) => Promise<string>;
|
|
8
|
+
};
|
|
9
|
+
export declare function resolveDevinCredential(options?: DevinCredentialOptions): Promise<DevinCredential>;
|
|
10
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/harnesses/devin/credentials.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACxE,CAAC;AAkEF,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,CAK1B"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const DEVIN_KEY_MISSING = "Devin API key not found; set DEVIN_API_KEY or log in with the Devin CLI";
|
|
5
|
+
const DEVIN_ORG_MISSING = "Devin organization id not found; set DEVIN_ORG_ID or log in with the Devin CLI";
|
|
6
|
+
function record(value) {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8
|
+
}
|
|
9
|
+
function nonEmptyText(value) {
|
|
10
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
11
|
+
}
|
|
12
|
+
async function readText(path, options) {
|
|
13
|
+
try {
|
|
14
|
+
return await (options.readFile ?? readFile)(path, "utf8");
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (record(error)?.code === "ENOENT") return null;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function parseFlatToml(contents) {
|
|
21
|
+
const entries = {};
|
|
22
|
+
for (const line of contents.split("\n")){
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("[")) continue;
|
|
25
|
+
const match = /^([A-Za-z0-9_]+)\s*=\s*"(.*)"\s*$/.exec(trimmed);
|
|
26
|
+
if (!match) return null;
|
|
27
|
+
entries[match[1]] = match[2];
|
|
28
|
+
}
|
|
29
|
+
return entries;
|
|
30
|
+
}
|
|
31
|
+
async function resolveKey(home, options) {
|
|
32
|
+
const envKey = nonEmptyText(options.env?.DEVIN_API_KEY ?? process.env.DEVIN_API_KEY);
|
|
33
|
+
if (envKey) return envKey;
|
|
34
|
+
const contents = await readText(join(home, ".local", "share", "devin", "credentials.toml"), options);
|
|
35
|
+
if (contents === null) throw new Error(DEVIN_KEY_MISSING);
|
|
36
|
+
const parsed = parseFlatToml(contents);
|
|
37
|
+
if (parsed === null) throw new Error("Devin credentials file is malformed");
|
|
38
|
+
const key = nonEmptyText(parsed.windsurf_api_key);
|
|
39
|
+
if (!key) throw new Error(DEVIN_KEY_MISSING);
|
|
40
|
+
return key;
|
|
41
|
+
}
|
|
42
|
+
async function resolveOrgId(home, options) {
|
|
43
|
+
const envOrg = nonEmptyText(options.env?.DEVIN_ORG_ID ?? process.env.DEVIN_ORG_ID);
|
|
44
|
+
if (envOrg) return envOrg;
|
|
45
|
+
const contents = await readText(join(home, ".config", "devin", "config.json"), options);
|
|
46
|
+
if (contents === null) throw new Error(DEVIN_ORG_MISSING);
|
|
47
|
+
const parsed = record(JSON.parse(contents));
|
|
48
|
+
if (!parsed) throw new Error("Devin config file is malformed");
|
|
49
|
+
const orgId = nonEmptyText(record(parsed.devin)?.org_id);
|
|
50
|
+
if (!orgId) throw new Error(DEVIN_ORG_MISSING);
|
|
51
|
+
return orgId;
|
|
52
|
+
}
|
|
53
|
+
export async function resolveDevinCredential(options = {}) {
|
|
54
|
+
const env = options.env ?? process.env;
|
|
55
|
+
const home = env.HOME || homedir();
|
|
56
|
+
const [key, orgId] = await Promise.all([
|
|
57
|
+
resolveKey(home, options),
|
|
58
|
+
resolveOrgId(home, options)
|
|
59
|
+
]);
|
|
60
|
+
return {
|
|
61
|
+
key,
|
|
62
|
+
orgId
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/harnesses/devin/credentials.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type DevinCredential = {\n key: string;\n orgId: string;\n};\n\nexport type DevinCredentialOptions = {\n env?: NodeJS.ProcessEnv;\n readFile?: (path: string, encoding: BufferEncoding) => Promise<string>;\n};\n\nconst DEVIN_KEY_MISSING = \"Devin API key not found; set DEVIN_API_KEY or log in with the Devin CLI\";\nconst DEVIN_ORG_MISSING =\n \"Devin organization id not found; set DEVIN_ORG_ID or log in with the Devin CLI\";\n\nfunction record(value: unknown): UnknownRecord | null {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? (value as UnknownRecord)\n : null;\n}\n\nfunction nonEmptyText(value: unknown): string | null {\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nasync function readText(path: string, options: DevinCredentialOptions): Promise<string | null> {\n try {\n return await (options.readFile ?? readFile)(path, \"utf8\");\n } catch (error) {\n if (record(error)?.code === \"ENOENT\") return null;\n throw error;\n }\n}\n\nfunction parseFlatToml(contents: string): UnknownRecord | null {\n const entries: UnknownRecord = {};\n for (const line of contents.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\") || trimmed.startsWith(\"[\")) continue;\n const match = /^([A-Za-z0-9_]+)\\s*=\\s*\"(.*)\"\\s*$/.exec(trimmed);\n if (!match) return null;\n entries[match[1]] = match[2];\n }\n return entries;\n}\n\nasync function resolveKey(home: string, options: DevinCredentialOptions): Promise<string> {\n const envKey = nonEmptyText(options.env?.DEVIN_API_KEY ?? process.env.DEVIN_API_KEY);\n if (envKey) return envKey;\n\n const contents = await readText(\n join(home, \".local\", \"share\", \"devin\", \"credentials.toml\"),\n options,\n );\n if (contents === null) throw new Error(DEVIN_KEY_MISSING);\n const parsed = parseFlatToml(contents);\n if (parsed === null) throw new Error(\"Devin credentials file is malformed\");\n const key = nonEmptyText(parsed.windsurf_api_key);\n if (!key) throw new Error(DEVIN_KEY_MISSING);\n return key;\n}\n\nasync function resolveOrgId(home: string, options: DevinCredentialOptions): Promise<string> {\n const envOrg = nonEmptyText(options.env?.DEVIN_ORG_ID ?? process.env.DEVIN_ORG_ID);\n if (envOrg) return envOrg;\n\n const contents = await readText(join(home, \".config\", \"devin\", \"config.json\"), options);\n if (contents === null) throw new Error(DEVIN_ORG_MISSING);\n const parsed = record(JSON.parse(contents));\n if (!parsed) throw new Error(\"Devin config file is malformed\");\n const orgId = nonEmptyText(record(parsed.devin)?.org_id);\n if (!orgId) throw new Error(DEVIN_ORG_MISSING);\n return orgId;\n}\n\nexport async function resolveDevinCredential(\n options: DevinCredentialOptions = {},\n): Promise<DevinCredential> {\n const env = options.env ?? process.env;\n const home = env.HOME || homedir();\n const [key, orgId] = await Promise.all([resolveKey(home, options), resolveOrgId(home, options)]);\n return { key, orgId };\n}\n"],"names":["readFile","homedir","join","DEVIN_KEY_MISSING","DEVIN_ORG_MISSING","record","value","Array","isArray","nonEmptyText","trim","readText","path","options","error","code","parseFlatToml","contents","entries","line","split","trimmed","startsWith","match","exec","resolveKey","home","envKey","env","DEVIN_API_KEY","process","Error","parsed","key","windsurf_api_key","resolveOrgId","envOrg","DEVIN_ORG_ID","JSON","parse","orgId","devin","org_id","resolveDevinCredential","HOME","Promise","all"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,mBAAmB;AAC5C,SAASC,OAAO,QAAQ,UAAU;AAClC,SAASC,IAAI,QAAQ,YAAY;AAcjC,MAAMC,oBAAoB;AAC1B,MAAMC,oBACJ;AAEF,SAASC,OAAOC,KAAc;IAC5B,OAAOA,UAAU,QAAQ,OAAOA,UAAU,YAAY,CAACC,MAAMC,OAAO,CAACF,SAChEA,QACD;AACN;AAEA,SAASG,aAAaH,KAAc;IAClC,OAAO,OAAOA,UAAU,YAAYA,MAAMI,IAAI,KAAKJ,MAAMI,IAAI,KAAK;AACpE;AAEA,eAAeC,SAASC,IAAY,EAAEC,OAA+B;IACnE,IAAI;QACF,OAAO,MAAM,AAACA,CAAAA,QAAQb,QAAQ,IAAIA,QAAO,EAAGY,MAAM;IACpD,EAAE,OAAOE,OAAO;QACd,IAAIT,OAAOS,QAAQC,SAAS,UAAU,OAAO;QAC7C,MAAMD;IACR;AACF;AAEA,SAASE,cAAcC,QAAgB;IACrC,MAAMC,UAAyB,CAAC;IAChC,KAAK,MAAMC,QAAQF,SAASG,KAAK,CAAC,MAAO;QACvC,MAAMC,UAAUF,KAAKT,IAAI;QACzB,IAAI,CAACW,WAAWA,QAAQC,UAAU,CAAC,QAAQD,QAAQC,UAAU,CAAC,MAAM;QACpE,MAAMC,QAAQ,oCAAoCC,IAAI,CAACH;QACvD,IAAI,CAACE,OAAO,OAAO;QACnBL,OAAO,CAACK,KAAK,CAAC,EAAE,CAAC,GAAGA,KAAK,CAAC,EAAE;IAC9B;IACA,OAAOL;AACT;AAEA,eAAeO,WAAWC,IAAY,EAAEb,OAA+B;IACrE,MAAMc,SAASlB,aAAaI,QAAQe,GAAG,EAAEC,iBAAiBC,QAAQF,GAAG,CAACC,aAAa;IACnF,IAAIF,QAAQ,OAAOA;IAEnB,MAAMV,WAAW,MAAMN,SACrBT,KAAKwB,MAAM,UAAU,SAAS,SAAS,qBACvCb;IAEF,IAAII,aAAa,MAAM,MAAM,IAAIc,MAAM5B;IACvC,MAAM6B,SAAShB,cAAcC;IAC7B,IAAIe,WAAW,MAAM,MAAM,IAAID,MAAM;IACrC,MAAME,MAAMxB,aAAauB,OAAOE,gBAAgB;IAChD,IAAI,CAACD,KAAK,MAAM,IAAIF,MAAM5B;IAC1B,OAAO8B;AACT;AAEA,eAAeE,aAAaT,IAAY,EAAEb,OAA+B;IACvE,MAAMuB,SAAS3B,aAAaI,QAAQe,GAAG,EAAES,gBAAgBP,QAAQF,GAAG,CAACS,YAAY;IACjF,IAAID,QAAQ,OAAOA;IAEnB,MAAMnB,WAAW,MAAMN,SAAST,KAAKwB,MAAM,WAAW,SAAS,gBAAgBb;IAC/E,IAAII,aAAa,MAAM,MAAM,IAAIc,MAAM3B;IACvC,MAAM4B,SAAS3B,OAAOiC,KAAKC,KAAK,CAACtB;IACjC,IAAI,CAACe,QAAQ,MAAM,IAAID,MAAM;IAC7B,MAAMS,QAAQ/B,aAAaJ,OAAO2B,OAAOS,KAAK,GAAGC;IACjD,IAAI,CAACF,OAAO,MAAM,IAAIT,MAAM3B;IAC5B,OAAOoC;AACT;AAEA,OAAO,eAAeG,uBACpB9B,UAAkC,CAAC,CAAC;IAEpC,MAAMe,MAAMf,QAAQe,GAAG,IAAIE,QAAQF,GAAG;IACtC,MAAMF,OAAOE,IAAIgB,IAAI,IAAI3C;IACzB,MAAM,CAACgC,KAAKO,MAAM,GAAG,MAAMK,QAAQC,GAAG,CAAC;QAACrB,WAAWC,MAAMb;QAAUsB,aAAaT,MAAMb;KAAS;IAC/F,OAAO;QAAEoB;QAAKO;IAAM;AACtB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AgentManager } from "./AgentManager.js";
|
|
2
|
-
export { getAnthropicCapacityReport, getClaudeCapacityReport, getCodexCapacityReport, getOpenAiCapacityReport, getZaiCapacityReport, } from "./capacity/index.js";
|
|
2
|
+
export { getAnthropicCapacityReport, getClaudeCapacityReport, getCodexCapacityReport, getDevinCapacityReport, getOpenAiCapacityReport, getZaiCapacityReport, } from "./capacity/index.js";
|
|
3
3
|
export type { CapacityReport, CapacityWindow } from "./capacity/index.js";
|
|
4
4
|
export { getAgentReadinessReports, worstReadinessStatus, } from "./harnesses/readiness/AgentReadiness.js";
|
|
5
5
|
export type { AgentReadinessOptions, AgentReadinessReport, ReadinessAgentType, ReadinessStatus, } from "./harnesses/readiness/AgentReadiness.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1E,OAAO,EACL,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,yCAAyC,CAAC;AACjD,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,GAChB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EACV,YAAY,EACZ,SAAS,EACT,SAAS,EACT,mBAAmB,EACnB,cAAc,EACd,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EACL,UAAU,EACV,SAAS,EACT,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uDAAuD,CAAC;AAChG,OAAO,EAAE,sBAAsB,EAAE,MAAM,qDAAqD,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1E,OAAO,EACL,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,yCAAyC,CAAC;AACjD,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,GAChB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EACV,YAAY,EACZ,SAAS,EACT,SAAS,EACT,mBAAmB,EACnB,cAAc,EACd,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EACL,UAAU,EACV,SAAS,EACT,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,GAC5B,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uDAAuD,CAAC;AAChG,OAAO,EAAE,sBAAsB,EAAE,MAAM,qDAAqD,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AgentManager } from "./AgentManager.js";
|
|
2
|
-
export { getAnthropicCapacityReport, getClaudeCapacityReport, getCodexCapacityReport, getOpenAiCapacityReport, getZaiCapacityReport } from "./capacity/index.js";
|
|
2
|
+
export { getAnthropicCapacityReport, getClaudeCapacityReport, getCodexCapacityReport, getDevinCapacityReport, getOpenAiCapacityReport, getZaiCapacityReport } from "./capacity/index.js";
|
|
3
3
|
export { getAgentReadinessReports, worstReadinessStatus } from "./harnesses/readiness/AgentReadiness.js";
|
|
4
4
|
export { createBuiltinAdapters } from "./harnesses/index.js";
|
|
5
5
|
export { HARNESS_RUNTIME_PROFILES } from "./harnesses/runtimeProfiles.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { AgentManager } from \"./AgentManager.js\";\nexport {\n getAnthropicCapacityReport,\n getClaudeCapacityReport,\n getCodexCapacityReport,\n getOpenAiCapacityReport,\n getZaiCapacityReport,\n} from \"./capacity/index.js\";\nexport type { CapacityReport, CapacityWindow } from \"./capacity/index.js\";\n\nexport {\n getAgentReadinessReports,\n worstReadinessStatus,\n} from \"./harnesses/readiness/AgentReadiness.js\";\nexport type {\n AgentReadinessOptions,\n AgentReadinessReport,\n ReadinessAgentType,\n ReadinessStatus,\n} from \"./harnesses/readiness/AgentReadiness.js\";\n\nexport { createBuiltinAdapters } from \"./harnesses/index.js\";\nexport { HARNESS_RUNTIME_PROFILES, type StartableAgentType } from \"./harnesses/runtimeProfiles.js\";\nexport { AGENT_TYPES, AgentStatus } from \"./adapters/AgentAdapter.js\";\nexport type {\n AgentAdapter,\n AgentType,\n AgentInfo,\n ConversationMessage,\n SessionSummary,\n ListSessionsOptions,\n} from \"./adapters/AgentAdapter.js\";\n\nexport { TerminalFocusManager } from \"./terminal/TerminalFocusManager.js\";\nexport { TerminalType } from \"./terminal/types.js\";\nexport type { TerminalLocation } from \"./terminal/types.js\";\nexport { TtyWriter } from \"./terminal/TtyWriter.js\";\n\nexport {\n AgentRegistry,\n RenameNotFoundError,\n RenameConflictError,\n AGENT_RUNTIME_PROVIDERS,\n parseTmuxRuntimeRef,\n} from \"./utils/AgentRegistry.js\";\nexport type { AgentRuntimeProvider } from \"./utils/AgentRegistry.js\";\nexport type { InteractiveAgentRuntime } from \"./runtime/types.js\";\nexport {\n startAgent,\n stopAgent,\n focusAgent,\n sendAgentPrompt,\n TmuxUnavailableError,\n AgentNameInUseError,\n AgentPidPollTimeoutError,\n AgentRuntimeUnavailableError,\n AgentTerminalNotFoundError,\n DEFAULT_PID_POLL_TIMEOUT_MS,\n} from \"./runtime/ManagedAgentRuntime.js\";\n\nexport type { AgentRequest } from \"./utils/agent-requests.js\";\nexport { readLatestAgentRequest, writeAgentRequest } from \"./utils/agent-requests.js\";\n\nexport { AGENT_MODES } from \"./durable/DurableAgent.js\";\nexport type { DurableProvider } from \"./durable/DurableAgent.js\";\nexport { DurableAgentRepository } from \"./durable/DurableAgentRepository.js\";\nexport { ClaudePrintAgentService } from \"./harnesses/claude/durable/ClaudePrintAgentService.js\";\nexport { CodexPrintAgentService } from \"./harnesses/codex/durable/CodexPrintAgentService.js\";\nexport { PiPrintAgentService } from \"./harnesses/pi/durable/PiPrintAgentService.js\";\n"],"names":["AgentManager","getAnthropicCapacityReport","getClaudeCapacityReport","getCodexCapacityReport","getOpenAiCapacityReport","getZaiCapacityReport","getAgentReadinessReports","worstReadinessStatus","createBuiltinAdapters","HARNESS_RUNTIME_PROFILES","AGENT_TYPES","AgentStatus","TerminalFocusManager","TerminalType","TtyWriter","AgentRegistry","RenameNotFoundError","RenameConflictError","AGENT_RUNTIME_PROVIDERS","parseTmuxRuntimeRef","startAgent","stopAgent","focusAgent","sendAgentPrompt","TmuxUnavailableError","AgentNameInUseError","AgentPidPollTimeoutError","AgentRuntimeUnavailableError","AgentTerminalNotFoundError","DEFAULT_PID_POLL_TIMEOUT_MS","readLatestAgentRequest","writeAgentRequest","AGENT_MODES","DurableAgentRepository","ClaudePrintAgentService","CodexPrintAgentService","PiPrintAgentService"],"mappings":"AAAA,SAASA,YAAY,QAAQ,oBAAoB;AACjD,SACEC,0BAA0B,EAC1BC,uBAAuB,EACvBC,sBAAsB,EACtBC,uBAAuB,EACvBC,oBAAoB,QACf,sBAAsB;AAG7B,SACEC,wBAAwB,EACxBC,oBAAoB,QACf,0CAA0C;AAQjD,SAASC,qBAAqB,QAAQ,uBAAuB;AAC7D,SAASC,wBAAwB,QAAiC,iCAAiC;AACnG,SAASC,WAAW,EAAEC,WAAW,QAAQ,6BAA6B;AAUtE,SAASC,oBAAoB,QAAQ,qCAAqC;AAC1E,SAASC,YAAY,QAAQ,sBAAsB;AAEnD,SAASC,SAAS,QAAQ,0BAA0B;AAEpD,SACEC,aAAa,EACbC,mBAAmB,EACnBC,mBAAmB,EACnBC,uBAAuB,EACvBC,mBAAmB,QACd,2BAA2B;AAGlC,SACEC,UAAU,EACVC,SAAS,EACTC,UAAU,EACVC,eAAe,EACfC,oBAAoB,EACpBC,mBAAmB,EACnBC,wBAAwB,EACxBC,4BAA4B,EAC5BC,0BAA0B,EAC1BC,2BAA2B,QACtB,mCAAmC;AAG1C,SAASC,sBAAsB,EAAEC,iBAAiB,QAAQ,4BAA4B;AAEtF,SAASC,WAAW,QAAQ,4BAA4B;AAExD,SAASC,sBAAsB,QAAQ,sCAAsC;AAC7E,SAASC,uBAAuB,QAAQ,wDAAwD;AAChG,SAASC,sBAAsB,QAAQ,sDAAsD;AAC7F,SAASC,mBAAmB,QAAQ,gDAAgD"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { AgentManager } from \"./AgentManager.js\";\nexport {\n getAnthropicCapacityReport,\n getClaudeCapacityReport,\n getCodexCapacityReport,\n getDevinCapacityReport,\n getOpenAiCapacityReport,\n getZaiCapacityReport,\n} from \"./capacity/index.js\";\nexport type { CapacityReport, CapacityWindow } from \"./capacity/index.js\";\n\nexport {\n getAgentReadinessReports,\n worstReadinessStatus,\n} from \"./harnesses/readiness/AgentReadiness.js\";\nexport type {\n AgentReadinessOptions,\n AgentReadinessReport,\n ReadinessAgentType,\n ReadinessStatus,\n} from \"./harnesses/readiness/AgentReadiness.js\";\n\nexport { createBuiltinAdapters } from \"./harnesses/index.js\";\nexport { HARNESS_RUNTIME_PROFILES, type StartableAgentType } from \"./harnesses/runtimeProfiles.js\";\nexport { AGENT_TYPES, AgentStatus } from \"./adapters/AgentAdapter.js\";\nexport type {\n AgentAdapter,\n AgentType,\n AgentInfo,\n ConversationMessage,\n SessionSummary,\n ListSessionsOptions,\n} from \"./adapters/AgentAdapter.js\";\n\nexport { TerminalFocusManager } from \"./terminal/TerminalFocusManager.js\";\nexport { TerminalType } from \"./terminal/types.js\";\nexport type { TerminalLocation } from \"./terminal/types.js\";\nexport { TtyWriter } from \"./terminal/TtyWriter.js\";\n\nexport {\n AgentRegistry,\n RenameNotFoundError,\n RenameConflictError,\n AGENT_RUNTIME_PROVIDERS,\n parseTmuxRuntimeRef,\n} from \"./utils/AgentRegistry.js\";\nexport type { AgentRuntimeProvider } from \"./utils/AgentRegistry.js\";\nexport type { InteractiveAgentRuntime } from \"./runtime/types.js\";\nexport {\n startAgent,\n stopAgent,\n focusAgent,\n sendAgentPrompt,\n TmuxUnavailableError,\n AgentNameInUseError,\n AgentPidPollTimeoutError,\n AgentRuntimeUnavailableError,\n AgentTerminalNotFoundError,\n DEFAULT_PID_POLL_TIMEOUT_MS,\n} from \"./runtime/ManagedAgentRuntime.js\";\n\nexport type { AgentRequest } from \"./utils/agent-requests.js\";\nexport { readLatestAgentRequest, writeAgentRequest } from \"./utils/agent-requests.js\";\n\nexport { AGENT_MODES } from \"./durable/DurableAgent.js\";\nexport type { DurableProvider } from \"./durable/DurableAgent.js\";\nexport { DurableAgentRepository } from \"./durable/DurableAgentRepository.js\";\nexport { ClaudePrintAgentService } from \"./harnesses/claude/durable/ClaudePrintAgentService.js\";\nexport { CodexPrintAgentService } from \"./harnesses/codex/durable/CodexPrintAgentService.js\";\nexport { PiPrintAgentService } from \"./harnesses/pi/durable/PiPrintAgentService.js\";\n"],"names":["AgentManager","getAnthropicCapacityReport","getClaudeCapacityReport","getCodexCapacityReport","getDevinCapacityReport","getOpenAiCapacityReport","getZaiCapacityReport","getAgentReadinessReports","worstReadinessStatus","createBuiltinAdapters","HARNESS_RUNTIME_PROFILES","AGENT_TYPES","AgentStatus","TerminalFocusManager","TerminalType","TtyWriter","AgentRegistry","RenameNotFoundError","RenameConflictError","AGENT_RUNTIME_PROVIDERS","parseTmuxRuntimeRef","startAgent","stopAgent","focusAgent","sendAgentPrompt","TmuxUnavailableError","AgentNameInUseError","AgentPidPollTimeoutError","AgentRuntimeUnavailableError","AgentTerminalNotFoundError","DEFAULT_PID_POLL_TIMEOUT_MS","readLatestAgentRequest","writeAgentRequest","AGENT_MODES","DurableAgentRepository","ClaudePrintAgentService","CodexPrintAgentService","PiPrintAgentService"],"mappings":"AAAA,SAASA,YAAY,QAAQ,oBAAoB;AACjD,SACEC,0BAA0B,EAC1BC,uBAAuB,EACvBC,sBAAsB,EACtBC,sBAAsB,EACtBC,uBAAuB,EACvBC,oBAAoB,QACf,sBAAsB;AAG7B,SACEC,wBAAwB,EACxBC,oBAAoB,QACf,0CAA0C;AAQjD,SAASC,qBAAqB,QAAQ,uBAAuB;AAC7D,SAASC,wBAAwB,QAAiC,iCAAiC;AACnG,SAASC,WAAW,EAAEC,WAAW,QAAQ,6BAA6B;AAUtE,SAASC,oBAAoB,QAAQ,qCAAqC;AAC1E,SAASC,YAAY,QAAQ,sBAAsB;AAEnD,SAASC,SAAS,QAAQ,0BAA0B;AAEpD,SACEC,aAAa,EACbC,mBAAmB,EACnBC,mBAAmB,EACnBC,uBAAuB,EACvBC,mBAAmB,QACd,2BAA2B;AAGlC,SACEC,UAAU,EACVC,SAAS,EACTC,UAAU,EACVC,eAAe,EACfC,oBAAoB,EACpBC,mBAAmB,EACnBC,wBAAwB,EACxBC,4BAA4B,EAC5BC,0BAA0B,EAC1BC,2BAA2B,QACtB,mCAAmC;AAG1C,SAASC,sBAAsB,EAAEC,iBAAiB,QAAQ,4BAA4B;AAEtF,SAASC,WAAW,QAAQ,4BAA4B;AAExD,SAASC,sBAAsB,QAAQ,sCAAsC;AAC7E,SAASC,uBAAuB,QAAQ,wDAAwD;AAChG,SAASC,sBAAsB,QAAQ,sDAAsD;AAC7F,SAASC,mBAAmB,QAAQ,gDAAgD"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { parseDevinCapacity } from "../../capacity/providers/devin.js";
|
|
5
|
+
import { probeDevinCapacity } from "../../capacity/sources/devin.js";
|
|
6
|
+
import { resolveDevinCredential } from "../../harnesses/devin/credentials.js";
|
|
7
|
+
|
|
8
|
+
const checkedAt = "2026-10-05T10:00:00.000Z";
|
|
9
|
+
const fixture = (name: string) =>
|
|
10
|
+
readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8");
|
|
11
|
+
|
|
12
|
+
const credentialFiles = async (path: string): Promise<string> => {
|
|
13
|
+
if (path === "/users/test/.local/share/devin/credentials.toml") {
|
|
14
|
+
return fixture("devin-credentials.toml");
|
|
15
|
+
}
|
|
16
|
+
if (path === "/users/test/.config/devin/config.json") {
|
|
17
|
+
return fixture("devin-config.json");
|
|
18
|
+
}
|
|
19
|
+
throw Object.assign(new Error("missing"), { code: "ENOENT" });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("devin credential resolution", () => {
|
|
23
|
+
it("prefers DEVIN_API_KEY and DEVIN_ORG_ID without reading files", async () => {
|
|
24
|
+
const reader = vi.fn(credentialFiles);
|
|
25
|
+
await expect(
|
|
26
|
+
resolveDevinCredential({
|
|
27
|
+
env: { DEVIN_API_KEY: "env-key", DEVIN_ORG_ID: "org-env" },
|
|
28
|
+
readFile: reader,
|
|
29
|
+
}),
|
|
30
|
+
).resolves.toEqual({ key: "env-key", orgId: "org-env" });
|
|
31
|
+
expect(reader).not.toHaveBeenCalled();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("reads the key from credentials.toml and org from config.json", async () => {
|
|
35
|
+
const reader = vi.fn(credentialFiles);
|
|
36
|
+
await expect(
|
|
37
|
+
resolveDevinCredential({ env: { HOME: "/users/test" }, readFile: reader }),
|
|
38
|
+
).resolves.toEqual({ key: "devin-test-key", orgId: "org-test1234" });
|
|
39
|
+
expect(reader).toHaveBeenCalledWith("/users/test/.local/share/devin/credentials.toml", "utf8");
|
|
40
|
+
expect(reader).toHaveBeenCalledWith("/users/test/.config/devin/config.json", "utf8");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it.each([
|
|
44
|
+
[
|
|
45
|
+
"missing credentials file",
|
|
46
|
+
async (path: string) => {
|
|
47
|
+
if (path.endsWith("config.json")) return fixture("devin-config.json");
|
|
48
|
+
throw Object.assign(new Error("missing"), { code: "ENOENT" });
|
|
49
|
+
},
|
|
50
|
+
/Devin API key not found/,
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
"malformed credentials file",
|
|
54
|
+
async (path: string) => {
|
|
55
|
+
if (path.endsWith("credentials.toml")) return "not = [valid";
|
|
56
|
+
return fixture("devin-config.json");
|
|
57
|
+
},
|
|
58
|
+
/credentials file is malformed/,
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"credentials without key",
|
|
62
|
+
async (path: string) => {
|
|
63
|
+
if (path.endsWith("credentials.toml")) return 'api_server_url = "https://x"';
|
|
64
|
+
return fixture("devin-config.json");
|
|
65
|
+
},
|
|
66
|
+
/Devin API key not found/,
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"missing config file",
|
|
70
|
+
async (path: string) => {
|
|
71
|
+
if (path.endsWith("credentials.toml")) return fixture("devin-credentials.toml");
|
|
72
|
+
throw Object.assign(new Error("missing"), { code: "ENOENT" });
|
|
73
|
+
},
|
|
74
|
+
/Devin organization id not found/,
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
"config without org_id",
|
|
78
|
+
async (path: string) => {
|
|
79
|
+
if (path.endsWith("credentials.toml")) return fixture("devin-credentials.toml");
|
|
80
|
+
return JSON.stringify({ devin: {} });
|
|
81
|
+
},
|
|
82
|
+
/Devin organization id not found/,
|
|
83
|
+
],
|
|
84
|
+
])("fails clearly for %s without exposing file content", async (_name, readFileImpl, pattern) => {
|
|
85
|
+
await expect(
|
|
86
|
+
resolveDevinCredential({ env: { HOME: "/users/test" }, readFile: readFileImpl }),
|
|
87
|
+
).rejects.toThrow(pattern);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("devin quota mapping", () => {
|
|
92
|
+
it("maps daily and weekly windows with credits", async () => {
|
|
93
|
+
const report = parseDevinCapacity(JSON.parse(await fixture("devin-quota.json")));
|
|
94
|
+
expect(report).toMatchObject({
|
|
95
|
+
authenticated: true,
|
|
96
|
+
available: "yes",
|
|
97
|
+
creditsRemaining: 12.5,
|
|
98
|
+
});
|
|
99
|
+
expect(report.windows).toEqual([
|
|
100
|
+
{
|
|
101
|
+
id: "devin:daily",
|
|
102
|
+
label: "Daily",
|
|
103
|
+
durationMinutes: 1440,
|
|
104
|
+
usedPercent: 25,
|
|
105
|
+
resetsAt: "2026-10-06T08:00:00.000Z",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "devin:weekly",
|
|
109
|
+
label: "Weekly",
|
|
110
|
+
durationMinutes: 10080,
|
|
111
|
+
usedPercent: 60,
|
|
112
|
+
resetsAt: "2026-10-11T08:00:00.000Z",
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("omits the daily window when hide_daily_quota is true", () => {
|
|
118
|
+
const report = parseDevinCapacity({
|
|
119
|
+
is_quota_plan: true,
|
|
120
|
+
has_quota_allocation: true,
|
|
121
|
+
daily_percentage: 90,
|
|
122
|
+
weekly_percentage: 10,
|
|
123
|
+
hide_daily_quota: true,
|
|
124
|
+
});
|
|
125
|
+
expect(report.windows.map((window) => window.id)).toEqual(["devin:weekly"]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("reports authenticated unknown availability for non-quota plans", async () => {
|
|
129
|
+
const report = parseDevinCapacity(JSON.parse(await fixture("devin-quota-nonplan.json")));
|
|
130
|
+
expect(report).toEqual({
|
|
131
|
+
authenticated: true,
|
|
132
|
+
available: "unknown",
|
|
133
|
+
windows: [],
|
|
134
|
+
creditsRemaining: null,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("clamps percentages and degrades missing fields honestly", () => {
|
|
139
|
+
const report = parseDevinCapacity({
|
|
140
|
+
is_quota_plan: true,
|
|
141
|
+
has_quota_allocation: true,
|
|
142
|
+
daily_percentage: 140,
|
|
143
|
+
weekly_reset_at: "not-a-date",
|
|
144
|
+
overage_balance: "unknown",
|
|
145
|
+
});
|
|
146
|
+
const [daily, weekly] = report.windows;
|
|
147
|
+
expect(daily.usedPercent).toBe(100);
|
|
148
|
+
expect(weekly.usedPercent).toBeNull();
|
|
149
|
+
expect(weekly.resetsAt).toBeNull();
|
|
150
|
+
expect(report.creditsRemaining).toBeNull();
|
|
151
|
+
expect(report.available).toBe("no");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("rejects non-object responses", () => {
|
|
155
|
+
expect(() => parseDevinCapacity("nope")).toThrow(/Devin quota/);
|
|
156
|
+
expect(() => parseDevinCapacity(null)).toThrow(/Devin quota/);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("devin request", () => {
|
|
161
|
+
it("GETs the org quota endpoint with only Bearer authentication", async () => {
|
|
162
|
+
const body = await fixture("devin-quota.json");
|
|
163
|
+
const fetch = vi.fn(async () => new Response(body, { status: 200 }));
|
|
164
|
+
const report = await probeDevinCapacity({
|
|
165
|
+
checkedAt,
|
|
166
|
+
env: { DEVIN_API_KEY: "request-test-key", DEVIN_ORG_ID: "org-abc" },
|
|
167
|
+
fetch,
|
|
168
|
+
});
|
|
169
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
170
|
+
"https://app.devin.ai/api/org-abc/billing/quota/usage",
|
|
171
|
+
expect.objectContaining({
|
|
172
|
+
method: "GET",
|
|
173
|
+
headers: { Authorization: "Bearer request-test-key" },
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
expect(report.harness).toBe("devin");
|
|
177
|
+
expect(report.provider).toBe("devin");
|
|
178
|
+
expect(JSON.stringify(report)).not.toContain("request-test-key");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it.each([401, 403])("reports HTTP %i as unauthenticated", async (status) => {
|
|
182
|
+
const report = await probeDevinCapacity({
|
|
183
|
+
checkedAt,
|
|
184
|
+
env: { DEVIN_API_KEY: "request-test-key", DEVIN_ORG_ID: "org-abc" },
|
|
185
|
+
fetch: vi.fn(async () => new Response("denied", { status })),
|
|
186
|
+
});
|
|
187
|
+
expect(report).toMatchObject({
|
|
188
|
+
authenticated: false,
|
|
189
|
+
available: "unknown",
|
|
190
|
+
windows: [],
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("rejects other non-200 and malformed JSON responses with sanitized errors", async () => {
|
|
195
|
+
await expect(
|
|
196
|
+
probeDevinCapacity({
|
|
197
|
+
checkedAt,
|
|
198
|
+
env: { DEVIN_API_KEY: "never-expose-this-key", DEVIN_ORG_ID: "org-abc" },
|
|
199
|
+
fetch: vi.fn(async () => new Response("private body", { status: 503 })),
|
|
200
|
+
}),
|
|
201
|
+
).rejects.toThrow("Devin quota request failed: HTTP 503");
|
|
202
|
+
await expect(
|
|
203
|
+
probeDevinCapacity({
|
|
204
|
+
checkedAt,
|
|
205
|
+
env: { DEVIN_API_KEY: "never-expose-this-key", DEVIN_ORG_ID: "org-abc" },
|
|
206
|
+
fetch: vi.fn(async () => new Response("not-json", { status: 200 })),
|
|
207
|
+
}),
|
|
208
|
+
).rejects.toThrow("Devin quota response is not valid JSON");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"is_quota_plan": true,
|
|
3
|
+
"has_quota_allocation": true,
|
|
4
|
+
"daily_percentage": 25,
|
|
5
|
+
"weekly_percentage": 60,
|
|
6
|
+
"daily_reset_at": "2026-10-06T00:00:00-08:00",
|
|
7
|
+
"weekly_reset_at": "2026-10-11T00:00:00-08:00",
|
|
8
|
+
"overage_balance": 12.5,
|
|
9
|
+
"hide_daily_quota": false
|
|
10
|
+
}
|
package/src/capacity/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { access as fsAccess } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { codexUnavailableReport, probeCodexCapacity } from "./sources/codex.js";
|
|
5
5
|
import { probeClaudeCapacity, type ClaudeCapacityOptions } from "./sources/claude.js";
|
|
6
|
+
import { probeDevinCapacity, type DevinCapacityOptions } from "./sources/devin.js";
|
|
6
7
|
import {
|
|
7
8
|
probeOpenAiCapacity,
|
|
8
9
|
probePiAnthropicCapacity,
|
|
@@ -15,6 +16,7 @@ import type { CapacityReport } from "./types.js";
|
|
|
15
16
|
|
|
16
17
|
export type { CapacityReport, CapacityWindow } from "./types.js";
|
|
17
18
|
export type { ClaudeCapacityOptions } from "./sources/claude.js";
|
|
19
|
+
export type { DevinCapacityOptions } from "./sources/devin.js";
|
|
18
20
|
export type { PiAnthropicCapacityOptions as AnthropicCapacityOptions } from "./sources/pi.js";
|
|
19
21
|
export type {
|
|
20
22
|
PiOpenAiCapacityOptions as OpenAiCapacityOptions,
|
|
@@ -103,6 +105,16 @@ export async function getClaudeCapacityReport(
|
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
export async function getDevinCapacityReport(
|
|
109
|
+
options: DevinCapacityOptions = {},
|
|
110
|
+
): Promise<CapacityReport> {
|
|
111
|
+
const { now, ...probeOptions } = options;
|
|
112
|
+
return probeDevinCapacity({
|
|
113
|
+
...probeOptions,
|
|
114
|
+
checkedAt: (now?.() ?? new Date()).toISOString(),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
106
118
|
export async function getAnthropicCapacityReport(
|
|
107
119
|
options: PiAnthropicCapacityOptions = {},
|
|
108
120
|
): Promise<CapacityReport> {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Availability, CapacityWindow, ProviderCapacitySnapshot } from "../types.js";
|
|
2
|
+
import type { DevinCredential } from "../../harnesses/devin/credentials.js";
|
|
3
|
+
|
|
4
|
+
const DEVIN_WEBAPP_HOST = "https://app.devin.ai";
|
|
5
|
+
const DAY_MINUTES = 24 * 60;
|
|
6
|
+
const WEEK_MINUTES = 7 * 24 * 60;
|
|
7
|
+
|
|
8
|
+
type UnknownRecord = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
export type DevinRequestOptions = {
|
|
11
|
+
fetch?: typeof globalThis.fetch;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class DevinHttpError extends Error {
|
|
16
|
+
constructor(
|
|
17
|
+
public readonly status: number,
|
|
18
|
+
message: string,
|
|
19
|
+
) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "DevinHttpError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function record(value: unknown): UnknownRecord | null {
|
|
26
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
27
|
+
? (value as UnknownRecord)
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function percent(value: unknown): number | null {
|
|
32
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
33
|
+
return Math.max(0, Math.min(100, value));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function timestamp(value: unknown): string | null {
|
|
37
|
+
if (typeof value !== "string" || !value) return null;
|
|
38
|
+
const ms = Date.parse(value);
|
|
39
|
+
return Number.isNaN(ms) ? null : new Date(ms).toISOString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function finiteNumber(value: unknown): number | null {
|
|
43
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function availability(windows: CapacityWindow[]): Availability {
|
|
47
|
+
if (windows.length === 0) return "unknown";
|
|
48
|
+
return windows.some((window) => window.usedPercent !== null && window.usedPercent < 100)
|
|
49
|
+
? "yes"
|
|
50
|
+
: "no";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseDevinCapacity(raw: unknown): ProviderCapacitySnapshot {
|
|
54
|
+
const root = record(raw);
|
|
55
|
+
if (!root) throw new Error("Invalid Devin quota response");
|
|
56
|
+
|
|
57
|
+
if (root.is_quota_plan !== true || root.has_quota_allocation !== true) {
|
|
58
|
+
return {
|
|
59
|
+
authenticated: true,
|
|
60
|
+
available: "unknown",
|
|
61
|
+
windows: [],
|
|
62
|
+
creditsRemaining: null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const windows: CapacityWindow[] = [];
|
|
67
|
+
if (root.hide_daily_quota !== true) {
|
|
68
|
+
windows.push({
|
|
69
|
+
id: "devin:daily",
|
|
70
|
+
label: "Daily",
|
|
71
|
+
durationMinutes: DAY_MINUTES,
|
|
72
|
+
usedPercent: percent(root.daily_percentage),
|
|
73
|
+
resetsAt: timestamp(root.daily_reset_at),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
windows.push({
|
|
77
|
+
id: "devin:weekly",
|
|
78
|
+
label: "Weekly",
|
|
79
|
+
durationMinutes: WEEK_MINUTES,
|
|
80
|
+
usedPercent: percent(root.weekly_percentage),
|
|
81
|
+
resetsAt: timestamp(root.weekly_reset_at),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
authenticated: true,
|
|
86
|
+
available: availability(windows),
|
|
87
|
+
windows,
|
|
88
|
+
creditsRemaining: finiteNumber(root.overage_balance),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function fetchDevinCapacity(
|
|
93
|
+
credential: DevinCredential,
|
|
94
|
+
options: DevinRequestOptions = {},
|
|
95
|
+
): Promise<ProviderCapacitySnapshot> {
|
|
96
|
+
const url = `${DEVIN_WEBAPP_HOST}/api/${encodeURIComponent(credential.orgId)}/billing/quota/usage`;
|
|
97
|
+
let response: Response;
|
|
98
|
+
try {
|
|
99
|
+
response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
100
|
+
method: "GET",
|
|
101
|
+
headers: { Authorization: `Bearer ${credential.key}` },
|
|
102
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 5000),
|
|
103
|
+
});
|
|
104
|
+
} catch {
|
|
105
|
+
throw new Error("Devin quota request failed");
|
|
106
|
+
}
|
|
107
|
+
if (response.status !== 200) {
|
|
108
|
+
throw new DevinHttpError(
|
|
109
|
+
response.status,
|
|
110
|
+
`Devin quota request failed: HTTP ${response.status}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
return parseDevinCapacity(await response.json());
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error instanceof SyntaxError) {
|
|
117
|
+
throw new Error("Devin quota response is not valid JSON");
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DevinHttpError, fetchDevinCapacity } from "../providers/devin.js";
|
|
2
|
+
import type { CapacityReport } from "../types.js";
|
|
3
|
+
import {
|
|
4
|
+
resolveDevinCredential,
|
|
5
|
+
type DevinCredentialOptions,
|
|
6
|
+
} from "../../harnesses/devin/credentials.js";
|
|
7
|
+
|
|
8
|
+
export type DevinCapacityOptions = DevinCredentialOptions & {
|
|
9
|
+
now?: () => Date;
|
|
10
|
+
fetch?: typeof globalThis.fetch;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type DevinProbeOptions = DevinCapacityOptions & {
|
|
15
|
+
checkedAt: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function devinReport(
|
|
19
|
+
snapshot: Omit<CapacityReport, "harness" | "provider" | "generatedAt">,
|
|
20
|
+
checkedAt: string,
|
|
21
|
+
): CapacityReport {
|
|
22
|
+
return {
|
|
23
|
+
harness: "devin",
|
|
24
|
+
provider: "devin",
|
|
25
|
+
generatedAt: checkedAt,
|
|
26
|
+
...snapshot,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function probeDevinCapacity(options: DevinProbeOptions): Promise<CapacityReport> {
|
|
31
|
+
const credential = await resolveDevinCredential(options);
|
|
32
|
+
try {
|
|
33
|
+
const snapshot = await fetchDevinCapacity(credential, options);
|
|
34
|
+
return devinReport(snapshot, options.checkedAt);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof DevinHttpError && (error.status === 401 || error.status === 403)) {
|
|
37
|
+
return devinReport(
|
|
38
|
+
{
|
|
39
|
+
authenticated: false,
|
|
40
|
+
available: "unknown",
|
|
41
|
+
windows: [],
|
|
42
|
+
creditsRemaining: null,
|
|
43
|
+
},
|
|
44
|
+
options.checkedAt,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
type UnknownRecord = Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
export type DevinCredential = {
|
|
8
|
+
key: string;
|
|
9
|
+
orgId: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type DevinCredentialOptions = {
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
readFile?: (path: string, encoding: BufferEncoding) => Promise<string>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const DEVIN_KEY_MISSING = "Devin API key not found; set DEVIN_API_KEY or log in with the Devin CLI";
|
|
18
|
+
const DEVIN_ORG_MISSING =
|
|
19
|
+
"Devin organization id not found; set DEVIN_ORG_ID or log in with the Devin CLI";
|
|
20
|
+
|
|
21
|
+
function record(value: unknown): UnknownRecord | null {
|
|
22
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
23
|
+
? (value as UnknownRecord)
|
|
24
|
+
: null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function nonEmptyText(value: unknown): string | null {
|
|
28
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function readText(path: string, options: DevinCredentialOptions): Promise<string | null> {
|
|
32
|
+
try {
|
|
33
|
+
return await (options.readFile ?? readFile)(path, "utf8");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (record(error)?.code === "ENOENT") return null;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseFlatToml(contents: string): UnknownRecord | null {
|
|
41
|
+
const entries: UnknownRecord = {};
|
|
42
|
+
for (const line of contents.split("\n")) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("[")) continue;
|
|
45
|
+
const match = /^([A-Za-z0-9_]+)\s*=\s*"(.*)"\s*$/.exec(trimmed);
|
|
46
|
+
if (!match) return null;
|
|
47
|
+
entries[match[1]] = match[2];
|
|
48
|
+
}
|
|
49
|
+
return entries;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function resolveKey(home: string, options: DevinCredentialOptions): Promise<string> {
|
|
53
|
+
const envKey = nonEmptyText(options.env?.DEVIN_API_KEY ?? process.env.DEVIN_API_KEY);
|
|
54
|
+
if (envKey) return envKey;
|
|
55
|
+
|
|
56
|
+
const contents = await readText(
|
|
57
|
+
join(home, ".local", "share", "devin", "credentials.toml"),
|
|
58
|
+
options,
|
|
59
|
+
);
|
|
60
|
+
if (contents === null) throw new Error(DEVIN_KEY_MISSING);
|
|
61
|
+
const parsed = parseFlatToml(contents);
|
|
62
|
+
if (parsed === null) throw new Error("Devin credentials file is malformed");
|
|
63
|
+
const key = nonEmptyText(parsed.windsurf_api_key);
|
|
64
|
+
if (!key) throw new Error(DEVIN_KEY_MISSING);
|
|
65
|
+
return key;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function resolveOrgId(home: string, options: DevinCredentialOptions): Promise<string> {
|
|
69
|
+
const envOrg = nonEmptyText(options.env?.DEVIN_ORG_ID ?? process.env.DEVIN_ORG_ID);
|
|
70
|
+
if (envOrg) return envOrg;
|
|
71
|
+
|
|
72
|
+
const contents = await readText(join(home, ".config", "devin", "config.json"), options);
|
|
73
|
+
if (contents === null) throw new Error(DEVIN_ORG_MISSING);
|
|
74
|
+
const parsed = record(JSON.parse(contents));
|
|
75
|
+
if (!parsed) throw new Error("Devin config file is malformed");
|
|
76
|
+
const orgId = nonEmptyText(record(parsed.devin)?.org_id);
|
|
77
|
+
if (!orgId) throw new Error(DEVIN_ORG_MISSING);
|
|
78
|
+
return orgId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function resolveDevinCredential(
|
|
82
|
+
options: DevinCredentialOptions = {},
|
|
83
|
+
): Promise<DevinCredential> {
|
|
84
|
+
const env = options.env ?? process.env;
|
|
85
|
+
const home = env.HOME || homedir();
|
|
86
|
+
const [key, orgId] = await Promise.all([resolveKey(home, options), resolveOrgId(home, options)]);
|
|
87
|
+
return { key, orgId };
|
|
88
|
+
}
|