bopodev-agent-sdk 0.1.25 → 0.1.27
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/README.md +27 -0
- package/dist/adapters/codex/src/server/quota.d.ts +2 -0
- package/dist/agent-sdk/src/adapters.d.ts +12 -1
- package/dist/agent-sdk/src/provider-failures/anthropic-api.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/claude-code.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/codex.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/common.d.ts +7 -0
- package/dist/agent-sdk/src/provider-failures/cursor.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/gemini-cli.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/http.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/index.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/openai-api.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/opencode.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/shell.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/types.d.ts +20 -0
- package/dist/agent-sdk/src/quota.d.ts +4 -0
- package/dist/agent-sdk/src/runtime-core.d.ts +1 -1
- package/dist/agent-sdk/src/runtime-http.d.ts +4 -2
- package/dist/agent-sdk/src/runtime-parsers.d.ts +1 -1
- package/dist/agent-sdk/src/runtime.d.ts +13 -0
- package/dist/agent-sdk/src/types.d.ts +19 -1
- package/dist/contracts/src/index.d.ts +426 -11
- package/package.json +2 -2
- package/src/adapters.ts +477 -58
- package/src/provider-failures/anthropic-api.ts +20 -0
- package/src/provider-failures/claude-code.ts +20 -0
- package/src/provider-failures/codex.ts +23 -0
- package/src/provider-failures/common.ts +86 -0
- package/src/provider-failures/cursor.ts +20 -0
- package/src/provider-failures/gemini-cli.ts +20 -0
- package/src/provider-failures/http.ts +12 -0
- package/src/provider-failures/index.ts +54 -0
- package/src/provider-failures/openai-api.ts +20 -0
- package/src/provider-failures/opencode.ts +20 -0
- package/src/provider-failures/shell.ts +12 -0
- package/src/provider-failures/types.ts +28 -0
- package/src/runtime-core.ts +7 -1
- package/src/runtime-http.ts +51 -6
- package/src/runtime-parsers.ts +1 -0
- package/src/runtime.ts +299 -1
- package/src/types.ts +20 -1
package/src/runtime.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { access, cp, lstat, mkdir, mkdtemp, readdir, rm, symlink } from "node:fs
|
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
4
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { AgentFinalRunOutputSchema, type AgentFinalRunOutput } from "bopodev-contracts";
|
|
6
7
|
import type { AgentRuntimeConfig } from "./types";
|
|
7
8
|
|
|
8
9
|
type LocalProvider = "claude_code" | "codex" | "cursor" | "opencode" | "gemini_cli";
|
|
@@ -32,6 +33,15 @@ type UsageSourceResolution = {
|
|
|
32
33
|
source?: "stdout" | "stderr";
|
|
33
34
|
};
|
|
34
35
|
|
|
36
|
+
type FinalRunOutputParseError = "missing" | "malformed" | "schema_mismatch";
|
|
37
|
+
|
|
38
|
+
type FinalRunOutputResolution = {
|
|
39
|
+
finalRunOutput?: AgentFinalRunOutput;
|
|
40
|
+
source?: "stdout" | "stderr";
|
|
41
|
+
error?: FinalRunOutputParseError;
|
|
42
|
+
detail?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
35
45
|
type CursorParsedStream = {
|
|
36
46
|
usage: ParsedUsageRecord;
|
|
37
47
|
sessionId?: string;
|
|
@@ -58,6 +68,8 @@ export interface RuntimeExecutionOutput {
|
|
|
58
68
|
usdCost?: number;
|
|
59
69
|
summary?: string;
|
|
60
70
|
};
|
|
71
|
+
finalRunOutput?: AgentFinalRunOutput;
|
|
72
|
+
finalRunOutputSource?: "stdout" | "stderr";
|
|
61
73
|
structuredOutputSource?: "stdout" | "stderr";
|
|
62
74
|
structuredOutputDiagnostics?: {
|
|
63
75
|
stdoutJsonObjectCount: number;
|
|
@@ -73,6 +85,8 @@ export interface RuntimeExecutionOutput {
|
|
|
73
85
|
| "json_missing"
|
|
74
86
|
| "json_on_stderr_only"
|
|
75
87
|
| "schema_or_shape_mismatch";
|
|
88
|
+
finalRunOutputStatus?: "valid" | "missing" | "malformed" | "schema_mismatch";
|
|
89
|
+
finalRunOutputError?: string;
|
|
76
90
|
claudeStopReason?: string;
|
|
77
91
|
claudeResultSubtype?: string;
|
|
78
92
|
claudeSessionId?: string;
|
|
@@ -141,6 +155,16 @@ function providerDefaultArgs(provider: "claude_code" | "codex", config?: AgentRu
|
|
|
141
155
|
return ["exec", "--full-auto", "--skip-git-repo-check"];
|
|
142
156
|
}
|
|
143
157
|
|
|
158
|
+
function shouldPassCodexReasoningEffortFlag(config?: AgentRuntimeConfig): boolean {
|
|
159
|
+
const raw = (
|
|
160
|
+
config?.env?.BOPO_CODEX_PASS_REASONING_EFFORT ?? process.env.BOPO_CODEX_PASS_REASONING_EFFORT ??
|
|
161
|
+
""
|
|
162
|
+
)
|
|
163
|
+
.trim()
|
|
164
|
+
.toLowerCase();
|
|
165
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
166
|
+
}
|
|
167
|
+
|
|
144
168
|
function providerConfigArgs(provider: "claude_code" | "codex", config?: AgentRuntimeConfig) {
|
|
145
169
|
const args: string[] = [];
|
|
146
170
|
if (provider === "codex") {
|
|
@@ -151,7 +175,12 @@ function providerConfigArgs(provider: "claude_code" | "codex", config?: AgentRun
|
|
|
151
175
|
// Codex JSON events carry usage metrics we need for reliable cost accounting.
|
|
152
176
|
args.push("--json");
|
|
153
177
|
}
|
|
154
|
-
|
|
178
|
+
// Many `codex` builds reject `--reasoning-effort`; only pass when explicitly enabled.
|
|
179
|
+
if (
|
|
180
|
+
shouldPassCodexReasoningEffortFlag(config) &&
|
|
181
|
+
config?.thinkingEffort &&
|
|
182
|
+
config.thinkingEffort !== "auto"
|
|
183
|
+
) {
|
|
155
184
|
args.push("--reasoning-effort", config.thinkingEffort);
|
|
156
185
|
}
|
|
157
186
|
if (config?.runPolicy?.allowWebSearch) {
|
|
@@ -380,6 +409,7 @@ export async function executePromptRuntime(
|
|
|
380
409
|
const stdoutUsage = parseStructuredUsage(stdout);
|
|
381
410
|
const stderrUsage = parseStructuredUsage(stderr);
|
|
382
411
|
const usageResolution = resolveUsageSource(stdoutUsage, stderrUsage);
|
|
412
|
+
const finalRunOutputResolution = resolveFinalRunOutput(stdout, stderr);
|
|
383
413
|
return {
|
|
384
414
|
ok: true,
|
|
385
415
|
code: attemptResult.code,
|
|
@@ -390,6 +420,8 @@ export async function executePromptRuntime(
|
|
|
390
420
|
attemptCount: attempts.length,
|
|
391
421
|
attempts,
|
|
392
422
|
parsedUsage: usageResolution.usage,
|
|
423
|
+
finalRunOutput: finalRunOutputResolution.finalRunOutput,
|
|
424
|
+
finalRunOutputSource: finalRunOutputResolution.source,
|
|
393
425
|
structuredOutputSource: usageResolution.source,
|
|
394
426
|
structuredOutputDiagnostics: {
|
|
395
427
|
stdoutJsonObjectCount: extractJsonObjectBlocks(stdout).length,
|
|
@@ -401,6 +433,12 @@ export async function executePromptRuntime(
|
|
|
401
433
|
lastStdoutLine: tailLine(stdout),
|
|
402
434
|
lastStderrLine: tailLine(stderr),
|
|
403
435
|
likelyCause: classifyStructuredOutputLikelyCause(stdout, stderr, stdoutUsage, stderrUsage),
|
|
436
|
+
finalRunOutputStatus: finalRunOutputResolution.finalRunOutput
|
|
437
|
+
? "valid"
|
|
438
|
+
: finalRunOutputResolution.error,
|
|
439
|
+
...(finalRunOutputResolution.detail
|
|
440
|
+
? { finalRunOutputError: finalRunOutputResolution.detail }
|
|
441
|
+
: {}),
|
|
404
442
|
...(claudeStream?.stopReason ? { claudeStopReason: claudeStream.stopReason } : {}),
|
|
405
443
|
...(claudeStream?.resultSubtype ? { claudeResultSubtype: claudeStream.resultSubtype } : {}),
|
|
406
444
|
...(claudeStream?.sessionId ? { claudeSessionId: claudeStream.sessionId } : {}),
|
|
@@ -439,6 +477,7 @@ export async function executePromptRuntime(
|
|
|
439
477
|
const stdoutUsage = parseStructuredUsage(stdout);
|
|
440
478
|
const stderrUsage = parseStructuredUsage(stderr);
|
|
441
479
|
const usageResolution = resolveUsageSource(stdoutUsage, stderrUsage);
|
|
480
|
+
const finalRunOutputResolution = resolveFinalRunOutput(stdout, stderr);
|
|
442
481
|
return {
|
|
443
482
|
ok: false,
|
|
444
483
|
code: lastResult?.code ?? null,
|
|
@@ -450,6 +489,8 @@ export async function executePromptRuntime(
|
|
|
450
489
|
attempts,
|
|
451
490
|
failureType: classifyFailure(lastResult?.timedOut ?? false, lastResult?.spawnErrorCode, lastResult?.code ?? null),
|
|
452
491
|
parsedUsage: usageResolution.usage,
|
|
492
|
+
finalRunOutput: finalRunOutputResolution.finalRunOutput,
|
|
493
|
+
finalRunOutputSource: finalRunOutputResolution.source,
|
|
453
494
|
structuredOutputSource: usageResolution.source,
|
|
454
495
|
structuredOutputDiagnostics: {
|
|
455
496
|
stdoutJsonObjectCount: extractJsonObjectBlocks(stdout).length,
|
|
@@ -461,6 +502,10 @@ export async function executePromptRuntime(
|
|
|
461
502
|
lastStdoutLine: tailLine(stdout),
|
|
462
503
|
lastStderrLine: tailLine(stderr),
|
|
463
504
|
likelyCause: classifyStructuredOutputLikelyCause(stdout, stderr, stdoutUsage, stderrUsage),
|
|
505
|
+
finalRunOutputStatus: finalRunOutputResolution.finalRunOutput
|
|
506
|
+
? "valid"
|
|
507
|
+
: finalRunOutputResolution.error,
|
|
508
|
+
...(finalRunOutputResolution.detail ? { finalRunOutputError: finalRunOutputResolution.detail } : {}),
|
|
464
509
|
...(claudeStream?.stopReason ? { claudeStopReason: claudeStream.stopReason } : {}),
|
|
465
510
|
...(claudeStream?.resultSubtype ? { claudeResultSubtype: claudeStream.resultSubtype } : {}),
|
|
466
511
|
...(claudeStream?.sessionId ? { claudeSessionId: claudeStream.sessionId } : {}),
|
|
@@ -1320,6 +1365,24 @@ export function containsRateLimitFailure(text: string) {
|
|
|
1320
1365
|
);
|
|
1321
1366
|
}
|
|
1322
1367
|
|
|
1368
|
+
export function containsUsageLimitHardStopFailure(text: string) {
|
|
1369
|
+
const normalized = text.toLowerCase();
|
|
1370
|
+
return (
|
|
1371
|
+
normalized.includes("api usage limits") ||
|
|
1372
|
+
normalized.includes("you have reached your specified api usage limits") ||
|
|
1373
|
+
normalized.includes("you will regain access on") ||
|
|
1374
|
+
normalized.includes("regain access on") ||
|
|
1375
|
+
normalized.includes("insufficient_quota") ||
|
|
1376
|
+
normalized.includes("billing_hard_limit_reached") ||
|
|
1377
|
+
normalized.includes("exceeded your current quota") ||
|
|
1378
|
+
normalized.includes("usage limit reached")
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
export function containsProviderUsageLimitFailure(text: string) {
|
|
1383
|
+
return containsUsageLimitHardStopFailure(text);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1323
1386
|
export async function checkRuntimeCommandHealth(
|
|
1324
1387
|
command: string,
|
|
1325
1388
|
options?: { cwd?: string; timeoutMs?: number; env?: Record<string, string> }
|
|
@@ -3240,6 +3303,241 @@ function extractJsonObjectBlocks(text: string) {
|
|
|
3240
3303
|
return blocks;
|
|
3241
3304
|
}
|
|
3242
3305
|
|
|
3306
|
+
export function parseAgentFinalRunOutput(text: string): {
|
|
3307
|
+
output?: AgentFinalRunOutput;
|
|
3308
|
+
error?: FinalRunOutputParseError;
|
|
3309
|
+
detail?: string;
|
|
3310
|
+
} {
|
|
3311
|
+
const candidates = extractFinalRunOutputCandidates(text);
|
|
3312
|
+
if (candidates.length === 0) {
|
|
3313
|
+
return { error: "missing", detail: "No final JSON object was found in runtime output." };
|
|
3314
|
+
}
|
|
3315
|
+
let sawMalformedJson = false;
|
|
3316
|
+
let lastSchemaError = "";
|
|
3317
|
+
for (let index = candidates.length - 1; index >= 0; index -= 1) {
|
|
3318
|
+
const candidate = candidates[index] ?? "";
|
|
3319
|
+
try {
|
|
3320
|
+
const parsed = JSON.parse(candidate) as unknown;
|
|
3321
|
+
const normalizedCandidate = normalizeAgentFinalRunOutputCandidate(parsed);
|
|
3322
|
+
const validated = AgentFinalRunOutputSchema.safeParse(normalizedCandidate ?? parsed);
|
|
3323
|
+
if (validated.success) {
|
|
3324
|
+
const canonical = toCanonicalFinalRunOutput(validated.data);
|
|
3325
|
+
if (!isPromptTemplateFinalRunOutput(canonical)) {
|
|
3326
|
+
return { output: canonical };
|
|
3327
|
+
}
|
|
3328
|
+
} else {
|
|
3329
|
+
lastSchemaError = formatFinalRunOutputSchemaError(validated.error);
|
|
3330
|
+
}
|
|
3331
|
+
const legacyOutput = toLegacyFinalRunOutput(parsed);
|
|
3332
|
+
if (legacyOutput) {
|
|
3333
|
+
return { output: legacyOutput };
|
|
3334
|
+
}
|
|
3335
|
+
} catch (error) {
|
|
3336
|
+
sawMalformedJson = true;
|
|
3337
|
+
lastSchemaError = error instanceof Error ? error.message : String(error);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return {
|
|
3341
|
+
error: sawMalformedJson ? "malformed" : "schema_mismatch",
|
|
3342
|
+
detail: lastSchemaError || "Final JSON object did not match the required schema."
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
function extractFinalRunOutputCandidates(text: string) {
|
|
3347
|
+
const candidates: string[] = [];
|
|
3348
|
+
const trimmed = text.trim();
|
|
3349
|
+
if (looksLikeFinalRunOutputText(trimmed)) {
|
|
3350
|
+
candidates.push(trimmed);
|
|
3351
|
+
}
|
|
3352
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
3353
|
+
const line = rawLine.trim();
|
|
3354
|
+
if (!line) {
|
|
3355
|
+
continue;
|
|
3356
|
+
}
|
|
3357
|
+
if (looksLikeFinalRunOutputText(line)) {
|
|
3358
|
+
candidates.push(line);
|
|
3359
|
+
}
|
|
3360
|
+
const parsed = parseJsonRecord(line);
|
|
3361
|
+
if (!parsed) {
|
|
3362
|
+
continue;
|
|
3363
|
+
}
|
|
3364
|
+
candidates.push(...collectPotentialFinalOutputStrings(parsed));
|
|
3365
|
+
}
|
|
3366
|
+
for (const block of extractJsonObjectBlocks(text)) {
|
|
3367
|
+
if (looksLikeFinalRunOutputText(block)) {
|
|
3368
|
+
candidates.push(block);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
return Array.from(new Set(candidates.map((value) => value.trim()).filter(Boolean)));
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
function normalizeAgentFinalRunOutputCandidate(parsed: unknown): AgentFinalRunOutput | null {
|
|
3375
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3376
|
+
return null;
|
|
3377
|
+
}
|
|
3378
|
+
const record = parsed as Record<string, unknown>;
|
|
3379
|
+
const employeeComment =
|
|
3380
|
+
typeof record.employee_comment === "string" && record.employee_comment.trim().length > 0
|
|
3381
|
+
? record.employee_comment.trim()
|
|
3382
|
+
: null;
|
|
3383
|
+
if (!employeeComment) {
|
|
3384
|
+
return null;
|
|
3385
|
+
}
|
|
3386
|
+
const results = normalizeStringList(record.results);
|
|
3387
|
+
const errors = normalizeStringList(record.errors);
|
|
3388
|
+
const artifacts = normalizeArtifactList(record.artifacts);
|
|
3389
|
+
return {
|
|
3390
|
+
employee_comment: employeeComment,
|
|
3391
|
+
results,
|
|
3392
|
+
errors,
|
|
3393
|
+
artifacts
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
function normalizeStringList(value: unknown) {
|
|
3398
|
+
if (typeof value === "string") {
|
|
3399
|
+
const trimmed = value.trim();
|
|
3400
|
+
return trimmed ? [trimmed] : [];
|
|
3401
|
+
}
|
|
3402
|
+
if (!Array.isArray(value)) {
|
|
3403
|
+
return [];
|
|
3404
|
+
}
|
|
3405
|
+
return value
|
|
3406
|
+
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
|
3407
|
+
.filter((entry): entry is string => Boolean(entry));
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
function normalizeArtifactList(value: unknown): AgentFinalRunOutput["artifacts"] {
|
|
3411
|
+
if (!Array.isArray(value)) {
|
|
3412
|
+
return [];
|
|
3413
|
+
}
|
|
3414
|
+
return value
|
|
3415
|
+
.map((entry) => normalizeArtifactEntry(entry))
|
|
3416
|
+
.filter((entry): entry is AgentFinalRunOutput["artifacts"][number] => Boolean(entry));
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
function normalizeArtifactEntry(value: unknown): AgentFinalRunOutput["artifacts"][number] | null {
|
|
3420
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3421
|
+
return null;
|
|
3422
|
+
}
|
|
3423
|
+
const record = value as Record<string, unknown>;
|
|
3424
|
+
const pathCandidate =
|
|
3425
|
+
(typeof record.path === "string" && record.path.trim()) ||
|
|
3426
|
+
(typeof record.relativePath === "string" && record.relativePath.trim()) ||
|
|
3427
|
+
(typeof record.absolutePath === "string" && record.absolutePath.trim()) ||
|
|
3428
|
+
"";
|
|
3429
|
+
if (!pathCandidate) {
|
|
3430
|
+
return null;
|
|
3431
|
+
}
|
|
3432
|
+
const explicitKind = typeof record.kind === "string" && record.kind.trim() ? record.kind.trim() : "";
|
|
3433
|
+
return {
|
|
3434
|
+
kind: explicitKind || inferArtifactKind(pathCandidate),
|
|
3435
|
+
path: pathCandidate
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
function inferArtifactKind(path: string) {
|
|
3440
|
+
const normalized = path.toLowerCase();
|
|
3441
|
+
if (/\.[a-z0-9]{1,10}$/i.test(normalized)) {
|
|
3442
|
+
return "file";
|
|
3443
|
+
}
|
|
3444
|
+
if (normalized.endsWith("/") || normalized.includes("/dist") || normalized.includes("/build")) {
|
|
3445
|
+
return "directory";
|
|
3446
|
+
}
|
|
3447
|
+
return "artifact";
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
function collectPotentialFinalOutputStrings(value: unknown, depth = 0): string[] {
|
|
3451
|
+
if (depth > 6 || value === null || value === undefined) {
|
|
3452
|
+
return [];
|
|
3453
|
+
}
|
|
3454
|
+
if (typeof value === "string") {
|
|
3455
|
+
return looksLikeFinalRunOutputText(value) ? [value.trim()] : [];
|
|
3456
|
+
}
|
|
3457
|
+
if (Array.isArray(value)) {
|
|
3458
|
+
return value.flatMap((entry) => collectPotentialFinalOutputStrings(entry, depth + 1));
|
|
3459
|
+
}
|
|
3460
|
+
if (typeof value === "object") {
|
|
3461
|
+
return Object.values(value).flatMap((entry) => collectPotentialFinalOutputStrings(entry, depth + 1));
|
|
3462
|
+
}
|
|
3463
|
+
return [];
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
function looksLikeFinalRunOutputText(value: string) {
|
|
3467
|
+
const trimmed = value.trim();
|
|
3468
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
3469
|
+
return false;
|
|
3470
|
+
}
|
|
3471
|
+
return trimmed.includes("\"employee_comment\"") || trimmed.includes("\"summary\"");
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
function formatFinalRunOutputSchemaError(output: {
|
|
3475
|
+
issues: Array<{
|
|
3476
|
+
path: PropertyKey[];
|
|
3477
|
+
message: string;
|
|
3478
|
+
}>;
|
|
3479
|
+
}) {
|
|
3480
|
+
const issue = output.issues[0];
|
|
3481
|
+
if (!issue) {
|
|
3482
|
+
return "Final JSON object did not match the required schema.";
|
|
3483
|
+
}
|
|
3484
|
+
const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
3485
|
+
return `${path}${issue.message}`;
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
function toCanonicalFinalRunOutput(output: AgentFinalRunOutput): AgentFinalRunOutput {
|
|
3489
|
+
return {
|
|
3490
|
+
employee_comment: output.employee_comment,
|
|
3491
|
+
results: output.results,
|
|
3492
|
+
errors: output.errors,
|
|
3493
|
+
artifacts: output.artifacts
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
function isPromptTemplateFinalRunOutput(output: AgentFinalRunOutput) {
|
|
3498
|
+
return (
|
|
3499
|
+
output.employee_comment.trim().toLowerCase() === "markdown update to the manager" &&
|
|
3500
|
+
output.results.length === 1 &&
|
|
3501
|
+
output.results[0]?.trim().toLowerCase() === "short concrete outcome" &&
|
|
3502
|
+
output.errors.length === 0 &&
|
|
3503
|
+
output.artifacts.length === 1 &&
|
|
3504
|
+
output.artifacts[0]?.kind === "file" &&
|
|
3505
|
+
output.artifacts[0]?.path === "relative/path"
|
|
3506
|
+
);
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
function toLegacyFinalRunOutput(parsed: unknown): AgentFinalRunOutput | undefined {
|
|
3510
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3511
|
+
return undefined;
|
|
3512
|
+
}
|
|
3513
|
+
const record = parsed as Record<string, unknown>;
|
|
3514
|
+
const summary = typeof record.summary === "string" ? record.summary.trim() : "";
|
|
3515
|
+
if (!summary) {
|
|
3516
|
+
return undefined;
|
|
3517
|
+
}
|
|
3518
|
+
return {
|
|
3519
|
+
employee_comment: summary,
|
|
3520
|
+
results: [summary],
|
|
3521
|
+
errors: [],
|
|
3522
|
+
artifacts: []
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
function resolveFinalRunOutput(stdout: string, stderr: string): FinalRunOutputResolution {
|
|
3527
|
+
const stdoutResolution = parseAgentFinalRunOutput(stdout);
|
|
3528
|
+
if (stdoutResolution.output) {
|
|
3529
|
+
return { finalRunOutput: stdoutResolution.output, source: "stdout" };
|
|
3530
|
+
}
|
|
3531
|
+
const stderrResolution = parseAgentFinalRunOutput(stderr);
|
|
3532
|
+
if (stderrResolution.output) {
|
|
3533
|
+
return { finalRunOutput: stderrResolution.output, source: "stderr" };
|
|
3534
|
+
}
|
|
3535
|
+
return {
|
|
3536
|
+
error: stdoutResolution.error ?? stderrResolution.error ?? "missing",
|
|
3537
|
+
detail: stdoutResolution.detail ?? stderrResolution.detail ?? "No final JSON object was found in runtime output."
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3243
3541
|
function tryParseUsage(candidate: string) {
|
|
3244
3542
|
try {
|
|
3245
3543
|
const parsed = JSON.parse(candidate) as Record<string, unknown>;
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExecutionOutcome, ProviderType } from "bopodev-contracts";
|
|
1
|
+
import type { AgentFinalRunOutput, ExecutionOutcome, ProviderType } from "bopodev-contracts";
|
|
2
2
|
|
|
3
3
|
export type AgentProviderType = ProviderType;
|
|
4
4
|
|
|
@@ -21,6 +21,8 @@ export interface AgentWorkItem {
|
|
|
21
21
|
fileSizeBytes: number;
|
|
22
22
|
relativePath: string;
|
|
23
23
|
absolutePath: string;
|
|
24
|
+
/** API-relative path, e.g. `/issues/{issueId}/attachments/{id}/download` */
|
|
25
|
+
downloadPath?: string;
|
|
24
26
|
}>;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -41,11 +43,20 @@ export interface AgentMemoryContext {
|
|
|
41
43
|
dailyNotes: string[];
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
export type HeartbeatPromptMode = "full" | "compact";
|
|
47
|
+
|
|
44
48
|
export interface HeartbeatContext {
|
|
45
49
|
companyId: string;
|
|
46
50
|
agentId: string;
|
|
47
51
|
providerType: AgentProviderType;
|
|
48
52
|
heartbeatRunId: string;
|
|
53
|
+
/** Controls how much issue/memory text is inlined in the heartbeat prompt. Default when omitted: full. */
|
|
54
|
+
promptMode?: HeartbeatPromptMode;
|
|
55
|
+
/**
|
|
56
|
+
* When true, emit a minimal idle prompt (no assigned work). Set by the API when
|
|
57
|
+
* `BOPO_HEARTBEAT_IDLE_POLICY=micro_prompt` and there are no work items.
|
|
58
|
+
*/
|
|
59
|
+
idleMicroPrompt?: boolean;
|
|
49
60
|
company: {
|
|
50
61
|
name: string;
|
|
51
62
|
mission?: string | null;
|
|
@@ -95,12 +106,20 @@ export interface AdapterExecutionResult {
|
|
|
95
106
|
tokenInput: number;
|
|
96
107
|
tokenOutput: number;
|
|
97
108
|
usdCost: number;
|
|
109
|
+
finalRunOutput?: AgentFinalRunOutput;
|
|
98
110
|
usage?: AdapterNormalizedUsage;
|
|
99
111
|
pricingProviderType?: string | null;
|
|
100
112
|
pricingModelId?: string | null;
|
|
101
113
|
outcome?: ExecutionOutcome;
|
|
102
114
|
nextState?: AgentState;
|
|
103
115
|
trace?: AdapterTrace;
|
|
116
|
+
dispositionHint?: {
|
|
117
|
+
kind: "provider_usage_limited";
|
|
118
|
+
persistStatus: "skipped";
|
|
119
|
+
pauseAgent: boolean;
|
|
120
|
+
notifyBoard: boolean;
|
|
121
|
+
message: string;
|
|
122
|
+
};
|
|
104
123
|
}
|
|
105
124
|
|
|
106
125
|
export interface AgentAdapter {
|