poe-code 3.0.427 → 3.0.428
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/dist/index.js +2412 -2135
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/dist/providers/poe-agent.js.map +1 -1
- package/package.json +2 -1
- package/packages/agent-trace-viewer/dist/breakdown.d.ts +5 -1
- package/packages/agent-trace-viewer/dist/breakdown.js +54 -5
- package/packages/agent-trace-viewer/dist/context.d.ts +1 -1
- package/packages/agent-trace-viewer/dist/context.js +2 -5
- package/packages/agent-trace-viewer/dist/loader.js +45 -2
- package/packages/agent-trace-viewer/dist/render.d.ts +4 -1
- package/packages/agent-trace-viewer/dist/render.js +63 -19
- package/packages/agent-trace-viewer/dist/run.js +31 -7
- package/packages/agent-trace-viewer/dist/token-cache.d.ts +10 -0
- package/packages/agent-trace-viewer/dist/token-cache.js +56 -0
- package/packages/agent-trace-viewer/dist/types.d.ts +5 -0
- package/packages/agent-traces/dist/types.d.ts +1 -0
- package/packages/tiny-http-mcp-server/dist/cli.js +23 -18
- package/packages/tiny-http-mcp-server/dist/http-transport.d.ts +6 -0
- package/packages/tiny-http-mcp-server/dist/http-transport.js +89 -55
- package/packages/tokenfill/dist/index.d.ts +0 -1
- package/packages/tokenfill/dist/index.js +0 -1
- package/packages/toolcraft-design/dist/explorer/reducer.js +14 -2
- package/packages/tokenfill/dist/estimate.d.ts +0 -2
- package/packages/tokenfill/dist/estimate.js +0 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poe-code",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.428",
|
|
4
4
|
"description": "CLI tool to configure Poe API for developer workflows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -196,6 +196,7 @@
|
|
|
196
196
|
"ignore": "^5.3.2",
|
|
197
197
|
"jose": "^6.1.3",
|
|
198
198
|
"jsonc-parser": "^3.3.1",
|
|
199
|
+
"lru-cache": "^10.4.3",
|
|
199
200
|
"mustache": "^4.2.0",
|
|
200
201
|
"openai": "^6.34.0",
|
|
201
202
|
"parse-duration": "^2.1.5",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { NormalizedTrace } from "../../agent-traces/dist/index.js";
|
|
2
2
|
import type { ContextBreakdown } from "./types.js";
|
|
3
|
-
export
|
|
3
|
+
export interface ComputeContextBreakdownOptions {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
mode?: "exact" | "estimated";
|
|
6
|
+
}
|
|
7
|
+
export declare function computeContextBreakdown(trace: NormalizedTrace, options?: ComputeContextBreakdownOptions): Promise<ContextBreakdown>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { countTokens } from "tokenfill";
|
|
2
2
|
const MATCHERS = [
|
|
3
3
|
{
|
|
4
4
|
id: "system-prompt",
|
|
@@ -44,15 +44,28 @@ const MATCHERS = [
|
|
|
44
44
|
matches: () => true
|
|
45
45
|
}
|
|
46
46
|
];
|
|
47
|
-
|
|
47
|
+
const TURNS_PER_YIELD = 256;
|
|
48
|
+
const ESTIMATOR_SAMPLE_TARGET_CHARS = 16_384;
|
|
49
|
+
const ESTIMATOR_SAMPLE_CHARS_PER_TURN = 1_024;
|
|
50
|
+
const ESTIMATOR_DEFAULT_CHARS_PER_TOKEN = 4;
|
|
51
|
+
export async function computeContextBreakdown(trace, options = {}) {
|
|
52
|
+
const mode = options.mode ?? "exact";
|
|
53
|
+
const estimate = mode === "exact" ? undefined : createTraceTokenEstimator(trace.turns);
|
|
48
54
|
const categories = MATCHERS.map((matcher) => ({
|
|
49
55
|
id: matcher.id,
|
|
50
56
|
label: matcher.label,
|
|
51
57
|
tokens: 0,
|
|
52
58
|
items: new Map()
|
|
53
59
|
}));
|
|
54
|
-
for (
|
|
55
|
-
|
|
60
|
+
for (let index = 0; index < trace.turns.length; index += 1) {
|
|
61
|
+
if (mode === "exact" && index > 0 && index % TURNS_PER_YIELD === 0) {
|
|
62
|
+
await yieldToEventLoop();
|
|
63
|
+
if (options.signal?.aborted) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const turn = trace.turns[index];
|
|
68
|
+
const tokens = estimate === undefined ? await countTokensYielding(turn.text) : estimate(turn.text);
|
|
56
69
|
const matcherIndex = MATCHERS.findIndex((matcher) => matcher.matches(turn));
|
|
57
70
|
const matcher = MATCHERS[matcherIndex];
|
|
58
71
|
const category = categories[matcherIndex];
|
|
@@ -85,6 +98,42 @@ export function computeContextBreakdown(trace) {
|
|
|
85
98
|
}));
|
|
86
99
|
return {
|
|
87
100
|
measuredTokens,
|
|
88
|
-
categories: outputCategories
|
|
101
|
+
categories: outputCategories,
|
|
102
|
+
source: mode
|
|
89
103
|
};
|
|
90
104
|
}
|
|
105
|
+
function createTraceTokenEstimator(turns) {
|
|
106
|
+
const sampleParts = [];
|
|
107
|
+
let sampleLength = 0;
|
|
108
|
+
for (const turn of turns) {
|
|
109
|
+
if (sampleLength >= ESTIMATOR_SAMPLE_TARGET_CHARS) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
const part = turn.text.slice(0, Math.min(ESTIMATOR_SAMPLE_CHARS_PER_TURN, ESTIMATOR_SAMPLE_TARGET_CHARS - sampleLength));
|
|
113
|
+
sampleParts.push(part);
|
|
114
|
+
sampleLength += part.length;
|
|
115
|
+
}
|
|
116
|
+
const sample = sampleParts.join("");
|
|
117
|
+
const sampleTokens = sample.length === 0 ? 0 : countTokens(sample);
|
|
118
|
+
const charsPerToken = sampleTokens === 0 ? ESTIMATOR_DEFAULT_CHARS_PER_TOKEN : sample.length / sampleTokens;
|
|
119
|
+
return (text) => Math.round(text.length / charsPerToken);
|
|
120
|
+
}
|
|
121
|
+
const COUNT_CHUNK_CHARS = 512 * 1024;
|
|
122
|
+
async function countTokensYielding(text) {
|
|
123
|
+
if (text.length <= COUNT_CHUNK_CHARS) {
|
|
124
|
+
return countTokens(text);
|
|
125
|
+
}
|
|
126
|
+
let total = 0;
|
|
127
|
+
for (let index = 0; index < text.length; index += COUNT_CHUNK_CHARS) {
|
|
128
|
+
if (index > 0) {
|
|
129
|
+
await yieldToEventLoop();
|
|
130
|
+
}
|
|
131
|
+
total += countTokens(text.slice(index, index + COUNT_CHUNK_CHARS));
|
|
132
|
+
}
|
|
133
|
+
return total;
|
|
134
|
+
}
|
|
135
|
+
function yieldToEventLoop() {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
setImmediate(resolve);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
@@ -5,4 +5,4 @@ export declare const CONTEXT_WINDOWS: {
|
|
|
5
5
|
match: string;
|
|
6
6
|
window: number;
|
|
7
7
|
}[];
|
|
8
|
-
export declare function computeContextUsage(trace: NormalizedTrace): ContextUsage;
|
|
8
|
+
export declare function computeContextUsage(trace: NormalizedTrace, measuredTokens: number): ContextUsage;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { estimateTokens } from "tokenfill";
|
|
2
1
|
export const DEFAULT_CONTEXT_WINDOW = 200000;
|
|
3
2
|
export const CONTEXT_WINDOWS = [{ match: "claude", window: 200000 }];
|
|
4
|
-
export function computeContextUsage(trace) {
|
|
3
|
+
export function computeContextUsage(trace, measuredTokens) {
|
|
5
4
|
const window = resolveContextWindow(trace);
|
|
6
|
-
const tokens = trace.usage !== undefined
|
|
7
|
-
? trace.usage.contextTokens
|
|
8
|
-
: trace.turns.reduce((sum, turn) => sum + estimateTokens(turn.text), 0);
|
|
5
|
+
const tokens = trace.usage !== undefined ? trace.usage.contextTokens : measuredTokens;
|
|
9
6
|
return {
|
|
10
7
|
tokens,
|
|
11
8
|
window,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { traceReaders } from "@poe-code/agent-traces";
|
|
2
2
|
import { computeContextBreakdown } from "./breakdown.js";
|
|
3
3
|
import { computeContextUsage } from "./context.js";
|
|
4
|
+
import { defaultTraceTokenCacheDir, readCachedBreakdown, traceFileIdentity, writeCachedBreakdown } from "./token-cache.js";
|
|
4
5
|
export async function listTraces(options) {
|
|
5
6
|
const readers = options.sources === undefined
|
|
6
7
|
? traceReaders
|
|
@@ -29,13 +30,55 @@ export async function loadTrace(reference, options) {
|
|
|
29
30
|
if (reader === undefined) {
|
|
30
31
|
throw new Error(`No trace reader registered for source: ${reference.source}`);
|
|
31
32
|
}
|
|
33
|
+
const filePath = reference.path;
|
|
34
|
+
const identity = filePath === undefined ? undefined : await traceFileIdentity(options.fs, filePath);
|
|
32
35
|
const trace = await reader.read(reference, { fs: options.fs });
|
|
36
|
+
const breakdown = await loadBreakdown(trace, filePath, identity, options);
|
|
33
37
|
return {
|
|
34
38
|
...trace,
|
|
35
|
-
context: computeContextUsage(trace),
|
|
36
|
-
breakdown
|
|
39
|
+
context: computeContextUsage(trace, breakdown.measuredTokens),
|
|
40
|
+
breakdown
|
|
37
41
|
};
|
|
38
42
|
}
|
|
43
|
+
const exactBreakdownsInFlight = new Map();
|
|
44
|
+
async function loadBreakdown(trace, filePath, identity, options) {
|
|
45
|
+
const cacheDir = options.cacheDir ?? defaultTraceTokenCacheDir();
|
|
46
|
+
if (filePath !== undefined && identity !== undefined) {
|
|
47
|
+
const cached = await readCachedBreakdown(options.fs, cacheDir, filePath, identity);
|
|
48
|
+
if (cached !== undefined) {
|
|
49
|
+
return cached;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (options.deferExactTokens === true) {
|
|
53
|
+
const estimated = await computeContextBreakdown(trace, { mode: "estimated" });
|
|
54
|
+
if (filePath !== undefined && identity !== undefined && options.signal?.aborted !== true) {
|
|
55
|
+
scheduleExactBreakdown(trace, filePath, identity, cacheDir, options);
|
|
56
|
+
}
|
|
57
|
+
return estimated;
|
|
58
|
+
}
|
|
59
|
+
const breakdown = await computeContextBreakdown(trace, { signal: options.signal });
|
|
60
|
+
if (filePath !== undefined && identity !== undefined && options.signal?.aborted !== true) {
|
|
61
|
+
await writeCachedBreakdown(options.fs, cacheDir, filePath, identity, breakdown);
|
|
62
|
+
}
|
|
63
|
+
return breakdown;
|
|
64
|
+
}
|
|
65
|
+
function scheduleExactBreakdown(trace, filePath, identity, cacheDir, options) {
|
|
66
|
+
if (exactBreakdownsInFlight.has(filePath)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const task = (async () => {
|
|
70
|
+
const breakdown = await computeContextBreakdown(trace);
|
|
71
|
+
await writeCachedBreakdown(options.fs, cacheDir, filePath, identity, breakdown);
|
|
72
|
+
options.onExactBreakdown?.(breakdown);
|
|
73
|
+
})()
|
|
74
|
+
.catch(() => {
|
|
75
|
+
// Exact counting is best-effort in the background; the estimate stays visible.
|
|
76
|
+
})
|
|
77
|
+
.finally(() => {
|
|
78
|
+
exactBreakdownsInFlight.delete(filePath);
|
|
79
|
+
});
|
|
80
|
+
exactBreakdownsInFlight.set(filePath, task);
|
|
81
|
+
}
|
|
39
82
|
export async function loadSubagentSummaries(view, options) {
|
|
40
83
|
if (view.children === undefined || view.children.length === 0) {
|
|
41
84
|
return [];
|
|
@@ -7,4 +7,7 @@ export declare function renderTraceLine(item: TraceReference): {
|
|
|
7
7
|
meta: string;
|
|
8
8
|
};
|
|
9
9
|
export declare function renderSubagents(summaries: SubagentSummary[]): string;
|
|
10
|
-
export
|
|
10
|
+
export interface RenderTraceDetailOptions {
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
export declare function renderTraceDetail(view: TraceView, subagents?: SubagentSummary[], options?: RenderTraceDetailOptions): Promise<string>;
|
|
@@ -8,7 +8,7 @@ const MAX_TRACE_META_CWD_WIDTH = 18;
|
|
|
8
8
|
const MAX_SUBAGENT_AGENT_WIDTH = 13;
|
|
9
9
|
const MAX_SUBAGENT_DESCRIPTION_WIDTH = 26;
|
|
10
10
|
const COLLAPSED_TURN_LINES = 3;
|
|
11
|
-
const
|
|
11
|
+
const TURNS_PER_RENDER_YIELD = 256;
|
|
12
12
|
const CATEGORY_ORDER = [
|
|
13
13
|
"system-prompt",
|
|
14
14
|
"skills",
|
|
@@ -58,11 +58,14 @@ export function renderBreakdown(breakdown, width = 32) {
|
|
|
58
58
|
return "Context breakdown\n No context tokens measured";
|
|
59
59
|
}
|
|
60
60
|
const theme = getTheme();
|
|
61
|
+
const title = breakdown.source === "estimated"
|
|
62
|
+
? `Context breakdown ${theme.muted("(counting exact tokens…)")}`
|
|
63
|
+
: "Context breakdown";
|
|
61
64
|
const bar = renderSegmentedBar(categories, normalizeGaugeWidth(width), theme);
|
|
62
65
|
const labelWidth = Math.max(10, ...categories.map((category) => category.label.length));
|
|
63
66
|
const tokenWidth = Math.max(1, ...categories.map((category) => formatCount(category.tokens).length));
|
|
64
67
|
const percentWidth = Math.max(3, ...categories.map((category) => `${category.percent}%`.length));
|
|
65
|
-
const lines = [
|
|
68
|
+
const lines = [title, ` ${bar}`];
|
|
66
69
|
for (const category of categories) {
|
|
67
70
|
const styler = categoryColor(category.id, theme);
|
|
68
71
|
lines.push([
|
|
@@ -130,7 +133,7 @@ export function renderSubagents(summaries) {
|
|
|
130
133
|
}
|
|
131
134
|
return lines.join("\n");
|
|
132
135
|
}
|
|
133
|
-
export function renderTraceDetail(view, subagents = []) {
|
|
136
|
+
export async function renderTraceDetail(view, subagents = [], options = {}) {
|
|
134
137
|
const theme = getTheme();
|
|
135
138
|
const lines = [
|
|
136
139
|
theme.header(truncate(sanitizeInline(view.title?.trim() || view.id), MAX_RENDER_WIDTH)),
|
|
@@ -152,26 +155,24 @@ export function renderTraceDetail(view, subagents = []) {
|
|
|
152
155
|
lines.push("", theme.header("Conversation"));
|
|
153
156
|
if (view.turns.length === 0) {
|
|
154
157
|
lines.push(` ${theme.muted("No turns")}`);
|
|
158
|
+
return lines.join("\n");
|
|
155
159
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (conversationLines.length >= MAX_CONVERSATION_LINES) {
|
|
160
|
+
for (let index = 0; index < view.turns.length; index += 1) {
|
|
161
|
+
if (index > 0 && index % TURNS_PER_RENDER_YIELD === 0) {
|
|
162
|
+
await yieldToEventLoop();
|
|
163
|
+
if (options.signal?.aborted) {
|
|
161
164
|
break;
|
|
162
165
|
}
|
|
163
|
-
conversationLines.push(...renderTurn(turn, theme));
|
|
164
|
-
renderedTurns += 1;
|
|
165
|
-
}
|
|
166
|
-
lines.push(...conversationLines.slice(0, MAX_CONVERSATION_LINES));
|
|
167
|
-
const truncatedLastTurn = conversationLines.length > MAX_CONVERSATION_LINES;
|
|
168
|
-
const remaining = view.turns.length - renderedTurns + (truncatedLastTurn ? 1 : 0);
|
|
169
|
-
if (remaining > 0) {
|
|
170
|
-
lines.push(` ${theme.muted(`… ${remaining} more turns`)}`);
|
|
171
166
|
}
|
|
167
|
+
lines.push(...renderTurn(view.turns[index], theme));
|
|
172
168
|
}
|
|
173
169
|
return lines.join("\n");
|
|
174
170
|
}
|
|
171
|
+
function yieldToEventLoop() {
|
|
172
|
+
return new Promise((resolve) => {
|
|
173
|
+
setImmediate(resolve);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
175
176
|
function renderBreakdownItems(items, minimumTokenWidth, theme) {
|
|
176
177
|
const shown = items.slice(0, 5);
|
|
177
178
|
const labels = shown.map((item) => truncate(item.name, 32));
|
|
@@ -247,14 +248,23 @@ function renderCompactContextGauge(context) {
|
|
|
247
248
|
function renderTurn(turn, theme) {
|
|
248
249
|
const renderer = ROLE_RENDERING[turn.role];
|
|
249
250
|
const role = renderer.color(theme)(`${turn.role} ${renderer.glyph}`);
|
|
250
|
-
const
|
|
251
|
+
const shouldCollapse = turn.role === "tool" || turn.role === "system";
|
|
252
|
+
let sourceText = turn.text;
|
|
253
|
+
let collapsedLines = 0;
|
|
254
|
+
if (shouldCollapse) {
|
|
255
|
+
const cut = lineEndIndex(sourceText, COLLAPSED_TURN_LINES);
|
|
256
|
+
if (cut !== -1) {
|
|
257
|
+
collapsedLines = countNewlines(sourceText, cut);
|
|
258
|
+
sourceText = sourceText.slice(0, cut);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const sanitizedText = sanitizeTraceText(sourceText);
|
|
251
262
|
const text = turn.role === "assistant"
|
|
252
263
|
? stripAnsi(renderMarkdown(sanitizedText)).trimEnd()
|
|
253
264
|
: sanitizedText.trimEnd();
|
|
254
265
|
const rawLines = text.length === 0 ? [""] : text.split("\n");
|
|
255
|
-
const shouldCollapse = turn.role === "tool" || turn.role === "system";
|
|
256
266
|
const visibleLines = shouldCollapse ? rawLines.slice(0, COLLAPSED_TURN_LINES) : rawLines;
|
|
257
|
-
const remaining = rawLines.length - visibleLines.length;
|
|
267
|
+
const remaining = collapsedLines + rawLines.length - visibleLines.length;
|
|
258
268
|
const prefix = ` ${role} `;
|
|
259
269
|
const continuationPrefix = " ".repeat(plainLength(` ${turn.role} ${renderer.glyph} `));
|
|
260
270
|
const lines = [];
|
|
@@ -348,7 +358,41 @@ function padVisible(value, width) {
|
|
|
348
358
|
function sanitizeInline(value) {
|
|
349
359
|
return sanitizeTraceText(value).split("\n").join(" ");
|
|
350
360
|
}
|
|
361
|
+
function lineEndIndex(value, lineCount) {
|
|
362
|
+
let index = -1;
|
|
363
|
+
for (let line = 0; line < lineCount; line += 1) {
|
|
364
|
+
index = value.indexOf("\n", index + 1);
|
|
365
|
+
if (index === -1) {
|
|
366
|
+
return -1;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return index;
|
|
370
|
+
}
|
|
371
|
+
function countNewlines(value, from) {
|
|
372
|
+
let count = 0;
|
|
373
|
+
for (let index = from; index < value.length; index += 1) {
|
|
374
|
+
if (value.charCodeAt(index) === 10) {
|
|
375
|
+
count += 1;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return count;
|
|
379
|
+
}
|
|
380
|
+
function needsSanitize(value) {
|
|
381
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
382
|
+
const code = value.charCodeAt(index);
|
|
383
|
+
if ((code < 32 && code !== 10) || code === 127) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
351
389
|
function sanitizeTraceText(value) {
|
|
390
|
+
if (!needsSanitize(value)) {
|
|
391
|
+
return value;
|
|
392
|
+
}
|
|
393
|
+
return sanitizeTraceTextSlow(value);
|
|
394
|
+
}
|
|
395
|
+
function sanitizeTraceTextSlow(value) {
|
|
352
396
|
let result = "";
|
|
353
397
|
let index = 0;
|
|
354
398
|
while (index < value.length) {
|
|
@@ -12,7 +12,7 @@ export async function runTraceViewer(options) {
|
|
|
12
12
|
writeLine(output, JSON.stringify({ ...view, subagents }, null, 2));
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
writeLine(output, renderTraceDetail(view, subagents));
|
|
15
|
+
writeLine(output, await renderTraceDetail(view, subagents));
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
const discover = () => listTraces({
|
|
@@ -115,12 +115,17 @@ function buildTraceExplorerConfig(options) {
|
|
|
115
115
|
ctx.openModal({ title: ctx.row.title, content: cached });
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
|
-
const
|
|
118
|
+
const rowId = ctx.row.id;
|
|
119
|
+
const view = await loadTrace(reference, {
|
|
120
|
+
fs: options.fs,
|
|
121
|
+
deferExactTokens: true,
|
|
122
|
+
onExactBreakdown: () => state.detailByRowId.delete(rowId)
|
|
123
|
+
});
|
|
119
124
|
const subagents = await loadSubagentSummariesIfPresent(view, options.fs);
|
|
120
125
|
const loaded = { view, subagents };
|
|
121
|
-
const content = renderTraceDetail(loaded.view, loaded.subagents);
|
|
122
|
-
state.detailByRowId.set(
|
|
123
|
-
state.childrenByRowId.set(
|
|
126
|
+
const content = await renderTraceDetail(loaded.view, loaded.subagents);
|
|
127
|
+
state.detailByRowId.set(rowId, content);
|
|
128
|
+
state.childrenByRowId.set(rowId, loaded.view.children ?? []);
|
|
124
129
|
ctx.openModal({ title: ctx.row.title, content });
|
|
125
130
|
}
|
|
126
131
|
},
|
|
@@ -177,8 +182,22 @@ function buildTraceExplorerConfig(options) {
|
|
|
177
182
|
detail: {
|
|
178
183
|
items: async (row, ctx) => {
|
|
179
184
|
const reference = getReference(state.referenceByRowId, row.id);
|
|
185
|
+
const cached = state.detailByRowId.get(row.id);
|
|
186
|
+
if (cached !== undefined) {
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
id: row.id,
|
|
190
|
+
render: () => cached
|
|
191
|
+
}
|
|
192
|
+
];
|
|
193
|
+
}
|
|
180
194
|
const loaded = await loadUnlessAborted(ctx.signal, async () => {
|
|
181
|
-
const view = await loadTrace(reference, {
|
|
195
|
+
const view = await loadTrace(reference, {
|
|
196
|
+
fs: options.fs,
|
|
197
|
+
signal: ctx.signal,
|
|
198
|
+
deferExactTokens: true,
|
|
199
|
+
onExactBreakdown: () => state.detailByRowId.delete(row.id)
|
|
200
|
+
});
|
|
182
201
|
const subagents = await loadSubagentSummariesIfPresent(view, options.fs);
|
|
183
202
|
return { view, subagents };
|
|
184
203
|
});
|
|
@@ -186,7 +205,12 @@ function buildTraceExplorerConfig(options) {
|
|
|
186
205
|
return [];
|
|
187
206
|
}
|
|
188
207
|
state.childrenByRowId.set(row.id, loaded.view.children ?? []);
|
|
189
|
-
const content = renderTraceDetail(loaded.view, loaded.subagents
|
|
208
|
+
const content = await renderTraceDetail(loaded.view, loaded.subagents, {
|
|
209
|
+
signal: ctx.signal
|
|
210
|
+
});
|
|
211
|
+
if (ctx.signal.aborted) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
190
214
|
state.detailByRowId.set(row.id, content);
|
|
191
215
|
return [
|
|
192
216
|
{
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentTraceFileSystem } from "../../agent-traces/dist/index.js";
|
|
2
|
+
import type { ContextBreakdown } from "./types.js";
|
|
3
|
+
export interface TraceFileIdentity {
|
|
4
|
+
mtimeMs: number;
|
|
5
|
+
size: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function defaultTraceTokenCacheDir(): string;
|
|
8
|
+
export declare function traceFileIdentity(fs: AgentTraceFileSystem, filePath: string): Promise<TraceFileIdentity | undefined>;
|
|
9
|
+
export declare function readCachedBreakdown(fs: AgentTraceFileSystem, cacheDir: string, filePath: string, identity: TraceFileIdentity): Promise<ContextBreakdown | undefined>;
|
|
10
|
+
export declare function writeCachedBreakdown(fs: AgentTraceFileSystem, cacheDir: string, filePath: string, identity: TraceFileIdentity, breakdown: ContextBreakdown): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveCacheDir } from "@poe-code/cached-resource";
|
|
4
|
+
const CACHE_VERSION = 2;
|
|
5
|
+
export function defaultTraceTokenCacheDir() {
|
|
6
|
+
return path.join(resolveCacheDir("poe-code"), "trace-tokens");
|
|
7
|
+
}
|
|
8
|
+
export async function traceFileIdentity(fs, filePath) {
|
|
9
|
+
try {
|
|
10
|
+
const stats = await fs.stat(filePath);
|
|
11
|
+
const mtimeMs = stats.mtime instanceof Date ? stats.mtime.getTime() : undefined;
|
|
12
|
+
if (mtimeMs === undefined || Number.isNaN(mtimeMs) || typeof stats.size !== "number") {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return { mtimeMs, size: stats.size };
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function readCachedBreakdown(fs, cacheDir, filePath, identity) {
|
|
22
|
+
let entry;
|
|
23
|
+
try {
|
|
24
|
+
entry = JSON.parse(await fs.readFile(cacheFilePath(cacheDir, filePath), "utf8"));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (entry.version !== CACHE_VERSION ||
|
|
30
|
+
entry.mtimeMs !== identity.mtimeMs ||
|
|
31
|
+
entry.size !== identity.size ||
|
|
32
|
+
typeof entry.breakdown?.measuredTokens !== "number" ||
|
|
33
|
+
entry.breakdown.source !== "exact") {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return entry.breakdown;
|
|
37
|
+
}
|
|
38
|
+
export async function writeCachedBreakdown(fs, cacheDir, filePath, identity, breakdown) {
|
|
39
|
+
const entry = {
|
|
40
|
+
version: CACHE_VERSION,
|
|
41
|
+
mtimeMs: identity.mtimeMs,
|
|
42
|
+
size: identity.size,
|
|
43
|
+
breakdown
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
47
|
+
await fs.writeFile(cacheFilePath(cacheDir, filePath), JSON.stringify(entry));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Token caching is best-effort; recomputing on the next view is always safe.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function cacheFilePath(cacheDir, filePath) {
|
|
54
|
+
const hash = createHash("sha256").update(filePath).digest("hex").slice(0, 32);
|
|
55
|
+
return path.join(cacheDir, `${hash}.json`);
|
|
56
|
+
}
|
|
@@ -11,6 +11,10 @@ export interface ListTracesOptions {
|
|
|
11
11
|
}
|
|
12
12
|
export interface LoadTraceOptions {
|
|
13
13
|
fs: AgentTraceFileSystem;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
cacheDir?: string;
|
|
16
|
+
deferExactTokens?: boolean;
|
|
17
|
+
onExactBreakdown?: (breakdown: ContextBreakdown) => void;
|
|
14
18
|
}
|
|
15
19
|
export interface ContextUsage {
|
|
16
20
|
tokens: number;
|
|
@@ -33,6 +37,7 @@ export interface ContextBreakdownCategory {
|
|
|
33
37
|
export interface ContextBreakdown {
|
|
34
38
|
measuredTokens: number;
|
|
35
39
|
categories: ContextBreakdownCategory[];
|
|
40
|
+
source: "exact" | "estimated";
|
|
36
41
|
}
|
|
37
42
|
export type TraceView = NormalizedTrace & {
|
|
38
43
|
context: ContextUsage;
|
|
@@ -7,11 +7,10 @@ import { loadOAuthVerifier } from "./load-oauth-verifier.js";
|
|
|
7
7
|
function readPackageInfo() {
|
|
8
8
|
try {
|
|
9
9
|
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
10
|
-
if (typeof packageJson.name === "string" &&
|
|
11
|
-
typeof packageJson.version === "string") {
|
|
10
|
+
if (typeof packageJson.name === "string" && typeof packageJson.version === "string") {
|
|
12
11
|
return {
|
|
13
12
|
name: packageJson.name,
|
|
14
|
-
version: packageJson.version
|
|
13
|
+
version: packageJson.version
|
|
15
14
|
};
|
|
16
15
|
}
|
|
17
16
|
}
|
|
@@ -20,7 +19,7 @@ function readPackageInfo() {
|
|
|
20
19
|
}
|
|
21
20
|
return {
|
|
22
21
|
name: "tiny-http-mcp-server",
|
|
23
|
-
version: "0.0.0"
|
|
22
|
+
version: "0.0.0"
|
|
24
23
|
};
|
|
25
24
|
}
|
|
26
25
|
const packageInfo = readPackageInfo();
|
|
@@ -45,6 +44,8 @@ const HELP_TEXT = [
|
|
|
45
44
|
" Maximum concurrent GET SSE streams per session",
|
|
46
45
|
" --max-sse-event-history <count>",
|
|
47
46
|
" Number of SSE events retained for Last-Event-ID replay",
|
|
47
|
+
" --sse-keep-alive-ms <ms>",
|
|
48
|
+
" GET SSE keepalive interval (default: 30000; 0 disables)",
|
|
48
49
|
" --max-concurrent-tool-calls <count>",
|
|
49
50
|
" Maximum concurrent tool calls across sessions",
|
|
50
51
|
" --trusted-proxy Trust X-Forwarded-Proto and X-Forwarded-Host",
|
|
@@ -67,7 +68,7 @@ const HELP_TEXT = [
|
|
|
67
68
|
" Module that exports the TokenVerifier implementation",
|
|
68
69
|
" --oauth-verifier-export <name>",
|
|
69
70
|
" Export name to load from the verifier module (default: default)",
|
|
70
|
-
" -h, --help Show this help message"
|
|
71
|
+
" -h, --help Show this help message"
|
|
71
72
|
].join("\n");
|
|
72
73
|
function parsePort(value) {
|
|
73
74
|
if (value === undefined) {
|
|
@@ -126,7 +127,7 @@ function hasConfiguredOAuthFlag(values) {
|
|
|
126
127
|
values["oauth-required-scope"],
|
|
127
128
|
values["oauth-bearer-method"],
|
|
128
129
|
values["oauth-verifier-module"],
|
|
129
|
-
values["oauth-verifier-export"]
|
|
130
|
+
values["oauth-verifier-export"]
|
|
130
131
|
].some((value) => value !== undefined);
|
|
131
132
|
}
|
|
132
133
|
function parseRepeatableStrings(value, flagName) {
|
|
@@ -174,9 +175,7 @@ function parseCliOAuthOptions(values) {
|
|
|
174
175
|
...(supportedScopes === undefined ? {} : { scopesSupported: supportedScopes }),
|
|
175
176
|
...(bearerMethods === undefined ? {} : { bearerMethodsSupported: bearerMethods }),
|
|
176
177
|
verifierModule,
|
|
177
|
-
verifierExport: typeof verifierExport === "string" && verifierExport.length > 0
|
|
178
|
-
? verifierExport
|
|
179
|
-
: "default",
|
|
178
|
+
verifierExport: typeof verifierExport === "string" && verifierExport.length > 0 ? verifierExport : "default"
|
|
180
179
|
};
|
|
181
180
|
}
|
|
182
181
|
function parseCliOptions(args) {
|
|
@@ -198,6 +197,7 @@ function parseCliOptions(args) {
|
|
|
198
197
|
"session-ttl-ms": { type: "string" },
|
|
199
198
|
"max-streams-per-session": { type: "string" },
|
|
200
199
|
"max-sse-event-history": { type: "string" },
|
|
200
|
+
"sse-keep-alive-ms": { type: "string" },
|
|
201
201
|
"max-concurrent-tool-calls": { type: "string" },
|
|
202
202
|
"trusted-proxy": { type: "boolean" },
|
|
203
203
|
"request-timeout-ms": { type: "string" },
|
|
@@ -210,8 +210,8 @@ function parseCliOptions(args) {
|
|
|
210
210
|
"oauth-bearer-method": { type: "string", multiple: true },
|
|
211
211
|
"oauth-verifier-module": { type: "string" },
|
|
212
212
|
"oauth-verifier-export": { type: "string" },
|
|
213
|
-
help: { type: "boolean", short: "h" }
|
|
214
|
-
}
|
|
213
|
+
help: { type: "boolean", short: "h" }
|
|
214
|
+
}
|
|
215
215
|
});
|
|
216
216
|
const maxRequestBytes = parseOptionalInteger(values["max-request-bytes"], "--max-request-bytes", 1);
|
|
217
217
|
const maxBatchSize = parseOptionalInteger(values["max-batch-size"], "--max-batch-size", 1);
|
|
@@ -219,6 +219,7 @@ function parseCliOptions(args) {
|
|
|
219
219
|
const sessionTtlMs = parseOptionalInteger(values["session-ttl-ms"], "--session-ttl-ms", 1);
|
|
220
220
|
const maxStreamsPerSession = parseOptionalInteger(values["max-streams-per-session"], "--max-streams-per-session", 1);
|
|
221
221
|
const maxSseEventHistory = parseOptionalInteger(values["max-sse-event-history"], "--max-sse-event-history", 0);
|
|
222
|
+
const sseKeepAliveMs = parseOptionalInteger(values["sse-keep-alive-ms"], "--sse-keep-alive-ms", 0);
|
|
222
223
|
const maxConcurrentToolCalls = parseOptionalInteger(values["max-concurrent-tool-calls"], "--max-concurrent-tool-calls", 1);
|
|
223
224
|
const requestTimeoutMs = parseOptionalInteger(values["request-timeout-ms"], "--request-timeout-ms", 0);
|
|
224
225
|
const headersTimeoutMs = parseOptionalInteger(values["headers-timeout-ms"], "--headers-timeout-ms", 0);
|
|
@@ -235,7 +236,7 @@ function parseCliOptions(args) {
|
|
|
235
236
|
: {}),
|
|
236
237
|
...(Array.isArray(values["allowed-origin"]) && values["allowed-origin"].length > 0
|
|
237
238
|
? {
|
|
238
|
-
allowedOrigins: values["allowed-origin"].map((value) => parseOrigin(value, "--allowed-origin"))
|
|
239
|
+
allowedOrigins: values["allowed-origin"].map((value) => parseOrigin(value, "--allowed-origin"))
|
|
239
240
|
}
|
|
240
241
|
: {}),
|
|
241
242
|
...(maxRequestBytes === undefined ? {} : { maxRequestBytes }),
|
|
@@ -244,12 +245,13 @@ function parseCliOptions(args) {
|
|
|
244
245
|
...(sessionTtlMs === undefined ? {} : { sessionTtlMs }),
|
|
245
246
|
...(maxStreamsPerSession === undefined ? {} : { maxStreamsPerSession }),
|
|
246
247
|
...(maxSseEventHistory === undefined ? {} : { maxSseEventHistory }),
|
|
248
|
+
...(sseKeepAliveMs === undefined ? {} : { sseKeepAliveMs }),
|
|
247
249
|
...(maxConcurrentToolCalls === undefined ? {} : { maxConcurrentToolCalls }),
|
|
248
250
|
trustedProxy: values["trusted-proxy"] ?? false,
|
|
249
251
|
...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
|
|
250
252
|
...(headersTimeoutMs === undefined ? {} : { headersTimeoutMs }),
|
|
251
253
|
...(keepAliveTimeoutMs === undefined ? {} : { keepAliveTimeoutMs }),
|
|
252
|
-
oauth: parseCliOAuthOptions(values)
|
|
254
|
+
oauth: parseCliOAuthOptions(values)
|
|
253
255
|
};
|
|
254
256
|
}
|
|
255
257
|
function waitForShutdown(shutdown) {
|
|
@@ -306,8 +308,8 @@ export async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
|
306
308
|
: {}),
|
|
307
309
|
verifier: await loadVerifier({
|
|
308
310
|
modulePath: options.oauth.verifierModule,
|
|
309
|
-
exportName: options.oauth.verifierExport
|
|
310
|
-
})
|
|
311
|
+
exportName: options.oauth.verifierExport
|
|
312
|
+
})
|
|
311
313
|
};
|
|
312
314
|
const server = createServer({
|
|
313
315
|
name: packageInfo.name,
|
|
@@ -316,7 +318,9 @@ export async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
|
316
318
|
...(options.jsonResponse ? { enableJsonResponse: true } : {}),
|
|
317
319
|
...(options.allowedHosts === undefined ? {} : { allowedHosts: options.allowedHosts }),
|
|
318
320
|
...(options.allowedOrigins === undefined ? {} : { allowedOrigins: options.allowedOrigins }),
|
|
319
|
-
...(options.maxRequestBytes === undefined
|
|
321
|
+
...(options.maxRequestBytes === undefined
|
|
322
|
+
? {}
|
|
323
|
+
: { maxRequestBytes: options.maxRequestBytes }),
|
|
320
324
|
...(options.maxBatchSize === undefined ? {} : { maxBatchSize: options.maxBatchSize }),
|
|
321
325
|
...(options.maxSessions === undefined ? {} : { maxSessions: options.maxSessions }),
|
|
322
326
|
...(options.sessionTtlMs === undefined ? {} : { sessionTtlMs: options.sessionTtlMs }),
|
|
@@ -326,11 +330,12 @@ export async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
|
326
330
|
...(options.maxSseEventHistory === undefined
|
|
327
331
|
? {}
|
|
328
332
|
: { maxSseEventHistory: options.maxSseEventHistory }),
|
|
333
|
+
...(options.sseKeepAliveMs === undefined ? {} : { sseKeepAliveMs: options.sseKeepAliveMs }),
|
|
329
334
|
...(options.maxConcurrentToolCalls === undefined
|
|
330
335
|
? {}
|
|
331
336
|
: { maxConcurrentToolCalls: options.maxConcurrentToolCalls }),
|
|
332
337
|
...(options.trustedProxy ? { trustedProxy: true } : {}),
|
|
333
|
-
...(oauth === undefined ? {} : { oauth })
|
|
338
|
+
...(oauth === undefined ? {} : { oauth })
|
|
334
339
|
});
|
|
335
340
|
handle = await server.listenHttp({
|
|
336
341
|
port: options.port,
|
|
@@ -344,7 +349,7 @@ export async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
|
344
349
|
: { headersTimeoutMs: options.headersTimeoutMs }),
|
|
345
350
|
...(options.keepAliveTimeoutMs === undefined
|
|
346
351
|
? {}
|
|
347
|
-
: { keepAliveTimeoutMs: options.keepAliveTimeoutMs })
|
|
352
|
+
: { keepAliveTimeoutMs: options.keepAliveTimeoutMs })
|
|
348
353
|
});
|
|
349
354
|
const shutdown = async () => {
|
|
350
355
|
await handle?.close();
|