codeep 1.1.36 → 1.2.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/README.md +90 -4
- package/dist/api/index.js +64 -2
- package/dist/renderer/App.d.ts +5 -0
- package/dist/renderer/App.js +164 -227
- package/dist/renderer/components/Export.d.ts +22 -0
- package/dist/renderer/components/Export.js +64 -0
- package/dist/renderer/components/Help.js +5 -1
- package/dist/renderer/components/Logout.d.ts +29 -0
- package/dist/renderer/components/Logout.js +91 -0
- package/dist/renderer/components/Search.d.ts +30 -0
- package/dist/renderer/components/Search.js +83 -0
- package/dist/renderer/components/Settings.js +20 -0
- package/dist/renderer/components/Status.d.ts +6 -0
- package/dist/renderer/components/Status.js +20 -1
- package/dist/renderer/main.js +296 -142
- package/dist/utils/agent.d.ts +5 -0
- package/dist/utils/agent.js +238 -3
- package/dist/utils/agent.test.d.ts +1 -0
- package/dist/utils/agent.test.js +250 -0
- package/dist/utils/diffPreview.js +104 -35
- package/dist/utils/gitignore.d.ts +24 -0
- package/dist/utils/gitignore.js +161 -0
- package/dist/utils/gitignore.test.d.ts +1 -0
- package/dist/utils/gitignore.test.js +167 -0
- package/dist/utils/skills.d.ts +21 -0
- package/dist/utils/skills.js +51 -0
- package/dist/utils/smartContext.js +8 -0
- package/dist/utils/smartContext.test.d.ts +1 -0
- package/dist/utils/smartContext.test.js +382 -0
- package/dist/utils/tokenTracker.d.ts +52 -0
- package/dist/utils/tokenTracker.js +86 -0
- package/dist/utils/tools.d.ts +16 -0
- package/dist/utils/tools.js +146 -19
- package/dist/utils/tools.test.d.ts +1 -0
- package/dist/utils/tools.test.js +664 -0
- package/package.json +1 -1
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { extractTargetFile, formatSmartContext, } from './smartContext.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Helper to build a SmartContextResult quickly
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function makeContext(files, overrides) {
|
|
7
|
+
const fullFiles = files.map((f) => ({
|
|
8
|
+
path: f.path ?? `/project/${f.relativePath}`,
|
|
9
|
+
relativePath: f.relativePath,
|
|
10
|
+
reason: f.reason ?? 'test reason',
|
|
11
|
+
priority: f.priority ?? 5,
|
|
12
|
+
content: f.content, // may be undefined
|
|
13
|
+
size: f.size ?? (f.content ? f.content.length : 0),
|
|
14
|
+
}));
|
|
15
|
+
return {
|
|
16
|
+
files: fullFiles,
|
|
17
|
+
totalSize: overrides?.totalSize ?? fullFiles.reduce((s, f) => s + (f.content?.length ?? 0), 0),
|
|
18
|
+
truncated: overrides?.truncated ?? false,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// ===========================================================================
|
|
22
|
+
// extractTargetFile
|
|
23
|
+
// ===========================================================================
|
|
24
|
+
describe('extractTargetFile', () => {
|
|
25
|
+
// ---- Action-verb patterns (edit, modify, update, change, fix) ----------
|
|
26
|
+
describe('action verb patterns', () => {
|
|
27
|
+
it('should extract file from "edit <file>"', () => {
|
|
28
|
+
expect(extractTargetFile('edit src/utils/helper.ts')).toBe('src/utils/helper.ts');
|
|
29
|
+
});
|
|
30
|
+
it('should extract file from "modify the file <file>"', () => {
|
|
31
|
+
expect(extractTargetFile('modify the file src/index.js')).toBe('src/index.js');
|
|
32
|
+
});
|
|
33
|
+
it('should extract file from "update <file>"', () => {
|
|
34
|
+
expect(extractTargetFile('update config/settings.json')).toBe('config/settings.json');
|
|
35
|
+
});
|
|
36
|
+
it('should extract file from "change <file>"', () => {
|
|
37
|
+
expect(extractTargetFile('change lib/core.ts')).toBe('lib/core.ts');
|
|
38
|
+
});
|
|
39
|
+
it('should extract file from "fix <file>"', () => {
|
|
40
|
+
expect(extractTargetFile('fix src/api/endpoint.ts')).toBe('src/api/endpoint.ts');
|
|
41
|
+
});
|
|
42
|
+
it('should be case-insensitive for action verbs', () => {
|
|
43
|
+
expect(extractTargetFile('Edit src/utils/helper.ts')).toBe('src/utils/helper.ts');
|
|
44
|
+
expect(extractTargetFile('MODIFY src/utils/helper.ts')).toBe('src/utils/helper.ts');
|
|
45
|
+
});
|
|
46
|
+
it('should handle "the file" preamble', () => {
|
|
47
|
+
expect(extractTargetFile('edit the file src/utils/helper.ts')).toBe('src/utils/helper.ts');
|
|
48
|
+
});
|
|
49
|
+
it('should handle "the" without "file"', () => {
|
|
50
|
+
expect(extractTargetFile('fix the src/bug.ts')).toBe('src/bug.ts');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
// ---- "in/to" pattern ---------------------------------------------------
|
|
54
|
+
describe('"in" and "to" patterns', () => {
|
|
55
|
+
it('should extract file from "in <file>"', () => {
|
|
56
|
+
expect(extractTargetFile('add a function in src/utils/helper.ts')).toBe('src/utils/helper.ts');
|
|
57
|
+
});
|
|
58
|
+
it('should extract file from "to <file>"', () => {
|
|
59
|
+
expect(extractTargetFile('add a method to src/utils/helper.ts')).toBe('src/utils/helper.ts');
|
|
60
|
+
});
|
|
61
|
+
it('should extract file from "in the file <file>"', () => {
|
|
62
|
+
expect(extractTargetFile('refactor code in the file src/app.tsx')).toBe('src/app.tsx');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
// ---- Quoted and backtick-quoted paths ----------------------------------
|
|
66
|
+
describe('quoted file paths', () => {
|
|
67
|
+
it('should extract file from single quotes', () => {
|
|
68
|
+
expect(extractTargetFile("look at 'src/utils/helper.ts'")).toBe('src/utils/helper.ts');
|
|
69
|
+
});
|
|
70
|
+
it('should extract file from double quotes', () => {
|
|
71
|
+
expect(extractTargetFile('look at "src/utils/helper.ts"')).toBe('src/utils/helper.ts');
|
|
72
|
+
});
|
|
73
|
+
it('should extract file from backticks', () => {
|
|
74
|
+
expect(extractTargetFile('look at `src/utils/helper.ts`')).toBe('src/utils/helper.ts');
|
|
75
|
+
});
|
|
76
|
+
it('should extract quoted file after action verb', () => {
|
|
77
|
+
expect(extractTargetFile('edit "src/utils/helper.ts"')).toBe('src/utils/helper.ts');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// ---- Bare file path pattern (last resort) ------------------------------
|
|
81
|
+
describe('bare file paths', () => {
|
|
82
|
+
it('should extract a bare file path in the middle of text', () => {
|
|
83
|
+
expect(extractTargetFile('please review src/utils/helper.ts soon')).toBe('src/utils/helper.ts');
|
|
84
|
+
});
|
|
85
|
+
it('should extract a bare file at the start of text', () => {
|
|
86
|
+
expect(extractTargetFile('src/utils/helper.ts needs work')).toBe('src/utils/helper.ts');
|
|
87
|
+
});
|
|
88
|
+
it('should extract a bare file at the end of text', () => {
|
|
89
|
+
expect(extractTargetFile('please look at src/utils/helper.ts')).toBe('src/utils/helper.ts');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
// ---- Various file extensions -------------------------------------------
|
|
93
|
+
describe('various file extensions', () => {
|
|
94
|
+
it('should match .ts files', () => {
|
|
95
|
+
expect(extractTargetFile('edit app.ts')).toBe('app.ts');
|
|
96
|
+
});
|
|
97
|
+
it('should match .tsx files', () => {
|
|
98
|
+
expect(extractTargetFile('edit App.tsx')).toBe('App.tsx');
|
|
99
|
+
});
|
|
100
|
+
it('should match .js files', () => {
|
|
101
|
+
expect(extractTargetFile('edit index.js')).toBe('index.js');
|
|
102
|
+
});
|
|
103
|
+
it('should match .jsx files', () => {
|
|
104
|
+
expect(extractTargetFile('edit Component.jsx')).toBe('Component.jsx');
|
|
105
|
+
});
|
|
106
|
+
it('should match .py files', () => {
|
|
107
|
+
expect(extractTargetFile('edit main.py')).toBe('main.py');
|
|
108
|
+
});
|
|
109
|
+
it('should match .go files', () => {
|
|
110
|
+
expect(extractTargetFile('edit main.go')).toBe('main.go');
|
|
111
|
+
});
|
|
112
|
+
it('should match .json files', () => {
|
|
113
|
+
expect(extractTargetFile('edit package.json')).toBe('package.json');
|
|
114
|
+
});
|
|
115
|
+
it('should match .css files', () => {
|
|
116
|
+
expect(extractTargetFile('edit styles.css')).toBe('styles.css');
|
|
117
|
+
});
|
|
118
|
+
it('should match .yaml files', () => {
|
|
119
|
+
expect(extractTargetFile('edit config.yaml')).toBe('config.yaml');
|
|
120
|
+
});
|
|
121
|
+
it('should match .rs files', () => {
|
|
122
|
+
expect(extractTargetFile('edit src/main.rs')).toBe('src/main.rs');
|
|
123
|
+
});
|
|
124
|
+
it('should match .html files', () => {
|
|
125
|
+
expect(extractTargetFile('edit index.html')).toBe('index.html');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
// ---- Dotfiles, relative paths, and deeply nested paths -----------------
|
|
129
|
+
describe('path variations', () => {
|
|
130
|
+
it('should match relative paths with dot prefix', () => {
|
|
131
|
+
expect(extractTargetFile('edit ./src/helper.ts')).toBe('./src/helper.ts');
|
|
132
|
+
});
|
|
133
|
+
it('should match deeply nested paths', () => {
|
|
134
|
+
expect(extractTargetFile('edit src/components/ui/buttons/Primary.tsx')).toBe('src/components/ui/buttons/Primary.tsx');
|
|
135
|
+
});
|
|
136
|
+
it('should match filenames without directory', () => {
|
|
137
|
+
expect(extractTargetFile('edit index.ts')).toBe('index.ts');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
// ---- No match cases ----------------------------------------------------
|
|
141
|
+
describe('no match / edge cases', () => {
|
|
142
|
+
it('should return null for empty string', () => {
|
|
143
|
+
expect(extractTargetFile('')).toBeNull();
|
|
144
|
+
});
|
|
145
|
+
it('should return null for text with no file paths', () => {
|
|
146
|
+
expect(extractTargetFile('add a new feature to the app')).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
it('should return null for text with no file extension', () => {
|
|
149
|
+
expect(extractTargetFile('edit the README')).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
it('should return null when only directories are mentioned', () => {
|
|
152
|
+
expect(extractTargetFile('look inside src/utils/')).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
// ---- Priority of patterns (first match wins) --------------------------
|
|
156
|
+
describe('pattern priority', () => {
|
|
157
|
+
it('should prefer the action-verb pattern when task starts with an action', () => {
|
|
158
|
+
// "edit src/a.ts something in src/b.ts" should pick src/a.ts (first pattern)
|
|
159
|
+
const result = extractTargetFile('edit src/a.ts something in src/b.ts');
|
|
160
|
+
expect(result).toBe('src/a.ts');
|
|
161
|
+
});
|
|
162
|
+
it('should fall through to the quoted pattern when no action verb', () => {
|
|
163
|
+
const result = extractTargetFile('look at "config.json" please');
|
|
164
|
+
expect(result).toBe('config.json');
|
|
165
|
+
});
|
|
166
|
+
it('should fall through to the bare path pattern when nothing else matches', () => {
|
|
167
|
+
// No action verb, no "in/to", no quotes — bare path is last resort
|
|
168
|
+
const result = extractTargetFile('check src/helper.ts');
|
|
169
|
+
expect(result).toBe('src/helper.ts');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
// ===========================================================================
|
|
174
|
+
// formatSmartContext
|
|
175
|
+
// ===========================================================================
|
|
176
|
+
describe('formatSmartContext', () => {
|
|
177
|
+
// ---- Empty context -----------------------------------------------------
|
|
178
|
+
describe('empty context', () => {
|
|
179
|
+
it('should return empty string when no files', () => {
|
|
180
|
+
const ctx = makeContext([]);
|
|
181
|
+
expect(formatSmartContext(ctx)).toBe('');
|
|
182
|
+
});
|
|
183
|
+
it('should return empty string when files array is empty and truncated is true', () => {
|
|
184
|
+
const ctx = makeContext([], { truncated: true });
|
|
185
|
+
// No files => early return ''
|
|
186
|
+
expect(formatSmartContext(ctx)).toBe('');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
// ---- Single file -------------------------------------------------------
|
|
190
|
+
describe('single file with content', () => {
|
|
191
|
+
it('should format one file with header, reason, and code block', () => {
|
|
192
|
+
const ctx = makeContext([
|
|
193
|
+
{
|
|
194
|
+
relativePath: 'src/index.ts',
|
|
195
|
+
reason: 'target file',
|
|
196
|
+
content: 'console.log("hello");',
|
|
197
|
+
},
|
|
198
|
+
]);
|
|
199
|
+
const result = formatSmartContext(ctx);
|
|
200
|
+
expect(result).toContain('## Related Files (Smart Context)');
|
|
201
|
+
expect(result).toContain('### src/index.ts');
|
|
202
|
+
expect(result).toContain('> Reason: target file');
|
|
203
|
+
expect(result).toContain('```');
|
|
204
|
+
expect(result).toContain('console.log("hello");');
|
|
205
|
+
});
|
|
206
|
+
it('should not include truncation note when truncated is false', () => {
|
|
207
|
+
const ctx = makeContext([
|
|
208
|
+
{
|
|
209
|
+
relativePath: 'src/index.ts',
|
|
210
|
+
reason: 'target file',
|
|
211
|
+
content: 'code',
|
|
212
|
+
},
|
|
213
|
+
]);
|
|
214
|
+
const result = formatSmartContext(ctx);
|
|
215
|
+
expect(result).not.toContain('truncated due to size limits');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
// ---- File without content (skipped) ------------------------------------
|
|
219
|
+
describe('file without content', () => {
|
|
220
|
+
it('should skip files that have no content', () => {
|
|
221
|
+
const ctx = makeContext([
|
|
222
|
+
{
|
|
223
|
+
relativePath: 'src/big.ts',
|
|
224
|
+
reason: 'imported module',
|
|
225
|
+
content: undefined,
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
const result = formatSmartContext(ctx);
|
|
229
|
+
// Header is still produced because files.length > 0, but the file
|
|
230
|
+
// itself should not appear as a section
|
|
231
|
+
expect(result).toContain('## Related Files (Smart Context)');
|
|
232
|
+
expect(result).not.toContain('### src/big.ts');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
// ---- Multiple files ----------------------------------------------------
|
|
236
|
+
describe('multiple files', () => {
|
|
237
|
+
it('should list multiple files in order', () => {
|
|
238
|
+
const ctx = makeContext([
|
|
239
|
+
{
|
|
240
|
+
relativePath: 'src/a.ts',
|
|
241
|
+
reason: 'target file',
|
|
242
|
+
priority: 10,
|
|
243
|
+
content: 'const a = 1;',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
relativePath: 'src/b.ts',
|
|
247
|
+
reason: 'imported module',
|
|
248
|
+
priority: 8,
|
|
249
|
+
content: 'const b = 2;',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
relativePath: 'src/c.ts',
|
|
253
|
+
reason: 'type definitions',
|
|
254
|
+
priority: 7,
|
|
255
|
+
content: 'export type C = string;',
|
|
256
|
+
},
|
|
257
|
+
]);
|
|
258
|
+
const result = formatSmartContext(ctx);
|
|
259
|
+
expect(result).toContain('### src/a.ts');
|
|
260
|
+
expect(result).toContain('### src/b.ts');
|
|
261
|
+
expect(result).toContain('### src/c.ts');
|
|
262
|
+
expect(result).toContain('> Reason: target file');
|
|
263
|
+
expect(result).toContain('> Reason: imported module');
|
|
264
|
+
expect(result).toContain('> Reason: type definitions');
|
|
265
|
+
// Verify order: a.ts appears before b.ts, b.ts before c.ts
|
|
266
|
+
const posA = result.indexOf('### src/a.ts');
|
|
267
|
+
const posB = result.indexOf('### src/b.ts');
|
|
268
|
+
const posC = result.indexOf('### src/c.ts');
|
|
269
|
+
expect(posA).toBeLessThan(posB);
|
|
270
|
+
expect(posB).toBeLessThan(posC);
|
|
271
|
+
});
|
|
272
|
+
it('should include content from all files that have it', () => {
|
|
273
|
+
const ctx = makeContext([
|
|
274
|
+
{ relativePath: 'src/a.ts', content: 'AAA' },
|
|
275
|
+
{ relativePath: 'src/b.ts', content: undefined },
|
|
276
|
+
{ relativePath: 'src/c.ts', content: 'CCC' },
|
|
277
|
+
]);
|
|
278
|
+
const result = formatSmartContext(ctx);
|
|
279
|
+
expect(result).toContain('AAA');
|
|
280
|
+
expect(result).not.toContain('### src/b.ts');
|
|
281
|
+
expect(result).toContain('CCC');
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
// ---- Truncation warning ------------------------------------------------
|
|
285
|
+
describe('truncation warning', () => {
|
|
286
|
+
it('should include truncation note when truncated is true', () => {
|
|
287
|
+
const ctx = makeContext([
|
|
288
|
+
{
|
|
289
|
+
relativePath: 'src/index.ts',
|
|
290
|
+
reason: 'target file',
|
|
291
|
+
content: 'code',
|
|
292
|
+
},
|
|
293
|
+
], { truncated: true });
|
|
294
|
+
const result = formatSmartContext(ctx);
|
|
295
|
+
expect(result).toContain('> Note: Some files were truncated due to size limits.');
|
|
296
|
+
});
|
|
297
|
+
it('should not include truncation note when truncated is false', () => {
|
|
298
|
+
const ctx = makeContext([
|
|
299
|
+
{
|
|
300
|
+
relativePath: 'src/index.ts',
|
|
301
|
+
reason: 'target file',
|
|
302
|
+
content: 'code',
|
|
303
|
+
},
|
|
304
|
+
], { truncated: false });
|
|
305
|
+
const result = formatSmartContext(ctx);
|
|
306
|
+
expect(result).not.toContain('truncated');
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
// ---- Output structure --------------------------------------------------
|
|
310
|
+
describe('output structure', () => {
|
|
311
|
+
it('should start with the smart context header', () => {
|
|
312
|
+
const ctx = makeContext([
|
|
313
|
+
{ relativePath: 'src/index.ts', content: 'x' },
|
|
314
|
+
]);
|
|
315
|
+
const result = formatSmartContext(ctx);
|
|
316
|
+
const lines = result.split('\n');
|
|
317
|
+
expect(lines[0]).toBe('## Related Files (Smart Context)');
|
|
318
|
+
expect(lines[1]).toBe('');
|
|
319
|
+
});
|
|
320
|
+
it('should wrap file content in fenced code blocks', () => {
|
|
321
|
+
const ctx = makeContext([
|
|
322
|
+
{ relativePath: 'src/index.ts', content: 'const x = 1;' },
|
|
323
|
+
]);
|
|
324
|
+
const result = formatSmartContext(ctx);
|
|
325
|
+
// Find the code fences surrounding the content
|
|
326
|
+
const codeBlockStart = result.indexOf('```\nconst x = 1;');
|
|
327
|
+
const codeBlockEnd = result.indexOf('```', codeBlockStart + 3);
|
|
328
|
+
expect(codeBlockStart).toBeGreaterThan(-1);
|
|
329
|
+
expect(codeBlockEnd).toBeGreaterThan(codeBlockStart);
|
|
330
|
+
});
|
|
331
|
+
it('should separate file sections with blank lines', () => {
|
|
332
|
+
const ctx = makeContext([
|
|
333
|
+
{ relativePath: 'src/a.ts', content: 'a' },
|
|
334
|
+
{ relativePath: 'src/b.ts', content: 'b' },
|
|
335
|
+
]);
|
|
336
|
+
const result = formatSmartContext(ctx);
|
|
337
|
+
// After the closing ``` of a file, there should be a blank line
|
|
338
|
+
// before the next ### header
|
|
339
|
+
const closingFenceA = result.indexOf('```\n\n### src/b.ts');
|
|
340
|
+
expect(closingFenceA).toBeGreaterThan(-1);
|
|
341
|
+
});
|
|
342
|
+
it('should include reason as a blockquote', () => {
|
|
343
|
+
const ctx = makeContext([
|
|
344
|
+
{ relativePath: 'x.ts', reason: 'imported module', content: 'y' },
|
|
345
|
+
]);
|
|
346
|
+
const result = formatSmartContext(ctx);
|
|
347
|
+
expect(result).toContain('> Reason: imported module');
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
// ---- Content with special characters -----------------------------------
|
|
351
|
+
describe('special content', () => {
|
|
352
|
+
it('should handle content with backticks inside code blocks', () => {
|
|
353
|
+
const ctx = makeContext([
|
|
354
|
+
{
|
|
355
|
+
relativePath: 'src/template.ts',
|
|
356
|
+
content: 'const s = `hello ${name}`;',
|
|
357
|
+
},
|
|
358
|
+
]);
|
|
359
|
+
const result = formatSmartContext(ctx);
|
|
360
|
+
expect(result).toContain('const s = `hello ${name}`;');
|
|
361
|
+
});
|
|
362
|
+
it('should handle empty string content', () => {
|
|
363
|
+
const ctx = makeContext([
|
|
364
|
+
{
|
|
365
|
+
relativePath: 'src/empty.ts',
|
|
366
|
+
content: '',
|
|
367
|
+
},
|
|
368
|
+
]);
|
|
369
|
+
// Empty content is falsy, so the file section is skipped
|
|
370
|
+
const result = formatSmartContext(ctx);
|
|
371
|
+
expect(result).not.toContain('### src/empty.ts');
|
|
372
|
+
});
|
|
373
|
+
it('should handle multiline content', () => {
|
|
374
|
+
const multiline = 'line1\nline2\nline3';
|
|
375
|
+
const ctx = makeContext([
|
|
376
|
+
{ relativePath: 'src/multi.ts', content: multiline },
|
|
377
|
+
]);
|
|
378
|
+
const result = formatSmartContext(ctx);
|
|
379
|
+
expect(result).toContain('line1\nline2\nline3');
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token and cost tracking for API usage
|
|
3
|
+
*/
|
|
4
|
+
export interface TokenUsage {
|
|
5
|
+
promptTokens: number;
|
|
6
|
+
completionTokens: number;
|
|
7
|
+
totalTokens: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionTokenStats {
|
|
10
|
+
totalPromptTokens: number;
|
|
11
|
+
totalCompletionTokens: number;
|
|
12
|
+
totalTokens: number;
|
|
13
|
+
requestCount: number;
|
|
14
|
+
estimatedCost: number;
|
|
15
|
+
}
|
|
16
|
+
interface TokenRecord {
|
|
17
|
+
timestamp: number;
|
|
18
|
+
promptTokens: number;
|
|
19
|
+
completionTokens: number;
|
|
20
|
+
totalTokens: number;
|
|
21
|
+
model: string;
|
|
22
|
+
provider: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Record token usage from an API response
|
|
26
|
+
*/
|
|
27
|
+
export declare function recordTokenUsage(usage: TokenUsage, model: string, provider: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Extract token usage from OpenAI-format API response
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractOpenAIUsage(data: any): TokenUsage | null;
|
|
32
|
+
/**
|
|
33
|
+
* Extract token usage from Anthropic-format API response
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractAnthropicUsage(data: any): TokenUsage | null;
|
|
36
|
+
/**
|
|
37
|
+
* Get session stats
|
|
38
|
+
*/
|
|
39
|
+
export declare function getSessionStats(): SessionTokenStats;
|
|
40
|
+
/**
|
|
41
|
+
* Get last request usage
|
|
42
|
+
*/
|
|
43
|
+
export declare function getLastUsage(): TokenRecord | null;
|
|
44
|
+
/**
|
|
45
|
+
* Format token count for display (e.g., 1234 -> "1.2K")
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatTokenCount(tokens: number): string;
|
|
48
|
+
/**
|
|
49
|
+
* Reset session tracking
|
|
50
|
+
*/
|
|
51
|
+
export declare function resetTokenTracking(): void;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token and cost tracking for API usage
|
|
3
|
+
*/
|
|
4
|
+
// Session-level accumulator
|
|
5
|
+
const records = [];
|
|
6
|
+
/**
|
|
7
|
+
* Record token usage from an API response
|
|
8
|
+
*/
|
|
9
|
+
export function recordTokenUsage(usage, model, provider) {
|
|
10
|
+
records.push({
|
|
11
|
+
timestamp: Date.now(),
|
|
12
|
+
promptTokens: usage.promptTokens,
|
|
13
|
+
completionTokens: usage.completionTokens,
|
|
14
|
+
totalTokens: usage.totalTokens,
|
|
15
|
+
model,
|
|
16
|
+
provider,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Extract token usage from OpenAI-format API response
|
|
21
|
+
*/
|
|
22
|
+
export function extractOpenAIUsage(data) {
|
|
23
|
+
if (data?.usage) {
|
|
24
|
+
return {
|
|
25
|
+
promptTokens: data.usage.prompt_tokens || 0,
|
|
26
|
+
completionTokens: data.usage.completion_tokens || 0,
|
|
27
|
+
totalTokens: data.usage.total_tokens || 0,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract token usage from Anthropic-format API response
|
|
34
|
+
*/
|
|
35
|
+
export function extractAnthropicUsage(data) {
|
|
36
|
+
if (data?.usage) {
|
|
37
|
+
return {
|
|
38
|
+
promptTokens: data.usage.input_tokens || 0,
|
|
39
|
+
completionTokens: data.usage.output_tokens || 0,
|
|
40
|
+
totalTokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get session stats
|
|
47
|
+
*/
|
|
48
|
+
export function getSessionStats() {
|
|
49
|
+
let totalPromptTokens = 0;
|
|
50
|
+
let totalCompletionTokens = 0;
|
|
51
|
+
let totalTokens = 0;
|
|
52
|
+
for (const record of records) {
|
|
53
|
+
totalPromptTokens += record.promptTokens;
|
|
54
|
+
totalCompletionTokens += record.completionTokens;
|
|
55
|
+
totalTokens += record.totalTokens;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
totalPromptTokens,
|
|
59
|
+
totalCompletionTokens,
|
|
60
|
+
totalTokens,
|
|
61
|
+
requestCount: records.length,
|
|
62
|
+
estimatedCost: 0, // Cost estimation requires price-per-token which varies by provider
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get last request usage
|
|
67
|
+
*/
|
|
68
|
+
export function getLastUsage() {
|
|
69
|
+
return records.length > 0 ? records[records.length - 1] : null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Format token count for display (e.g., 1234 -> "1.2K")
|
|
73
|
+
*/
|
|
74
|
+
export function formatTokenCount(tokens) {
|
|
75
|
+
if (tokens < 1000)
|
|
76
|
+
return tokens.toString();
|
|
77
|
+
if (tokens < 1000000)
|
|
78
|
+
return (tokens / 1000).toFixed(1) + 'K';
|
|
79
|
+
return (tokens / 1000000).toFixed(2) + 'M';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reset session tracking
|
|
83
|
+
*/
|
|
84
|
+
export function resetTokenTracking() {
|
|
85
|
+
records.length = 0;
|
|
86
|
+
}
|
package/dist/utils/tools.d.ts
CHANGED
|
@@ -172,6 +172,22 @@ export declare const AGENT_TOOLS: {
|
|
|
172
172
|
};
|
|
173
173
|
};
|
|
174
174
|
};
|
|
175
|
+
find_files: {
|
|
176
|
+
name: string;
|
|
177
|
+
description: string;
|
|
178
|
+
parameters: {
|
|
179
|
+
pattern: {
|
|
180
|
+
type: string;
|
|
181
|
+
description: string;
|
|
182
|
+
required: boolean;
|
|
183
|
+
};
|
|
184
|
+
path: {
|
|
185
|
+
type: string;
|
|
186
|
+
description: string;
|
|
187
|
+
required: boolean;
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
};
|
|
175
191
|
fetch_url: {
|
|
176
192
|
name: string;
|
|
177
193
|
description: string;
|