lastlight 0.10.1 → 0.10.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 +34 -9
- package/deploy/.env.production.example +9 -1
- package/dist/cli/setup.d.ts +13 -14
- package/dist/cli/setup.js +84 -92
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/overlay-bootstrap.js +4 -1
- package/dist/config/overlay-bootstrap.js.map +1 -1
- package/dist/engine/agent-executor.js +10 -9
- package/dist/engine/agent-executor.js.map +1 -1
- package/dist/engine/llm.d.ts +44 -21
- package/dist/engine/llm.js +199 -164
- package/dist/engine/llm.js.map +1 -1
- package/dist/providers.d.ts +88 -0
- package/dist/providers.js +245 -0
- package/dist/providers.js.map +1 -0
- package/dist/sandbox/egress-allowlist.d.ts +5 -0
- package/dist/sandbox/egress-allowlist.js +7 -7
- package/dist/sandbox/egress-allowlist.js.map +1 -1
- package/dist/workflows/simple.js +9 -1
- package/dist/workflows/simple.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lastlight/.claude-plugin/plugin.json +1 -1
package/dist/engine/llm.d.ts
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thin one-shot LLM chat helper used by the prompt-injection screener and the
|
|
3
|
-
* intent classifier.
|
|
4
|
-
*
|
|
3
|
+
* intent classifier. Powered by `src/providers.ts` — that registry is the
|
|
4
|
+
* single source of truth for which providers this helper can call.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The screener + classifier are deliberately small and explicit. They need a
|
|
7
|
+
* cheap/fast model on the SAME provider the user picked (or any registered
|
|
8
|
+
* provider whose key is present) so the agent's primary model and the cheap
|
|
9
|
+
* helper share one footprint. We support two request families:
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* `openai-completions` — body `{ model, messages, <maxTokensField> }`,
|
|
12
|
+
* response `choices[0].message.content`. Covers
|
|
13
|
+
* OpenAI, OpenRouter, Groq, Cerebras, xAI,
|
|
14
|
+
* Hugging Face, Moonshot, NVIDIA, Fireworks,
|
|
15
|
+
* Together, DeepSeek, Z.AI, and Google/Mistral
|
|
16
|
+
* via their OpenAI-compatible endpoints.
|
|
17
|
+
* `anthropic-messages` — `system` field + content-block response.
|
|
18
|
+
* Covers Anthropic, Kimi for Coding, MiniMax.
|
|
19
|
+
*
|
|
20
|
+
* Scope: this helper supports the providers listed in `src/providers.ts`.
|
|
21
|
+
* Workflow phases themselves are provider-agnostic (whatever pi-ai
|
|
22
|
+
* supports); only the screener/classifier path is constrained here.
|
|
13
23
|
*/
|
|
24
|
+
import { type ApiType } from "../providers.js";
|
|
14
25
|
export type ChatRole = "system" | "user" | "assistant";
|
|
15
26
|
export interface ChatMessage {
|
|
16
27
|
role: ChatRole;
|
|
@@ -24,24 +35,36 @@ export interface ChatOptions {
|
|
|
24
35
|
}
|
|
25
36
|
export type CallLlmOptions = ChatOptions;
|
|
26
37
|
export type ChatFunction = (model: string, messages: ChatMessage[], opts?: ChatOptions) => Promise<string>;
|
|
27
|
-
|
|
38
|
+
/** Resolved routing for a `provider/model` spec. */
|
|
39
|
+
export interface ResolvedProvider {
|
|
40
|
+
/** Pi-ai-style provider prefix (`anthropic`, `openai`, `openrouter`, …). */
|
|
41
|
+
provider: string;
|
|
42
|
+
/** Model id the upstream API expects (prefix stripped, nested tail preserved if applicable). */
|
|
43
|
+
modelId: string;
|
|
44
|
+
/** Which request family to use. */
|
|
45
|
+
api: ApiType;
|
|
46
|
+
}
|
|
28
47
|
/**
|
|
29
|
-
*
|
|
48
|
+
* Resolve a `provider/model` spec (or a bare model id) into a routing
|
|
49
|
+
* decision. Prefers an explicit `<prefix>/<model>` over name-inference.
|
|
30
50
|
*
|
|
31
|
-
* @throws if the
|
|
51
|
+
* @throws if the prefix is present but not registered — silently
|
|
52
|
+
* mis-routing a `xai/grok-4` to OpenAI would bill the wrong account and
|
|
53
|
+
* fail at request time with a confusing error, so we surface it early.
|
|
32
54
|
*/
|
|
33
|
-
export declare function
|
|
34
|
-
/** Compatibility wrapper for older single-turn call sites. */
|
|
35
|
-
export declare function callLlm(model: string, systemPrompt: string, userPrompt: string, opts?: CallLlmOptions): Promise<string>;
|
|
55
|
+
export declare function resolveProvider(model: string): ResolvedProvider;
|
|
36
56
|
/**
|
|
37
57
|
* Resolve the model id for a tiny one-shot helper call (classifier / screener).
|
|
38
58
|
* Prefers explicit per-task overrides from OPENCODE_MODELS, then the first
|
|
39
|
-
* configured provider in PROVIDERS order
|
|
40
|
-
*
|
|
59
|
+
* configured provider in `PROVIDERS` order (registry order: Anthropic first,
|
|
60
|
+
* then OpenAI, OpenRouter, then the rest — see `src/providers.ts`).
|
|
41
61
|
*/
|
|
42
62
|
export declare function defaultFastModel(taskType?: string): string;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Make a provider-agnostic chat call and return the final assistant text.
|
|
65
|
+
*
|
|
66
|
+
* @throws if the relevant API key isn't set or the upstream returns non-2xx
|
|
67
|
+
*/
|
|
68
|
+
export declare function chat(model: string, messages: ChatMessage[], opts?: ChatOptions): Promise<string>;
|
|
69
|
+
/** Compatibility wrapper for older single-turn call sites. */
|
|
70
|
+
export declare function callLlm(model: string, systemPrompt: string, userPrompt: string, opts?: CallLlmOptions): Promise<string>;
|
package/dist/engine/llm.js
CHANGED
|
@@ -1,126 +1,223 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thin one-shot LLM chat helper used by the prompt-injection screener and the
|
|
3
|
-
* intent classifier.
|
|
4
|
-
*
|
|
3
|
+
* intent classifier. Powered by `src/providers.ts` — that registry is the
|
|
4
|
+
* single source of truth for which providers this helper can call.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The screener + classifier are deliberately small and explicit. They need a
|
|
7
|
+
* cheap/fast model on the SAME provider the user picked (or any registered
|
|
8
|
+
* provider whose key is present) so the agent's primary model and the cheap
|
|
9
|
+
* helper share one footprint. We support two request families:
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* `openai-completions` — body `{ model, messages, <maxTokensField> }`,
|
|
12
|
+
* response `choices[0].message.content`. Covers
|
|
13
|
+
* OpenAI, OpenRouter, Groq, Cerebras, xAI,
|
|
14
|
+
* Hugging Face, Moonshot, NVIDIA, Fireworks,
|
|
15
|
+
* Together, DeepSeek, Z.AI, and Google/Mistral
|
|
16
|
+
* via their OpenAI-compatible endpoints.
|
|
17
|
+
* `anthropic-messages` — `system` field + content-block response.
|
|
18
|
+
* Covers Anthropic, Kimi for Coding, MiniMax.
|
|
19
|
+
*
|
|
20
|
+
* Scope: this helper supports the providers listed in `src/providers.ts`.
|
|
21
|
+
* Workflow phases themselves are provider-agnostic (whatever pi-ai
|
|
22
|
+
* supports); only the screener/classifier path is constrained here.
|
|
23
|
+
*/
|
|
24
|
+
import { PROVIDERS, providerByPrefix } from "../providers.js";
|
|
25
|
+
// ── Resolvers (exported for unit tests) ──────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a `provider/model` spec (or a bare model id) into a routing
|
|
28
|
+
* decision. Prefers an explicit `<prefix>/<model>` over name-inference.
|
|
29
|
+
*
|
|
30
|
+
* @throws if the prefix is present but not registered — silently
|
|
31
|
+
* mis-routing a `xai/grok-4` to OpenAI would bill the wrong account and
|
|
32
|
+
* fail at request time with a confusing error, so we surface it early.
|
|
13
33
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
prefix
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
buildRequest: ({ modelId, messages, opts, apiKey, signal }) => {
|
|
22
|
-
const system = messages
|
|
23
|
-
.filter((message) => message.role === "system")
|
|
24
|
-
.map((message) => message.content)
|
|
25
|
-
.join("\n");
|
|
34
|
+
export function resolveProvider(model) {
|
|
35
|
+
const slash = model.indexOf("/");
|
|
36
|
+
if (slash > 0) {
|
|
37
|
+
const prefix = model.slice(0, slash).toLowerCase();
|
|
38
|
+
const tail = model.slice(slash + 1);
|
|
39
|
+
const spec = providerByPrefix(prefix);
|
|
40
|
+
if (spec) {
|
|
26
41
|
return {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
signal,
|
|
31
|
-
headers: {
|
|
32
|
-
"content-type": "application/json",
|
|
33
|
-
"x-api-key": apiKey,
|
|
34
|
-
"anthropic-version": "2023-06-01",
|
|
35
|
-
},
|
|
36
|
-
body: JSON.stringify({
|
|
37
|
-
model: modelId,
|
|
38
|
-
max_tokens: opts.maxTokens ?? 256,
|
|
39
|
-
...(system ? { system } : {}),
|
|
40
|
-
messages: messages
|
|
41
|
-
.filter((message) => message.role !== "system")
|
|
42
|
-
.map((message) => ({ role: message.role, content: message.content })),
|
|
43
|
-
}),
|
|
44
|
-
},
|
|
42
|
+
provider: spec.prefix,
|
|
43
|
+
modelId: tail,
|
|
44
|
+
api: spec.api,
|
|
45
45
|
};
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`llm helper: unsupported provider prefix "${prefix}" ` +
|
|
48
|
+
`(registered: ${PROVIDERS.map((p) => p.prefix).join(", ")})`);
|
|
49
|
+
}
|
|
50
|
+
// Bare model id — fall back to inference by name. OpenRouter's nested
|
|
51
|
+
// `vendor/model` ids are handled above (its prefix is "openrouter").
|
|
52
|
+
if (/^claude/i.test(model)) {
|
|
53
|
+
return { provider: "anthropic", modelId: model, api: "anthropic-messages" };
|
|
54
|
+
}
|
|
55
|
+
if (/^gpt/i.test(model) || /^o\d/.test(model)) {
|
|
56
|
+
return { provider: "openai", modelId: model, api: "openai-completions" };
|
|
57
|
+
}
|
|
58
|
+
// Anything else: assume OpenAI-completions shape against an OpenAI endpoint.
|
|
59
|
+
return { provider: "openai", modelId: model, api: "openai-completions" };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the model id for a tiny one-shot helper call (classifier / screener).
|
|
63
|
+
* Prefers explicit per-task overrides from OPENCODE_MODELS, then the first
|
|
64
|
+
* configured provider in `PROVIDERS` order (registry order: Anthropic first,
|
|
65
|
+
* then OpenAI, OpenRouter, then the rest — see `src/providers.ts`).
|
|
66
|
+
*/
|
|
67
|
+
export function defaultFastModel(taskType) {
|
|
68
|
+
if (taskType) {
|
|
69
|
+
const override = readOpencodeModelOverride(taskType);
|
|
70
|
+
if (override)
|
|
71
|
+
return override;
|
|
72
|
+
}
|
|
73
|
+
for (const spec of PROVIDERS) {
|
|
74
|
+
if (process.env[spec.envKey])
|
|
75
|
+
return `${spec.prefix}/${spec.fastModel}`;
|
|
76
|
+
}
|
|
77
|
+
// No keys set — fall back to the OpenAI default; chat() will then throw a
|
|
78
|
+
// clear "OPENAI_API_KEY not set" error at call time.
|
|
79
|
+
const openaiSpec = providerByPrefix("openai");
|
|
80
|
+
return `${openaiSpec.prefix}/${openaiSpec.fastModel}`;
|
|
81
|
+
}
|
|
82
|
+
function readOpencodeModelOverride(taskType) {
|
|
83
|
+
const raw = process.env.OPENCODE_MODELS;
|
|
84
|
+
if (!raw)
|
|
85
|
+
return undefined;
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(raw);
|
|
88
|
+
const v = parsed?.[taskType];
|
|
89
|
+
return typeof v === "string" ? v : undefined;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// ── Request builders ────────────────────────────────────────────────────────
|
|
96
|
+
function apiKeyFor(spec) {
|
|
97
|
+
const apiKey = process.env[spec.envKey];
|
|
98
|
+
if (!apiKey)
|
|
99
|
+
throw new Error(`${spec.envKey} not set`);
|
|
100
|
+
return apiKey;
|
|
101
|
+
}
|
|
102
|
+
/** Append `/messages` (Anthropic-style) taking baseUrl shape into account. */
|
|
103
|
+
function anthropicUrl(spec) {
|
|
104
|
+
return spec.baseUrl.endsWith("/v1")
|
|
105
|
+
? `${spec.baseUrl}/messages`
|
|
106
|
+
: `${spec.baseUrl}/v1/messages`;
|
|
107
|
+
}
|
|
108
|
+
/** Append `/chat/completions` (OpenAI-style). */
|
|
109
|
+
function openaiUrl(spec) {
|
|
110
|
+
return `${spec.baseUrl}/chat/completions`;
|
|
111
|
+
}
|
|
112
|
+
function buildRequest(spec, modelId, messages, opts, apiKey, signal) {
|
|
113
|
+
if (spec.api === "anthropic-messages") {
|
|
114
|
+
return buildAnthropicMessages({ spec, modelId, messages, opts, apiKey, signal });
|
|
115
|
+
}
|
|
116
|
+
return buildOpenaiCompletions({ spec, modelId, messages, opts, apiKey, signal });
|
|
117
|
+
}
|
|
118
|
+
function buildAnthropicMessages(args) {
|
|
119
|
+
const { spec, modelId, messages, opts, apiKey, signal } = args;
|
|
120
|
+
const system = messages
|
|
121
|
+
.filter((m) => m.role === "system")
|
|
122
|
+
.map((m) => m.content)
|
|
123
|
+
.join("\n");
|
|
124
|
+
return {
|
|
125
|
+
url: anthropicUrl(spec),
|
|
126
|
+
init: {
|
|
127
|
+
method: "POST",
|
|
128
|
+
signal,
|
|
129
|
+
headers: {
|
|
130
|
+
"content-type": "application/json",
|
|
131
|
+
"x-api-key": apiKey,
|
|
132
|
+
"anthropic-version": "2023-06-01",
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
model: modelId,
|
|
136
|
+
max_tokens: opts.maxTokens ?? 256,
|
|
137
|
+
...(system ? { system } : {}),
|
|
138
|
+
messages: messages
|
|
139
|
+
.filter((m) => m.role !== "system")
|
|
140
|
+
.map((m) => ({ role: m.role, content: m.content })),
|
|
141
|
+
}),
|
|
46
142
|
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function buildOpenaiCompletions(args) {
|
|
146
|
+
const { spec, modelId, messages, opts, apiKey, signal } = args;
|
|
147
|
+
// OpenAI's `max_completion_tokens` (newer family) vs. the cross-provider
|
|
148
|
+
// `max_tokens`. Almost every OpenAI-completions provider accepts `max_tokens`;
|
|
149
|
+
// OpenAI itself prefers `max_completion_tokens` so we surface that as a
|
|
150
|
+
// per-provider override in the registry.
|
|
151
|
+
const maxField = spec.maxTokensField ?? "max_tokens";
|
|
152
|
+
const headers = {
|
|
153
|
+
"content-type": "application/json",
|
|
154
|
+
authorization: `Bearer ${apiKey}`,
|
|
155
|
+
};
|
|
156
|
+
if (spec.extraHeaders) {
|
|
157
|
+
for (const [k, v] of Object.entries(spec.extraHeaders))
|
|
158
|
+
headers[k] = v;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
url: openaiUrl(spec),
|
|
162
|
+
init: {
|
|
163
|
+
method: "POST",
|
|
164
|
+
signal,
|
|
165
|
+
headers,
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
model: modelId,
|
|
168
|
+
[maxField]: opts.maxTokens ?? 256,
|
|
169
|
+
messages,
|
|
170
|
+
}),
|
|
53
171
|
},
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
max_completion_tokens: opts.maxTokens ?? 256,
|
|
73
|
-
messages,
|
|
74
|
-
}),
|
|
75
|
-
},
|
|
76
|
-
}),
|
|
77
|
-
extractText: (data) => data.choices?.[0]?.message?.content ?? "",
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: "openrouter",
|
|
81
|
-
prefix: "openrouter",
|
|
82
|
-
envKey: "OPENROUTER_API_KEY",
|
|
83
|
-
defaultModel: "openrouter/google/gemini-2.5-flash",
|
|
84
|
-
resolveModelId: () => undefined,
|
|
85
|
-
buildRequest: ({ modelId, messages, opts, apiKey, signal }) => ({
|
|
86
|
-
url: "https://openrouter.ai/api/v1/chat/completions",
|
|
87
|
-
init: {
|
|
88
|
-
method: "POST",
|
|
89
|
-
signal,
|
|
90
|
-
headers: {
|
|
91
|
-
"content-type": "application/json",
|
|
92
|
-
authorization: `Bearer ${apiKey}`,
|
|
93
|
-
"HTTP-Referer": "https://github.com/cliftonc/lastlight",
|
|
94
|
-
"X-Title": "Last Light",
|
|
95
|
-
},
|
|
96
|
-
body: JSON.stringify({
|
|
97
|
-
model: modelId,
|
|
98
|
-
max_tokens: opts.maxTokens ?? 256,
|
|
99
|
-
messages,
|
|
100
|
-
}),
|
|
101
|
-
},
|
|
102
|
-
}),
|
|
103
|
-
extractText: (data) => data.choices?.[0]?.message?.content ?? "",
|
|
104
|
-
},
|
|
105
|
-
];
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// ── Response extractors ─────────────────────────────────────────────────────
|
|
175
|
+
function extractAnthropicText(data) {
|
|
176
|
+
const content = data.content ?? [];
|
|
177
|
+
return content
|
|
178
|
+
.filter((block) => block.type === "text" && typeof block.text === "string")
|
|
179
|
+
.map((block) => block.text)
|
|
180
|
+
.join("");
|
|
181
|
+
}
|
|
182
|
+
function extractOpenaiText(data) {
|
|
183
|
+
return (data.choices?.[0]?.message?.content ??
|
|
184
|
+
"");
|
|
185
|
+
}
|
|
186
|
+
function extractText(spec, data) {
|
|
187
|
+
return spec.api === "anthropic-messages" ? extractAnthropicText(data) : extractOpenaiText(data);
|
|
188
|
+
}
|
|
189
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
106
190
|
/**
|
|
107
191
|
* Make a provider-agnostic chat call and return the final assistant text.
|
|
108
192
|
*
|
|
109
193
|
* @throws if the relevant API key isn't set or the upstream returns non-2xx
|
|
110
194
|
*/
|
|
111
195
|
export async function chat(model, messages, opts = {}) {
|
|
112
|
-
const { provider, modelId } = resolveProvider(model);
|
|
113
|
-
const
|
|
196
|
+
const { provider, modelId, api } = resolveProvider(model);
|
|
197
|
+
const spec = providerByPrefix(provider);
|
|
198
|
+
// For bare-id inference where the prefix isn't in the registry (the
|
|
199
|
+
// generic "assume OpenAI" fallback), synthesize a pseudo-spec so the
|
|
200
|
+
// OpenAI-completions builder still runs.
|
|
201
|
+
const resolvedSpec = spec ?? {
|
|
202
|
+
prefix: provider,
|
|
203
|
+
displayName: provider,
|
|
204
|
+
envKey: "OPENAI_API_KEY",
|
|
205
|
+
baseUrl: "https://api.openai.com/v1",
|
|
206
|
+
api,
|
|
207
|
+
host: "openai.com",
|
|
208
|
+
fastModel: modelId,
|
|
209
|
+
sampleModel: modelId,
|
|
210
|
+
};
|
|
114
211
|
return withRetry(async (signal) => {
|
|
115
|
-
const apiKey = apiKeyFor(
|
|
116
|
-
const { url, init } =
|
|
212
|
+
const apiKey = apiKeyFor(resolvedSpec);
|
|
213
|
+
const { url, init } = buildRequest(resolvedSpec, modelId, messages, opts, apiKey, signal);
|
|
117
214
|
const res = await fetch(url, init);
|
|
118
215
|
if (!res.ok) {
|
|
119
216
|
const text = await res.text().catch(() => "");
|
|
120
|
-
throw new Error(`${
|
|
217
|
+
throw new Error(`${resolvedSpec.prefix} api ${res.status}: ${text}`);
|
|
121
218
|
}
|
|
122
219
|
const data = await res.json();
|
|
123
|
-
return
|
|
220
|
+
return extractText(resolvedSpec, data);
|
|
124
221
|
}, opts.timeoutMs ?? 30_000);
|
|
125
222
|
}
|
|
126
223
|
/** Compatibility wrapper for older single-turn call sites. */
|
|
@@ -156,66 +253,4 @@ async function withRetry(call, timeoutMs) {
|
|
|
156
253
|
// Unreachable — loop either returns or throws.
|
|
157
254
|
throw new Error("chat: retry loop fell through");
|
|
158
255
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Resolve the model id for a tiny one-shot helper call (classifier / screener).
|
|
161
|
-
* Prefers explicit per-task overrides from OPENCODE_MODELS, then the first
|
|
162
|
-
* configured provider in PROVIDERS order. That registry is the single source
|
|
163
|
-
* of precedence (currently anthropic > openai > openrouter).
|
|
164
|
-
*/
|
|
165
|
-
export function defaultFastModel(taskType) {
|
|
166
|
-
if (taskType) {
|
|
167
|
-
const override = readOpencodeModelOverride(taskType);
|
|
168
|
-
if (override)
|
|
169
|
-
return override;
|
|
170
|
-
}
|
|
171
|
-
for (const adapter of PROVIDERS) {
|
|
172
|
-
if (process.env[adapter.envKey])
|
|
173
|
-
return adapter.defaultModel;
|
|
174
|
-
}
|
|
175
|
-
// No keys set — fall back to the OpenAI default; chat() will then throw a
|
|
176
|
-
// clear "OPENAI_API_KEY not set" error at call time.
|
|
177
|
-
return adapterFor("openai").defaultModel;
|
|
178
|
-
}
|
|
179
|
-
function readOpencodeModelOverride(taskType) {
|
|
180
|
-
const raw = process.env.OPENCODE_MODELS;
|
|
181
|
-
if (!raw)
|
|
182
|
-
return undefined;
|
|
183
|
-
try {
|
|
184
|
-
const parsed = JSON.parse(raw);
|
|
185
|
-
const v = parsed?.[taskType];
|
|
186
|
-
return typeof v === "string" ? v : undefined;
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
return undefined;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
export function resolveProvider(model) {
|
|
193
|
-
const slash = model.indexOf("/");
|
|
194
|
-
if (slash > 0) {
|
|
195
|
-
const head = model.slice(0, slash).toLowerCase();
|
|
196
|
-
const tail = model.slice(slash + 1);
|
|
197
|
-
const adapter = PROVIDERS.find((candidate) => candidate.prefix === head);
|
|
198
|
-
if (adapter)
|
|
199
|
-
return { provider: adapter.name, modelId: tail };
|
|
200
|
-
throw new Error(`llm helper: unsupported provider prefix "${head}" (only "anthropic", "openai", and "openrouter" are supported)`);
|
|
201
|
-
}
|
|
202
|
-
for (const adapter of PROVIDERS) {
|
|
203
|
-
const modelId = adapter.resolveModelId(model);
|
|
204
|
-
if (modelId)
|
|
205
|
-
return { provider: adapter.name, modelId };
|
|
206
|
-
}
|
|
207
|
-
return { provider: "openai", modelId: model };
|
|
208
|
-
}
|
|
209
|
-
function adapterFor(provider) {
|
|
210
|
-
const adapter = PROVIDERS.find((candidate) => candidate.name === provider);
|
|
211
|
-
if (!adapter)
|
|
212
|
-
throw new Error(`llm helper: unsupported provider "${provider}"`);
|
|
213
|
-
return adapter;
|
|
214
|
-
}
|
|
215
|
-
function apiKeyFor(adapter) {
|
|
216
|
-
const apiKey = process.env[adapter.envKey];
|
|
217
|
-
if (!apiKey)
|
|
218
|
-
throw new Error(`${adapter.envKey} not set`);
|
|
219
|
-
return apiKey;
|
|
220
|
-
}
|
|
221
256
|
//# sourceMappingURL=llm.js.map
|
package/dist/engine/llm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/engine/llm.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/engine/llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
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";
|