bopodev-agent-sdk 0.1.14 → 0.1.15
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/dist/adapters/claude-code/src/server/parse.d.ts +12 -1
- package/dist/adapters/codex/src/server/parse.d.ts +12 -1
- package/dist/adapters/cursor/src/server/parse.d.ts +12 -1
- package/dist/adapters/gemini-cli/src/server/parse.d.ts +12 -0
- package/dist/agent-sdk/src/adapters.d.ts +33 -15
- package/dist/agent-sdk/src/runtime-parsers.d.ts +1 -1
- package/dist/agent-sdk/src/runtime.d.ts +2 -6
- package/dist/agent-sdk/src/types.d.ts +17 -0
- package/package.json +2 -2
- package/src/adapters.ts +350 -70
- package/src/registry.ts +7 -3
- package/src/runtime-http.ts +1 -1
- package/src/runtime-parsers.ts +1 -0
- package/src/runtime.ts +199 -27
- package/src/types.ts +18 -0
- package/.turbo/turbo-typecheck.log +0 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> bopodev-agent-sdk@0.1.
|
|
3
|
+
> bopodev-agent-sdk@0.1.15 build /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopodev/packages/agent-sdk
|
|
4
4
|
> tsc -p tsconfig.json --emitDeclarationOnly
|
|
5
5
|
|
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AdapterRuntimeUsageResolution } from "../../../../agent-sdk/src/adapters";
|
|
2
2
|
export { isClaudeRunIncomplete, isUnknownSessionError as isClaudeUnknownSessionError } from "../../../../agent-sdk/src/adapters";
|
|
3
|
+
export declare function resolveClaudeRuntimeUsage(input: {
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
parsedUsage?: {
|
|
7
|
+
tokenInput?: number;
|
|
8
|
+
tokenOutput?: number;
|
|
9
|
+
usdCost?: number;
|
|
10
|
+
summary?: string;
|
|
11
|
+
};
|
|
12
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
13
|
+
}): AdapterRuntimeUsageResolution;
|
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AdapterRuntimeUsageResolution } from "../../../../agent-sdk/src/adapters";
|
|
2
2
|
export { isUnknownSessionError as isCodexUnknownSessionError } from "../../../../agent-sdk/src/adapters";
|
|
3
|
+
export declare function resolveCodexRuntimeUsage(input: {
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
parsedUsage?: {
|
|
7
|
+
tokenInput?: number;
|
|
8
|
+
tokenOutput?: number;
|
|
9
|
+
usdCost?: number;
|
|
10
|
+
summary?: string;
|
|
11
|
+
};
|
|
12
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
13
|
+
}): AdapterRuntimeUsageResolution;
|
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AdapterRuntimeUsageResolution } from "../../../../agent-sdk/src/adapters";
|
|
2
2
|
export { isUnknownSessionError as isCursorUnknownSessionError, readRuntimeSessionId } from "../../../../agent-sdk/src/adapters";
|
|
3
|
+
export declare function resolveCursorRuntimeUsage(input: {
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
parsedUsage?: {
|
|
7
|
+
tokenInput?: number;
|
|
8
|
+
tokenOutput?: number;
|
|
9
|
+
usdCost?: number;
|
|
10
|
+
summary?: string;
|
|
11
|
+
};
|
|
12
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
13
|
+
}): AdapterRuntimeUsageResolution;
|
|
@@ -1 +1,13 @@
|
|
|
1
|
+
import type { AdapterRuntimeUsageResolution } from "../../../../agent-sdk/src/adapters";
|
|
1
2
|
export { parseGeminiOutput, isGeminiUnknownSessionError } from "../../../../agent-sdk/src/adapters";
|
|
3
|
+
export declare function resolveGeminiRuntimeUsage(input: {
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
parsedUsage?: {
|
|
7
|
+
tokenInput?: number;
|
|
8
|
+
tokenOutput?: number;
|
|
9
|
+
usdCost?: number;
|
|
10
|
+
summary?: string;
|
|
11
|
+
};
|
|
12
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
13
|
+
}): AdapterRuntimeUsageResolution;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
-
import type { AdapterEnvironmentCheck, AdapterEnvironmentResult, AdapterExecutionResult, AdapterMetadata, AdapterModelOption, AgentAdapter, AgentProviderType, AgentRuntimeConfig, HeartbeatContext } from "./types";
|
|
1
|
+
import type { AdapterEnvironmentCheck, AdapterEnvironmentResult, AdapterExecutionResult, AdapterMetadata, AdapterModelOption, AdapterNormalizedUsage, AgentAdapter, AgentProviderType, AgentRuntimeConfig, HeartbeatContext } from "./types";
|
|
2
2
|
import { type DirectApiProvider } from "./runtime-http";
|
|
3
|
+
type RuntimeParsedUsage = {
|
|
4
|
+
tokenInput?: number;
|
|
5
|
+
tokenOutput?: number;
|
|
6
|
+
usdCost?: number;
|
|
7
|
+
summary?: string;
|
|
8
|
+
inputTokens?: number;
|
|
9
|
+
cachedInputTokens?: number;
|
|
10
|
+
outputTokens?: number;
|
|
11
|
+
costUsd?: number;
|
|
12
|
+
};
|
|
13
|
+
export type AdapterRuntimeUsageResolution = {
|
|
14
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
15
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
16
|
+
};
|
|
17
|
+
export type AdapterRuntimeUsageResolver = (runtime: {
|
|
18
|
+
stdout: string;
|
|
19
|
+
stderr: string;
|
|
20
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
21
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
22
|
+
}) => AdapterRuntimeUsageResolution;
|
|
3
23
|
export declare class ClaudeCodeAdapter implements AgentAdapter {
|
|
4
24
|
providerType: "claude_code";
|
|
5
25
|
execute(context: HeartbeatContext): Promise<AdapterExecutionResult>;
|
|
@@ -47,17 +67,18 @@ export declare function testDirectApiEnvironment(providerType: DirectApiProvider
|
|
|
47
67
|
export declare function runProviderWork(context: HeartbeatContext, provider: "claude_code" | "codex", pricing: {
|
|
48
68
|
inputRate: number;
|
|
49
69
|
outputRate: number;
|
|
70
|
+
}, options?: {
|
|
71
|
+
usageResolver?: AdapterRuntimeUsageResolver;
|
|
72
|
+
}): Promise<AdapterExecutionResult>;
|
|
73
|
+
export declare function runCursorWork(context: HeartbeatContext, options?: {
|
|
74
|
+
usageResolver?: AdapterRuntimeUsageResolver;
|
|
50
75
|
}): Promise<AdapterExecutionResult>;
|
|
51
|
-
export declare function runCursorWork(context: HeartbeatContext): Promise<AdapterExecutionResult>;
|
|
52
76
|
export declare function runOpenCodeWork(context: HeartbeatContext): Promise<AdapterExecutionResult>;
|
|
53
|
-
export declare function runGeminiCliWork(context: HeartbeatContext
|
|
77
|
+
export declare function runGeminiCliWork(context: HeartbeatContext, options?: {
|
|
78
|
+
usageResolver?: AdapterRuntimeUsageResolver;
|
|
79
|
+
}): Promise<AdapterExecutionResult>;
|
|
54
80
|
export declare function resolveFailedUsage(runtime: {
|
|
55
|
-
parsedUsage?:
|
|
56
|
-
tokenInput?: number;
|
|
57
|
-
tokenOutput?: number;
|
|
58
|
-
usdCost?: number;
|
|
59
|
-
summary?: string;
|
|
60
|
-
};
|
|
81
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
61
82
|
failureType?: "timeout" | "spawn_error" | "nonzero_exit";
|
|
62
83
|
stdout: string;
|
|
63
84
|
stderr: string;
|
|
@@ -65,11 +86,13 @@ export declare function resolveFailedUsage(runtime: {
|
|
|
65
86
|
tokenInput: number;
|
|
66
87
|
tokenOutput: number;
|
|
67
88
|
usdCost: number;
|
|
89
|
+
usage: AdapterNormalizedUsage | undefined;
|
|
68
90
|
source: "structured";
|
|
69
91
|
} | {
|
|
70
92
|
tokenInput: number;
|
|
71
93
|
tokenOutput: number;
|
|
72
94
|
usdCost: number;
|
|
95
|
+
usage: AdapterNormalizedUsage;
|
|
73
96
|
source: "none";
|
|
74
97
|
};
|
|
75
98
|
export declare function toProviderResult(context: HeartbeatContext, provider: AgentProviderType, prompt: string, runtime: {
|
|
@@ -90,12 +113,7 @@ export declare function toProviderResult(context: HeartbeatContext, provider: Ag
|
|
|
90
113
|
spawnErrorCode?: string;
|
|
91
114
|
forcedKill: boolean;
|
|
92
115
|
}>;
|
|
93
|
-
parsedUsage?:
|
|
94
|
-
tokenInput?: number;
|
|
95
|
-
tokenOutput?: number;
|
|
96
|
-
usdCost?: number;
|
|
97
|
-
summary?: string;
|
|
98
|
-
};
|
|
116
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
99
117
|
structuredOutputSource?: "stdout" | "stderr";
|
|
100
118
|
structuredOutputDiagnostics?: {
|
|
101
119
|
stdoutJsonObjectCount: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { parseClaudeStreamOutput, parseCursorStreamOutput, parseRuntimeTranscript, parseStructuredUsage } from "./runtime";
|
|
1
|
+
export { parseClaudeStreamOutput, parseCursorStreamOutput, parseGeminiStreamOutput, parseRuntimeTranscript, parseStructuredUsage } from "./runtime";
|
|
@@ -97,13 +97,9 @@ export declare function containsRateLimitFailure(text: string): boolean;
|
|
|
97
97
|
export declare function checkRuntimeCommandHealth(command: string, options?: {
|
|
98
98
|
cwd?: string;
|
|
99
99
|
timeoutMs?: number;
|
|
100
|
+
env?: Record<string, string>;
|
|
100
101
|
}): Promise<RuntimeCommandHealth>;
|
|
101
|
-
export declare function parseStructuredUsage(stdout: string):
|
|
102
|
-
tokenInput: number | undefined;
|
|
103
|
-
tokenOutput: number | undefined;
|
|
104
|
-
usdCost: number | undefined;
|
|
105
|
-
summary: string | undefined;
|
|
106
|
-
} | undefined;
|
|
102
|
+
export declare function parseStructuredUsage(stdout: string): ParsedUsageRecord | undefined;
|
|
107
103
|
export declare function parseClaudeStreamOutput(stdout: string): {
|
|
108
104
|
usage: {
|
|
109
105
|
summary: string | undefined;
|
|
@@ -58,12 +58,29 @@ export interface HeartbeatContext {
|
|
|
58
58
|
memoryContext?: AgentMemoryContext;
|
|
59
59
|
runtime?: AgentRuntimeConfig;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Normalized usage contract produced by adapter execution.
|
|
63
|
+
*
|
|
64
|
+
* Invariants:
|
|
65
|
+
* - `inputTokens` excludes cache reads.
|
|
66
|
+
* - `cachedInputTokens` tracks cache-hit prompt tokens only.
|
|
67
|
+
* - persisted `tokenInput` = `inputTokens + cachedInputTokens` for backwards compatibility.
|
|
68
|
+
* - `outputTokens` reflects generated/completion tokens.
|
|
69
|
+
*/
|
|
70
|
+
export interface AdapterNormalizedUsage {
|
|
71
|
+
inputTokens: number;
|
|
72
|
+
cachedInputTokens: number;
|
|
73
|
+
outputTokens: number;
|
|
74
|
+
costUsd?: number;
|
|
75
|
+
summary?: string;
|
|
76
|
+
}
|
|
61
77
|
export interface AdapterExecutionResult {
|
|
62
78
|
status: "ok" | "skipped" | "failed";
|
|
63
79
|
summary: string;
|
|
64
80
|
tokenInput: number;
|
|
65
81
|
tokenOutput: number;
|
|
66
82
|
usdCost: number;
|
|
83
|
+
usage?: AdapterNormalizedUsage;
|
|
67
84
|
pricingProviderType?: string | null;
|
|
68
85
|
pricingModelId?: string | null;
|
|
69
86
|
outcome?: ExecutionOutcome;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bopodev-agent-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"bopodev-contracts": "0.1.
|
|
9
|
+
"bopodev-contracts": "0.1.15"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc -p tsconfig.json --emitDeclarationOnly",
|
package/src/adapters.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
AdapterExecutionResult,
|
|
5
5
|
AdapterMetadata,
|
|
6
6
|
AdapterModelOption,
|
|
7
|
+
AdapterNormalizedUsage,
|
|
7
8
|
AgentAdapter,
|
|
8
9
|
AgentProviderType,
|
|
9
10
|
AgentRuntimeConfig,
|
|
@@ -11,6 +12,12 @@ import type {
|
|
|
11
12
|
} from "./types";
|
|
12
13
|
import { ExecutionOutcomeSchema, type ExecutionOutcome } from "bopodev-contracts";
|
|
13
14
|
import { checkRuntimeCommandHealth, containsRateLimitFailure, executeAgentRuntime, executePromptRuntime } from "./runtime-core";
|
|
15
|
+
import {
|
|
16
|
+
parseClaudeStreamOutput,
|
|
17
|
+
parseCursorStreamOutput,
|
|
18
|
+
parseGeminiStreamOutput,
|
|
19
|
+
parseStructuredUsage
|
|
20
|
+
} from "./runtime-parsers";
|
|
14
21
|
import {
|
|
15
22
|
executeDirectApiRuntime,
|
|
16
23
|
probeDirectApiEnvironment,
|
|
@@ -39,6 +46,195 @@ function isRateLimitedRuntimeFailure(runtime: { stdout: string; stderr: string }
|
|
|
39
46
|
return containsRateLimitFailure(`${detail ?? ""}\n${runtime.stderr}\n${runtime.stdout}`);
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
type RuntimeParsedUsage = {
|
|
50
|
+
tokenInput?: number;
|
|
51
|
+
tokenOutput?: number;
|
|
52
|
+
usdCost?: number;
|
|
53
|
+
summary?: string;
|
|
54
|
+
inputTokens?: number;
|
|
55
|
+
cachedInputTokens?: number;
|
|
56
|
+
outputTokens?: number;
|
|
57
|
+
costUsd?: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type AdapterRuntimeUsageResolution = {
|
|
61
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
62
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type AdapterRuntimeUsageResolver = (runtime: {
|
|
66
|
+
stdout: string;
|
|
67
|
+
stderr: string;
|
|
68
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
69
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
70
|
+
}) => AdapterRuntimeUsageResolution;
|
|
71
|
+
|
|
72
|
+
function withResolvedRuntimeUsage<
|
|
73
|
+
T extends {
|
|
74
|
+
stdout: string;
|
|
75
|
+
stderr: string;
|
|
76
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
77
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
78
|
+
}
|
|
79
|
+
>(
|
|
80
|
+
runtime: T,
|
|
81
|
+
usageResolver?: AdapterRuntimeUsageResolver
|
|
82
|
+
): Omit<T, "parsedUsage" | "structuredOutputSource"> & {
|
|
83
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
84
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
85
|
+
} {
|
|
86
|
+
if (!usageResolver) {
|
|
87
|
+
return runtime;
|
|
88
|
+
}
|
|
89
|
+
const resolution = usageResolver({
|
|
90
|
+
stdout: runtime.stdout,
|
|
91
|
+
stderr: runtime.stderr,
|
|
92
|
+
parsedUsage: runtime.parsedUsage,
|
|
93
|
+
structuredOutputSource: runtime.structuredOutputSource
|
|
94
|
+
});
|
|
95
|
+
if (!resolution.parsedUsage && !resolution.structuredOutputSource) {
|
|
96
|
+
return runtime;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
...runtime,
|
|
100
|
+
parsedUsage: resolution.parsedUsage ?? runtime.parsedUsage,
|
|
101
|
+
structuredOutputSource: resolution.structuredOutputSource ?? runtime.structuredOutputSource
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function toNormalizedUsage(usage: RuntimeParsedUsage | undefined): AdapterNormalizedUsage | undefined {
|
|
106
|
+
if (!usage) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const inputTokens = usage.inputTokens ?? usage.tokenInput ?? 0;
|
|
110
|
+
const cachedInputTokens = usage.cachedInputTokens ?? 0;
|
|
111
|
+
const outputTokens = usage.outputTokens ?? usage.tokenOutput ?? 0;
|
|
112
|
+
const costUsd = usage.costUsd ?? usage.usdCost;
|
|
113
|
+
const summary = usage.summary;
|
|
114
|
+
return {
|
|
115
|
+
inputTokens: Math.max(0, inputTokens),
|
|
116
|
+
cachedInputTokens: Math.max(0, cachedInputTokens),
|
|
117
|
+
outputTokens: Math.max(0, outputTokens),
|
|
118
|
+
...(costUsd !== undefined ? { costUsd } : {}),
|
|
119
|
+
...(summary ? { summary } : {})
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function usageTokenInputTotal(usage: RuntimeParsedUsage | undefined) {
|
|
124
|
+
if (!usage) {
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
if (usage.inputTokens !== undefined || usage.cachedInputTokens !== undefined) {
|
|
128
|
+
return Math.max(0, (usage.inputTokens ?? 0) + (usage.cachedInputTokens ?? 0));
|
|
129
|
+
}
|
|
130
|
+
return Math.max(0, usage.tokenInput ?? 0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function hasUsageMetrics(usage: RuntimeParsedUsage | undefined) {
|
|
134
|
+
if (!usage) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return usage.tokenInput !== undefined || usage.tokenOutput !== undefined || usage.usdCost !== undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function resolveCodexDefaultRuntimeUsage(input: {
|
|
141
|
+
stdout: string;
|
|
142
|
+
stderr: string;
|
|
143
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
144
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
145
|
+
}): AdapterRuntimeUsageResolution {
|
|
146
|
+
const stdoutUsage = parseStructuredUsage(input.stdout);
|
|
147
|
+
const stderrUsage = parseStructuredUsage(input.stderr);
|
|
148
|
+
if (!hasUsageMetrics(stdoutUsage) && hasUsageMetrics(stderrUsage)) {
|
|
149
|
+
return { parsedUsage: { ...stdoutUsage, ...stderrUsage }, structuredOutputSource: "stderr" };
|
|
150
|
+
}
|
|
151
|
+
if (hasUsageMetrics(stdoutUsage)) {
|
|
152
|
+
return { parsedUsage: stdoutUsage, structuredOutputSource: "stdout" };
|
|
153
|
+
}
|
|
154
|
+
if (hasUsageMetrics(stderrUsage)) {
|
|
155
|
+
return { parsedUsage: stderrUsage, structuredOutputSource: "stderr" };
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
parsedUsage: stdoutUsage ?? stderrUsage ?? input.parsedUsage,
|
|
159
|
+
structuredOutputSource: input.structuredOutputSource
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function resolveClaudeDefaultRuntimeUsage(input: {
|
|
164
|
+
stdout: string;
|
|
165
|
+
stderr: string;
|
|
166
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
167
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
168
|
+
}): AdapterRuntimeUsageResolution {
|
|
169
|
+
const parsed = parseClaudeStreamOutput(input.stdout);
|
|
170
|
+
if (parsed?.usage) {
|
|
171
|
+
return {
|
|
172
|
+
parsedUsage: {
|
|
173
|
+
summary: parsed.usage.summary ?? input.parsedUsage?.summary,
|
|
174
|
+
tokenInput: parsed.usage.tokenInput,
|
|
175
|
+
tokenOutput: parsed.usage.tokenOutput,
|
|
176
|
+
usdCost: parsed.usage.usdCost,
|
|
177
|
+
inputTokens: parsed.usage.tokenInput ?? 0,
|
|
178
|
+
cachedInputTokens: 0,
|
|
179
|
+
outputTokens: parsed.usage.tokenOutput ?? 0,
|
|
180
|
+
costUsd: parsed.usage.usdCost
|
|
181
|
+
},
|
|
182
|
+
structuredOutputSource: "stdout"
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return resolveCodexDefaultRuntimeUsage(input);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function resolveCursorDefaultRuntimeUsage(input: {
|
|
189
|
+
stdout: string;
|
|
190
|
+
stderr: string;
|
|
191
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
192
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
193
|
+
}): AdapterRuntimeUsageResolution {
|
|
194
|
+
const parsed = parseCursorStreamOutput(input.stdout);
|
|
195
|
+
if (parsed?.usage) {
|
|
196
|
+
return {
|
|
197
|
+
parsedUsage: {
|
|
198
|
+
summary: parsed.usage.summary ?? input.parsedUsage?.summary,
|
|
199
|
+
tokenInput: parsed.usage.tokenInput,
|
|
200
|
+
tokenOutput: parsed.usage.tokenOutput,
|
|
201
|
+
usdCost: parsed.usage.usdCost,
|
|
202
|
+
inputTokens: parsed.usage.tokenInput ?? 0,
|
|
203
|
+
cachedInputTokens: 0,
|
|
204
|
+
outputTokens: parsed.usage.tokenOutput ?? 0,
|
|
205
|
+
costUsd: parsed.usage.usdCost
|
|
206
|
+
},
|
|
207
|
+
structuredOutputSource: "stdout"
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return resolveCodexDefaultRuntimeUsage(input);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function resolveGeminiDefaultRuntimeUsage(input: {
|
|
214
|
+
stdout: string;
|
|
215
|
+
stderr: string;
|
|
216
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
217
|
+
structuredOutputSource?: "stdout" | "stderr";
|
|
218
|
+
}): AdapterRuntimeUsageResolution {
|
|
219
|
+
const parsed = parseGeminiStreamOutput(input.stdout, input.stderr);
|
|
220
|
+
if (parsed?.usage) {
|
|
221
|
+
return {
|
|
222
|
+
parsedUsage: {
|
|
223
|
+
summary: parsed.usage.summary ?? input.parsedUsage?.summary,
|
|
224
|
+
tokenInput: parsed.usage.tokenInput,
|
|
225
|
+
tokenOutput: parsed.usage.tokenOutput,
|
|
226
|
+
usdCost: parsed.usage.usdCost,
|
|
227
|
+
inputTokens: parsed.usage.tokenInput ?? 0,
|
|
228
|
+
cachedInputTokens: 0,
|
|
229
|
+
outputTokens: parsed.usage.tokenOutput ?? 0,
|
|
230
|
+
costUsd: parsed.usage.usdCost
|
|
231
|
+
},
|
|
232
|
+
structuredOutputSource: "stdout"
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return resolveCodexDefaultRuntimeUsage(input);
|
|
236
|
+
}
|
|
237
|
+
|
|
42
238
|
export class ClaudeCodeAdapter implements AgentAdapter {
|
|
43
239
|
providerType = "claude_code" as const;
|
|
44
240
|
|
|
@@ -699,11 +895,27 @@ export async function testDirectApiEnvironment(
|
|
|
699
895
|
export async function runProviderWork(
|
|
700
896
|
context: HeartbeatContext,
|
|
701
897
|
provider: "claude_code" | "codex",
|
|
702
|
-
pricing: { inputRate: number; outputRate: number }
|
|
898
|
+
pricing: { inputRate: number; outputRate: number },
|
|
899
|
+
options?: { usageResolver?: AdapterRuntimeUsageResolver }
|
|
703
900
|
): Promise<AdapterExecutionResult> {
|
|
901
|
+
const usageResolver =
|
|
902
|
+
options?.usageResolver ?? (provider === "claude_code" ? resolveClaudeDefaultRuntimeUsage : resolveCodexDefaultRuntimeUsage);
|
|
704
903
|
const pricingProviderType = resolveCanonicalPricingProviderKey(provider);
|
|
705
904
|
const prompt = createPrompt(context);
|
|
706
|
-
const
|
|
905
|
+
const hasCodexResume = provider === "codex" && hasCodexResumeArgs(context.runtime?.args ?? []);
|
|
906
|
+
let runtimeOutput = await executeAgentRuntime(
|
|
907
|
+
provider,
|
|
908
|
+
prompt,
|
|
909
|
+
hasCodexResume ? { ...context.runtime, retryCount: 0 } : context.runtime
|
|
910
|
+
);
|
|
911
|
+
if (provider === "codex" && !runtimeOutput.ok && hasCodexResume) {
|
|
912
|
+
runtimeOutput = await executeAgentRuntime(provider, prompt, {
|
|
913
|
+
...context.runtime,
|
|
914
|
+
retryCount: 0,
|
|
915
|
+
args: stripCodexResumeArgs(context.runtime?.args ?? [])
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
const runtime = withResolvedRuntimeUsage(runtimeOutput, usageResolver);
|
|
707
919
|
const pricingModelId = resolvePricingModelId(context.runtime?.model, runtime);
|
|
708
920
|
if (runtime.ok) {
|
|
709
921
|
if (!runtime.parsedUsage) {
|
|
@@ -746,12 +958,14 @@ export async function runProviderWork(
|
|
|
746
958
|
}
|
|
747
959
|
if (provider === "claude_code" && isClaudeRunIncomplete(runtime)) {
|
|
748
960
|
const detail = "Claude run reached max-turns before completing execution for this issue.";
|
|
961
|
+
const usage = toNormalizedUsage(runtime.parsedUsage);
|
|
749
962
|
return {
|
|
750
963
|
status: "failed",
|
|
751
964
|
summary: runtime.parsedUsage?.summary ?? `${provider} runtime failed: ${detail}`,
|
|
752
|
-
tokenInput: runtime.parsedUsage
|
|
753
|
-
tokenOutput: runtime.parsedUsage?.tokenOutput ?? 0,
|
|
754
|
-
usdCost: runtime.parsedUsage?.usdCost ?? 0,
|
|
965
|
+
tokenInput: usageTokenInputTotal(runtime.parsedUsage),
|
|
966
|
+
tokenOutput: runtime.parsedUsage?.outputTokens ?? runtime.parsedUsage?.tokenOutput ?? 0,
|
|
967
|
+
usdCost: runtime.parsedUsage?.costUsd ?? runtime.parsedUsage?.usdCost ?? 0,
|
|
968
|
+
usage,
|
|
755
969
|
pricingProviderType,
|
|
756
970
|
pricingModelId,
|
|
757
971
|
outcome: toOutcome({
|
|
@@ -783,9 +997,10 @@ export async function runProviderWork(
|
|
|
783
997
|
nextState: context.state
|
|
784
998
|
};
|
|
785
999
|
}
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
const
|
|
1000
|
+
const usage = toNormalizedUsage(runtime.parsedUsage);
|
|
1001
|
+
const tokenInput = usageTokenInputTotal(runtime.parsedUsage);
|
|
1002
|
+
const tokenOutput = runtime.parsedUsage?.outputTokens ?? runtime.parsedUsage?.tokenOutput ?? 0;
|
|
1003
|
+
const usdCost = runtime.parsedUsage?.costUsd ?? runtime.parsedUsage?.usdCost ?? 0;
|
|
789
1004
|
const summary = runtime.parsedUsage?.summary ?? `${provider} runtime finished in ${runtime.elapsedMs}ms.`;
|
|
790
1005
|
|
|
791
1006
|
return {
|
|
@@ -794,6 +1009,7 @@ export async function runProviderWork(
|
|
|
794
1009
|
tokenInput,
|
|
795
1010
|
tokenOutput,
|
|
796
1011
|
usdCost,
|
|
1012
|
+
usage,
|
|
797
1013
|
pricingProviderType,
|
|
798
1014
|
pricingModelId,
|
|
799
1015
|
outcome: toOutcome({
|
|
@@ -834,6 +1050,7 @@ export async function runProviderWork(
|
|
|
834
1050
|
tokenInput: failedUsage.tokenInput,
|
|
835
1051
|
tokenOutput: failedUsage.tokenOutput,
|
|
836
1052
|
usdCost: failedUsage.usdCost,
|
|
1053
|
+
usage: failedUsage.usage,
|
|
837
1054
|
pricingProviderType,
|
|
838
1055
|
pricingModelId,
|
|
839
1056
|
outcome: toOutcome({
|
|
@@ -872,7 +1089,11 @@ export async function runProviderWork(
|
|
|
872
1089
|
};
|
|
873
1090
|
}
|
|
874
1091
|
|
|
875
|
-
export async function runCursorWork(
|
|
1092
|
+
export async function runCursorWork(
|
|
1093
|
+
context: HeartbeatContext,
|
|
1094
|
+
options?: { usageResolver?: AdapterRuntimeUsageResolver }
|
|
1095
|
+
): Promise<AdapterExecutionResult> {
|
|
1096
|
+
const usageResolver = options?.usageResolver ?? resolveCursorDefaultRuntimeUsage;
|
|
876
1097
|
const prompt = createPrompt(context);
|
|
877
1098
|
const cursorLaunch = await resolveCursorLaunchConfig(context.runtime);
|
|
878
1099
|
const cwd = context.runtime?.cwd?.trim() || process.cwd();
|
|
@@ -892,16 +1113,19 @@ export async function runCursorWork(context: HeartbeatContext): Promise<AdapterE
|
|
|
892
1113
|
}
|
|
893
1114
|
return [...baseArgs, ...(context.runtime?.args ?? [])];
|
|
894
1115
|
};
|
|
895
|
-
const runtime =
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1116
|
+
const runtime = withResolvedRuntimeUsage(
|
|
1117
|
+
await executePromptRuntime(
|
|
1118
|
+
cursorLaunch.command,
|
|
1119
|
+
prompt,
|
|
1120
|
+
{
|
|
1121
|
+
...context.runtime,
|
|
1122
|
+
timeoutMs: runtimeTimeoutMs,
|
|
1123
|
+
retryCount: 0,
|
|
1124
|
+
args: buildArgs(resumeState.resumeSessionId)
|
|
1125
|
+
},
|
|
1126
|
+
{ provider: "cursor" }
|
|
1127
|
+
),
|
|
1128
|
+
usageResolver
|
|
905
1129
|
);
|
|
906
1130
|
const initialSessionId = readRuntimeSessionId(
|
|
907
1131
|
runtime,
|
|
@@ -913,16 +1137,19 @@ export async function runCursorWork(context: HeartbeatContext): Promise<AdapterE
|
|
|
913
1137
|
!isRateLimitedRuntimeFailure(runtime) &&
|
|
914
1138
|
isUnknownSessionError(runtime.stderr, runtime.stdout)
|
|
915
1139
|
) {
|
|
916
|
-
const retry =
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1140
|
+
const retry = withResolvedRuntimeUsage(
|
|
1141
|
+
await executePromptRuntime(
|
|
1142
|
+
cursorLaunch.command,
|
|
1143
|
+
prompt,
|
|
1144
|
+
{
|
|
1145
|
+
...context.runtime,
|
|
1146
|
+
timeoutMs: runtimeTimeoutMs,
|
|
1147
|
+
retryCount: 0,
|
|
1148
|
+
args: buildArgs(null)
|
|
1149
|
+
},
|
|
1150
|
+
{ provider: "cursor" }
|
|
1151
|
+
),
|
|
1152
|
+
usageResolver
|
|
926
1153
|
);
|
|
927
1154
|
return toProviderResult(context, "cursor", prompt, retry, {
|
|
928
1155
|
inputRate: 0.0000015,
|
|
@@ -1066,7 +1293,11 @@ function resolveGeminiResumeState(state: HeartbeatContext["state"], cwd: string,
|
|
|
1066
1293
|
return { resumeSessionId: savedSessionId, resumeAttempted: true, resumeSkippedReason: null };
|
|
1067
1294
|
}
|
|
1068
1295
|
|
|
1069
|
-
export async function runGeminiCliWork(
|
|
1296
|
+
export async function runGeminiCliWork(
|
|
1297
|
+
context: HeartbeatContext,
|
|
1298
|
+
options?: { usageResolver?: AdapterRuntimeUsageResolver }
|
|
1299
|
+
): Promise<AdapterExecutionResult> {
|
|
1300
|
+
const usageResolver = options?.usageResolver ?? resolveGeminiDefaultRuntimeUsage;
|
|
1070
1301
|
const prompt = createPrompt(context);
|
|
1071
1302
|
const cwd = context.runtime?.cwd?.trim() || process.cwd();
|
|
1072
1303
|
const command = context.runtime?.command?.trim() || "gemini";
|
|
@@ -1089,16 +1320,19 @@ export async function runGeminiCliWork(context: HeartbeatContext): Promise<Adapt
|
|
|
1089
1320
|
return base;
|
|
1090
1321
|
};
|
|
1091
1322
|
|
|
1092
|
-
const runtime =
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1323
|
+
const runtime = withResolvedRuntimeUsage(
|
|
1324
|
+
await executePromptRuntime(
|
|
1325
|
+
command,
|
|
1326
|
+
prompt,
|
|
1327
|
+
{
|
|
1328
|
+
...context.runtime,
|
|
1329
|
+
timeoutMs: runtimeTimeoutMs,
|
|
1330
|
+
retryCount: 0,
|
|
1331
|
+
args: buildArgs(resumeState.resumeSessionId)
|
|
1332
|
+
},
|
|
1333
|
+
{ provider: "gemini_cli" }
|
|
1334
|
+
),
|
|
1335
|
+
usageResolver
|
|
1102
1336
|
);
|
|
1103
1337
|
|
|
1104
1338
|
const parsed = parseGeminiOutput(runtime.stdout);
|
|
@@ -1109,16 +1343,19 @@ export async function runGeminiCliWork(context: HeartbeatContext): Promise<Adapt
|
|
|
1109
1343
|
!isRateLimitedRuntimeFailure(runtime) &&
|
|
1110
1344
|
isGeminiUnknownSessionError(runtime.stdout, runtime.stderr)
|
|
1111
1345
|
) {
|
|
1112
|
-
const retry =
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1346
|
+
const retry = withResolvedRuntimeUsage(
|
|
1347
|
+
await executePromptRuntime(
|
|
1348
|
+
command,
|
|
1349
|
+
prompt,
|
|
1350
|
+
{
|
|
1351
|
+
...context.runtime,
|
|
1352
|
+
timeoutMs: runtimeTimeoutMs,
|
|
1353
|
+
retryCount: 0,
|
|
1354
|
+
args: buildArgs(null)
|
|
1355
|
+
},
|
|
1356
|
+
{ provider: "gemini_cli" }
|
|
1357
|
+
),
|
|
1358
|
+
usageResolver
|
|
1122
1359
|
);
|
|
1123
1360
|
const retryParsed = parseGeminiOutput(retry.stdout);
|
|
1124
1361
|
return toProviderResult(context, "gemini_cli", prompt, retry, {
|
|
@@ -1147,22 +1384,19 @@ export async function runGeminiCliWork(context: HeartbeatContext): Promise<Adapt
|
|
|
1147
1384
|
|
|
1148
1385
|
export function resolveFailedUsage(
|
|
1149
1386
|
runtime: {
|
|
1150
|
-
parsedUsage?:
|
|
1151
|
-
tokenInput?: number;
|
|
1152
|
-
tokenOutput?: number;
|
|
1153
|
-
usdCost?: number;
|
|
1154
|
-
summary?: string;
|
|
1155
|
-
};
|
|
1387
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
1156
1388
|
failureType?: "timeout" | "spawn_error" | "nonzero_exit";
|
|
1157
1389
|
stdout: string;
|
|
1158
1390
|
stderr: string;
|
|
1159
1391
|
}
|
|
1160
1392
|
) {
|
|
1161
1393
|
if (runtime.parsedUsage) {
|
|
1394
|
+
const usage = toNormalizedUsage(runtime.parsedUsage);
|
|
1162
1395
|
return {
|
|
1163
|
-
tokenInput: runtime.parsedUsage
|
|
1164
|
-
tokenOutput: runtime.parsedUsage.tokenOutput ?? 0,
|
|
1165
|
-
usdCost: runtime.parsedUsage.usdCost ?? 0,
|
|
1396
|
+
tokenInput: usageTokenInputTotal(runtime.parsedUsage),
|
|
1397
|
+
tokenOutput: runtime.parsedUsage.outputTokens ?? runtime.parsedUsage.tokenOutput ?? 0,
|
|
1398
|
+
usdCost: runtime.parsedUsage.costUsd ?? runtime.parsedUsage.usdCost ?? 0,
|
|
1399
|
+
usage,
|
|
1166
1400
|
source: "structured" as const
|
|
1167
1401
|
};
|
|
1168
1402
|
}
|
|
@@ -1171,6 +1405,11 @@ export function resolveFailedUsage(
|
|
|
1171
1405
|
tokenInput: 0,
|
|
1172
1406
|
tokenOutput: 0,
|
|
1173
1407
|
usdCost: 0,
|
|
1408
|
+
usage: {
|
|
1409
|
+
inputTokens: 0,
|
|
1410
|
+
cachedInputTokens: 0,
|
|
1411
|
+
outputTokens: 0
|
|
1412
|
+
} as AdapterNormalizedUsage,
|
|
1174
1413
|
source: "none" as const
|
|
1175
1414
|
};
|
|
1176
1415
|
}
|
|
@@ -1178,6 +1417,11 @@ export function resolveFailedUsage(
|
|
|
1178
1417
|
tokenInput: 0,
|
|
1179
1418
|
tokenOutput: 0,
|
|
1180
1419
|
usdCost: 0,
|
|
1420
|
+
usage: {
|
|
1421
|
+
inputTokens: 0,
|
|
1422
|
+
cachedInputTokens: 0,
|
|
1423
|
+
outputTokens: 0
|
|
1424
|
+
} as AdapterNormalizedUsage,
|
|
1181
1425
|
source: "none" as const
|
|
1182
1426
|
};
|
|
1183
1427
|
}
|
|
@@ -1204,12 +1448,7 @@ export function toProviderResult(
|
|
|
1204
1448
|
spawnErrorCode?: string;
|
|
1205
1449
|
forcedKill: boolean;
|
|
1206
1450
|
}>;
|
|
1207
|
-
parsedUsage?:
|
|
1208
|
-
tokenInput?: number;
|
|
1209
|
-
tokenOutput?: number;
|
|
1210
|
-
usdCost?: number;
|
|
1211
|
-
summary?: string;
|
|
1212
|
-
};
|
|
1451
|
+
parsedUsage?: RuntimeParsedUsage;
|
|
1213
1452
|
structuredOutputSource?: "stdout" | "stderr";
|
|
1214
1453
|
structuredOutputDiagnostics?: {
|
|
1215
1454
|
stdoutJsonObjectCount: number;
|
|
@@ -1301,16 +1540,17 @@ export function toProviderResult(
|
|
|
1301
1540
|
nextState: applyProviderSessionState(context, provider, sessionUpdate)
|
|
1302
1541
|
};
|
|
1303
1542
|
}
|
|
1304
|
-
const
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1543
|
+
const tokenOutput = runtime.parsedUsage?.outputTokens ?? runtime.parsedUsage?.tokenOutput ?? 0;
|
|
1544
|
+
const usdCost = runtime.parsedUsage?.costUsd ?? runtime.parsedUsage?.usdCost ?? 0;
|
|
1545
|
+
const usage = toNormalizedUsage(runtime.parsedUsage);
|
|
1307
1546
|
const summary = runtime.parsedUsage?.summary ?? `${provider} runtime finished in ${runtime.elapsedMs}ms.`;
|
|
1308
1547
|
return {
|
|
1309
1548
|
status: "ok",
|
|
1310
1549
|
summary,
|
|
1311
|
-
tokenInput,
|
|
1550
|
+
tokenInput: usageTokenInputTotal(runtime.parsedUsage),
|
|
1312
1551
|
tokenOutput,
|
|
1313
1552
|
usdCost,
|
|
1553
|
+
usage,
|
|
1314
1554
|
pricingProviderType,
|
|
1315
1555
|
pricingModelId,
|
|
1316
1556
|
outcome: toOutcome({
|
|
@@ -1352,6 +1592,7 @@ export function toProviderResult(
|
|
|
1352
1592
|
tokenInput: failedUsage.tokenInput,
|
|
1353
1593
|
tokenOutput: failedUsage.tokenOutput,
|
|
1354
1594
|
usdCost: failedUsage.usdCost,
|
|
1595
|
+
usage: failedUsage.usage,
|
|
1355
1596
|
pricingProviderType,
|
|
1356
1597
|
pricingModelId,
|
|
1357
1598
|
outcome: toOutcome({
|
|
@@ -1537,6 +1778,40 @@ export function isUnknownSessionError(stderr: string, stdout: string) {
|
|
|
1537
1778
|
);
|
|
1538
1779
|
}
|
|
1539
1780
|
|
|
1781
|
+
function hasCodexResumeArgs(args: string[]) {
|
|
1782
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1783
|
+
const current = (args[index] ?? "").trim().toLowerCase();
|
|
1784
|
+
if (current === "--resume" || current.startsWith("--resume=")) {
|
|
1785
|
+
return true;
|
|
1786
|
+
}
|
|
1787
|
+
if (current === "resume") {
|
|
1788
|
+
return true;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return false;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
function stripCodexResumeArgs(args: string[]) {
|
|
1795
|
+
const next: string[] = [];
|
|
1796
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1797
|
+
const current = (args[index] ?? "").trim();
|
|
1798
|
+
const lowered = current.toLowerCase();
|
|
1799
|
+
if (lowered === "--resume") {
|
|
1800
|
+
index += 1;
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
if (lowered.startsWith("--resume=")) {
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
if (lowered === "resume") {
|
|
1807
|
+
index += 1;
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
next.push(args[index] ?? "");
|
|
1811
|
+
}
|
|
1812
|
+
return next;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1540
1815
|
export function hasTrustFlag(args: string[]) {
|
|
1541
1816
|
return args.includes("--trust") || args.includes("--yolo") || args.includes("-f");
|
|
1542
1817
|
}
|
|
@@ -1970,13 +2245,18 @@ export function createPrompt(context: HeartbeatContext) {
|
|
|
1970
2245
|
const controlPlaneApiBaseUrl =
|
|
1971
2246
|
context.runtime?.env?.BOPODEV_API_BASE_URL?.trim() || context.runtime?.env?.BOPODEV_API_URL?.trim() || "";
|
|
1972
2247
|
const hasControlPlaneHeaders = Boolean(context.runtime?.env?.BOPODEV_REQUEST_HEADERS_JSON?.trim());
|
|
2248
|
+
const safeControlPlaneCurl =
|
|
2249
|
+
'curl -sS -H "x-company-id: $BOPODEV_COMPANY_ID" -H "x-actor-type: $BOPODEV_ACTOR_TYPE" -H "x-actor-id: $BOPODEV_ACTOR_ID" -H "x-actor-companies: $BOPODEV_ACTOR_COMPANIES" -H "x-actor-permissions: $BOPODEV_ACTOR_PERMISSIONS" "$BOPODEV_API_BASE_URL/agents"';
|
|
1973
2250
|
const controlPlaneDirectives = [
|
|
1974
2251
|
"Control-plane API directives:",
|
|
1975
2252
|
controlPlaneApiBaseUrl
|
|
1976
2253
|
? `- Use BOPODEV_API_BASE_URL (or BOPODEV_API_URL) for API calls. Current value: ${controlPlaneApiBaseUrl}`
|
|
1977
2254
|
: "- BOPODEV_API_BASE_URL is missing. Report this as blocker instead of guessing URLs.",
|
|
1978
2255
|
"- Never guess fallback URLs such as localhost:3000.",
|
|
1979
|
-
"-
|
|
2256
|
+
"- For curl requests, pass control-plane headers directly from env vars (`BOPODEV_COMPANY_ID`, `BOPODEV_ACTOR_TYPE`, `BOPODEV_ACTOR_ID`, `BOPODEV_ACTOR_COMPANIES`, `BOPODEV_ACTOR_PERMISSIONS`).",
|
|
2257
|
+
"- Use BOPODEV_REQUEST_HEADERS_JSON only as a compatibility fallback when direct vars are unavailable.",
|
|
2258
|
+
`- Safe example command (copy and edit path only): ${safeControlPlaneCurl}`,
|
|
2259
|
+
"- Avoid building curl headers by parsing JSON in shell unless direct header env vars are unavailable.",
|
|
1980
2260
|
hasControlPlaneHeaders
|
|
1981
2261
|
? "- BOPODEV_REQUEST_HEADERS_JSON is present in env."
|
|
1982
2262
|
: "- BOPODEV_REQUEST_HEADERS_JSON is missing. Report this as blocker."
|
package/src/registry.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { listAdapterModels, testAdapterEnvironment } from "./adapters";
|
|
2
1
|
import type {
|
|
3
2
|
AdapterEnvironmentResult,
|
|
4
3
|
AdapterMetadata,
|
|
@@ -85,7 +84,7 @@ export async function getAdapterModels(
|
|
|
85
84
|
if (fromModule) {
|
|
86
85
|
return fromModule;
|
|
87
86
|
}
|
|
88
|
-
return adapterModules[providerType].models ? [...adapterModules[providerType].models] :
|
|
87
|
+
return adapterModules[providerType].models ? [...adapterModules[providerType].models] : [];
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
export function getAdapterMetadata(): AdapterMetadata[] {
|
|
@@ -100,5 +99,10 @@ export async function runAdapterEnvironmentTest(
|
|
|
100
99
|
if (testEnvironment) {
|
|
101
100
|
return testEnvironment(runtime);
|
|
102
101
|
}
|
|
103
|
-
return
|
|
102
|
+
return {
|
|
103
|
+
providerType,
|
|
104
|
+
status: "warn",
|
|
105
|
+
testedAt: new Date().toISOString(),
|
|
106
|
+
checks: [{ code: "test_environment_unavailable", level: "warn", message: "Adapter does not expose testEnvironment." }]
|
|
107
|
+
};
|
|
104
108
|
}
|
package/src/runtime-http.ts
CHANGED
|
@@ -378,7 +378,7 @@ function isRetryableFailure(
|
|
|
378
378
|
failureType: NonNullable<DirectApiExecutionOutput["failureType"]>,
|
|
379
379
|
statusCode: number
|
|
380
380
|
) {
|
|
381
|
-
if (failureType === "timeout" || failureType === "network") {
|
|
381
|
+
if (failureType === "timeout" || failureType === "network" || failureType === "rate_limit") {
|
|
382
382
|
return true;
|
|
383
383
|
}
|
|
384
384
|
if (failureType === "http_error" && statusCode >= 500) {
|
package/src/runtime-parsers.ts
CHANGED
package/src/runtime.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { access, cp, lstat, mkdir, mkdtemp, readdir, rm, symlink } from "node:fs/promises";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import type { AgentRuntimeConfig } from "./types";
|
|
7
7
|
|
|
@@ -27,6 +27,11 @@ type ParsedUsageRecord = {
|
|
|
27
27
|
summary?: string;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
type UsageSourceResolution = {
|
|
31
|
+
usage?: ParsedUsageRecord;
|
|
32
|
+
source?: "stdout" | "stderr";
|
|
33
|
+
};
|
|
34
|
+
|
|
30
35
|
type CursorParsedStream = {
|
|
31
36
|
usage: ParsedUsageRecord;
|
|
32
37
|
sessionId?: string;
|
|
@@ -142,6 +147,10 @@ function providerConfigArgs(provider: "claude_code" | "codex", config?: AgentRun
|
|
|
142
147
|
if (config?.model?.trim()) {
|
|
143
148
|
args.push("--model", config.model.trim());
|
|
144
149
|
}
|
|
150
|
+
if (!hasCliFlag(config?.args ?? [], "--json")) {
|
|
151
|
+
// Codex JSON events carry usage metrics we need for reliable cost accounting.
|
|
152
|
+
args.push("--json");
|
|
153
|
+
}
|
|
145
154
|
if (config?.thinkingEffort && config.thinkingEffort !== "auto") {
|
|
146
155
|
args.push("--reasoning-effort", config.thinkingEffort);
|
|
147
156
|
}
|
|
@@ -199,7 +208,11 @@ export async function executeAgentRuntime(
|
|
|
199
208
|
prompt: string,
|
|
200
209
|
config?: AgentRuntimeConfig
|
|
201
210
|
): Promise<RuntimeExecutionOutput> {
|
|
202
|
-
const
|
|
211
|
+
const mergedEnv = {
|
|
212
|
+
...process.env,
|
|
213
|
+
...(config?.env ?? {})
|
|
214
|
+
};
|
|
215
|
+
const command = await resolveProviderCommand(provider, config?.command, mergedEnv);
|
|
203
216
|
const commandOverride = Boolean(config?.command && config.command.trim().length > 0);
|
|
204
217
|
const effectiveRetryCount = config?.retryCount ?? (provider === "codex" ? 1 : 0);
|
|
205
218
|
const candidateArgs = [
|
|
@@ -364,8 +377,9 @@ export async function executePromptRuntime(
|
|
|
364
377
|
const claudeStream = provider === "claude_code" ? parseClaudeStreamOutput(stdout) : undefined;
|
|
365
378
|
const cursorStream = provider === "cursor" ? parseCursorStreamOutput(stdout) : undefined;
|
|
366
379
|
const geminiStream = provider === "gemini_cli" ? parseGeminiStreamOutput(stdout, stderr) : undefined;
|
|
367
|
-
const stdoutUsage =
|
|
380
|
+
const stdoutUsage = parseStructuredUsage(stdout);
|
|
368
381
|
const stderrUsage = parseStructuredUsage(stderr);
|
|
382
|
+
const usageResolution = resolveUsageSource(stdoutUsage, stderrUsage);
|
|
369
383
|
return {
|
|
370
384
|
ok: true,
|
|
371
385
|
code: attemptResult.code,
|
|
@@ -375,8 +389,8 @@ export async function executePromptRuntime(
|
|
|
375
389
|
elapsedMs: attempts.reduce((sum, item) => sum + item.elapsedMs, 0),
|
|
376
390
|
attemptCount: attempts.length,
|
|
377
391
|
attempts,
|
|
378
|
-
parsedUsage:
|
|
379
|
-
structuredOutputSource:
|
|
392
|
+
parsedUsage: usageResolution.usage,
|
|
393
|
+
structuredOutputSource: usageResolution.source,
|
|
380
394
|
structuredOutputDiagnostics: {
|
|
381
395
|
stdoutJsonObjectCount: extractJsonObjectBlocks(stdout).length,
|
|
382
396
|
stderrJsonObjectCount: extractJsonObjectBlocks(stderr).length,
|
|
@@ -422,8 +436,9 @@ export async function executePromptRuntime(
|
|
|
422
436
|
const claudeStream = provider === "claude_code" ? parseClaudeStreamOutput(stdout) : undefined;
|
|
423
437
|
const cursorStream = provider === "cursor" ? parseCursorStreamOutput(stdout) : undefined;
|
|
424
438
|
const geminiStream = provider === "gemini_cli" ? parseGeminiStreamOutput(stdout, stderr) : undefined;
|
|
425
|
-
const stdoutUsage =
|
|
439
|
+
const stdoutUsage = parseStructuredUsage(stdout);
|
|
426
440
|
const stderrUsage = parseStructuredUsage(stderr);
|
|
441
|
+
const usageResolution = resolveUsageSource(stdoutUsage, stderrUsage);
|
|
427
442
|
return {
|
|
428
443
|
ok: false,
|
|
429
444
|
code: lastResult?.code ?? null,
|
|
@@ -434,8 +449,8 @@ export async function executePromptRuntime(
|
|
|
434
449
|
attemptCount: attempts.length,
|
|
435
450
|
attempts,
|
|
436
451
|
failureType: classifyFailure(lastResult?.timedOut ?? false, lastResult?.spawnErrorCode, lastResult?.code ?? null),
|
|
437
|
-
parsedUsage:
|
|
438
|
-
structuredOutputSource:
|
|
452
|
+
parsedUsage: usageResolution.usage,
|
|
453
|
+
structuredOutputSource: usageResolution.source,
|
|
439
454
|
structuredOutputDiagnostics: {
|
|
440
455
|
stdoutJsonObjectCount: extractJsonObjectBlocks(stdout).length,
|
|
441
456
|
stderrJsonObjectCount: extractJsonObjectBlocks(stderr).length,
|
|
@@ -536,14 +551,62 @@ function inspectClaudeOutputContract(command: string, args: string[], commandOve
|
|
|
536
551
|
};
|
|
537
552
|
}
|
|
538
553
|
|
|
539
|
-
function
|
|
554
|
+
function isBareCommandToken(command: string) {
|
|
555
|
+
return command.length > 0 && !command.includes("/") && !command.includes("\\");
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function splitPathEntries(pathValue: string | undefined) {
|
|
559
|
+
return (pathValue ?? "")
|
|
560
|
+
.split(delimiter)
|
|
561
|
+
.map((segment) => segment.trim())
|
|
562
|
+
.filter(Boolean);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function pathExists(path: string) {
|
|
566
|
+
try {
|
|
567
|
+
await access(path);
|
|
568
|
+
return true;
|
|
569
|
+
} catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async function resolveClaudeBinaryCommand(env: NodeJS.ProcessEnv) {
|
|
575
|
+
const binaryName = process.platform === "win32" ? "claude.exe" : "claude";
|
|
576
|
+
const candidates = new Set<string>();
|
|
577
|
+
for (const entry of splitPathEntries(env.PATH)) {
|
|
578
|
+
candidates.add(join(entry, binaryName));
|
|
579
|
+
}
|
|
580
|
+
const home = (env.HOME ?? "").trim() || homedir();
|
|
581
|
+
if (home) {
|
|
582
|
+
candidates.add(join(home, ".local", "bin", binaryName));
|
|
583
|
+
}
|
|
584
|
+
for (const candidate of candidates) {
|
|
585
|
+
if (await pathExists(candidate)) {
|
|
586
|
+
return candidate;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return "claude";
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async function resolveProviderCommand(
|
|
593
|
+
provider: "claude_code" | "codex",
|
|
594
|
+
configuredCommand: string | undefined,
|
|
595
|
+
env: NodeJS.ProcessEnv
|
|
596
|
+
) {
|
|
540
597
|
const trimmed = configuredCommand?.trim();
|
|
541
598
|
if (!trimmed) {
|
|
599
|
+
if (provider === "claude_code") {
|
|
600
|
+
return resolveClaudeBinaryCommand(env);
|
|
601
|
+
}
|
|
542
602
|
return pickDefaultCommand(provider);
|
|
543
603
|
}
|
|
544
604
|
// Normalize accidental provider id aliases used as command strings.
|
|
545
605
|
if (provider === "claude_code" && trimmed === "claude_code") {
|
|
546
|
-
return
|
|
606
|
+
return resolveClaudeBinaryCommand(env);
|
|
607
|
+
}
|
|
608
|
+
if (provider === "claude_code" && isBareCommandToken(trimmed) && trimmed.toLowerCase() === "claude") {
|
|
609
|
+
return resolveClaudeBinaryCommand(env);
|
|
547
610
|
}
|
|
548
611
|
return trimmed;
|
|
549
612
|
}
|
|
@@ -1259,13 +1322,22 @@ export function containsRateLimitFailure(text: string) {
|
|
|
1259
1322
|
|
|
1260
1323
|
export async function checkRuntimeCommandHealth(
|
|
1261
1324
|
command: string,
|
|
1262
|
-
options?: { cwd?: string; timeoutMs?: number }
|
|
1325
|
+
options?: { cwd?: string; timeoutMs?: number; env?: Record<string, string> }
|
|
1263
1326
|
): Promise<RuntimeCommandHealth> {
|
|
1264
1327
|
const startedAt = Date.now();
|
|
1328
|
+
const mergedEnv: NodeJS.ProcessEnv = {
|
|
1329
|
+
...process.env,
|
|
1330
|
+
...(options?.env ?? {})
|
|
1331
|
+
};
|
|
1332
|
+
const trimmedCommand = command.trim();
|
|
1333
|
+
const normalizedCommand =
|
|
1334
|
+
trimmedCommand === "claude_code" || trimmedCommand.toLowerCase() === "claude" || trimmedCommand.toLowerCase() === "claude.exe"
|
|
1335
|
+
? await resolveClaudeBinaryCommand(mergedEnv)
|
|
1336
|
+
: command;
|
|
1265
1337
|
return new Promise((resolve) => {
|
|
1266
|
-
const child = spawn(
|
|
1338
|
+
const child = spawn(normalizedCommand, ["--version"], {
|
|
1267
1339
|
cwd: options?.cwd ?? process.cwd(),
|
|
1268
|
-
env:
|
|
1340
|
+
env: mergedEnv,
|
|
1269
1341
|
shell: false
|
|
1270
1342
|
});
|
|
1271
1343
|
|
|
@@ -1277,7 +1349,7 @@ export async function checkRuntimeCommandHealth(
|
|
|
1277
1349
|
resolved = true;
|
|
1278
1350
|
child.kill("SIGTERM");
|
|
1279
1351
|
resolve({
|
|
1280
|
-
command,
|
|
1352
|
+
command: normalizedCommand,
|
|
1281
1353
|
available: false,
|
|
1282
1354
|
exitCode: null,
|
|
1283
1355
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -1292,7 +1364,7 @@ export async function checkRuntimeCommandHealth(
|
|
|
1292
1364
|
resolved = true;
|
|
1293
1365
|
clearTimeout(timeout);
|
|
1294
1366
|
resolve({
|
|
1295
|
-
command,
|
|
1367
|
+
command: normalizedCommand,
|
|
1296
1368
|
available: true,
|
|
1297
1369
|
exitCode: code,
|
|
1298
1370
|
elapsedMs: Date.now() - startedAt
|
|
@@ -1306,7 +1378,7 @@ export async function checkRuntimeCommandHealth(
|
|
|
1306
1378
|
resolved = true;
|
|
1307
1379
|
clearTimeout(timeout);
|
|
1308
1380
|
resolve({
|
|
1309
|
-
command,
|
|
1381
|
+
command: normalizedCommand,
|
|
1310
1382
|
available: false,
|
|
1311
1383
|
exitCode: null,
|
|
1312
1384
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -3149,10 +3221,13 @@ function tryParseUsage(candidate: string) {
|
|
|
3149
3221
|
try {
|
|
3150
3222
|
const parsed = JSON.parse(candidate) as Record<string, unknown>;
|
|
3151
3223
|
const direct = toUsageRecord(parsed);
|
|
3224
|
+
const nested = findNestedUsage(parsed);
|
|
3225
|
+
if (direct && nested) {
|
|
3226
|
+
return mergeUsageRecords(direct, nested);
|
|
3227
|
+
}
|
|
3152
3228
|
if (direct) {
|
|
3153
3229
|
return direct;
|
|
3154
3230
|
}
|
|
3155
|
-
const nested = findNestedUsage(parsed);
|
|
3156
3231
|
if (nested) {
|
|
3157
3232
|
return nested;
|
|
3158
3233
|
}
|
|
@@ -3163,22 +3238,119 @@ function tryParseUsage(candidate: string) {
|
|
|
3163
3238
|
}
|
|
3164
3239
|
|
|
3165
3240
|
function toUsageRecord(parsed: Record<string, unknown>) {
|
|
3166
|
-
const
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3241
|
+
const directInputTokens =
|
|
3242
|
+
toNumber(parsed.tokenInput) ??
|
|
3243
|
+
toNumber(parsed.input_tokens) ??
|
|
3244
|
+
toNumber(parsed.inputTokens) ??
|
|
3245
|
+
toNumber(parsed.prompt_tokens) ??
|
|
3246
|
+
toNumber(parsed.promptTokens) ??
|
|
3247
|
+
toNumber(parsed.promptTokenCount);
|
|
3248
|
+
const cachedInputTokens =
|
|
3249
|
+
toNumber(parsed.cached_input_tokens) ??
|
|
3250
|
+
toNumber(parsed.cachedInputTokens) ??
|
|
3251
|
+
toNumber(parsed.cache_read_input_tokens) ??
|
|
3252
|
+
toNumber(parsed.cacheReadInputTokens) ??
|
|
3253
|
+
toNumber(parsed.cache_read_tokens) ??
|
|
3254
|
+
toNumber(parsed.cacheReadTokens) ??
|
|
3255
|
+
toNumber(parsed.cachedContentTokenCount);
|
|
3256
|
+
const tokenInput =
|
|
3257
|
+
directInputTokens !== undefined || cachedInputTokens !== undefined
|
|
3258
|
+
? (directInputTokens ?? 0) + (cachedInputTokens ?? 0)
|
|
3259
|
+
: undefined;
|
|
3260
|
+
const tokenOutput =
|
|
3261
|
+
toNumber(parsed.tokenOutput) ??
|
|
3262
|
+
toNumber(parsed.output_tokens) ??
|
|
3263
|
+
toNumber(parsed.outputTokens) ??
|
|
3264
|
+
toNumber(parsed.completion_tokens) ??
|
|
3265
|
+
toNumber(parsed.completionTokens) ??
|
|
3266
|
+
toNumber(parsed.candidatesTokenCount) ??
|
|
3267
|
+
toNumber(parsed.generated_tokens) ??
|
|
3268
|
+
toNumber(parsed.generatedTokens);
|
|
3269
|
+
const estimatedOutputFromTotals =
|
|
3270
|
+
tokenOutput === undefined &&
|
|
3271
|
+
tokenInput !== undefined &&
|
|
3272
|
+
(toNumber(parsed.total_tokens) ?? toNumber(parsed.totalTokens) ?? toNumber(parsed.totalTokenCount)) !== undefined
|
|
3273
|
+
? Math.max(
|
|
3274
|
+
0,
|
|
3275
|
+
(toNumber(parsed.total_tokens) ?? toNumber(parsed.totalTokens) ?? toNumber(parsed.totalTokenCount) ?? 0) -
|
|
3276
|
+
tokenInput
|
|
3277
|
+
)
|
|
3278
|
+
: undefined;
|
|
3279
|
+
const resolvedTokenOutput = tokenOutput ?? estimatedOutputFromTotals;
|
|
3280
|
+
const usdCost = toNumber(parsed.usdCost) ?? toNumber(parsed.total_cost_usd) ?? toNumber(parsed.cost_usd) ?? toNumber(parsed.cost);
|
|
3281
|
+
const summary =
|
|
3282
|
+
(typeof parsed.summary === "string" ? parsed.summary : undefined) ??
|
|
3283
|
+
(typeof parsed.result === "string" ? parsed.result : undefined) ??
|
|
3284
|
+
(typeof parsed.message === "string" ? parsed.message : undefined);
|
|
3285
|
+
if (isPromptTemplateUsage(summary, tokenInput, resolvedTokenOutput, usdCost)) {
|
|
3171
3286
|
return undefined;
|
|
3172
3287
|
}
|
|
3173
3288
|
if (
|
|
3174
3289
|
tokenInput === undefined &&
|
|
3175
|
-
|
|
3290
|
+
resolvedTokenOutput === undefined &&
|
|
3176
3291
|
usdCost === undefined &&
|
|
3177
3292
|
!summary
|
|
3178
3293
|
) {
|
|
3179
3294
|
return undefined;
|
|
3180
3295
|
}
|
|
3181
|
-
return { tokenInput, tokenOutput, usdCost, summary };
|
|
3296
|
+
return { tokenInput, tokenOutput: resolvedTokenOutput, usdCost, summary };
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
function hasUsageMetrics(usage: ParsedUsageRecord | undefined) {
|
|
3300
|
+
if (!usage) {
|
|
3301
|
+
return false;
|
|
3302
|
+
}
|
|
3303
|
+
return usage.tokenInput !== undefined || usage.tokenOutput !== undefined || usage.usdCost !== undefined;
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
function mergeUsageRecords(primary: ParsedUsageRecord, secondary: ParsedUsageRecord): ParsedUsageRecord {
|
|
3307
|
+
return {
|
|
3308
|
+
summary: primary.summary ?? secondary.summary,
|
|
3309
|
+
tokenInput: primary.tokenInput ?? secondary.tokenInput,
|
|
3310
|
+
tokenOutput: primary.tokenOutput ?? secondary.tokenOutput,
|
|
3311
|
+
usdCost: primary.usdCost ?? secondary.usdCost
|
|
3312
|
+
};
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
function resolveUsageSource(
|
|
3316
|
+
stdoutUsage: ParsedUsageRecord | undefined,
|
|
3317
|
+
stderrUsage: ParsedUsageRecord | undefined
|
|
3318
|
+
): UsageSourceResolution {
|
|
3319
|
+
if (!stdoutUsage && !stderrUsage) {
|
|
3320
|
+
return {};
|
|
3321
|
+
}
|
|
3322
|
+
if (stdoutUsage && !stderrUsage) {
|
|
3323
|
+
return { usage: stdoutUsage, source: "stdout" };
|
|
3324
|
+
}
|
|
3325
|
+
if (!stdoutUsage && stderrUsage) {
|
|
3326
|
+
return { usage: stderrUsage, source: "stderr" };
|
|
3327
|
+
}
|
|
3328
|
+
const stdoutRecord = stdoutUsage as ParsedUsageRecord;
|
|
3329
|
+
const stderrRecord = stderrUsage as ParsedUsageRecord;
|
|
3330
|
+
const stdoutHasMetrics = hasUsageMetrics(stdoutUsage);
|
|
3331
|
+
const stderrHasMetrics = hasUsageMetrics(stderrUsage);
|
|
3332
|
+
if (!stdoutHasMetrics && stderrHasMetrics) {
|
|
3333
|
+
return {
|
|
3334
|
+
usage: mergeUsageRecords(stderrRecord, stdoutRecord),
|
|
3335
|
+
source: "stderr"
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
if (stdoutHasMetrics && !stderrHasMetrics) {
|
|
3339
|
+
return {
|
|
3340
|
+
usage: mergeUsageRecords(stdoutRecord, stderrRecord),
|
|
3341
|
+
source: "stdout"
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
if (stdoutHasMetrics && stderrHasMetrics) {
|
|
3345
|
+
return {
|
|
3346
|
+
usage: mergeUsageRecords(stdoutRecord, stderrRecord),
|
|
3347
|
+
source: "stdout"
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
return {
|
|
3351
|
+
usage: mergeUsageRecords(stdoutRecord, stderrRecord),
|
|
3352
|
+
source: "stdout"
|
|
3353
|
+
};
|
|
3182
3354
|
}
|
|
3183
3355
|
|
|
3184
3356
|
function findNestedUsage(parsed: Record<string, unknown>) {
|
|
@@ -3266,15 +3438,15 @@ function tailLine(value: string) {
|
|
|
3266
3438
|
function classifyStructuredOutputLikelyCause(
|
|
3267
3439
|
stdout: string,
|
|
3268
3440
|
stderr: string,
|
|
3269
|
-
stdoutUsage:
|
|
3270
|
-
stderrUsage:
|
|
3441
|
+
stdoutUsage: ParsedUsageRecord | undefined,
|
|
3442
|
+
stderrUsage: ParsedUsageRecord | undefined
|
|
3271
3443
|
) {
|
|
3272
3444
|
const hasStdout = stdout.trim().length > 0;
|
|
3273
3445
|
const hasStderr = stderr.trim().length > 0;
|
|
3274
3446
|
if (!hasStdout && !hasStderr) {
|
|
3275
3447
|
return "no_output_from_runtime" as const;
|
|
3276
3448
|
}
|
|
3277
|
-
if (!stdoutUsage && stderrUsage) {
|
|
3449
|
+
if (!hasUsageMetrics(stdoutUsage) && hasUsageMetrics(stderrUsage)) {
|
|
3278
3450
|
return "json_on_stderr_only" as const;
|
|
3279
3451
|
}
|
|
3280
3452
|
const jsonLike = /[\{\}\[\]\"]/m.test(stdout) || /[\{\}\[\]\"]/m.test(stderr);
|
package/src/types.ts
CHANGED
|
@@ -64,12 +64,30 @@ export interface HeartbeatContext {
|
|
|
64
64
|
runtime?: AgentRuntimeConfig;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Normalized usage contract produced by adapter execution.
|
|
69
|
+
*
|
|
70
|
+
* Invariants:
|
|
71
|
+
* - `inputTokens` excludes cache reads.
|
|
72
|
+
* - `cachedInputTokens` tracks cache-hit prompt tokens only.
|
|
73
|
+
* - persisted `tokenInput` = `inputTokens + cachedInputTokens` for backwards compatibility.
|
|
74
|
+
* - `outputTokens` reflects generated/completion tokens.
|
|
75
|
+
*/
|
|
76
|
+
export interface AdapterNormalizedUsage {
|
|
77
|
+
inputTokens: number;
|
|
78
|
+
cachedInputTokens: number;
|
|
79
|
+
outputTokens: number;
|
|
80
|
+
costUsd?: number;
|
|
81
|
+
summary?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
67
84
|
export interface AdapterExecutionResult {
|
|
68
85
|
status: "ok" | "skipped" | "failed";
|
|
69
86
|
summary: string;
|
|
70
87
|
tokenInput: number;
|
|
71
88
|
tokenOutput: number;
|
|
72
89
|
usdCost: number;
|
|
90
|
+
usage?: AdapterNormalizedUsage;
|
|
73
91
|
pricingProviderType?: string | null;
|
|
74
92
|
pricingModelId?: string | null;
|
|
75
93
|
outcome?: ExecutionOutcome;
|