@gitterm/sdk 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +588 -263
- package/dist/client.d.ts +39 -21
- package/dist/direct/client.d.ts +15 -14
- package/dist/direct/index.js +1377 -179
- package/dist/direct/runs.d.ts +19 -0
- package/dist/direct/types.d.ts +25 -23
- package/dist/errors.d.ts +9 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +375 -82
- package/dist/runs.d.ts +19 -0
- package/dist/types.d.ts +277 -23
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createTRPCClient,
|
|
4
|
+
httpBatchLink,
|
|
5
|
+
httpSubscriptionLink,
|
|
6
|
+
splitLink,
|
|
7
|
+
TRPCClientError
|
|
8
|
+
} from "@trpc/client";
|
|
9
|
+
import { EventSource } from "eventsource";
|
|
3
10
|
|
|
4
11
|
// src/config.ts
|
|
5
12
|
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
@@ -51,6 +58,14 @@ async function deleteConfig(configPath) {
|
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
// src/errors.ts
|
|
61
|
+
var WORKSPACE_LIFECYCLE_ERROR_CODES = [
|
|
62
|
+
"WORKSPACE_NOT_RUNNING",
|
|
63
|
+
"WORKSPACE_TERMINATED",
|
|
64
|
+
"WORKSPACE_NON_RECOVERABLE",
|
|
65
|
+
"WORKSPACE_START_TIMEOUT",
|
|
66
|
+
"WORKSPACE_RESTART_FAILED"
|
|
67
|
+
];
|
|
68
|
+
|
|
54
69
|
class GittermError extends Error {
|
|
55
70
|
code;
|
|
56
71
|
cause;
|
|
@@ -69,6 +84,15 @@ class WorkspaceLifecycleError extends GittermError {
|
|
|
69
84
|
}
|
|
70
85
|
}
|
|
71
86
|
|
|
87
|
+
class AgentRunError extends GittermError {
|
|
88
|
+
run;
|
|
89
|
+
constructor(run, code) {
|
|
90
|
+
super(code, run.error ?? (code === "INPUT_REQUIRED" ? "The agent needs input; supply a handler or use runs.respond()" : `Agent run ${run.status}`));
|
|
91
|
+
this.run = run;
|
|
92
|
+
this.name = "AgentRunError";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
// src/transport.ts
|
|
73
97
|
var LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
74
98
|
function normalizeServerUrl(value) {
|
|
@@ -99,6 +123,152 @@ function createNoRedirectFetch(fetchImpl = fetch) {
|
|
|
99
123
|
};
|
|
100
124
|
}
|
|
101
125
|
|
|
126
|
+
// src/runs.ts
|
|
127
|
+
function terminal(status) {
|
|
128
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
129
|
+
}
|
|
130
|
+
function aborted(signal) {
|
|
131
|
+
if (signal?.aborted)
|
|
132
|
+
throw new GittermError("ABORTED", "Run observation was aborted; the agent was not cancelled");
|
|
133
|
+
}
|
|
134
|
+
async function abortable(operation, signal) {
|
|
135
|
+
if (signal.aborted)
|
|
136
|
+
operation.catch(() => {});
|
|
137
|
+
aborted(signal);
|
|
138
|
+
let onAbort;
|
|
139
|
+
try {
|
|
140
|
+
return await Promise.race([
|
|
141
|
+
operation,
|
|
142
|
+
new Promise((_, reject) => {
|
|
143
|
+
onAbort = () => reject(new GittermError("ABORTED", "Run observation was aborted; the agent was not cancelled"));
|
|
144
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
145
|
+
})
|
|
146
|
+
]);
|
|
147
|
+
} finally {
|
|
148
|
+
if (onAbort)
|
|
149
|
+
signal.removeEventListener("abort", onAbort);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function delay(ms, signal) {
|
|
153
|
+
aborted(signal);
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
const onAbort = () => {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
reject(new GittermError("ABORTED", "Operation aborted"));
|
|
158
|
+
};
|
|
159
|
+
const timer = setTimeout(() => {
|
|
160
|
+
signal.removeEventListener("abort", onAbort);
|
|
161
|
+
resolve();
|
|
162
|
+
}, ms);
|
|
163
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function deadline(options, operation) {
|
|
167
|
+
aborted(options?.signal);
|
|
168
|
+
const timeoutMs = options?.timeoutMs ?? 30 * 60000;
|
|
169
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0)
|
|
170
|
+
throw new GittermError("BAD_REQUEST", "timeoutMs must be finite and non-negative");
|
|
171
|
+
const controller = new AbortController;
|
|
172
|
+
const onAbort = () => controller.abort();
|
|
173
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
174
|
+
let timedOut = false;
|
|
175
|
+
const timer = setTimeout(() => {
|
|
176
|
+
timedOut = true;
|
|
177
|
+
controller.abort();
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
try {
|
|
180
|
+
return await operation(controller.signal);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (timedOut)
|
|
183
|
+
throw new GittermError("TIMEOUT", "Run observation timed out; the agent was not cancelled", {
|
|
184
|
+
cause: error
|
|
185
|
+
});
|
|
186
|
+
aborted(options?.signal);
|
|
187
|
+
throw error;
|
|
188
|
+
} finally {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
191
|
+
controller.abort();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function* runEvents(states) {
|
|
195
|
+
const seen = new Set;
|
|
196
|
+
let pending = new Set;
|
|
197
|
+
let status;
|
|
198
|
+
for await (const run of states) {
|
|
199
|
+
if (run.status !== status) {
|
|
200
|
+
status = run.status;
|
|
201
|
+
yield { type: "run.status", run };
|
|
202
|
+
}
|
|
203
|
+
const current = new Set(run.pendingInputs.map((request) => request.id));
|
|
204
|
+
for (const requestId of pending) {
|
|
205
|
+
if (!current.has(requestId))
|
|
206
|
+
yield { type: "input.resolved", run, requestId };
|
|
207
|
+
}
|
|
208
|
+
pending = current;
|
|
209
|
+
for (const request of run.pendingInputs) {
|
|
210
|
+
if (seen.has(request.id))
|
|
211
|
+
continue;
|
|
212
|
+
seen.add(request.id);
|
|
213
|
+
yield { type: "input.required", run, request };
|
|
214
|
+
}
|
|
215
|
+
if (run.status === "completed" || run.status === "failed" || run.status === "cancelled") {
|
|
216
|
+
yield { type: `run.${run.status}`, run };
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function runHelpers(operations) {
|
|
222
|
+
return {
|
|
223
|
+
events: (ref, options) => runEvents(operations.watch(ref, options)),
|
|
224
|
+
wait: (ref, options) => deadline(options, async (signal) => {
|
|
225
|
+
for await (const run of operations.watch(ref, { signal })) {
|
|
226
|
+
if (terminal(run.status) || run.status === "awaiting_input" && options?.until !== "terminal")
|
|
227
|
+
return run;
|
|
228
|
+
}
|
|
229
|
+
throw new GittermError("NETWORK", "Run stream ended before a terminal state");
|
|
230
|
+
}),
|
|
231
|
+
result: (ref, options) => deadline(options, async (signal) => {
|
|
232
|
+
for await (const event of runEvents(operations.watch(ref, { signal }))) {
|
|
233
|
+
aborted(signal);
|
|
234
|
+
if (event.type === "run.completed")
|
|
235
|
+
return event.run;
|
|
236
|
+
if (event.type === "run.failed")
|
|
237
|
+
throw new AgentRunError(event.run, "RUN_FAILED");
|
|
238
|
+
if (event.type === "run.cancelled")
|
|
239
|
+
throw new AgentRunError(event.run, "RUN_CANCELLED");
|
|
240
|
+
if (event.type !== "input.required")
|
|
241
|
+
continue;
|
|
242
|
+
const { request } = event;
|
|
243
|
+
let reply;
|
|
244
|
+
if (request.kind === "permission") {
|
|
245
|
+
if (!options?.onPermission)
|
|
246
|
+
throw new AgentRunError(event.run, "INPUT_REQUIRED");
|
|
247
|
+
reply = {
|
|
248
|
+
type: "permission",
|
|
249
|
+
response: await abortable(Promise.resolve(options.onPermission(request, { signal })), signal)
|
|
250
|
+
};
|
|
251
|
+
} else {
|
|
252
|
+
if (!options?.onQuestion)
|
|
253
|
+
throw new AgentRunError(event.run, "INPUT_REQUIRED");
|
|
254
|
+
reply = {
|
|
255
|
+
type: "question",
|
|
256
|
+
...await abortable(Promise.resolve(options.onQuestion(request, { signal })), signal)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
aborted(signal);
|
|
260
|
+
try {
|
|
261
|
+
await abortable(operations.respond(ref, { requestId: request.id, reply }), signal);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (!(error instanceof GittermError) || error.code !== "INPUT_NOT_PENDING")
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
throw new GittermError("NETWORK", "Run stream ended before a terminal state");
|
|
268
|
+
})
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
102
272
|
// src/client.ts
|
|
103
273
|
function envValue(name) {
|
|
104
274
|
const value = typeof process !== "undefined" ? process.env[name] : undefined;
|
|
@@ -129,6 +299,29 @@ function normalizeBaseCommit(value) {
|
|
|
129
299
|
return null;
|
|
130
300
|
return /^[0-9a-f]{40}([0-9a-f]{24})?$/i.test(trimmed) ? trimmed.toLowerCase() : trimmed;
|
|
131
301
|
}
|
|
302
|
+
function workspaceIdOf(ref) {
|
|
303
|
+
return typeof ref === "string" ? ref : ref.id;
|
|
304
|
+
}
|
|
305
|
+
function runTargetOf(ref) {
|
|
306
|
+
return { workspaceId: ref.workspaceId, runId: ref.id };
|
|
307
|
+
}
|
|
308
|
+
function throwIfAborted(signal, what) {
|
|
309
|
+
if (signal?.aborted)
|
|
310
|
+
throw new GittermError("ABORTED", `Aborted while ${what}`);
|
|
311
|
+
}
|
|
312
|
+
function sleep(ms, signal, what) {
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
const timer = setTimeout(() => {
|
|
315
|
+
signal?.removeEventListener("abort", onAbort);
|
|
316
|
+
resolve();
|
|
317
|
+
}, ms);
|
|
318
|
+
const onAbort = () => {
|
|
319
|
+
clearTimeout(timer);
|
|
320
|
+
reject(new GittermError("ABORTED", `Aborted while ${what}`));
|
|
321
|
+
};
|
|
322
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
323
|
+
});
|
|
324
|
+
}
|
|
132
325
|
function normalizeWorkspace(workspace) {
|
|
133
326
|
if (!workspace)
|
|
134
327
|
return null;
|
|
@@ -157,6 +350,10 @@ function normalizeWorkspace(workspace) {
|
|
|
157
350
|
name: workspace.image.name,
|
|
158
351
|
imageId: workspace.image.imageId
|
|
159
352
|
} : null,
|
|
353
|
+
metadata: workspace.metadata ?? {},
|
|
354
|
+
opencodeApi: workspace.opencodeApi ?? "v1",
|
|
355
|
+
autoTerminateAt: toIso(workspace.autoTerminateAt),
|
|
356
|
+
customImage: workspace.customImage ?? null,
|
|
160
357
|
startedAt: toIso(workspace.startedAt),
|
|
161
358
|
pausedAt: toIso(workspace.pausedAt),
|
|
162
359
|
terminatedAt: toIso(workspace.terminatedAt),
|
|
@@ -193,6 +390,8 @@ function mapTrpcCode(code) {
|
|
|
193
390
|
return "BAD_REQUEST";
|
|
194
391
|
case "CONFLICT":
|
|
195
392
|
return "CONFLICT";
|
|
393
|
+
case "TIMEOUT":
|
|
394
|
+
return "TIMEOUT";
|
|
196
395
|
default:
|
|
197
396
|
return "SERVER_ERROR";
|
|
198
397
|
}
|
|
@@ -201,58 +400,113 @@ function credentialErrorCode(message) {
|
|
|
201
400
|
const match = /^(MODEL_CREDENTIAL_(?:UNAVAILABLE|DUPLICATE_PROVIDER|INVALID|REQUIRED)):\s*/.exec(message);
|
|
202
401
|
return match?.[1];
|
|
203
402
|
}
|
|
403
|
+
function toGittermError(serverUrl, error) {
|
|
404
|
+
if (error instanceof GittermError)
|
|
405
|
+
return error;
|
|
406
|
+
if (error instanceof TRPCClientError) {
|
|
407
|
+
const data = error.data;
|
|
408
|
+
const trpcCode = data?.code;
|
|
409
|
+
if (!error.data) {
|
|
410
|
+
return new GittermError("NETWORK", `Could not reach the GitTerm server at ${serverUrl}`, {
|
|
411
|
+
cause: error
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
const code = mapTrpcCode(trpcCode);
|
|
415
|
+
const structuredLifecycleCode = WORKSPACE_LIFECYCLE_ERROR_CODES.find((candidate) => candidate === data?.workspaceLifecycleCode);
|
|
416
|
+
if (structuredLifecycleCode) {
|
|
417
|
+
return new WorkspaceLifecycleError(structuredLifecycleCode, error.message, { cause: error });
|
|
418
|
+
}
|
|
419
|
+
if (error.message.startsWith("INPUT_NOT_PENDING:"))
|
|
420
|
+
return new GittermError("INPUT_NOT_PENDING", error.message, { cause: error });
|
|
421
|
+
const credentialCode = credentialErrorCode(error.message);
|
|
422
|
+
if (credentialCode)
|
|
423
|
+
return new GittermError(credentialCode, error.message, { cause: error });
|
|
424
|
+
const lifecycleCode = WORKSPACE_LIFECYCLE_ERROR_CODES.find((candidate) => error.message.startsWith(`${candidate}:`));
|
|
425
|
+
if (lifecycleCode) {
|
|
426
|
+
return new WorkspaceLifecycleError(lifecycleCode, error.message, { cause: error });
|
|
427
|
+
}
|
|
428
|
+
return new GittermError(code, code === "UNAUTHORIZED" ? `Authentication failed: ${error.message}. Check that the API token is valid and has not expired.` : error.message, { cause: error });
|
|
429
|
+
}
|
|
430
|
+
return new GittermError("NETWORK", error instanceof Error ? error.message : "Network request failed", { cause: error });
|
|
431
|
+
}
|
|
204
432
|
async function runWithServer(serverUrl, operation) {
|
|
205
433
|
try {
|
|
206
434
|
return await operation();
|
|
207
435
|
} catch (error) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
436
|
+
throw toGittermError(serverUrl, error);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function isTerminalRun(status) {
|
|
440
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
441
|
+
}
|
|
442
|
+
async function* watchRun(trpc, serverUrl, target, options) {
|
|
443
|
+
const what = `watching run ${target.runId}`;
|
|
444
|
+
throwIfAborted(options?.signal, what);
|
|
445
|
+
const queue = [];
|
|
446
|
+
let wake;
|
|
447
|
+
const push = (item) => {
|
|
448
|
+
queue.push(item);
|
|
449
|
+
wake?.();
|
|
450
|
+
wake = undefined;
|
|
451
|
+
};
|
|
452
|
+
const onAbort = () => push({ error: new GittermError("ABORTED", `Aborted while ${what}`) });
|
|
453
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
454
|
+
let last;
|
|
455
|
+
const subscription = trpc.run.lifecycle.subscribe(target, {
|
|
456
|
+
onData: (event) => {
|
|
457
|
+
last = event.run;
|
|
458
|
+
push({ run: last });
|
|
459
|
+
},
|
|
460
|
+
onError: (error) => push({ error }),
|
|
461
|
+
onComplete: () => push(last && isTerminalRun(last.status) ? { done: true } : {
|
|
462
|
+
error: new GittermError("NETWORK", `Run ${target.runId} event stream ended unexpectedly`)
|
|
463
|
+
})
|
|
464
|
+
});
|
|
465
|
+
try {
|
|
466
|
+
while (true) {
|
|
467
|
+
throwIfAborted(options?.signal, what);
|
|
468
|
+
if (queue.length === 0)
|
|
469
|
+
await new Promise((resolve) => wake = resolve);
|
|
470
|
+
const item = queue.shift();
|
|
471
|
+
if ("done" in item)
|
|
472
|
+
return;
|
|
473
|
+
if ("error" in item)
|
|
474
|
+
throw toGittermError(serverUrl, item.error);
|
|
475
|
+
yield item.run;
|
|
476
|
+
if (isTerminalRun(item.run.status))
|
|
477
|
+
return;
|
|
240
478
|
}
|
|
241
|
-
|
|
479
|
+
} finally {
|
|
480
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
481
|
+
subscription.unsubscribe();
|
|
242
482
|
}
|
|
243
483
|
}
|
|
244
484
|
function createGittermClient(options = {}) {
|
|
245
485
|
const credentials = resolveCredentials(options);
|
|
486
|
+
const fetchImpl = createNoRedirectFetch(options.fetch);
|
|
487
|
+
const authorization = `Bearer ${credentials.token}`;
|
|
246
488
|
const trpc = createTRPCClient({
|
|
247
489
|
links: [
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
490
|
+
splitLink({
|
|
491
|
+
condition: (operation) => operation.type === "subscription",
|
|
492
|
+
true: httpSubscriptionLink({
|
|
493
|
+
url: toTrpcUrl(credentials.serverUrl),
|
|
494
|
+
EventSource,
|
|
495
|
+
eventSourceOptions: {
|
|
496
|
+
fetch: (url, init) => fetchImpl(url, { ...init, headers: { ...init.headers, authorization } })
|
|
497
|
+
}
|
|
498
|
+
}),
|
|
499
|
+
false: httpBatchLink({
|
|
500
|
+
url: toTrpcUrl(credentials.serverUrl),
|
|
501
|
+
fetch: fetchImpl,
|
|
502
|
+
headers: () => ({ authorization })
|
|
503
|
+
})
|
|
252
504
|
})
|
|
253
505
|
]
|
|
254
506
|
});
|
|
255
507
|
const run = (operation) => runWithServer(credentials.serverUrl, operation);
|
|
508
|
+
const watch = (ref, watchOptions) => watchRun(trpc, credentials.serverUrl, runTargetOf(ref), watchOptions);
|
|
509
|
+
const respond = (ref, input) => run(async () => trpc.run.respond.mutate({ ...runTargetOf(ref), ...input }));
|
|
256
510
|
const normalizeCreateResult = (result) => {
|
|
257
511
|
const workspace = normalizeWorkspace(result.workspace);
|
|
258
512
|
if (!workspace)
|
|
@@ -279,9 +533,11 @@ function createGittermClient(options = {}) {
|
|
|
279
533
|
const waitForWorkspaceSetup = async (workspaceId, waitOptions) => {
|
|
280
534
|
const timeoutMs = waitOptions?.timeoutMs ?? 10 * 60000;
|
|
281
535
|
const pollIntervalMs = waitOptions?.pollIntervalMs ?? 2000;
|
|
282
|
-
const
|
|
536
|
+
const signal = waitOptions?.signal;
|
|
537
|
+
const deadline2 = Date.now() + timeoutMs;
|
|
283
538
|
while (true) {
|
|
284
|
-
|
|
539
|
+
throwIfAborted(signal, "waiting for workspace setup");
|
|
540
|
+
const result = await trpc.workspace.getSetupStatus.query({ workspaceId }, { signal });
|
|
285
541
|
if (result.status === "not_requested" || result.status === "succeeded")
|
|
286
542
|
return result;
|
|
287
543
|
if (result.status === "failed") {
|
|
@@ -289,12 +545,12 @@ function createGittermClient(options = {}) {
|
|
|
289
545
|
throw new GittermError("BAD_REQUEST", `Workspace setup failed${result.exitCode === null ? "" : ` with exit code ${result.exitCode}`}${log ? `
|
|
290
546
|
${log}` : ""}`);
|
|
291
547
|
}
|
|
292
|
-
if (Date.now() >=
|
|
548
|
+
if (Date.now() >= deadline2) {
|
|
293
549
|
const log = result.log?.trim();
|
|
294
|
-
throw new GittermError("
|
|
550
|
+
throw new GittermError("TIMEOUT", `Timed out waiting for workspace ${workspaceId} setup (last status: ${result.status})${log ? `
|
|
295
551
|
${log}` : ""}`);
|
|
296
552
|
}
|
|
297
|
-
await
|
|
553
|
+
await sleep(pollIntervalMs, signal, "waiting for workspace setup");
|
|
298
554
|
}
|
|
299
555
|
};
|
|
300
556
|
return {
|
|
@@ -316,27 +572,32 @@ ${log}` : ""}`);
|
|
|
316
572
|
list: (input) => run(async () => {
|
|
317
573
|
const result = await trpc.workspace.listWorkspaces.query(input);
|
|
318
574
|
return {
|
|
319
|
-
workspaces: result.workspaces.map((workspace) =>
|
|
575
|
+
workspaces: result.workspaces.map(normalizeWorkspace).filter((workspace) => workspace !== null),
|
|
320
576
|
pagination: result.pagination
|
|
321
577
|
};
|
|
322
578
|
}),
|
|
323
|
-
get: (
|
|
324
|
-
const result = await trpc.workspace.getWorkspace.query({
|
|
579
|
+
get: (ref) => run(async () => {
|
|
580
|
+
const result = await trpc.workspace.getWorkspace.query({
|
|
581
|
+
workspaceId: workspaceIdOf(ref)
|
|
582
|
+
});
|
|
325
583
|
const workspace = normalizeWorkspace(result.workspace);
|
|
326
584
|
if (!workspace)
|
|
327
585
|
throw new GittermError("NOT_FOUND", "Workspace not found");
|
|
328
586
|
return workspace;
|
|
329
587
|
}),
|
|
330
|
-
getRuntimeAccess: (
|
|
331
|
-
const result = await trpc.workspace.getRuntimeAccess.query({
|
|
588
|
+
getRuntimeAccess: (ref) => run(async () => {
|
|
589
|
+
const result = await trpc.workspace.getRuntimeAccess.query({
|
|
590
|
+
workspaceId: workspaceIdOf(ref)
|
|
591
|
+
});
|
|
332
592
|
return normalizeRuntime(result);
|
|
333
593
|
}),
|
|
334
|
-
ensureRunning: (
|
|
594
|
+
ensureRunning: (ref, waitOptions) => run(async () => {
|
|
595
|
+
throwIfAborted(waitOptions?.signal, "ensuring the workspace is running");
|
|
335
596
|
const result = await trpc.workspace.ensureRunning.mutate({
|
|
336
|
-
workspaceId,
|
|
337
|
-
timeoutMs:
|
|
338
|
-
pollIntervalMs:
|
|
339
|
-
});
|
|
597
|
+
workspaceId: workspaceIdOf(ref),
|
|
598
|
+
timeoutMs: waitOptions?.timeoutMs,
|
|
599
|
+
pollIntervalMs: waitOptions?.pollIntervalMs
|
|
600
|
+
}, { signal: waitOptions?.signal });
|
|
340
601
|
const workspace = normalizeWorkspace(result.workspace);
|
|
341
602
|
if (!workspace)
|
|
342
603
|
throw new GittermError("SERVER_ERROR", "ensureRunning failed");
|
|
@@ -345,44 +606,62 @@ ${log}` : ""}`);
|
|
|
345
606
|
runtime: normalizeRuntime(result.runtime)
|
|
346
607
|
};
|
|
347
608
|
}),
|
|
348
|
-
pause: (
|
|
349
|
-
const result = await trpc.workspace.pauseWorkspace.mutate({
|
|
609
|
+
pause: (ref) => run(async () => {
|
|
610
|
+
const result = await trpc.workspace.pauseWorkspace.mutate({
|
|
611
|
+
workspaceId: workspaceIdOf(ref)
|
|
612
|
+
});
|
|
350
613
|
return { durationMinutes: result.durationMinutes };
|
|
351
614
|
}),
|
|
352
|
-
restart: (
|
|
353
|
-
const result = await trpc.workspace.restartWorkspace.mutate({
|
|
615
|
+
restart: (ref) => run(async () => {
|
|
616
|
+
const result = await trpc.workspace.restartWorkspace.mutate({
|
|
617
|
+
workspaceId: workspaceIdOf(ref)
|
|
618
|
+
});
|
|
354
619
|
return { status: result.status };
|
|
355
620
|
}),
|
|
356
|
-
terminate: (
|
|
357
|
-
const result = await trpc.workspace.deleteWorkspace.mutate({
|
|
621
|
+
terminate: (ref) => run(async () => {
|
|
622
|
+
const result = await trpc.workspace.deleteWorkspace.mutate({
|
|
623
|
+
workspaceId: workspaceIdOf(ref)
|
|
624
|
+
});
|
|
358
625
|
return {
|
|
359
626
|
workspace: normalizeWorkspace(result.workspace),
|
|
360
627
|
cleanupInBackground: result.cleanupInBackground
|
|
361
628
|
};
|
|
362
629
|
}),
|
|
363
630
|
create: createWorkspace,
|
|
364
|
-
|
|
365
|
-
|
|
631
|
+
models: (ref) => run(() => trpc.workspace.getModelAccess.query({ workspaceId: workspaceIdOf(ref) })),
|
|
632
|
+
setupStatus: (ref) => run(async () => trpc.workspace.getSetupStatus.query({ workspaceId: workspaceIdOf(ref) })),
|
|
633
|
+
waitForSetup: (ref, waitOptions) => run(() => waitForWorkspaceSetup(workspaceIdOf(ref), waitOptions))
|
|
366
634
|
},
|
|
367
635
|
runs: {
|
|
368
|
-
create: (input) => run(async () =>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (Date.now() >= deadline) {
|
|
381
|
-
throw new GittermError("NETWORK", `Timed out waiting for run ${runId}`);
|
|
382
|
-
}
|
|
383
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
636
|
+
create: (input) => run(async () => {
|
|
637
|
+
const { signal, workspace, ...rest } = input;
|
|
638
|
+
const request = {
|
|
639
|
+
...rest,
|
|
640
|
+
workspaceId: workspaceIdOf(workspace),
|
|
641
|
+
context: rest.context?.type === "continue" ? { type: "continue", runId: rest.context.run.id } : rest.context
|
|
642
|
+
};
|
|
643
|
+
if (request.waitForSetup) {
|
|
644
|
+
await waitForWorkspaceSetup(request.workspaceId, {
|
|
645
|
+
timeoutMs: request.setupTimeoutMs,
|
|
646
|
+
signal
|
|
647
|
+
});
|
|
384
648
|
}
|
|
385
|
-
|
|
649
|
+
throwIfAborted(signal, "creating the run");
|
|
650
|
+
return trpc.run.create.mutate({ ...request, idempotencyKey: request.idempotencyKey ?? crypto.randomUUID() }, { signal });
|
|
651
|
+
}),
|
|
652
|
+
list: (ref, listOptions) => run(async () => trpc.run.list.query({
|
|
653
|
+
workspaceId: workspaceIdOf(ref),
|
|
654
|
+
...listOptions
|
|
655
|
+
})),
|
|
656
|
+
get: (ref) => run(async () => trpc.run.get.query(runTargetOf(ref))),
|
|
657
|
+
messages: (ref) => run(async () => {
|
|
658
|
+
const messages = await trpc.run.messages.query(runTargetOf(ref));
|
|
659
|
+
return messages.map((message) => ({ ...message, parts: message.parts ?? [] }));
|
|
660
|
+
}),
|
|
661
|
+
cancel: (ref) => run(async () => trpc.run.cancel.mutate(runTargetOf(ref))),
|
|
662
|
+
respond,
|
|
663
|
+
watch,
|
|
664
|
+
...runHelpers({ watch, respond })
|
|
386
665
|
},
|
|
387
666
|
catalog: {
|
|
388
667
|
agentTypes: (input) => run(async () => {
|
|
@@ -395,6 +674,18 @@ ${log}` : ""}`);
|
|
|
395
674
|
}),
|
|
396
675
|
workspaceOptions: () => run(async () => trpc.workspace.getWorkspaceCatalog.query())
|
|
397
676
|
},
|
|
677
|
+
models: {
|
|
678
|
+
list: (listOptions) => run(async () => {
|
|
679
|
+
const result = await trpc.modelCredentials.listModels.query();
|
|
680
|
+
return result.models.map((model) => ({
|
|
681
|
+
id: model.modelId,
|
|
682
|
+
name: model.displayName,
|
|
683
|
+
provider: model.modelId.slice(0, model.modelId.indexOf("/")),
|
|
684
|
+
isFree: model.isFree,
|
|
685
|
+
isRecommended: model.isRecommended
|
|
686
|
+
})).filter((model) => !listOptions?.provider || model.provider === listOptions.provider);
|
|
687
|
+
})
|
|
688
|
+
},
|
|
398
689
|
credentials: {
|
|
399
690
|
list: () => run(async () => {
|
|
400
691
|
const result = await trpc.modelCredentials.listMyCredentials.query();
|
|
@@ -411,6 +702,7 @@ ${log}` : ""}`);
|
|
|
411
702
|
return result.providers.map((provider) => ({
|
|
412
703
|
id: provider.id,
|
|
413
704
|
name: provider.name,
|
|
705
|
+
logicalProviderKey: provider.logicalProviderKey,
|
|
414
706
|
displayName: provider.displayName,
|
|
415
707
|
authType: provider.authType,
|
|
416
708
|
isRecommended: provider.isRecommended
|
|
@@ -420,7 +712,7 @@ ${log}` : ""}`);
|
|
|
420
712
|
};
|
|
421
713
|
}
|
|
422
714
|
// src/device-login.ts
|
|
423
|
-
function
|
|
715
|
+
function sleep2(ms) {
|
|
424
716
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
425
717
|
}
|
|
426
718
|
async function loginWithDeviceCode(serverUrl, options = {}) {
|
|
@@ -440,8 +732,8 @@ async function loginWithDeviceCode(serverUrl, options = {}) {
|
|
|
440
732
|
intervalSeconds: codeJson.intervalSeconds,
|
|
441
733
|
expiresInSeconds: codeJson.expiresInSeconds
|
|
442
734
|
});
|
|
443
|
-
const
|
|
444
|
-
while (Date.now() <
|
|
735
|
+
const deadline2 = Date.now() + codeJson.expiresInSeconds * 1000;
|
|
736
|
+
while (Date.now() < deadline2) {
|
|
445
737
|
const tokenRes = await fetchImpl(new URL("/api/device/token", normalizedServerUrl), {
|
|
446
738
|
method: "POST",
|
|
447
739
|
headers: { "content-type": "application/json" },
|
|
@@ -455,7 +747,7 @@ async function loginWithDeviceCode(serverUrl, options = {}) {
|
|
|
455
747
|
const errText = await tokenRes.text().catch(() => "");
|
|
456
748
|
throw new Error(`Login failed: ${tokenRes.status} ${errText}`);
|
|
457
749
|
}
|
|
458
|
-
await
|
|
750
|
+
await sleep2(Math.max(1, codeJson.intervalSeconds) * 1000);
|
|
459
751
|
}
|
|
460
752
|
throw new Error("Device code expired; try again.");
|
|
461
753
|
}
|
|
@@ -525,6 +817,7 @@ function createGittermWorkspaceClient(options = {}) {
|
|
|
525
817
|
};
|
|
526
818
|
}
|
|
527
819
|
export {
|
|
820
|
+
AgentRunError,
|
|
528
821
|
DEFAULT_GITTERM_SERVER_URL,
|
|
529
822
|
GittermError,
|
|
530
823
|
WorkspaceLifecycleError,
|
package/dist/runs.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AgentRun, AgentRunEvent, AgentRunReply, AgentRunResult, RunResultOptions, RunWaitOptions, RunWatchOptions } from "./types.js";
|
|
2
|
+
export declare function terminal(status: AgentRun["status"]): boolean;
|
|
3
|
+
export declare function aborted(signal?: AbortSignal): void;
|
|
4
|
+
export declare function abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T>;
|
|
5
|
+
export declare function delay(ms: number, signal: AbortSignal): Promise<void>;
|
|
6
|
+
/** Deduplication is subscriber-local, not an exactly-once delivery guarantee. */
|
|
7
|
+
export declare function runEvents<T extends AgentRun>(states: AsyncIterable<T>): AsyncGenerator<AgentRunEvent<T>>;
|
|
8
|
+
/** Same interaction helpers for hosted and direct runs. */
|
|
9
|
+
export declare function runHelpers<Ref, T extends AgentRun>(operations: {
|
|
10
|
+
watch(ref: Ref, options?: RunWatchOptions): AsyncIterable<T>;
|
|
11
|
+
respond(ref: Ref, input: {
|
|
12
|
+
requestId: string;
|
|
13
|
+
reply: AgentRunReply;
|
|
14
|
+
}): Promise<T>;
|
|
15
|
+
}): {
|
|
16
|
+
events: (ref: Ref, options?: RunWatchOptions) => AsyncIterable<AgentRunEvent<T>>;
|
|
17
|
+
wait: (ref: Ref, options?: RunWaitOptions) => Promise<T>;
|
|
18
|
+
result: (ref: Ref, options?: RunResultOptions) => Promise<AgentRunResult<T>>;
|
|
19
|
+
};
|