atom-agent 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/README.md +13 -4
- package/atom.example.json +11 -0
- package/dist/App.js +923 -200
- package/dist/adapters.js +82 -13
- package/dist/agent/goal-evaluator.js +69 -0
- package/dist/agent/loop.js +517 -76
- package/dist/cli.js +11 -3
- package/dist/compact.js +41 -15
- package/dist/config.js +43 -7
- package/dist/context-manager.js +16 -198
- package/dist/context-windows.js +4 -2
- package/dist/env-block.js +5 -5
- package/dist/extension-commands.js +196 -0
- package/dist/extension-ui.js +153 -0
- package/dist/extensions.js +1571 -0
- package/dist/goal.js +583 -0
- package/dist/project-trust.js +96 -0
- package/dist/providers.js +6 -6
- package/dist/scheduler.js +74 -36
- package/dist/session.js +23 -5
- package/dist/sessions.js +25 -6
- package/dist/telemetry-dashboard.js +28 -0
- package/dist/telemetry.js +39 -0
- package/dist/tools/compaction-hooks.js +165 -0
- package/dist/tools/custom.js +189 -0
- package/dist/tools/intercept.js +145 -0
- package/dist/tools/overrides.js +105 -0
- package/dist/tools/provider-hooks.js +224 -0
- package/dist/tools/registry.js +246 -17
- package/dist/tools.js +44 -0
- package/dist/ui/palette.js +1 -1
- package/dist/ui/status-bar.js +80 -5
- package/dist/zen.js +305 -75
- package/documentation/architecture.md +114 -0
- package/documentation/cli.md +82 -0
- package/documentation/compaction.md +50 -0
- package/documentation/configuration.md +111 -0
- package/documentation/development.md +62 -0
- package/documentation/extensions.md +160 -0
- package/documentation/getting-started.md +63 -0
- package/documentation/goals.md +41 -0
- package/documentation/index.md +41 -0
- package/documentation/observability.md +70 -0
- package/documentation/permissions.md +66 -0
- package/documentation/providers.md +78 -0
- package/documentation/sessions.md +92 -0
- package/documentation/skills.md +57 -0
- package/documentation/tools.md +94 -0
- package/documentation/troubleshooting.md +54 -0
- package/examples/extensions/01-audit-gate.js +24 -0
- package/examples/extensions/02-notes-tool.js +32 -0
- package/examples/extensions/03-custom-command.js +32 -0
- package/package.json +6 -2
package/dist/adapters.js
CHANGED
|
@@ -6,12 +6,68 @@
|
|
|
6
6
|
// Normalized output matches zen ChatResult:
|
|
7
7
|
// {content, tool_calls:[{id,function:{name,arguments}}], usage?}
|
|
8
8
|
// so runAgenticLoop/retry/rollback/status code is untouched.
|
|
9
|
-
import {
|
|
9
|
+
import { allToolDefinitions } from "./tools.js";
|
|
10
10
|
import { getProvider, modelsUrlForProvider, } from "./providers.js";
|
|
11
11
|
export const ANTHROPIC_VERSION = "2023-06-01";
|
|
12
12
|
export const ANTHROPIC_MAX_TOKENS = 4096;
|
|
13
|
+
// ---- Reasoning-effort mappings (one /effort knob, three wire shapes) ----
|
|
14
|
+
//
|
|
15
|
+
// OpenAI-chat kind sends `reasoning_effort` verbatim (no mapping needed).
|
|
16
|
+
// Anthropic takes a thinking budget in tokens: minimum 1024, and it must
|
|
17
|
+
// stay under max_tokens for the turn (budgets count toward max_tokens).
|
|
18
|
+
// Gemini takes a thinkingLevel enum (minimal/low/medium/high): our Max maps
|
|
19
|
+
// to high, the deepest level the API offers.
|
|
20
|
+
// Minimum thinking budget Anthropic accepts (see extended-thinking docs).
|
|
21
|
+
export const ANTHROPIC_MIN_THINKING_BUDGET = 1024;
|
|
22
|
+
export const ANTHROPIC_EFFORT_BUDGETS = {
|
|
23
|
+
low: 1024,
|
|
24
|
+
medium: 2048,
|
|
25
|
+
high: 3072,
|
|
26
|
+
max: 3500,
|
|
27
|
+
};
|
|
28
|
+
// Budget for an effort level under a max_tokens cap, or undefined when the
|
|
29
|
+
// knob must be omitted (Auto/unknown effort, or a cap too small to fit the
|
|
30
|
+
// 1024 minimum — e.g. a tiny compaction cap). Oversized wants shrink to
|
|
31
|
+
// cap - 1 instead of 400ing.
|
|
32
|
+
export function anthropicThinkingFor(effort, maxTokens) {
|
|
33
|
+
if (!effort)
|
|
34
|
+
return undefined;
|
|
35
|
+
const want = ANTHROPIC_EFFORT_BUDGETS[effort];
|
|
36
|
+
if (want === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (typeof maxTokens !== "number" || !Number.isFinite(maxTokens))
|
|
39
|
+
return want;
|
|
40
|
+
const max = Math.floor(maxTokens);
|
|
41
|
+
if (want < max)
|
|
42
|
+
return want;
|
|
43
|
+
const shrunk = max - 1;
|
|
44
|
+
return shrunk >= ANTHROPIC_MIN_THINKING_BUDGET ? shrunk : undefined;
|
|
45
|
+
}
|
|
46
|
+
const GEMINI_EFFORT_LEVELS = {
|
|
47
|
+
low: "low",
|
|
48
|
+
medium: "medium",
|
|
49
|
+
high: "high",
|
|
50
|
+
// The API's deepest level is high — Max rides it.
|
|
51
|
+
max: "high",
|
|
52
|
+
};
|
|
53
|
+
// thinkingLevel for an effort level, or undefined when the knob must be
|
|
54
|
+
// omitted (Auto/unknown effort).
|
|
55
|
+
export function geminiThinkingLevelFor(effort) {
|
|
56
|
+
if (!effort)
|
|
57
|
+
return undefined;
|
|
58
|
+
return GEMINI_EFFORT_LEVELS[effort];
|
|
59
|
+
}
|
|
60
|
+
// Server-authoritative unsupported detection: a 400 that names the effort
|
|
61
|
+
// knob means this model/deployment has no such control, and the caller
|
|
62
|
+
// retries once without it. Deliberately narrow (knob names only) so
|
|
63
|
+
// unrelated 400s keep failing loudly instead of silently dropping effort.
|
|
64
|
+
export function isEffortRejection(errorText) {
|
|
65
|
+
return /reasoning_effort|reasoning effort|thinking_level|budget_tokens|\bthinking\b/i.test(errorText);
|
|
66
|
+
}
|
|
13
67
|
function toolDefs() {
|
|
14
|
-
|
|
68
|
+
// Builtins plus extension-registered custom tools, so non-OpenAI kinds
|
|
69
|
+
// see the same model-visible surface as the OpenAI-chat path.
|
|
70
|
+
return allToolDefinitions();
|
|
15
71
|
}
|
|
16
72
|
function parseArgsObject(raw) {
|
|
17
73
|
try {
|
|
@@ -450,16 +506,9 @@ function finiteCount(value) {
|
|
|
450
506
|
? Math.floor(value)
|
|
451
507
|
: undefined;
|
|
452
508
|
}
|
|
453
|
-
function openAIUsage(prompt, completion, cache) {
|
|
509
|
+
function openAIUsage(prompt, completion, cache, opts) {
|
|
454
510
|
const out = {};
|
|
455
511
|
const p = finiteCount(prompt);
|
|
456
|
-
if (p !== undefined)
|
|
457
|
-
out.prompt_tokens = p;
|
|
458
|
-
const c = finiteCount(completion);
|
|
459
|
-
if (c !== undefined)
|
|
460
|
-
out.completion_tokens = c;
|
|
461
|
-
if (p !== undefined && c !== undefined)
|
|
462
|
-
out.total_tokens = p + c;
|
|
463
512
|
// Provider-reported cache counters ride alongside (Anthropic
|
|
464
513
|
// cache_read/_creation, Gemini cachedContentTokenCount) — present-only.
|
|
465
514
|
const read = finiteCount(cache?.read);
|
|
@@ -468,6 +517,22 @@ function openAIUsage(prompt, completion, cache) {
|
|
|
468
517
|
const write = finiteCount(cache?.write);
|
|
469
518
|
if (write !== undefined)
|
|
470
519
|
out.cacheWriteTokens = write;
|
|
520
|
+
// Anthropic's input_tokens EXCLUDES cache_read/_creation (they are separate
|
|
521
|
+
// counters for the same context), while OpenAI/Gemini prompt counts already
|
|
522
|
+
// include cached tokens. With foldCacheIntoPrompt, prompt_tokens is
|
|
523
|
+
// normalized to total input-side tokens so load (P%), spend (NK), and the
|
|
524
|
+
// auto-compact trigger all see the real context. Detail fields above stay
|
|
525
|
+
// provider-faithful regardless.
|
|
526
|
+
const folded = opts?.foldCacheIntoPrompt === true
|
|
527
|
+
? [p, read, write].reduce((acc, v) => (v === undefined ? acc : (acc ?? 0) + v), undefined)
|
|
528
|
+
: p;
|
|
529
|
+
if (folded !== undefined)
|
|
530
|
+
out.prompt_tokens = folded;
|
|
531
|
+
const c = finiteCount(completion);
|
|
532
|
+
if (c !== undefined)
|
|
533
|
+
out.completion_tokens = c;
|
|
534
|
+
if (folded !== undefined && c !== undefined)
|
|
535
|
+
out.total_tokens = folded + c;
|
|
471
536
|
return out.prompt_tokens !== undefined ||
|
|
472
537
|
out.completion_tokens !== undefined ||
|
|
473
538
|
out.total_tokens !== undefined
|
|
@@ -546,10 +611,12 @@ export async function readAnthropicSSEMessage(res, opts) {
|
|
|
546
611
|
const msg = o["message"];
|
|
547
612
|
const u = msg?.["usage"];
|
|
548
613
|
if (u) {
|
|
614
|
+
// Anthropic input_tokens excludes cache_read/_creation — fold them
|
|
615
|
+
// into prompt_tokens so the value is total input-side tokens.
|
|
549
616
|
const hit = openAIUsage(u["input_tokens"], u["output_tokens"], {
|
|
550
617
|
read: u["cache_read_input_tokens"],
|
|
551
618
|
write: u["cache_creation_input_tokens"],
|
|
552
|
-
});
|
|
619
|
+
}, { foldCacheIntoPrompt: true });
|
|
553
620
|
const merged = mergeUsage(usage, hit, { recomputeTotal: true });
|
|
554
621
|
if (merged !== undefined)
|
|
555
622
|
usage = merged;
|
|
@@ -631,10 +698,11 @@ export async function readAnthropicSSEMessage(res, opts) {
|
|
|
631
698
|
const inputSrc = u?.["input_tokens"] !== undefined ? u["input_tokens"] : o["input_tokens"];
|
|
632
699
|
const outputSrc = u?.["output_tokens"] !== undefined ? u["output_tokens"] : o["output_tokens"];
|
|
633
700
|
if (u !== undefined || o["input_tokens"] !== undefined || o["output_tokens"] !== undefined) {
|
|
701
|
+
// Same Anthropic-exclusive-cache fold as message_start above.
|
|
634
702
|
const hit = openAIUsage(inputSrc, outputSrc, {
|
|
635
703
|
read: u?.["cache_read_input_tokens"] ?? o["cache_read_input_tokens"],
|
|
636
704
|
write: u?.["cache_creation_input_tokens"] ?? o["cache_creation_input_tokens"],
|
|
637
|
-
});
|
|
705
|
+
}, { foldCacheIntoPrompt: true });
|
|
638
706
|
const merged = mergeUsage(usage, hit, { recomputeTotal: true });
|
|
639
707
|
if (merged !== undefined)
|
|
640
708
|
usage = merged;
|
|
@@ -753,10 +821,11 @@ export function parseAnthropicJson(data) {
|
|
|
753
821
|
};
|
|
754
822
|
const u = o["usage"];
|
|
755
823
|
if (u) {
|
|
824
|
+
// Same Anthropic-exclusive-cache fold as the SSE path above.
|
|
756
825
|
const hit = openAIUsage(u["input_tokens"], u["output_tokens"], {
|
|
757
826
|
read: u["cache_read_input_tokens"],
|
|
758
827
|
write: u["cache_creation_input_tokens"],
|
|
759
|
-
});
|
|
828
|
+
}, { foldCacheIntoPrompt: true });
|
|
760
829
|
if (hit)
|
|
761
830
|
result.usage = hit;
|
|
762
831
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Goal evaluator fallback (ticket 04): one bounded, read-only judge call
|
|
2
|
+
// for a goal turn that ends with no disposition report. Follows the
|
|
3
|
+
// compaction summary-POST precedent (requestCompactSummary in src/compact.ts):
|
|
4
|
+
// same provider/model, tools disabled, capped output, single attempt, throws
|
|
5
|
+
// on failure (the loop turns that into a pause, never a crash).
|
|
6
|
+
//
|
|
7
|
+
// Split of responsibilities: the loop owns WHEN (report-less turn end) and
|
|
8
|
+
// WHAT NEXT (consumed verdicts flow through the model-report path); this
|
|
9
|
+
// module owns the request shape, the judge prompt, and the transport. App
|
|
10
|
+
// builds the full request from its live provider/key/model refs; unit tests
|
|
11
|
+
// inject a fake GoalJudgeRunner through AgenticOpts without fetch mocks.
|
|
12
|
+
import { chatCompletionForProvider } from "../zen.js";
|
|
13
|
+
import { parseGoalJudgeVerdict } from "../goal.js";
|
|
14
|
+
// Output cap for the judge POST: a verdict is a few dozen tokens of JSON —
|
|
15
|
+
// 256 leaves headroom for a short reason without letting the judge write prose.
|
|
16
|
+
export const GOAL_JUDGE_MAX_TOKENS = 256;
|
|
17
|
+
// The judge instruction: goal text plus the demand for one strict
|
|
18
|
+
// machine-readable verdict. No tools are offered, so there is nothing to
|
|
19
|
+
// call — the transcript above is the only evidence.
|
|
20
|
+
export function buildGoalJudgeInstruction(goal) {
|
|
21
|
+
return (`You are judging whether a coding-goal turn advanced. Goal: "${goal}".\n` +
|
|
22
|
+
`The recent transcript turns are above. Decide the turn outcome and answer ` +
|
|
23
|
+
`with a single JSON object only (no prose, no code fences):\n` +
|
|
24
|
+
`{"status": "continue", "next": "<concrete next action>"} — work remains; name the next action.\n` +
|
|
25
|
+
`{"status": "complete", "reason": "<why the goal is done>"} — the goal is fully achieved.\n` +
|
|
26
|
+
`{"status": "blocked", "reason": "<what blocks progress>"} — a genuine blocker, not mere remaining work.\n` +
|
|
27
|
+
`Rules: no tools are available for this request — judge from the transcript only, ` +
|
|
28
|
+
`answer with the JSON object only.`);
|
|
29
|
+
}
|
|
30
|
+
export function buildGoalJudgeMessages(systemContent, goal, turns) {
|
|
31
|
+
const system = typeof systemContent === "string" && systemContent.length > 0
|
|
32
|
+
? systemContent
|
|
33
|
+
: "You are a concise engineering judge.";
|
|
34
|
+
return [
|
|
35
|
+
{ role: "system", content: system },
|
|
36
|
+
...turns.map((m) => ({ ...m })),
|
|
37
|
+
{ role: "user", content: buildGoalJudgeInstruction(goal) },
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
// Judge POST: SAME provider/model via the existing chat path but TOOLS
|
|
41
|
+
// DISABLED (no `tools` key in the POST body) and output capped
|
|
42
|
+
// (max_tokens/maxOutputTokens per kind — see zen.ts/adapters.ts). Single
|
|
43
|
+
// attempt, no retry — bounded by construction. Returns the parsed verdict,
|
|
44
|
+
// or null when the judge output is anything but a clear verdict (tolerant
|
|
45
|
+
// parsing lives in parseGoalJudgeVerdict). Transport failures and empty
|
|
46
|
+
// replies throw with history untouched (the loop pauses on them).
|
|
47
|
+
export async function requestGoalVerdict(req) {
|
|
48
|
+
const messages = buildGoalJudgeMessages(req.systemContent, req.goal, req.turns);
|
|
49
|
+
const res = await chatCompletionForProvider(req.provider, req.apiKey, req.model, messages, {
|
|
50
|
+
baseURL: req.baseURL,
|
|
51
|
+
endpointOverride: req.endpointOverride,
|
|
52
|
+
disableTools: true,
|
|
53
|
+
maxOutputTokens: req.maxOutputTokens ?? GOAL_JUDGE_MAX_TOKENS,
|
|
54
|
+
...(req.signal ? { signal: req.signal } : {}),
|
|
55
|
+
});
|
|
56
|
+
// Totals keep accumulating: forward real judge usage when present.
|
|
57
|
+
if (res.usage !== undefined) {
|
|
58
|
+
try {
|
|
59
|
+
req.onUsage?.(res.usage);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// observer errors never break the judge
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const text = (res.content ?? "").trim();
|
|
66
|
+
if (!text)
|
|
67
|
+
throw new Error("Empty reply from model (unexpected payload).");
|
|
68
|
+
return parseGoalJudgeVerdict(text);
|
|
69
|
+
}
|