lastlight-shared 0.1.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/LICENSE +21 -0
- package/dist/config-types.d.ts +17 -0
- package/dist/config-types.js +8 -0
- package/dist/config-types.js.map +1 -0
- package/dist/core-pin.d.ts +16 -0
- package/dist/core-pin.js +85 -0
- package/dist/core-pin.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +43 -0
- package/dist/oauth.js +103 -0
- package/dist/oauth.js.map +1 -0
- package/dist/overlay-assets.d.ts +20 -0
- package/dist/overlay-assets.js +114 -0
- package/dist/overlay-assets.js.map +1 -0
- package/dist/overlay-bootstrap.d.ts +44 -0
- package/dist/overlay-bootstrap.js +304 -0
- package/dist/overlay-bootstrap.js.map +1 -0
- package/dist/providers.d.ts +125 -0
- package/dist/providers.js +279 -0
- package/dist/providers.js.map +1 -0
- package/dist/workflow-loader.d.ts +53 -0
- package/dist/workflow-loader.js +421 -0
- package/dist/workflow-loader.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared bootstrap for the private deployment overlay (`instance/`).
|
|
3
|
+
*
|
|
4
|
+
* The overlay is a separate, private folder (typically its own GitHub repo)
|
|
5
|
+
* holding this deployment's `config.yaml` + `secrets/` + optional asset
|
|
6
|
+
* overrides. It's mounted read-only at `/app/instance` and selected via
|
|
7
|
+
* `LASTLIGHT_OVERLAY_DIR`. On a clean server there's nothing to clone yet — so
|
|
8
|
+
* this module can *create* one from scratch:
|
|
9
|
+
*
|
|
10
|
+
* - {@link scaffoldOverlayFiles} — write the default files into a fresh overlay
|
|
11
|
+
* (never overwriting anything that already exists).
|
|
12
|
+
* - {@link detectGh} — probe the GitHub CLI for install + auth status.
|
|
13
|
+
* - {@link bootstrapOverlayRepo} — `git init` + initial commit, then offer to
|
|
14
|
+
* create + push a private GitHub repo (via `gh` when authed, otherwise print
|
|
15
|
+
* the exact git/GitHub commands to run by hand).
|
|
16
|
+
*
|
|
17
|
+
* Used by both setup entry points: the `lastlight setup` wizard (`src/setup.ts`)
|
|
18
|
+
* and host-local `lastlight server setup` (`src/cli-server.ts`).
|
|
19
|
+
*/
|
|
20
|
+
import { execFile, spawn } from "node:child_process";
|
|
21
|
+
import { promisify } from "node:util";
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import * as p from "@clack/prompts";
|
|
25
|
+
import chalk from "chalk";
|
|
26
|
+
const exec = promisify(execFile);
|
|
27
|
+
const dim = chalk.dim;
|
|
28
|
+
const teal = chalk.hex("#1A7A8A");
|
|
29
|
+
// ── templates ────────────────────────────────────────────────────────────────
|
|
30
|
+
/** `.gitignore` for the overlay so it can be its own private repo without
|
|
31
|
+
* leaking secrets. Everything under `secrets/` is ignored except the template. */
|
|
32
|
+
export const OVERLAY_GITIGNORE = [
|
|
33
|
+
"# Host-only secrets — never commit. Everything under secrets/ is ignored",
|
|
34
|
+
"# except the template.",
|
|
35
|
+
"secrets/*",
|
|
36
|
+
"!secrets/.env.example",
|
|
37
|
+
".env",
|
|
38
|
+
"*.pem",
|
|
39
|
+
"",
|
|
40
|
+
].join("\n");
|
|
41
|
+
/** Placeholder `config.yaml` for a fresh overlay (no managed repos yet). */
|
|
42
|
+
export const OVERLAY_CONFIG_PLACEHOLDER = [
|
|
43
|
+
"# Last Light — private deployment overlay config",
|
|
44
|
+
"# Merged over the public config/default.yaml at startup; arrays replace,",
|
|
45
|
+
"# maps deep-merge, env vars override. Restart the agent to apply:",
|
|
46
|
+
"# docker compose restart agent (or: lastlight server restart agent)",
|
|
47
|
+
"",
|
|
48
|
+
"managedRepos:",
|
|
49
|
+
" [] # add owner/repo entries — the bot ignores repos not listed here",
|
|
50
|
+
"",
|
|
51
|
+
"# Pin which core version this instance runs. `lastlight server update|setup`",
|
|
52
|
+
"# checks core out at this git tag/ref and rebuilds; unset (or main/latest)",
|
|
53
|
+
"# tracks main. Bump + commit here to drive deploys from the overlay.",
|
|
54
|
+
"# deploy:",
|
|
55
|
+
"# version: v0.10.6",
|
|
56
|
+
"",
|
|
57
|
+
].join("\n");
|
|
58
|
+
/** Template `secrets/.env.example` — the only tracked file under `secrets/`.
|
|
59
|
+
* Mirrors the keys `npx lastlight setup` writes into `secrets/.env`. */
|
|
60
|
+
export const OVERLAY_ENV_EXAMPLE = [
|
|
61
|
+
"# ── Last Light — environment template ───────────────────────────",
|
|
62
|
+
"# Copy to secrets/.env and fill in. The whole instance/ folder is mounted",
|
|
63
|
+
"# read-only at /app/instance; the entrypoint reads secrets from",
|
|
64
|
+
"# /app/instance/secrets. secrets/.env and *.pem are gitignored.",
|
|
65
|
+
"",
|
|
66
|
+
"# ── Overlay (this deployment's private config + assets) ──",
|
|
67
|
+
"LASTLIGHT_OVERLAY_DIR=/app/instance",
|
|
68
|
+
"",
|
|
69
|
+
"# ── GitHub App (required) ────────────────────────────────",
|
|
70
|
+
"GITHUB_APP_ID=",
|
|
71
|
+
"# PEM lives at instance/secrets/app.pem; the entrypoint symlinks it to /app/app.pem.",
|
|
72
|
+
"GITHUB_APP_PRIVATE_KEY_PATH=./app.pem",
|
|
73
|
+
"GITHUB_APP_INSTALLATION_ID=",
|
|
74
|
+
"",
|
|
75
|
+
"# ── Webhook (required) — must match the GitHub App webhook secret ──",
|
|
76
|
+
"WEBHOOK_SECRET=",
|
|
77
|
+
"",
|
|
78
|
+
"# ── Domain (used by Caddy for TLS) ───────────────────────",
|
|
79
|
+
"DOMAIN=localhost",
|
|
80
|
+
"",
|
|
81
|
+
"# ── Model + provider API key ─────────────────────────────",
|
|
82
|
+
"LASTLIGHT_MODEL=anthropic/claude-sonnet-4-6",
|
|
83
|
+
"# Set the env var that matches your LASTLIGHT_MODEL's provider.",
|
|
84
|
+
"# The wizard-able registry (src/providers.ts): anthropic, openai, openrouter,",
|
|
85
|
+
"# google (GEMINI_API_KEY), mistral, groq, cerebras, xai, huggingface (HF_TOKEN),",
|
|
86
|
+
"# moonshotai, nvidia, fireworks, together, deepseek, zai, kimi-coding, minimax.",
|
|
87
|
+
"# ANTHROPIC_API_KEY=",
|
|
88
|
+
"# OPENAI_API_KEY=",
|
|
89
|
+
"# OPENROUTER_API_KEY=",
|
|
90
|
+
"",
|
|
91
|
+
"# ── Admin dashboard ──────────────────────────────────────",
|
|
92
|
+
"ADMIN_SECRET=",
|
|
93
|
+
"# ADMIN_PASSWORD=",
|
|
94
|
+
"",
|
|
95
|
+
"# ── Slack (optional) ─────────────────────────────────────",
|
|
96
|
+
"# SLACK_BOT_TOKEN=",
|
|
97
|
+
"# SLACK_APP_TOKEN=",
|
|
98
|
+
"# SLACK_DELIVERY_CHANNEL=",
|
|
99
|
+
"# SLACK_ALLOWED_USERS=",
|
|
100
|
+
"",
|
|
101
|
+
].join("\n");
|
|
102
|
+
/** README dropped into a freshly scaffolded overlay. */
|
|
103
|
+
export const OVERLAY_README = [
|
|
104
|
+
"# lastlight-instance",
|
|
105
|
+
"",
|
|
106
|
+
"Private deployment overlay for [Last Light](https://github.com/nearform/lastlight).",
|
|
107
|
+
"Keeps deployment-specific config and secrets out of the public repo.",
|
|
108
|
+
"",
|
|
109
|
+
"Cloned into `instance/` next to the lastlight checkout's `docker-compose.yml`,",
|
|
110
|
+
"mounted read-only at `/app/instance`, selected via `LASTLIGHT_OVERLAY_DIR=/app/instance`.",
|
|
111
|
+
"",
|
|
112
|
+
"## Layout",
|
|
113
|
+
"",
|
|
114
|
+
"```",
|
|
115
|
+
"config.yaml # overlay config — merged over public config/default.yaml",
|
|
116
|
+
"agent-context/*.md # (optional) persona/rules overrides, merged by filename",
|
|
117
|
+
"workflows/*.yaml # (optional) add or replace workflows by logical name",
|
|
118
|
+
"workflows/prompts/*.md # (optional) prompt overrides",
|
|
119
|
+
"skills/<name>/SKILL.md # (optional) skill overrides",
|
|
120
|
+
"secrets/ # host-only, gitignored: .env + GitHub App *.pem",
|
|
121
|
+
" .env.example # template (the only tracked file under secrets/)",
|
|
122
|
+
"```",
|
|
123
|
+
"",
|
|
124
|
+
"## First-time secrets",
|
|
125
|
+
"",
|
|
126
|
+
"```bash",
|
|
127
|
+
"cp secrets/.env.example secrets/.env # then fill in keys, DOMAIN, etc.",
|
|
128
|
+
"cp /path/to/app.pem secrets/app.pem # GitHub App private key",
|
|
129
|
+
"```",
|
|
130
|
+
"",
|
|
131
|
+
"Editing the overlay is **restart-only** (no image rebuild):",
|
|
132
|
+
"`git pull` then `lastlight server restart agent`.",
|
|
133
|
+
"",
|
|
134
|
+
"## Pinning the core version",
|
|
135
|
+
"",
|
|
136
|
+
"Add a `deploy.version` to `config.yaml` to pin which core release this",
|
|
137
|
+
"instance runs (a git tag/ref); unset — or `main`/`latest` — tracks `main`:",
|
|
138
|
+
"",
|
|
139
|
+
"```yaml",
|
|
140
|
+
"deploy:",
|
|
141
|
+
" version: v0.10.6",
|
|
142
|
+
"```",
|
|
143
|
+
"",
|
|
144
|
+
"`lastlight server update` (and `server setup`) checks core out at that tag and",
|
|
145
|
+
"rebuilds. Bump + commit here and run `lastlight server update` on the host —",
|
|
146
|
+
"so this overlay repo is the single source of truth for the deployed version.",
|
|
147
|
+
"",
|
|
148
|
+
].join("\n");
|
|
149
|
+
/** Probe the GitHub CLI. Never throws — a missing/unauthed `gh` resolves to a
|
|
150
|
+
* status the callers fall back from (print manual commands instead). */
|
|
151
|
+
export async function detectGh() {
|
|
152
|
+
try {
|
|
153
|
+
await exec("gh", ["--version"], { timeout: 10_000 });
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return { installed: false, authed: false };
|
|
157
|
+
}
|
|
158
|
+
let authed = false;
|
|
159
|
+
try {
|
|
160
|
+
await exec("gh", ["auth", "status"], { timeout: 10_000 });
|
|
161
|
+
authed = true;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return { installed: true, authed: false };
|
|
165
|
+
}
|
|
166
|
+
let login;
|
|
167
|
+
try {
|
|
168
|
+
const { stdout } = await exec("gh", ["api", "user", "--jq", ".login"], { timeout: 10_000 });
|
|
169
|
+
login = stdout.trim() || undefined;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* authed but couldn't read login — leave undefined */
|
|
173
|
+
}
|
|
174
|
+
return { installed: true, authed, login };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Write the default overlay files into `instanceDir`, creating only what's
|
|
178
|
+
* missing — never overwriting an existing file. Returns the relative paths
|
|
179
|
+
* created so callers can report them.
|
|
180
|
+
*/
|
|
181
|
+
export function scaffoldOverlayFiles(instanceDir) {
|
|
182
|
+
const created = [];
|
|
183
|
+
const secretsDir = path.join(instanceDir, "secrets");
|
|
184
|
+
fs.mkdirSync(secretsDir, { recursive: true });
|
|
185
|
+
const writeIfMissing = (rel, content) => {
|
|
186
|
+
const abs = path.join(instanceDir, rel);
|
|
187
|
+
if (fs.existsSync(abs))
|
|
188
|
+
return;
|
|
189
|
+
fs.writeFileSync(abs, content, { encoding: "utf8" });
|
|
190
|
+
created.push(rel);
|
|
191
|
+
};
|
|
192
|
+
writeIfMissing("config.yaml", OVERLAY_CONFIG_PLACEHOLDER);
|
|
193
|
+
writeIfMissing(".gitignore", OVERLAY_GITIGNORE);
|
|
194
|
+
writeIfMissing("README.md", OVERLAY_README);
|
|
195
|
+
writeIfMissing(path.join("secrets", ".env.example"), OVERLAY_ENV_EXAMPLE);
|
|
196
|
+
return { created };
|
|
197
|
+
}
|
|
198
|
+
/** Run a command with inherited stdio so the user sees native git/gh output. */
|
|
199
|
+
function run(label, cmd, args, cwd) {
|
|
200
|
+
p.log.step(chalk.bold(label));
|
|
201
|
+
console.log(dim(` $ ${cmd} ${args.join(" ")}`));
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
const child = spawn(cmd, args, { stdio: "inherit", cwd });
|
|
204
|
+
child.on("error", reject);
|
|
205
|
+
child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`${label} failed (exit ${code ?? "signal"})`)));
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async function gitCaptureSoft(args, cwd) {
|
|
209
|
+
try {
|
|
210
|
+
const { stdout } = await exec("git", args, { cwd, timeout: 20_000 });
|
|
211
|
+
return stdout.trim();
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function isGitRepo(dir) {
|
|
218
|
+
return fs.existsSync(path.join(dir, ".git"));
|
|
219
|
+
}
|
|
220
|
+
/** Print the exact commands to version + publish the overlay by hand. */
|
|
221
|
+
function printManualHint(instanceDir, gh, needsInit) {
|
|
222
|
+
const slug = gh.login ? `${gh.login}/lastlight-instance` : "<owner>/lastlight-instance";
|
|
223
|
+
const lines = ["To version + publish this overlay yourself:"];
|
|
224
|
+
if (needsInit) {
|
|
225
|
+
lines.push(` git -C ${instanceDir} init -b main`, ` git -C ${instanceDir} add -A`, ` git -C ${instanceDir} commit -m "Initial Last Light overlay"`);
|
|
226
|
+
}
|
|
227
|
+
if (gh.installed) {
|
|
228
|
+
lines.push(` gh repo create ${slug} --private --source ${instanceDir} --remote origin --push`);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
lines.push(` # create a PRIVATE repo on GitHub (e.g. ${slug}), then:`, ` git -C ${instanceDir} remote add origin git@github.com:${slug}.git`, ` git -C ${instanceDir} push -u origin main`);
|
|
232
|
+
}
|
|
233
|
+
p.log.info(lines.map((l, i) => (i === 0 ? l : dim(l))).join("\n "));
|
|
234
|
+
p.log.info(dim("Secrets (secrets/.env, *.pem) stay gitignored. To match the prod deploy-key style,") +
|
|
235
|
+
"\n " +
|
|
236
|
+
dim("clone via an SSH host alias, e.g. git@github-instance:<owner>/lastlight-instance.git."));
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Version `instanceDir` as a private git repo and (optionally) create + push a
|
|
240
|
+
* GitHub repo for it. Idempotent: skips `git init` when already a repo, and
|
|
241
|
+
* skips repo creation when an `origin` remote already exists. Falls back to
|
|
242
|
+
* printing the manual commands whenever `gh` is unavailable or the user declines.
|
|
243
|
+
*/
|
|
244
|
+
export async function bootstrapOverlayRepo(instanceDir, opts) {
|
|
245
|
+
let versioned = isGitRepo(instanceDir);
|
|
246
|
+
if (!versioned) {
|
|
247
|
+
if (opts.yes) {
|
|
248
|
+
printManualHint(instanceDir, opts.gh, true);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const want = await p.confirm({
|
|
252
|
+
message: "Version instance/ as a private git repo now?",
|
|
253
|
+
initialValue: true,
|
|
254
|
+
});
|
|
255
|
+
if (p.isCancel(want) || !want) {
|
|
256
|
+
printManualHint(instanceDir, opts.gh, true);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
await run("git init", "git", ["init", "-b", "main", instanceDir]);
|
|
261
|
+
await run("Stage overlay files", "git", ["-C", instanceDir, "add", "-A"]);
|
|
262
|
+
await run("Initial commit", "git", ["-C", instanceDir, "commit", "-m", "Initial Last Light overlay"]);
|
|
263
|
+
versioned = true;
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
p.log.warn(`Couldn't auto-create the git repo: ${err.message}\n` +
|
|
267
|
+
" (Is git's user.name / user.email configured?)");
|
|
268
|
+
printManualHint(instanceDir, opts.gh, !versioned);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Already (or now) a repo — does it already point somewhere?
|
|
273
|
+
const remotes = await gitCaptureSoft(["remote"], instanceDir);
|
|
274
|
+
if (remotes && remotes.split(/\s+/).includes("origin")) {
|
|
275
|
+
p.log.info("Overlay already has an 'origin' remote — leaving it as-is.");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (opts.gh.authed && !opts.yes) {
|
|
279
|
+
const want = await p.confirm({
|
|
280
|
+
message: "Create a private GitHub repo and push the overlay now?",
|
|
281
|
+
initialValue: true,
|
|
282
|
+
});
|
|
283
|
+
if (!p.isCancel(want) && want) {
|
|
284
|
+
const def = opts.gh.login ? `${opts.gh.login}/lastlight-instance` : "lastlight-instance";
|
|
285
|
+
const slug = await p.text({ message: "Repository (owner/name)", initialValue: def });
|
|
286
|
+
if (!p.isCancel(slug) && slug.trim()) {
|
|
287
|
+
try {
|
|
288
|
+
await run("Create + push private repo", "gh", [
|
|
289
|
+
"repo", "create", slug.trim(),
|
|
290
|
+
"--private", "--source", instanceDir, "--remote", "origin", "--push",
|
|
291
|
+
]);
|
|
292
|
+
p.log.success(`Pushed overlay to ${teal(slug.trim())} ${dim("(private)")}.`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
p.log.warn(`gh repo create failed: ${err.message}`);
|
|
297
|
+
// fall through to the manual hint
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
printManualHint(instanceDir, opts.gh, false);
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=overlay-bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-bootstrap.js","sourceRoot":"","sources":["../src/overlay-bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider registry — single source of truth for the LLM providers Last Light
|
|
3
|
+
* knows how to wire end-to-end.
|
|
4
|
+
*
|
|
5
|
+
* pi-ai (`@earendil-works/pi-ai`) is provider-agnostic and supports 15+
|
|
6
|
+
* providers out of the box. Of those, only a subset is "wizard-able" here:
|
|
7
|
+
* they authenticate with a single API-key env var and expose a stable
|
|
8
|
+
* endpoint reachable from the sandbox egress firewall. Excluded are the
|
|
9
|
+
* OAuth-only providers (`openai-codex`, `github-copilot`), the multi-env
|
|
10
|
+
* Ambient-cred ones (`amazon-bedrock`, `google-vertex`, `azure-openai-*`,
|
|
11
|
+
* `cloudflare-*`), and the regional-CN variants.
|
|
12
|
+
*
|
|
13
|
+
* This registry is consumed by:
|
|
14
|
+
* - `src/engine/llm.ts` — the cheap one-shot helper used by the
|
|
15
|
+
* prompt screener + intent classifier. It builds requests for the
|
|
16
|
+
* `openai-completions` family (which is most wizard-able providers —
|
|
17
|
+
* Google and Mistral are routed through their OpenAI-compatible
|
|
18
|
+
* endpoints) and the `anthropic-messages` family (Kimi for Coding,
|
|
19
|
+
* MiniMax, Anthropic itself).
|
|
20
|
+
* - `src/engine/agent-executor.ts` — forwards each provider's env var
|
|
21
|
+
* into the sandbox so agentic-pi can auth.
|
|
22
|
+
* - `src/sandbox/egress-allowlist.ts` — the SNI/firewall allowlist is
|
|
23
|
+
* seeded from each provider's `host`.
|
|
24
|
+
* - `src/cli/setup.ts` — the install wizard's step-4 provider picker.
|
|
25
|
+
*
|
|
26
|
+
* Keep this list aligned with pi-ai's provider registry. When pi-ai adds a
|
|
27
|
+
* new provider that we want to surface, add an entry here and everything
|
|
28
|
+
* else (forwarding, egress, wizard UI) follows automatically.
|
|
29
|
+
*/
|
|
30
|
+
/** API request/response family pi-ai uses to talk to a provider. */
|
|
31
|
+
export type ApiType =
|
|
32
|
+
/** Anthropic Messages API — `system` field, content-block response. */
|
|
33
|
+
"anthropic-messages"
|
|
34
|
+
/** OpenAI Chat Completions API — `messages`, `choices[0].message.content`. */
|
|
35
|
+
| "openai-completions";
|
|
36
|
+
/**
|
|
37
|
+
* Metadata for one wizard-able provider. The request-building differences
|
|
38
|
+
* between OpenAI-completions-family providers (maxTokensField name,
|
|
39
|
+
* extra headers, nested-model-id quirk) are small, so they live as optional
|
|
40
|
+
* fields here rather than separate adapter objects.
|
|
41
|
+
*/
|
|
42
|
+
export interface ProviderSpec {
|
|
43
|
+
/** pi-ai model-spec prefix — the part before `/` in `provider/model`. */
|
|
44
|
+
readonly prefix: string;
|
|
45
|
+
/** Display name (shown in the wizard). */
|
|
46
|
+
readonly displayName: string;
|
|
47
|
+
/** Env var that carries the API key (also the env var forwarded into the sandbox). */
|
|
48
|
+
readonly envKey: string;
|
|
49
|
+
/** API base URL — `chat/completions` or `messages` is appended per `api`. */
|
|
50
|
+
readonly baseUrl: string;
|
|
51
|
+
/** API request/response family. */
|
|
52
|
+
readonly api: ApiType;
|
|
53
|
+
/** Egress allowlist host — apex (matches all subdomains) or specific host. */
|
|
54
|
+
readonly host: string;
|
|
55
|
+
/** Small/fast model id used by the screener + classifier cheap helper. */
|
|
56
|
+
readonly fastModel: string;
|
|
57
|
+
/** Canonical primary model id (used as the wizard placeholder). */
|
|
58
|
+
readonly sampleModel: string;
|
|
59
|
+
/** OpenAI-completions only: body field for the token cap. Default `max_tokens`. */
|
|
60
|
+
readonly maxTokensField?: "max_tokens" | "max_completion_tokens";
|
|
61
|
+
/** OpenAI-completions only: keep the nested `vendor/model` tail verbatim (OpenRouter). */
|
|
62
|
+
readonly preserveNestedModelId?: boolean;
|
|
63
|
+
/** OpenAI-completions only: extra request headers (OpenRouter's referer/title). */
|
|
64
|
+
readonly extraHeaders?: Readonly<Record<string, string>>;
|
|
65
|
+
/** Optional wizard hint for the API-key prefix (used in placeholder + loose validation). */
|
|
66
|
+
readonly keyPrefix?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The registry. Order matters — `defaultFastModel()` in `llm.ts` selects
|
|
70
|
+
* a fast model by iterating this list and picking the first provider
|
|
71
|
+
* whose env var is present. Always keep Anthropic first (best raw-latency
|
|
72
|
+
* cheap helper) then OpenAI then OpenRouter, then the rest grouped by
|
|
73
|
+
* families so the array is easy to scan.
|
|
74
|
+
*/
|
|
75
|
+
export declare const PROVIDERS: readonly ProviderSpec[];
|
|
76
|
+
export declare function providerByPrefix(prefix: string): ProviderSpec | undefined;
|
|
77
|
+
export declare function providerByEnvKey(envKey: string): ProviderSpec | undefined;
|
|
78
|
+
/** All the env var names a harness must forward to reach every registered provider. */
|
|
79
|
+
export declare const PROVIDER_ENV_KEYS: readonly string[];
|
|
80
|
+
/** All the hosts the sandbox egress firewall must allowlist to reach these providers. */
|
|
81
|
+
export declare const PROVIDER_HOSTS: readonly string[];
|
|
82
|
+
/**
|
|
83
|
+
* Default model spec shipped with the harness. Keep aligned with the
|
|
84
|
+
* `OPENCODE_MODEL` default in `config/default.yaml` and the wizard's
|
|
85
|
+
* initial provider selection.
|
|
86
|
+
*/
|
|
87
|
+
export declare const DEFAULT_PROVIDER = "anthropic";
|
|
88
|
+
export declare const DEFAULT_MODEL = "anthropic/claude-sonnet-4-6";
|
|
89
|
+
/**
|
|
90
|
+
* OAuth (subscription-login) providers — the ones the API-key registry above
|
|
91
|
+
* deliberately excludes. These don't authenticate with a static key env var;
|
|
92
|
+
* a user logs in once (`lastlight oauth login <id>`) and pi-ai manages the
|
|
93
|
+
* token. Two consumption seams with different reach:
|
|
94
|
+
*
|
|
95
|
+
* - **chat** (in-process pi-ai) supports ALL of these — the chat runner
|
|
96
|
+
* passes the resolved token as the per-call `apiKey`.
|
|
97
|
+
* - **sandbox** (agentic-pi) resolves creds from ENV only, so a provider is
|
|
98
|
+
* sandbox-usable ONLY if `sandboxEnvVar` is set (the env var pi-ai reads
|
|
99
|
+
* inside the sandbox). Codex has none → chat-only.
|
|
100
|
+
*
|
|
101
|
+
* Egress note: chat is in-process (not behind the sandbox firewall), and the
|
|
102
|
+
* one sandbox-capable OAuth provider whose models we ship (`anthropic`) reuses
|
|
103
|
+
* the `anthropic.com` host already in the API-key registry — so no OAuth host
|
|
104
|
+
* is added to the sandbox allowlist here.
|
|
105
|
+
*/
|
|
106
|
+
export interface OAuthProviderSpec {
|
|
107
|
+
/** pi-ai OAuth provider id — also `lastlight oauth login <id>`. */
|
|
108
|
+
readonly id: string;
|
|
109
|
+
/** Display name (wizard + CLI). */
|
|
110
|
+
readonly displayName: string;
|
|
111
|
+
/** pi-ai model-spec prefix (`provider` in `provider/model`) for its models. */
|
|
112
|
+
readonly modelPrefix: string;
|
|
113
|
+
/** Representative model spec — wizard placeholder / docs hint. */
|
|
114
|
+
readonly sampleModel: string;
|
|
115
|
+
/**
|
|
116
|
+
* Env var pi-ai reads for this provider's OAuth token inside a sandbox, or
|
|
117
|
+
* `null` when there's no env route (⇒ chat-only; cannot run sandbox phases).
|
|
118
|
+
*/
|
|
119
|
+
readonly sandboxEnvVar: string | null;
|
|
120
|
+
/** True when login is mandatory (no API-key fallback). */
|
|
121
|
+
readonly oauthOnly: boolean;
|
|
122
|
+
}
|
|
123
|
+
export declare const OAUTH_PROVIDERS: readonly OAuthProviderSpec[];
|
|
124
|
+
export declare function oauthProviderByModelPrefix(prefix: string): OAuthProviderSpec | undefined;
|
|
125
|
+
export declare function oauthProviderById(id: string): OAuthProviderSpec | undefined;
|