@timefly/opencode-plugin 0.2.7 → 0.2.9
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 +13 -15
- package/dist/event-handlers.d.ts.map +1 -1
- package/dist/event-handlers.js +4 -2
- package/dist/event-tracker.d.ts.map +1 -1
- package/dist/event-tracker.js +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/map-opencode-event.d.ts.map +1 -1
- package/dist/map-opencode-event.js +84 -47
- package/dist/opencode-readers.d.ts.map +1 -1
- package/dist/opencode-readers.js +25 -16
- package/dist/token-usage.d.ts.map +1 -1
- package/dist/token-usage.js +8 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -104,26 +104,24 @@ OpenCode plugins run inside the OpenCode process. There is no built-in TimeFly U
|
|
|
104
104
|
|
|
105
105
|
## How sync works
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
The SDK batches OpenCode events before sending them to TimeFly. This keeps VPS load predictable as usage grows.
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
| Setting | Default | Behavior |
|
|
110
|
+
|---------|---------|----------|
|
|
111
|
+
| Debounce | 5s | Flush pending events 5 seconds after the last event in a burst |
|
|
112
|
+
| Max batch | 50 events | Flush immediately when the in-memory buffer is full |
|
|
113
|
+
| Immediate flush | `session_end` | Session totals are sent right away |
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|---------|----------------|
|
|
113
|
-
| LLM request (`chat.params`) | Immediate sync attempt |
|
|
114
|
-
| Assistant turn completes | Immediate sync attempt |
|
|
115
|
-
| Tool call / result | Immediate sync attempt |
|
|
116
|
-
| Session start / end | Immediate sync attempt |
|
|
117
|
-
| Next event after failure | Retries queued events first, then sends new ones |
|
|
115
|
+
Typical flow during a coding session:
|
|
118
116
|
|
|
119
|
-
|
|
117
|
+
1. You send a prompt → `llm_request` is queued
|
|
118
|
+
2. Model responds → `turn_complete`, tool events, etc. join the same batch
|
|
119
|
+
3. After ~5s idle (or 50 events), one gzip `POST /ai/sync` sends the batch
|
|
120
|
+
4. `session_end` flushes immediately so session totals are not delayed
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
2. Model responds → `turn_complete` + `llm_response` sync (~instant when turn finishes)
|
|
123
|
-
3. Agent runs tools → each `tool_call` / `tool_result` syncs
|
|
124
|
-
4. Session goes idle → `session_end` with session totals syncs
|
|
122
|
+
Compared with the old per-event sync model, one active agent turn usually produces **1–2 HTTP requests** instead of **10–15+**.
|
|
125
123
|
|
|
126
|
-
|
|
124
|
+
Unlike the VS Code extension (timer every ~2 minutes), OpenCode still syncs in near real time — just batched within each active burst.
|
|
127
125
|
|
|
128
126
|
### What each sync request does
|
|
129
127
|
|
|
@@ -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,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,
|
|
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
|
@@ -33,8 +33,9 @@ export const handleMessageUpdated = (eventProperties, tracker, publish) => {
|
|
|
33
33
|
if (assistantMessage.time.completed === undefined || tracker.hasProcessedMessage(assistantMessage.id)) {
|
|
34
34
|
return Promise.resolve();
|
|
35
35
|
}
|
|
36
|
+
tracker.recordSessionStart(assistantMessage.sessionID, assistantMessage.time.created);
|
|
36
37
|
tracker.markMessageProcessed(assistantMessage.id);
|
|
37
|
-
const durationMs = assistantMessage.time.completed - assistantMessage.time.created;
|
|
38
|
+
const durationMs = Math.max(0, assistantMessage.time.completed - assistantMessage.time.created);
|
|
38
39
|
const tokenMetrics = buildTokenMetrics(assistantMessage.tokens, durationMs);
|
|
39
40
|
const sessionStats = tracker.getSessionStats(assistantMessage.sessionID);
|
|
40
41
|
const compactionDelta = tracker.recordTurnTokens(assistantMessage.sessionID, {
|
|
@@ -65,6 +66,7 @@ export const handleMessageUpdated = (eventProperties, tracker, publish) => {
|
|
|
65
66
|
if (tracker.hasProcessedUserMessage(userMessage.id)) {
|
|
66
67
|
return Promise.resolve();
|
|
67
68
|
}
|
|
69
|
+
tracker.recordSessionStart(userMessage.sessionID);
|
|
68
70
|
tracker.markUserMessageProcessed(userMessage.id);
|
|
69
71
|
return publish([mapUserMessageInput(userMessage)]);
|
|
70
72
|
}
|
|
@@ -124,7 +126,7 @@ export const handleSessionError = (eventProperties, tracker, publish) => {
|
|
|
124
126
|
return publish([
|
|
125
127
|
mapErrorInput(sessionId, {
|
|
126
128
|
error_name: errorName,
|
|
127
|
-
|
|
129
|
+
error_message_length: errorMessage.length,
|
|
128
130
|
error_scope: 'session'
|
|
129
131
|
})
|
|
130
132
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-tracker.d.ts","sourceRoot":"","sources":["../src/event-tracker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC7B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,gBAAgB,EAAE,MAAM,CAAA;IACxB,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,EAAE,MAAM,CAAA;IAC7B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,iBAAiB,EAAE,MAAM,CAAA;IACzB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;CACtB,CAAA;
|
|
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
|
@@ -21,6 +21,11 @@ const emptySessionStats = () => ({
|
|
|
21
21
|
primaryProviderSource: '',
|
|
22
22
|
aiGenerationMs: 0
|
|
23
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
|
+
};
|
|
24
29
|
const mergeSessionStats = (currentStats, delta) => ({
|
|
25
30
|
billedInputTokens: currentStats.billedInputTokens + (delta.billedInputTokens ?? 0),
|
|
26
31
|
outputTokens: currentStats.outputTokens + (delta.outputTokens ?? 0),
|
|
@@ -42,8 +47,8 @@ const mergeSessionStats = (currentStats, delta) => ({
|
|
|
42
47
|
: (delta.contextAtStartTokens ?? currentStats.contextAtStartTokens),
|
|
43
48
|
compactionTokensSaved: currentStats.compactionTokensSaved + (delta.compactionTokensSaved ?? 0),
|
|
44
49
|
totalToolOutputChars: currentStats.totalToolOutputChars + (delta.totalToolOutputChars ?? 0),
|
|
45
|
-
primaryProviderId: delta.primaryProviderId
|
|
46
|
-
primaryProviderSource: delta.primaryProviderSource
|
|
50
|
+
primaryProviderId: delta.primaryProviderId !== undefined ? cleanIdentity(delta.primaryProviderId) : currentStats.primaryProviderId,
|
|
51
|
+
primaryProviderSource: delta.primaryProviderSource !== undefined ? cleanIdentity(delta.primaryProviderSource) : currentStats.primaryProviderSource,
|
|
47
52
|
startedAtMs: currentStats.startedAtMs,
|
|
48
53
|
aiGenerationMs: currentStats.aiGenerationMs + (delta.aiGenerationMs ?? 0)
|
|
49
54
|
});
|
|
@@ -84,8 +89,8 @@ export const createEventTracker = () => {
|
|
|
84
89
|
const currentStats = sessionStatsById.get(sessionId) ?? emptySessionStats();
|
|
85
90
|
sessionStatsById.set(sessionId, {
|
|
86
91
|
...currentStats,
|
|
87
|
-
primaryProviderId: providerId,
|
|
88
|
-
primaryProviderSource: providerSource
|
|
92
|
+
primaryProviderId: cleanIdentity(providerId),
|
|
93
|
+
primaryProviderSource: cleanIdentity(providerSource)
|
|
89
94
|
});
|
|
90
95
|
},
|
|
91
96
|
recordCompactionPending: (sessionId, contextBeforeTokens) => {
|
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,6 +25,7 @@ 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);
|
|
30
31
|
tracker.recordProviderConnection(resolvedParams.sessionID, resolvedParams.providerId, resolvedParams.providerSource);
|
|
@@ -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;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEtF,YAAY,EACX,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAE1D,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,KAAG,MAG7C,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: {
|
|
@@ -41,43 +72,46 @@ export const mapSessionEndInput = (sessionId, sessionStats, sessionTiming) => ({
|
|
|
41
72
|
user_wait_ms: sessionTiming.userWaitMs
|
|
42
73
|
}
|
|
43
74
|
});
|
|
44
|
-
export const mapLlmRequestInput = (input) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
})
|
|
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
|
+
};
|
|
59
92
|
export const mapAssistantTurnCompleteInput = (message, options) => {
|
|
60
93
|
if (message.time.completed === undefined) {
|
|
61
94
|
return undefined;
|
|
62
95
|
}
|
|
63
|
-
const durationMs = message.time.completed - message.time.created;
|
|
96
|
+
const durationMs = Math.max(0, message.time.completed - message.time.created);
|
|
64
97
|
const tokenMetrics = buildTokenMetrics(message.tokens, durationMs);
|
|
98
|
+
const modelId = buildModelId(message.providerID, message.modelID);
|
|
65
99
|
return {
|
|
66
100
|
sessionId: readSessionIdOverride(message.sessionID),
|
|
67
101
|
eventType: 'turn_complete',
|
|
68
|
-
modelId:
|
|
102
|
+
...(modelId ? { modelId } : {}),
|
|
69
103
|
planMode: message.mode,
|
|
70
104
|
inputTokens: tokenMetrics.inputTokens,
|
|
71
105
|
outputTokens: tokenMetrics.outputTokens,
|
|
72
106
|
totalTokens: tokenMetrics.totalTokens,
|
|
73
107
|
durationMs,
|
|
74
|
-
|
|
108
|
+
eventAtUtc: new Date(message.time.completed),
|
|
109
|
+
metadata: cleanMetadata(buildTokenMetadata(tokenMetrics, {
|
|
75
110
|
message_id: message.id,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
...(options?.providerSource ? { provider_source: options.providerSource } : {}),
|
|
111
|
+
...identityMetadata(message.providerID, message.modelID),
|
|
112
|
+
...(options?.providerSource ? { provider_source: cleanIdentity(options.providerSource) } : {}),
|
|
79
113
|
context_tokens: tokenMetrics.inputTokens,
|
|
80
|
-
cost: message.cost,
|
|
114
|
+
cost: cleanNumber(message.cost),
|
|
81
115
|
...(message.finish ? { finish_reason: message.finish } : {}),
|
|
82
116
|
has_error: Boolean(message.error),
|
|
83
117
|
...(message.error ? { error_name: message.error.name } : {}),
|
|
@@ -88,31 +122,32 @@ export const mapAssistantTurnCompleteInput = (message, options) => {
|
|
|
88
122
|
compaction_tokens_saved: options.compactionDelta.tokensSaved
|
|
89
123
|
}
|
|
90
124
|
: {})
|
|
91
|
-
})
|
|
125
|
+
}))
|
|
92
126
|
};
|
|
93
127
|
};
|
|
94
128
|
export const mapAssistantLlmResponseInput = (message) => {
|
|
95
129
|
if (message.time.completed === undefined) {
|
|
96
130
|
return undefined;
|
|
97
131
|
}
|
|
98
|
-
const durationMs = message.time.completed - message.time.created;
|
|
132
|
+
const durationMs = Math.max(0, message.time.completed - message.time.created);
|
|
99
133
|
const tokenMetrics = buildTokenMetrics(message.tokens, durationMs);
|
|
134
|
+
const modelId = buildModelId(message.providerID, message.modelID);
|
|
100
135
|
return {
|
|
101
136
|
sessionId: readSessionIdOverride(message.sessionID),
|
|
102
137
|
eventType: 'llm_response',
|
|
103
|
-
modelId:
|
|
138
|
+
...(modelId ? { modelId } : {}),
|
|
104
139
|
planMode: message.mode,
|
|
105
140
|
inputTokens: tokenMetrics.inputTokens,
|
|
106
141
|
outputTokens: tokenMetrics.outputTokens,
|
|
107
142
|
totalTokens: tokenMetrics.totalTokens,
|
|
108
143
|
durationMs,
|
|
109
|
-
|
|
144
|
+
eventAtUtc: new Date(message.time.completed),
|
|
145
|
+
metadata: cleanMetadata(buildTokenMetadata(tokenMetrics, {
|
|
110
146
|
message_id: message.id,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
cost: message.cost,
|
|
147
|
+
...identityMetadata(message.providerID, message.modelID),
|
|
148
|
+
cost: cleanNumber(message.cost),
|
|
114
149
|
response_scope: 'message'
|
|
115
|
-
})
|
|
150
|
+
}))
|
|
116
151
|
};
|
|
117
152
|
};
|
|
118
153
|
export const mapStepFinishInput = (part) => {
|
|
@@ -123,28 +158,30 @@ export const mapStepFinishInput = (part) => {
|
|
|
123
158
|
inputTokens: tokenMetrics.inputTokens,
|
|
124
159
|
outputTokens: tokenMetrics.outputTokens,
|
|
125
160
|
totalTokens: tokenMetrics.totalTokens,
|
|
126
|
-
metadata: buildTokenMetadata(tokenMetrics, {
|
|
161
|
+
metadata: cleanMetadata(buildTokenMetadata(tokenMetrics, {
|
|
127
162
|
message_id: part.messageID,
|
|
128
163
|
part_id: part.id,
|
|
129
164
|
step_reason: part.reason,
|
|
130
|
-
cost: part.cost,
|
|
165
|
+
cost: cleanNumber(part.cost),
|
|
131
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'
|
|
132
182
|
})
|
|
133
183
|
};
|
|
134
184
|
};
|
|
135
|
-
export const mapUserMessageInput = (message) => ({
|
|
136
|
-
sessionId: readSessionIdOverride(message.sessionID),
|
|
137
|
-
eventType: 'llm_request',
|
|
138
|
-
modelId: buildModelId(message.model.providerID, message.model.modelID),
|
|
139
|
-
planMode: message.agent,
|
|
140
|
-
metadata: {
|
|
141
|
-
message_id: message.id,
|
|
142
|
-
provider_id: message.model.providerID,
|
|
143
|
-
model_id: message.model.modelID,
|
|
144
|
-
agent: message.agent,
|
|
145
|
-
request_scope: 'user_message'
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
185
|
export const mapCompactionInput = (sessionId, contextBeforeTokens, auto) => ({
|
|
149
186
|
sessionId: readSessionIdOverride(sessionId),
|
|
150
187
|
eventType: 'compaction',
|
|
@@ -184,7 +221,7 @@ export const mapCommandExecutedInput = (input) => ({
|
|
|
184
221
|
toolName: `command:${input.name}`,
|
|
185
222
|
metadata: {
|
|
186
223
|
command_name: input.name,
|
|
187
|
-
|
|
224
|
+
command_arguments_length: input.arguments.length,
|
|
188
225
|
message_id: input.messageID
|
|
189
226
|
}
|
|
190
227
|
});
|
|
@@ -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
|
};
|
|
@@ -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.9",
|
|
4
4
|
"description": "TimeFly telemetry plugin for OpenCode — sessions, tokens, models, and tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./dist/cli.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"license": "MIT",
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@timefly/ai-sdk": "^0.2.
|
|
53
|
+
"@timefly/ai-sdk": "^0.2.2"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@opencode-ai/plugin": ">=1.0.0"
|