Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c9487e7

Browse files
authored
Keep OpenCode router calls without usage (getagentseal#342)
1 parent 06f6948 commit c9487e7

4 files changed

Lines changed: 57 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
child and grandchild agent sessions contribute token and tool usage under
4444
the discovered root session while still excluding child sessions from
4545
top-level discovery to avoid double counting.
46+
- **OpenCode router sessions with missing usage are still reported.**
47+
Some OpenCode router/provider combinations can persist assistant messages
48+
with text or tool activity but zero token and cost fields. The OpenCode
49+
parser now keeps those turns as zero-cost calls instead of dropping the
50+
session entirely. Closes #341.
4651

4752
## 0.9.9 - 2026-05-15
4853

docs/providers/opencode.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ Per `<sessionId>:<messageId>`.
3232
token, and tool usage back to the root session.
3333
- Each message's `parts` are indexed; preserving the order matters for reasoning-token correctness.
3434
- Tokens are reported across `input`, `output`, `reasoning`, `cache.read`, and `cache.write`. Anthropic semantics.
35+
- Assistant messages with missing router usage are kept as zero-cost calls
36+
when their parts contain non-empty text or tool activity. Empty zero-usage
37+
assistant placeholders are still skipped.
3538
- External MCP tools are stored as `<server>_<tool>` names (for example
3639
`clickup_clickup_get_task`). The provider normalizes those to CodeBurn's
3740
canonical `mcp__<server>__<tool>` names before aggregation so shared MCP

src/providers/opencode.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,16 +264,19 @@ function createParser(
264264
cacheWrite: data.tokens?.cache?.write ?? 0,
265265
}
266266

267+
const msgParts = partsByMsg.get(msg.id) ?? []
268+
const toolParts = msgParts.filter((p) => p.type === 'tool' && normalizeToolName(p.tool))
269+
const hasTextOutput = msgParts.some((p) => p.type === 'text' && typeof p.text === 'string' && p.text.trim().length > 0)
270+
const hasActivity = hasTextOutput || toolParts.length > 0
271+
267272
const allZero =
268273
tokens.input === 0 &&
269274
tokens.output === 0 &&
270275
tokens.reasoning === 0 &&
271276
tokens.cacheRead === 0 &&
272277
tokens.cacheWrite === 0
273-
if (allZero && (data.cost ?? 0) === 0) continue
278+
if (allZero && (data.cost ?? 0) === 0 && !hasActivity) continue
274279

275-
const msgParts = partsByMsg.get(msg.id) ?? []
276-
const toolParts = msgParts.filter((p) => p.type === 'tool')
277280
const tools = toolParts
278281
.map((p) => normalizeToolName(p.tool))
279282
.filter(Boolean)

tests/providers/opencode.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,49 @@ skipUnlessSqlite('opencode provider - session parsing', () => {
469469
expect(calls).toHaveLength(0)
470470
})
471471

472+
it('keeps zero-usage assistant messages when router responses contain text', async () => {
473+
const dbPath = createTestDb(tmpDir)
474+
withTestDb(dbPath, (db) => {
475+
insertSession(db, 'sess-1')
476+
insertMessage(db, 'msg-u1', 'sess-1', 1700000000000, { role: 'user' })
477+
insertPart(db, 'part-u1', 'msg-u1', 'sess-1', { type: 'text', text: 'use the configured router' })
478+
insertMessage(db, 'msg-a1', 'sess-1', 1700000001000, {
479+
role: 'assistant', modelID: 'edenai/router-model', cost: 0,
480+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
481+
})
482+
insertPart(db, 'part-a1', 'msg-a1', 'sess-1', { type: 'text', text: 'router response text' })
483+
})
484+
485+
const calls = await collectCalls(createOpenCodeProvider(tmpDir), dbPath, 'sess-1')
486+
expect(calls).toHaveLength(1)
487+
expect(calls[0]!.model).toBe('edenai/router-model')
488+
expect(calls[0]!.inputTokens).toBe(0)
489+
expect(calls[0]!.outputTokens).toBe(0)
490+
expect(calls[0]!.costUSD).toBe(0)
491+
expect(calls[0]!.userMessage).toBe('use the configured router')
492+
})
493+
494+
it('keeps zero-usage assistant messages when router responses contain tool calls', async () => {
495+
const dbPath = createTestDb(tmpDir)
496+
withTestDb(dbPath, (db) => {
497+
insertSession(db, 'sess-1')
498+
insertMessage(db, 'msg-a1', 'sess-1', 1700000001000, {
499+
role: 'assistant', modelID: 'edenai/router-model', cost: 0,
500+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
501+
})
502+
insertPart(db, 'part-a1', 'msg-a1', 'sess-1', {
503+
type: 'tool', tool: 'bash',
504+
state: { status: 'completed', input: { command: 'npm test' } },
505+
})
506+
})
507+
508+
const calls = await collectCalls(createOpenCodeProvider(tmpDir), dbPath, 'sess-1')
509+
expect(calls).toHaveLength(1)
510+
expect(calls[0]!.tools).toEqual(['Bash'])
511+
expect(calls[0]!.bashCommands).toEqual(['npm'])
512+
expect(calls[0]!.costUSD).toBe(0)
513+
})
514+
472515
it('deduplicates messages across parses', async () => {
473516
const dbPath = createTestDb(tmpDir)
474517
withTestDb(dbPath, (db) => {

0 commit comments

Comments
 (0)