@timefly/opencode-plugin 0.2.3 → 0.2.8
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 +5 -20
- package/dist/cli.js +0 -0
- package/dist/event-handlers.d.ts +3 -3
- package/dist/event-handlers.d.ts.map +1 -1
- package/dist/event-handlers.js +43 -18
- package/dist/event-tracker.d.ts +41 -1
- package/dist/event-tracker.d.ts.map +1 -1
- package/dist/event-tracker.js +118 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -9
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +29 -12
- package/dist/map-opencode-event.d.ts +8 -12
- package/dist/map-opencode-event.d.ts.map +1 -1
- package/dist/map-opencode-event.js +124 -54
- package/dist/opencode-readers.d.ts.map +1 -1
- package/dist/opencode-readers.js +25 -16
- package/dist/publish-events.js +1 -1
- package/dist/token-usage.d.ts.map +1 -1
- package/dist/token-usage.js +8 -7
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ Example result:
|
|
|
76
76
|
```json
|
|
77
77
|
{
|
|
78
78
|
"$schema": "https://opencode.ai/config.json",
|
|
79
|
-
"plugin": ["@timefly/opencode-plugin"]
|
|
79
|
+
"plugin": ["@timefly/opencode-plugin@latest"]
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
82
|
|
|
@@ -97,18 +97,11 @@ OpenCode plugins run inside the OpenCode process. There is no built-in TimeFly U
|
|
|
97
97
|
| Step | What happens |
|
|
98
98
|
|------|----------------|
|
|
99
99
|
| `login` | Opens browser → Google OAuth → saves tokens to `~/.config/opencode/timefly-auth.json` |
|
|
100
|
-
| Plugin startup | Reads auth file
|
|
100
|
+
| Plugin startup | Reads auth file from `login` |
|
|
101
101
|
| Each sync | Sends `Authorization: Bearer <accessToken>` to `POST /ai/sync` |
|
|
102
102
|
| Token expiry | SDK auto-refreshes using `refreshToken` (~30 days) and updates the auth file |
|
|
103
103
|
| No auth | Events queue locally in `.timefly-ai-queue.json` (project cwd) |
|
|
104
104
|
|
|
105
|
-
Manual env override (not recommended — access tokens expire in ~15 minutes):
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
export TIMEFLY_ACCESS_TOKEN="..."
|
|
109
|
-
export TIMEFLY_API_BASE_URL="https://api.timefly.dev"
|
|
110
|
-
```
|
|
111
|
-
|
|
112
105
|
## How sync works
|
|
113
106
|
|
|
114
107
|
Unlike the VS Code extension (which syncs on a timer every ~2 minutes), the OpenCode plugin syncs **on every event** — there is no background interval.
|
|
@@ -213,20 +206,11 @@ Add to `opencode.json` or `~/.config/opencode/opencode.json`:
|
|
|
213
206
|
```json
|
|
214
207
|
{
|
|
215
208
|
"$schema": "https://opencode.ai/config.json",
|
|
216
|
-
"plugin": ["@timefly/opencode-plugin"]
|
|
209
|
+
"plugin": ["@timefly/opencode-plugin@latest"]
|
|
217
210
|
}
|
|
218
211
|
```
|
|
219
212
|
|
|
220
|
-
OpenCode installs npm plugins automatically at startup via Bun.
|
|
221
|
-
|
|
222
|
-
## Environment
|
|
223
|
-
|
|
224
|
-
| Variable | Required | Default |
|
|
225
|
-
|----------|----------|---------|
|
|
226
|
-
| Auth file from `login` | Recommended | `~/.config/opencode/timefly-auth.json` |
|
|
227
|
-
| `TIMEFLY_ACCESS_TOKEN` | Optional override | — |
|
|
228
|
-
| `TIMEFLY_API_BASE_URL` | No | `https://api.timefly.dev` |
|
|
229
|
-
| `TIMEFLY_OPENCODE_SESSION_ID` | No | OpenCode session ID |
|
|
213
|
+
OpenCode installs npm plugins automatically at startup via Bun. Re-run `setup-opencode` to refresh `@latest` and clear the plugin cache.
|
|
230
214
|
|
|
231
215
|
## Events captured
|
|
232
216
|
|
|
@@ -287,6 +271,7 @@ Based on OpenCode source (`packages/opencode`, `@opencode-ai/plugin` hooks, and
|
|
|
287
271
|
|
|
288
272
|
| Topic | Detail |
|
|
289
273
|
|-------|--------|
|
|
274
|
+
| **Plugin cache** | OpenCode caches plugins under `~/.cache/opencode/packages/`. If you upgraded but still see old errors, delete `@timefly` there and restart OpenCode. |
|
|
290
275
|
| **Custom providers** (e.g. `nan`) | OpenCode runtime passes flat `Provider.Info` (`provider.id`) via `LLMRequestPrep.prepare` — not `provider.info.id`. Plugin reads both runtime and typed `ProviderContext` shapes. |
|
|
291
276
|
| **Token counts** | Require provider to report usage. If `message.updated` lacks token fields, turn events are skipped (no crash). |
|
|
292
277
|
| **Provider-side tools** | Tools executed inside the provider (`providerExecuted`) may not hit `tool.execute.*` — no tool events for those. |
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/event-handlers.d.ts
CHANGED
|
@@ -2,12 +2,12 @@ import { createEventTracker } from './event-tracker.js';
|
|
|
2
2
|
import { type OpenCodeBusEvent } from './opencode-readers.js';
|
|
3
3
|
import type { createEventPublisher } from './publish-events.js';
|
|
4
4
|
type EventPublisher = ReturnType<typeof createEventPublisher>['publish'];
|
|
5
|
-
export declare const handleSessionCreated: (eventProperties: Record<string, unknown>, publish: EventPublisher) => Promise<void>;
|
|
5
|
+
export declare const handleSessionCreated: (eventProperties: Record<string, unknown>, tracker: ReturnType<typeof createEventTracker>, publish: EventPublisher) => Promise<void>;
|
|
6
6
|
export declare const handleSessionIdle: (eventProperties: Record<string, unknown>, tracker: ReturnType<typeof createEventTracker>, publish: EventPublisher) => Promise<void>;
|
|
7
7
|
export declare const handleMessageUpdated: (eventProperties: Record<string, unknown>, tracker: ReturnType<typeof createEventTracker>, publish: EventPublisher) => Promise<void>;
|
|
8
8
|
export declare const handleMessagePartUpdated: (eventProperties: Record<string, unknown>, tracker: ReturnType<typeof createEventTracker>, publish: EventPublisher) => Promise<void>;
|
|
9
|
-
export declare const handleSessionCompacted: (eventProperties: Record<string, unknown>, publish: EventPublisher) => Promise<void>;
|
|
10
|
-
export declare const handleSessionError: (eventProperties: Record<string, unknown>, publish: EventPublisher) => Promise<void>;
|
|
9
|
+
export declare const handleSessionCompacted: (eventProperties: Record<string, unknown>, tracker: ReturnType<typeof createEventTracker>, publish: EventPublisher) => Promise<void>;
|
|
10
|
+
export declare const handleSessionError: (eventProperties: Record<string, unknown>, tracker: ReturnType<typeof createEventTracker>, publish: EventPublisher) => Promise<void>;
|
|
11
11
|
export declare const handleCommandExecuted: (eventProperties: Record<string, unknown>, publish: EventPublisher) => Promise<void>;
|
|
12
12
|
export declare const handleBusEvent: (event: OpenCodeBusEvent, tracker: ReturnType<typeof createEventTracker>, publish: EventPublisher) => Promise<void>;
|
|
13
13
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-handlers.d.ts","sourceRoot":"","sources":["../src/event-handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAiBvD,OAAO,EASN,KAAK,gBAAgB,EACrB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAG/D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,SAAS,CAAC,CAAA;AAKxE,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"event-handlers.d.ts","sourceRoot":"","sources":["../src/event-handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAiBvD,OAAO,EASN,KAAK,gBAAgB,EACrB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAG/D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,SAAS,CAAC,CAAA;AAKxE,eAAO,MAAM,oBAAoB,GAChC,iBAAiB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,SAAS,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAC9C,SAAS,cAAc,KACrB,OAAO,CAAC,IAAI,CAed,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC7B,iBAAiB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,SAAS,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAC9C,SAAS,cAAc,KACrB,OAAO,CAAC,IAAI,CAad,CAAA;AAED,eAAO,MAAM,oBAAoB,GAChC,iBAAiB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,SAAS,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAC9C,SAAS,cAAc,KACrB,OAAO,CAAC,IAAI,CA0Dd,CAAA;AAED,eAAO,MAAM,wBAAwB,GACpC,iBAAiB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,SAAS,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAC9C,SAAS,cAAc,KACrB,OAAO,CAAC,IAAI,CAqCd,CAAA;AAED,eAAO,MAAM,sBAAsB,GAClC,iBAAiB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,SAAS,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAC9C,SAAS,cAAc,KACrB,OAAO,CAAC,IAAI,CAWd,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC9B,iBAAiB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,SAAS,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAC9C,SAAS,cAAc,KACrB,OAAO,CAAC,IAAI,CAqBd,CAAA;AAED,eAAO,MAAM,qBAAqB,GACjC,iBAAiB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,SAAS,cAAc,KACrB,OAAO,CAAC,IAAI,CAad,CAAA;AAED,eAAO,MAAM,cAAc,GAC1B,OAAO,gBAAgB,EACvB,SAAS,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAC9C,SAAS,cAAc,KACrB,OAAO,CAAC,IAAI,CAkCd,CAAA"}
|
package/dist/event-handlers.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { mapAssistantLlmResponseInput, mapAssistantTurnCompleteInput, mapCommandExecutedInput, mapCompactionInput, mapCompactionPartInput, mapErrorInput, mapRetryPartInput, mapSessionEndInput, mapSessionStartInput, mapStepFinishInput, mapUserMessageInput } from './map-opencode-event.js';
|
|
2
2
|
import { readAssistantMessage, readCompactionPart, readEventProperties, readRetryPart, readSessionIdFromProperties, readSessionInfo, readStepFinishPart, readUserMessage } from './opencode-readers.js';
|
|
3
|
-
import {
|
|
3
|
+
import { buildTokenMetrics } from './token-usage.js';
|
|
4
4
|
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
5
|
-
export const handleSessionCreated = (eventProperties, publish) => {
|
|
5
|
+
export const handleSessionCreated = (eventProperties, tracker, publish) => {
|
|
6
6
|
const sessionInfo = readSessionInfo(eventProperties.info);
|
|
7
7
|
if (!sessionInfo) {
|
|
8
8
|
return Promise.resolve();
|
|
9
9
|
}
|
|
10
|
+
tracker.recordSessionStart(sessionInfo.id);
|
|
10
11
|
return publish([
|
|
11
12
|
{
|
|
12
13
|
eventType: 'session_start',
|
|
@@ -22,7 +23,7 @@ export const handleSessionIdle = (eventProperties, tracker, publish) => {
|
|
|
22
23
|
return publish([
|
|
23
24
|
{
|
|
24
25
|
eventType: 'session_end',
|
|
25
|
-
...mapSessionEndInput(sessionId, tracker.getSessionStats(sessionId))
|
|
26
|
+
...mapSessionEndInput(sessionId, tracker.getSessionStats(sessionId), tracker.getSessionTiming(sessionId))
|
|
26
27
|
}
|
|
27
28
|
]);
|
|
28
29
|
};
|
|
@@ -32,17 +33,32 @@ export const handleMessageUpdated = (eventProperties, tracker, publish) => {
|
|
|
32
33
|
if (assistantMessage.time.completed === undefined || tracker.hasProcessedMessage(assistantMessage.id)) {
|
|
33
34
|
return Promise.resolve();
|
|
34
35
|
}
|
|
36
|
+
tracker.recordSessionStart(assistantMessage.sessionID, assistantMessage.time.created);
|
|
35
37
|
tracker.markMessageProcessed(assistantMessage.id);
|
|
36
|
-
const
|
|
38
|
+
const durationMs = Math.max(0, assistantMessage.time.completed - assistantMessage.time.created);
|
|
39
|
+
const tokenMetrics = buildTokenMetrics(assistantMessage.tokens, durationMs);
|
|
40
|
+
const sessionStats = tracker.getSessionStats(assistantMessage.sessionID);
|
|
41
|
+
const compactionDelta = tracker.recordTurnTokens(assistantMessage.sessionID, {
|
|
42
|
+
contextTokens: tokenMetrics.inputTokens,
|
|
43
|
+
billedInputTokens: tokenMetrics.inputTokens,
|
|
44
|
+
outputTokens: tokenMetrics.outputTokens,
|
|
45
|
+
totalTokens: tokenMetrics.totalTokens,
|
|
46
|
+
cacheReadTokens: tokenMetrics.cacheReadTokens,
|
|
47
|
+
cacheWriteTokens: tokenMetrics.cacheWriteTokens,
|
|
48
|
+
reasoningTokens: tokenMetrics.reasoningTokens
|
|
49
|
+
});
|
|
50
|
+
tracker.recordSessionStats(assistantMessage.sessionID, {
|
|
51
|
+
aiGenerationMs: durationMs,
|
|
52
|
+
primaryProviderId: assistantMessage.providerID
|
|
53
|
+
});
|
|
54
|
+
const turnCompleteInput = mapAssistantTurnCompleteInput(assistantMessage, {
|
|
55
|
+
providerSource: sessionStats.primaryProviderSource || undefined,
|
|
56
|
+
compactionDelta
|
|
57
|
+
});
|
|
58
|
+
const eventsToPublish = [turnCompleteInput, mapAssistantLlmResponseInput(assistantMessage)].filter((eventInput) => eventInput !== undefined);
|
|
37
59
|
if (!eventsToPublish.length) {
|
|
38
60
|
return Promise.resolve();
|
|
39
61
|
}
|
|
40
|
-
tracker.recordSessionStats(assistantMessage.sessionID, {
|
|
41
|
-
inputTokens: assistantMessage.tokens.input,
|
|
42
|
-
outputTokens: assistantMessage.tokens.output,
|
|
43
|
-
totalTokens: sumTokenUsage(assistantMessage.tokens),
|
|
44
|
-
turnCount: 1
|
|
45
|
-
});
|
|
46
62
|
return publish(eventsToPublish);
|
|
47
63
|
}
|
|
48
64
|
const userMessage = readUserMessage(eventProperties.info);
|
|
@@ -50,6 +66,7 @@ export const handleMessageUpdated = (eventProperties, tracker, publish) => {
|
|
|
50
66
|
if (tracker.hasProcessedUserMessage(userMessage.id)) {
|
|
51
67
|
return Promise.resolve();
|
|
52
68
|
}
|
|
69
|
+
tracker.recordSessionStart(userMessage.sessionID);
|
|
53
70
|
tracker.markUserMessageProcessed(userMessage.id);
|
|
54
71
|
return publish([mapUserMessageInput(userMessage)]);
|
|
55
72
|
}
|
|
@@ -71,6 +88,7 @@ export const handleMessagePartUpdated = (eventProperties, tracker, publish) => {
|
|
|
71
88
|
return Promise.resolve();
|
|
72
89
|
}
|
|
73
90
|
tracker.markPartProcessed(retryPart.id);
|
|
91
|
+
tracker.recordSessionStats(retryPart.sessionID, { retryCount: 1 });
|
|
74
92
|
return publish([mapRetryPartInput(retryPart)]);
|
|
75
93
|
}
|
|
76
94
|
if (compactionPart) {
|
|
@@ -78,18 +96,24 @@ export const handleMessagePartUpdated = (eventProperties, tracker, publish) => {
|
|
|
78
96
|
return Promise.resolve();
|
|
79
97
|
}
|
|
80
98
|
tracker.markPartProcessed(compactionPart.id);
|
|
81
|
-
|
|
99
|
+
const contextBeforeTokens = tracker.getSessionStats(compactionPart.sessionID).latestContextTokens;
|
|
100
|
+
tracker.recordCompactionPending(compactionPart.sessionID, contextBeforeTokens);
|
|
101
|
+
tracker.recordSessionStats(compactionPart.sessionID, { compactionCount: 1 });
|
|
102
|
+
return publish([mapCompactionPartInput(compactionPart, contextBeforeTokens)]);
|
|
82
103
|
}
|
|
83
104
|
return Promise.resolve();
|
|
84
105
|
};
|
|
85
|
-
export const handleSessionCompacted = (eventProperties, publish) => {
|
|
106
|
+
export const handleSessionCompacted = (eventProperties, tracker, publish) => {
|
|
86
107
|
const sessionId = readSessionIdFromProperties(eventProperties);
|
|
87
108
|
if (!sessionId) {
|
|
88
109
|
return Promise.resolve();
|
|
89
110
|
}
|
|
90
|
-
|
|
111
|
+
tracker.recordSessionStats(sessionId, { compactionCount: 1 });
|
|
112
|
+
const contextBeforeTokens = tracker.getSessionStats(sessionId).latestContextTokens;
|
|
113
|
+
tracker.recordCompactionPending(sessionId, contextBeforeTokens);
|
|
114
|
+
return publish([mapCompactionInput(sessionId, contextBeforeTokens)]);
|
|
91
115
|
};
|
|
92
|
-
export const handleSessionError = (eventProperties, publish) => {
|
|
116
|
+
export const handleSessionError = (eventProperties, tracker, publish) => {
|
|
93
117
|
const sessionId = readSessionIdFromProperties(eventProperties);
|
|
94
118
|
const errorRecord = isRecord(eventProperties.error) ? eventProperties.error : undefined;
|
|
95
119
|
const errorName = errorRecord && typeof errorRecord.name === 'string' ? errorRecord.name : 'unknown';
|
|
@@ -98,10 +122,11 @@ export const handleSessionError = (eventProperties, publish) => {
|
|
|
98
122
|
if (!sessionId) {
|
|
99
123
|
return Promise.resolve();
|
|
100
124
|
}
|
|
125
|
+
tracker.recordSessionStats(sessionId, { errorCount: 1 });
|
|
101
126
|
return publish([
|
|
102
127
|
mapErrorInput(sessionId, {
|
|
103
128
|
error_name: errorName,
|
|
104
|
-
|
|
129
|
+
error_message_length: errorMessage.length,
|
|
105
130
|
error_scope: 'session'
|
|
106
131
|
})
|
|
107
132
|
]);
|
|
@@ -126,7 +151,7 @@ export const handleBusEvent = (event, tracker, publish) => {
|
|
|
126
151
|
}
|
|
127
152
|
switch (event.type) {
|
|
128
153
|
case 'session.created':
|
|
129
|
-
return handleSessionCreated(eventProperties, publish);
|
|
154
|
+
return handleSessionCreated(eventProperties, tracker, publish);
|
|
130
155
|
case 'session.status': {
|
|
131
156
|
const statusRecord = isRecord(eventProperties.status) ? eventProperties.status : undefined;
|
|
132
157
|
if (statusRecord?.type !== 'idle') {
|
|
@@ -141,9 +166,9 @@ export const handleBusEvent = (event, tracker, publish) => {
|
|
|
141
166
|
case 'message.part.updated':
|
|
142
167
|
return handleMessagePartUpdated(eventProperties, tracker, publish);
|
|
143
168
|
case 'session.compacted':
|
|
144
|
-
return handleSessionCompacted(eventProperties, publish);
|
|
169
|
+
return handleSessionCompacted(eventProperties, tracker, publish);
|
|
145
170
|
case 'session.error':
|
|
146
|
-
return handleSessionError(eventProperties, publish);
|
|
171
|
+
return handleSessionError(eventProperties, tracker, publish);
|
|
147
172
|
case 'command.executed':
|
|
148
173
|
return handleCommandExecuted(eventProperties, publish);
|
|
149
174
|
default:
|
package/dist/event-tracker.d.ts
CHANGED
|
@@ -1,11 +1,45 @@
|
|
|
1
|
+
export type CompactionDelta = {
|
|
2
|
+
contextBeforeTokens: number;
|
|
3
|
+
contextAfterTokens: number;
|
|
4
|
+
tokensSaved: number;
|
|
5
|
+
};
|
|
1
6
|
export type SessionStats = {
|
|
2
|
-
|
|
7
|
+
billedInputTokens: number;
|
|
3
8
|
outputTokens: number;
|
|
4
9
|
totalTokens: number;
|
|
5
10
|
turnCount: number;
|
|
6
11
|
toolCallCount: number;
|
|
7
12
|
requestCount: number;
|
|
8
13
|
stepCount: number;
|
|
14
|
+
compactionCount: number;
|
|
15
|
+
retryCount: number;
|
|
16
|
+
errorCount: number;
|
|
17
|
+
cacheReadTokens: number;
|
|
18
|
+
cacheWriteTokens: number;
|
|
19
|
+
reasoningTokens: number;
|
|
20
|
+
peakContextTokens: number;
|
|
21
|
+
latestContextTokens: number;
|
|
22
|
+
contextAtStartTokens: number;
|
|
23
|
+
compactionTokensSaved: number;
|
|
24
|
+
totalToolOutputChars: number;
|
|
25
|
+
primaryProviderId: string;
|
|
26
|
+
primaryProviderSource: string;
|
|
27
|
+
startedAtMs?: number;
|
|
28
|
+
aiGenerationMs: number;
|
|
29
|
+
};
|
|
30
|
+
export type SessionTiming = {
|
|
31
|
+
sessionWallMs: number;
|
|
32
|
+
aiGenerationMs: number;
|
|
33
|
+
userWaitMs: number;
|
|
34
|
+
};
|
|
35
|
+
export type TurnTokenSnapshot = {
|
|
36
|
+
contextTokens: number;
|
|
37
|
+
cacheReadTokens: number;
|
|
38
|
+
cacheWriteTokens: number;
|
|
39
|
+
reasoningTokens: number;
|
|
40
|
+
billedInputTokens: number;
|
|
41
|
+
outputTokens: number;
|
|
42
|
+
totalTokens: number;
|
|
9
43
|
};
|
|
10
44
|
export type EventTracker = {
|
|
11
45
|
hasProcessedMessage: (messageId: string) => boolean;
|
|
@@ -14,8 +48,14 @@ export type EventTracker = {
|
|
|
14
48
|
markUserMessageProcessed: (messageId: string) => void;
|
|
15
49
|
hasProcessedPart: (partId: string) => boolean;
|
|
16
50
|
markPartProcessed: (partId: string) => void;
|
|
51
|
+
recordSessionStart: (sessionId: string, startedAtMs?: number) => void;
|
|
17
52
|
recordSessionStats: (sessionId: string, delta: Partial<SessionStats>) => void;
|
|
53
|
+
recordProviderConnection: (sessionId: string, providerId: string, providerSource: string) => void;
|
|
54
|
+
recordCompactionPending: (sessionId: string, contextBeforeTokens: number) => void;
|
|
55
|
+
recordToolOutputChars: (sessionId: string, outputChars: number) => void;
|
|
56
|
+
recordTurnTokens: (sessionId: string, turnTokens: TurnTokenSnapshot) => CompactionDelta | undefined;
|
|
18
57
|
getSessionStats: (sessionId: string) => SessionStats;
|
|
58
|
+
getSessionTiming: (sessionId: string, endedAtMs?: number) => SessionTiming;
|
|
19
59
|
};
|
|
20
60
|
export declare const createEventTracker: () => EventTracker;
|
|
21
61
|
//# sourceMappingURL=event-tracker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-tracker.d.ts","sourceRoot":"","sources":["../src/event-tracker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"event-tracker.d.ts","sourceRoot":"","sources":["../src/event-tracker.ts"],"names":[],"mappings":"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"}
|
package/dist/event-tracker.js
CHANGED
|
@@ -1,17 +1,63 @@
|
|
|
1
1
|
const emptySessionStats = () => ({
|
|
2
|
-
|
|
2
|
+
billedInputTokens: 0,
|
|
3
3
|
outputTokens: 0,
|
|
4
4
|
totalTokens: 0,
|
|
5
5
|
turnCount: 0,
|
|
6
6
|
toolCallCount: 0,
|
|
7
7
|
requestCount: 0,
|
|
8
|
-
stepCount: 0
|
|
8
|
+
stepCount: 0,
|
|
9
|
+
compactionCount: 0,
|
|
10
|
+
retryCount: 0,
|
|
11
|
+
errorCount: 0,
|
|
12
|
+
cacheReadTokens: 0,
|
|
13
|
+
cacheWriteTokens: 0,
|
|
14
|
+
reasoningTokens: 0,
|
|
15
|
+
peakContextTokens: 0,
|
|
16
|
+
latestContextTokens: 0,
|
|
17
|
+
contextAtStartTokens: 0,
|
|
18
|
+
compactionTokensSaved: 0,
|
|
19
|
+
totalToolOutputChars: 0,
|
|
20
|
+
primaryProviderId: '',
|
|
21
|
+
primaryProviderSource: '',
|
|
22
|
+
aiGenerationMs: 0
|
|
23
|
+
});
|
|
24
|
+
const INVALID_ID_VALUES = new Set(['', 'unknown', 'undefined', 'null']);
|
|
25
|
+
const cleanIdentity = (value) => {
|
|
26
|
+
const cleanedValue = value.trim();
|
|
27
|
+
return INVALID_ID_VALUES.has(cleanedValue.toLowerCase()) ? '' : cleanedValue;
|
|
28
|
+
};
|
|
29
|
+
const mergeSessionStats = (currentStats, delta) => ({
|
|
30
|
+
billedInputTokens: currentStats.billedInputTokens + (delta.billedInputTokens ?? 0),
|
|
31
|
+
outputTokens: currentStats.outputTokens + (delta.outputTokens ?? 0),
|
|
32
|
+
totalTokens: currentStats.totalTokens + (delta.totalTokens ?? 0),
|
|
33
|
+
turnCount: currentStats.turnCount + (delta.turnCount ?? 0),
|
|
34
|
+
toolCallCount: currentStats.toolCallCount + (delta.toolCallCount ?? 0),
|
|
35
|
+
requestCount: currentStats.requestCount + (delta.requestCount ?? 0),
|
|
36
|
+
stepCount: currentStats.stepCount + (delta.stepCount ?? 0),
|
|
37
|
+
compactionCount: currentStats.compactionCount + (delta.compactionCount ?? 0),
|
|
38
|
+
retryCount: currentStats.retryCount + (delta.retryCount ?? 0),
|
|
39
|
+
errorCount: currentStats.errorCount + (delta.errorCount ?? 0),
|
|
40
|
+
cacheReadTokens: currentStats.cacheReadTokens + (delta.cacheReadTokens ?? 0),
|
|
41
|
+
cacheWriteTokens: currentStats.cacheWriteTokens + (delta.cacheWriteTokens ?? 0),
|
|
42
|
+
reasoningTokens: currentStats.reasoningTokens + (delta.reasoningTokens ?? 0),
|
|
43
|
+
peakContextTokens: Math.max(currentStats.peakContextTokens, delta.peakContextTokens ?? 0),
|
|
44
|
+
latestContextTokens: delta.latestContextTokens ?? currentStats.latestContextTokens,
|
|
45
|
+
contextAtStartTokens: currentStats.contextAtStartTokens > 0
|
|
46
|
+
? currentStats.contextAtStartTokens
|
|
47
|
+
: (delta.contextAtStartTokens ?? currentStats.contextAtStartTokens),
|
|
48
|
+
compactionTokensSaved: currentStats.compactionTokensSaved + (delta.compactionTokensSaved ?? 0),
|
|
49
|
+
totalToolOutputChars: currentStats.totalToolOutputChars + (delta.totalToolOutputChars ?? 0),
|
|
50
|
+
primaryProviderId: delta.primaryProviderId !== undefined ? cleanIdentity(delta.primaryProviderId) : currentStats.primaryProviderId,
|
|
51
|
+
primaryProviderSource: delta.primaryProviderSource !== undefined ? cleanIdentity(delta.primaryProviderSource) : currentStats.primaryProviderSource,
|
|
52
|
+
startedAtMs: currentStats.startedAtMs,
|
|
53
|
+
aiGenerationMs: currentStats.aiGenerationMs + (delta.aiGenerationMs ?? 0)
|
|
9
54
|
});
|
|
10
55
|
export const createEventTracker = () => {
|
|
11
56
|
const processedMessageIds = new Set();
|
|
12
57
|
const processedUserMessageIds = new Set();
|
|
13
58
|
const processedPartIds = new Set();
|
|
14
59
|
const sessionStatsById = new Map();
|
|
60
|
+
const pendingCompactionContextBySession = new Map();
|
|
15
61
|
return {
|
|
16
62
|
hasProcessedMessage: (messageId) => processedMessageIds.has(messageId),
|
|
17
63
|
markMessageProcessed: (messageId) => {
|
|
@@ -25,18 +71,80 @@ export const createEventTracker = () => {
|
|
|
25
71
|
markPartProcessed: (partId) => {
|
|
26
72
|
processedPartIds.add(partId);
|
|
27
73
|
},
|
|
74
|
+
recordSessionStart: (sessionId, startedAtMs = Date.now()) => {
|
|
75
|
+
const currentStats = sessionStatsById.get(sessionId) ?? emptySessionStats();
|
|
76
|
+
if (currentStats.startedAtMs !== undefined) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
sessionStatsById.set(sessionId, {
|
|
80
|
+
...currentStats,
|
|
81
|
+
startedAtMs
|
|
82
|
+
});
|
|
83
|
+
},
|
|
28
84
|
recordSessionStats: (sessionId, delta) => {
|
|
85
|
+
const currentStats = sessionStatsById.get(sessionId) ?? emptySessionStats();
|
|
86
|
+
sessionStatsById.set(sessionId, mergeSessionStats(currentStats, delta));
|
|
87
|
+
},
|
|
88
|
+
recordProviderConnection: (sessionId, providerId, providerSource) => {
|
|
29
89
|
const currentStats = sessionStatsById.get(sessionId) ?? emptySessionStats();
|
|
30
90
|
sessionStatsById.set(sessionId, {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
turnCount: currentStats.turnCount + (delta.turnCount ?? 0),
|
|
35
|
-
toolCallCount: currentStats.toolCallCount + (delta.toolCallCount ?? 0),
|
|
36
|
-
requestCount: currentStats.requestCount + (delta.requestCount ?? 0),
|
|
37
|
-
stepCount: currentStats.stepCount + (delta.stepCount ?? 0)
|
|
91
|
+
...currentStats,
|
|
92
|
+
primaryProviderId: cleanIdentity(providerId),
|
|
93
|
+
primaryProviderSource: cleanIdentity(providerSource)
|
|
38
94
|
});
|
|
39
95
|
},
|
|
40
|
-
|
|
96
|
+
recordCompactionPending: (sessionId, contextBeforeTokens) => {
|
|
97
|
+
if (contextBeforeTokens > 0) {
|
|
98
|
+
pendingCompactionContextBySession.set(sessionId, contextBeforeTokens);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
recordToolOutputChars: (sessionId, outputChars) => {
|
|
102
|
+
const currentStats = sessionStatsById.get(sessionId) ?? emptySessionStats();
|
|
103
|
+
sessionStatsById.set(sessionId, {
|
|
104
|
+
...currentStats,
|
|
105
|
+
totalToolOutputChars: currentStats.totalToolOutputChars + Math.max(0, outputChars)
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
recordTurnTokens: (sessionId, turnTokens) => {
|
|
109
|
+
const currentStats = sessionStatsById.get(sessionId) ?? emptySessionStats();
|
|
110
|
+
const isFirstTurn = currentStats.turnCount === 0;
|
|
111
|
+
const contextBeforeCompaction = pendingCompactionContextBySession.get(sessionId);
|
|
112
|
+
const compactionDelta = contextBeforeCompaction !== undefined && contextBeforeCompaction > turnTokens.contextTokens
|
|
113
|
+
? {
|
|
114
|
+
contextBeforeTokens: contextBeforeCompaction,
|
|
115
|
+
contextAfterTokens: turnTokens.contextTokens,
|
|
116
|
+
tokensSaved: contextBeforeCompaction - turnTokens.contextTokens
|
|
117
|
+
}
|
|
118
|
+
: undefined;
|
|
119
|
+
if (compactionDelta) {
|
|
120
|
+
pendingCompactionContextBySession.delete(sessionId);
|
|
121
|
+
}
|
|
122
|
+
sessionStatsById.set(sessionId, mergeSessionStats(currentStats, {
|
|
123
|
+
billedInputTokens: turnTokens.billedInputTokens,
|
|
124
|
+
outputTokens: turnTokens.outputTokens,
|
|
125
|
+
totalTokens: turnTokens.totalTokens,
|
|
126
|
+
cacheReadTokens: turnTokens.cacheReadTokens,
|
|
127
|
+
cacheWriteTokens: turnTokens.cacheWriteTokens,
|
|
128
|
+
reasoningTokens: turnTokens.reasoningTokens,
|
|
129
|
+
turnCount: 1,
|
|
130
|
+
peakContextTokens: turnTokens.contextTokens,
|
|
131
|
+
latestContextTokens: turnTokens.contextTokens,
|
|
132
|
+
contextAtStartTokens: isFirstTurn ? turnTokens.contextTokens : currentStats.contextAtStartTokens,
|
|
133
|
+
...(compactionDelta ? { compactionTokensSaved: compactionDelta.tokensSaved } : {})
|
|
134
|
+
}));
|
|
135
|
+
return compactionDelta;
|
|
136
|
+
},
|
|
137
|
+
getSessionStats: (sessionId) => sessionStatsById.get(sessionId) ?? emptySessionStats(),
|
|
138
|
+
getSessionTiming: (sessionId, endedAtMs = Date.now()) => {
|
|
139
|
+
const currentStats = sessionStatsById.get(sessionId) ?? emptySessionStats();
|
|
140
|
+
const sessionWallMs = currentStats.startedAtMs !== undefined ? Math.max(0, endedAtMs - currentStats.startedAtMs) : 0;
|
|
141
|
+
const aiGenerationMs = currentStats.aiGenerationMs;
|
|
142
|
+
const userWaitMs = Math.max(0, sessionWallMs - aiGenerationMs);
|
|
143
|
+
return {
|
|
144
|
+
sessionWallMs,
|
|
145
|
+
aiGenerationMs,
|
|
146
|
+
userWaitMs
|
|
147
|
+
};
|
|
148
|
+
}
|
|
41
149
|
};
|
|
42
150
|
};
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAgBjD,eAAO,MAAM,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAgBjD,eAAO,MAAM,qBAAqB,EAAE,MAuEnC,CAAA;AAED,eAAe,qBAAqB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -25,8 +25,10 @@ export const timeflyOpenCodePlugin = ({ client }) => {
|
|
|
25
25
|
.then(() => Promise.resolve({
|
|
26
26
|
event: (input) => runHookSafely(() => handleBusEvent(input.event, tracker, publisher.publish)),
|
|
27
27
|
'chat.params': (input, output) => runHookSafely(() => {
|
|
28
|
+
tracker.recordSessionStart(input.sessionID);
|
|
28
29
|
tracker.recordSessionStats(input.sessionID, { requestCount: 1 });
|
|
29
30
|
const resolvedParams = resolveChatParams(input, output);
|
|
31
|
+
tracker.recordProviderConnection(resolvedParams.sessionID, resolvedParams.providerId, resolvedParams.providerSource);
|
|
30
32
|
return publisher.publish([
|
|
31
33
|
mapLlmRequestInput({
|
|
32
34
|
sessionID: resolvedParams.sessionID,
|
|
@@ -44,15 +46,18 @@ export const timeflyOpenCodePlugin = ({ client }) => {
|
|
|
44
46
|
tracker.recordSessionStats(input.sessionID, { toolCallCount: 1 });
|
|
45
47
|
return publisher.publish([mapToolCallInput(input)]);
|
|
46
48
|
}),
|
|
47
|
-
'tool.execute.after': (input, output) => runHookSafely(() =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
'tool.execute.after': (input, output) => runHookSafely(() => {
|
|
50
|
+
tracker.recordToolOutputChars(input.sessionID, output.output.length);
|
|
51
|
+
return publisher.publish([
|
|
52
|
+
mapToolResultInput({
|
|
53
|
+
sessionID: input.sessionID,
|
|
54
|
+
tool: input.tool,
|
|
55
|
+
callID: input.callID,
|
|
56
|
+
hasOutput: Boolean(output.output),
|
|
57
|
+
outputLength: output.output.length
|
|
58
|
+
})
|
|
59
|
+
]);
|
|
60
|
+
}),
|
|
56
61
|
'experimental.session.compacting': (input) => runHookSafely(() => publisher.publish([mapCompactionInput(input.sessionID)]))
|
|
57
62
|
}));
|
|
58
63
|
};
|
package/dist/install.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":";AAuJA,eAAO,MAAM,UAAU,GAAI,eAAe,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAGhE,CAAA"}
|
package/dist/install.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { resolveDefaultAuthFilePath } from '@timefly/ai-sdk';
|
|
6
|
-
const
|
|
6
|
+
const PLUGIN_PACKAGE_LATEST = '@timefly/opencode-plugin@latest';
|
|
7
|
+
const TIMEFLY_PLUGIN_PATTERN = /^@timefly\/opencode-plugin(?:@|$)/;
|
|
7
8
|
const PRICING_URL = 'https://timefly.dev/pricing';
|
|
8
9
|
const currentDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const packageRoot = path.resolve(currentDirectory, '..');
|
|
@@ -36,6 +37,22 @@ const resolveConfigDestination = (options) => {
|
|
|
36
37
|
}
|
|
37
38
|
return path.join(homeDirectory, '.config', 'opencode', 'opencode.json');
|
|
38
39
|
};
|
|
40
|
+
const resolveOpenCodePluginCachePath = () => {
|
|
41
|
+
const homeDirectory = process.env.HOME ?? process.env.USERPROFILE;
|
|
42
|
+
if (!homeDirectory) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return path.join(homeDirectory, '.cache', 'opencode', 'packages', '@timefly');
|
|
46
|
+
};
|
|
47
|
+
const clearOpenCodePluginCache = () => {
|
|
48
|
+
const cachePath = resolveOpenCodePluginCachePath();
|
|
49
|
+
if (!cachePath) {
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
return rm(cachePath, { recursive: true, force: true })
|
|
53
|
+
.then(() => undefined)
|
|
54
|
+
.catch(() => undefined);
|
|
55
|
+
};
|
|
39
56
|
const readConfigFile = (configPath) => readFile(configPath, 'utf8')
|
|
40
57
|
.then((configContent) => JSON.parse(configContent))
|
|
41
58
|
.catch((error) => {
|
|
@@ -44,16 +61,17 @@ const readConfigFile = (configPath) => readFile(configPath, 'utf8')
|
|
|
44
61
|
}
|
|
45
62
|
throw error;
|
|
46
63
|
});
|
|
47
|
-
const resolvePluginEntry = (options) => options.useLocalPath ? localPluginPath.replaceAll('\\', '/') :
|
|
48
|
-
const
|
|
64
|
+
const resolvePluginEntry = (options) => options.useLocalPath ? localPluginPath.replaceAll('\\', '/') : PLUGIN_PACKAGE_LATEST;
|
|
65
|
+
const pluginEntryName = (entry) => Array.isArray(entry) ? entry[0] : entry;
|
|
66
|
+
const pluginEntryExists = (pluginList, pluginEntry) => (pluginList ?? []).some((entry) => pluginEntryName(entry) === pluginEntry);
|
|
49
67
|
const mergePluginConfig = (config, pluginEntry) => {
|
|
50
|
-
|
|
51
|
-
return config;
|
|
52
|
-
}
|
|
68
|
+
const pluginListWithoutStaleTimefly = (config.plugin ?? []).filter((entry) => !TIMEFLY_PLUGIN_PATTERN.test(pluginEntryName(entry)));
|
|
53
69
|
return {
|
|
54
70
|
...config,
|
|
55
71
|
$schema: config.$schema ?? 'https://opencode.ai/config.json',
|
|
56
|
-
plugin:
|
|
72
|
+
plugin: pluginEntryExists(pluginListWithoutStaleTimefly, pluginEntry)
|
|
73
|
+
? pluginListWithoutStaleTimefly
|
|
74
|
+
: [...pluginListWithoutStaleTimefly, pluginEntry]
|
|
57
75
|
};
|
|
58
76
|
};
|
|
59
77
|
const printPostInstallInstructions = (configPath) => {
|
|
@@ -69,9 +87,7 @@ const printPostInstallInstructions = (configPath) => {
|
|
|
69
87
|
console.log(` Upgrade: ${PRICING_URL}`);
|
|
70
88
|
console.log(' 3. Restart OpenCode.');
|
|
71
89
|
console.log('');
|
|
72
|
-
console.log('
|
|
73
|
-
console.log(' TIMEFLY_API_BASE_URL=https://api.timefly.dev');
|
|
74
|
-
console.log(' TIMEFLY_ACCESS_TOKEN=... (manual token, expires in ~15 min without refresh file)');
|
|
90
|
+
console.log('To update later, run setup-opencode again — it refreshes @latest and clears the plugin cache.');
|
|
75
91
|
console.log('');
|
|
76
92
|
console.log(`Auth file: ${authFilePath}`);
|
|
77
93
|
};
|
|
@@ -79,7 +95,8 @@ const installPlugin = (options) => {
|
|
|
79
95
|
const configPath = resolveConfigDestination(options);
|
|
80
96
|
const configDirectory = path.dirname(configPath);
|
|
81
97
|
const pluginEntry = resolvePluginEntry(options);
|
|
82
|
-
return
|
|
98
|
+
return clearOpenCodePluginCache()
|
|
99
|
+
.then(() => mkdir(configDirectory, { recursive: true }))
|
|
83
100
|
.then(() => readConfigFile(configPath))
|
|
84
101
|
.then((config) => mergePluginConfig(config, pluginEntry))
|
|
85
102
|
.then((mergedConfig) => writeFile(configPath, `${JSON.stringify(mergedConfig, null, 2)}\n`, 'utf8'))
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import type { CreateAiUsageEventInput } from '@timefly/ai-sdk';
|
|
2
2
|
import type { OpenCodeAssistantMessage, OpenCodeCompactionPart, OpenCodeRetryPart, OpenCodeSessionInfo, OpenCodeStepFinishPart, OpenCodeUserMessage } from './opencode-readers.js';
|
|
3
|
+
import type { SessionStats, SessionTiming, CompactionDelta } from './event-tracker.js';
|
|
3
4
|
export type { OpenCodeAssistantMessage, OpenCodeCompactionPart, OpenCodeRetryPart, OpenCodeSessionInfo, OpenCodeStepFinishPart, OpenCodeUserMessage } from './opencode-readers.js';
|
|
4
5
|
export type { OpenCodeTokenUsage } from './token-usage.js';
|
|
5
6
|
export declare const readSessionIdOverride: (sessionId: string) => string;
|
|
6
7
|
export declare const buildModelId: (providerId: string, modelId: string) => string;
|
|
7
8
|
export declare const mapSessionStartInput: (sessionInfo: OpenCodeSessionInfo) => Pick<CreateAiUsageEventInput, "sessionId" | "metadata">;
|
|
8
|
-
export declare const mapSessionEndInput: (sessionId: string, sessionStats:
|
|
9
|
-
inputTokens: number;
|
|
10
|
-
outputTokens: number;
|
|
11
|
-
totalTokens: number;
|
|
12
|
-
turnCount: number;
|
|
13
|
-
toolCallCount: number;
|
|
14
|
-
requestCount: number;
|
|
15
|
-
stepCount: number;
|
|
16
|
-
}) => Pick<CreateAiUsageEventInput, "sessionId" | "metadata">;
|
|
9
|
+
export declare const mapSessionEndInput: (sessionId: string, sessionStats: SessionStats, sessionTiming: SessionTiming) => Pick<CreateAiUsageEventInput, "sessionId" | "metadata" | "durationMs">;
|
|
17
10
|
export declare const mapLlmRequestInput: (input: {
|
|
18
11
|
sessionID: string;
|
|
19
12
|
agent: string;
|
|
@@ -24,11 +17,14 @@ export declare const mapLlmRequestInput: (input: {
|
|
|
24
17
|
topP: number;
|
|
25
18
|
maxOutputTokens?: number;
|
|
26
19
|
}) => CreateAiUsageEventInput;
|
|
27
|
-
export declare const mapAssistantTurnCompleteInput: (message: OpenCodeAssistantMessage
|
|
20
|
+
export declare const mapAssistantTurnCompleteInput: (message: OpenCodeAssistantMessage, options?: {
|
|
21
|
+
providerSource?: string;
|
|
22
|
+
compactionDelta?: CompactionDelta;
|
|
23
|
+
}) => CreateAiUsageEventInput | undefined;
|
|
28
24
|
export declare const mapAssistantLlmResponseInput: (message: OpenCodeAssistantMessage) => CreateAiUsageEventInput | undefined;
|
|
29
25
|
export declare const mapStepFinishInput: (part: OpenCodeStepFinishPart) => CreateAiUsageEventInput;
|
|
30
26
|
export declare const mapUserMessageInput: (message: OpenCodeUserMessage) => CreateAiUsageEventInput;
|
|
31
|
-
export declare const mapCompactionInput: (sessionId: string, auto?: boolean) => CreateAiUsageEventInput;
|
|
27
|
+
export declare const mapCompactionInput: (sessionId: string, contextBeforeTokens?: number, auto?: boolean) => CreateAiUsageEventInput;
|
|
32
28
|
export declare const mapErrorInput: (sessionId: string, errorMetadata: Record<string, string | number | boolean>) => CreateAiUsageEventInput;
|
|
33
29
|
export declare const mapToolCallInput: (input: {
|
|
34
30
|
sessionID: string;
|
|
@@ -49,5 +45,5 @@ export declare const mapCommandExecutedInput: (input: {
|
|
|
49
45
|
messageID: string;
|
|
50
46
|
}) => CreateAiUsageEventInput;
|
|
51
47
|
export declare const mapRetryPartInput: (part: OpenCodeRetryPart) => CreateAiUsageEventInput;
|
|
52
|
-
export declare const mapCompactionPartInput: (part: OpenCodeCompactionPart) => CreateAiUsageEventInput;
|
|
48
|
+
export declare const mapCompactionPartInput: (part: OpenCodeCompactionPart, contextBeforeTokens?: number) => CreateAiUsageEventInput;
|
|
53
49
|
//# sourceMappingURL=map-opencode-event.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-opencode-event.d.ts","sourceRoot":"","sources":["../src/map-opencode-event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,KAAK,EACX,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"map-opencode-event.d.ts","sourceRoot":"","sources":["../src/map-opencode-event.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,38 @@ import { buildTokenMetadata, buildTokenMetrics } from './token-usage.js';
|
|
|
2
2
|
export const readSessionIdOverride = (sessionId) => typeof process !== 'undefined' && process.env.TIMEFLY_OPENCODE_SESSION_ID
|
|
3
3
|
? process.env.TIMEFLY_OPENCODE_SESSION_ID
|
|
4
4
|
: sessionId;
|
|
5
|
-
|
|
5
|
+
const INVALID_ID_VALUES = new Set(['', 'unknown', 'undefined', 'null']);
|
|
6
|
+
const cleanText = (value) => value.trim();
|
|
7
|
+
const cleanIdentity = (value) => {
|
|
8
|
+
const cleanedValue = cleanText(value);
|
|
9
|
+
return INVALID_ID_VALUES.has(cleanedValue.toLowerCase()) ? '' : cleanedValue;
|
|
10
|
+
};
|
|
11
|
+
const cleanNumber = (value) => Number.isFinite(value) ? value : 0;
|
|
12
|
+
const cleanMetadata = (metadata) => Object.fromEntries(Object.entries(metadata).filter(([, metadataValue]) => {
|
|
13
|
+
if (metadataValue === undefined) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (typeof metadataValue === 'number') {
|
|
17
|
+
return Number.isFinite(metadataValue);
|
|
18
|
+
}
|
|
19
|
+
if (typeof metadataValue === 'string') {
|
|
20
|
+
return cleanText(metadataValue).length > 0;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}));
|
|
24
|
+
const identityMetadata = (providerId, modelId) => {
|
|
25
|
+
const cleanProviderId = cleanIdentity(providerId);
|
|
26
|
+
const cleanModelId = cleanIdentity(modelId);
|
|
27
|
+
return {
|
|
28
|
+
...(cleanProviderId ? { provider_id: cleanProviderId } : {}),
|
|
29
|
+
...(cleanModelId ? { model_id: cleanModelId } : {})
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export const buildModelId = (providerId, modelId) => {
|
|
33
|
+
const cleanProviderId = cleanIdentity(providerId);
|
|
34
|
+
const cleanModelId = cleanIdentity(modelId);
|
|
35
|
+
return cleanProviderId && cleanModelId ? `${cleanProviderId}/${cleanModelId}` : cleanModelId;
|
|
36
|
+
};
|
|
6
37
|
export const mapSessionStartInput = (sessionInfo) => ({
|
|
7
38
|
sessionId: readSessionIdOverride(sessionInfo.id),
|
|
8
39
|
metadata: {
|
|
@@ -11,81 +42,112 @@ export const mapSessionStartInput = (sessionInfo) => ({
|
|
|
11
42
|
directory: sessionInfo.directory
|
|
12
43
|
}
|
|
13
44
|
});
|
|
14
|
-
export const mapSessionEndInput = (sessionId, sessionStats) => ({
|
|
45
|
+
export const mapSessionEndInput = (sessionId, sessionStats, sessionTiming) => ({
|
|
15
46
|
sessionId: readSessionIdOverride(sessionId),
|
|
47
|
+
durationMs: sessionTiming.sessionWallMs,
|
|
16
48
|
metadata: {
|
|
17
|
-
session_input_tokens: sessionStats.
|
|
49
|
+
session_input_tokens: sessionStats.billedInputTokens,
|
|
50
|
+
session_billed_input_tokens: sessionStats.billedInputTokens,
|
|
18
51
|
session_output_tokens: sessionStats.outputTokens,
|
|
19
52
|
session_total_tokens: sessionStats.totalTokens,
|
|
20
53
|
session_turn_count: sessionStats.turnCount,
|
|
21
54
|
session_tool_call_count: sessionStats.toolCallCount,
|
|
22
55
|
session_request_count: sessionStats.requestCount,
|
|
23
|
-
session_step_count: sessionStats.stepCount
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
session_step_count: sessionStats.stepCount,
|
|
57
|
+
session_compaction_count: sessionStats.compactionCount,
|
|
58
|
+
session_retry_count: sessionStats.retryCount,
|
|
59
|
+
session_error_count: sessionStats.errorCount,
|
|
60
|
+
session_cache_read_tokens: sessionStats.cacheReadTokens,
|
|
61
|
+
session_cache_write_tokens: sessionStats.cacheWriteTokens,
|
|
62
|
+
session_reasoning_tokens: sessionStats.reasoningTokens,
|
|
63
|
+
session_compaction_tokens_saved: sessionStats.compactionTokensSaved,
|
|
64
|
+
session_tool_output_chars: sessionStats.totalToolOutputChars,
|
|
65
|
+
primary_provider_id: sessionStats.primaryProviderId,
|
|
66
|
+
primary_provider_source: sessionStats.primaryProviderSource,
|
|
67
|
+
peak_context_tokens: sessionStats.peakContextTokens,
|
|
68
|
+
latest_context_tokens: sessionStats.latestContextTokens,
|
|
69
|
+
context_at_start_tokens: sessionStats.contextAtStartTokens,
|
|
70
|
+
session_wall_ms: sessionTiming.sessionWallMs,
|
|
71
|
+
ai_generation_ms: sessionTiming.aiGenerationMs,
|
|
72
|
+
user_wait_ms: sessionTiming.userWaitMs
|
|
39
73
|
}
|
|
40
74
|
});
|
|
41
|
-
export const
|
|
75
|
+
export const mapLlmRequestInput = (input) => {
|
|
76
|
+
const modelId = buildModelId(input.providerId, input.modelId);
|
|
77
|
+
return {
|
|
78
|
+
sessionId: readSessionIdOverride(input.sessionID),
|
|
79
|
+
eventType: 'llm_request',
|
|
80
|
+
...(modelId ? { modelId } : {}),
|
|
81
|
+
planMode: input.agent,
|
|
82
|
+
metadata: cleanMetadata({
|
|
83
|
+
...identityMetadata(input.providerId, input.modelId),
|
|
84
|
+
provider_source: cleanIdentity(input.providerSource),
|
|
85
|
+
agent: input.agent,
|
|
86
|
+
temperature: cleanNumber(input.temperature),
|
|
87
|
+
top_p: cleanNumber(input.topP),
|
|
88
|
+
...(input.maxOutputTokens !== undefined ? { max_output_tokens: cleanNumber(input.maxOutputTokens) } : {})
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
export const mapAssistantTurnCompleteInput = (message, options) => {
|
|
42
93
|
if (message.time.completed === undefined) {
|
|
43
94
|
return undefined;
|
|
44
95
|
}
|
|
45
|
-
const durationMs = message.time.completed - message.time.created;
|
|
96
|
+
const durationMs = Math.max(0, message.time.completed - message.time.created);
|
|
46
97
|
const tokenMetrics = buildTokenMetrics(message.tokens, durationMs);
|
|
98
|
+
const modelId = buildModelId(message.providerID, message.modelID);
|
|
47
99
|
return {
|
|
48
100
|
sessionId: readSessionIdOverride(message.sessionID),
|
|
49
101
|
eventType: 'turn_complete',
|
|
50
|
-
modelId:
|
|
102
|
+
...(modelId ? { modelId } : {}),
|
|
51
103
|
planMode: message.mode,
|
|
52
104
|
inputTokens: tokenMetrics.inputTokens,
|
|
53
105
|
outputTokens: tokenMetrics.outputTokens,
|
|
54
106
|
totalTokens: tokenMetrics.totalTokens,
|
|
55
107
|
durationMs,
|
|
56
|
-
|
|
108
|
+
eventAtUtc: new Date(message.time.completed),
|
|
109
|
+
metadata: cleanMetadata(buildTokenMetadata(tokenMetrics, {
|
|
57
110
|
message_id: message.id,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
111
|
+
...identityMetadata(message.providerID, message.modelID),
|
|
112
|
+
...(options?.providerSource ? { provider_source: cleanIdentity(options.providerSource) } : {}),
|
|
113
|
+
context_tokens: tokenMetrics.inputTokens,
|
|
114
|
+
cost: cleanNumber(message.cost),
|
|
61
115
|
...(message.finish ? { finish_reason: message.finish } : {}),
|
|
62
116
|
has_error: Boolean(message.error),
|
|
63
|
-
...(message.error ? { error_name: message.error.name } : {})
|
|
64
|
-
|
|
117
|
+
...(message.error ? { error_name: message.error.name } : {}),
|
|
118
|
+
...(options?.compactionDelta
|
|
119
|
+
? {
|
|
120
|
+
context_before_compaction: options.compactionDelta.contextBeforeTokens,
|
|
121
|
+
context_after_compaction: options.compactionDelta.contextAfterTokens,
|
|
122
|
+
compaction_tokens_saved: options.compactionDelta.tokensSaved
|
|
123
|
+
}
|
|
124
|
+
: {})
|
|
125
|
+
}))
|
|
65
126
|
};
|
|
66
127
|
};
|
|
67
128
|
export const mapAssistantLlmResponseInput = (message) => {
|
|
68
129
|
if (message.time.completed === undefined) {
|
|
69
130
|
return undefined;
|
|
70
131
|
}
|
|
71
|
-
const durationMs = message.time.completed - message.time.created;
|
|
132
|
+
const durationMs = Math.max(0, message.time.completed - message.time.created);
|
|
72
133
|
const tokenMetrics = buildTokenMetrics(message.tokens, durationMs);
|
|
134
|
+
const modelId = buildModelId(message.providerID, message.modelID);
|
|
73
135
|
return {
|
|
74
136
|
sessionId: readSessionIdOverride(message.sessionID),
|
|
75
137
|
eventType: 'llm_response',
|
|
76
|
-
modelId:
|
|
138
|
+
...(modelId ? { modelId } : {}),
|
|
77
139
|
planMode: message.mode,
|
|
78
140
|
inputTokens: tokenMetrics.inputTokens,
|
|
79
141
|
outputTokens: tokenMetrics.outputTokens,
|
|
80
142
|
totalTokens: tokenMetrics.totalTokens,
|
|
81
143
|
durationMs,
|
|
82
|
-
|
|
144
|
+
eventAtUtc: new Date(message.time.completed),
|
|
145
|
+
metadata: cleanMetadata(buildTokenMetadata(tokenMetrics, {
|
|
83
146
|
message_id: message.id,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
cost: message.cost,
|
|
147
|
+
...identityMetadata(message.providerID, message.modelID),
|
|
148
|
+
cost: cleanNumber(message.cost),
|
|
87
149
|
response_scope: 'message'
|
|
88
|
-
})
|
|
150
|
+
}))
|
|
89
151
|
};
|
|
90
152
|
};
|
|
91
153
|
export const mapStepFinishInput = (part) => {
|
|
@@ -96,32 +158,37 @@ export const mapStepFinishInput = (part) => {
|
|
|
96
158
|
inputTokens: tokenMetrics.inputTokens,
|
|
97
159
|
outputTokens: tokenMetrics.outputTokens,
|
|
98
160
|
totalTokens: tokenMetrics.totalTokens,
|
|
99
|
-
metadata: buildTokenMetadata(tokenMetrics, {
|
|
161
|
+
metadata: cleanMetadata(buildTokenMetadata(tokenMetrics, {
|
|
100
162
|
message_id: part.messageID,
|
|
101
163
|
part_id: part.id,
|
|
102
164
|
step_reason: part.reason,
|
|
103
|
-
cost: part.cost,
|
|
165
|
+
cost: cleanNumber(part.cost),
|
|
104
166
|
response_scope: 'step'
|
|
167
|
+
}))
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
export const mapUserMessageInput = (message) => {
|
|
171
|
+
const modelId = buildModelId(message.model.providerID, message.model.modelID);
|
|
172
|
+
return {
|
|
173
|
+
sessionId: readSessionIdOverride(message.sessionID),
|
|
174
|
+
eventType: 'llm_request',
|
|
175
|
+
...(modelId ? { modelId } : {}),
|
|
176
|
+
planMode: message.agent,
|
|
177
|
+
metadata: cleanMetadata({
|
|
178
|
+
message_id: message.id,
|
|
179
|
+
...identityMetadata(message.model.providerID, message.model.modelID),
|
|
180
|
+
agent: message.agent,
|
|
181
|
+
request_scope: 'user_message'
|
|
105
182
|
})
|
|
106
183
|
};
|
|
107
184
|
};
|
|
108
|
-
export const
|
|
109
|
-
sessionId: readSessionIdOverride(message.sessionID),
|
|
110
|
-
eventType: 'llm_request',
|
|
111
|
-
modelId: buildModelId(message.model.providerID, message.model.modelID),
|
|
112
|
-
planMode: message.agent,
|
|
113
|
-
metadata: {
|
|
114
|
-
message_id: message.id,
|
|
115
|
-
provider_id: message.model.providerID,
|
|
116
|
-
model_id: message.model.modelID,
|
|
117
|
-
agent: message.agent,
|
|
118
|
-
request_scope: 'user_message'
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
export const mapCompactionInput = (sessionId, auto) => ({
|
|
185
|
+
export const mapCompactionInput = (sessionId, contextBeforeTokens, auto) => ({
|
|
122
186
|
sessionId: readSessionIdOverride(sessionId),
|
|
123
187
|
eventType: 'compaction',
|
|
124
188
|
metadata: {
|
|
189
|
+
...(contextBeforeTokens !== undefined && contextBeforeTokens > 0
|
|
190
|
+
? { context_before_compaction: contextBeforeTokens }
|
|
191
|
+
: {}),
|
|
125
192
|
...(auto !== undefined ? { auto_compaction: auto } : {})
|
|
126
193
|
}
|
|
127
194
|
});
|
|
@@ -154,7 +221,7 @@ export const mapCommandExecutedInput = (input) => ({
|
|
|
154
221
|
toolName: `command:${input.name}`,
|
|
155
222
|
metadata: {
|
|
156
223
|
command_name: input.name,
|
|
157
|
-
|
|
224
|
+
command_arguments_length: input.arguments.length,
|
|
158
225
|
message_id: input.messageID
|
|
159
226
|
}
|
|
160
227
|
});
|
|
@@ -168,12 +235,15 @@ export const mapRetryPartInput = (part) => ({
|
|
|
168
235
|
error_scope: 'retry'
|
|
169
236
|
}
|
|
170
237
|
});
|
|
171
|
-
export const mapCompactionPartInput = (part) => ({
|
|
238
|
+
export const mapCompactionPartInput = (part, contextBeforeTokens) => ({
|
|
172
239
|
sessionId: readSessionIdOverride(part.sessionID),
|
|
173
240
|
eventType: 'compaction',
|
|
174
241
|
metadata: {
|
|
175
242
|
message_id: part.messageID,
|
|
176
243
|
part_id: part.id,
|
|
177
|
-
auto_compaction: part.auto
|
|
244
|
+
auto_compaction: part.auto,
|
|
245
|
+
...(contextBeforeTokens !== undefined && contextBeforeTokens > 0
|
|
246
|
+
? { context_before_compaction: contextBeforeTokens }
|
|
247
|
+
: {})
|
|
178
248
|
}
|
|
179
249
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode-readers.d.ts","sourceRoot":"","sources":["../src/opencode-readers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAE1D,MAAM,MAAM,gBAAgB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE;QACL,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,kBAAkB,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,CAAA;KACZ,CAAA;CACD,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE;QACN,UAAU,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,MAAM,CAAA;KACf,CAAA;CACD,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,kBAAkB,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,YAAY,CAAA;IAClB,IAAI,EAAE,OAAO,CAAA;CACb,CAAA;
|
|
1
|
+
{"version":3,"file":"opencode-readers.d.ts","sourceRoot":"","sources":["../src/opencode-readers.ts"],"names":[],"mappings":"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"}
|
package/dist/opencode-readers.js
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
2
|
+
const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
3
|
+
const readTelemetryNumber = (value) => typeof value === 'number' ? (Number.isFinite(value) ? value : 0) : undefined;
|
|
2
4
|
const readTokenUsage = (value) => {
|
|
3
5
|
if (!isRecord(value)) {
|
|
4
6
|
return undefined;
|
|
5
7
|
}
|
|
6
8
|
const cacheRecord = isRecord(value.cache) ? value.cache : undefined;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const inputTokens = readTelemetryNumber(value.input);
|
|
10
|
+
const outputTokens = readTelemetryNumber(value.output);
|
|
11
|
+
const reasoningTokens = readTelemetryNumber(value.reasoning);
|
|
12
|
+
const cacheReadTokens = readTelemetryNumber(cacheRecord?.read);
|
|
13
|
+
const cacheWriteTokens = readTelemetryNumber(cacheRecord?.write);
|
|
14
|
+
if (inputTokens === undefined ||
|
|
15
|
+
outputTokens === undefined ||
|
|
16
|
+
reasoningTokens === undefined ||
|
|
17
|
+
cacheReadTokens === undefined ||
|
|
18
|
+
cacheWriteTokens === undefined) {
|
|
12
19
|
return undefined;
|
|
13
20
|
}
|
|
14
21
|
return {
|
|
15
|
-
input:
|
|
16
|
-
output:
|
|
17
|
-
reasoning:
|
|
22
|
+
input: inputTokens,
|
|
23
|
+
output: outputTokens,
|
|
24
|
+
reasoning: reasoningTokens,
|
|
18
25
|
cache: {
|
|
19
|
-
read:
|
|
20
|
-
write:
|
|
26
|
+
read: cacheReadTokens,
|
|
27
|
+
write: cacheWriteTokens
|
|
21
28
|
}
|
|
22
29
|
};
|
|
23
30
|
};
|
|
@@ -34,13 +41,14 @@ export const readAssistantMessage = (message) => {
|
|
|
34
41
|
}
|
|
35
42
|
const tokenUsage = readTokenUsage(message.tokens);
|
|
36
43
|
const timeRecord = isRecord(message.time) ? message.time : undefined;
|
|
44
|
+
const cost = readTelemetryNumber(message.cost);
|
|
37
45
|
if (typeof message.id !== 'string' ||
|
|
38
46
|
typeof message.sessionID !== 'string' ||
|
|
39
47
|
typeof message.modelID !== 'string' ||
|
|
40
48
|
typeof message.providerID !== 'string' ||
|
|
41
49
|
typeof message.mode !== 'string' ||
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
cost === undefined ||
|
|
51
|
+
!isFiniteNumber(timeRecord?.created) ||
|
|
44
52
|
!tokenUsage) {
|
|
45
53
|
return undefined;
|
|
46
54
|
}
|
|
@@ -50,12 +58,12 @@ export const readAssistantMessage = (message) => {
|
|
|
50
58
|
role: 'assistant',
|
|
51
59
|
time: {
|
|
52
60
|
created: timeRecord.created,
|
|
53
|
-
completed:
|
|
61
|
+
completed: isFiniteNumber(timeRecord.completed) ? timeRecord.completed : undefined
|
|
54
62
|
},
|
|
55
63
|
modelID: message.modelID,
|
|
56
64
|
providerID: message.providerID,
|
|
57
65
|
mode: message.mode,
|
|
58
|
-
cost
|
|
66
|
+
cost,
|
|
59
67
|
tokens: tokenUsage,
|
|
60
68
|
finish: typeof message.finish === 'string' ? message.finish : undefined,
|
|
61
69
|
error: isRecord(message.error) && typeof message.error.name === 'string' ? { name: message.error.name } : undefined
|
|
@@ -100,11 +108,12 @@ export const readStepFinishPart = (part) => {
|
|
|
100
108
|
return undefined;
|
|
101
109
|
}
|
|
102
110
|
const tokenUsage = readTokenUsage(part.tokens);
|
|
111
|
+
const cost = readTelemetryNumber(part.cost);
|
|
103
112
|
if (typeof part.id !== 'string' ||
|
|
104
113
|
typeof part.sessionID !== 'string' ||
|
|
105
114
|
typeof part.messageID !== 'string' ||
|
|
106
115
|
typeof part.reason !== 'string' ||
|
|
107
|
-
|
|
116
|
+
cost === undefined ||
|
|
108
117
|
!tokenUsage) {
|
|
109
118
|
return undefined;
|
|
110
119
|
}
|
|
@@ -114,7 +123,7 @@ export const readStepFinishPart = (part) => {
|
|
|
114
123
|
messageID: part.messageID,
|
|
115
124
|
type: 'step-finish',
|
|
116
125
|
reason: part.reason,
|
|
117
|
-
cost
|
|
126
|
+
cost,
|
|
118
127
|
tokens: tokenUsage
|
|
119
128
|
};
|
|
120
129
|
};
|
package/dist/publish-events.js
CHANGED
|
@@ -5,7 +5,7 @@ const buildSyncFailureMessage = (error) => {
|
|
|
5
5
|
return `TimeFly sync blocked: Supporter plan required. Upgrade at ${PRICING_URL}`;
|
|
6
6
|
}
|
|
7
7
|
if (error.isUnauthorized) {
|
|
8
|
-
return 'TimeFly sync blocked: not signed in. Run `bunx @timefly/opencode-plugin login
|
|
8
|
+
return 'TimeFly sync blocked: not signed in. Run `bunx @timefly/opencode-plugin login`.';
|
|
9
9
|
}
|
|
10
10
|
return `TimeFly sync failed (${error.statusCode}): ${error.message}`;
|
|
11
11
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-usage.d.ts","sourceRoot":"","sources":["../src/token-usage.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE;QACN,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE,MAAM,CAAA;KACb,CAAA;CACD,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,gBAAgB,EAAE,MAAM,CAAA;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;
|
|
1
|
+
{"version":3,"file":"token-usage.d.ts","sourceRoot":"","sources":["../src/token-usage.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE;QACN,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE,MAAM,CAAA;KACb,CAAA;CACD,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,gBAAgB,EAAE,MAAM,CAAA;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAKD,eAAO,MAAM,aAAa,GAAI,YAAY,kBAAkB,KAAG,MACyD,CAAA;AAExH,eAAO,MAAM,iBAAiB,GAAI,YAAY,kBAAkB,EAAE,aAAa,MAAM,KAAG,YAmBvF,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,cAAc,YAAY,EAAE,QAAO,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAM,KAAG,MAAM,CAC5H,MAAM,EACN,MAAM,GAAG,MAAM,GAAG,OAAO,CAUxB,CAAA"}
|
package/dist/token-usage.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
const sanitizeTokenCount = (value) => Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
2
|
+
export const sumTokenUsage = (tokenUsage) => sanitizeTokenCount(tokenUsage.input) + sanitizeTokenCount(tokenUsage.output) + sanitizeTokenCount(tokenUsage.reasoning);
|
|
2
3
|
export const buildTokenMetrics = (tokenUsage, durationMs) => {
|
|
3
|
-
const inputTokens = tokenUsage.input;
|
|
4
|
-
const outputTokens = tokenUsage.output;
|
|
5
|
-
const reasoningTokens = tokenUsage.reasoning;
|
|
4
|
+
const inputTokens = sanitizeTokenCount(tokenUsage.input);
|
|
5
|
+
const outputTokens = sanitizeTokenCount(tokenUsage.output);
|
|
6
|
+
const reasoningTokens = sanitizeTokenCount(tokenUsage.reasoning);
|
|
6
7
|
const totalTokens = sumTokenUsage(tokenUsage);
|
|
7
|
-
const durationSeconds = durationMs && durationMs > 0 ? durationMs / 1000 : undefined;
|
|
8
|
+
const durationSeconds = durationMs && Number.isFinite(durationMs) && durationMs > 0 ? durationMs / 1000 : undefined;
|
|
8
9
|
return {
|
|
9
10
|
inputTokens,
|
|
10
11
|
outputTokens,
|
|
11
12
|
totalTokens,
|
|
12
13
|
reasoningTokens,
|
|
13
|
-
cacheReadTokens: tokenUsage.cache.read,
|
|
14
|
-
cacheWriteTokens: tokenUsage.cache.write,
|
|
14
|
+
cacheReadTokens: sanitizeTokenCount(tokenUsage.cache.read),
|
|
15
|
+
cacheWriteTokens: sanitizeTokenCount(tokenUsage.cache.write),
|
|
15
16
|
outputTokensPerSecond: durationSeconds !== undefined ? Math.round((outputTokens / durationSeconds) * 100) / 100 : undefined,
|
|
16
17
|
totalTokensPerSecond: durationSeconds !== undefined ? Math.round((totalTokens / durationSeconds) * 100) / 100 : undefined
|
|
17
18
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timefly/opencode-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "TimeFly telemetry plugin for OpenCode — sessions, tokens, models, and tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./dist/cli.js",
|
|
@@ -50,19 +50,19 @@
|
|
|
50
50
|
},
|
|
51
51
|
"license": "MIT",
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@timefly/ai-sdk": "^0.2.
|
|
53
|
+
"@timefly/ai-sdk": "^0.2.1"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@opencode-ai/plugin": ">=1.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@opencode-ai/plugin": "^1.17.7",
|
|
60
|
-
"@timefly/ai-sdk": "
|
|
60
|
+
"@timefly/ai-sdk": "0.2.1",
|
|
61
61
|
"@types/bun": "^1.3.14",
|
|
62
62
|
"@types/node": "^22.15.21",
|
|
63
63
|
"typescript": "^5.9.3"
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
|
-
"
|
|
66
|
+
"bun": ">=1.3.0"
|
|
67
67
|
}
|
|
68
68
|
}
|