@agent-native/core 0.94.2 → 0.95.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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +20 -12
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/artifact-response.ts +154 -1
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +3 -0
- package/corpus/core/src/agent/engine/registry.ts +6 -1
- package/corpus/core/src/agent/engine/types.ts +2 -0
- package/corpus/core/src/cli/code-agent-executor.ts +61 -1
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/cli/recap.ts +262 -25
- package/corpus/core/src/client/settings/UsageSection.tsx +26 -5
- package/corpus/core/src/index.ts +2 -0
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +118 -31
- package/corpus/core/src/integrations/a2a-continuations-store.ts +165 -7
- package/corpus/core/src/integrations/adapters/slack.ts +77 -7
- package/corpus/core/src/integrations/types.ts +30 -0
- package/corpus/core/src/integrations/webhook-handler.ts +54 -18
- package/corpus/core/src/scripts/call-agent.ts +1 -0
- package/corpus/core/src/server/request-context.ts +2 -0
- package/corpus/core/src/usage/store.ts +82 -20
- package/corpus/templates/plan/actions/record-recap-usage-cost.ts +49 -0
- package/corpus/templates/plan/actions/record-recap-usage.ts +15 -22
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +127 -1
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +3 -0
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/registry.d.ts +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +2 -0
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/engine/types.d.ts +2 -0
- package/dist/agent/engine/types.d.ts.map +1 -1
- package/dist/agent/engine/types.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts +2 -0
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +52 -3
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +22 -2
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +199 -22
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/settings/UsageSection.d.ts.map +1 -1
- package/dist/client/settings/UsageSection.js +12 -3
- package/dist/client/settings/UsageSection.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +76 -21
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts +4 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +130 -6
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +56 -7
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/types.d.ts +25 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.js +54 -16
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +1 -0
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/usage/store.d.ts +21 -0
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +54 -19
- package/dist/usage/store.js.map +1 -1
- package/docs/content/pr-visual-recap.mdx +20 -12
- package/package.json +1 -1
package/dist/cli/recap.js
CHANGED
|
@@ -37,6 +37,7 @@ import { execFileSync } from "node:child_process";
|
|
|
37
37
|
import { createHash } from "node:crypto";
|
|
38
38
|
import fs from "node:fs";
|
|
39
39
|
import path from "node:path";
|
|
40
|
+
import { normalizeOpenAiBaseUrl } from "../agent/engine/openai-compatible-endpoint.js";
|
|
40
41
|
import { DEFAULT_PLAN_APP_URL, fetchPlanBlockCatalog, planActionEndpoint, } from "./plan-blocks.js";
|
|
41
42
|
import { readPlanPublishAuth } from "./plan-publish-store.js";
|
|
42
43
|
import { PR_VISUAL_RECAP_WORKFLOW_YML } from "./pr-visual-recap-workflow.js";
|
|
@@ -82,7 +83,9 @@ export const PR_VISUAL_RECAP_SETUP = [
|
|
|
82
83
|
" ANTHROPIC_API_KEY — the LLM key for the default Claude Code backend",
|
|
83
84
|
"Optional (only if you change defaults):",
|
|
84
85
|
" OPENAI_API_KEY (secret) + VISUAL_RECAP_AGENT=codex (variable) — use Codex instead of Claude",
|
|
85
|
-
"
|
|
86
|
+
" VISUAL_RECAP_API_KEY (secret) + VISUAL_RECAP_AGENT=openai-compatible + VISUAL_RECAP_BASE_URL (variable) — use DeepSeek, Kimi, or any OpenAI-compatible API",
|
|
87
|
+
" VISUAL_RECAP_MODEL (variable, required for openai-compatible) — provider model id; optional override for Claude/Codex",
|
|
88
|
+
" VISUAL_RECAP_REASONING (variable) — reasoning depth (none|minimal|low|medium|high|xhigh; Codex only)",
|
|
86
89
|
" VISUAL_RECAP_SKILL_SOURCE=repo (variable) — pin CI to the repo-local visual-recap skill instead of latest bundled guidance",
|
|
87
90
|
" VISUAL_RECAP_SECRET_SCAN=off|high-confidence|strict (variable) — default high-confidence; strict restores generic TOKEN/SECRET assignment suppression",
|
|
88
91
|
" PLAN_RECAP_APP_URL (secret) — only when self-hosting the plan app (defaults to https://plan.agent-native.com)",
|
|
@@ -154,10 +157,12 @@ export function buildReusableCallerWorkflow(options = {}) {
|
|
|
154
157
|
` PLAN_RECAP_TOKEN: \${{ secrets.PLAN_RECAP_TOKEN }}\n` +
|
|
155
158
|
` ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}\n` +
|
|
156
159
|
` OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}\n` +
|
|
160
|
+
` VISUAL_RECAP_API_KEY: \${{ secrets.VISUAL_RECAP_API_KEY }}\n` +
|
|
157
161
|
` PLAN_RECAP_APP_URL: \${{ secrets.PLAN_RECAP_APP_URL }}\n` +
|
|
158
162
|
` with:\n` +
|
|
159
163
|
` agent: ${agentValue}\n` +
|
|
160
164
|
` model: ${modelValue}\n` +
|
|
165
|
+
` base-url: \${{ vars.VISUAL_RECAP_BASE_URL || '' }}\n` +
|
|
161
166
|
` reasoning: \${{ vars.VISUAL_RECAP_REASONING || '' }}\n` +
|
|
162
167
|
` skill-source: \${{ vars.VISUAL_RECAP_SKILL_SOURCE || 'auto' }}\n` +
|
|
163
168
|
` secret-scan: \${{ vars.VISUAL_RECAP_SECRET_SCAN || 'high-confidence' }}\n` +
|
|
@@ -202,12 +207,19 @@ export function normalizeRecapAgent(value) {
|
|
|
202
207
|
return "codex";
|
|
203
208
|
if (agent === "claude")
|
|
204
209
|
return "claude";
|
|
205
|
-
|
|
210
|
+
if (["openai-compatible", "deepseek", "kimi", "moonshot", "custom"].includes(agent)) {
|
|
211
|
+
return "openai-compatible";
|
|
212
|
+
}
|
|
213
|
+
throw new Error(`Unsupported recap agent "${value}" (expected "claude", "codex", or "openai-compatible").`);
|
|
206
214
|
}
|
|
207
215
|
export function recapRequiredSecrets(agent) {
|
|
208
216
|
return [
|
|
209
217
|
"PLAN_RECAP_TOKEN",
|
|
210
|
-
agent === "codex"
|
|
218
|
+
agent === "codex"
|
|
219
|
+
? "OPENAI_API_KEY"
|
|
220
|
+
: agent === "openai-compatible"
|
|
221
|
+
? "VISUAL_RECAP_API_KEY"
|
|
222
|
+
: "ANTHROPIC_API_KEY",
|
|
211
223
|
];
|
|
212
224
|
}
|
|
213
225
|
function recapWorkflowFile(baseDir) {
|
|
@@ -338,13 +350,60 @@ function setGithubVariable(name, value, repo, dryRun) {
|
|
|
338
350
|
args.push("--repo", repo);
|
|
339
351
|
return gh(args).ok ? "set" : "failed";
|
|
340
352
|
}
|
|
353
|
+
const OPENAI_COMPATIBLE_VARIABLE_REQUIREMENTS = [
|
|
354
|
+
{
|
|
355
|
+
name: "VISUAL_RECAP_BASE_URL",
|
|
356
|
+
example: "https://provider.example/v1",
|
|
357
|
+
},
|
|
358
|
+
{ name: "VISUAL_RECAP_MODEL", example: "provider-model-id" },
|
|
359
|
+
];
|
|
360
|
+
const RECAP_MODEL_PATTERN = /^[a-zA-Z0-9._-]{1,80}$/;
|
|
361
|
+
const OPENAI_COMPATIBLE_RECAP_MODEL_PATTERN = /^[^\p{C}\p{Z}]{1,200}$/u;
|
|
362
|
+
export function validateOpenAiCompatibleRecapVariables(input) {
|
|
363
|
+
const [baseUrlRequirement, modelRequirement] = OPENAI_COMPATIBLE_VARIABLE_REQUIREMENTS;
|
|
364
|
+
const problems = [];
|
|
365
|
+
const baseUrl = input.baseUrl?.trim() || "";
|
|
366
|
+
const rawModel = input.model || "";
|
|
367
|
+
const model = rawModel.trim();
|
|
368
|
+
try {
|
|
369
|
+
const parsed = normalizeOpenAiBaseUrl(baseUrl);
|
|
370
|
+
if (!parsed)
|
|
371
|
+
throw new Error("empty");
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
problems.push({
|
|
375
|
+
requirement: baseUrlRequirement,
|
|
376
|
+
reason: "VISUAL_RECAP_BASE_URL must be a valid http(s) URL without credentials",
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
if (!model) {
|
|
380
|
+
problems.push({
|
|
381
|
+
requirement: modelRequirement,
|
|
382
|
+
reason: "VISUAL_RECAP_MODEL is required (openai-compatible backend)",
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
else if (!OPENAI_COMPATIBLE_RECAP_MODEL_PATTERN.test(rawModel)) {
|
|
386
|
+
problems.push({
|
|
387
|
+
requirement: modelRequirement,
|
|
388
|
+
reason: "invalid VISUAL_RECAP_MODEL value (must be 1-200 characters without whitespace or controls)",
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return problems;
|
|
392
|
+
}
|
|
341
393
|
export function buildRecapSetupPlan(input) {
|
|
342
394
|
const env = input.env ?? process.env;
|
|
343
395
|
const appUrl = stripTrailingSlash(input.appUrl || env.PLAN_RECAP_APP_URL || DEFAULT_RECAP_APP_URL);
|
|
344
396
|
const agent = normalizeRecapAgent(input.agent || env.VISUAL_RECAP_AGENT);
|
|
345
397
|
const requiredSecrets = recapRequiredSecrets(agent);
|
|
398
|
+
const requiredVariables = agent === "openai-compatible"
|
|
399
|
+
? OPENAI_COMPATIBLE_VARIABLE_REQUIREMENTS
|
|
400
|
+
: [];
|
|
346
401
|
const planToken = envValue(env, "PLAN_RECAP_TOKEN") ?? planTokenFromLocalStore(appUrl);
|
|
347
|
-
const llmSecretName = agent === "codex"
|
|
402
|
+
const llmSecretName = agent === "codex"
|
|
403
|
+
? "OPENAI_API_KEY"
|
|
404
|
+
: agent === "openai-compatible"
|
|
405
|
+
? "VISUAL_RECAP_API_KEY"
|
|
406
|
+
: "ANTHROPIC_API_KEY";
|
|
348
407
|
const variableValues = {};
|
|
349
408
|
if (agent !== "claude")
|
|
350
409
|
variableValues.VISUAL_RECAP_AGENT = agent;
|
|
@@ -357,6 +416,17 @@ export function buildRecapSetupPlan(input) {
|
|
|
357
416
|
if (value)
|
|
358
417
|
variableValues[key] = value;
|
|
359
418
|
}
|
|
419
|
+
if (agent === "openai-compatible") {
|
|
420
|
+
const baseUrl = envValue(env, "VISUAL_RECAP_BASE_URL");
|
|
421
|
+
if (baseUrl)
|
|
422
|
+
variableValues.VISUAL_RECAP_BASE_URL = baseUrl;
|
|
423
|
+
}
|
|
424
|
+
const variableProblems = agent === "openai-compatible"
|
|
425
|
+
? validateOpenAiCompatibleRecapVariables({
|
|
426
|
+
baseUrl: variableValues.VISUAL_RECAP_BASE_URL,
|
|
427
|
+
model: variableValues.VISUAL_RECAP_MODEL,
|
|
428
|
+
})
|
|
429
|
+
: [];
|
|
360
430
|
return {
|
|
361
431
|
agent,
|
|
362
432
|
appUrl,
|
|
@@ -364,6 +434,8 @@ export function buildRecapSetupPlan(input) {
|
|
|
364
434
|
workflowPath: path.relative(input.baseDir, recapWorkflowFile(input.baseDir)),
|
|
365
435
|
workflowExists: fs.existsSync(recapWorkflowFile(input.baseDir)),
|
|
366
436
|
requiredSecrets,
|
|
437
|
+
requiredVariables,
|
|
438
|
+
variableProblems,
|
|
367
439
|
variableValues,
|
|
368
440
|
secretValues: {
|
|
369
441
|
PLAN_RECAP_TOKEN: planToken,
|
|
@@ -469,7 +541,10 @@ function runSetup(args) {
|
|
|
469
541
|
lines.push(` Set manually: ${commandForMissingSecret(name, repo)}`);
|
|
470
542
|
}
|
|
471
543
|
}
|
|
544
|
+
const invalidVariableNames = new Set(plan.variableProblems.map((problem) => problem.requirement.name));
|
|
472
545
|
for (const [name, value] of Object.entries(plan.variableValues)) {
|
|
546
|
+
if (invalidVariableNames.has(name))
|
|
547
|
+
continue;
|
|
473
548
|
const status = setGithubVariable(name, value, repo, dryRun);
|
|
474
549
|
if (status === "set") {
|
|
475
550
|
lines.push(` ${name}: set to ${value}.`);
|
|
@@ -482,6 +557,13 @@ function runSetup(args) {
|
|
|
482
557
|
lines.push(` Set manually: ${commandForMissingVariable(name, value, repo)}`);
|
|
483
558
|
}
|
|
484
559
|
}
|
|
560
|
+
for (const problem of plan.variableProblems) {
|
|
561
|
+
const { requirement } = problem;
|
|
562
|
+
const hasValue = Boolean(plan.variableValues[requirement.name]);
|
|
563
|
+
lines.push(` ${requirement.name}: ${hasValue ? "invalid value" : "missing value"}.`);
|
|
564
|
+
lines.push(` ${problem.reason}.`);
|
|
565
|
+
lines.push(` Set manually: ${commandForMissingVariable(requirement.name, requirement.example, repo)}`);
|
|
566
|
+
}
|
|
485
567
|
}
|
|
486
568
|
lines.push("");
|
|
487
569
|
lines.push(`Next: commit ${plan.workflowPath}, then run npx @agent-native/core@latest recap doctor.`);
|
|
@@ -558,6 +640,28 @@ function runDoctor(args) {
|
|
|
558
640
|
else {
|
|
559
641
|
const configuredAgent = variables.get("VISUAL_RECAP_AGENT") || "claude";
|
|
560
642
|
lines.push(`[ok] Recap backend variable: ${configuredAgent}.`);
|
|
643
|
+
const remoteVariableProblems = plan.agent === "openai-compatible"
|
|
644
|
+
? validateOpenAiCompatibleRecapVariables({
|
|
645
|
+
baseUrl: variables.get("VISUAL_RECAP_BASE_URL"),
|
|
646
|
+
model: variables.get("VISUAL_RECAP_MODEL"),
|
|
647
|
+
})
|
|
648
|
+
: [];
|
|
649
|
+
const problemsByName = new Map(remoteVariableProblems.map((problem) => [
|
|
650
|
+
problem.requirement.name,
|
|
651
|
+
problem,
|
|
652
|
+
]));
|
|
653
|
+
for (const requirement of plan.requiredVariables) {
|
|
654
|
+
const problem = problemsByName.get(requirement.name);
|
|
655
|
+
if (!problem) {
|
|
656
|
+
lines.push(`[ok] GitHub variable configured: ${requirement.name}.`);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
ok = false;
|
|
660
|
+
const hasValue = Boolean(variables.get(requirement.name)?.trim());
|
|
661
|
+
lines.push(`[${hasValue ? "invalid" : "missing"}] ${problem.reason}.`);
|
|
662
|
+
lines.push(` Set it with: ${commandForMissingVariable(requirement.name, requirement.example, repo)}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
561
665
|
}
|
|
562
666
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
563
667
|
if (!ok)
|
|
@@ -810,6 +914,8 @@ function agentLabel(agent) {
|
|
|
810
914
|
return "Codex";
|
|
811
915
|
if (normalized === "claude")
|
|
812
916
|
return "Claude";
|
|
917
|
+
if (normalized === "openai-compatible")
|
|
918
|
+
return "OpenAI-compatible";
|
|
813
919
|
return agent || "Agent";
|
|
814
920
|
}
|
|
815
921
|
export function summarizeAgentRun(input) {
|
|
@@ -850,10 +956,18 @@ function localAgentResultCandidates(agent) {
|
|
|
850
956
|
stderrFile: "codex-stderr.log",
|
|
851
957
|
exitCodeFile: "codex-exit-code.txt",
|
|
852
958
|
},
|
|
959
|
+
{
|
|
960
|
+
agent: "openai-compatible",
|
|
961
|
+
resultFile: "openai-compatible-result.txt",
|
|
962
|
+
stderrFile: "openai-compatible-stderr.log",
|
|
963
|
+
exitCodeFile: "openai-compatible-exit-code.txt",
|
|
964
|
+
},
|
|
853
965
|
];
|
|
854
966
|
const normalized = agent.toLowerCase();
|
|
855
967
|
if (normalized === "codex")
|
|
856
|
-
return [all[1], all[0]];
|
|
968
|
+
return [all[1], all[0], all[2]];
|
|
969
|
+
if (normalized === "openai-compatible")
|
|
970
|
+
return [all[2], all[0], all[1]];
|
|
857
971
|
return all;
|
|
858
972
|
}
|
|
859
973
|
export function summarizeLocalAgentFailure(input = {}) {
|
|
@@ -2599,22 +2713,35 @@ export function evaluateRecapGate(input) {
|
|
|
2599
2713
|
// The chosen backend's API key must be present. Normalize the agent value once
|
|
2600
2714
|
// here and validate it: an unknown or mis-cased value (e.g. "Claude", "gpt")
|
|
2601
2715
|
// must NOT silently pass the gate and then match neither agent step.
|
|
2602
|
-
const
|
|
2603
|
-
|
|
2604
|
-
|
|
2716
|
+
const rawAgent = (input.agentRaw || "claude").toLowerCase();
|
|
2717
|
+
const agent = ["deepseek", "kimi", "moonshot", "custom"].includes(rawAgent)
|
|
2718
|
+
? "openai-compatible"
|
|
2719
|
+
: rawAgent;
|
|
2720
|
+
if (!["claude", "codex", "openai-compatible"].includes(agent)) {
|
|
2721
|
+
reasons.push(`unsupported VISUAL_RECAP_AGENT "${input.agentRaw}" (expected "claude", "codex", or "openai-compatible")`);
|
|
2605
2722
|
}
|
|
2606
2723
|
else if (agent === "codex") {
|
|
2607
2724
|
if (!input.hasOpenai)
|
|
2608
2725
|
reasons.push("OPENAI_API_KEY not configured (codex backend)");
|
|
2609
2726
|
}
|
|
2610
|
-
else {
|
|
2727
|
+
else if (agent === "claude") {
|
|
2611
2728
|
if (!input.hasAnthropic)
|
|
2612
2729
|
reasons.push("ANTHROPIC_API_KEY not configured (claude backend)");
|
|
2613
2730
|
}
|
|
2731
|
+
else {
|
|
2732
|
+
if (!input.hasOpenaiCompatible)
|
|
2733
|
+
reasons.push("VISUAL_RECAP_API_KEY not configured (openai-compatible backend)");
|
|
2734
|
+
reasons.push(...validateOpenAiCompatibleRecapVariables({
|
|
2735
|
+
baseUrl: input.baseUrl,
|
|
2736
|
+
model: input.model,
|
|
2737
|
+
}).map((problem) => problem.reason));
|
|
2738
|
+
}
|
|
2614
2739
|
// Validate VISUAL_RECAP_MODEL if set — an unchecked value could be injected by
|
|
2615
2740
|
// a repo settings writer and passed straight to the agent CLI.
|
|
2616
2741
|
const model = input.model || "";
|
|
2617
|
-
if (
|
|
2742
|
+
if (agent !== "openai-compatible" &&
|
|
2743
|
+
model &&
|
|
2744
|
+
!RECAP_MODEL_PATTERN.test(model)) {
|
|
2618
2745
|
reasons.push("invalid VISUAL_RECAP_MODEL value (must match [a-zA-Z0-9._-]{1,80})");
|
|
2619
2746
|
}
|
|
2620
2747
|
const skillSource = normalizeRecapSkillSourceMode(input.skillSource);
|
|
@@ -2729,8 +2856,10 @@ async function runGate() {
|
|
|
2729
2856
|
hasPlan: process.env.HAS_PLAN === "true",
|
|
2730
2857
|
hasAnthropic: process.env.HAS_ANTHROPIC === "true",
|
|
2731
2858
|
hasOpenai: process.env.HAS_OPENAI === "true",
|
|
2859
|
+
hasOpenaiCompatible: process.env.HAS_COMPATIBLE === "true",
|
|
2732
2860
|
agentRaw: process.env.AGENT,
|
|
2733
2861
|
model: process.env.VISUAL_RECAP_MODEL,
|
|
2862
|
+
baseUrl: process.env.VISUAL_RECAP_BASE_URL,
|
|
2734
2863
|
skillSource: process.env.VISUAL_RECAP_SKILL_SOURCE,
|
|
2735
2864
|
changedFiles,
|
|
2736
2865
|
});
|
|
@@ -3190,6 +3319,41 @@ export function parseCodexUsage(jsonl) {
|
|
|
3190
3319
|
model: typeof u.model === "string" ? u.model : undefined,
|
|
3191
3320
|
};
|
|
3192
3321
|
}
|
|
3322
|
+
/** Parse the usage sidecar emitted by an Agent-Native Code run. */
|
|
3323
|
+
export function parseOpenAiCompatibleUsage(json) {
|
|
3324
|
+
const obj = parseLastJsonObject(json);
|
|
3325
|
+
const usage = obj?.usage ?? obj;
|
|
3326
|
+
if (!usage || typeof usage !== "object")
|
|
3327
|
+
return null;
|
|
3328
|
+
const input = usage.inputTokens ?? usage.input_tokens ?? usage.prompt_tokens ?? undefined;
|
|
3329
|
+
const output = usage.outputTokens ??
|
|
3330
|
+
usage.output_tokens ??
|
|
3331
|
+
usage.completion_tokens ??
|
|
3332
|
+
undefined;
|
|
3333
|
+
if (input == null && output == null)
|
|
3334
|
+
return null;
|
|
3335
|
+
const asCount = (value) => {
|
|
3336
|
+
const parsed = Number(value ?? 0);
|
|
3337
|
+
return Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
|
|
3338
|
+
};
|
|
3339
|
+
const inputDetails = usage.inputTokenDetails;
|
|
3340
|
+
return {
|
|
3341
|
+
inputTokens: asCount(input),
|
|
3342
|
+
outputTokens: asCount(output),
|
|
3343
|
+
cacheReadTokens: asCount(usage.cacheReadTokens ??
|
|
3344
|
+
usage.cachedInputTokens ??
|
|
3345
|
+
usage.cache_read_input_tokens ??
|
|
3346
|
+
inputDetails?.cacheReadTokens),
|
|
3347
|
+
cacheWriteTokens: asCount(usage.cacheWriteTokens ??
|
|
3348
|
+
usage.cache_write_tokens ??
|
|
3349
|
+
inputDetails?.cacheWriteTokens),
|
|
3350
|
+
model: typeof obj?.model === "string"
|
|
3351
|
+
? obj.model
|
|
3352
|
+
: typeof usage.model === "string"
|
|
3353
|
+
? usage.model
|
|
3354
|
+
: undefined,
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3193
3357
|
/**
|
|
3194
3358
|
* `recap usage` — parse the agent's run output for token usage and POST it to
|
|
3195
3359
|
* the plan app's record-recap-usage action so the recap row carries cost. The
|
|
@@ -3214,7 +3378,12 @@ async function runUsage(args) {
|
|
|
3214
3378
|
let parsed = null;
|
|
3215
3379
|
try {
|
|
3216
3380
|
const raw = fs.readFileSync(path.resolve(stringArg(args, "result-file")), "utf8");
|
|
3217
|
-
parsed =
|
|
3381
|
+
parsed =
|
|
3382
|
+
agent === "codex"
|
|
3383
|
+
? parseCodexUsage(raw)
|
|
3384
|
+
: agent === "openai-compatible"
|
|
3385
|
+
? parseOpenAiCompatibleUsage(raw)
|
|
3386
|
+
: parseClaudeUsage(raw);
|
|
3218
3387
|
}
|
|
3219
3388
|
catch (err) {
|
|
3220
3389
|
done({ ok: false, reason: `could not read/parse usage: ${String(err)}` });
|
|
@@ -3228,10 +3397,17 @@ async function runUsage(args) {
|
|
|
3228
3397
|
// the pinned --model (VISUAL_RECAP_MODEL) and finally the documented default.
|
|
3229
3398
|
const model = parsed.model ??
|
|
3230
3399
|
optionalArg(args, "model") ??
|
|
3231
|
-
(agent === "codex"
|
|
3400
|
+
(agent === "codex"
|
|
3401
|
+
? "gpt-5.6-sol"
|
|
3402
|
+
: agent === "openai-compatible"
|
|
3403
|
+
? "openai-compatible"
|
|
3404
|
+
: "claude");
|
|
3405
|
+
const usageAgent = agent === "claude" || agent === "codex" || agent === "openai-compatible"
|
|
3406
|
+
? agent
|
|
3407
|
+
: undefined;
|
|
3232
3408
|
const body = {
|
|
3233
3409
|
planId,
|
|
3234
|
-
...(
|
|
3410
|
+
...(usageAgent ? { agent: usageAgent } : {}),
|
|
3235
3411
|
model,
|
|
3236
3412
|
inputTokens: parsed.inputTokens,
|
|
3237
3413
|
outputTokens: parsed.outputTokens,
|
|
@@ -3302,16 +3478,16 @@ function runAgentSummary(args) {
|
|
|
3302
3478
|
const HELP = `npx @agent-native/core@latest recap — PR visual recap helpers (used by the GitHub Action)
|
|
3303
3479
|
|
|
3304
3480
|
Usage:
|
|
3305
|
-
npx @agent-native/core@latest recap setup [--repo owner/name] [--agent claude|codex] [--app-url <url>] [--skip-secrets] [--dry-run] [--force]
|
|
3306
|
-
npx @agent-native/core@latest recap doctor [--repo owner/name] [--agent claude|codex] [--app-url <url>]
|
|
3481
|
+
npx @agent-native/core@latest recap setup [--repo owner/name] [--agent claude|codex|openai-compatible] [--app-url <url>] [--skip-secrets] [--dry-run] [--force]
|
|
3482
|
+
npx @agent-native/core@latest recap doctor [--repo owner/name] [--agent claude|codex|openai-compatible] [--app-url <url>]
|
|
3307
3483
|
npx @agent-native/core@latest recap collect-diff --base <baseSha> --head <headSha> [--out recap.diff] [--stat recap.stat]
|
|
3308
3484
|
npx @agent-native/core@latest recap block-reference [--app-url <url>] [--out recap-blocks.md]
|
|
3309
3485
|
npx @agent-native/core@latest recap scan --diff <path> [--mode off|high-confidence|strict]
|
|
3310
3486
|
npx @agent-native/core@latest recap build-prompt --pr <n> [--repo owner/name] [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--block-reference recap-blocks.md] [--prev-plan-id <id>] [--huge] [--local-files] [--local-dir <folder>] [--skill-source auto|latest|repo] [--out <path>]
|
|
3311
3487
|
npx @agent-native/core@latest recap publish [--source recap-source.json] [--out recap-url.txt] [--repo owner/name] [--pr <n>] [--prev-plan-id <id>] [--source-pr-state open|closed|merged] [--source-pr-merged-at <iso>] [--source-author-email <email>] [--source-author-name <name>] [--source-author-login <login>] [--app-url <url>] [--token <planToken>] [--github-token <ghToken>]
|
|
3312
3488
|
npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png] [--theme light|dark] [--image-cache-key <key>]
|
|
3313
|
-
npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex] [--model <id>]
|
|
3314
|
-
npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex]
|
|
3489
|
+
npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex|openai-compatible] [--model <id>]
|
|
3490
|
+
npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex|openai-compatible]
|
|
3315
3491
|
npx @agent-native/core@latest recap comment <find-plan-id|upsert> --repo owner/name --issue <n> --token <github-token>
|
|
3316
3492
|
npx @agent-native/core@latest recap check start [--repo owner/name] [--sha <headSha>] [--token <github-token>] [--workflow-url <url>]
|
|
3317
3493
|
Create the in-progress "Visual Recap" GitHub check run and write its id to
|
|
@@ -3328,16 +3504,16 @@ Usage:
|
|
|
3328
3504
|
The PR Visual Recap security gate. Decides whether to run the recap at all
|
|
3329
3505
|
and which (normalized) backend agent to use. Reads the pull_request payload
|
|
3330
3506
|
from $GITHUB_EVENT_PATH, the secret-presence/agent/model signals from the
|
|
3331
|
-
environment (HAS_PLAN / HAS_ANTHROPIC / HAS_OPENAI === 'true', AGENT,
|
|
3332
|
-
VISUAL_RECAP_MODEL), the repo from $GITHUB_REPOSITORY, and the PR's changed
|
|
3507
|
+
environment (HAS_PLAN / HAS_ANTHROPIC / HAS_OPENAI / HAS_COMPATIBLE === 'true', AGENT,
|
|
3508
|
+
VISUAL_RECAP_MODEL / VISUAL_RECAP_BASE_URL), the repo from $GITHUB_REPOSITORY, and the PR's changed
|
|
3333
3509
|
files from the GitHub REST API (paged, with GH_TOKEN/GITHUB_TOKEN). Skips
|
|
3334
3510
|
drafts, forks without secret access, bot authors, the missing-secret case, an
|
|
3335
3511
|
invalid agent/model, and any untrusted PR that touches recap-control files
|
|
3336
3512
|
(repo-pinned skill instructions, .claude/**, root CLAUDE.md, root AGENTS.md,
|
|
3337
3513
|
root .mcp.json) — failing CLOSED on any file-list error. Writes
|
|
3338
|
-
run=<true|false> and agent=<claude|codex> to $GITHUB_OUTPUT.
|
|
3514
|
+
run=<true|false> and agent=<claude|codex|openai-compatible> to $GITHUB_OUTPUT.
|
|
3339
3515
|
npx @agent-native/core@latest recap agent-summary
|
|
3340
|
-
Read the captured
|
|
3516
|
+
Read the captured agent result file and write a sanitized one-line
|
|
3341
3517
|
summary to stdout and $GITHUB_OUTPUT (summary). Used only when no plan URL
|
|
3342
3518
|
was produced, so PR comments/checks explain the actual failure.
|
|
3343
3519
|
npx @agent-native/core@latest recap scan
|
|
@@ -3358,7 +3534,8 @@ Usage:
|
|
|
3358
3534
|
commands; secret values are sent to gh through stdin, never argv.
|
|
3359
3535
|
npx @agent-native/core@latest recap doctor
|
|
3360
3536
|
Check workflow presence/drift, local Plans publish-token availability, gh
|
|
3361
|
-
repo access, and required GitHub Actions secrets for the
|
|
3537
|
+
repo access, and required GitHub Actions secrets and variables for the
|
|
3538
|
+
selected backend, including provider-variable validity.
|
|
3362
3539
|
`;
|
|
3363
3540
|
export async function runRecap(argv) {
|
|
3364
3541
|
const [sub, ...rest] = argv;
|