@gitterm/sdk 0.1.3 → 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 +441 -341
- package/dist/client.d.ts +19 -9
- 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 +7 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +267 -88
- package/dist/runs.d.ts +19 -0
- package/dist/types.d.ts +119 -59
- package/package.json +2 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { GittermError } from "../errors.js";
|
|
2
|
+
import type { AgentRunReply, RunWatchOptions } from "../types.js";
|
|
3
|
+
import type { DirectRun, DirectRunMessage, DirectWorkspace } from "./types.js";
|
|
4
|
+
export declare function directError(error: unknown): GittermError;
|
|
5
|
+
export declare function directRunOperations(assertWorkspace: (workspace: DirectWorkspace) => void, fetchImpl?: typeof fetch): {
|
|
6
|
+
messages(run: DirectRun): Promise<DirectRunMessage[]>;
|
|
7
|
+
cancel(run: DirectRun): Promise<{
|
|
8
|
+
cancelled: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
events: (ref: DirectRun, options?: RunWatchOptions) => AsyncIterable<import("../types.js").AgentRunEvent<DirectRun>>;
|
|
11
|
+
wait: (ref: DirectRun, options?: import("../types.js").RunWaitOptions) => Promise<DirectRun>;
|
|
12
|
+
result: (ref: DirectRun, options?: import("../types.js").RunResultOptions) => Promise<import("../types.js").AgentRunResult<DirectRun>>;
|
|
13
|
+
get: (run: DirectRun) => Promise<DirectRun>;
|
|
14
|
+
watch: (run: DirectRun, options?: RunWatchOptions) => AsyncGenerator<DirectRun>;
|
|
15
|
+
respond: (run: DirectRun, input: {
|
|
16
|
+
requestId: string;
|
|
17
|
+
reply: AgentRunReply;
|
|
18
|
+
}) => Promise<DirectRun>;
|
|
19
|
+
};
|
package/dist/direct/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentRun, AgentRunMessage, OpencodeApi, RunWaitOptions } from "../types.js";
|
|
1
2
|
export type DirectWorkspaceLifecycle = "ephemeral" | "persistent";
|
|
2
3
|
export type DirectWorkspaceStatus = "pending" | "running" | "paused" | "failed" | "terminated" | "unknown";
|
|
3
4
|
export type DirectProviderCapabilities = {
|
|
@@ -10,13 +11,13 @@ export type DirectProviderCapabilities = {
|
|
|
10
11
|
};
|
|
11
12
|
export type DirectApiModelCredential = {
|
|
12
13
|
providerName: string;
|
|
13
|
-
|
|
14
|
+
source: "apiKey";
|
|
14
15
|
apiKey: string;
|
|
15
16
|
metadata?: Record<string, string>;
|
|
16
17
|
};
|
|
17
18
|
export type DirectOAuthModelCredential = {
|
|
18
19
|
providerName: string;
|
|
19
|
-
|
|
20
|
+
source: "oauth";
|
|
20
21
|
refreshToken: string;
|
|
21
22
|
/** May be omitted when OpenCode should refresh immediately. */
|
|
22
23
|
accessToken?: string;
|
|
@@ -131,7 +132,11 @@ export type DirectWorkspaceCreateInput = {
|
|
|
131
132
|
};
|
|
132
133
|
lifecycle?: DirectWorkspaceLifecycle;
|
|
133
134
|
environmentVariables?: Record<string, string>;
|
|
134
|
-
|
|
135
|
+
models?: {
|
|
136
|
+
default?: string;
|
|
137
|
+
inherit?: "none";
|
|
138
|
+
providers?: Record<string, Omit<DirectApiModelCredential, "providerName"> | Omit<DirectOAuthModelCredential, "providerName">>;
|
|
139
|
+
};
|
|
135
140
|
setup?: DirectWorkspaceSetup;
|
|
136
141
|
secretFiles?: DirectSecretFile[];
|
|
137
142
|
/** Provider-specific attachment settings. */
|
|
@@ -141,6 +146,8 @@ export type DirectWorkspaceCreateInput = {
|
|
|
141
146
|
/** Trusted integration context appended to the generated global AGENTS.md. */
|
|
142
147
|
additionalAgentInstructions?: string;
|
|
143
148
|
opencode?: {
|
|
149
|
+
/** Defaults to v1. v2 requires a compatible provider image/template. */
|
|
150
|
+
api?: OpencodeApi;
|
|
144
151
|
config?: Record<string, unknown>;
|
|
145
152
|
plugins?: string[];
|
|
146
153
|
skills?: Array<{
|
|
@@ -162,6 +169,7 @@ export type DirectWorkspace = {
|
|
|
162
169
|
status: DirectWorkspaceStatus;
|
|
163
170
|
lifecycle: DirectWorkspaceLifecycle;
|
|
164
171
|
runtime: DirectWorkspaceRuntime;
|
|
172
|
+
opencodeApi: OpencodeApi;
|
|
165
173
|
setup: "not_requested" | "before_agent_complete" | "after_agent";
|
|
166
174
|
createdAt: string;
|
|
167
175
|
};
|
|
@@ -293,27 +301,21 @@ export type DirectRunCreateInput = {
|
|
|
293
301
|
title?: string;
|
|
294
302
|
agent?: string;
|
|
295
303
|
model?: string;
|
|
296
|
-
|
|
297
|
-
|
|
304
|
+
context?: {
|
|
305
|
+
type: "isolated";
|
|
306
|
+
} | {
|
|
307
|
+
type: "continue";
|
|
308
|
+
run: DirectRun;
|
|
309
|
+
};
|
|
310
|
+
waitForSetup?: boolean;
|
|
311
|
+
setupTimeoutMs?: number;
|
|
312
|
+
signal?: AbortSignal;
|
|
298
313
|
};
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
314
|
+
/** Contains runtime credentials. Encrypt when persisting; do not send to an untrusted UI. */
|
|
315
|
+
export type DirectRun = AgentRun & {
|
|
316
|
+
workspace: DirectWorkspace;
|
|
302
317
|
sessionId: string;
|
|
303
318
|
messageId: string;
|
|
304
|
-
title: string;
|
|
305
|
-
status: "running" | "retrying" | "completed" | "failed" | "cancelled";
|
|
306
|
-
error: string | null;
|
|
307
|
-
finalText: string | null;
|
|
308
|
-
submittedAt: string;
|
|
309
|
-
};
|
|
310
|
-
export type DirectRunMessage = {
|
|
311
|
-
id: string;
|
|
312
|
-
role: "user" | "assistant";
|
|
313
|
-
text: string;
|
|
314
|
-
error: string | null;
|
|
315
|
-
};
|
|
316
|
-
export type DirectRunWaitOptions = {
|
|
317
|
-
timeoutMs?: number;
|
|
318
|
-
pollIntervalMs?: number;
|
|
319
319
|
};
|
|
320
|
+
export type DirectRunMessage = AgentRunMessage;
|
|
321
|
+
export type DirectRunWaitOptions = RunWaitOptions;
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AgentRun } from "./types.js";
|
|
2
|
+
export type GittermErrorCode = "NOT_LOGGED_IN" | "UNAUTHORIZED" | "NOT_FOUND" | "FORBIDDEN" | "BAD_REQUEST" | "CONFLICT" | "SERVER_ERROR" | "NETWORK" | "ABORTED" | "TIMEOUT" | "INPUT_NOT_PENDING" | "INPUT_REQUIRED" | "RUN_FAILED" | "RUN_CANCELLED" | CredentialErrorCode | WorkspaceLifecycleErrorCode;
|
|
2
3
|
export type CredentialErrorCode = "MODEL_CREDENTIAL_UNAVAILABLE" | "MODEL_CREDENTIAL_DUPLICATE_PROVIDER" | "MODEL_CREDENTIAL_INVALID" | "MODEL_CREDENTIAL_REQUIRED";
|
|
3
4
|
export type WorkspaceLifecycleErrorCode = "WORKSPACE_NOT_RUNNING" | "WORKSPACE_TERMINATED" | "WORKSPACE_NON_RECOVERABLE" | "WORKSPACE_START_TIMEOUT" | "WORKSPACE_RESTART_FAILED";
|
|
4
5
|
export declare const WORKSPACE_LIFECYCLE_ERROR_CODES: readonly WorkspaceLifecycleErrorCode[];
|
|
@@ -15,3 +16,8 @@ export declare class WorkspaceLifecycleError extends GittermError {
|
|
|
15
16
|
cause?: unknown;
|
|
16
17
|
});
|
|
17
18
|
}
|
|
19
|
+
/** result() could not produce a successful result. The run remains inspectable. */
|
|
20
|
+
export declare class AgentRunError<T extends AgentRun = AgentRun> extends GittermError {
|
|
21
|
+
readonly run: T;
|
|
22
|
+
constructor(run: T, code: "INPUT_REQUIRED" | "RUN_FAILED" | "RUN_CANCELLED");
|
|
23
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { DEFAULT_GITTERM_SERVER_URL, getConfigPath, loadConfig, loadConfigSync,
|
|
|
3
3
|
export type { CliConfig } from "./config.js";
|
|
4
4
|
export { loginWithDeviceCode } from "./device-login.js";
|
|
5
5
|
export type { DeviceCodeInfo, LoginWithDeviceCodeOptions } from "./device-login.js";
|
|
6
|
-
export { GittermError, WorkspaceLifecycleError } from "./errors.js";
|
|
6
|
+
export { AgentRunError, GittermError, WorkspaceLifecycleError } from "./errors.js";
|
|
7
7
|
export { createGittermWorkspaceClient, getWorkspaceEnvironment, type GittermWorkspaceClient, type WorkspaceClientOptions, type WorkspaceEnvironment, type WorkspacePort, type WorkspaceSelf, } from "./workspace-client.js";
|
|
8
8
|
export type { CredentialErrorCode, GittermErrorCode, WorkspaceLifecycleErrorCode, } from "./errors.js";
|
|
9
|
-
export type { AgentRun, AgentRunCreateInput, AgentRunInputRequest, AgentRunMessage, AgentRunMessagePart, AgentRunReply, AgentRunStatus, RunWaitOptions, OpencodeApi, AgentType, AgentKey, AuthStatus, BuiltInAgentKey, CloudProvider, ProviderKey, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceCatalog, WorkspaceEnsureRunningResult, WorkspaceHostingType, WorkspaceListOptions, WorkspaceListResult, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspaceStatus, WorkspaceSetupStatus, ModelCredential, ModelProviderInfo,
|
|
9
|
+
export type { AgentPermissionRequest, AgentQuestion, AgentQuestionOption, AgentQuestionRequest, AgentRun, AgentRunCreateInput, AgentRunInputRequest, AgentRunMessage, AgentRunMessagePart, AgentRunReply, AgentRunStatus, AgentRunEvent, AgentRunResult, AgentRunReplyPermission, AgentRunReplyQuestion, RunResultOptions, RunRef, RunWaitOptions, RunWatchOptions, OpencodeApi, AgentType, AgentKey, AuthStatus, BuiltInAgentKey, CloudProvider, ProviderKey, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceCatalog, WorkspaceEnsureRunningResult, WorkspaceHostingType, WorkspaceListOptions, WorkspaceListResult, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspaceStatus, WorkspaceSetupStatus, ModelCredential, ModelProviderInfo, ModelInfo, ModelCredentialSource, WorkspaceModelsInput, WorkspaceModelAccess, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceProviderSelection, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -84,6 +84,15 @@ class WorkspaceLifecycleError extends GittermError {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
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
|
+
|
|
87
96
|
// src/transport.ts
|
|
88
97
|
var LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
89
98
|
function normalizeServerUrl(value) {
|
|
@@ -114,6 +123,152 @@ function createNoRedirectFetch(fetchImpl = fetch) {
|
|
|
114
123
|
};
|
|
115
124
|
}
|
|
116
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
|
+
|
|
117
272
|
// src/client.ts
|
|
118
273
|
function envValue(name) {
|
|
119
274
|
const value = typeof process !== "undefined" ? process.env[name] : undefined;
|
|
@@ -147,16 +302,8 @@ function normalizeBaseCommit(value) {
|
|
|
147
302
|
function workspaceIdOf(ref) {
|
|
148
303
|
return typeof ref === "string" ? ref : ref.id;
|
|
149
304
|
}
|
|
150
|
-
function runTargetOf(
|
|
151
|
-
|
|
152
|
-
if (!runId)
|
|
153
|
-
throw new GittermError("BAD_REQUEST", "runId is required");
|
|
154
|
-
return { workspaceId: refOrWorkspaceId, runId };
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
workspaceId: refOrWorkspaceId.workspaceId,
|
|
158
|
-
runId: "runId" in refOrWorkspaceId ? refOrWorkspaceId.runId : refOrWorkspaceId.id
|
|
159
|
-
};
|
|
305
|
+
function runTargetOf(ref) {
|
|
306
|
+
return { workspaceId: ref.workspaceId, runId: ref.id };
|
|
160
307
|
}
|
|
161
308
|
function throwIfAborted(signal, what) {
|
|
162
309
|
if (signal?.aborted)
|
|
@@ -243,6 +390,8 @@ function mapTrpcCode(code) {
|
|
|
243
390
|
return "BAD_REQUEST";
|
|
244
391
|
case "CONFLICT":
|
|
245
392
|
return "CONFLICT";
|
|
393
|
+
case "TIMEOUT":
|
|
394
|
+
return "TIMEOUT";
|
|
246
395
|
default:
|
|
247
396
|
return "SERVER_ERROR";
|
|
248
397
|
}
|
|
@@ -251,71 +400,86 @@ function credentialErrorCode(message) {
|
|
|
251
400
|
const match = /^(MODEL_CREDENTIAL_(?:UNAVAILABLE|DUPLICATE_PROVIDER|INVALID|REQUIRED)):\s*/.exec(message);
|
|
252
401
|
return match?.[1];
|
|
253
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
|
+
}
|
|
254
432
|
async function runWithServer(serverUrl, operation) {
|
|
255
433
|
try {
|
|
256
434
|
return await operation();
|
|
257
435
|
} catch (error) {
|
|
258
|
-
|
|
259
|
-
throw error;
|
|
260
|
-
if (error instanceof TRPCClientError) {
|
|
261
|
-
const trpcCode = error.data?.code;
|
|
262
|
-
if (!error.data) {
|
|
263
|
-
throw new GittermError("NETWORK", `Could not reach the GitTerm server at ${serverUrl}`, {
|
|
264
|
-
cause: error
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
const code = mapTrpcCode(trpcCode);
|
|
268
|
-
const credentialCode = credentialErrorCode(error.message);
|
|
269
|
-
if (credentialCode)
|
|
270
|
-
throw new GittermError(credentialCode, error.message, { cause: error });
|
|
271
|
-
const lifecycleCode = WORKSPACE_LIFECYCLE_ERROR_CODES.find((candidate) => error.message.includes(candidate));
|
|
272
|
-
if (lifecycleCode) {
|
|
273
|
-
throw new WorkspaceLifecycleError(lifecycleCode, error.message, { cause: error });
|
|
274
|
-
}
|
|
275
|
-
throw new GittermError(code, code === "UNAUTHORIZED" ? `Authentication failed: ${error.message}. Check that the API token is valid and has not expired.` : error.message, { cause: error });
|
|
276
|
-
}
|
|
277
|
-
throw new GittermError("NETWORK", error instanceof Error ? error.message : "Network request failed", { cause: error });
|
|
436
|
+
throw toGittermError(serverUrl, error);
|
|
278
437
|
}
|
|
279
438
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const run = last;
|
|
306
|
-
finish(() => resolve(run));
|
|
307
|
-
}
|
|
308
|
-
},
|
|
309
|
-
onError: (error) => finish(() => reject(error)),
|
|
310
|
-
onComplete: () => finish(() => {
|
|
311
|
-
if (last && settlesWait(last.status, options?.until))
|
|
312
|
-
resolve(last);
|
|
313
|
-
else {
|
|
314
|
-
reject(new GittermError("NETWORK", `Run ${target.runId} event stream ended unexpectedly`));
|
|
315
|
-
}
|
|
316
|
-
})
|
|
317
|
-
});
|
|
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
|
+
})
|
|
318
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;
|
|
478
|
+
}
|
|
479
|
+
} finally {
|
|
480
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
481
|
+
subscription.unsubscribe();
|
|
482
|
+
}
|
|
319
483
|
}
|
|
320
484
|
function createGittermClient(options = {}) {
|
|
321
485
|
const credentials = resolveCredentials(options);
|
|
@@ -341,6 +505,8 @@ function createGittermClient(options = {}) {
|
|
|
341
505
|
]
|
|
342
506
|
});
|
|
343
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 }));
|
|
344
510
|
const normalizeCreateResult = (result) => {
|
|
345
511
|
const workspace = normalizeWorkspace(result.workspace);
|
|
346
512
|
if (!workspace)
|
|
@@ -368,7 +534,7 @@ function createGittermClient(options = {}) {
|
|
|
368
534
|
const timeoutMs = waitOptions?.timeoutMs ?? 10 * 60000;
|
|
369
535
|
const pollIntervalMs = waitOptions?.pollIntervalMs ?? 2000;
|
|
370
536
|
const signal = waitOptions?.signal;
|
|
371
|
-
const
|
|
537
|
+
const deadline2 = Date.now() + timeoutMs;
|
|
372
538
|
while (true) {
|
|
373
539
|
throwIfAborted(signal, "waiting for workspace setup");
|
|
374
540
|
const result = await trpc.workspace.getSetupStatus.query({ workspaceId }, { signal });
|
|
@@ -379,9 +545,9 @@ function createGittermClient(options = {}) {
|
|
|
379
545
|
throw new GittermError("BAD_REQUEST", `Workspace setup failed${result.exitCode === null ? "" : ` with exit code ${result.exitCode}`}${log ? `
|
|
380
546
|
${log}` : ""}`);
|
|
381
547
|
}
|
|
382
|
-
if (Date.now() >=
|
|
548
|
+
if (Date.now() >= deadline2) {
|
|
383
549
|
const log = result.log?.trim();
|
|
384
|
-
throw new GittermError("
|
|
550
|
+
throw new GittermError("TIMEOUT", `Timed out waiting for workspace ${workspaceId} setup (last status: ${result.status})${log ? `
|
|
385
551
|
${log}` : ""}`);
|
|
386
552
|
}
|
|
387
553
|
await sleep(pollIntervalMs, signal, "waiting for workspace setup");
|
|
@@ -462,12 +628,18 @@ ${log}` : ""}`);
|
|
|
462
628
|
};
|
|
463
629
|
}),
|
|
464
630
|
create: createWorkspace,
|
|
631
|
+
models: (ref) => run(() => trpc.workspace.getModelAccess.query({ workspaceId: workspaceIdOf(ref) })),
|
|
465
632
|
setupStatus: (ref) => run(async () => trpc.workspace.getSetupStatus.query({ workspaceId: workspaceIdOf(ref) })),
|
|
466
633
|
waitForSetup: (ref, waitOptions) => run(() => waitForWorkspaceSetup(workspaceIdOf(ref), waitOptions))
|
|
467
634
|
},
|
|
468
635
|
runs: {
|
|
469
636
|
create: (input) => run(async () => {
|
|
470
|
-
const { signal, ...
|
|
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
|
+
};
|
|
471
643
|
if (request.waitForSetup) {
|
|
472
644
|
await waitForWorkspaceSetup(request.workspaceId, {
|
|
473
645
|
timeoutMs: request.setupTimeoutMs,
|
|
@@ -481,22 +653,15 @@ ${log}` : ""}`);
|
|
|
481
653
|
workspaceId: workspaceIdOf(ref),
|
|
482
654
|
...listOptions
|
|
483
655
|
})),
|
|
484
|
-
get: (
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}),
|
|
488
|
-
messages: (refOrWorkspaceId, runId) => run(async () => {
|
|
489
|
-
const target = runTargetOf(refOrWorkspaceId, runId);
|
|
490
|
-
const messages = await trpc.run.messages.query(target);
|
|
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));
|
|
491
659
|
return messages.map((message) => ({ ...message, parts: message.parts ?? [] }));
|
|
492
660
|
}),
|
|
493
|
-
cancel: (
|
|
494
|
-
respond
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const waitOptions = typeof refOrWorkspaceId === "string" ? maybeOptions : runIdOrOptions;
|
|
498
|
-
return waitForRun(trpc, target, waitOptions);
|
|
499
|
-
})
|
|
661
|
+
cancel: (ref) => run(async () => trpc.run.cancel.mutate(runTargetOf(ref))),
|
|
662
|
+
respond,
|
|
663
|
+
watch,
|
|
664
|
+
...runHelpers({ watch, respond })
|
|
500
665
|
},
|
|
501
666
|
catalog: {
|
|
502
667
|
agentTypes: (input) => run(async () => {
|
|
@@ -509,6 +674,18 @@ ${log}` : ""}`);
|
|
|
509
674
|
}),
|
|
510
675
|
workspaceOptions: () => run(async () => trpc.workspace.getWorkspaceCatalog.query())
|
|
511
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
|
+
},
|
|
512
689
|
credentials: {
|
|
513
690
|
list: () => run(async () => {
|
|
514
691
|
const result = await trpc.modelCredentials.listMyCredentials.query();
|
|
@@ -525,6 +702,7 @@ ${log}` : ""}`);
|
|
|
525
702
|
return result.providers.map((provider) => ({
|
|
526
703
|
id: provider.id,
|
|
527
704
|
name: provider.name,
|
|
705
|
+
logicalProviderKey: provider.logicalProviderKey,
|
|
528
706
|
displayName: provider.displayName,
|
|
529
707
|
authType: provider.authType,
|
|
530
708
|
isRecommended: provider.isRecommended
|
|
@@ -554,8 +732,8 @@ async function loginWithDeviceCode(serverUrl, options = {}) {
|
|
|
554
732
|
intervalSeconds: codeJson.intervalSeconds,
|
|
555
733
|
expiresInSeconds: codeJson.expiresInSeconds
|
|
556
734
|
});
|
|
557
|
-
const
|
|
558
|
-
while (Date.now() <
|
|
735
|
+
const deadline2 = Date.now() + codeJson.expiresInSeconds * 1000;
|
|
736
|
+
while (Date.now() < deadline2) {
|
|
559
737
|
const tokenRes = await fetchImpl(new URL("/api/device/token", normalizedServerUrl), {
|
|
560
738
|
method: "POST",
|
|
561
739
|
headers: { "content-type": "application/json" },
|
|
@@ -639,6 +817,7 @@ function createGittermWorkspaceClient(options = {}) {
|
|
|
639
817
|
};
|
|
640
818
|
}
|
|
641
819
|
export {
|
|
820
|
+
AgentRunError,
|
|
642
821
|
DEFAULT_GITTERM_SERVER_URL,
|
|
643
822
|
GittermError,
|
|
644
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
|
+
};
|