Skip to content

Commit b627462

Browse files
authored
Merge pull request #49 from drivecore/emoji-log-prefixes
Emoji log prefixes
2 parents 8e3b838 + 352ba5b commit b627462

38 files changed

+356
-358
lines changed

packages/agent/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# mycoder-agent
22

3+
## 0.2.0
4+
5+
### Minor Changes
6+
7+
- Add token caching, better user input handling, token usage logging (--tokenUsage), the ability to see the browser (--headless=false), and log prefixes with emojis.
8+
39
## 0.1.3
410

511
### Patch Changes

packages/agent/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mycoder-agent",
3-
"version": "0.1.4",
3+
"version": "0.2.0",
44
"description": "Agent module for mycoder - an AI-powered software development assistant",
55
"type": "module",
66
"main": "dist/index.js",

packages/agent/src/core/executeToolCall.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ export const executeToolCall = async (
1212
tools: Tool[],
1313
context: ToolContext,
1414
): Promise<string> => {
15-
const logger = new Logger({
16-
name: `Tool:${toolCall.name}`,
17-
parent: context.logger,
18-
});
19-
2015
const tool = tools.find((t) => t.name === toolCall.name);
2116
if (!tool) {
2217
throw new Error(`No tool with the name '${toolCall.name}' exists.`);
2318
}
2419

20+
const logger = new Logger({
21+
name: `Tool:${toolCall.name}`,
22+
parent: context.logger,
23+
customPrefix: tool.logPrefix,
24+
});
25+
2526
const toolContext = {
2627
...context,
2728
logger,

packages/agent/src/core/tokens.ts

Lines changed: 91 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,100 @@
11
import Anthropic from '@anthropic-ai/sdk';
22

3-
export type TokenUsage = {
4-
input: number;
5-
inputCacheWrites: number;
6-
inputCacheReads: number;
7-
output: number;
8-
};
9-
10-
export const getTokenUsage = (response: Anthropic.Message): TokenUsage => {
11-
return {
12-
input: response.usage.input_tokens,
13-
inputCacheWrites: response.usage.cache_creation_input_tokens ?? 0,
14-
inputCacheReads: response.usage.cache_read_input_tokens ?? 0,
15-
output: response.usage.output_tokens,
16-
};
17-
};
18-
19-
export const addTokenUsage = (a: TokenUsage, b: TokenUsage): TokenUsage => {
20-
return {
21-
input: a.input + b.input,
22-
inputCacheWrites: a.inputCacheWrites + b.inputCacheWrites,
23-
inputCacheReads: a.inputCacheReads + b.inputCacheReads,
24-
output: a.output + b.output,
25-
};
26-
};
3+
import { LogLevel } from '../utils/logger.js';
274

285
const PER_MILLION = 1 / 1000000;
29-
const TOKEN_COST: TokenUsage = {
6+
const TOKEN_COST = {
307
input: 3 * PER_MILLION,
31-
inputCacheWrites: 3.75 * PER_MILLION,
32-
inputCacheReads: 0.3 * PER_MILLION,
8+
cacheWrites: 3.75 * PER_MILLION,
9+
cacheReads: 0.3 * PER_MILLION,
3310
output: 15 * PER_MILLION,
3411
};
3512

36-
const formatter = new Intl.NumberFormat('en-US', {
37-
style: 'currency',
38-
currency: 'USD',
39-
minimumFractionDigits: 2,
40-
});
41-
42-
export const getTokenCost = (usage: TokenUsage): string => {
43-
return formatter.format(
44-
usage.input * TOKEN_COST.input +
45-
usage.inputCacheWrites * TOKEN_COST.inputCacheWrites +
46-
usage.inputCacheReads * TOKEN_COST.inputCacheReads +
47-
usage.output * TOKEN_COST.output,
48-
);
49-
};
13+
export class TokenUsage {
14+
public input: number = 0;
15+
public cacheWrites: number = 0;
16+
public cacheReads: number = 0;
17+
public output: number = 0;
5018

51-
export const getTokenString = (usage: TokenUsage): string => {
52-
return `input: ${usage.input} input-cache-writes: ${usage.inputCacheWrites} input-cache-reads: ${usage.inputCacheReads} output: ${usage.output} COST: ${getTokenCost(usage)}`;
53-
};
19+
constructor() {}
20+
21+
add(usage: TokenUsage) {
22+
this.input += usage.input;
23+
this.cacheWrites += usage.cacheWrites;
24+
this.cacheReads += usage.cacheReads;
25+
this.output += usage.output;
26+
}
27+
28+
clone() {
29+
const usage = new TokenUsage();
30+
usage.input = this.input;
31+
usage.cacheWrites = this.cacheWrites;
32+
usage.cacheReads = this.cacheReads;
33+
usage.output = this.output;
34+
return usage;
35+
}
36+
37+
static fromMessage(message: Anthropic.Message) {
38+
const usage = new TokenUsage();
39+
usage.input = message.usage.input_tokens;
40+
usage.cacheWrites = message.usage.cache_creation_input_tokens ?? 0;
41+
usage.cacheReads = message.usage.cache_read_input_tokens ?? 0;
42+
usage.output = message.usage.output_tokens;
43+
return usage;
44+
}
45+
46+
static sum(usages: TokenUsage[]) {
47+
const usage = new TokenUsage();
48+
usages.forEach((u) => usage.add(u));
49+
return usage;
50+
}
51+
52+
getCost() {
53+
const formatter = new Intl.NumberFormat('en-US', {
54+
style: 'currency',
55+
currency: 'USD',
56+
minimumFractionDigits: 2,
57+
});
58+
59+
return formatter.format(
60+
this.input * TOKEN_COST.input +
61+
this.cacheWrites * TOKEN_COST.cacheWrites +
62+
this.cacheReads * TOKEN_COST.cacheReads +
63+
this.output * TOKEN_COST.output,
64+
);
65+
}
66+
67+
toString() {
68+
return `input: ${this.input} cache-writes: ${this.cacheWrites} cache-reads: ${this.cacheReads} output: ${this.output} COST: ${this.getCost()}`;
69+
}
70+
}
71+
72+
export class TokenTracker {
73+
public tokenUsage = new TokenUsage();
74+
public children: TokenTracker[] = [];
75+
76+
constructor(
77+
public readonly name: string = 'unnamed',
78+
public readonly parent: TokenTracker | undefined = undefined,
79+
public readonly logLevel: LogLevel = parent?.logLevel ?? LogLevel.debug,
80+
) {
81+
if (parent) {
82+
parent.children.push(this);
83+
}
84+
}
85+
86+
getTotalUsage() {
87+
const usage = this.tokenUsage.clone();
88+
this.children.forEach((child) => usage.add(child.getTotalUsage()));
89+
return usage;
90+
}
91+
92+
getTotalCost() {
93+
const usage = this.getTotalUsage();
94+
return usage.getCost();
95+
}
96+
97+
toString() {
98+
return `${this.name}: ${this.getTotalUsage().toString()}`;
99+
}
100+
}

packages/agent/src/core/toolAgent.cache.test.ts

Lines changed: 0 additions & 102 deletions
This file was deleted.

packages/agent/src/core/toolAgent.respawn.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ import { toolAgent } from '../../src/core/toolAgent.js';
44
import { getTools } from '../../src/tools/getTools.js';
55
import { MockLogger } from '../utils/mockLogger.js';
66

7-
const logger = new MockLogger();
7+
import { TokenTracker } from './tokens.js';
88

9+
const toolContext = {
10+
logger: new MockLogger(),
11+
headless: true,
12+
workingDirectory: '.',
13+
tokenTracker: new TokenTracker(),
14+
};
915
// Mock Anthropic SDK
1016
vi.mock('@anthropic-ai/sdk', () => {
1117
return {
@@ -52,12 +58,7 @@ describe('toolAgent respawn functionality', () => {
5258
temperature: 0,
5359
getSystemPrompt: () => 'test system prompt',
5460
},
55-
{
56-
logger,
57-
headless: true,
58-
workingDirectory: '.',
59-
tokenLevel: 'debug',
60-
},
61+
toolContext,
6162
);
6263

6364
expect(result.result).toBe(

0 commit comments

Comments
 (0)