bopodev-agent-sdk 0.1.24 → 0.1.26

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.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +4 -0
  3. package/.turbo/turbo-typecheck.log +1 -1
  4. package/dist/agent-sdk/src/adapters.d.ts +12 -1
  5. package/dist/agent-sdk/src/provider-failures/anthropic-api.d.ts +5 -0
  6. package/dist/agent-sdk/src/provider-failures/claude-code.d.ts +5 -0
  7. package/dist/agent-sdk/src/provider-failures/codex.d.ts +5 -0
  8. package/dist/agent-sdk/src/provider-failures/common.d.ts +7 -0
  9. package/dist/agent-sdk/src/provider-failures/cursor.d.ts +5 -0
  10. package/dist/agent-sdk/src/provider-failures/gemini-cli.d.ts +5 -0
  11. package/dist/agent-sdk/src/provider-failures/http.d.ts +5 -0
  12. package/dist/agent-sdk/src/provider-failures/index.d.ts +5 -0
  13. package/dist/agent-sdk/src/provider-failures/openai-api.d.ts +5 -0
  14. package/dist/agent-sdk/src/provider-failures/opencode.d.ts +5 -0
  15. package/dist/agent-sdk/src/provider-failures/shell.d.ts +5 -0
  16. package/dist/agent-sdk/src/provider-failures/types.d.ts +20 -0
  17. package/dist/agent-sdk/src/runtime-core.d.ts +1 -1
  18. package/dist/agent-sdk/src/runtime-http.d.ts +4 -2
  19. package/dist/agent-sdk/src/runtime-parsers.d.ts +1 -1
  20. package/dist/agent-sdk/src/runtime.d.ts +13 -0
  21. package/dist/agent-sdk/src/types.d.ts +17 -1
  22. package/dist/contracts/src/index.d.ts +1134 -2
  23. package/package.json +2 -2
  24. package/src/adapters.ts +426 -52
  25. package/src/provider-failures/anthropic-api.ts +20 -0
  26. package/src/provider-failures/claude-code.ts +20 -0
  27. package/src/provider-failures/codex.ts +23 -0
  28. package/src/provider-failures/common.ts +86 -0
  29. package/src/provider-failures/cursor.ts +20 -0
  30. package/src/provider-failures/gemini-cli.ts +20 -0
  31. package/src/provider-failures/http.ts +12 -0
  32. package/src/provider-failures/index.ts +54 -0
  33. package/src/provider-failures/openai-api.ts +20 -0
  34. package/src/provider-failures/opencode.ts +20 -0
  35. package/src/provider-failures/shell.ts +12 -0
  36. package/src/provider-failures/types.ts +28 -0
  37. package/src/runtime-core.ts +7 -1
  38. package/src/runtime-http.ts +51 -6
  39. package/src/runtime-parsers.ts +1 -0
  40. package/src/runtime.ts +283 -0
  41. package/src/types.ts +17 -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;
