@almightygpt/core 0.9.1 → 0.10.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/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +3 -1
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/defaults.d.ts +34 -0
- package/dist/adapters/defaults.d.ts.map +1 -0
- package/dist/adapters/defaults.js +41 -0
- package/dist/adapters/defaults.js.map +1 -0
- package/dist/adapters/factory.d.ts +12 -0
- package/dist/adapters/factory.d.ts.map +1 -0
- package/dist/adapters/factory.js +40 -0
- package/dist/adapters/factory.js.map +1 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +3 -1
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +3 -1
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/keychain.d.ts +11 -1
- package/dist/auth/keychain.d.ts.map +1 -1
- package/dist/auth/keychain.js +13 -4
- package/dist/auth/keychain.js.map +1 -1
- package/dist/auth/resolver.d.ts.map +1 -1
- package/dist/auth/resolver.js +19 -3
- package/dist/auth/resolver.js.map +1 -1
- package/dist/auth/types.d.ts +18 -5
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js +8 -6
- package/dist/auth/types.js.map +1 -1
- package/dist/auth/validator.d.ts +23 -3
- package/dist/auth/validator.d.ts.map +1 -1
- package/dist/auth/validator.js +126 -21
- package/dist/auth/validator.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/plan/run-plan-review.d.ts +40 -0
- package/dist/plan/run-plan-review.d.ts.map +1 -0
- package/dist/plan/run-plan-review.js +224 -0
- package/dist/plan/run-plan-review.js.map +1 -0
- package/dist/plan/run-plan.d.ts +42 -0
- package/dist/plan/run-plan.d.ts.map +1 -0
- package/dist/plan/run-plan.js +193 -0
- package/dist/plan/run-plan.js.map +1 -0
- package/dist/runs/types.d.ts +1 -1
- package/dist/runs/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude.ts +3 -1
- package/src/adapters/defaults.ts +44 -0
- package/src/adapters/factory.ts +45 -0
- package/src/adapters/gemini.ts +3 -1
- package/src/adapters/openai.ts +3 -1
- package/src/auth/keychain.ts +20 -6
- package/src/auth/resolver.ts +23 -3
- package/src/auth/types.ts +27 -8
- package/src/auth/validator.ts +149 -25
- package/src/index.ts +13 -1
- package/src/plan/run-plan-review.ts +302 -0
- package/src/plan/run-plan.ts +247 -0
- package/src/runs/types.ts +3 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `almightygpt plan` — Worker AI reads a free-text requirement +
|
|
3
|
+
* project memory, produces a structured plan markdown.
|
|
4
|
+
*
|
|
5
|
+
* Distinct from the review pipeline because the INPUT is a
|
|
6
|
+
* requirement (a sentence or paragraph from the user describing
|
|
7
|
+
* what they want), not a git diff. Output is a plan doc with
|
|
8
|
+
* required sections — same shape on every run so the Reviewer
|
|
9
|
+
* downstream knows what to expect.
|
|
10
|
+
*
|
|
11
|
+
* Plans land at `docs/<worker>-plans/<topic>.md`. The Reviewer step
|
|
12
|
+
* then runs against this plan via the `review --plan <file>` mode
|
|
13
|
+
* (see run-plan-review.ts).
|
|
14
|
+
*/
|
|
15
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { loadConfig } from "../config/load.js";
|
|
18
|
+
import { makeAdapter } from "../adapters/factory.js";
|
|
19
|
+
import { AdapterError } from "../adapters/types.js";
|
|
20
|
+
import { assembleMemory } from "../review/memory.js";
|
|
21
|
+
import { createRunFolder, writeRunMetadata, writeRunInput, writeAgentOutput, collectGitContext, } from "../runs/folder.js";
|
|
22
|
+
import { assertSafeToWrite } from "../git/status.js";
|
|
23
|
+
const PLAN_SYSTEM_FRAMING = [
|
|
24
|
+
"You are the Worker AI in an AlmightyGPT Plan stage.",
|
|
25
|
+
"",
|
|
26
|
+
"WHAT YOU ARE DOING: turning a free-text requirement (the user message",
|
|
27
|
+
"below) into a structured plan markdown that a Reviewer AI will then",
|
|
28
|
+
"critique. You are NOT writing code — only the plan. The human will",
|
|
29
|
+
"approve / reject / refine the plan, and only then will implementation",
|
|
30
|
+
"happen (in a separate tool).",
|
|
31
|
+
"",
|
|
32
|
+
"WHAT THE ORCHESTRATOR OWNS (do NOT include these in your response):",
|
|
33
|
+
" - The H1 title (the orchestrator prepends `# Plan: <topic>`).",
|
|
34
|
+
" - The header block with model / tokens / cost.",
|
|
35
|
+
"",
|
|
36
|
+
"WHAT YOU MUST PRODUCE — emit exactly these sections in this order:",
|
|
37
|
+
" ## Goal",
|
|
38
|
+
" ## Affected modules / surfaces",
|
|
39
|
+
" ## Step-by-step approach",
|
|
40
|
+
" ## Risks",
|
|
41
|
+
" ## Test plan",
|
|
42
|
+
" ## Open questions",
|
|
43
|
+
"",
|
|
44
|
+
"STYLE:",
|
|
45
|
+
" - Be concrete. Name specific files, classes, functions, env vars.",
|
|
46
|
+
" - Write so the Reviewer can find concrete things to critique.",
|
|
47
|
+
" - Open questions are not a weakness; they're the things you'd ask",
|
|
48
|
+
" the human if you could. Surface them.",
|
|
49
|
+
].join("\n");
|
|
50
|
+
function buildPlanUserMessage(topic, requirement) {
|
|
51
|
+
return [
|
|
52
|
+
`# Plan request — topic: \`${topic}\``,
|
|
53
|
+
"",
|
|
54
|
+
"## Requirement (from human)",
|
|
55
|
+
"",
|
|
56
|
+
requirement.trim(),
|
|
57
|
+
"",
|
|
58
|
+
"## Your task",
|
|
59
|
+
"",
|
|
60
|
+
"Produce the plan markdown using the structure in your system prompt.",
|
|
61
|
+
"Start your response with `## Goal` (no H1, no preamble).",
|
|
62
|
+
].join("\n");
|
|
63
|
+
}
|
|
64
|
+
export async function runWorkerPlan(opts) {
|
|
65
|
+
const config = await loadConfig(opts.repoRoot);
|
|
66
|
+
const workerName = opts.worker ?? config.defaults.worker;
|
|
67
|
+
if (!workerName) {
|
|
68
|
+
throw new Error("No worker specified. Pass --worker <name> or set defaults.worker in .almightygpt/config.yaml.");
|
|
69
|
+
}
|
|
70
|
+
const agentConfig = config.agents[workerName];
|
|
71
|
+
if (!agentConfig) {
|
|
72
|
+
throw new Error(`Worker "${workerName}" not found in .almightygpt/config.yaml agents map.`);
|
|
73
|
+
}
|
|
74
|
+
if (!agentConfig.enabled) {
|
|
75
|
+
throw new Error(`Worker "${workerName}" is disabled in .almightygpt/config.yaml.`);
|
|
76
|
+
}
|
|
77
|
+
const adapter = makeAdapter(workerName, agentConfig.provider);
|
|
78
|
+
if (!(await adapter.isAvailable())) {
|
|
79
|
+
throw new AdapterError(`Adapter "${workerName}" (${agentConfig.provider}) is not available. Set the provider's API key.`, workerName);
|
|
80
|
+
}
|
|
81
|
+
// Plans land at docs/<worker>-plans/<topic>.md — symmetric with reviews.
|
|
82
|
+
const plansDir = `docs/${workerName}-plans`;
|
|
83
|
+
const planRel = `${plansDir}/${opts.topic}.md`;
|
|
84
|
+
await assertSafeToWrite(opts.repoRoot, planRel, opts.force ?? false);
|
|
85
|
+
const runFolder = await createRunFolder({
|
|
86
|
+
repoRoot: opts.repoRoot,
|
|
87
|
+
runsDir: config.runsDir,
|
|
88
|
+
topic: opts.topic,
|
|
89
|
+
type: "plan",
|
|
90
|
+
});
|
|
91
|
+
const createdAt = new Date().toISOString();
|
|
92
|
+
const memory = await assembleMemory(opts.repoRoot, agentConfig.memoryFile);
|
|
93
|
+
const systemPrompt = PLAN_SYSTEM_FRAMING + "\n\n" + memory.text;
|
|
94
|
+
const userMessage = buildPlanUserMessage(opts.topic, opts.requirement);
|
|
95
|
+
await writeRunInput(runFolder.absPath, userMessage);
|
|
96
|
+
const adapterOut = await adapter.execute({
|
|
97
|
+
role: "worker",
|
|
98
|
+
systemPrompt,
|
|
99
|
+
userMessage,
|
|
100
|
+
});
|
|
101
|
+
await writeAgentOutput(runFolder.absPath, "worker", adapterOut.content);
|
|
102
|
+
// Compose the final plan file: orchestrator-owned header + worker body.
|
|
103
|
+
const header = [
|
|
104
|
+
`# Plan: ${opts.topic}`,
|
|
105
|
+
"",
|
|
106
|
+
`> Worker: \`${workerName}\` (${adapter.provider}, ${adapterOut.modelUsed})`,
|
|
107
|
+
`> Generated: ${new Date().toISOString()}`,
|
|
108
|
+
`> Tokens: ${adapterOut.tokensIn} in / ${adapterOut.tokensOut} out`,
|
|
109
|
+
`> Cost: $${adapterOut.costUsd.toFixed(4)} USD`,
|
|
110
|
+
`> Run folder: \`${runFolder.relPath}\``,
|
|
111
|
+
"",
|
|
112
|
+
].join("\n");
|
|
113
|
+
const footer = [
|
|
114
|
+
"",
|
|
115
|
+
"---",
|
|
116
|
+
"",
|
|
117
|
+
"## How this plan was produced",
|
|
118
|
+
"",
|
|
119
|
+
`The AlmightyGPT Plan stage produced this. Worker (\`${workerName}\`)`,
|
|
120
|
+
`read the requirement above + the project's memory files.`,
|
|
121
|
+
"",
|
|
122
|
+
"**Next stage:** cross-AI review with `almightygpt review --plan",
|
|
123
|
+
`${planRel} --reviewer codex --topic ${opts.topic}\` (Reviewer critiques`,
|
|
124
|
+
"this plan), then human approval, then implementation in your",
|
|
125
|
+
"preferred coding tool.",
|
|
126
|
+
].join("\n");
|
|
127
|
+
const planContent = header + adapterOut.content.trim() + footer;
|
|
128
|
+
const planAbs = join(opts.repoRoot, planRel);
|
|
129
|
+
await mkdir(dirname(planAbs), { recursive: true });
|
|
130
|
+
await writeFile(planAbs, planContent, "utf8");
|
|
131
|
+
const gitContext = await collectGitContext(opts.repoRoot);
|
|
132
|
+
await writeRunMetadata(runFolder.absPath, {
|
|
133
|
+
id: runFolder.id,
|
|
134
|
+
type: "plan",
|
|
135
|
+
createdAt,
|
|
136
|
+
finishedAt: new Date().toISOString(),
|
|
137
|
+
workspacePath: opts.repoRoot,
|
|
138
|
+
topic: opts.topic,
|
|
139
|
+
git: gitContext,
|
|
140
|
+
input: { source: "requirement-file" },
|
|
141
|
+
agents: [
|
|
142
|
+
{
|
|
143
|
+
name: workerName,
|
|
144
|
+
role: "worker",
|
|
145
|
+
provider: agentConfig.provider,
|
|
146
|
+
enabled: true,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
adapterVersions: [],
|
|
150
|
+
status: "completed",
|
|
151
|
+
metrics: [
|
|
152
|
+
{
|
|
153
|
+
agent: workerName,
|
|
154
|
+
role: "worker",
|
|
155
|
+
provider: adapter.provider,
|
|
156
|
+
model: adapterOut.modelUsed,
|
|
157
|
+
tokensIn: adapterOut.tokensIn,
|
|
158
|
+
cachedTokensIn: adapterOut.cachedTokensIn ?? 0,
|
|
159
|
+
tokensOut: adapterOut.tokensOut,
|
|
160
|
+
costUsd: adapterOut.costUsd,
|
|
161
|
+
latencyMs: adapterOut.latencyMs,
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
totals: {
|
|
165
|
+
tokensIn: adapterOut.tokensIn,
|
|
166
|
+
tokensOut: adapterOut.tokensOut,
|
|
167
|
+
costUsd: adapterOut.costUsd,
|
|
168
|
+
latencyMs: adapterOut.latencyMs,
|
|
169
|
+
},
|
|
170
|
+
reviewPath: planRel,
|
|
171
|
+
budget: {
|
|
172
|
+
maxCostPerRunUsd: config.budget.maxCostPerRunUsd,
|
|
173
|
+
maxTokensPerRun: config.budget.maxTokensPerRun,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
planPath: planRel,
|
|
178
|
+
planBytes: planContent.length,
|
|
179
|
+
worker: workerName,
|
|
180
|
+
provider: adapter.provider,
|
|
181
|
+
modelUsed: adapterOut.modelUsed,
|
|
182
|
+
tokensIn: adapterOut.tokensIn,
|
|
183
|
+
cachedTokensIn: adapterOut.cachedTokensIn ?? 0,
|
|
184
|
+
tokensOut: adapterOut.tokensOut,
|
|
185
|
+
costUsd: adapterOut.costUsd,
|
|
186
|
+
latencyMs: adapterOut.latencyMs,
|
|
187
|
+
memorySources: memory.sources,
|
|
188
|
+
memoryMissing: memory.missing,
|
|
189
|
+
runId: runFolder.id,
|
|
190
|
+
runFolder: runFolder.relPath,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=run-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-plan.js","sourceRoot":"","sources":["../../src/plan/run-plan.ts"],"names":[],"mappings":"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"}
|
package/dist/runs/types.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - audit what was sent to which provider,
|
|
10
10
|
* - drive the future VS Code / dashboard / CLI history views.
|
|
11
11
|
*/
|
|
12
|
-
export type RunType = "review-diff" | "review-path" | "review-requirement" | "review-worker-reviewer";
|
|
12
|
+
export type RunType = "review-diff" | "review-path" | "review-requirement" | "review-worker-reviewer" | "plan" | "review-plan";
|
|
13
13
|
export type RunStatus = "running" | "completed" | "failed" | "aborted_budget";
|
|
14
14
|
export interface AgentMetrics {
|
|
15
15
|
agent: string;
|
package/dist/runs/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runs/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,OAAO,GACf,aAAa,GACb,aAAa,GACb,oBAAoB,GACpB,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runs/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/adapters/claude.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type AdapterOutput,
|
|
23
23
|
} from "./types.js";
|
|
24
24
|
import { resolveApiKey } from "../auth/resolver.js";
|
|
25
|
+
import { DEFAULT_MODELS } from "./defaults.js";
|
|
25
26
|
|
|
26
27
|
const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
|
|
27
28
|
// Claude 4.x family (current)
|
|
@@ -35,7 +36,8 @@ const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
|
|
|
35
36
|
"claude-3-opus": { input: 15.0, output: 75.0 },
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
// Default lives in adapters/defaults.ts so validator + adapter can't drift.
|
|
40
|
+
const DEFAULT_MODEL = DEFAULT_MODELS.anthropic;
|
|
39
41
|
|
|
40
42
|
export interface ClaudeAdapterOptions {
|
|
41
43
|
apiKey?: string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for each adapter's default model.
|
|
3
|
+
*
|
|
4
|
+
* Per Codex's v0.8 security review (P2): the validator previously
|
|
5
|
+
* hardcoded a separate `DEFAULT_VALIDATION_MODELS` constant that
|
|
6
|
+
* could drift from the adapters' defaults. We already paid the
|
|
7
|
+
* cost of one drift incident (v0.7.1, the `claude-3-5-sonnet-latest`
|
|
8
|
+
* 404 after Anthropic retired the alias) — having two drift surfaces
|
|
9
|
+
* for the same fact would have made the next one worse.
|
|
10
|
+
*
|
|
11
|
+
* Now both the adapters AND the validator import from here. Updating
|
|
12
|
+
* a default in one place updates everywhere.
|
|
13
|
+
*
|
|
14
|
+
* When you change a default model:
|
|
15
|
+
* 1. Update the value here.
|
|
16
|
+
* 2. Confirm the pricing entry in the adapter still covers the new
|
|
17
|
+
* model (`startsWith` matching means `claude-sonnet-5-X` still
|
|
18
|
+
* hits the `claude-sonnet-5` pricing entry, but a new family
|
|
19
|
+
* needs a new entry).
|
|
20
|
+
* 3. Update the comment in the relevant adapter's header block.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { ProviderId } from "../auth/types.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default model per provider used by both the adapter (when no
|
|
27
|
+
* per-call override is passed) and the auth validator (the
|
|
28
|
+
* model-level validation call).
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_MODELS: Record<ProviderId, string> = {
|
|
31
|
+
openai: "gpt-4o",
|
|
32
|
+
anthropic: "claude-sonnet-4-6",
|
|
33
|
+
google: "gemini-2.5-flash",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pretty name for each provider — used in CLI prompts, error
|
|
38
|
+
* messages, and the wizard UI.
|
|
39
|
+
*/
|
|
40
|
+
export const PROVIDER_PRETTY_NAME: Record<ProviderId, string> = {
|
|
41
|
+
openai: "OpenAI",
|
|
42
|
+
anthropic: "Anthropic",
|
|
43
|
+
google: "Google",
|
|
44
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter factory — single place to map (name, provider) → concrete
|
|
3
|
+
* Adapter instance.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from review/run-diff-review.ts during v0.10.0 (plan
|
|
6
|
+
* module) so the plan + review pipelines share the same factory and
|
|
7
|
+
* a new adapter only needs to be wired in one place.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type Adapter } from "./types.js";
|
|
11
|
+
import { MockAdapter } from "./mock.js";
|
|
12
|
+
import { OpenAIAdapter } from "./openai.js";
|
|
13
|
+
import { ClaudeAdapter } from "./claude.js";
|
|
14
|
+
import { GeminiAdapter } from "./gemini.js";
|
|
15
|
+
|
|
16
|
+
export function makeAdapter(name: string, provider: string): Adapter {
|
|
17
|
+
switch (provider) {
|
|
18
|
+
case "openai":
|
|
19
|
+
return new OpenAIAdapter(name);
|
|
20
|
+
case "anthropic":
|
|
21
|
+
return new ClaudeAdapter(name);
|
|
22
|
+
case "google":
|
|
23
|
+
return new GeminiAdapter(name);
|
|
24
|
+
case "mock":
|
|
25
|
+
return new MockAdapter();
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Provider "${provider}" not supported. ` +
|
|
29
|
+
`Use "openai", "anthropic", "google", or "mock".`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function envHintForProvider(provider: string): string {
|
|
35
|
+
switch (provider) {
|
|
36
|
+
case "openai":
|
|
37
|
+
return "Export OPENAI_API_KEY in your environment.";
|
|
38
|
+
case "anthropic":
|
|
39
|
+
return "Export ANTHROPIC_API_KEY in your environment.";
|
|
40
|
+
case "google":
|
|
41
|
+
return "Export GOOGLE_API_KEY (or GEMINI_API_KEY) in your environment.";
|
|
42
|
+
default:
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/adapters/gemini.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type AdapterOutput,
|
|
31
31
|
} from "./types.js";
|
|
32
32
|
import { resolveApiKey } from "../auth/resolver.js";
|
|
33
|
+
import { DEFAULT_MODELS } from "./defaults.js";
|
|
33
34
|
|
|
34
35
|
const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
|
|
35
36
|
"gemini-2.5-pro": { input: 1.25, output: 10.0 },
|
|
@@ -46,7 +47,8 @@ const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
|
|
|
46
47
|
// field lands in v0.3) or by constructing the adapter directly. For code
|
|
47
48
|
// review of typical-sized diffs, flash quality is sufficient — pro pays for
|
|
48
49
|
// itself only on very large diffs or complex reasoning chains.
|
|
49
|
-
|
|
50
|
+
// Default lives in adapters/defaults.ts so validator + adapter can't drift.
|
|
51
|
+
const DEFAULT_MODEL = DEFAULT_MODELS.google;
|
|
50
52
|
|
|
51
53
|
export interface GeminiAdapterOptions {
|
|
52
54
|
apiKey?: string;
|
package/src/adapters/openai.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import OpenAI from "openai";
|
|
16
16
|
import { AdapterError, type Adapter, type AdapterInput, type AdapterOutput } from "./types.js";
|
|
17
17
|
import { resolveApiKey } from "../auth/resolver.js";
|
|
18
|
+
import { DEFAULT_MODELS } from "./defaults.js";
|
|
18
19
|
|
|
19
20
|
/** USD per 1M tokens, by model. Lowercased keys. */
|
|
20
21
|
const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
|
|
@@ -24,7 +25,8 @@ const PRICING_USD_PER_1M: Record<string, { input: number; output: number }> = {
|
|
|
24
25
|
"gpt-3.5-turbo": { input: 0.5, output: 1.5 },
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
// Default lives in adapters/defaults.ts so validator + adapter can't drift.
|
|
29
|
+
const DEFAULT_MODEL = DEFAULT_MODELS.openai;
|
|
28
30
|
|
|
29
31
|
export interface OpenAIAdapterOptions {
|
|
30
32
|
/** Override the API key source. Defaults to process.env.OPENAI_API_KEY. */
|
package/src/auth/keychain.ts
CHANGED
|
@@ -20,9 +20,15 @@ import type { ProviderId } from "./types.js";
|
|
|
20
20
|
|
|
21
21
|
const KEYCHAIN_SERVICE = "almightygpt";
|
|
22
22
|
|
|
23
|
+
/** Result of a keychain `get` — distinguishes "no entry" from "read failed". */
|
|
24
|
+
export type KeychainGetResult =
|
|
25
|
+
| { kind: "found"; key: string }
|
|
26
|
+
| { kind: "absent" }
|
|
27
|
+
| { kind: "error"; message: string };
|
|
28
|
+
|
|
23
29
|
export interface KeychainAdapter {
|
|
24
30
|
available: boolean;
|
|
25
|
-
get(provider: ProviderId): Promise<
|
|
31
|
+
get(provider: ProviderId): Promise<KeychainGetResult>;
|
|
26
32
|
set(provider: ProviderId, key: string): Promise<void>;
|
|
27
33
|
remove(provider: ProviderId): Promise<boolean>;
|
|
28
34
|
/** Optional diagnostic — backend name, or "unavailable". */
|
|
@@ -68,12 +74,20 @@ async function loadKeychain(): Promise<KeychainAdapter> {
|
|
|
68
74
|
return {
|
|
69
75
|
available: true,
|
|
70
76
|
describeBackend: () => detectBackend(),
|
|
71
|
-
async get(provider) {
|
|
77
|
+
async get(provider): Promise<KeychainGetResult> {
|
|
72
78
|
try {
|
|
73
79
|
const v = entryFor(provider).getPassword();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
if (v === null || v === undefined) return { kind: "absent" };
|
|
81
|
+
return { kind: "found", key: v };
|
|
82
|
+
} catch (err) {
|
|
83
|
+
// Real read failure — do NOT collapse into "absent". Codex's
|
|
84
|
+
// v0.8 review (P2 #4): silent fallthrough hid macOS Keychain
|
|
85
|
+
// denied access, Secret Service unavailability, corrupted
|
|
86
|
+
// entries, native-binding faults. Bubble up instead.
|
|
87
|
+
return {
|
|
88
|
+
kind: "error",
|
|
89
|
+
message: err instanceof Error ? err.message : String(err),
|
|
90
|
+
};
|
|
77
91
|
}
|
|
78
92
|
},
|
|
79
93
|
async set(provider, key) {
|
|
@@ -99,7 +113,7 @@ function makeUnavailable(reason: string): KeychainAdapter {
|
|
|
99
113
|
available: false,
|
|
100
114
|
describeBackend: () => `unavailable (${reason})`,
|
|
101
115
|
async get() {
|
|
102
|
-
return
|
|
116
|
+
return { kind: "absent" };
|
|
103
117
|
},
|
|
104
118
|
async set() {
|
|
105
119
|
throw new Error(
|
package/src/auth/resolver.ts
CHANGED
|
@@ -53,10 +53,20 @@ export async function resolveApiKey(
|
|
|
53
53
|
if (!options.skipKeychain) {
|
|
54
54
|
const keychain = await getKeychain();
|
|
55
55
|
if (keychain.available) {
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
return { provider, source: "keychain", key:
|
|
56
|
+
const result = await keychain.get(provider);
|
|
57
|
+
if (result.kind === "found" && result.key.length > 0) {
|
|
58
|
+
return { provider, source: "keychain", key: result.key };
|
|
59
59
|
}
|
|
60
|
+
if (result.kind === "error") {
|
|
61
|
+
// Per Codex's v0.8 review (P2 #4) — surface as a distinct state
|
|
62
|
+
// so the user sees "stored but couldn't be read" not "missing".
|
|
63
|
+
return {
|
|
64
|
+
provider,
|
|
65
|
+
source: "keychain_error",
|
|
66
|
+
keychainError: result.message,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// kind === "absent" → fall through to "missing".
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
|
|
@@ -77,5 +87,15 @@ export async function requireApiKey(
|
|
|
77
87
|
const primaryEnv = PROVIDER_ENV_VARS[provider][0]!;
|
|
78
88
|
throw new AuthMissingError(provider, primaryEnv);
|
|
79
89
|
}
|
|
90
|
+
if (resolved.source === "keychain_error") {
|
|
91
|
+
const primaryEnv = PROVIDER_ENV_VARS[provider][0]!;
|
|
92
|
+
throw new AuthMissingError(
|
|
93
|
+
provider,
|
|
94
|
+
primaryEnv,
|
|
95
|
+
`Keychain read failed: ${resolved.keychainError ?? "unknown error"}. ` +
|
|
96
|
+
`Set ${primaryEnv} as a per-process override, or fix the keychain ` +
|
|
97
|
+
`state (the entry exists but the OS denied or failed the read).`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
80
100
|
return resolved.key!;
|
|
81
101
|
}
|
package/src/auth/types.ts
CHANGED
|
@@ -19,29 +19,48 @@
|
|
|
19
19
|
|
|
20
20
|
export type ProviderId = "openai" | "anthropic" | "google";
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Where a resolved API key came from.
|
|
24
|
+
*
|
|
25
|
+
* `keychain_error` is a distinct state from `missing` — per Codex's
|
|
26
|
+
* v0.8 security review, silently treating a keychain read failure as
|
|
27
|
+
* "no key" hides important states (denied access, daemon unavailable,
|
|
28
|
+
* corrupted entry, native binding fault). Callers can choose to fall
|
|
29
|
+
* back or surface the error, but the resolver no longer pretends the
|
|
30
|
+
* key simply doesn't exist.
|
|
31
|
+
*/
|
|
32
|
+
export type KeySource =
|
|
33
|
+
| "explicit"
|
|
34
|
+
| "env"
|
|
35
|
+
| "keychain"
|
|
36
|
+
| "keychain_error"
|
|
37
|
+
| "missing";
|
|
23
38
|
|
|
24
39
|
export interface KeyResolution {
|
|
25
40
|
provider: ProviderId;
|
|
26
41
|
source: KeySource;
|
|
27
|
-
/** Present iff source
|
|
42
|
+
/** Present iff source resolves to a usable key. */
|
|
28
43
|
key?: string;
|
|
29
44
|
/** Which env var was used (only set when source === "env"). */
|
|
30
45
|
envVar?: string;
|
|
46
|
+
/** Human-readable diagnostic when source === "keychain_error". */
|
|
47
|
+
keychainError?: string;
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
/**
|
|
34
|
-
* Thrown when no key is found in any source
|
|
35
|
-
* the message verbatim — it tells the user exactly
|
|
51
|
+
* Thrown when no key is found in any source (or when keychain read failed).
|
|
52
|
+
* Caller should surface the message verbatim — it tells the user exactly
|
|
53
|
+
* how to fix it.
|
|
36
54
|
*/
|
|
37
55
|
export class AuthMissingError extends Error {
|
|
38
56
|
readonly provider: ProviderId;
|
|
39
57
|
readonly envVar: string;
|
|
40
|
-
constructor(provider: ProviderId, envVar: string) {
|
|
58
|
+
constructor(provider: ProviderId, envVar: string, customMessage?: string) {
|
|
41
59
|
super(
|
|
42
|
-
|
|
43
|
-
`
|
|
44
|
-
|
|
60
|
+
customMessage ??
|
|
61
|
+
`No API key found for ${provider}. ` +
|
|
62
|
+
`Run "almightygpt auth ${provider}" to set one up interactively, ` +
|
|
63
|
+
`or export ${envVar} in your shell.`,
|
|
45
64
|
);
|
|
46
65
|
this.name = "AuthMissingError";
|
|
47
66
|
this.provider = provider;
|