@gitterm/sdk 0.1.3 → 0.2.1

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.
@@ -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
+ };
@@ -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
- type?: "api";
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
- type: "oauth";
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
- modelCredentials?: DirectModelCredential[];
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
- /** Reuse this native OpenCode session for conversational context. */
297
- sessionId?: string;
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
- export type DirectRun = {
300
- id: string;
301
- workspaceId: string;
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
- export type GittermErrorCode = "NOT_LOGGED_IN" | "UNAUTHORIZED" | "NOT_FOUND" | "FORBIDDEN" | "BAD_REQUEST" | "CONFLICT" | "SERVER_ERROR" | "NETWORK" | "ABORTED" | CredentialErrorCode | WorkspaceLifecycleErrorCode;
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, WorkspaceModelCredentialInput, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceProviderSelection, } from "./types.js";
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(refOrWorkspaceId, runId) {
151
- if (typeof refOrWorkspaceId === "string") {
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
- if (error instanceof GittermError)
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 settlesWait(status, until) {
281
- if (status === "completed" || status === "failed" || status === "cancelled")
282
- return true;
283
- return status === "awaiting_input" && until !== "terminal";
284
- }
285
- function waitForRun(trpc, target, options) {
286
- const timeoutMs = options?.timeoutMs ?? 30 * 60000;
287
- const what = `waiting for run ${target.runId}`;
288
- return new Promise((resolve, reject) => {
289
- throwIfAborted(options?.signal, what);
290
- let last;
291
- let subscription;
292
- const finish = (settle) => {
293
- clearTimeout(timer);
294
- options?.signal?.removeEventListener("abort", onAbort);
295
- settle();
296
- subscription?.unsubscribe();
297
- };
298
- const onAbort = () => finish(() => reject(new GittermError("ABORTED", `Aborted while ${what}`)));
299
- const timer = setTimeout(() => finish(() => reject(new GittermError("NETWORK", `Timed out ${what}`))), timeoutMs);
300
- options?.signal?.addEventListener("abort", onAbort, { once: true });
301
- subscription = trpc.run.lifecycle.subscribe(target, {
302
- onData: (event) => {
303
- last = event.run;
304
- if (settlesWait(last.status, options?.until)) {
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 deadline = Date.now() + timeoutMs;
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() >= deadline) {
548
+ if (Date.now() >= deadline2) {
383
549
  const log = result.log?.trim();
384
- throw new GittermError("NETWORK", `Timed out waiting for workspace ${workspaceId} setup (last status: ${result.status})${log ? `
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, ...request } = input;
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: (refOrWorkspaceId, runId) => run(async () => {
485
- const target = runTargetOf(refOrWorkspaceId, runId);
486
- return trpc.run.get.query(target);
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: (refOrWorkspaceId, runId) => run(async () => trpc.run.cancel.mutate(runTargetOf(refOrWorkspaceId, runId))),
494
- respond: (ref, input) => run(async () => trpc.run.respond.mutate({ ...runTargetOf(ref), ...input })),
495
- wait: (refOrWorkspaceId, runIdOrOptions, maybeOptions) => run(async () => {
496
- const target = typeof refOrWorkspaceId === "string" ? runTargetOf(refOrWorkspaceId, runIdOrOptions) : runTargetOf(refOrWorkspaceId);
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 deadline = Date.now() + codeJson.expiresInSeconds * 1000;
558
- while (Date.now() < deadline) {
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
+ };