@gitterm/sdk 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -6
- package/dist/client.d.ts +9 -1
- package/dist/direct/index.js +295 -9
- package/dist/direct/provisioning.d.ts +1 -1
- package/dist/direct/types.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +28 -0
- package/dist/types.d.ts +36 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,10 +23,12 @@ Create a workspace, run a prompt, and get the final result:
|
|
|
23
23
|
import { createGittermClient } from "@gitterm/sdk";
|
|
24
24
|
|
|
25
25
|
const client = createGittermClient({ token: process.env.GITTERM_API_TOKEN });
|
|
26
|
+
const [github] = await client.integrations.github.list();
|
|
27
|
+
if (!github) throw new Error("Connect GitHub in the Gitterm dashboard first");
|
|
26
28
|
|
|
27
29
|
const { workspace } = await client.workspaces.create({
|
|
28
30
|
repo: "https://github.com/acme/product",
|
|
29
|
-
|
|
31
|
+
gitIntegrationId: github.id,
|
|
30
32
|
autoTerminateAfterMs: 2 * 60 * 60 * 1000,
|
|
31
33
|
});
|
|
32
34
|
|
|
@@ -43,6 +45,22 @@ try {
|
|
|
43
45
|
}
|
|
44
46
|
```
|
|
45
47
|
|
|
48
|
+
Connected cloud identities are discoverable without returning credentials:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const [github] = await client.integrations.github.list();
|
|
52
|
+
const [googleCloud] = await client.integrations.googleCloud.list();
|
|
53
|
+
|
|
54
|
+
await client.workspaces.create({
|
|
55
|
+
repo: "https://github.com/acme/product",
|
|
56
|
+
gitIntegrationId: github.id,
|
|
57
|
+
googleCloudIntegrationId: googleCloud.id,
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
GitHub tokens and Google workload credentials are issued inside the workspace; the SDK caller does
|
|
62
|
+
not receive or forward them.
|
|
63
|
+
|
|
46
64
|
`result()` returns only on successful completion. If the agent asks something and no handler is
|
|
47
65
|
registered, it throws `AgentRunError` with code `INPUT_REQUIRED` and the current `error.run`.
|
|
48
66
|
Register `onPermission` / `onQuestion` handlers for interactive runs, or use the event relay below.
|
|
@@ -194,6 +212,44 @@ as pull and push. Without inline credentials, `gitIntegrationId` continues to us
|
|
|
194
212
|
dashboard integration. Omitting `models` likewise continues to
|
|
195
213
|
use dashboard-managed model credentials.
|
|
196
214
|
|
|
215
|
+
#### GitHub CLI authentication
|
|
216
|
+
|
|
217
|
+
For GitHub repositories, GitTerm configures both Git and `gh` from the same workspace
|
|
218
|
+
credentials. Agents can run commands such as `gh pr list` or `gh pr create` without
|
|
219
|
+
running `gh auth login`:
|
|
220
|
+
|
|
221
|
+
- `repositoryCredentials.token` takes precedence over `gitIntegrationId`. The supplied
|
|
222
|
+
token is retained in a permission-restricted file on the workspace machine and is
|
|
223
|
+
not automatically renewed. This also works with the standalone SDK's direct providers.
|
|
224
|
+
- `gitIntegrationId` uses the GitHub App installation token. Git and `gh` share a
|
|
225
|
+
cached token and refresh it through GitTerm before expiry. Concurrent commands share
|
|
226
|
+
the refresh, and a failed refresh stops the command rather than using an expired token.
|
|
227
|
+
- Without repository credentials or an integration, GitTerm leaves CLI authentication
|
|
228
|
+
to the environment or the CLI's existing configuration.
|
|
229
|
+
|
|
230
|
+
GitTerm supplies `GH_TOKEN` only to the invoked CLI process, so there is no stale
|
|
231
|
+
installation token exported into the agent's long-running environment. Explicit
|
|
232
|
+
`GH_TOKEN` or `GITHUB_TOKEN` environment variables override this CLI authentication;
|
|
233
|
+
they do not change the credentials used by Git. Managed credentials are for `github.com`.
|
|
234
|
+
|
|
235
|
+
The generated `AGENTS.md` tells agents that `$HOME/.gitterm/bin/gh` indicates GitTerm
|
|
236
|
+
authentication was provisioned. Agents install the real CLI separately when needed, keep
|
|
237
|
+
the GitTerm launcher first on `PATH`, and use the normal `gh` command. `$GH_TOKEN` is
|
|
238
|
+
intentionally not exported globally, and agents are instructed not to pipe it into
|
|
239
|
+
`gh auth login`.
|
|
240
|
+
CLI installation is left to the image or the agent as needed for the task; there is no SDK
|
|
241
|
+
installation option. The guidance explains checking `gh --version` and installing the CLI
|
|
242
|
+
if needed. The launcher discovers the
|
|
243
|
+
actual binary on each invocation, including installations made after startup.
|
|
244
|
+
It lives in `~/.gitterm/bin`, which GitTerm adds to the agent's PATH and standard shell
|
|
245
|
+
profiles. Install the actual CLI elsewhere and keep `~/.gitterm/bin` first on PATH so it
|
|
246
|
+
does not overwrite or bypass the launcher. Node.js and Git are required by the
|
|
247
|
+
authentication runtime.
|
|
248
|
+
|
|
249
|
+
Available commands depend on the token's repository access and permissions. Installation
|
|
250
|
+
tokens act as the GitHub App bot; user-scoped commands may require user authentication.
|
|
251
|
+
Updating an integration's token does not grant permissions absent from its installation.
|
|
252
|
+
|
|
197
253
|
GitTerm does not save inline PATs in its application database. Inline PATs must be delivered to the
|
|
198
254
|
selected compute provider and retained on the workspace machine for runtime Git operations, so
|
|
199
255
|
provider infrastructure and processes running in that workspace may be able to access them. Prefer
|
|
@@ -520,6 +576,10 @@ remain. The exported types are `AgentPermissionRequest`, `AgentQuestionRequest`,
|
|
|
520
576
|
`custom` defaults to true, matching OpenCode; false only when the agent sets it.
|
|
521
577
|
Questions with `options: []` accept free-text answers when `custom` is true.
|
|
522
578
|
|
|
579
|
+
Question options are normalised at the runtime boundary: labels and descriptions are trimmed,
|
|
580
|
+
OpenCode's copied "Type your own answer" entry is removed and enables `custom: true`, and
|
|
581
|
+
duplicate and blank labels are dropped. Hosted users need the API redeployed for this to take effect.
|
|
582
|
+
|
|
523
583
|
The quick start above shows a complete relay. Rejecting a permission or dismissing a question
|
|
524
584
|
ends the turn: OpenCode records a failed tool call and the run finishes. A run left
|
|
525
585
|
`awaiting_input` does not keep its workspace awake; if nobody answers before the workspace's
|
|
@@ -622,11 +682,10 @@ would otherwise change them for other runs on the same runtime.
|
|
|
622
682
|
|
|
623
683
|
## OpenCode API versions
|
|
624
684
|
|
|
625
|
-
Managed workspaces
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
workspace. `v2` is experimental until OpenCode 2 ships.
|
|
685
|
+
Managed workspaces run OpenCode V2 from `@opencode/cli@2` and use `opencodeApi: "v2"`. V2 stores
|
|
686
|
+
credentials in SQLite; GitTerm writes a short-lived V1-shaped `auth.json` seed before first startup,
|
|
687
|
+
triggers V2's supported credential import, then deletes the seed. Runs, questions, permissions, and
|
|
688
|
+
events use GitTerm's V2 runtime adapter.
|
|
630
689
|
|
|
631
690
|
## Errors
|
|
632
691
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentType, AgentRun, AgentRunCreateInput, AgentRunListOptions, AgentRunListResult, AgentRunMessage, AgentRunReply, AuthStatus, CloudProvider, RunRef, RunWaitOptions, RunWatchOptions, WaitOptions, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceListOptions, WorkspaceListResult, WorkspaceRef, 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, RunWatchOptions, WaitOptions, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceListOptions, WorkspaceListResult, WorkspaceRef, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceCatalog, WorkspaceSetupStatus, ModelCredential, ModelProviderInfo, GitHubIntegration, GoogleCloudIntegration } from "./types.js";
|
|
2
2
|
import type { AgentRunEvent, AgentRunResult, RunResultOptions, ModelInfo, WorkspaceModelAccess } from "./types.js";
|
|
3
3
|
export type GittermClientOptions = {
|
|
4
4
|
serverUrl?: string;
|
|
@@ -69,6 +69,14 @@ export type GittermClient = {
|
|
|
69
69
|
list(): Promise<ModelCredential[]>;
|
|
70
70
|
listProviders(): Promise<ModelProviderInfo[]>;
|
|
71
71
|
};
|
|
72
|
+
integrations: {
|
|
73
|
+
github: {
|
|
74
|
+
list(): Promise<GitHubIntegration[]>;
|
|
75
|
+
};
|
|
76
|
+
googleCloud: {
|
|
77
|
+
list(): Promise<GoogleCloudIntegration[]>;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
72
80
|
models: {
|
|
73
81
|
list(options?: {
|
|
74
82
|
provider?: string;
|
package/dist/direct/index.js
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { createOpencodeClient as createOpencodeClient2 } from "@opencode-ai/sdk";
|
|
4
4
|
|
|
5
|
+
// ../agent-runtime/src/normalize.ts
|
|
6
|
+
var OPENCODE_CUSTOM_OPTION_LABEL = /^type your own answer\W*$/i;
|
|
7
|
+
function normalizeQuestion(question) {
|
|
8
|
+
const options = [];
|
|
9
|
+
const labels = new Set;
|
|
10
|
+
let changed = false;
|
|
11
|
+
let custom = question.custom;
|
|
12
|
+
for (const option of question.options) {
|
|
13
|
+
const label = option.label.trim();
|
|
14
|
+
const description = option.description.trim();
|
|
15
|
+
if (OPENCODE_CUSTOM_OPTION_LABEL.test(label)) {
|
|
16
|
+
custom = true;
|
|
17
|
+
changed = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (!label || labels.has(label)) {
|
|
21
|
+
changed = true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
labels.add(label);
|
|
25
|
+
if (label !== option.label || description !== option.description) {
|
|
26
|
+
changed = true;
|
|
27
|
+
options.push({ ...option, label, description });
|
|
28
|
+
} else {
|
|
29
|
+
options.push(option);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return changed ? { ...question, options, custom } : question;
|
|
33
|
+
}
|
|
34
|
+
|
|
5
35
|
// ../agent-runtime/src/opencode.ts
|
|
6
36
|
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
7
37
|
|
|
@@ -426,7 +456,7 @@ function questionRequest(raw) {
|
|
|
426
456
|
kind: "question",
|
|
427
457
|
createdAt: null,
|
|
428
458
|
toolCallId: asString(tool.callID),
|
|
429
|
-
questions: questions.map((question, index) => ({
|
|
459
|
+
questions: questions.map((question, index) => normalizeQuestion({
|
|
430
460
|
key: `q${index}`,
|
|
431
461
|
header: asString(question.header) ?? `Question ${index + 1}`,
|
|
432
462
|
question: asString(question.question) ?? "",
|
|
@@ -714,7 +744,7 @@ function formRequest(raw) {
|
|
|
714
744
|
toolCallId: asString(tool.id) ?? asString(tool.callID),
|
|
715
745
|
questions: fields.map((field, index) => {
|
|
716
746
|
const key = asString(field.key) ?? `q${index}`;
|
|
717
|
-
return {
|
|
747
|
+
return normalizeQuestion({
|
|
718
748
|
key,
|
|
719
749
|
header: asString(field.title) ?? key,
|
|
720
750
|
question: asString(field.description) ?? asString(field.title) ?? key,
|
|
@@ -729,7 +759,7 @@ function formRequest(raw) {
|
|
|
729
759
|
}),
|
|
730
760
|
multiple: field.type === "multiselect",
|
|
731
761
|
custom: field.custom !== false
|
|
732
|
-
};
|
|
762
|
+
});
|
|
733
763
|
})
|
|
734
764
|
};
|
|
735
765
|
}
|
|
@@ -793,6 +823,12 @@ function parseV2Signal(raw) {
|
|
|
793
823
|
return null;
|
|
794
824
|
}
|
|
795
825
|
}
|
|
826
|
+
// ../agent-runtime/src/opencode-credentials.ts
|
|
827
|
+
var OPENCODE_V2_IMPORT_CREDENTIALS = String.raw`AUTH_FILE="\${OPENCODE_DATA_DIR:-\${XDG_DATA_HOME:-$HOME/.local/share}/opencode}/auth.json"
|
|
828
|
+
if [ -f "$AUTH_FILE" ]; then
|
|
829
|
+
opencode auth list >/dev/null
|
|
830
|
+
rm -f "$AUTH_FILE"
|
|
831
|
+
fi`;
|
|
796
832
|
// ../agent-runtime/src/derive.ts
|
|
797
833
|
var MISSING_ASSISTANT_GRACE_MS = 1e4;
|
|
798
834
|
function deriveRunState(snapshot, context) {
|
|
@@ -1243,6 +1279,243 @@ import {
|
|
|
1243
1279
|
// src/direct/ascii.ts
|
|
1244
1280
|
import { BoxApi, Configuration, waitUntilReady } from "@asciidev/box-sdk";
|
|
1245
1281
|
|
|
1282
|
+
// ../agent-runtime/src/github-auth.ts
|
|
1283
|
+
var GITHUB_AUTH_PATH = 'export PATH="$HOME/.gitterm/bin:$PATH"';
|
|
1284
|
+
var GITHUB_CLI_INSTRUCTIONS = `GitHub CLI authentication:
|
|
1285
|
+
- The user may have supplied repository credentials or selected a GitHub integration for this workspace. GitTerm authentication is configured only when $HOME/.gitterm/bin/gh exists. Check for that executable, but never read or print files under $HOME/.gitterm/github.
|
|
1286
|
+
- The image may not include the real GitHub CLI. Run gh --version. If the GitTerm launcher says the CLI is missing, install the official gh binary using the image's package manager or in another directory.
|
|
1287
|
+
- Keep $HOME/.gitterm/bin first on PATH after installation. For a downloaded CLI, use: export PATH="$HOME/.gitterm/bin:/path/to/installed/gh/bin:$PATH". Never prepend the real CLI ahead of $HOME/.gitterm/bin and never overwrite $HOME/.gitterm/bin/gh.
|
|
1288
|
+
- With the launcher first, use the normal gh command (for example, gh pr create). The launcher supplies the user-provided token or a fresh integration token only to that operation.
|
|
1289
|
+
- Never run gh auth login, pipe $GH_TOKEN, copy tokens, or use gh auth status for this workspace authentication. $GH_TOKEN is intentionally not exported to the agent shell.
|
|
1290
|
+
- If $HOME/.gitterm/bin/gh is absent, tell the user that this workspace has no GitTerm-provided GitHub credentials. If an authenticated operation is denied, report the denied operation and ask for repository access or the required GitHub App/token permission.`;
|
|
1291
|
+
function githubAuthCommand(command) {
|
|
1292
|
+
return `env PATH="$HOME/.gitterm/bin:$PATH" sh -c '${command.replaceAll("'", `'"'"'`)}'`;
|
|
1293
|
+
}
|
|
1294
|
+
function hasGithubAuth(repo) {
|
|
1295
|
+
return Boolean(repo?.authToken && /^(https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)/i.test(repo.url));
|
|
1296
|
+
}
|
|
1297
|
+
function githubAuthProvisioning(repo) {
|
|
1298
|
+
if (!hasGithubAuth(repo)) {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const config = {
|
|
1302
|
+
username: repo.authUsername ?? "x-access-token",
|
|
1303
|
+
renewable: !repo.inlineAuth,
|
|
1304
|
+
expiresAt: repo.authExpiresAt
|
|
1305
|
+
};
|
|
1306
|
+
return {
|
|
1307
|
+
files: [
|
|
1308
|
+
{
|
|
1309
|
+
path: "~/.gitterm/github/runtime.cjs",
|
|
1310
|
+
contentBase64: Buffer.from(GITHUB_AUTH_RUNTIME).toString("base64"),
|
|
1311
|
+
mode: 384
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
path: "~/.gitterm/github/config.json",
|
|
1315
|
+
contentBase64: Buffer.from(JSON.stringify(config)).toString("base64"),
|
|
1316
|
+
mode: 384
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
path: "/run/gitterm/github/credential.json",
|
|
1320
|
+
contentBase64: Buffer.from(JSON.stringify({
|
|
1321
|
+
token: repo.authToken,
|
|
1322
|
+
username: repo.authUsername ?? "x-access-token",
|
|
1323
|
+
expiresAt: repo.authExpiresAt
|
|
1324
|
+
})).toString("base64"),
|
|
1325
|
+
mode: 384
|
|
1326
|
+
}
|
|
1327
|
+
],
|
|
1328
|
+
setup: `node "$HOME/.gitterm/github/runtime.cjs" setup || exit $?
|
|
1329
|
+
${GITHUB_AUTH_PATH}`
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
var GITHUB_AUTH_RUNTIME = String.raw`
|
|
1333
|
+
const fs = require("node:fs");
|
|
1334
|
+
const path = require("node:path");
|
|
1335
|
+
const os = require("node:os");
|
|
1336
|
+
const cp = require("node:child_process");
|
|
1337
|
+
const { randomUUID } = require("node:crypto");
|
|
1338
|
+
const directory = path.join(os.homedir(), ".gitterm/github");
|
|
1339
|
+
const runtimeDirectory = path.join(process.env.GITTERM_RUNTIME_DIR || "/run/gitterm", "github");
|
|
1340
|
+
const bin = path.join(os.homedir(), ".gitterm/bin");
|
|
1341
|
+
const configFile = path.join(directory, "config.json");
|
|
1342
|
+
const credentialFile = path.join(runtimeDirectory, "credential.json");
|
|
1343
|
+
const cacheFile = path.join(runtimeDirectory, "cache.json");
|
|
1344
|
+
const read = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
|
|
1345
|
+
const valid = (value) => typeof value === "string" && value.length > 0 && !/[\r\n\0]/.test(value);
|
|
1346
|
+
const fresh = (value) => valid(value?.token) && Date.parse(value.expiresAt) > Date.now() + 300000;
|
|
1347
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1348
|
+
|
|
1349
|
+
function atomicWrite(file, value) {
|
|
1350
|
+
const temporary = file + "." + randomUUID();
|
|
1351
|
+
try {
|
|
1352
|
+
fs.writeFileSync(temporary, JSON.stringify(value), { mode: 0o600, flag: "wx" });
|
|
1353
|
+
fs.renameSync(temporary, file);
|
|
1354
|
+
} finally {
|
|
1355
|
+
fs.rmSync(temporary, { force: true });
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
async function credential() {
|
|
1360
|
+
const config = read(configFile);
|
|
1361
|
+
const initial = read(credentialFile);
|
|
1362
|
+
if (!config.renewable) {
|
|
1363
|
+
if (!valid(initial.token) || !valid(initial.username)) throw new Error("invalid credential");
|
|
1364
|
+
return initial;
|
|
1365
|
+
}
|
|
1366
|
+
const cached = () => {
|
|
1367
|
+
try { const value = read(cacheFile); if (fresh(value)) return value; } catch {}
|
|
1368
|
+
if (fresh(initial)) return initial;
|
|
1369
|
+
};
|
|
1370
|
+
let value = cached();
|
|
1371
|
+
if (value) return value;
|
|
1372
|
+
const lock = path.join(runtimeDirectory, "refresh.lock");
|
|
1373
|
+
const deadline = Date.now() + 15000;
|
|
1374
|
+
while (true) {
|
|
1375
|
+
try { fs.mkdirSync(lock, { mode: 0o700 }); break; }
|
|
1376
|
+
catch (error) {
|
|
1377
|
+
if (error.code !== "EEXIST") throw error;
|
|
1378
|
+
try {
|
|
1379
|
+
if (Date.now() - fs.statSync(lock).mtimeMs > 30000) fs.rmdirSync(lock);
|
|
1380
|
+
} catch {}
|
|
1381
|
+
if (Date.now() > deadline) throw new Error("refresh busy");
|
|
1382
|
+
await delay(100);
|
|
1383
|
+
value = cached();
|
|
1384
|
+
if (value) return value;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
try {
|
|
1388
|
+
value = cached();
|
|
1389
|
+
if (value) return value;
|
|
1390
|
+
const url = process.env.WORKSPACE_API_URL;
|
|
1391
|
+
const auth = process.env.WORKSPACE_AGENT_AUTH_TOKEN;
|
|
1392
|
+
if (!url || !auth) throw new Error("refresh unavailable");
|
|
1393
|
+
const response = await fetch(url.replace(/\/$/, "") + "/workspaceOps.gitCredential", {
|
|
1394
|
+
method: "POST",
|
|
1395
|
+
headers: { Authorization: "Bearer " + auth, "Content-Type": "application/json" },
|
|
1396
|
+
body: "{}",
|
|
1397
|
+
redirect: "error",
|
|
1398
|
+
signal: AbortSignal.timeout(10000),
|
|
1399
|
+
});
|
|
1400
|
+
if (!response.ok) throw new Error("refresh failed");
|
|
1401
|
+
const body = await response.json();
|
|
1402
|
+
const data = body.result?.data?.json ?? body.result?.data;
|
|
1403
|
+
if (!fresh(data)) throw new Error("invalid credential");
|
|
1404
|
+
value = { token: data.token, expiresAt: data.expiresAt, username: "x-access-token" };
|
|
1405
|
+
atomicWrite(cacheFile, value);
|
|
1406
|
+
return value;
|
|
1407
|
+
} finally { fs.rmdirSync(lock); }
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function setup() {
|
|
1411
|
+
fs.mkdirSync(bin, { recursive: true, mode: 0o700 });
|
|
1412
|
+
fs.mkdirSync(runtimeDirectory, { recursive: true, mode: 0o700 });
|
|
1413
|
+
fs.chmodSync(directory, 0o700);
|
|
1414
|
+
fs.chmodSync(runtimeDirectory, 0o700);
|
|
1415
|
+
fs.chmodSync(configFile, 0o600);
|
|
1416
|
+
fs.chmodSync(credentialFile, 0o600);
|
|
1417
|
+
for (const [name, mode] of [["gh", "gh"], ["git-credential-gitterm", "credential"]]) {
|
|
1418
|
+
const file = path.join(bin, name);
|
|
1419
|
+
fs.writeFileSync(file, '#!/bin/sh\nexec node "$HOME/.gitterm/github/runtime.cjs" ' + mode + ' "$@"\n', { mode: 0o700 });
|
|
1420
|
+
fs.chmodSync(file, 0o700);
|
|
1421
|
+
}
|
|
1422
|
+
// Reset only GitHub's helper chain. Other hosts retain their own credentials.
|
|
1423
|
+
// Retire our clone-time helpers, which predate host-scoped runtime auth.
|
|
1424
|
+
let helpers = [];
|
|
1425
|
+
try { helpers = cp.execFileSync("git", ["config", "--global", "--get-all", "credential.helper"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim().split("\n"); } catch {}
|
|
1426
|
+
for (const helper of helpers) {
|
|
1427
|
+
if (["/usr/local/bin/gitterm-git-credential", "/run/gitterm/git-credential-helper.sh", "/tmp/gitterm-repository-auth/git-credential-helper.sh", "/workspace/.git-credential-helper.sh"].includes(helper) || helper.includes('password=$GITHUB_APP_TOKEN')) {
|
|
1428
|
+
cp.execFileSync("git", ["config", "--global", "--fixed-value", "--unset-all", "credential.helper", helper]);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
const key = "credential.https://github.com.helper";
|
|
1432
|
+
cp.execFileSync("git", ["config", "--global", "--replace-all", key, ""]);
|
|
1433
|
+
cp.execFileSync("git", ["config", "--global", "--add", key, '!node "$HOME/.gitterm/github/runtime.cjs" credential']);
|
|
1434
|
+
// Login shells and interactive agent shells may reset PATH. No secrets in profiles.
|
|
1435
|
+
const line = 'export PATH="$HOME/.gitterm/bin:$PATH" # gitterm-github-cli';
|
|
1436
|
+
for (const name of [".profile", ".bashrc", ".zshrc"]) {
|
|
1437
|
+
const file = path.join(os.homedir(), name);
|
|
1438
|
+
const text = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
1439
|
+
if (!text.includes(line)) fs.appendFileSync(file, "\n" + line + "\n", { mode: 0o600 });
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function githubTarget(args) {
|
|
1444
|
+
let host = process.env.GH_HOST;
|
|
1445
|
+
let repo = process.env.GH_REPO;
|
|
1446
|
+
for (let i = 0; i < args.length; i++) {
|
|
1447
|
+
if (args[i] === "--hostname") host = args[++i];
|
|
1448
|
+
else if (args[i].startsWith("--hostname=")) host = args[i].slice(11);
|
|
1449
|
+
else if (args[i] === "--repo" || args[i] === "-R") repo = args[++i];
|
|
1450
|
+
else if (args[i].startsWith("--repo=")) repo = args[i].slice(7);
|
|
1451
|
+
else if (args[i].startsWith("-R") && args[i].length > 2) repo = args[i].slice(2);
|
|
1452
|
+
}
|
|
1453
|
+
if (host && host.toLowerCase() !== "github.com") return false;
|
|
1454
|
+
if (repo && !/^(https:\/\/github\.com\/|github\.com\/)?[^/]+\/[^/]+\/?$/.test(repo)) return false;
|
|
1455
|
+
if (args.some((arg) => /^https?:\/\//.test(arg) && !/^https:\/\/(api\.)?github\.com\//.test(arg))) return false;
|
|
1456
|
+
if (!host && !repo) {
|
|
1457
|
+
try {
|
|
1458
|
+
const origin = cp.execFileSync("git", ["remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
1459
|
+
if (!/^(https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)/i.test(origin)) return false;
|
|
1460
|
+
} catch {}
|
|
1461
|
+
}
|
|
1462
|
+
return true;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
async function main() {
|
|
1466
|
+
const [mode, ...args] = process.argv.slice(2);
|
|
1467
|
+
if (mode === "setup") return setup();
|
|
1468
|
+
if (mode === "credential") {
|
|
1469
|
+
if (args[0] !== "get") return;
|
|
1470
|
+
const input = fs.readFileSync(0, "utf8");
|
|
1471
|
+
const fields = Object.fromEntries(input.trim().split("\n").map((line) => { const i = line.indexOf("="); return [line.slice(0, i), line.slice(i + 1)]; }));
|
|
1472
|
+
if (fields.protocol !== "https" || fields.host !== "github.com") return;
|
|
1473
|
+
const value = await credential();
|
|
1474
|
+
process.stdout.write("username=" + (value.username ?? "x-access-token") + "\npassword=" + value.token + "\n");
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (mode !== "gh") throw new Error("invalid mode");
|
|
1478
|
+
// Find the actual binary on every invocation, including installations made later.
|
|
1479
|
+
const wrapper = fs.realpathSync(path.join(bin, "gh"));
|
|
1480
|
+
let executable;
|
|
1481
|
+
for (const entry of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
1482
|
+
if (!entry) continue;
|
|
1483
|
+
const candidate = path.resolve(entry, "gh");
|
|
1484
|
+
try {
|
|
1485
|
+
if (fs.statSync(candidate).isFile() && fs.realpathSync(candidate) !== wrapper) {
|
|
1486
|
+
fs.accessSync(candidate, fs.constants.X_OK); executable = candidate; break;
|
|
1487
|
+
}
|
|
1488
|
+
} catch {}
|
|
1489
|
+
}
|
|
1490
|
+
if (!executable) {
|
|
1491
|
+
console.error("GitHub CLI is not installed in this image. Install gh, then retry.");
|
|
1492
|
+
process.exitCode = 127;
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
const env = { ...process.env };
|
|
1496
|
+
if (!env.GH_TOKEN && !env.GITHUB_TOKEN && args.length && githubTarget(args) && !args.some((arg) => ["--help", "--version"].includes(arg)) && args[0] !== "help") {
|
|
1497
|
+
env.GH_TOKEN = (await credential()).token;
|
|
1498
|
+
}
|
|
1499
|
+
const child = cp.spawn(executable, args, { env, stdio: "inherit" });
|
|
1500
|
+
const handlers = new Map();
|
|
1501
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
1502
|
+
const handler = () => child.kill(signal);
|
|
1503
|
+
handlers.set(signal, handler);
|
|
1504
|
+
process.on(signal, handler);
|
|
1505
|
+
}
|
|
1506
|
+
child.on("error", () => { console.error("Could not start GitHub CLI."); process.exitCode = 127; });
|
|
1507
|
+
child.on("exit", (code, signal) => {
|
|
1508
|
+
for (const [name, handler] of handlers) process.removeListener(name, handler);
|
|
1509
|
+
if (signal) process.kill(process.pid, signal);
|
|
1510
|
+
else process.exitCode = code ?? 1;
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
main().catch(() => {
|
|
1514
|
+
console.error("GitTerm GitHub authentication failed; check the workspace credentials or integration.");
|
|
1515
|
+
process.exitCode = 1;
|
|
1516
|
+
});
|
|
1517
|
+
`;
|
|
1518
|
+
|
|
1246
1519
|
// src/direct/provisioning.ts
|
|
1247
1520
|
var DIRECT_OPENCODE_PORT = 4096;
|
|
1248
1521
|
var DIRECT_OPENCODE_COMMAND = `opencode serve --hostname 0.0.0.0 --port ${DIRECT_OPENCODE_PORT}`;
|
|
@@ -1251,7 +1524,9 @@ var DIRECT_E2B_TEMPLATES = {
|
|
|
1251
1524
|
standard: "gitterm-opencode-server",
|
|
1252
1525
|
large: "gitterm-opencode-server-lg"
|
|
1253
1526
|
};
|
|
1254
|
-
var DIRECT_GITTERM_INSTRUCTIONS =
|
|
1527
|
+
var DIRECT_GITTERM_INSTRUCTIONS = `You are running in a direct Gitterm workspace. Follow the user's instructions and verify outcomes before reporting success.
|
|
1528
|
+
|
|
1529
|
+
${GITHUB_CLI_INSTRUCTIONS}`;
|
|
1255
1530
|
var MAX_ADDITIONAL_AGENT_INSTRUCTIONS = 50000;
|
|
1256
1531
|
function resolveDirectImage(image) {
|
|
1257
1532
|
return image?.trim() || DIRECT_OPENCODE_SERVER_IMAGE;
|
|
@@ -1368,7 +1643,14 @@ function buildDirectProvisioningPlan(input) {
|
|
|
1368
1643
|
},
|
|
1369
1644
|
...plugins.length ? { plugin: plugins } : {}
|
|
1370
1645
|
};
|
|
1646
|
+
const github = githubAuthProvisioning(input.repo ? {
|
|
1647
|
+
url: input.repo,
|
|
1648
|
+
authToken: input.repositoryCredentials?.token,
|
|
1649
|
+
authUsername: input.repositoryCredentials?.username,
|
|
1650
|
+
inlineAuth: true
|
|
1651
|
+
} : undefined);
|
|
1371
1652
|
const files = [
|
|
1653
|
+
...github?.files ?? [],
|
|
1372
1654
|
{
|
|
1373
1655
|
path: "~/.local/share/opencode/auth.json",
|
|
1374
1656
|
contentBase64: base64(JSON.stringify(Object.fromEntries(credentials)))
|
|
@@ -1412,11 +1694,15 @@ function buildDirectProvisioningPlan(input) {
|
|
|
1412
1694
|
...environmentVariables,
|
|
1413
1695
|
OPENCODE_SERVER_PASSWORD: input.password
|
|
1414
1696
|
},
|
|
1415
|
-
command: DIRECT_OPENCODE_COMMAND,
|
|
1697
|
+
command: github ? githubAuthCommand(DIRECT_OPENCODE_COMMAND) : DIRECT_OPENCODE_COMMAND,
|
|
1416
1698
|
port: DIRECT_OPENCODE_PORT
|
|
1417
1699
|
},
|
|
1418
1700
|
setup: {
|
|
1419
|
-
beforeAgent:
|
|
1701
|
+
beforeAgent: [
|
|
1702
|
+
...(input.opencode?.api ?? "v2") === "v2" ? [OPENCODE_V2_IMPORT_CREDENTIALS] : [],
|
|
1703
|
+
...github ? [github.setup] : [],
|
|
1704
|
+
...input.setup?.beforeAgent ?? []
|
|
1705
|
+
],
|
|
1420
1706
|
afterAgent: input.setup?.afterAgent ?? []
|
|
1421
1707
|
}
|
|
1422
1708
|
};
|
|
@@ -1611,7 +1897,7 @@ function createAsciiDirectProvider(config) {
|
|
|
1611
1897
|
});
|
|
1612
1898
|
await runCommand(handle.boxId, `mkdir -p ${shellQuote(directory)}`);
|
|
1613
1899
|
for (const command of config.runtimeSetupCommands ?? [
|
|
1614
|
-
"npm install -g opencode
|
|
1900
|
+
"npm install -g @opencode/cli@2 --no-audit --fund=false"
|
|
1615
1901
|
]) {
|
|
1616
1902
|
await runCommand(handle.boxId, command, undefined, 600);
|
|
1617
1903
|
}
|
|
@@ -2603,7 +2889,7 @@ function createVercelDirectProvider(config) {
|
|
|
2603
2889
|
});
|
|
2604
2890
|
try {
|
|
2605
2891
|
await run(sandbox, `mkdir -p ${shellQuote(handle.directory)}`);
|
|
2606
|
-
for (const command of config.runtimeSetupCommands ?? (config.image ? [] : ["npm install -g opencode
|
|
2892
|
+
for (const command of config.runtimeSetupCommands ?? (config.image ? [] : ["npm install -g @opencode/cli@2 --no-audit --fund=false"])) {
|
|
2607
2893
|
await run(sandbox, command);
|
|
2608
2894
|
}
|
|
2609
2895
|
if (plan.repository) {
|
|
@@ -2763,7 +3049,7 @@ function runtimeClient(workspace, fetchImpl) {
|
|
|
2763
3049
|
}
|
|
2764
3050
|
function createAuthClient(workspace, fetchImpl) {
|
|
2765
3051
|
if (workspace.opencodeApi !== "v2")
|
|
2766
|
-
throw new GittermError("BAD_REQUEST", "Runtime OAuth connection management requires OpenCode v2
|
|
3052
|
+
throw new GittermError("BAD_REQUEST", "Runtime OAuth connection management requires OpenCode v2");
|
|
2767
3053
|
const authorization = workspace.runtime.password ? `Basic ${Buffer.from(`opencode:${workspace.runtime.password}`).toString("base64")}` : undefined;
|
|
2768
3054
|
return createOpencodeV2Client({
|
|
2769
3055
|
fetch: fetchImpl,
|
|
@@ -6,7 +6,7 @@ export declare const DIRECT_E2B_TEMPLATES: {
|
|
|
6
6
|
readonly standard: "gitterm-opencode-server";
|
|
7
7
|
readonly large: "gitterm-opencode-server-lg";
|
|
8
8
|
};
|
|
9
|
-
export declare const DIRECT_GITTERM_INSTRUCTIONS = "You are running in a direct Gitterm workspace. Follow the user's instructions and verify outcomes before reporting success.";
|
|
9
|
+
export declare const DIRECT_GITTERM_INSTRUCTIONS = "You are running in a direct Gitterm workspace. Follow the user's instructions and verify outcomes before reporting success.\n\nGitHub CLI authentication:\n- The user may have supplied repository credentials or selected a GitHub integration for this workspace. GitTerm authentication is configured only when $HOME/.gitterm/bin/gh exists. Check for that executable, but never read or print files under $HOME/.gitterm/github.\n- The image may not include the real GitHub CLI. Run gh --version. If the GitTerm launcher says the CLI is missing, install the official gh binary using the image's package manager or in another directory.\n- Keep $HOME/.gitterm/bin first on PATH after installation. For a downloaded CLI, use: export PATH=\"$HOME/.gitterm/bin:/path/to/installed/gh/bin:$PATH\". Never prepend the real CLI ahead of $HOME/.gitterm/bin and never overwrite $HOME/.gitterm/bin/gh.\n- With the launcher first, use the normal gh command (for example, gh pr create). The launcher supplies the user-provided token or a fresh integration token only to that operation.\n- Never run gh auth login, pipe $GH_TOKEN, copy tokens, or use gh auth status for this workspace authentication. $GH_TOKEN is intentionally not exported to the agent shell.\n- If $HOME/.gitterm/bin/gh is absent, tell the user that this workspace has no GitTerm-provided GitHub credentials. If an authenticated operation is denied, report the denied operation and ask for repository access or the required GitHub App/token permission.";
|
|
10
10
|
export declare function resolveDirectImage(image?: string): string;
|
|
11
11
|
export declare function directModelAuth(credential: DirectModelCredential): {
|
|
12
12
|
enterpriseUrl?: string | undefined;
|
package/dist/direct/types.d.ts
CHANGED
|
@@ -126,6 +126,7 @@ export type DirectWorkspaceCreateInput = {
|
|
|
126
126
|
branch?: string;
|
|
127
127
|
baseCommit?: string;
|
|
128
128
|
checkoutRef?: string;
|
|
129
|
+
/** Git credentials; also configure runtime Git/gh authentication for github.com. Not auto-renewed. */
|
|
129
130
|
repositoryCredentials?: {
|
|
130
131
|
username?: string;
|
|
131
132
|
token: string;
|
|
@@ -146,7 +147,7 @@ export type DirectWorkspaceCreateInput = {
|
|
|
146
147
|
/** Trusted integration context appended to the generated global AGENTS.md. */
|
|
147
148
|
additionalAgentInstructions?: string;
|
|
148
149
|
opencode?: {
|
|
149
|
-
/** Defaults to
|
|
150
|
+
/** Defaults to v2. */
|
|
150
151
|
api?: OpencodeApi;
|
|
151
152
|
config?: Record<string, unknown>;
|
|
152
153
|
plugins?: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export type { DeviceCodeInfo, LoginWithDeviceCodeOptions } from "./device-login.
|
|
|
6
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 { 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";
|
|
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, GitHubIntegration, GoogleCloudIntegration, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -686,6 +686,34 @@ ${log}` : ""}`);
|
|
|
686
686
|
})).filter((model) => !listOptions?.provider || model.provider === listOptions.provider);
|
|
687
687
|
})
|
|
688
688
|
},
|
|
689
|
+
integrations: {
|
|
690
|
+
github: {
|
|
691
|
+
list: () => run(async () => {
|
|
692
|
+
const result = await trpc.workspace.listUserInstallations.query();
|
|
693
|
+
return result.installations.map(({ git_integration, github_app_installation }) => ({
|
|
694
|
+
id: git_integration.id,
|
|
695
|
+
accountLogin: git_integration.providerAccountLogin,
|
|
696
|
+
accountType: github_app_installation.accountType,
|
|
697
|
+
repositorySelection: github_app_installation.repositorySelection,
|
|
698
|
+
suspended: github_app_installation.suspended,
|
|
699
|
+
connectedAt: toIso(git_integration.connectedAt)
|
|
700
|
+
}));
|
|
701
|
+
})
|
|
702
|
+
},
|
|
703
|
+
googleCloud: {
|
|
704
|
+
list: () => run(async () => {
|
|
705
|
+
const result = await trpc.googleCloud.list.query();
|
|
706
|
+
return result.map((integration) => ({
|
|
707
|
+
id: integration.id,
|
|
708
|
+
name: integration.name,
|
|
709
|
+
projectId: integration.projectId,
|
|
710
|
+
workloadIdentityProvider: integration.workloadIdentityProvider,
|
|
711
|
+
serviceAccountEmail: integration.serviceAccountEmail,
|
|
712
|
+
connectedAt: toIso(integration.connectedAt)
|
|
713
|
+
}));
|
|
714
|
+
})
|
|
715
|
+
}
|
|
716
|
+
},
|
|
689
717
|
credentials: {
|
|
690
718
|
list: () => run(async () => {
|
|
691
719
|
const result = await trpc.modelCredentials.listMyCredentials.query();
|
package/dist/types.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export type RunRef = {
|
|
|
63
63
|
workspaceId: string;
|
|
64
64
|
id: string;
|
|
65
65
|
};
|
|
66
|
-
/** `v1` = OpenCode 1.x (`/event`, `/session/*`); `v2` = OpenCode 2 (`/api/*`)
|
|
66
|
+
/** `v1` = OpenCode 1.x (`/event`, `/session/*`); `v2` = OpenCode 2 (`/api/*`) and the default. */
|
|
67
67
|
export type OpencodeApi = "v1" | "v2";
|
|
68
68
|
export type WaitOptions = {
|
|
69
69
|
timeoutMs?: number;
|
|
@@ -161,6 +161,7 @@ export type WorkspaceProviderSelection = {
|
|
|
161
161
|
} | ({
|
|
162
162
|
type: "aws";
|
|
163
163
|
region?: string;
|
|
164
|
+
accessProfile?: string;
|
|
164
165
|
} & Omit<ProviderSelectionBase, "machine"> & {
|
|
165
166
|
machine?: FlexibleMachine<AwsResources>;
|
|
166
167
|
}) | ({
|
|
@@ -223,11 +224,15 @@ export type WorkspaceCreateInput = {
|
|
|
223
224
|
/** Provider intent. Defaults to the user's or deployment's preferred provider. */
|
|
224
225
|
provider?: WorkspaceProviderSelection;
|
|
225
226
|
/** Inline Git credentials for repository validation, cloning, and runtime pull/push. */
|
|
227
|
+
/** Runtime Git/gh authentication. Takes precedence over gitIntegrationId; not auto-renewed. */
|
|
226
228
|
repositoryCredentials?: {
|
|
227
229
|
username?: string;
|
|
228
230
|
token: string;
|
|
229
231
|
};
|
|
232
|
+
/** Dashboard GitHub App integration; runtime Git/gh credentials are renewed automatically. */
|
|
230
233
|
gitIntegrationId?: string;
|
|
234
|
+
/** Google Workload Identity Federation integration; injects keyless ADC/gcloud auth. */
|
|
235
|
+
googleCloudIntegrationId?: string;
|
|
231
236
|
/** Defaults from the selected provider. */
|
|
232
237
|
persistent?: boolean;
|
|
233
238
|
workspaceProfile?: "standard" | "ssh-enabled";
|
|
@@ -267,12 +272,27 @@ export type WorkspaceCreateInput = {
|
|
|
267
272
|
*/
|
|
268
273
|
config?: Record<string, unknown>;
|
|
269
274
|
/**
|
|
270
|
-
* OpenCode API generation served by the image. Defaults to `
|
|
271
|
-
* with an image built on OpenCode 2 (`@opencode-ai/cli`); experimental.
|
|
275
|
+
* OpenCode API generation served by the image. Defaults to `v2`.
|
|
272
276
|
*/
|
|
273
277
|
api?: OpencodeApi;
|
|
274
278
|
};
|
|
275
279
|
};
|
|
280
|
+
export type GitHubIntegration = {
|
|
281
|
+
id: string;
|
|
282
|
+
accountLogin: string;
|
|
283
|
+
accountType: string;
|
|
284
|
+
repositorySelection: string;
|
|
285
|
+
suspended: boolean;
|
|
286
|
+
connectedAt: string;
|
|
287
|
+
};
|
|
288
|
+
export type GoogleCloudIntegration = {
|
|
289
|
+
id: string;
|
|
290
|
+
name: string;
|
|
291
|
+
projectId: string;
|
|
292
|
+
workloadIdentityProvider: string;
|
|
293
|
+
serviceAccountEmail: string;
|
|
294
|
+
connectedAt: string;
|
|
295
|
+
};
|
|
276
296
|
export type WorkspaceRestartResult = {
|
|
277
297
|
status: WorkspaceStatus;
|
|
278
298
|
};
|
|
@@ -309,6 +329,7 @@ export type AgentQuestionOption = {
|
|
|
309
329
|
label: string;
|
|
310
330
|
description: string;
|
|
311
331
|
};
|
|
332
|
+
/** Options are normalised: OpenCode's own type-your-own entry, duplicates and blank labels never appear. */
|
|
312
333
|
export type AgentQuestion = {
|
|
313
334
|
/** Identifies this question in `AgentRunReply.answers`. Unique within a request. */
|
|
314
335
|
key: string;
|
|
@@ -593,6 +614,12 @@ export type AgentType = {
|
|
|
593
614
|
export type CloudProvider = {
|
|
594
615
|
id: string;
|
|
595
616
|
name: string;
|
|
617
|
+
awsAccessProfiles?: Array<{
|
|
618
|
+
id: string;
|
|
619
|
+
name: string;
|
|
620
|
+
description: string;
|
|
621
|
+
roleArn: string;
|
|
622
|
+
}>;
|
|
596
623
|
providerKey: ProviderKey | string;
|
|
597
624
|
regions?: Array<{
|
|
598
625
|
id: string;
|
|
@@ -622,6 +649,12 @@ export type WorkspaceCatalog = {
|
|
|
622
649
|
name: string;
|
|
623
650
|
location: string;
|
|
624
651
|
}>;
|
|
652
|
+
accessProfiles?: Array<{
|
|
653
|
+
id: string;
|
|
654
|
+
name: string;
|
|
655
|
+
description: string;
|
|
656
|
+
roleArn: string;
|
|
657
|
+
}>;
|
|
625
658
|
machines: Array<{
|
|
626
659
|
id: string;
|
|
627
660
|
key: string;
|