@@ -380,6 +394,7 @@ export async function executePromptRuntime(
380
394
  const stdoutUsage = parseStructuredUsage(stdout);
381
395
  const stderrUsage = parseStructuredUsage(stderr);
382
396
  const usageResolution = resolveUsageSource(stdoutUsage, stderrUsage);
397
+ const finalRunOutputResolution = resolveFinalRunOutput(stdout, stderr);
383
398
  return {
384
399
  ok: true,
385
400
  code: attemptResult.code,
@@ -390,6 +405,8 @@ export async function executePromptRuntime(
390
405
  attemptCount: attempts.length,
391
406
  attempts,
392
407
  parsedUsage: usageResolution.usage,
408
+ finalRunOutput: finalRunOutputResolution.finalRunOutput,
409
+ finalRunOutputSource: finalRunOutputResolution.source,
393
410
  structuredOutputSource: usageResolution.source,
394
411
  structuredOutputDiagnostics: {
395
412
  stdoutJsonObjectCount: extractJsonObjectBlocks(stdout).length,
@@ -401,6 +418,12 @@ export async function executePromptRuntime(
401
418
  lastStdoutLine: tailLine(stdout),
402
419
  lastStderrLine: tailLine(stderr),
403
420
  likelyCause: classifyStructuredOutputLikelyCause(stdout, stderr, stdoutUsage, stderrUsage),
421
+ finalRunOutputStatus: finalRunOutputResolution.finalRunOutput
422
+ ? "valid"
423
+ : finalRunOutputResolution.error,
424
+ ...(finalRunOutputResolution.detail
425
+ ? { finalRunOutputError: finalRunOutputResolution.detail }
426
+ : {}),
404
427
  ...(claudeStream?.stopReason ? { claudeStopReason: claudeStream.stopReason } : {}),
405
428
  ...(claudeStream?.resultSubtype ? { claudeResultSubtype: claudeStream.resultSubtype } : {}),
406
429
  ...(claudeStream?.sessionId ? { claudeSessionId: claudeStream.sessionId } : {}),
@@ -439,6 +462,7 @@ export async function executePromptRuntime(
439
462
  const stdoutUsage = parseStructuredUsage(stdout);
440
463
  const stderrUsage = parseStructuredUsage(stderr);
441
464
  const usageResolution = resolveUsageSource(stdoutUsage, stderrUsage);
465
+ const finalRunOutputResolution = resolveFinalRunOutput(stdout, stderr);
442
466
  return {
443
467
  ok: false,
444
468
  code: lastResult?.code ?? null,
@@ -450,6 +474,8 @@ export async function executePromptRuntime(
450
474
  attempts,
451
475
  failureType: classifyFailure(lastResult?.timedOut ?? false, lastResult?.spawnErrorCode, lastResult?.code ?? null),
452
476
  parsedUsage: usageResolution.usage,
477
+ finalRunOutput: finalRunOutputResolution.finalRunOutput,
478
+ finalRunOutputSource: finalRunOutputResolution.source,
453
479
  structuredOutputSource: usageResolution.source,
454
480
  structuredOutputDiagnostics: {
455
481
  stdoutJsonObjectCount: extractJsonObjectBlocks(stdout).length,
@@ -461,6 +487,10 @@ export async function executePromptRuntime(
461
487
  lastStdoutLine: tailLine(stdout),
462
488
  lastStderrLine: tailLine(stderr),
463
489
  likelyCause: classifyStructuredOutputLikelyCause(stdout, stderr, stdoutUsage, stderrUsage),
490
+ finalRunOutputStatus: finalRunOutputResolution.finalRunOutput
491
+ ? "valid"
492
+ : finalRunOutputResolution.error,
493
+ ...(finalRunOutputResolution.detail ? { finalRunOutputError: finalRunOutputResolution.detail } : {}),
464
494
  ...(claudeStream?.stopReason ? { claudeStopReason: claudeStream.stopReason } : {}),
465
495
  ...(claudeStream?.resultSubtype ? { claudeResultSubtype: claudeStream.resultSubtype } : {}),
466
496
  ...(claudeStream?.sessionId ? { claudeSessionId: claudeStream.sessionId } : {}),
@@ -1320,6 +1350,24 @@ export function containsRateLimitFailure(text: string) {
1320
1350
  );
1321
1351
  }
1322
1352
 
1353
+ export function containsUsageLimitHardStopFailure(text: string) {
1354
+ const normalized = text.toLowerCase();
1355
+ return (
1356
+ normalized.includes("api usage limits") ||
1357
+ normalized.includes("you have reached your specified api usage limits") ||
1358
+ normalized.includes("you will regain access on") ||
1359
+ normalized.includes("regain access on") ||
1360
+ normalized.includes("insufficient_quota") ||
1361
+ normalized.includes("billing_hard_limit_reached") ||
1362
+ normalized.includes("exceeded your current quota") ||
1363
+ normalized.includes("usage limit reached")
1364
+ );
1365
+ }
1366
+
1367
+ export function containsProviderUsageLimitFailure(text: string) {
1368
+ return containsUsageLimitHardStopFailure(text);
1369
+ }
1370
+
1323
1371
  export async function checkRuntimeCommandHealth(
1324
1372
  command: string,
1325
1373
  options?: { cwd?: string; timeoutMs?: number; env?: Record<string, string> }
@@ -3240,6 +3288,241 @@ function extractJsonObjectBlocks(text: string) {
3240
3288
  return blocks;
3241
3289
  }
3242
3290
 
3291
+ export function parseAgentFinalRunOutput(text: string): {
3292
+ output?: AgentFinalRunOutput;
3293
+ error?: FinalRunOutputParseError;
3294
+ detail?: string;
3295
+ } {
3296
+ const candidates = extractFinalRunOutputCandidates(text);
3297
+ if (candidates.length === 0) {
3298
+ return { error: "missing", detail: "No final JSON object was found in runtime output." };
3299
+ }
3300
+ let sawMalformedJson = false;
3301
+ let lastSchemaError = "";
3302
+ for (let index = candidates.length - 1; index >= 0; index -= 1) {
3303
+ const candidate = candidates[index] ?? "";
3304
+ try {
3305
+ const parsed = JSON.parse(candidate) as unknown;
3306
+ const normalizedCandidate = normalizeAgentFinalRunOutputCandidate(parsed);
3307
+ const validated = AgentFinalRunOutputSchema.safeParse(normalizedCandidate ?? parsed);
3308
+ if (validated.success) {
3309
+ const canonical = toCanonicalFinalRunOutput(validated.data);
3310
+ if (!isPromptTemplateFinalRunOutput(canonical)) {
3311
+ return { output: canonical };
3312
+ }
3313
+ } else {
3314
+ lastSchemaError = formatFinalRunOutputSchemaError(validated.error);
3315
+ }
3316
+ const legacyOutput = toLegacyFinalRunOutput(parsed);
3317
+ if (legacyOutput) {
3318
+ return { output: legacyOutput };
3319
+ }
3320
+ } catch (error) {
3321
+ sawMalformedJson = true;
3322
+ lastSchemaError = error instanceof Error ? error.message : String(error);
3323
+ }
3324
+ }
3325
+ return {
3326
+ error: sawMalformedJson ? "malformed" : "schema_mismatch",
3327
+ detail: lastSchemaError || "Final JSON object did not match the required schema."
3328
+ };
3329
+ }
3330
+
3331
+ function extractFinalRunOutputCandidates(text: string) {
3332
+ const candidates: string[] = [];
3333
+ const trimmed = text.trim();
3334
+ if (looksLikeFinalRunOutputText(trimmed)) {
3335
+ candidates.push(trimmed);
3336
+ }
3337
+ for (const rawLine of text.split(/\r?\n/)) {
3338
+ const line = rawLine.trim();
3339
+ if (!line) {
3340
+ continue;
3341
+ }
3342
+ if (looksLikeFinalRunOutputText(line)) {
3343
+ candidates.push(line);
3344
+ }
3345
+ const parsed = parseJsonRecord(line);
3346
+ if (!parsed) {
3347
+ continue;
3348
+ }
3349
+ candidates.push(...collectPotentialFinalOutputStrings(parsed));
3350
+ }
3351
+ for (const block of extractJsonObjectBlocks(text)) {
3352
+ if (looksLikeFinalRunOutputText(block)) {
3353
+ candidates.push(block);
3354
+ }
3355
+ }
3356
+ return Array.from(new Set(candidates.map((value) => value.trim()).filter(Boolean)));
3357
+ }
3358
+
3359
+ function normalizeAgentFinalRunOutputCandidate(parsed: unknown): AgentFinalRunOutput | null {
3360
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3361
+ return null;
3362
+ }
3363
+ const record = parsed as Record<string, unknown>;
3364
+ const employeeComment =
3365
+ typeof record.employee_comment === "string" && record.employee_comment.trim().length > 0
3366
+ ? record.employee_comment.trim()
3367
+ : null;
3368
+ if (!employeeComment) {
3369
+ return null;
3370
+ }
3371
+ const results = normalizeStringList(record.results);
3372
+ const errors = normalizeStringList(record.errors);
3373
+ const artifacts = normalizeArtifactList(record.artifacts);
3374
+ return {
3375
+ employee_comment: employeeComment,
3376
+ results,
3377
+ errors,
3378
+ artifacts
3379
+ };
3380
+ }
3381
+
3382
+ function normalizeStringList(value: unknown) {
3383
+ if (typeof value === "string") {
3384
+ const trimmed = value.trim();
3385
+ return trimmed ? [trimmed] : [];
3386
+ }
3387
+ if (!Array.isArray(value)) {
3388
+ return [];
3389
+ }
3390
+ return value
3391
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
3392
+ .filter((entry): entry is string => Boolean(entry));
3393
+ }
3394
+
3395
+ function normalizeArtifactList(value: unknown): AgentFinalRunOutput["artifacts"] {
3396
+ if (!Array.isArray(value)) {
3397
+ return [];
3398
+ }
3399
+ return value
3400
+ .map((entry) => normalizeArtifactEntry(entry))
3401
+ .filter((entry): entry is AgentFinalRunOutput["artifacts"][number] => Boolean(entry));
3402
+ }
3403
+
3404
+ function normalizeArtifactEntry(value: unknown): AgentFinalRunOutput["artifacts"][number] | null {
3405
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3406
+ return null;
3407
+ }
3408
+ const record = value as Record<string, unknown>;
3409
+ const pathCandidate =
3410
+ (typeof record.path === "string" && record.path.trim()) ||
3411
+ (typeof record.relativePath === "string" && record.relativePath.trim()) ||
3412
+ (typeof record.absolutePath === "string" && record.absolutePath.trim()) ||
3413
+ "";
3414
+ if (!pathCandidate) {
3415
+ return null;
3416
+ }
3417
+ const explicitKind = typeof record.kind === "string" && record.kind.trim() ? record.kind.trim() : "";
3418
+ return {
3419
+ kind: explicitKind || inferArtifactKind(pathCandidate),
3420
+ path: pathCandidate
3421
+ };
3422
+ }
3423
+
3424
+ function inferArtifactKind(path: string) {
3425
+ const normalized = path.toLowerCase();
3426
+ if (/\.[a-z0-9]{1,10}$/i.test(normalized)) {
3427
+ return "file";
3428
+ }
3429
+ if (normalized.endsWith("/") || normalized.includes("/dist") || normalized.includes("/build")) {
3430
+ return "directory";
3431
+ }
3432
+ return "artifact";
3433
+ }
3434
+
3435
+ function collectPotentialFinalOutputStrings(value: unknown, depth = 0): string[] {
3436
+ if (depth > 6 || value === null || value === undefined) {
3437
+ return [];
3438
+ }
3439
+ if (typeof value === "string") {
3440
+ return looksLikeFinalRunOutputText(value) ? [value.trim()] : [];
3441
+ }
3442
+ if (Array.isArray(value)) {
3443
+ return value.flatMap((entry) => collectPotentialFinalOutputStrings(entry, depth + 1));
3444
+ }
3445
+ if (typeof value === "object") {
3446
+ return Object.values(value).flatMap((entry) => collectPotentialFinalOutputStrings(entry, depth + 1));
3447
+ }
3448
+ return [];
3449
+ }
3450
+
3451
+ function looksLikeFinalRunOutputText(value: string) {
3452
+ const trimmed = value.trim();
3453
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
3454
+ return false;
3455
+ }
3456
+ return trimmed.includes("\"employee_comment\"") || trimmed.includes("\"summary\"");
3457
+ }
3458
+
3459
+ function formatFinalRunOutputSchemaError(output: {
3460
+ issues: Array<{
3461
+ path: PropertyKey[];
3462
+ message: string;
3463
+ }>;
3464
+ }) {
3465
+ const issue = output.issues[0];
3466
+ if (!issue) {
3467
+ return "Final JSON object did not match the required schema.";
3468
+ }
3469
+ const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
3470
+ return `${path}${issue.message}`;
3471
+ }
3472
+
3473
+ function toCanonicalFinalRunOutput(output: AgentFinalRunOutput): AgentFinalRunOutput {
3474
+ return {
3475
+ employee_comment: output.employee_comment,
3476
+ results: output.results,
3477
+ errors: output.errors,
3478
+ artifacts: output.artifacts
3479
+ };
3480
+ }
3481
+
3482
+ function isPromptTemplateFinalRunOutput(output: AgentFinalRunOutput) {
3483
+ return (
3484
+ output.employee_comment.trim().toLowerCase() === "markdown update to the manager" &&
3485
+ output.results.length === 1 &&
3486
+ output.results[0]?.trim().toLowerCase() === "short concrete outcome" &&
3487
+ output.errors.length === 0 &&
3488
+ output.artifacts.length === 1 &&
3489
+ output.artifacts[0]?.kind === "file" &&
3490
+ output.artifacts[0]?.path === "relative/path"
3491
+ );
3492
+ }
3493
+
3494
+ function toLegacyFinalRunOutput(parsed: unknown): AgentFinalRunOutput | undefined {
3495
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3496
+ return undefined;
3497
+ }
3498
+ const record = parsed as Record<string, unknown>;
3499
+ const summary = typeof record.summary === "string" ? record.summary.trim() : "";
3500
+ if (!summary) {
3501
+ return undefined;
3502
+ }
3503
+ return {
3504
+ employee_comment: summary,
3505
+ results: [summary],
3506
+ errors: [],
3507
+ artifacts: []
3508
+ };
3509
+ }
3510
+
3511
+ function resolveFinalRunOutput(stdout: string, stderr: string): FinalRunOutputResolution {
3512
+ const stdoutResolution = parseAgentFinalRunOutput(stdout);
3513
+ if (stdoutResolution.output) {
3514
+ return { finalRunOutput: stdoutResolution.output, source: "stdout" };
3515
+ }
3516
+ const stderrResolution = parseAgentFinalRunOutput(stderr);
3517
+ if (stderrResolution.output) {
3518
+ return { finalRunOutput: stderrResolution.output, source: "stderr" };
3519
+ }
3520
+ return {
3521
+ error: stdoutResolution.error ?? stderrResolution.error ?? "missing",
3522
+ detail: stdoutResolution.detail ?? stderrResolution.detail ?? "No final JSON object was found in runtime output."
3523
+ };
3524
+ }
3525
+
3243
3526
  function tryParseUsage(candidate: string) {
3244
3527
  try {
3245
3528
  const parsed = JSON.parse(candidate) as Record<string, unknown>;
package/src/types.ts CHANGED
@@ -1,10 +1,12 @@
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
 
5
5
  export interface AgentWorkItem {
6
6
  issueId: string;
7
7
  projectId: string;
8
+ parentIssueId?: string | null;
9
+ childIssueIds?: string[];
8
10
  projectName?: string | null;
9
11
  title: string;
10
12
  body?: string | null;
@@ -62,6 +64,12 @@ export interface HeartbeatContext {
62
64
  state: AgentState;
63
65
  memoryContext?: AgentMemoryContext;
64
66
  runtime?: AgentRuntimeConfig;
67
+ wakeContext?: {
68
+ reason?: string | null;
69
+ commentId?: string | null;
70
+ commentBody?: string | null;
71
+ issueIds?: string[];
72
+ };
65
73
  }
66
74
 
67
75
  /**
@@ -87,12 +95,20 @@ export interface AdapterExecutionResult {
87
95
  tokenInput: number;
88
96
  tokenOutput: number;
89
97
  usdCost: number;
98
+ finalRunOutput?: AgentFinalRunOutput;
90
99
  usage?: AdapterNormalizedUsage;
91
100
  pricingProviderType?: string | null;
92
101
  pricingModelId?: string | null;
93
102
  outcome?: ExecutionOutcome;
94
103
  nextState?: AgentState;
95
104
  trace?: AdapterTrace;
105
+ dispositionHint?: {
106
+ kind: "provider_usage_limited";
107
+ persistStatus: "skipped";
108
+ pauseAgent: boolean;
109
+ notifyBoard: boolean;
110
+ message: string;
111
+ };
96
112
  }
97
113
 
98
114
  export interface AgentAdapter {