codehere 1.0.1 → 1.0.2
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 +36 -192
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
|
@@ -1,611 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aider adapter — third concrete AgentAdapter.
|
|
3
|
-
*
|
|
4
|
-
* `aider --message "<prompt>" --yes-always --no-check-update --no-pretty --no-stream`
|
|
5
|
-
*
|
|
6
|
-
* Aider's output is free-form text (not JSON), so the event log shows raw lines
|
|
7
|
-
* instead of semantic tool_use blocks. Step aggregator produces zero steps — but
|
|
8
|
-
* Final report still works via git-diff (artifacts-extractor is agent-agnostic)
|
|
9
|
-
* and token accounting works through Aider's cost summary line at session end.
|
|
10
|
-
*
|
|
11
|
-
* Same structural pattern as Claude Code / Codex adapters: emit buffer,
|
|
12
|
-
* SIGTERM→SIGKILL escalation, known-error fingerprints. Intentionally keeping
|
|
13
|
-
* the duplication for now — if we grow to 5+ adapters, extract a shared helper.
|
|
14
|
-
*/
|
|
15
|
-
import { spawn, execFileSync } from 'child_process';
|
|
16
|
-
import { EventEmitter } from 'events';
|
|
17
|
-
import { randomUUID } from 'crypto';
|
|
18
|
-
import { startRecording } from './cassette-recorder.js';
|
|
19
|
-
import { ProgressAggregator } from './progress-aggregator.js';
|
|
20
|
-
import { resolveAgentSpawn } from './mocks/resolve-agent-spawn.js';
|
|
21
|
-
import { detectOllama } from './detect-ollama.js';
|
|
22
|
-
import { applyMemoryPrefix } from './memory-injector.js';
|
|
23
|
-
import { mirrorToolCallToAudit } from '../hooks/mirror-tool-call-to-audit.js';
|
|
24
|
-
import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
|
|
25
|
-
const HEADLESS_FLAGS = [
|
|
26
|
-
'--yes-always', // auto-accept all file-edit confirmations
|
|
27
|
-
'--no-check-update', // skip aider self-update probe
|
|
28
|
-
'--no-pretty', // disable ANSI colors in output
|
|
29
|
-
'--no-stream', // stream=false → one chunk per turn, cleaner stdout parsing
|
|
30
|
-
'--no-auto-commits', // don't auto-commit — leave file changes for artifacts-extractor
|
|
31
|
-
// Aider detects URLs in the message and, under --yes-always, silently
|
|
32
|
-
// scrapes them. Our injected context (memory pack, repo docs) legitimately
|
|
33
|
-
// contains URLs — live 2026-07-02 a verify run burned its whole turn failing
|
|
34
|
-
// to scrape `codehere.uk` (markdown backtick glued onto the URL) and that
|
|
35
|
-
// noise became the report. Headless runs never want surprise scraping.
|
|
36
|
-
'--no-detect-urls',
|
|
37
|
-
];
|
|
38
|
-
// v0.7 HIGH-1 fix — extended ERROR_PATTERNS for the 18+ provider families
|
|
39
|
-
// Aider supports. Pre-fix: 4 regexes shaped only for Anthropic + OpenAI text.
|
|
40
|
-
// Aider routes through LiteLLM which forwards each provider's native error
|
|
41
|
-
// message, so DeepSeek "Insufficient Balance", Ollama "connection refused",
|
|
42
|
-
// Bedrock "UnrecognizedClientException", Gemini "API key not valid", and even
|
|
43
|
-
// Anthropic's actual "invalid x-api-key" all fell through and never fired
|
|
44
|
-
// failover. Per docs/contracts/aider.md §3 + docs/contracts/00-FINDINGS.md HIGH-1.
|
|
45
|
-
const ERROR_PATTERNS = [
|
|
46
|
-
{
|
|
47
|
-
code: 'credit_low',
|
|
48
|
-
// Anthropic + OpenAI canonical + DeepSeek ("Insufficient Balance").
|
|
49
|
-
// Adjacent words only — a spanning /insufficient.*credit/ combines distant
|
|
50
|
-
// benign prose under the de-wrap collapse (2026-06-07 cold review).
|
|
51
|
-
regex: /credit balance is too low|insufficient\s+credits?\b|no credits\b|you exceeded your current quota|insufficient\s+balance/i,
|
|
52
|
-
message: 'The API key has no credit (or balance is too low).',
|
|
53
|
-
recovery: 'Aider needs a billed API key with balance. Top up at your provider (console.anthropic.com / ' +
|
|
54
|
-
'platform.openai.com / platform.deepseek.com), or switch the task to Claude Code / Codex / Gemini ' +
|
|
55
|
-
'which use subscription or free-tier auth.',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
code: 'auth_invalid',
|
|
59
|
-
// Anthropic + OpenAI + Anthropic's actual "invalid x-api-key" + Gemini's
|
|
60
|
-
// "API key not valid" + Bedrock's "UnrecognizedClientException" + generic.
|
|
61
|
-
regex: /invalid api key|invalid x-api-key|api key not valid|authentication\s+(?:request\s+)?failed|401 unauth|please provide your\s+\S{0,20}\s*api key|unrecognizedclientexception|the security token included in the request is invalid/i,
|
|
62
|
-
message: 'Aider auth was rejected or missing.',
|
|
63
|
-
recovery: 'Check the API key for your model provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, ' +
|
|
64
|
-
'GEMINI_API_KEY, AWS_ACCESS_KEY_ID for Bedrock, etc.). Re-export and retry.',
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
code: 'rate_limit',
|
|
68
|
-
// Generic 429 + named providers (Gemini RESOURCE_EXHAUSTED, Bedrock ThrottlingException).
|
|
69
|
-
// Deliberately requires error-shaped context ("rate limited", "rate limit
|
|
70
|
-
// exceeded") — a bare /rate.?limit/ matches the literal filename
|
|
71
|
-
// rate-limit.ts in an "Applied edit to …" line and flags a SUCCESSFUL run
|
|
72
|
-
// as failed (2026-06-07 batch cold review, HIGH).
|
|
73
|
-
regex: /\brate.?limited\b|rate.?limit\s*(?:exceeded|reached|hit|error)|hit.{0,20}rate.?limit|\b429\b|too many requests|resource.?exhausted|throttlingexception/i,
|
|
74
|
-
message: 'Aider was rate-limited by the provider.',
|
|
75
|
-
recovery: 'Wait a minute and retry, or switch --model via extraArgs to a different provider.',
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
code: 'model_unavailable',
|
|
79
|
-
// Bounded gap (≤40 chars, no sentence boundary) so "model X not found"
|
|
80
|
-
// matches but "model" + distant "not found" in prose doesn't combine.
|
|
81
|
-
regex: /model\b[^.!?]{0,40}(not found|not available|unavailable|does not exist)|invalidmodelid/i,
|
|
82
|
-
message: 'Aider could not find the model.',
|
|
83
|
-
recovery: 'Run `aider --list-models` and pass `--model <name>` via extraArgs.',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
code: 'network_failure',
|
|
87
|
-
// Ollama (local), self-hosted, generic socket errors. Local providers
|
|
88
|
-
// typically fail at the transport layer with these strings.
|
|
89
|
-
regex: /connection refused|econnrefused|enotfound|fetch failed|getaddrinfo|network is unreachable/i,
|
|
90
|
-
message: "Aider couldn't reach the model provider.",
|
|
91
|
-
recovery: 'Network or provider endpoint issue. For local Ollama: confirm `ollama serve` is running. ' +
|
|
92
|
-
'For cloud providers: check internet + provider status page.',
|
|
93
|
-
},
|
|
94
|
-
// 2026-06-07 user-#1 dogfood (session 4b6bc481): aider HARD-WRAPS its error
|
|
95
|
-
// body at ~80 cols, so "Your credit\nbalance is too low" lands on two lines.
|
|
96
|
-
// The per-line scan above never sees the contiguous phrase → no failover →
|
|
97
|
-
// exit 0 → a no-op run presented as "verified". The litellm exception CLASS
|
|
98
|
-
// name always rides ONE line (it's the prefix before the wrapped JSON), so
|
|
99
|
-
// these are wrap-proof fingerprints. Keep them last so the more specific
|
|
100
|
-
// phrase patterns above win when they DO land on a single line.
|
|
101
|
-
{
|
|
102
|
-
code: 'credit_low',
|
|
103
|
-
regex: /litellm\.(?:BadRequestError|InsufficientCreditsError).*(?:credit|balance|quota)/i,
|
|
104
|
-
message: 'The API key has no credit (or balance is too low).',
|
|
105
|
-
recovery: 'Aider needs a billed API key with balance. Top up at your provider (console.anthropic.com / ' +
|
|
106
|
-
'platform.openai.com / platform.deepseek.com), or switch the task to Claude Code / Codex / Gemini ' +
|
|
107
|
-
'which use subscription or free-tier auth.',
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
code: 'auth_invalid',
|
|
111
|
-
regex: /litellm\.(?:AuthenticationError|PermissionDeniedError)/i,
|
|
112
|
-
message: 'Aider auth was rejected or missing.',
|
|
113
|
-
recovery: 'Check the API key for your model provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, ' +
|
|
114
|
-
'GEMINI_API_KEY, AWS_ACCESS_KEY_ID for Bedrock, etc.). Re-export and retry.',
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
code: 'rate_limit',
|
|
118
|
-
regex: /litellm\.RateLimitError/i,
|
|
119
|
-
message: 'Aider was rate-limited by the provider.',
|
|
120
|
-
recovery: 'Wait a minute and retry, or switch --model via extraArgs to a different provider.',
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
code: 'model_unavailable',
|
|
124
|
-
regex: /litellm\.NotFoundError/i,
|
|
125
|
-
message: 'Aider could not find the model.',
|
|
126
|
-
recovery: 'Run `aider --list-models` and pass `--model <name>` via extraArgs.',
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
code: 'network_failure',
|
|
130
|
-
regex: /litellm\.(?:APIConnectionError|Timeout|ServiceUnavailableError)/i,
|
|
131
|
-
message: "Aider couldn't reach the model provider.",
|
|
132
|
-
recovery: 'Network or provider endpoint issue. For local Ollama: confirm `ollama serve` is running. ' +
|
|
133
|
-
'For cloud providers: check internet + provider status page.',
|
|
134
|
-
},
|
|
135
|
-
];
|
|
136
|
-
export function scanForKnownError(text) {
|
|
137
|
-
// Aider wraps long error bodies across lines; collapse whitespace so a phrase
|
|
138
|
-
// split by a hard wrap ("Your credit\nbalance is too low") is matched as one
|
|
139
|
-
// contiguous string. Scan BOTH the raw text (preserves single-line patterns
|
|
140
|
-
// like litellm class names) and the de-wrapped text (recovers wrapped phrases).
|
|
141
|
-
const dewrapped = text.replace(/\s+/g, ' ');
|
|
142
|
-
for (const p of ERROR_PATTERNS) {
|
|
143
|
-
if (p.regex.test(text) || p.regex.test(dewrapped)) {
|
|
144
|
-
return { code: p.code, message: p.message, recovery: p.recovery };
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
export function spawnAider(prompt, options = {}) {
|
|
150
|
-
const sessionId = randomUUID();
|
|
151
|
-
const startedAt = Date.now();
|
|
152
|
-
const emitter = new EventEmitter();
|
|
153
|
-
const buffer = [];
|
|
154
|
-
let statusValue = 'running';
|
|
155
|
-
let killRequested = false;
|
|
156
|
-
const cwd = options.cwd ?? process.cwd();
|
|
157
|
-
// F3 Task 8 — memory injection (same shape as every other adapter).
|
|
158
|
-
const injected = applyMemoryPrefix(prompt, cwd, options);
|
|
159
|
-
const finalPrompt = injected.prompt;
|
|
160
|
-
const snapshot = snapshotCwd(cwd);
|
|
161
|
-
// ProgressAggregator parses aider's stdout for `Running ...`, `Applied edit
|
|
162
|
-
// to ...`, and `Creating empty file ...` lines and emits Run/Edit/Write
|
|
163
|
-
// ProgressSteps. Observability parity (Path B) — see ingestAider() in
|
|
164
|
-
// progress-aggregator.ts. Blocking parity (Path A) needs the upstream
|
|
165
|
-
// `--pre-action-hook` PR.
|
|
166
|
-
const aggregator = new ProgressAggregator('aider');
|
|
167
|
-
let stepsSnapshot = [];
|
|
168
|
-
let artifactsSnapshot;
|
|
169
|
-
let textAccum = '';
|
|
170
|
-
// Bounded tail of recent stdout for error scanning. Aider hard-wraps error
|
|
171
|
-
// bodies at ~80 cols, so a wrapped phrase spans 2-3 ADJACENT lines — a small
|
|
172
|
-
// tail catches it. Scanning the WHOLE accumulated buffer instead would let
|
|
173
|
-
// prose from unrelated parts of a successful run combine into a phrase match
|
|
174
|
-
// (2026-06-07 batch cold review, HIGH: a session about rate limiting read as
|
|
175
|
-
// a rate-limit failure).
|
|
176
|
-
let scanTail = '';
|
|
177
|
-
const SCAN_TAIL_CHARS = 600;
|
|
178
|
-
let usageSnapshot;
|
|
179
|
-
let aiderModel; // model aider announced in stdout → per-model cost
|
|
180
|
-
let errorEmitted = false;
|
|
181
|
-
const emit = (event) => {
|
|
182
|
-
buffer.push(event);
|
|
183
|
-
emitter.emit('event', event);
|
|
184
|
-
if (aggregator.ingest(event)) {
|
|
185
|
-
stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
|
|
186
|
-
const snapEvt = { type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } };
|
|
187
|
-
buffer.push(snapEvt);
|
|
188
|
-
emitter.emit('event', snapEvt);
|
|
189
|
-
}
|
|
190
|
-
// Text accumulator for test-summary parsing + aider cost-line token extraction
|
|
191
|
-
if (event.type === 'stdout' && event.data?.line) {
|
|
192
|
-
const line = String(event.data.line);
|
|
193
|
-
textAccum += line + '\n';
|
|
194
|
-
// Aider announces its model: "Main model: claude-3-5-sonnet-... with ...".
|
|
195
|
-
if (!aiderModel) {
|
|
196
|
-
const mm = line.match(/(?:main )?model:\s*([A-Za-z0-9._/:-]+)/i);
|
|
197
|
-
if (mm)
|
|
198
|
-
aiderModel = mm[1];
|
|
199
|
-
}
|
|
200
|
-
// #110 slice 3 — mirror aider tool calls to the audit log. Match
|
|
201
|
-
// the same stdout patterns the aggregator parses (per
|
|
202
|
-
// learning_aider.md): "Running <cmd>" → Bash, "Applied edit to
|
|
203
|
-
// <path>" → Edit, "Creating empty file <path>" → Write.
|
|
204
|
-
// Strip ANSI as defense-in-depth (HEADLESS_FLAGS already includes
|
|
205
|
-
// --no-pretty but extraArgs could override).
|
|
206
|
-
const stripAnsi = (s) => s.replace(/\[[0-9;]*m/g, '');
|
|
207
|
-
const clean = stripAnsi(line).trim();
|
|
208
|
-
let toolName = '';
|
|
209
|
-
let input = '';
|
|
210
|
-
let mirrorMatch;
|
|
211
|
-
if ((mirrorMatch = clean.match(/^Running\s+(.+)$/))) {
|
|
212
|
-
toolName = 'Bash';
|
|
213
|
-
input = mirrorMatch[1];
|
|
214
|
-
}
|
|
215
|
-
else if ((mirrorMatch = clean.match(/^Applied edit to\s+(.+)$/))) {
|
|
216
|
-
toolName = 'Edit';
|
|
217
|
-
input = mirrorMatch[1];
|
|
218
|
-
}
|
|
219
|
-
else if ((mirrorMatch = clean.match(/^Creating empty file\s+(.+)$/))) {
|
|
220
|
-
toolName = 'Write';
|
|
221
|
-
input = mirrorMatch[1];
|
|
222
|
-
}
|
|
223
|
-
if (toolName && input) {
|
|
224
|
-
mirrorToolCallToAudit({ sessionId, agent: 'aider', toolName, toolInput: input, cwd });
|
|
225
|
-
}
|
|
226
|
-
// Aider emits a cost summary like:
|
|
227
|
-
// "Tokens: 1.2k sent, 412 received. Cost: $0.01 request, $0.01 session."
|
|
228
|
-
const m = line.match(/Tokens:\s*([\d.]+)([kM]?)\s*sent,\s*([\d.]+)([kM]?)\s*received/i);
|
|
229
|
-
if (m) {
|
|
230
|
-
const scale = (s) => (s === 'k' ? 1000 : s === 'M' ? 1_000_000 : 1);
|
|
231
|
-
const sent = Math.round(parseFloat(m[1]) * scale(m[2]));
|
|
232
|
-
const recv = Math.round(parseFloat(m[3]) * scale(m[4]));
|
|
233
|
-
usageSnapshot = {
|
|
234
|
-
inputTokens: (usageSnapshot?.inputTokens ?? 0) + sent,
|
|
235
|
-
outputTokens: (usageSnapshot?.outputTokens ?? 0) + recv,
|
|
236
|
-
...(aiderModel ? { model: aiderModel } : {}),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (!errorEmitted) {
|
|
241
|
-
const data = event.data;
|
|
242
|
-
let text = '';
|
|
243
|
-
if ((event.type === 'stderr' || event.type === 'stdout') && typeof data?.line === 'string') {
|
|
244
|
-
// Scan a bounded RECENT tail, not just this one line and not the whole
|
|
245
|
-
// buffer. Aider hard-wraps error bodies across lines (user-#1 dogfood,
|
|
246
|
-
// session 4b6bc481), so a single-line scan misses "Your credit\nbalance
|
|
247
|
-
// is too low" — but the whole accumulated buffer lets distant prose
|
|
248
|
-
// combine into false matches. The wrapped phrase always sits on
|
|
249
|
-
// adjacent lines, so the tail window catches it. The de-wrap collapse
|
|
250
|
-
// in scanForKnownError joins the wrapped phrase. stderr is scanned
|
|
251
|
-
// line-by-line (it isn't accumulated).
|
|
252
|
-
if (event.type === 'stdout') {
|
|
253
|
-
scanTail = (scanTail + '\n' + data.line).slice(-SCAN_TAIL_CHARS);
|
|
254
|
-
text = scanTail;
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
text = data.line;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (text) {
|
|
261
|
-
const hit = scanForKnownError(text);
|
|
262
|
-
if (hit) {
|
|
263
|
-
errorEmitted = true;
|
|
264
|
-
const errEvent = {
|
|
265
|
-
type: 'codehere:error',
|
|
266
|
-
timestamp: Date.now(),
|
|
267
|
-
data: { code: hit.code, message: hit.message, recovery: hit.recovery },
|
|
268
|
-
};
|
|
269
|
-
buffer.push(errEvent);
|
|
270
|
-
emitter.emit('event', errEvent);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
// Whenever a codehere:error fires, we treat the session as effectively failed
|
|
276
|
-
// even if the subprocess later exits 0. Many CLIs print a logical error to
|
|
277
|
-
// stdout then return success — the status pill would otherwise lie.
|
|
278
|
-
emitter.on('event', (ev) => {
|
|
279
|
-
if (ev.type === 'codehere:error' && statusValue === 'running') {
|
|
280
|
-
// Don't overwrite if exit already settled a final status.
|
|
281
|
-
// We rely on exit handler to flip running→completed/killed/error; we pre-empt
|
|
282
|
-
// that with 'error' here so the status pill is honest from the moment the
|
|
283
|
-
// error is detected.
|
|
284
|
-
statusValue = 'error';
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
const spawnEnv = { ...process.env, ...(options.env ?? {}) };
|
|
288
|
-
// Local-first reliable vendor: when the user hasn't pinned a model (no
|
|
289
|
-
// AIDER_MODEL / CODEHERE_AIDER_MODEL env, no --model in extraArgs) and Ollama
|
|
290
|
-
// is serving a local model, default aider to it. That turns aider into a FREE,
|
|
291
|
-
// RELIABLE, LOCAL second vendor (no rate limits, no cloud key, no waste) — the
|
|
292
|
-
// unlock for the cross-vendor wedge on a machine that has Ollama. Always
|
|
293
|
-
// overridable; opt out with CODEHERE_NO_LOCAL_OLLAMA=1.
|
|
294
|
-
const extra = options.extraArgs ?? [];
|
|
295
|
-
const modelPinned = !!spawnEnv.AIDER_MODEL ||
|
|
296
|
-
!!spawnEnv.CODEHERE_AIDER_MODEL ||
|
|
297
|
-
extra.some((a) => a === '--model' || a.startsWith('--model='));
|
|
298
|
-
const localArgs = [];
|
|
299
|
-
if (!modelPinned && spawnEnv.CODEHERE_NO_LOCAL_OLLAMA !== '1') {
|
|
300
|
-
const ollama = detectOllama();
|
|
301
|
-
if (ollama.available && ollama.model) {
|
|
302
|
-
localArgs.push('--model', `ollama_chat/${ollama.model}`);
|
|
303
|
-
if (!spawnEnv.OLLAMA_API_BASE)
|
|
304
|
-
spawnEnv.OLLAMA_API_BASE = 'http://localhost:11434';
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
let child;
|
|
308
|
-
try {
|
|
309
|
-
const sp = resolveAgentSpawn('aider', 'aider', ['--message', finalPrompt, ...HEADLESS_FLAGS, ...localArgs, ...extra], finalPrompt);
|
|
310
|
-
child = spawn(sp.command, sp.args, {
|
|
311
|
-
cwd,
|
|
312
|
-
env: spawnEnv,
|
|
313
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
catch (err) {
|
|
317
|
-
statusValue = 'error';
|
|
318
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
319
|
-
queueMicrotask(() => emit({ type: 'error', timestamp: Date.now(), data: { message: msg } }));
|
|
320
|
-
return {
|
|
321
|
-
sessionId,
|
|
322
|
-
startedAt,
|
|
323
|
-
prompt,
|
|
324
|
-
agent: 'aider',
|
|
325
|
-
get status() {
|
|
326
|
-
return statusValue;
|
|
327
|
-
},
|
|
328
|
-
get steps() {
|
|
329
|
-
return stepsSnapshot;
|
|
330
|
-
},
|
|
331
|
-
get artifacts() {
|
|
332
|
-
return artifactsSnapshot;
|
|
333
|
-
},
|
|
334
|
-
subscribe(handler) {
|
|
335
|
-
for (const e of buffer)
|
|
336
|
-
handler(e);
|
|
337
|
-
emitter.on('event', handler);
|
|
338
|
-
return () => emitter.off('event', handler);
|
|
339
|
-
},
|
|
340
|
-
kill() {
|
|
341
|
-
/* already not running */
|
|
342
|
-
},
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
// Organic cassette recording (S2b · capture-first plan) · no-op unless
|
|
346
|
-
// CODEHERE_RECORD_CASSETTES=1, so zero overhead by default.
|
|
347
|
-
const recorder = startRecording({ adapter: 'aider', sessionId, prompt: finalPrompt, cwd });
|
|
348
|
-
let stdoutBuf = '';
|
|
349
|
-
child.stdout?.on('data', (chunk) => {
|
|
350
|
-
const text = chunk.toString('utf-8');
|
|
351
|
-
recorder.onStdout(text);
|
|
352
|
-
stdoutBuf += text;
|
|
353
|
-
let idx;
|
|
354
|
-
while ((idx = stdoutBuf.indexOf('\n')) >= 0) {
|
|
355
|
-
const line = stdoutBuf.slice(0, idx).trim();
|
|
356
|
-
stdoutBuf = stdoutBuf.slice(idx + 1);
|
|
357
|
-
if (!line)
|
|
358
|
-
continue;
|
|
359
|
-
emit({ type: 'stdout', timestamp: Date.now(), data: { line } });
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
child.stderr?.on('data', (chunk) => {
|
|
363
|
-
const text = chunk.toString('utf-8');
|
|
364
|
-
recorder.onStderr(text);
|
|
365
|
-
for (const line of text.split('\n')) {
|
|
366
|
-
if (line.trim())
|
|
367
|
-
emit({ type: 'stderr', timestamp: Date.now(), data: { line } });
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
child.on('exit', (code, signal) => {
|
|
371
|
-
recorder.onExit(code ?? -1, signal);
|
|
372
|
-
// Preserve 'error' if we already flipped to it on codehere:error.
|
|
373
|
-
statusValue =
|
|
374
|
-
killRequested || signal !== null ? 'killed' : errorEmitted ? 'error' : code === 0 ? 'completed' : 'error';
|
|
375
|
-
const finalizeChanged = aggregator.finalize(statusValue === 'completed' ? 'completed' : statusValue === 'killed' ? 'killed' : 'error');
|
|
376
|
-
// Extract artifacts BEFORE emitting steps so the git-diff fallback can run.
|
|
377
|
-
if (statusValue === 'completed') {
|
|
378
|
-
try {
|
|
379
|
-
artifactsSnapshot = { ...extractArtifacts(snapshot, textAccum), usage: usageSnapshot };
|
|
380
|
-
}
|
|
381
|
-
catch {
|
|
382
|
-
/* best-effort */
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
// Aider parity safety net: if the brittle stdout-parse produced no
|
|
386
|
-
// file-action steps but git shows real changes, synthesize them from the
|
|
387
|
-
// diff so the timeline + report can't claim "did nothing" on a real edit.
|
|
388
|
-
const fallbackChanged = aggregator.applyFileFallback(artifactsSnapshot?.files);
|
|
389
|
-
// PM steps BEFORE artifacts BEFORE exit (client closes SSE on exit; order matters).
|
|
390
|
-
if (finalizeChanged || fallbackChanged) {
|
|
391
|
-
stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
|
|
392
|
-
emit({ type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } });
|
|
393
|
-
}
|
|
394
|
-
if (artifactsSnapshot) {
|
|
395
|
-
emit({ type: 'codehere:artifacts', timestamp: Date.now(), data: artifactsSnapshot });
|
|
396
|
-
}
|
|
397
|
-
emit({
|
|
398
|
-
type: 'exit',
|
|
399
|
-
timestamp: Date.now(),
|
|
400
|
-
data: { code, signal, status: statusValue, durationMs: Date.now() - startedAt },
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
child.on('error', (err) => {
|
|
404
|
-
statusValue = 'error';
|
|
405
|
-
emit({ type: 'error', timestamp: Date.now(), data: { message: err.message } });
|
|
406
|
-
});
|
|
407
|
-
return {
|
|
408
|
-
sessionId,
|
|
409
|
-
startedAt,
|
|
410
|
-
prompt,
|
|
411
|
-
agent: 'aider',
|
|
412
|
-
memoryInjection: injected.record,
|
|
413
|
-
get status() {
|
|
414
|
-
return statusValue;
|
|
415
|
-
},
|
|
416
|
-
get steps() {
|
|
417
|
-
return stepsSnapshot;
|
|
418
|
-
},
|
|
419
|
-
get artifacts() {
|
|
420
|
-
return artifactsSnapshot;
|
|
421
|
-
},
|
|
422
|
-
subscribe(handler) {
|
|
423
|
-
for (const e of buffer)
|
|
424
|
-
handler(e);
|
|
425
|
-
emitter.on('event', handler);
|
|
426
|
-
return () => emitter.off('event', handler);
|
|
427
|
-
},
|
|
428
|
-
kill() {
|
|
429
|
-
if (statusValue !== 'running')
|
|
430
|
-
return;
|
|
431
|
-
killRequested = true;
|
|
432
|
-
child.kill('SIGTERM');
|
|
433
|
-
setTimeout(() => {
|
|
434
|
-
if (statusValue === 'running') {
|
|
435
|
-
try {
|
|
436
|
-
child.kill('SIGKILL');
|
|
437
|
-
}
|
|
438
|
-
catch {
|
|
439
|
-
/* already dead */
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}, 3000);
|
|
443
|
-
},
|
|
444
|
-
canAppendMessage: true,
|
|
445
|
-
/**
|
|
446
|
-
* v0.6.0 — Slice 2 of REPL persistence. Aider has no per-session UUID
|
|
447
|
-
* (unlike claude-code's session_id or codex's thread_id); it persists chat
|
|
448
|
-
* history to ./.aider.chat.history.md in the cwd. `--restore-chat-history`
|
|
449
|
-
* reads that file before processing the new --message, giving multi-turn
|
|
450
|
-
* continuity scoped to the cwd.
|
|
451
|
-
*
|
|
452
|
-
* Limitation: aider is single-cwd-single-session. Two concurrent aider
|
|
453
|
-
* sessions in the same cwd would collide on the same history file. Same
|
|
454
|
-
* limitation aider's own users have when running it interactively;
|
|
455
|
-
* codehere doesn't make it worse.
|
|
456
|
-
*
|
|
457
|
-
* Same shape as the other appendMessage implementations: one-shot Q&A,
|
|
458
|
-
* returns assistant text + duration, no streaming.
|
|
459
|
-
*/
|
|
460
|
-
appendMessage(text) {
|
|
461
|
-
const startedAt = Date.now();
|
|
462
|
-
return new Promise((resolve, reject) => {
|
|
463
|
-
let stdout = '';
|
|
464
|
-
let stderr = '';
|
|
465
|
-
// Route follow-ups through the mock seam too — under CODEHERE_MOCK=1 this
|
|
466
|
-
// hits the mock REPL, not real aider. (Was a real-spend leak.)
|
|
467
|
-
// BUG-1 (E2E 2.4) · pin the follow-up to the SAME model as the original
|
|
468
|
-
// spawn (localArgs + extra). Without them, a free ollama session's reply
|
|
469
|
-
// fell back to aider's default cloud model + env API key — a silent
|
|
470
|
-
// vendor/spend switch, observed live 2026-07-18.
|
|
471
|
-
const sp2 = resolveAgentSpawn('aider', 'aider', ['--restore-chat-history', '--message', text, ...HEADLESS_FLAGS, ...localArgs, ...extra], text);
|
|
472
|
-
const followup = spawn(sp2.command, sp2.args, {
|
|
473
|
-
cwd,
|
|
474
|
-
env: spawnEnv,
|
|
475
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
476
|
-
});
|
|
477
|
-
followup.on('error', (err) => reject(err));
|
|
478
|
-
followup.stdout?.on('data', (chunk) => {
|
|
479
|
-
stdout += chunk.toString('utf-8');
|
|
480
|
-
});
|
|
481
|
-
followup.stderr?.on('data', (chunk) => {
|
|
482
|
-
stderr += chunk.toString('utf-8');
|
|
483
|
-
});
|
|
484
|
-
followup.on('exit', (code) => {
|
|
485
|
-
if (code !== 0) {
|
|
486
|
-
reject(new Error(`aider --restore-chat-history exited ${code}: ${stderr.slice(0, 300)}`));
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
// Aider's --no-stream + --no-pretty output is plain text; the
|
|
490
|
-
// assistant's reply lands as a top-level paragraph after any
|
|
491
|
-
// tool-execution lines. Strip aider's own prefix lines (Added,
|
|
492
|
-
// Tokens, Cost, Applied, etc.) and keep the conversational body.
|
|
493
|
-
const stripped = stdout
|
|
494
|
-
.split('\n')
|
|
495
|
-
.filter((l) => {
|
|
496
|
-
const t = l.trim();
|
|
497
|
-
if (!t)
|
|
498
|
-
return false;
|
|
499
|
-
if (/^(Added|Removed|Tokens?:|Cost:|Applied|Creating|Running|Commit|Aider|Model:|Main model:|Weak model:)/i.test(t))
|
|
500
|
-
return false;
|
|
501
|
-
return true;
|
|
502
|
-
})
|
|
503
|
-
.join('\n')
|
|
504
|
-
.trim();
|
|
505
|
-
const result = stripped || '(aider returned no conversational output)';
|
|
506
|
-
resolve({ result, durationMs: Date.now() - startedAt });
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
},
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
export const AiderAdapter = {
|
|
513
|
-
id: 'aider',
|
|
514
|
-
label: 'Aider',
|
|
515
|
-
description: 'Best when you already know which file to change and what to change in it. Tight, focused edits — no plan-then-explore phase.',
|
|
516
|
-
processPattern: /(?:^|\/)aider(?:\s|$)/,
|
|
517
|
-
isInstalled() {
|
|
518
|
-
try {
|
|
519
|
-
const out = execFileSync('which', ['aider'], { encoding: 'utf-8', timeout: 2000 });
|
|
520
|
-
return out.trim().length > 0;
|
|
521
|
-
}
|
|
522
|
-
catch {
|
|
523
|
-
return false;
|
|
524
|
-
}
|
|
525
|
-
},
|
|
526
|
-
spawn(prompt, options) {
|
|
527
|
-
return spawnAider(prompt, options);
|
|
528
|
-
},
|
|
529
|
-
checkReadiness() {
|
|
530
|
-
const installed = this.isInstalled();
|
|
531
|
-
if (!installed) {
|
|
532
|
-
return {
|
|
533
|
-
id: this.id,
|
|
534
|
-
label: this.label,
|
|
535
|
-
state: 'needs_install',
|
|
536
|
-
installed: false,
|
|
537
|
-
installHint: { command: 'brew install aider', url: 'https://aider.chat/docs/install.html' },
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
// Aider has no subscription path, but it routes through LiteLLM — so it can
|
|
541
|
-
// run on a BILLED key (Anthropic/OpenAI, needs credit) OR for FREE on a
|
|
542
|
-
// Gemini AI-Studio key / DeepSeek / a local Ollama model, by passing
|
|
543
|
-
// `--model <provider>/<model>`. The pre-May "needs a BILLED key" hint was
|
|
544
|
-
// misleading for free-tier-first users (Phase 1): it sent them to top up a
|
|
545
|
-
// card when a free Gemini key would have worked. We can't verify credit
|
|
546
|
-
// pre-spawn without an API call, so we surface BOTH paths honestly.
|
|
547
|
-
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
|
548
|
-
const hasOpenai = !!process.env.OPENAI_API_KEY;
|
|
549
|
-
const hasGemini = !!process.env.GEMINI_API_KEY || !!process.env.GOOGLE_API_KEY;
|
|
550
|
-
const hasDeepseek = !!process.env.DEEPSEEK_API_KEY;
|
|
551
|
-
const FREE_PATH = 'Free option: set GEMINI_API_KEY (aistudio.google.com/apikey) and run aider with `--model gemini/gemini-2.5-flash`, or point it at a local Ollama model (`--model ollama/…`) — aider routes through LiteLLM, so no paid key is required.';
|
|
552
|
-
// A configured model + its matching key = genuinely READY (the user picked a
|
|
553
|
-
// working combo, so don't fall through to billed_key_required). AIDER_MODEL
|
|
554
|
-
// is aider's own env override (= `--model`); it's how codehere routes aider
|
|
555
|
-
// to a free provider, since the task intake doesn't pass --model. A local
|
|
556
|
-
// model (ollama/…) needs no cloud key — ready by config, like claude's
|
|
557
|
-
// subscription (runtime catches a down endpoint via the network fingerprint).
|
|
558
|
-
const model = (process.env.AIDER_MODEL || '').toLowerCase();
|
|
559
|
-
const modelReady = (model.startsWith('gemini') && hasGemini) ||
|
|
560
|
-
(model.startsWith('deepseek') && hasDeepseek) ||
|
|
561
|
-
(model.startsWith('openai') && hasOpenai) ||
|
|
562
|
-
(model.startsWith('anthropic') && hasAnthropic) ||
|
|
563
|
-
model.startsWith('ollama') ||
|
|
564
|
-
model.startsWith('ollama_chat');
|
|
565
|
-
if (modelReady) {
|
|
566
|
-
return { id: this.id, label: this.label, state: 'ready', installed: true };
|
|
567
|
-
}
|
|
568
|
-
// Local-first: a serving Ollama model makes aider ready for FREE with no
|
|
569
|
-
// cloud key. The spawn path auto-routes aider to it (detect-ollama) when no
|
|
570
|
-
// model is pinned — so readiness agrees, or the intake gate would refuse a
|
|
571
|
-
// spawn that will actually run. Mirrors the spawn-side guard on the ENV pins
|
|
572
|
-
// (AIDER_MODEL / CODEHERE_AIDER_MODEL / CODEHERE_NO_LOCAL_OLLAMA); it cannot
|
|
573
|
-
// see a per-task extraArgs `--model` (readiness has never seen extraArgs —
|
|
574
|
-
// the product's /api/task intake doesn't pass them, so this is unreachable
|
|
575
|
-
// through the surface, just honestly noted).
|
|
576
|
-
const localPinnedAway = !!process.env.CODEHERE_AIDER_MODEL || process.env.CODEHERE_NO_LOCAL_OLLAMA === '1';
|
|
577
|
-
if (!model && !localPinnedAway && detectOllama().available) {
|
|
578
|
-
return { id: this.id, label: this.label, state: 'ready', installed: true };
|
|
579
|
-
}
|
|
580
|
-
if (!hasAnthropic && !hasOpenai && !hasGemini && !hasDeepseek) {
|
|
581
|
-
return {
|
|
582
|
-
id: this.id,
|
|
583
|
-
label: this.label,
|
|
584
|
-
state: 'needs_auth',
|
|
585
|
-
installed: true,
|
|
586
|
-
authHint: {
|
|
587
|
-
command: 'export GEMINI_API_KEY=... # free tier: https://aistudio.google.com/apikey',
|
|
588
|
-
detail: `Aider has no subscription path, but it doesn't need a paid key either. ${FREE_PATH} A billed Anthropic/OpenAI key with credit also works.`,
|
|
589
|
-
url: 'https://aistudio.google.com/apikey',
|
|
590
|
-
},
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
// A provider key is present. If it's only the free Gemini/DeepSeek key,
|
|
594
|
-
// aider runs at $0 but needs `--model …` to target that provider. If it's a
|
|
595
|
-
// billed Anthropic/OpenAI key, that key needs real credit. Either way the
|
|
596
|
-
// key's mere presence doesn't guarantee a successful first call.
|
|
597
|
-
const onlyFree = !hasAnthropic && !hasOpenai && (hasGemini || hasDeepseek);
|
|
598
|
-
return {
|
|
599
|
-
id: this.id,
|
|
600
|
-
label: this.label,
|
|
601
|
-
state: 'billed_key_required',
|
|
602
|
-
installed: true,
|
|
603
|
-
authHint: {
|
|
604
|
-
detail: onlyFree
|
|
605
|
-
? `Free provider key detected (${hasGemini ? 'Gemini' : 'DeepSeek'}). Aider runs at $0 on it, but target it explicitly with \`--model ${hasGemini ? 'gemini/gemini-2.5-flash' : 'deepseek/deepseek-chat'}\` — aider's default model expects an Anthropic/OpenAI key.`
|
|
606
|
-
: `API key detected. Aider needs the key to have billing credit (not just presence). If you run on a Claude Code subscription, Aider will fail at the first model call. ${FREE_PATH}`,
|
|
607
|
-
},
|
|
608
|
-
};
|
|
609
|
-
},
|
|
610
|
-
};
|
|
611
|
-
//# sourceMappingURL=aider-adapter.js.map
|