@hecer/yoke 1.21.1 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +27 -0
- package/README.md +6 -1
- package/bench/analyze-codex-comparison.mjs +90 -17
- package/bench/compare-codex.mjs +159 -36
- package/bench/result-schema.mjs +132 -0
- package/canon/manifest.yaml +1 -1
- package/dist/agents/pi-telemetry.js +2 -1
- package/dist/agents/process-streams.js +12 -64
- package/dist/agents/provider-selection.js +12 -0
- package/dist/agents/telemetry.js +52 -52
- package/dist/change/inbox.js +8 -3
- package/dist/check/command.js +69 -17
- package/dist/check/delivery.js +121 -0
- package/dist/cli.js +12 -3
- package/dist/code-intelligence/budgets.js +138 -0
- package/dist/code-intelligence/contracts.js +2 -0
- package/dist/code-intelligence/coordinator.js +156 -84
- package/dist/code-intelligence/evidence.js +87 -34
- package/dist/code-intelligence/mcp-client.js +10 -2
- package/dist/code-intelligence/mcp-server.js +10 -10
- package/dist/dashboard/analytics.js +5 -3
- package/dist/goals/command.js +183 -53
- package/dist/goals/usage.js +87 -0
- package/dist/loop/candidate-cleanup.js +47 -17
- package/dist/loop/candidates.js +17 -11
- package/dist/loop/dispatcher.js +89 -26
- package/dist/loop/failure.js +104 -0
- package/dist/loop/gate-snapshot.js +19 -0
- package/dist/loop/git.js +1 -1
- package/dist/loop/loop.js +100 -66
- package/dist/loop/parallel-adapters.js +22 -4
- package/dist/loop/parallel-command.js +32 -5
- package/dist/loop/recovery.js +23 -5
- package/dist/loop/reporter.js +21 -4
- package/dist/loop/run-command.js +95 -46
- package/dist/loop/runner.js +5 -4
- package/dist/loop/worker.js +145 -91
- package/dist/observability/history.js +1 -1
- package/dist/observability/invocation.js +42 -0
- package/dist/observability/usage.js +17 -0
- package/dist/prd/command.js +20 -7
- package/dist/prd/decompose.js +5 -2
- package/dist/retrofit/config.js +26 -1
- package/dist/retrofit/gitignore.js +2 -0
- package/dist/routing/attempts.js +241 -0
- package/dist/routing/capability.js +13 -9
- package/dist/routing/optimization.js +73 -0
- package/dist/routing/registry.js +7 -1
- package/dist/routing/router.js +280 -127
- package/dist/setup/command.js +8 -2
- package/dist/smoke/command.js +302 -74
- package/docs/BENCHMARK-MANIFEST.md +131 -0
- package/docs/CODE-INTELLIGENCE.md +43 -1
- package/docs/CODEX-COMPARISON-2026-09-29.md +15 -0
- package/docs/DELIVERY-JOURNEYS.md +199 -0
- package/docs/ECONOMIC-ROUTING.md +180 -0
- package/docs/GOALS.md +61 -4
- package/docs/RELEASE-VALIDATION-1.22.0.md +115 -0
- package/docs/parallel-execution.md +37 -9
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { assertSafePath } from './snapshots.js';
|
|
3
|
+
/** Truncate only between Unicode code points, including at normalizer limits. */
|
|
4
|
+
export function shorten(value, maxChars) {
|
|
5
|
+
const clipped = value.slice(0, Math.max(0, maxChars));
|
|
6
|
+
return /[\uD800-\uDBFF]$/u.test(clipped) ? clipped.slice(0, -1) : clipped;
|
|
7
|
+
}
|
|
3
8
|
function hash(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
4
9
|
function textOf(raw) {
|
|
5
10
|
if (typeof raw === 'string')
|
|
@@ -22,32 +27,46 @@ function locate(text, root) {
|
|
|
22
27
|
return { path: null, line: null };
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
|
-
export function makeProvenance(options, text, resolution = '
|
|
30
|
+
export function makeProvenance(options, text, resolution = 'unresolved') {
|
|
26
31
|
const location = locate(text, options.root);
|
|
27
|
-
const file = location.path ? options.snapshot.files.find(item => item.path === location.path) : undefined;
|
|
28
32
|
return {
|
|
29
|
-
backend: options.adapter.name, version: options.adapter.version, source_path: location.path, content_hash:
|
|
30
|
-
|
|
33
|
+
backend: options.adapter.name, version: options.adapter.version, source_path: location.path, content_hash: null,
|
|
34
|
+
// A current workspace snapshot does not prove that a backend index read it.
|
|
35
|
+
// None of the supported backend wire contracts supplies a validated index
|
|
36
|
+
// snapshot binding. Do not trust raw `freshness` or attach a newer file hash.
|
|
37
|
+
byte_range: null, origin: options.origin ?? (options.adapter.semantic ? 'lsp' : 'parser'), resolution, freshness: 'unknown',
|
|
31
38
|
};
|
|
32
39
|
}
|
|
33
40
|
export function evidenceId(provenance, excerpt) {
|
|
34
|
-
|
|
41
|
+
const { evidence_id: _id, ...fields } = provenance;
|
|
42
|
+
return `evidence-${hash(JSON.stringify({ provenance: fields, excerpt })).slice(0, 24)}`;
|
|
43
|
+
}
|
|
44
|
+
function candidates(raw) {
|
|
45
|
+
if (Array.isArray(raw))
|
|
46
|
+
return raw;
|
|
47
|
+
if (raw && typeof raw === 'object' && Array.isArray(raw.results))
|
|
48
|
+
return raw.results;
|
|
49
|
+
return null;
|
|
35
50
|
}
|
|
36
51
|
export function normalizeItems(raw, options, kind = 'code', limit = 100) {
|
|
37
|
-
const
|
|
38
|
-
const chunks = Array.isArray(raw) ? raw : (raw && typeof raw === 'object' && Array.isArray(raw.results) ? raw.results : [raw]);
|
|
52
|
+
const chunks = candidates(raw) ?? (raw === null || raw === undefined || raw === '' ? [] : [raw]);
|
|
39
53
|
const provenance = [];
|
|
40
54
|
const items = [];
|
|
41
|
-
const
|
|
55
|
+
const warnings = [];
|
|
56
|
+
const maxChars = Math.min(12000, options.maxChars ?? 12000);
|
|
57
|
+
if (chunks.length > limit)
|
|
58
|
+
warnings.push('backend result list truncated');
|
|
42
59
|
for (const candidate of chunks.slice(0, limit)) {
|
|
43
|
-
const
|
|
60
|
+
const text = textOf(candidate);
|
|
61
|
+
const excerpt = shorten(text, maxChars);
|
|
62
|
+
if (excerpt !== text)
|
|
63
|
+
warnings.push('backend result excerpt truncated');
|
|
44
64
|
if (!excerpt)
|
|
45
65
|
continue;
|
|
46
|
-
const p = makeProvenance(options, excerpt
|
|
47
|
-
if (candidate && typeof candidate === 'object' && typeof candidate.relative_path === 'string') {
|
|
66
|
+
const p = makeProvenance(options, excerpt);
|
|
67
|
+
if (candidate && typeof candidate === 'object' && 'relative_path' in candidate && typeof candidate.relative_path === 'string') {
|
|
48
68
|
try {
|
|
49
69
|
p.source_path = assertSafePath(options.root, candidate.relative_path);
|
|
50
|
-
p.content_hash = options.snapshot.files.find(file => file.path === p.source_path)?.hash ?? null;
|
|
51
70
|
}
|
|
52
71
|
catch {
|
|
53
72
|
p.source_path = null;
|
|
@@ -56,33 +75,67 @@ export function normalizeItems(raw, options, kind = 'code', limit = 100) {
|
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
77
|
const id = evidenceId(p, excerpt);
|
|
78
|
+
p.evidence_id = id;
|
|
59
79
|
provenance.push(p);
|
|
60
80
|
items.push({ item_id: `item-${hash(excerpt).slice(0, 24)}`, kind, path: p.source_path, excerpt, evidence_ids: [id], rank: items.length });
|
|
61
81
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
provenance.push(p);
|
|
67
|
-
items.push({ item_id: `item-${hash(excerpt).slice(0, 24)}`, kind, path: p.source_path, excerpt, evidence_ids: [id], rank: 0 });
|
|
68
|
-
}
|
|
69
|
-
return { items, provenance };
|
|
82
|
+
return { items, provenance, warnings: [...new Set(warnings)] };
|
|
83
|
+
}
|
|
84
|
+
export function serenaSymbolId(namePath, relativePath) {
|
|
85
|
+
return `serena:${encodeURIComponent(JSON.stringify({ name_path: namePath, relative_path: relativePath }))}`;
|
|
70
86
|
}
|
|
71
87
|
export function normalizeSymbols(raw, options) {
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
const list = candidates(raw);
|
|
89
|
+
const symbols = [];
|
|
90
|
+
const provenance = [];
|
|
91
|
+
const warnings = [];
|
|
92
|
+
if (!list) {
|
|
93
|
+
const normalized = normalizeItems(raw, options, 'symbol');
|
|
94
|
+
return { symbols, references: [], diagnostics: [], provenance: normalized.provenance, warnings: [...normalized.warnings, 'unrecognized semantic symbol response'] };
|
|
95
|
+
}
|
|
96
|
+
if (list.length > 100)
|
|
97
|
+
warnings.push('semantic symbol result list truncated');
|
|
98
|
+
for (const value of list.slice(0, 100)) {
|
|
99
|
+
const normalized = normalizeItems([value], options, 'symbol', 1);
|
|
100
|
+
warnings.push(...normalized.warnings);
|
|
101
|
+
const item = normalized.items[0];
|
|
102
|
+
const p = normalized.provenance[0];
|
|
103
|
+
if (!item || !p)
|
|
104
|
+
continue;
|
|
105
|
+
const candidate = value && typeof value === 'object' ? value : {};
|
|
106
|
+
const namePath = typeof candidate.name_path === 'string' ? candidate.name_path.trim() : '';
|
|
107
|
+
if (!options.adapter.semantic || !namePath || !item.path || !options.snapshot.files.some(file => file.path === item.path)) {
|
|
108
|
+
provenance.push(p);
|
|
109
|
+
warnings.push('unresolved semantic symbol record');
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
p.resolution = 'resolved';
|
|
113
|
+
p.evidence_id = evidenceId(p, item.excerpt);
|
|
114
|
+
provenance.push(p);
|
|
115
|
+
symbols.push({ symbol_id: serenaSymbolId(namePath, item.path), name: typeof candidate.name === 'string' && candidate.name ? candidate.name : namePath.split('/').at(-1) || namePath,
|
|
116
|
+
path: item.path, kind: typeof candidate.kind === 'string' ? candidate.kind : 'unknown', signature: shorten(item.excerpt, 500), evidence_ids: [p.evidence_id] });
|
|
117
|
+
}
|
|
118
|
+
return { symbols, references: [], diagnostics: [], provenance, warnings: [...new Set(warnings)] };
|
|
82
119
|
}
|
|
83
|
-
|
|
120
|
+
/** Graft prose/search hits are candidates, not an ordered graph path. */
|
|
121
|
+
export function normalizeEdges(raw, options, _relation) {
|
|
84
122
|
const normalized = normalizeItems(raw, options, 'code');
|
|
85
|
-
const nodes = normalized.items.map(item => ({ id: item.item_id, label: item.excerpt
|
|
86
|
-
|
|
87
|
-
|
|
123
|
+
const nodes = normalized.items.map(item => ({ id: item.item_id, label: shorten(item.excerpt, 160), path: item.path, evidence_ids: item.evidence_ids }));
|
|
124
|
+
return { nodes, edges: [], provenance: normalized.provenance, warnings: [...normalized.warnings, 'structural trace response has no validated edge contract'] };
|
|
125
|
+
}
|
|
126
|
+
export function normalizeReferenceEdges(raw, options, target, relation = 'references') {
|
|
127
|
+
const normalized = normalizeSymbols(raw, options);
|
|
128
|
+
const nodes = normalized.symbols.map(symbol => ({ id: symbol.symbol_id, label: symbol.name, path: symbol.path, evidence_ids: symbol.evidence_ids }));
|
|
129
|
+
let targetPath;
|
|
130
|
+
try {
|
|
131
|
+
targetPath = assertSafePath(options.root, target.relative_path);
|
|
132
|
+
if (!options.snapshot.files.some(file => file.path === targetPath))
|
|
133
|
+
throw new Error('target is absent from snapshot');
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { nodes, edges: [], provenance: normalized.provenance, warnings: [...normalized.warnings, 'reference target is unresolved'] };
|
|
137
|
+
}
|
|
138
|
+
const targetNode = { id: target.symbol_id, label: target.name_path, path: targetPath, evidence_ids: [] };
|
|
139
|
+
const edges = nodes.map(node => ({ from: node.id, to: targetNode.id, relation, evidence_ids: node.evidence_ids }));
|
|
140
|
+
return { nodes: [targetNode, ...nodes], edges, provenance: normalized.provenance, warnings: normalized.warnings };
|
|
88
141
|
}
|
|
@@ -20,8 +20,14 @@ export class McpStdioClient {
|
|
|
20
20
|
this.maxBytes = maxBytes;
|
|
21
21
|
}
|
|
22
22
|
async call(tool, arguments_, timeoutMs) {
|
|
23
|
+
const deadline = Date.now() + timeoutMs;
|
|
23
24
|
await this.start(timeoutMs);
|
|
24
|
-
|
|
25
|
+
const remaining = deadline - Date.now();
|
|
26
|
+
if (remaining <= 0) {
|
|
27
|
+
void this.close();
|
|
28
|
+
throw new Error('MCP call deadline exceeded during initialization');
|
|
29
|
+
}
|
|
30
|
+
return this.request('tools/call', { name: tool, arguments: arguments_ }, remaining);
|
|
25
31
|
}
|
|
26
32
|
async close() {
|
|
27
33
|
if (!this.child)
|
|
@@ -35,7 +41,7 @@ export class McpStdioClient {
|
|
|
35
41
|
this.pending.clear();
|
|
36
42
|
child.kill('SIGTERM');
|
|
37
43
|
await new Promise(resolve => {
|
|
38
|
-
const timer = setTimeout(() => { if (
|
|
44
|
+
const timer = setTimeout(() => { if (child.exitCode === null && child.signalCode === null)
|
|
39
45
|
child.kill('SIGKILL'); resolve(); }, 500);
|
|
40
46
|
child.once('close', () => { clearTimeout(timer); resolve(); });
|
|
41
47
|
});
|
|
@@ -60,6 +66,8 @@ export class McpStdioClient {
|
|
|
60
66
|
return Promise.resolve();
|
|
61
67
|
}
|
|
62
68
|
request(method, params, timeoutMs) {
|
|
69
|
+
if (timeoutMs <= 0)
|
|
70
|
+
return Promise.reject(new Error(`MCP request deadline exceeded: ${method}`));
|
|
63
71
|
if (!this.child)
|
|
64
72
|
return Promise.reject(new Error('MCP process is not running'));
|
|
65
73
|
const id = this.nextId++;
|
|
@@ -4,22 +4,22 @@ import { TOOL_DEFINITIONS } from './contracts.js';
|
|
|
4
4
|
import { loadConfig } from '../retrofit/config.js';
|
|
5
5
|
const INPUT_SCHEMAS = {
|
|
6
6
|
code_context: { type: 'object', required: ['workspace_id', 'query'], properties: { workspace_id: { type: 'string' }, query: { type: 'string' }, snapshot_id: { type: 'string' }, mode: { type: 'string', enum: ['orient', 'locate', 'explain'] }, paths: { type: 'array', items: { type: 'string' } }, include_docs: { type: 'boolean' }, token_budget: { type: 'integer' }, timeout_ms: { type: 'integer' } } },
|
|
7
|
-
code_symbol: { type: 'object', required: ['workspace_id', 'snapshot_id'], properties: { workspace_id: { type: 'string' }, snapshot_id: { type: 'string' }, symbol_id: { type: 'string' }, query: { type: 'string' }, path: { type: 'string' }, include: { type: 'array' } } },
|
|
8
|
-
code_trace: { type: 'object', required: ['workspace_id', 'snapshot_id', 'symbol_id', 'direction', 'relations'], properties: { workspace_id: { type: 'string' }, snapshot_id: { type: 'string' }, symbol_id: { type: 'string' }, direction: { type: 'string', enum: ['in', 'out'] }, relations: { type: 'array' }, max_depth: { type: 'integer' }, max_nodes: { type: 'integer' } } },
|
|
9
|
-
code_impact: { type: 'object', required: ['workspace_id', 'snapshot_id'], properties: { workspace_id: { type: 'string' }, snapshot_id: { type: 'string' }, targets: { type: 'array' }, plan_id: { type: 'string' }, include_docs: { type: 'boolean' }, require_semantic: { type: 'boolean' } } },
|
|
10
|
-
code_edit_preview: { type: 'object', required: ['workspace_id', 'snapshot_id', 'operations', 'validation_profile'], properties: { workspace_id: { type: 'string' }, snapshot_id: { type: 'string' }, operations: { type: 'array' }, validation_profile: { type: 'string', enum: ['default', 'extended'] } } },
|
|
7
|
+
code_symbol: { type: 'object', required: ['workspace_id', 'snapshot_id'], properties: { workspace_id: { type: 'string' }, snapshot_id: { type: 'string' }, symbol_id: { type: 'string' }, query: { type: 'string' }, path: { type: 'string' }, include: { type: 'array' }, token_budget: { type: 'integer', minimum: 128, maximum: 16000 }, timeout_ms: { type: 'integer', minimum: 100, maximum: 60000 } } },
|
|
8
|
+
code_trace: { type: 'object', required: ['workspace_id', 'snapshot_id', 'symbol_id', 'direction', 'relations'], properties: { workspace_id: { type: 'string' }, snapshot_id: { type: 'string' }, symbol_id: { type: 'string' }, direction: { type: 'string', enum: ['in', 'out'] }, relations: { type: 'array' }, max_depth: { type: 'integer' }, max_nodes: { type: 'integer' }, require_resolved: { type: 'boolean' }, timeout_ms: { type: 'integer', minimum: 100, maximum: 60000 } } },
|
|
9
|
+
code_impact: { type: 'object', required: ['workspace_id', 'snapshot_id'], properties: { workspace_id: { type: 'string' }, snapshot_id: { type: 'string' }, targets: { type: 'array' }, plan_id: { type: 'string' }, include_docs: { type: 'boolean' }, require_semantic: { type: 'boolean' }, max_depth: { type: 'integer' }, timeout_ms: { type: 'integer', minimum: 100, maximum: 60000 } } },
|
|
10
|
+
code_edit_preview: { type: 'object', required: ['workspace_id', 'snapshot_id', 'operations', 'validation_profile'], properties: { workspace_id: { type: 'string' }, snapshot_id: { type: 'string' }, operations: { type: 'array' }, validation_profile: { type: 'string', enum: ['default', 'extended'] }, timeout_ms: { type: 'integer', minimum: 100, maximum: 600000 } } },
|
|
11
11
|
code_edit_apply: { type: 'object', required: ['workspace_id', 'plan_id', 'expected_snapshot_id', 'idempotency_key'], properties: { workspace_id: { type: 'string' }, plan_id: { type: 'string' }, expected_snapshot_id: { type: 'string' }, idempotency_key: { type: 'string' } } },
|
|
12
12
|
};
|
|
13
13
|
function arg(name, args) { return args.find(value => value.startsWith(`--${name}=`))?.slice(name.length + 3); }
|
|
14
14
|
function summary(response) { return `status=${response.status} snapshot=${response.snapshot_id} warnings=${response.warnings.length} (full result in structuredContent)`; }
|
|
15
|
-
export async function runCodeIntelligenceServer(argv = process.argv.slice(2)) {
|
|
15
|
+
export async function runCodeIntelligenceServer(argv = process.argv.slice(2), io = process) {
|
|
16
16
|
const workspace = arg('workspace', argv) ?? process.cwd();
|
|
17
17
|
const configured = loadConfig(workspace)?.codeIntelligence;
|
|
18
18
|
const mode = arg('mode', argv) ?? configured?.mode ?? 'active';
|
|
19
19
|
const coordinator = new CodeIntelligenceCoordinator(workspace, {
|
|
20
20
|
mode,
|
|
21
21
|
policy: { excludePatterns: configured?.policy?.excludePatterns, maxFileBytes: configured?.policy?.maxFileBytes },
|
|
22
|
-
limits:
|
|
22
|
+
limits: configured?.limits,
|
|
23
23
|
graft: configured?.graft, graphify: configured?.graphify, serena: configured?.serena,
|
|
24
24
|
});
|
|
25
25
|
let framing = 'line';
|
|
@@ -27,9 +27,9 @@ export async function runCodeIntelligenceServer(argv = process.argv.slice(2)) {
|
|
|
27
27
|
const send = (message) => {
|
|
28
28
|
const payload = JSON.stringify(message);
|
|
29
29
|
if (framing === 'line')
|
|
30
|
-
|
|
30
|
+
io.stdout.write(`${payload}\n`);
|
|
31
31
|
else
|
|
32
|
-
|
|
32
|
+
io.stdout.write(`Content-Length: ${Buffer.byteLength(payload, 'utf8')}\r\n\r\n${payload}`);
|
|
33
33
|
};
|
|
34
34
|
const handle = async (message) => {
|
|
35
35
|
if (message.method === 'notifications/initialized' || message.method?.startsWith('notifications/'))
|
|
@@ -51,7 +51,7 @@ export async function runCodeIntelligenceServer(argv = process.argv.slice(2)) {
|
|
|
51
51
|
if (message.id !== undefined)
|
|
52
52
|
send({ jsonrpc: '2.0', id: message.id, error: { code: -32601, message: `method not found: ${message.method}` } });
|
|
53
53
|
};
|
|
54
|
-
|
|
54
|
+
io.stdin.on('data', chunk => {
|
|
55
55
|
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
|
56
56
|
while (buffer.length) {
|
|
57
57
|
let raw;
|
|
@@ -87,7 +87,7 @@ export async function runCodeIntelligenceServer(argv = process.argv.slice(2)) {
|
|
|
87
87
|
catch { /* malformed protocol input is ignored */ }
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
|
-
await new Promise(resolve => { const stop = () => { process.off('SIGTERM', stop); process.off('SIGINT', stop); void coordinator.close().finally(resolve); }; process.once('SIGTERM', stop); process.once('SIGINT', stop);
|
|
90
|
+
await new Promise(resolve => { const stop = () => { process.off('SIGTERM', stop); process.off('SIGINT', stop); io.stdin.off('end', stop); void coordinator.close().finally(resolve); }; process.once('SIGTERM', stop); process.once('SIGINT', stop); io.stdin.once('end', stop); });
|
|
91
91
|
}
|
|
92
92
|
if (process.argv[1]?.endsWith('code-intelligence-server.js'))
|
|
93
93
|
void runCodeIntelligenceServer();
|
|
@@ -50,7 +50,8 @@ export function aggregateMeasurements(events, period) {
|
|
|
50
50
|
for (const target of targets)
|
|
51
51
|
target.activity++;
|
|
52
52
|
if (event.type === 'tokens') {
|
|
53
|
-
const
|
|
53
|
+
const hasCallBreakdown = Array.isArray(data.calls) && data.calls.length > 0;
|
|
54
|
+
const calls = hasCallBreakdown ? data.calls : [{ ...data, actualModel: data.model, durationMs: event.durationMs }];
|
|
54
55
|
for (const item of calls) {
|
|
55
56
|
if (!item || typeof item !== 'object')
|
|
56
57
|
continue;
|
|
@@ -70,7 +71,8 @@ export function aggregateMeasurements(events, period) {
|
|
|
70
71
|
dimensionTargets.push(dimension);
|
|
71
72
|
}
|
|
72
73
|
for (const target of [...targets, get(models, key), get(modelBuckets, JSON.stringify([bucketOf(event.timestamp, period.bucket), provider, model, role])), ...dimensionTargets]) {
|
|
73
|
-
const usageAvailable = call.usageAvailable !== false &&
|
|
74
|
+
const usageAvailable = call.usageAvailable !== false && call.measurementComplete !== false
|
|
75
|
+
&& (hasCallBreakdown || (data.usageAvailable !== false && data.measurementComplete !== false));
|
|
74
76
|
if (usageAvailable) {
|
|
75
77
|
addMetric(target, 'inputTokens', call.inputTokens);
|
|
76
78
|
addMetric(target, 'outputTokens', call.outputTokens);
|
|
@@ -87,7 +89,7 @@ export function aggregateMeasurements(events, period) {
|
|
|
87
89
|
target.unknownCalls++;
|
|
88
90
|
if (finite(call.totalCostUsd))
|
|
89
91
|
target.costReportedCalls++;
|
|
90
|
-
if (call.costMeasurementComplete === false || data.costMeasurementComplete === false)
|
|
92
|
+
if (call.costMeasurementComplete === false || (!hasCallBreakdown && data.costMeasurementComplete === false))
|
|
91
93
|
target.incompleteCosts++;
|
|
92
94
|
}
|
|
93
95
|
}
|