@gitterm/sdk 0.1.1 → 0.1.3
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 +314 -46
- package/dist/client.d.ts +26 -18
- package/dist/errors.d.ts +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +174 -60
- package/dist/types.d.ts +213 -14
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -224,6 +224,49 @@ const client = createGittermClient({
|
|
|
224
224
|
const { workspaces } = await client.workspaces.list();
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
### Managed private repositories
|
|
228
|
+
|
|
229
|
+
For renewable, short-lived repository authentication, connect the GitHub App in the GitTerm
|
|
230
|
+
dashboard and copy its **SDK integration ID** from the Integrations page:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const { workspace, runtime } = await client.workspaces.create({
|
|
234
|
+
repo: "https://github.com/acme/private-repo",
|
|
235
|
+
branch: "main",
|
|
236
|
+
gitIntegrationId: "your-dashboard-integration-id",
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Managed workspaces can also use dashboard-managed model subscriptions while accepting an
|
|
241
|
+
application-owned GitHub PAT inline:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const client = createGittermClient({
|
|
245
|
+
token: process.env.GITTERM_API_TOKEN,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const { workspace, runtime } = await client.workspaces.create({
|
|
249
|
+
repo: "https://github.com/acme/private-repo",
|
|
250
|
+
branch: "main",
|
|
251
|
+
repositoryCredentials: {
|
|
252
|
+
username: "x-access-token",
|
|
253
|
+
token: process.env.GITHUB_TOKEN!,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The username defaults to `x-access-token`. Inline repository credentials take precedence over
|
|
259
|
+
`gitIntegrationId` and authenticate repository validation, cloning, and runtime Git operations such
|
|
260
|
+
as pull and push. Without inline credentials, `gitIntegrationId` continues to use the connected
|
|
261
|
+
dashboard integration. Omitting `modelCredentials` likewise continues to
|
|
262
|
+
use dashboard-managed model credentials.
|
|
263
|
+
|
|
264
|
+
GitTerm does not save inline PATs in its application database. Inline PATs must be delivered to the
|
|
265
|
+
selected compute provider and retained on the workspace machine for runtime Git operations, so
|
|
266
|
+
provider infrastructure and processes running in that workspace may be able to access them. Prefer
|
|
267
|
+
`gitIntegrationId` for durable managed workspaces and use narrowly scoped, short-lived PATs when
|
|
268
|
+
inline credentials are necessary.
|
|
269
|
+
|
|
227
270
|
The SDK deliberately exposes two clients. `createGittermClient()` uses a user API token and
|
|
228
271
|
can manage the user's workspaces. `createGittermWorkspaceClient()` uses the scoped identity
|
|
229
272
|
injected into a GitTerm workspace and can inspect only that workspace and its ports:
|
|
@@ -251,21 +294,102 @@ const status = await client.auth.status();
|
|
|
251
294
|
|
|
252
295
|
```ts
|
|
253
296
|
client.auth.status(); // -> { userId, email, name, plan, authMethod }
|
|
254
|
-
client.workspaces.list(options?); // -> { workspaces, pagination }
|
|
255
|
-
client.workspaces.get(
|
|
256
|
-
client.workspaces.getRuntimeAccess(
|
|
257
|
-
client.workspaces.ensureRunning(
|
|
258
|
-
client.workspaces.
|
|
259
|
-
client.workspaces.
|
|
260
|
-
client.workspaces.
|
|
297
|
+
client.workspaces.list(options?); // -> { workspaces, pagination }; filter by status or metadata
|
|
298
|
+
client.workspaces.get(workspace); // workspace = id string or any object with an `id`
|
|
299
|
+
client.workspaces.getRuntimeAccess(workspace); // read-only; never resumes compute
|
|
300
|
+
client.workspaces.ensureRunning(workspace, options?); // resume if paused, wait until running
|
|
301
|
+
client.workspaces.setupStatus(workspace);
|
|
302
|
+
client.workspaces.waitForSetup(workspace, options?);
|
|
303
|
+
client.workspaces.pause(workspace); // -> { durationMinutes } of the usage session just closed
|
|
304
|
+
client.workspaces.restart(workspace);
|
|
305
|
+
client.workspaces.terminate(workspace); // -> { workspace, cleanupInBackground }
|
|
261
306
|
client.workspaces.create({
|
|
262
307
|
repo: "https://github.com/acme/product",
|
|
263
308
|
});
|
|
309
|
+
client.runs.create(input);
|
|
310
|
+
client.runs.list(workspace, options?); // -> { runs, pagination }
|
|
311
|
+
client.runs.get(run); // run = AgentRun, { workspaceId, runId }, or (workspaceId, runId)
|
|
312
|
+
client.runs.messages(run);
|
|
313
|
+
client.runs.cancel(run);
|
|
314
|
+
client.runs.wait(run, options?); // resolves when terminal or awaiting_input (push-based)
|
|
315
|
+
client.runs.respond(run, { requestId, reply }); // answer a permission prompt or agent question
|
|
264
316
|
client.catalog.agentTypes();
|
|
265
317
|
client.catalog.cloudProviders();
|
|
266
318
|
client.catalog.workspaceOptions();
|
|
267
319
|
```
|
|
268
320
|
|
|
321
|
+
Every wait (`ensureRunning`, `waitForSetup`, and `runs.create` with `waitForSetup`) accepts
|
|
322
|
+
`{ timeoutMs, pollIntervalMs, signal }`. `runs.wait` is push-based (server-sent events) and
|
|
323
|
+
accepts `{ timeoutMs, signal, until }`. Pass an `AbortSignal` to stop waiting on shutdown; the
|
|
324
|
+
promise rejects with code `ABORTED`.
|
|
325
|
+
|
|
326
|
+
`pause()` returns `durationMinutes`, the length of the usage session it just closed, which is
|
|
327
|
+
what billing records. `terminate()` returns `cleanupInBackground: true` when the provider
|
|
328
|
+
finishes tearing down resources after the call returns.
|
|
329
|
+
|
|
330
|
+
#### Tagging and lifetime
|
|
331
|
+
|
|
332
|
+
`metadata` attaches caller-owned tags to a workspace so you can find it again without a
|
|
333
|
+
lookup table of your own, and `autoTerminateAfterMs` caps how long it can live:
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
await client.workspaces.create({
|
|
337
|
+
repo: "https://github.com/acme/product",
|
|
338
|
+
metadata: { tenant: "acme", channel: "C0123" },
|
|
339
|
+
autoTerminateAfterMs: 2 * 60 * 60 * 1000, // gone in 2h even if this process crashes
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const { workspaces } = await client.workspaces.list({
|
|
343
|
+
metadata: { tenant: "acme", channel: "C0123" },
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Metadata allows up to 20 keys of letters, digits, `_ . : -`, with values up to 500 characters,
|
|
348
|
+
and `list` matches workspaces containing every given pair. `autoTerminateAfterMs` ranges from
|
|
349
|
+
1 minute to 30 days; the reaper terminates the workspace once the time passes regardless of
|
|
350
|
+
activity, and `workspace.autoTerminateAt` shows the deadline. Idle workspaces are still paused
|
|
351
|
+
by the platform's idle policy independently of this.
|
|
352
|
+
|
|
353
|
+
#### Bring your own image
|
|
354
|
+
|
|
355
|
+
Pass `image` to run your own build instead of the catalog image. On the managed service the
|
|
356
|
+
image must be pullable without credentials; creation fails immediately with the reason if it
|
|
357
|
+
isn't, rather than leaving a workspace stuck pulling.
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
await client.workspaces.create({
|
|
361
|
+
repo: "https://github.com/acme/product",
|
|
362
|
+
provider: { type: "railway" },
|
|
363
|
+
image: "ghcr.io/acme/agent-runner:1.4.0",
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// E2B takes a public template id or alias instead of a registry reference.
|
|
367
|
+
await client.workspaces.create({
|
|
368
|
+
repo: "https://github.com/acme/product",
|
|
369
|
+
provider: { type: "e2b" },
|
|
370
|
+
image: "acme-python-runner",
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Supported on Railway, AWS, Daytona, exe.dev, and E2B. Vercel and Cloudflare run fixed
|
|
375
|
+
runtimes and reject `image`. Floating tags such as `latest` are pinned to the digest verified
|
|
376
|
+
at create time, so a restart months later runs the same image; `workspace.customImage` shows
|
|
377
|
+
what was pinned.
|
|
378
|
+
|
|
379
|
+
The image has to behave like the stock one, because GitTerm's entrypoint does the clone, writes
|
|
380
|
+
agent files, runs setup phases, and starts the agent. Layer on the published base and keep its
|
|
381
|
+
entrypoint:
|
|
382
|
+
|
|
383
|
+
```dockerfile
|
|
384
|
+
FROM opeoginni/gitterm-opencode-server:latest
|
|
385
|
+
RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-pip poppler-utils \
|
|
386
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Rules: do not override `ENTRYPOINT` or `CMD`; keep global installs outside `/workspace`, which
|
|
390
|
+
is a persisted volume; keep `opencode` and `@gitterm/cli` on `PATH`; keep port 7681. For E2B,
|
|
391
|
+
build your template from the same base and publish it so any account can start it.
|
|
392
|
+
|
|
269
393
|
The server defaults the agent to `opencode`, selects the user's preferred provider,
|
|
270
394
|
uses that provider's default machine profile, and applies the provider's persistence policy.
|
|
271
395
|
Override only the placement decisions your integration cares about:
|
|
@@ -342,12 +466,104 @@ run UI review or browser capture tools in the sandbox, upload the resulting medi
|
|
|
342
466
|
the changelog in the checked-out repository, then terminate the workspace. Use an
|
|
343
467
|
`idempotencyKey` based on the release SHA when the workflow may be retried.
|
|
344
468
|
|
|
469
|
+
### Workspace lifecycle and readiness
|
|
470
|
+
|
|
471
|
+
A workspace has one of four statuses:
|
|
472
|
+
|
|
473
|
+
| Status | Meaning |
|
|
474
|
+
| ------------ | ----------------------------------------------------------------------------------------- |
|
|
475
|
+
| `pending` | Compute is being provisioned or resumed. `runtime.url` is `null`. |
|
|
476
|
+
| `running` | The provider reports the sandbox or container up. The agent process may still be booting. |
|
|
477
|
+
| `paused` | Stopped but resumable. |
|
|
478
|
+
| `terminated` | Gone for good. |
|
|
479
|
+
|
|
480
|
+
Whether `create()` returns `running` or `pending` depends on the provider. Sandbox providers
|
|
481
|
+
(E2B, Daytona, Vercel, Ascii, exe.dev, Cloudflare) settle immediately and return `running`.
|
|
482
|
+
Railway settles by webhook and returns `pending` until its deployment reports success, usually
|
|
483
|
+
within a minute. Code that only ever ran against a sandbox provider will see `pending` for the
|
|
484
|
+
first time when it moves to Railway.
|
|
485
|
+
|
|
486
|
+
Three things have to be true before a prompt can be delivered, and the SDK handles each:
|
|
487
|
+
|
|
488
|
+
1. **The workspace is `running`.** `runs.create()` waits for a `pending` workspace to
|
|
489
|
+
transition on its own, up to `startTimeoutMs` (default 120s). It does not resume a `paused`
|
|
490
|
+
workspace; call `ensureRunning()` for that, which resumes, polls until `running`, and returns
|
|
491
|
+
the runtime access.
|
|
492
|
+
2. **The agent answers.** `running` is the provider's view. `runs.create()` then probes the
|
|
493
|
+
workspace URL until the agent server responds before submitting the prompt, so callers never
|
|
494
|
+
need their own health check.
|
|
495
|
+
3. **Setup finished (optional).** `afterAgent` commands run in the background once the agent is
|
|
496
|
+
reachable. Pass `waitForSetup: true` to `runs.create()` to block on them, or poll with
|
|
497
|
+
`setupStatus()` / `waitForSetup()`. Status `waiting` means the agent isn't reachable yet, so
|
|
498
|
+
setup hasn't started; `running`, `succeeded`, and `failed` describe the phase itself;
|
|
499
|
+
`not_requested` means there were no `afterAgent` commands. The SDK polls setup from the
|
|
500
|
+
client so a long `npm ci` never holds one HTTP request open; that polling needs the
|
|
501
|
+
`workspace:read` scope on the token in addition to `run:write`. `waiting` cannot last
|
|
502
|
+
forever: while you poll, the server also reads the setup state files inside the workspace
|
|
503
|
+
(through the agent when the provider has no exec channel), and if nothing has progressed
|
|
504
|
+
after 15 minutes it marks the setup `failed` with a log explaining what stalled.
|
|
505
|
+
|
|
506
|
+
The shortest correct sequence is therefore:
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
const { workspace } = await client.workspaces.create({ repo, setup: { afterAgent: ["npm ci"] } });
|
|
510
|
+
const run = await client.runs.create({
|
|
511
|
+
workspaceId: workspace.id,
|
|
512
|
+
idempotencyKey: `review-${sha}`,
|
|
513
|
+
waitForSetup: true,
|
|
514
|
+
prompt: "Review the open pull request",
|
|
515
|
+
});
|
|
516
|
+
const done = await client.runs.wait(workspace.id, run.id);
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
For a workspace you didn't just create, start with `ensureRunning()`:
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
const { workspace, runtime } = await client.workspaces.ensureRunning(workspaceId);
|
|
523
|
+
// runtime.url is set; workspace.status is "running"
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
Failures surface as `WorkspaceLifecycleError` with stable codes: `WORKSPACE_NOT_RUNNING` (the
|
|
527
|
+
workspace is `paused`, or stopped while waiting), `WORKSPACE_TERMINATED`,
|
|
528
|
+
`WORKSPACE_START_TIMEOUT` (still `pending` after the timeout, or no runtime URL),
|
|
529
|
+
`WORKSPACE_NON_RECOVERABLE`, and `WORKSPACE_RESTART_FAILED`.
|
|
530
|
+
|
|
531
|
+
Direct provider mode behaves differently: `direct.workspaces.create()` waits for the OpenCode
|
|
532
|
+
runtime to answer before returning, so a direct workspace is ready for `runs.create()` as soon
|
|
533
|
+
as `create()` resolves.
|
|
534
|
+
|
|
345
535
|
### Agent runs
|
|
346
536
|
|
|
347
537
|
Runs use durable GitTerm IDs backed by the workspace's native OpenCode session. Reusing an
|
|
348
538
|
idempotency key with the same input returns the original run, and terminal results remain
|
|
349
|
-
available after the workspace is paused.
|
|
350
|
-
|
|
539
|
+
available after the workspace is paused. `idempotencyKey` defaults to a random UUID; pass your
|
|
540
|
+
own whenever a retry could otherwise submit the same prompt twice. Completion means the native
|
|
541
|
+
session became idle; it does not claim that a pull request, upload, or other product outcome
|
|
542
|
+
succeeded.
|
|
543
|
+
|
|
544
|
+
Every run carries `createdAt`, `submittedAt` (when the prompt reached the agent), and
|
|
545
|
+
`completedAt`. `runs.list(workspace, { status: "active" })` returns what is still in flight, so a
|
|
546
|
+
process recovering after a restart can pick up where it left off without having persisted run
|
|
547
|
+
ids itself. The server watches every active run's OpenCode session and keeps the run current;
|
|
548
|
+
`get`/`list`/`messages` read that state without touching the workspace.
|
|
549
|
+
|
|
550
|
+
Statuses: `pending` → `running` (or `retrying` while OpenCode backs off from the model
|
|
551
|
+
provider) → `completed` | `failed` | `cancelled`. A run that stops to ask something is
|
|
552
|
+
`awaiting_input` until you answer it (below).
|
|
553
|
+
|
|
554
|
+
`runs.messages()` returns each turn's concatenated `text` plus an ordered `parts` array. Tool
|
|
555
|
+
calls appear as `{ type: "tool", tool, status, title, input, output, error }` with output
|
|
556
|
+
truncated to 4000 characters, which is usually enough to see why a run went wrong:
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
for (const message of await client.runs.messages(run)) {
|
|
560
|
+
for (const part of message.parts) {
|
|
561
|
+
if (part.type === "tool" && part.status === "error") {
|
|
562
|
+
console.log(part.tool, part.title, part.error);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
351
567
|
|
|
352
568
|
```ts
|
|
353
569
|
const { workspace } = await client.workspaces.create({
|
|
@@ -366,6 +582,50 @@ const completed = await client.runs.wait(workspace.id, run.id);
|
|
|
366
582
|
const messages = await client.runs.messages(workspace.id, run.id);
|
|
367
583
|
```
|
|
368
584
|
|
|
585
|
+
#### Questions and permission prompts
|
|
586
|
+
|
|
587
|
+
When the agent calls OpenCode's `question` tool, or a tool needs approval under your
|
|
588
|
+
`permission` config, the run becomes `awaiting_input` and `run.pendingInputs` describes what it
|
|
589
|
+
is waiting for. `runs.wait()` returns at that point by default; answer with `runs.respond()` and
|
|
590
|
+
wait again. This is what makes a chat bot able to relay "should I do A or B?" to a human.
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
let run = await client.runs.wait(workspace.id, created.id);
|
|
594
|
+
while (run.status === "awaiting_input") {
|
|
595
|
+
for (const request of run.pendingInputs) {
|
|
596
|
+
if (request.kind === "permission") {
|
|
597
|
+
// request.title, e.g. "bash: rm -rf dist"; also permission / patterns / always
|
|
598
|
+
await client.runs.respond(run, {
|
|
599
|
+
requestId: request.id,
|
|
600
|
+
reply: { type: "permission", response: "once" }, // "once" | "always" | "reject"
|
|
601
|
+
});
|
|
602
|
+
} else {
|
|
603
|
+
// request.questions[i] has header, question, options[{ label, description }], multiple, custom
|
|
604
|
+
const answers = request.questions.map((question) => [question.options[0]!.label]);
|
|
605
|
+
await client.runs.respond(run, {
|
|
606
|
+
requestId: request.id,
|
|
607
|
+
reply: { type: "question", answers },
|
|
608
|
+
});
|
|
609
|
+
// or: reply: { type: "question", reject: true }
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
run = await client.runs.wait(workspace.id, created.id);
|
|
613
|
+
}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
Rejecting a permission or dismissing a question ends the turn: OpenCode records a failed tool
|
|
617
|
+
call and the run finishes. Pass `{ until: "terminal" }` to `runs.wait()` to keep waiting through
|
|
618
|
+
`awaiting_input` (for example when a separate process answers). A run left `awaiting_input` does
|
|
619
|
+
not keep its workspace awake; if nobody answers before the workspace's idle timeout pauses it,
|
|
620
|
+
the run is cancelled with `"Workspace paused while waiting for input"`. To avoid prompts entirely
|
|
621
|
+
in headless runs, allow the relevant tools in `opencode.config.permission` when creating the
|
|
622
|
+
workspace.
|
|
623
|
+
|
|
624
|
+
`runs.wait()` uses a lightweight lifecycle stream internally; it does not mirror OpenCode's
|
|
625
|
+
native message, tool, or token events. `runs.messages()` provides the persisted transcript at
|
|
626
|
+
lifecycle checkpoints and completion. For live execution details, use
|
|
627
|
+
`workspaces.getRuntimeAccess()` with the official OpenCode SDK.
|
|
628
|
+
|
|
369
629
|
Runs are isolated by default and can execute in parallel. To preserve conversational context,
|
|
370
630
|
continue a terminal run; continued runs sharing context must remain sequential:
|
|
371
631
|
|
|
@@ -380,57 +640,65 @@ const next = await client.runs.create({
|
|
|
380
640
|
|
|
381
641
|
### Model provider credentials
|
|
382
642
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
**Dashboard credentials** — list the account's credential metadata, choose one active credential
|
|
386
|
-
per provider, and pass the IDs when creating a workspace. The SDK never returns credential
|
|
387
|
-
secrets.
|
|
643
|
+
`modelCredentials` takes one entry per provider. Each entry selects where that provider's
|
|
644
|
+
credential comes from, and the three forms compose in one array:
|
|
388
645
|
|
|
389
646
|
```ts
|
|
390
|
-
const credentials = await client.credentials.list();
|
|
391
|
-
const selected = credentials.filter(
|
|
392
|
-
(credential) =>
|
|
393
|
-
credential.isActive && ["anthropic", "openai"].includes(credential.logicalProviderKey),
|
|
394
|
-
);
|
|
395
|
-
|
|
396
647
|
const { workspace } = await client.workspaces.create({
|
|
397
648
|
repo: "https://github.com/acme/product",
|
|
398
|
-
|
|
649
|
+
modelCredentials: [
|
|
650
|
+
{ providerName: "openai", label: "work" }, // dashboard credential, by label
|
|
651
|
+
{ providerName: "anthropic" }, // that provider's dashboard default
|
|
652
|
+
{ providerName: "google", apiKey: process.env.GOOGLE_API_KEY! }, // inline, this workspace only
|
|
653
|
+
],
|
|
399
654
|
});
|
|
400
655
|
```
|
|
401
656
|
|
|
402
|
-
**
|
|
403
|
-
|
|
404
|
-
`client.credentials.
|
|
405
|
-
|
|
657
|
+
**Dashboard credentials** are addressed by `providerName` plus the `label` you gave them in the
|
|
658
|
+
dashboard (labels are unique per provider). Omit `label` to use the provider's default. The SDK
|
|
659
|
+
never returns credential secrets; `client.credentials.list()` returns metadata including labels
|
|
660
|
+
if you need to discover them, and requires an API token with the `workspace:write` scope.
|
|
406
661
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
});
|
|
662
|
+
**Inline credentials** pass an API key directly for this workspace only. They are injected into
|
|
663
|
+
the provisioned agent and never stored in the dashboard. Use `client.credentials.listProviders()`
|
|
664
|
+
for valid provider names; OAuth providers (e.g. GitHub Copilot) can only be connected through
|
|
665
|
+
the dashboard.
|
|
412
666
|
|
|
413
667
|
const run = await client.runs.create({
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
668
|
+
workspaceId: workspace.workspaceId,
|
|
669
|
+
model: "anthropic/claude-sonnet-4-20250514",
|
|
670
|
+
prompt: "Record before/after videos of the changes in PR #42",
|
|
417
671
|
});
|
|
418
|
-
|
|
672
|
+
|
|
673
|
+
````
|
|
419
674
|
|
|
420
675
|
Rules and errors:
|
|
421
676
|
|
|
422
|
-
- Omit
|
|
677
|
+
- Omit `modelCredentials` to inject every dashboard default.
|
|
678
|
+
- Naming any dashboard credential (an entry without `apiKey`) switches off the implicit defaults,
|
|
679
|
+
so list each provider the workspace needs. Inline-only arrays keep the defaults for the other
|
|
680
|
+
providers.
|
|
423
681
|
- One credential per logical provider. An inline credential always overrides the dashboard
|
|
424
|
-
credential
|
|
425
|
-
|
|
426
|
-
- Unknown providers or inline keys for OAuth-only providers throw `MODEL_CREDENTIAL_INVALID
|
|
427
|
-
|
|
682
|
+
credential for the same provider; two entries for the same provider throw
|
|
683
|
+
`MODEL_CREDENTIAL_DUPLICATE_PROVIDER`.
|
|
684
|
+
- Unknown providers or inline keys for OAuth-only providers throw `MODEL_CREDENTIAL_INVALID`.
|
|
685
|
+
A label that doesn't exist throws `MODEL_CREDENTIAL_UNAVAILABLE` and the message lists the
|
|
686
|
+
labels that do.
|
|
428
687
|
- A run that requests a credential-backed `provider/model` not available in its workspace throws
|
|
429
688
|
`MODEL_CREDENTIAL_REQUIRED` before the prompt is submitted.
|
|
430
689
|
|
|
431
|
-
Use `client.runs.cancel(workspaceId, runId)` to abort the current run
|
|
432
|
-
|
|
433
|
-
`workspaces.getRuntimeAccess()` and connect
|
|
690
|
+
Use `client.runs.cancel(workspaceId, runId)` to abort the current run; a run that is
|
|
691
|
+
`awaiting_input` has its pending prompt rejected first. GitTerm keeps the underlying OpenCode
|
|
692
|
+
session private. For native session control, use `workspaces.getRuntimeAccess()` and connect
|
|
693
|
+
with the official OpenCode SDK.
|
|
694
|
+
|
|
695
|
+
### OpenCode API versions
|
|
696
|
+
|
|
697
|
+
Managed workspaces continue to run `opencode-ai@latest` (`opencodeApi: "v1"`). OpenCode 2
|
|
698
|
+
changes the server API; callers testing it must supply their own compatible image and create the
|
|
699
|
+
workspace with `opencode: { api: "v2" }`. Runs, questions, permissions, and events behave the
|
|
700
|
+
same from the SDK's point of view; the flag only tells GitTerm which protocol to speak to the
|
|
701
|
+
workspace. `v2` is experimental until OpenCode 2 ships.
|
|
434
702
|
|
|
435
703
|
### Errors
|
|
436
704
|
|
|
@@ -446,13 +714,13 @@ try {
|
|
|
446
714
|
// "Not logged in. Run: gitterm login"
|
|
447
715
|
}
|
|
448
716
|
}
|
|
449
|
-
|
|
717
|
+
````
|
|
450
718
|
|
|
451
719
|
Workspace lifecycle failures are also exposed as `WorkspaceLifecycleError`, with stable
|
|
452
|
-
`
|
|
453
|
-
`WORKSPACE_RESTART_FAILED` codes. General codes are
|
|
720
|
+
`WORKSPACE_NOT_RUNNING`, `WORKSPACE_TERMINATED`, `WORKSPACE_NON_RECOVERABLE`,
|
|
721
|
+
`WORKSPACE_START_TIMEOUT`, and `WORKSPACE_RESTART_FAILED` codes. General codes are
|
|
454
722
|
`NOT_LOGGED_IN`, `UNAUTHORIZED`, `NOT_FOUND`, `FORBIDDEN`, `BAD_REQUEST`, `CONFLICT`,
|
|
455
|
-
`SERVER_ERROR`, and `
|
|
723
|
+
`SERVER_ERROR`, `NETWORK`, and `ABORTED` (a wait was cancelled through its `signal`).
|
|
456
724
|
|
|
457
725
|
The package ships self-contained declarations from `dist`; TypeScript consumers do not
|
|
458
726
|
need GitTerm's API package or tRPC server types.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentType, AgentRun, AgentRunCreateInput, AgentRunMessage, AuthStatus, CloudProvider, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceListOptions, WorkspaceListResult, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceCatalog, WorkspaceSetupStatus, ModelCredential, ModelProviderInfo } from "./types.js";
|
|
1
|
+
import type { AgentType, AgentRun, AgentRunCreateInput, AgentRunListOptions, AgentRunListResult, AgentRunMessage, AgentRunReply, AuthStatus, CloudProvider, RunRef, RunWaitOptions, WaitOptions, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceListOptions, WorkspaceListResult, WorkspaceRef, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceCatalog, WorkspaceSetupStatus, ModelCredential, ModelProviderInfo } from "./types.js";
|
|
2
2
|
export type GittermClientOptions = {
|
|
3
3
|
serverUrl?: string;
|
|
4
4
|
token?: string;
|
|
@@ -12,33 +12,41 @@ export type GittermClient = {
|
|
|
12
12
|
};
|
|
13
13
|
workspaces: {
|
|
14
14
|
list(input?: WorkspaceListOptions): Promise<WorkspaceListResult>;
|
|
15
|
-
get(
|
|
16
|
-
getRuntimeAccess(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
restart(workspaceId: string): Promise<WorkspaceRestartResult>;
|
|
23
|
-
terminate(workspaceId: string): Promise<WorkspaceTerminateResult>;
|
|
15
|
+
get(workspace: WorkspaceRef): Promise<Workspace>;
|
|
16
|
+
getRuntimeAccess(workspace: WorkspaceRef): Promise<WorkspaceRuntimeAccess>;
|
|
17
|
+
/** Resume a paused workspace if needed and wait until it is running. */
|
|
18
|
+
ensureRunning(workspace: WorkspaceRef, options?: WaitOptions): Promise<WorkspaceEnsureRunningResult>;
|
|
19
|
+
pause(workspace: WorkspaceRef): Promise<WorkspacePauseResult>;
|
|
20
|
+
restart(workspace: WorkspaceRef): Promise<WorkspaceRestartResult>;
|
|
21
|
+
terminate(workspace: WorkspaceRef): Promise<WorkspaceTerminateResult>;
|
|
24
22
|
create(input: WorkspaceCreateInput): Promise<WorkspaceCreateResult>;
|
|
25
|
-
setupStatus(
|
|
26
|
-
waitForSetup(
|
|
27
|
-
timeoutMs?: number;
|
|
28
|
-
pollIntervalMs?: number;
|
|
29
|
-
}): Promise<WorkspaceSetupStatus>;
|
|
23
|
+
setupStatus(workspace: WorkspaceRef): Promise<WorkspaceSetupStatus>;
|
|
24
|
+
waitForSetup(workspace: WorkspaceRef, options?: WaitOptions): Promise<WorkspaceSetupStatus>;
|
|
30
25
|
};
|
|
31
26
|
runs: {
|
|
32
27
|
create(input: AgentRunCreateInput): Promise<AgentRun>;
|
|
28
|
+
list(workspace: WorkspaceRef, options?: AgentRunListOptions): Promise<AgentRunListResult>;
|
|
29
|
+
get(run: RunRef): Promise<AgentRun>;
|
|
33
30
|
get(workspaceId: string, runId: string): Promise<AgentRun>;
|
|
31
|
+
messages(run: RunRef): Promise<AgentRunMessage[]>;
|
|
34
32
|
messages(workspaceId: string, runId: string): Promise<AgentRunMessage[]>;
|
|
33
|
+
cancel(run: RunRef): Promise<{
|
|
34
|
+
cancelled: boolean;
|
|
35
|
+
}>;
|
|
35
36
|
cancel(workspaceId: string, runId: string): Promise<{
|
|
36
37
|
cancelled: boolean;
|
|
37
38
|
}>;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
/** Answer the permission prompt or question a run is `awaiting_input` on. */
|
|
40
|
+
respond(run: RunRef, input: {
|
|
41
|
+
requestId: string;
|
|
42
|
+
reply: AgentRunReply;
|
|
41
43
|
}): Promise<AgentRun>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve once the run needs attention: terminal, or `awaiting_input`
|
|
46
|
+
* unless `until: "terminal"`. Push-based; no polling.
|
|
47
|
+
*/
|
|
48
|
+
wait(run: RunRef, options?: RunWaitOptions): Promise<AgentRun>;
|
|
49
|
+
wait(workspaceId: string, runId: string, options?: RunWaitOptions): Promise<AgentRun>;
|
|
42
50
|
};
|
|
43
51
|
catalog: {
|
|
44
52
|
agentTypes(input?: {
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export type GittermErrorCode = "NOT_LOGGED_IN" | "UNAUTHORIZED" | "NOT_FOUND" | "FORBIDDEN" | "BAD_REQUEST" | "CONFLICT" | "SERVER_ERROR" | "NETWORK" | CredentialErrorCode | WorkspaceLifecycleErrorCode;
|
|
1
|
+
export type GittermErrorCode = "NOT_LOGGED_IN" | "UNAUTHORIZED" | "NOT_FOUND" | "FORBIDDEN" | "BAD_REQUEST" | "CONFLICT" | "SERVER_ERROR" | "NETWORK" | "ABORTED" | CredentialErrorCode | WorkspaceLifecycleErrorCode;
|
|
2
2
|
export type CredentialErrorCode = "MODEL_CREDENTIAL_UNAVAILABLE" | "MODEL_CREDENTIAL_DUPLICATE_PROVIDER" | "MODEL_CREDENTIAL_INVALID" | "MODEL_CREDENTIAL_REQUIRED";
|
|
3
|
-
export type WorkspaceLifecycleErrorCode = "WORKSPACE_TERMINATED" | "WORKSPACE_NON_RECOVERABLE" | "WORKSPACE_START_TIMEOUT" | "WORKSPACE_RESTART_FAILED";
|
|
3
|
+
export type WorkspaceLifecycleErrorCode = "WORKSPACE_NOT_RUNNING" | "WORKSPACE_TERMINATED" | "WORKSPACE_NON_RECOVERABLE" | "WORKSPACE_START_TIMEOUT" | "WORKSPACE_RESTART_FAILED";
|
|
4
|
+
export declare const WORKSPACE_LIFECYCLE_ERROR_CODES: readonly WorkspaceLifecycleErrorCode[];
|
|
4
5
|
export declare class GittermError extends Error {
|
|
5
6
|
readonly code: GittermErrorCode;
|
|
6
7
|
readonly cause?: unknown;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export type { DeviceCodeInfo, LoginWithDeviceCodeOptions } from "./device-login.
|
|
|
6
6
|
export { 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, AgentRunMessage, AgentRunStatus, 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 { 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";
|
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;
|
|
@@ -129,6 +144,37 @@ function normalizeBaseCommit(value) {
|
|
|
129
144
|
return null;
|
|
130
145
|
return /^[0-9a-f]{40}([0-9a-f]{24})?$/i.test(trimmed) ? trimmed.toLowerCase() : trimmed;
|
|
131
146
|
}
|
|
147
|
+
function workspaceIdOf(ref) {
|
|
148
|
+
return typeof ref === "string" ? ref : ref.id;
|
|
149
|
+
}
|
|
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
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function throwIfAborted(signal, what) {
|
|
162
|
+
if (signal?.aborted)
|
|
163
|
+
throw new GittermError("ABORTED", `Aborted while ${what}`);
|
|
164
|
+
}
|
|
165
|
+
function sleep(ms, signal, what) {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
const timer = setTimeout(() => {
|
|
168
|
+
signal?.removeEventListener("abort", onAbort);
|
|
169
|
+
resolve();
|
|
170
|
+
}, ms);
|
|
171
|
+
const onAbort = () => {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
reject(new GittermError("ABORTED", `Aborted while ${what}`));
|
|
174
|
+
};
|
|
175
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
176
|
+
});
|
|
177
|
+
}
|
|
132
178
|
function normalizeWorkspace(workspace) {
|
|
133
179
|
if (!workspace)
|
|
134
180
|
return null;
|
|
@@ -157,6 +203,10 @@ function normalizeWorkspace(workspace) {
|
|
|
157
203
|
name: workspace.image.name,
|
|
158
204
|
imageId: workspace.image.imageId
|
|
159
205
|
} : null,
|
|
206
|
+
metadata: workspace.metadata ?? {},
|
|
207
|
+
opencodeApi: workspace.opencodeApi ?? "v1",
|
|
208
|
+
autoTerminateAt: toIso(workspace.autoTerminateAt),
|
|
209
|
+
customImage: workspace.customImage ?? null,
|
|
160
210
|
startedAt: toIso(workspace.startedAt),
|
|
161
211
|
pausedAt: toIso(workspace.pausedAt),
|
|
162
212
|
terminatedAt: toIso(workspace.terminatedAt),
|
|
@@ -218,37 +268,75 @@ async function runWithServer(serverUrl, operation) {
|
|
|
218
268
|
const credentialCode = credentialErrorCode(error.message);
|
|
219
269
|
if (credentialCode)
|
|
220
270
|
throw new GittermError(credentialCode, error.message, { cause: error });
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (/WORKSPACE_NON_RECOVERABLE/.test(error.message)) {
|
|
225
|
-
throw new WorkspaceLifecycleError("WORKSPACE_NON_RECOVERABLE", error.message, {
|
|
226
|
-
cause: error
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
if (/WORKSPACE_START_TIMEOUT/.test(error.message)) {
|
|
230
|
-
throw new WorkspaceLifecycleError("WORKSPACE_START_TIMEOUT", error.message, {
|
|
231
|
-
cause: error
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
if (/WORKSPACE_RESTART_FAILED/.test(error.message)) {
|
|
235
|
-
throw new WorkspaceLifecycleError("WORKSPACE_RESTART_FAILED", error.message, {
|
|
236
|
-
cause: error
|
|
237
|
-
});
|
|
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 });
|
|
238
274
|
}
|
|
239
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 });
|
|
240
276
|
}
|
|
241
277
|
throw new GittermError("NETWORK", error instanceof Error ? error.message : "Network request failed", { cause: error });
|
|
242
278
|
}
|
|
243
279
|
}
|
|
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
|
+
});
|
|
318
|
+
});
|
|
319
|
+
}
|
|
244
320
|
function createGittermClient(options = {}) {
|
|
245
321
|
const credentials = resolveCredentials(options);
|
|
322
|
+
const fetchImpl = createNoRedirectFetch(options.fetch);
|
|
323
|
+
const authorization = `Bearer ${credentials.token}`;
|
|
246
324
|
const trpc = createTRPCClient({
|
|
247
325
|
links: [
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
326
|
+
splitLink({
|
|
327
|
+
condition: (operation) => operation.type === "subscription",
|
|
328
|
+
true: httpSubscriptionLink({
|
|
329
|
+
url: toTrpcUrl(credentials.serverUrl),
|
|
330
|
+
EventSource,
|
|
331
|
+
eventSourceOptions: {
|
|
332
|
+
fetch: (url, init) => fetchImpl(url, { ...init, headers: { ...init.headers, authorization } })
|
|
333
|
+
}
|
|
334
|
+
}),
|
|
335
|
+
false: httpBatchLink({
|
|
336
|
+
url: toTrpcUrl(credentials.serverUrl),
|
|
337
|
+
fetch: fetchImpl,
|
|
338
|
+
headers: () => ({ authorization })
|
|
339
|
+
})
|
|
252
340
|
})
|
|
253
341
|
]
|
|
254
342
|
});
|
|
@@ -279,9 +367,11 @@ function createGittermClient(options = {}) {
|
|
|
279
367
|
const waitForWorkspaceSetup = async (workspaceId, waitOptions) => {
|
|
280
368
|
const timeoutMs = waitOptions?.timeoutMs ?? 10 * 60000;
|
|
281
369
|
const pollIntervalMs = waitOptions?.pollIntervalMs ?? 2000;
|
|
370
|
+
const signal = waitOptions?.signal;
|
|
282
371
|
const deadline = Date.now() + timeoutMs;
|
|
283
372
|
while (true) {
|
|
284
|
-
|
|
373
|
+
throwIfAborted(signal, "waiting for workspace setup");
|
|
374
|
+
const result = await trpc.workspace.getSetupStatus.query({ workspaceId }, { signal });
|
|
285
375
|
if (result.status === "not_requested" || result.status === "succeeded")
|
|
286
376
|
return result;
|
|
287
377
|
if (result.status === "failed") {
|
|
@@ -294,7 +384,7 @@ ${log}` : ""}`);
|
|
|
294
384
|
throw new GittermError("NETWORK", `Timed out waiting for workspace ${workspaceId} setup (last status: ${result.status})${log ? `
|
|
295
385
|
${log}` : ""}`);
|
|
296
386
|
}
|
|
297
|
-
await
|
|
387
|
+
await sleep(pollIntervalMs, signal, "waiting for workspace setup");
|
|
298
388
|
}
|
|
299
389
|
};
|
|
300
390
|
return {
|
|
@@ -316,27 +406,32 @@ ${log}` : ""}`);
|
|
|
316
406
|
list: (input) => run(async () => {
|
|
317
407
|
const result = await trpc.workspace.listWorkspaces.query(input);
|
|
318
408
|
return {
|
|
319
|
-
workspaces: result.workspaces.map((workspace) =>
|
|
409
|
+
workspaces: result.workspaces.map(normalizeWorkspace).filter((workspace) => workspace !== null),
|
|
320
410
|
pagination: result.pagination
|
|
321
411
|
};
|
|
322
412
|
}),
|
|
323
|
-
get: (
|
|
324
|
-
const result = await trpc.workspace.getWorkspace.query({
|
|
413
|
+
get: (ref) => run(async () => {
|
|
414
|
+
const result = await trpc.workspace.getWorkspace.query({
|
|
415
|
+
workspaceId: workspaceIdOf(ref)
|
|
416
|
+
});
|
|
325
417
|
const workspace = normalizeWorkspace(result.workspace);
|
|
326
418
|
if (!workspace)
|
|
327
419
|
throw new GittermError("NOT_FOUND", "Workspace not found");
|
|
328
420
|
return workspace;
|
|
329
421
|
}),
|
|
330
|
-
getRuntimeAccess: (
|
|
331
|
-
const result = await trpc.workspace.getRuntimeAccess.query({
|
|
422
|
+
getRuntimeAccess: (ref) => run(async () => {
|
|
423
|
+
const result = await trpc.workspace.getRuntimeAccess.query({
|
|
424
|
+
workspaceId: workspaceIdOf(ref)
|
|
425
|
+
});
|
|
332
426
|
return normalizeRuntime(result);
|
|
333
427
|
}),
|
|
334
|
-
ensureRunning: (
|
|
428
|
+
ensureRunning: (ref, waitOptions) => run(async () => {
|
|
429
|
+
throwIfAborted(waitOptions?.signal, "ensuring the workspace is running");
|
|
335
430
|
const result = await trpc.workspace.ensureRunning.mutate({
|
|
336
|
-
workspaceId,
|
|
337
|
-
timeoutMs:
|
|
338
|
-
pollIntervalMs:
|
|
339
|
-
});
|
|
431
|
+
workspaceId: workspaceIdOf(ref),
|
|
432
|
+
timeoutMs: waitOptions?.timeoutMs,
|
|
433
|
+
pollIntervalMs: waitOptions?.pollIntervalMs
|
|
434
|
+
}, { signal: waitOptions?.signal });
|
|
340
435
|
const workspace = normalizeWorkspace(result.workspace);
|
|
341
436
|
if (!workspace)
|
|
342
437
|
throw new GittermError("SERVER_ERROR", "ensureRunning failed");
|
|
@@ -345,43 +440,62 @@ ${log}` : ""}`);
|
|
|
345
440
|
runtime: normalizeRuntime(result.runtime)
|
|
346
441
|
};
|
|
347
442
|
}),
|
|
348
|
-
pause: (
|
|
349
|
-
const result = await trpc.workspace.pauseWorkspace.mutate({
|
|
443
|
+
pause: (ref) => run(async () => {
|
|
444
|
+
const result = await trpc.workspace.pauseWorkspace.mutate({
|
|
445
|
+
workspaceId: workspaceIdOf(ref)
|
|
446
|
+
});
|
|
350
447
|
return { durationMinutes: result.durationMinutes };
|
|
351
448
|
}),
|
|
352
|
-
restart: (
|
|
353
|
-
const result = await trpc.workspace.restartWorkspace.mutate({
|
|
449
|
+
restart: (ref) => run(async () => {
|
|
450
|
+
const result = await trpc.workspace.restartWorkspace.mutate({
|
|
451
|
+
workspaceId: workspaceIdOf(ref)
|
|
452
|
+
});
|
|
354
453
|
return { status: result.status };
|
|
355
454
|
}),
|
|
356
|
-
terminate: (
|
|
357
|
-
const result = await trpc.workspace.deleteWorkspace.mutate({
|
|
455
|
+
terminate: (ref) => run(async () => {
|
|
456
|
+
const result = await trpc.workspace.deleteWorkspace.mutate({
|
|
457
|
+
workspaceId: workspaceIdOf(ref)
|
|
458
|
+
});
|
|
358
459
|
return {
|
|
359
460
|
workspace: normalizeWorkspace(result.workspace),
|
|
360
461
|
cleanupInBackground: result.cleanupInBackground
|
|
361
462
|
};
|
|
362
463
|
}),
|
|
363
464
|
create: createWorkspace,
|
|
364
|
-
setupStatus: (
|
|
365
|
-
waitForSetup: (
|
|
465
|
+
setupStatus: (ref) => run(async () => trpc.workspace.getSetupStatus.query({ workspaceId: workspaceIdOf(ref) })),
|
|
466
|
+
waitForSetup: (ref, waitOptions) => run(() => waitForWorkspaceSetup(workspaceIdOf(ref), waitOptions))
|
|
366
467
|
},
|
|
367
468
|
runs: {
|
|
368
|
-
create: (input) => run(async () =>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const deadline = Date.now() + timeoutMs;
|
|
376
|
-
while (true) {
|
|
377
|
-
const result = await trpc.run.get.query({ workspaceId, runId });
|
|
378
|
-
if (result.status !== "pending" && result.status !== "running" && result.status !== "retrying")
|
|
379
|
-
return result;
|
|
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));
|
|
469
|
+
create: (input) => run(async () => {
|
|
470
|
+
const { signal, ...request } = input;
|
|
471
|
+
if (request.waitForSetup) {
|
|
472
|
+
await waitForWorkspaceSetup(request.workspaceId, {
|
|
473
|
+
timeoutMs: request.setupTimeoutMs,
|
|
474
|
+
signal
|
|
475
|
+
});
|
|
384
476
|
}
|
|
477
|
+
throwIfAborted(signal, "creating the run");
|
|
478
|
+
return trpc.run.create.mutate({ ...request, idempotencyKey: request.idempotencyKey ?? crypto.randomUUID() }, { signal });
|
|
479
|
+
}),
|
|
480
|
+
list: (ref, listOptions) => run(async () => trpc.run.list.query({
|
|
481
|
+
workspaceId: workspaceIdOf(ref),
|
|
482
|
+
...listOptions
|
|
483
|
+
})),
|
|
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);
|
|
491
|
+
return messages.map((message) => ({ ...message, parts: message.parts ?? [] }));
|
|
492
|
+
}),
|
|
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);
|
|
385
499
|
})
|
|
386
500
|
},
|
|
387
501
|
catalog: {
|
|
@@ -420,7 +534,7 @@ ${log}` : ""}`);
|
|
|
420
534
|
};
|
|
421
535
|
}
|
|
422
536
|
// src/device-login.ts
|
|
423
|
-
function
|
|
537
|
+
function sleep2(ms) {
|
|
424
538
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
425
539
|
}
|
|
426
540
|
async function loginWithDeviceCode(serverUrl, options = {}) {
|
|
@@ -455,7 +569,7 @@ async function loginWithDeviceCode(serverUrl, options = {}) {
|
|
|
455
569
|
const errText = await tokenRes.text().catch(() => "");
|
|
456
570
|
throw new Error(`Login failed: ${tokenRes.status} ${errText}`);
|
|
457
571
|
}
|
|
458
|
-
await
|
|
572
|
+
await sleep2(Math.max(1, codeJson.intervalSeconds) * 1000);
|
|
459
573
|
}
|
|
460
574
|
throw new Error("Device code expired; try again.");
|
|
461
575
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export type AuthStatus = {
|
|
|
6
6
|
plan: string;
|
|
7
7
|
authMethod: "session" | "apiToken";
|
|
8
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* `pending` — compute is being provisioned or resumed; `runtime.url` is null.
|
|
11
|
+
* `running` — the provider reports the sandbox/container up. The agent may
|
|
12
|
+
* still be booting; `runs.create()` waits for it to answer.
|
|
13
|
+
* `paused` — stopped but resumable with `workspaces.ensureRunning()`.
|
|
14
|
+
* `terminated` — gone for good.
|
|
15
|
+
*/
|
|
9
16
|
export type WorkspaceStatus = "pending" | "running" | "paused" | "terminated";
|
|
10
17
|
export type WorkspaceHostingType = "cloud" | "local";
|
|
11
18
|
export type Workspace = {
|
|
@@ -33,12 +40,40 @@ export type Workspace = {
|
|
|
33
40
|
name: string;
|
|
34
41
|
imageId: string;
|
|
35
42
|
} | null;
|
|
43
|
+
/** Caller-owned tags supplied at create time. */
|
|
44
|
+
metadata: Record<string, string>;
|
|
45
|
+
/** OpenCode HTTP API generation the workspace's agent server speaks. */
|
|
46
|
+
opencodeApi: OpencodeApi;
|
|
47
|
+
/** When set, the workspace is terminated at this time regardless of activity. */
|
|
48
|
+
autoTerminateAt: string | null;
|
|
49
|
+
/** The caller-supplied image or E2B template this workspace runs, if any. */
|
|
50
|
+
customImage: string | null;
|
|
36
51
|
startedAt: string | null;
|
|
37
52
|
pausedAt: string | null;
|
|
38
53
|
terminatedAt: string | null;
|
|
39
54
|
lastActiveAt: string | null;
|
|
40
55
|
updatedAt: string | null;
|
|
41
56
|
};
|
|
57
|
+
/** A workspace id, or any object carrying one (e.g. a `Workspace`). */
|
|
58
|
+
export type WorkspaceRef = string | {
|
|
59
|
+
id: string;
|
|
60
|
+
};
|
|
61
|
+
/** A run id pair, or any object carrying one (e.g. an `AgentRun`). */
|
|
62
|
+
export type RunRef = {
|
|
63
|
+
workspaceId: string;
|
|
64
|
+
id: string;
|
|
65
|
+
} | {
|
|
66
|
+
workspaceId: string;
|
|
67
|
+
runId: string;
|
|
68
|
+
};
|
|
69
|
+
/** `v1` = OpenCode 1.x (`/event`, `/session/*`); `v2` = OpenCode 2 (`/api/*`), experimental until 2.0 ships. */
|
|
70
|
+
export type OpencodeApi = "v1" | "v2";
|
|
71
|
+
export type WaitOptions = {
|
|
72
|
+
timeoutMs?: number;
|
|
73
|
+
pollIntervalMs?: number;
|
|
74
|
+
/** Abort the wait early; the promise rejects with code `ABORTED`. */
|
|
75
|
+
signal?: AbortSignal;
|
|
76
|
+
};
|
|
42
77
|
export type WorkspaceRuntimeAccess = {
|
|
43
78
|
workspaceId: string;
|
|
44
79
|
status: WorkspaceStatus;
|
|
@@ -62,6 +97,8 @@ export type WorkspaceListOptions = {
|
|
|
62
97
|
limit?: number;
|
|
63
98
|
offset?: number;
|
|
64
99
|
status?: "all" | "active" | "terminated";
|
|
100
|
+
/** Only workspaces whose metadata contains every given key/value. */
|
|
101
|
+
metadata?: Record<string, string>;
|
|
65
102
|
};
|
|
66
103
|
export type WorkspaceListResult = {
|
|
67
104
|
workspaces: Workspace[];
|
|
@@ -156,6 +193,27 @@ export type AgentKey = BuiltInAgentKey | (string & {});
|
|
|
156
193
|
export type WorkspaceCreateInput = {
|
|
157
194
|
idempotencyKey?: string;
|
|
158
195
|
name?: string;
|
|
196
|
+
/**
|
|
197
|
+
* Caller-owned tags (e.g. tenant or channel ids). Up to 20 keys of letters,
|
|
198
|
+
* digits, `_ . : -`; values up to 500 chars. Returned on the workspace and
|
|
199
|
+
* filterable with `workspaces.list({ metadata })`.
|
|
200
|
+
*/
|
|
201
|
+
metadata?: Record<string, string>;
|
|
202
|
+
/**
|
|
203
|
+
* Terminate automatically this long after creation, whatever its activity.
|
|
204
|
+
* Between 1 minute and 30 days. Use it as a cost guardrail for ephemeral
|
|
205
|
+
* workspaces in case the caller crashes before `terminate()`.
|
|
206
|
+
*/
|
|
207
|
+
autoTerminateAfterMs?: number;
|
|
208
|
+
/**
|
|
209
|
+
* Run your own image instead of the catalog image. Registry-backed providers
|
|
210
|
+
* (railway, aws, daytona, exedev) take an OCI reference such as
|
|
211
|
+
* `ghcr.io/acme/agent-runner:1.4.0`; E2B takes a public template id or
|
|
212
|
+
* alias. The managed service only runs images that can be pulled without
|
|
213
|
+
* credentials, and pins floating tags to a digest at create time. Build on
|
|
214
|
+
* `opeoginni/gitterm-opencode-server` and keep its entrypoint.
|
|
215
|
+
*/
|
|
216
|
+
image?: string;
|
|
159
217
|
repo: string;
|
|
160
218
|
branch?: string;
|
|
161
219
|
/** Commit SHA to pin the checkout to after cloning `branch`/`checkoutRef`. */
|
|
@@ -167,17 +225,19 @@ export type WorkspaceCreateInput = {
|
|
|
167
225
|
agent?: AgentKey;
|
|
168
226
|
/** Provider intent. Defaults to the user's or deployment's preferred provider. */
|
|
169
227
|
provider?: WorkspaceProviderSelection;
|
|
228
|
+
/** Inline Git credentials for repository validation, cloning, and runtime pull/push. */
|
|
229
|
+
repositoryCredentials?: {
|
|
230
|
+
username?: string;
|
|
231
|
+
token: string;
|
|
232
|
+
};
|
|
170
233
|
gitIntegrationId?: string;
|
|
171
234
|
/** Defaults from the selected provider. */
|
|
172
235
|
persistent?: boolean;
|
|
173
236
|
workspaceProfile?: "standard" | "ssh-enabled";
|
|
174
|
-
/** Credential IDs from client.credentials.list(). Omit to inject dashboard defaults. */
|
|
175
|
-
modelCredentialIds?: string[];
|
|
176
237
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* inline; connect those in the dashboard.
|
|
238
|
+
* One entry per provider. Omit entirely to inject the dashboard defaults.
|
|
239
|
+
* Naming any dashboard credential (an entry without `apiKey`) turns off the
|
|
240
|
+
* implicit defaults, so list every provider the workspace needs.
|
|
181
241
|
*/
|
|
182
242
|
modelCredentials?: WorkspaceModelCredentialInput[];
|
|
183
243
|
/** Ephemeral environment variables injected into this workspace only. */
|
|
@@ -213,6 +273,11 @@ export type WorkspaceCreateInput = {
|
|
|
213
273
|
* webfetch: "allow" } } disables tool approval prompts in headless runs.
|
|
214
274
|
*/
|
|
215
275
|
config?: Record<string, unknown>;
|
|
276
|
+
/**
|
|
277
|
+
* OpenCode API generation served by the image. Defaults to `v1`. Use `v2`
|
|
278
|
+
* with an image built on OpenCode 2 (`@opencode-ai/cli`); experimental.
|
|
279
|
+
*/
|
|
280
|
+
api?: OpencodeApi;
|
|
216
281
|
};
|
|
217
282
|
};
|
|
218
283
|
export type WorkspaceRestartResult = {
|
|
@@ -229,7 +294,54 @@ export type WorkspaceEnsureRunningResult = {
|
|
|
229
294
|
workspace: Workspace;
|
|
230
295
|
runtime: WorkspaceRuntimeAccess;
|
|
231
296
|
};
|
|
232
|
-
export type AgentRunStatus = "pending" | "running" | "retrying"
|
|
297
|
+
export type AgentRunStatus = "pending" | "running" | "retrying"
|
|
298
|
+
/** Blocked on a permission prompt or agent question; answer with `runs.respond()`. */
|
|
299
|
+
| "awaiting_input" | "completed" | "failed" | "cancelled";
|
|
300
|
+
/** A prompt the agent is blocked on. `permission` = OpenCode tool approval; `question` = the agent's `question` tool. */
|
|
301
|
+
export type AgentRunInputRequest = {
|
|
302
|
+
id: string;
|
|
303
|
+
kind: "permission";
|
|
304
|
+
createdAt: string;
|
|
305
|
+
toolCallId: string | null;
|
|
306
|
+
/** Permission action, e.g. "bash" or "edit". */
|
|
307
|
+
permission: string;
|
|
308
|
+
/** What the action applies to, e.g. the shell command or file paths. */
|
|
309
|
+
patterns: string[];
|
|
310
|
+
/** Patterns OpenCode remembers when answered with "always". */
|
|
311
|
+
always: string[];
|
|
312
|
+
title: string;
|
|
313
|
+
} | {
|
|
314
|
+
id: string;
|
|
315
|
+
kind: "question";
|
|
316
|
+
createdAt: string;
|
|
317
|
+
toolCallId: string | null;
|
|
318
|
+
questions: Array<{
|
|
319
|
+
key: string;
|
|
320
|
+
header: string;
|
|
321
|
+
question: string;
|
|
322
|
+
options: Array<{
|
|
323
|
+
label: string;
|
|
324
|
+
description: string;
|
|
325
|
+
value?: string;
|
|
326
|
+
}>;
|
|
327
|
+
/** More than one option may be selected. */
|
|
328
|
+
multiple: boolean;
|
|
329
|
+
/** A free-text answer outside `options` is accepted. */
|
|
330
|
+
custom: boolean;
|
|
331
|
+
}>;
|
|
332
|
+
};
|
|
333
|
+
export type AgentRunReply = {
|
|
334
|
+
type: "permission";
|
|
335
|
+
response: "once" | "always" | "reject";
|
|
336
|
+
}
|
|
337
|
+
/** One entry per question, each the selected option labels (or a custom answer when allowed). */
|
|
338
|
+
| {
|
|
339
|
+
type: "question";
|
|
340
|
+
answers: string[][];
|
|
341
|
+
} | {
|
|
342
|
+
type: "question";
|
|
343
|
+
reject: true;
|
|
344
|
+
};
|
|
233
345
|
export type AgentRun = {
|
|
234
346
|
id: string;
|
|
235
347
|
workspaceId: string;
|
|
@@ -237,17 +349,43 @@ export type AgentRun = {
|
|
|
237
349
|
status: AgentRunStatus;
|
|
238
350
|
error: string | null;
|
|
239
351
|
finalText: string | null;
|
|
352
|
+
/** Non-empty exactly while `status` is `awaiting_input`. */
|
|
353
|
+
pendingInputs: AgentRunInputRequest[];
|
|
240
354
|
context: {
|
|
241
355
|
type: "isolated";
|
|
242
356
|
} | {
|
|
243
357
|
type: "continued";
|
|
244
358
|
runId: string;
|
|
245
359
|
};
|
|
360
|
+
createdAt: string;
|
|
361
|
+
/** When the prompt reached the agent; null while `pending`. */
|
|
362
|
+
submittedAt: string | null;
|
|
363
|
+
completedAt: string | null;
|
|
364
|
+
};
|
|
365
|
+
export type AgentRunListOptions = {
|
|
366
|
+
/** `active` = pending/running/retrying/awaiting_input; `terminal` = completed/failed/cancelled. */
|
|
367
|
+
status?: "all" | "active" | "terminal";
|
|
368
|
+
/** 1–50, default 20. */
|
|
369
|
+
limit?: number;
|
|
370
|
+
offset?: number;
|
|
371
|
+
};
|
|
372
|
+
export type AgentRunListResult = {
|
|
373
|
+
runs: AgentRun[];
|
|
374
|
+
pagination: {
|
|
375
|
+
total: number;
|
|
376
|
+
limit: number;
|
|
377
|
+
offset: number;
|
|
378
|
+
hasMore: boolean;
|
|
379
|
+
};
|
|
246
380
|
};
|
|
247
381
|
export type AgentRunCreateInput = {
|
|
248
382
|
workspaceId: string;
|
|
249
|
-
/**
|
|
250
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Stable key used to return the same run when a request is retried.
|
|
385
|
+
* Defaults to a random UUID, which gives no retry protection; pass your own
|
|
386
|
+
* for anything a retry could duplicate.
|
|
387
|
+
*/
|
|
388
|
+
idempotencyKey?: string;
|
|
251
389
|
prompt: string;
|
|
252
390
|
title?: string;
|
|
253
391
|
agent?: string;
|
|
@@ -260,20 +398,67 @@ export type AgentRunCreateInput = {
|
|
|
260
398
|
type: "continue";
|
|
261
399
|
runId: string;
|
|
262
400
|
};
|
|
263
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* Wait for the `afterAgent` setup phase to succeed before submitting the
|
|
403
|
+
* prompt. Fails the call with the setup log if that phase failed.
|
|
404
|
+
*/
|
|
264
405
|
waitForSetup?: boolean;
|
|
265
406
|
/** How long to wait for setup, in ms. Server maximum is 600000 (10 minutes). */
|
|
266
407
|
setupTimeoutMs?: number;
|
|
408
|
+
/**
|
|
409
|
+
* How long to wait for a `pending` workspace to become `running`, in ms.
|
|
410
|
+
* Defaults to 120000; server maximum is 240000. Paused workspaces are not
|
|
411
|
+
* resumed — call `workspaces.ensureRunning()` first.
|
|
412
|
+
*/
|
|
413
|
+
startTimeoutMs?: number;
|
|
414
|
+
/** Abort while waiting for setup or start; rejects with code `ABORTED`. */
|
|
415
|
+
signal?: AbortSignal;
|
|
416
|
+
};
|
|
417
|
+
export type AgentRunMessagePart = {
|
|
418
|
+
type: "text";
|
|
419
|
+
text: string;
|
|
420
|
+
} | {
|
|
421
|
+
type: "tool";
|
|
422
|
+
callId: string;
|
|
423
|
+
tool: string;
|
|
424
|
+
status: "pending" | "running" | "completed" | "error";
|
|
425
|
+
title: string | null;
|
|
426
|
+
input: Record<string, unknown>;
|
|
427
|
+
/** Tool output when completed, truncated to 4000 characters. */
|
|
428
|
+
output: string | null;
|
|
429
|
+
error: string | null;
|
|
430
|
+
startedAt: string | null;
|
|
431
|
+
completedAt: string | null;
|
|
432
|
+
};
|
|
433
|
+
export type RunWaitOptions = {
|
|
434
|
+
/** Default 30 minutes. */
|
|
435
|
+
timeoutMs?: number;
|
|
436
|
+
/** Abort the wait early; the promise rejects with code `ABORTED`. */
|
|
437
|
+
signal?: AbortSignal;
|
|
438
|
+
/**
|
|
439
|
+
* `input-or-terminal` (default) also returns when the run is `awaiting_input`
|
|
440
|
+
* so the caller can answer with `runs.respond()`; `terminal` keeps waiting.
|
|
441
|
+
*/
|
|
442
|
+
until?: "input-or-terminal" | "terminal";
|
|
267
443
|
};
|
|
268
444
|
export type AgentRunMessage = {
|
|
269
445
|
id: string;
|
|
270
446
|
role: "user" | "assistant";
|
|
271
447
|
createdAt: string;
|
|
272
448
|
completedAt: string | null;
|
|
449
|
+
/** Concatenated text parts. */
|
|
273
450
|
text: string;
|
|
274
451
|
error: string | null;
|
|
452
|
+
/** Ordered text and tool-call parts, for seeing what the agent actually did. */
|
|
453
|
+
parts: AgentRunMessagePart[];
|
|
275
454
|
};
|
|
276
455
|
export type WorkspaceSetupStatus = {
|
|
456
|
+
/**
|
|
457
|
+
* `not_requested` — no `afterAgent` commands. `waiting` — the agent isn't
|
|
458
|
+
* reachable yet, so setup hasn't started. `running`, `succeeded`, `failed`
|
|
459
|
+
* describe the `afterAgent` phase itself. A setup stuck in `waiting` for
|
|
460
|
+
* 15 minutes is marked `failed` with the reason in `log`.
|
|
461
|
+
*/
|
|
277
462
|
status: "not_requested" | "waiting" | "running" | "succeeded" | "failed";
|
|
278
463
|
exitCode: number | null;
|
|
279
464
|
startedAt: string | null;
|
|
@@ -293,13 +478,26 @@ export type ModelProviderInfo = {
|
|
|
293
478
|
isRecommended: boolean;
|
|
294
479
|
};
|
|
295
480
|
/**
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
* "openai"; unknown
|
|
481
|
+
* Selects the credential for one provider on workspaces.create().
|
|
482
|
+
* `providerName` comes from credentials.listProviders(), e.g. "anthropic" or
|
|
483
|
+
* "openai"; unknown providers throw MODEL_CREDENTIAL_INVALID.
|
|
484
|
+
*
|
|
485
|
+
* - `{ providerName, apiKey }` — inline key for this workspace only, never
|
|
486
|
+
* stored. Overrides any dashboard credential for the same provider.
|
|
487
|
+
* OAuth-only providers throw MODEL_CREDENTIAL_INVALID.
|
|
488
|
+
* - `{ providerName, label }` — the dashboard credential with that label.
|
|
489
|
+
* Unknown labels throw MODEL_CREDENTIAL_UNAVAILABLE listing the labels
|
|
490
|
+
* that exist.
|
|
491
|
+
* - `{ providerName }` — that provider's dashboard default credential.
|
|
299
492
|
*/
|
|
300
493
|
export type WorkspaceModelCredentialInput = {
|
|
301
494
|
providerName: string;
|
|
302
495
|
apiKey: string;
|
|
496
|
+
label?: never;
|
|
497
|
+
} | {
|
|
498
|
+
providerName: string;
|
|
499
|
+
label?: string;
|
|
500
|
+
apiKey?: never;
|
|
303
501
|
};
|
|
304
502
|
/** Safe dashboard credential metadata. Secret material is never returned by the SDK. */
|
|
305
503
|
export type ModelCredential = {
|
|
@@ -309,7 +507,8 @@ export type ModelCredential = {
|
|
|
309
507
|
providerDisplayName: string;
|
|
310
508
|
logicalProviderKey: string;
|
|
311
509
|
authType: string;
|
|
312
|
-
|
|
510
|
+
/** Unique per provider; use it in `modelCredentials: [{ providerName, label }]`. */
|
|
511
|
+
label: string;
|
|
313
512
|
keyHash: string;
|
|
314
513
|
isActive: boolean;
|
|
315
514
|
isDefault: boolean;
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitterm/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
7
7
|
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
8
10
|
"exports": {
|
|
9
11
|
".": {
|
|
10
12
|
"types": "./dist/index.d.ts",
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
"access": "public"
|
|
24
26
|
},
|
|
25
27
|
"scripts": {
|
|
26
|
-
"build": "rm -rf dist && bun build src/index.ts src/direct/index.ts --outdir dist --target node --format esm --external @trpc/client --external @opencode-ai/sdk --external @opencode-ai/sdk/v2 --external e2b --external @asciidev/box-sdk --external @daytonaio/sdk --external @vercel/sandbox && tsc -p tsconfig.build.json",
|
|
28
|
+
"build": "rm -rf dist && bun build src/index.ts src/direct/index.ts --outdir dist --target node --format esm --external @trpc/client --external eventsource --external @opencode-ai/sdk --external @opencode-ai/sdk/v2 --external e2b --external @asciidev/box-sdk --external @daytonaio/sdk --external @vercel/sandbox && tsc -p tsconfig.build.json",
|
|
27
29
|
"prepublishOnly": "bun run check-types && bun run build",
|
|
28
30
|
"check-types": "tsc --noEmit"
|
|
29
31
|
},
|
|
@@ -33,7 +35,8 @@
|
|
|
33
35
|
"@opencode-ai/sdk": "^1.18.25",
|
|
34
36
|
"@trpc/client": "^11.8.1",
|
|
35
37
|
"@vercel/sandbox": "^3.2.1",
|
|
36
|
-
"e2b": "^2.46.1"
|
|
38
|
+
"e2b": "^2.46.1",
|
|
39
|
+
"eventsource": "^5.1.1"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@gitterm/api": "workspace:*",
|
|
@@ -42,6 +45,6 @@
|
|
|
42
45
|
"typescript": "^5.8.2"
|
|
43
46
|
},
|
|
44
47
|
"engines": {
|
|
45
|
-
"node": ">=
|
|
48
|
+
"node": ">=22.12.0"
|
|
46
49
|
}
|
|
47
50
|
}
|