@vidiemme/copilot-proxy 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +38 -5
  2. package/dist/cli.js +1 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/collector/forwarder.d.ts +7 -1
  5. package/dist/collector/forwarder.js +48 -18
  6. package/dist/collector/forwarder.js.map +1 -1
  7. package/dist/config.d.ts +2 -0
  8. package/dist/config.js +1 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/proxy/handler.d.ts +3 -2
  11. package/dist/proxy/handler.js +13 -4
  12. package/dist/proxy/handler.js.map +1 -1
  13. package/dist/reload/copilotCli.d.ts +10 -3
  14. package/dist/reload/copilotCli.js +61 -13
  15. package/dist/reload/copilotCli.js.map +1 -1
  16. package/dist/reload/index.d.ts +1 -0
  17. package/dist/reload/index.js +5 -2
  18. package/dist/reload/index.js.map +1 -1
  19. package/dist/reload/vscodeChat.d.ts +1 -0
  20. package/dist/reload/vscodeChat.js +2 -0
  21. package/dist/reload/vscodeChat.js.map +1 -1
  22. package/dist/server.d.ts +2 -0
  23. package/dist/server.js +5 -1
  24. package/dist/server.js.map +1 -1
  25. package/dist/usage/collector.d.ts +11 -1
  26. package/dist/usage/collector.js +39 -2
  27. package/dist/usage/collector.js.map +1 -1
  28. package/dist/usage/copilotUsage.d.ts +25 -0
  29. package/dist/usage/copilotUsage.js +59 -0
  30. package/dist/usage/copilotUsage.js.map +1 -0
  31. package/dist/usage/fusion.d.ts +6 -0
  32. package/dist/usage/fusion.js +45 -0
  33. package/dist/usage/fusion.js.map +1 -0
  34. package/dist/usage/fusionCorrelator.d.ts +34 -0
  35. package/dist/usage/fusionCorrelator.js +182 -0
  36. package/dist/usage/fusionCorrelator.js.map +1 -0
  37. package/dist/usage/types.d.ts +2 -0
  38. package/dist/usage/types.js.map +1 -1
  39. package/package.json +2 -2
@@ -0,0 +1,59 @@
1
+ import { coerceTokenCount } from './types.js';
2
+ function asObject(value) {
3
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : undefined;
4
+ }
5
+ const TOKEN_FIELDS = {
6
+ input: 'inputTokens',
7
+ cache_read: 'cachedInputTokens',
8
+ cache_write: 'cacheWriteTokens',
9
+ output: 'outputTokens',
10
+ };
11
+ /**
12
+ * Legge il blocco `copilot_usage` di una risposta GitHub Copilot.
13
+ *
14
+ * Le righe di `token_details` usano la stessa convenzione dei nostri contatori
15
+ * (input senza cache, letture, scritture, output). Sono anche l'unico posto
16
+ * in cui `/chat/completions` riporta le scritture in cache dei modelli
17
+ * Anthropic: l'`usage` standard le conta dentro `prompt_tokens`.
18
+ */
19
+ export function readCopilotUsage(value) {
20
+ const block = asObject(value);
21
+ if (!block)
22
+ return undefined;
23
+ const nano = block.total_nano_aiu;
24
+ if (typeof nano !== 'number' || !Number.isFinite(nano) || nano < 0)
25
+ return undefined;
26
+ const billing = { totalNanoAiu: Math.round(nano) };
27
+ if (!Array.isArray(block.token_details))
28
+ return billing;
29
+ const tokens = { inputTokens: 0, cachedInputTokens: 0, cacheWriteTokens: 0, outputTokens: 0 };
30
+ let recognized = false;
31
+ for (const item of block.token_details) {
32
+ const detail = asObject(item);
33
+ const field = typeof detail?.token_type === 'string' ? TOKEN_FIELDS[detail.token_type] : undefined;
34
+ if (!detail || !field)
35
+ continue;
36
+ recognized = true;
37
+ tokens[field] += coerceTokenCount(detail.token_count);
38
+ if (!billing.model && typeof detail.model === 'string' && detail.model.length > 0) {
39
+ billing.model = detail.model;
40
+ }
41
+ }
42
+ if (recognized)
43
+ billing.tokens = tokens;
44
+ return billing;
45
+ }
46
+ /**
47
+ * Cerca `copilot_usage` in un payload: al primo livello in tutti i dialetti
48
+ * (risposta intera, chunk o evento SSE), e per prudenza anche dentro
49
+ * `response` / `message`, dove i due protocolli annidano il resto.
50
+ */
51
+ export function findCopilotUsage(payload) {
52
+ const root = asObject(payload);
53
+ if (!root)
54
+ return undefined;
55
+ return (readCopilotUsage(root.copilot_usage) ??
56
+ readCopilotUsage(asObject(root.response)?.copilot_usage) ??
57
+ readCopilotUsage(asObject(root.message)?.copilot_usage));
58
+ }
59
+ //# sourceMappingURL=copilotUsage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilotUsage.js","sourceRoot":"","sources":["../../src/usage/copilotUsage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAmB,MAAM,YAAY,CAAC;AAI/D,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAc,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5G,CAAC;AAYD,MAAM,YAAY,GAAqC;IACrD,KAAK,EAAE,aAAa;IACpB,UAAU,EAAE,mBAAmB;IAC/B,WAAW,EAAE,kBAAkB;IAC/B,MAAM,EAAE,cAAc;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,cAAc,CAAC;IAClC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAErF,MAAM,OAAO,GAAmB,EAAE,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IACnE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC;QAAE,OAAO,OAAO,CAAC;IAExD,MAAM,MAAM,GAAe,EAAE,WAAW,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IAC1G,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,OAAO,MAAM,EAAE,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACnG,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK;YAAE,SAAS;QAChC,UAAU,GAAG,IAAI,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,IAAI,gBAAgB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClF,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,IAAI,UAAU;QAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IACxC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,OAAO,CACL,gBAAgB,CAAC,IAAI,CAAC,aAAa,CAAC;QACpC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;QACxD,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,CACxD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function isHydraFusionModel(value: unknown): boolean;
2
+ /** Un modello selezionato esplicitamente, non il modello fisico restituito dall'upstream. */
3
+ export declare function fusionFromSelectedModel(value: unknown): boolean | null;
4
+ export declare function fusionFromChatSelection(selected: unknown, resolved: unknown): boolean | null;
5
+ /** Il modello fisico da solo non basta: puo' essere stato scelto dal router Fusion. */
6
+ export declare function fusionFromCliUsage(usage: Record<string, unknown>, selectedModel?: string): boolean | null;
@@ -0,0 +1,45 @@
1
+ function modelName(value) {
2
+ if (typeof value !== 'string' || value.trim().length === 0)
3
+ return undefined;
4
+ return value.trim().toLowerCase().replace(/^copilot\//, '');
5
+ }
6
+ export function isHydraFusionModel(value) {
7
+ const model = modelName(value);
8
+ return model === 'hydrafusion' || model === 'hydrafusion-max';
9
+ }
10
+ /** Un modello selezionato esplicitamente, non il modello fisico restituito dall'upstream. */
11
+ export function fusionFromSelectedModel(value) {
12
+ const model = modelName(value);
13
+ if (!model || model === 'auto')
14
+ return null;
15
+ if (isHydraFusionModel(model))
16
+ return true;
17
+ if (model.includes('fusion'))
18
+ return null;
19
+ return false;
20
+ }
21
+ export function fusionFromChatSelection(selected, resolved) {
22
+ const selection = fusionFromSelectedModel(selected);
23
+ if (selection !== false)
24
+ return selection;
25
+ const requested = modelName(selected)?.replace(/[._]/g, '-');
26
+ const actual = modelName(resolved)?.replace(/[._]/g, '-');
27
+ return requested && actual === requested ? false : null;
28
+ }
29
+ /** Il modello fisico da solo non basta: puo' essere stato scelto dal router Fusion. */
30
+ export function fusionFromCliUsage(usage, selectedModel) {
31
+ const fusion = usage.fusion;
32
+ if (typeof fusion === 'object' && fusion !== null && !Array.isArray(fusion)) {
33
+ const syntheticModel = fusion.syntheticModel;
34
+ return isHydraFusionModel(syntheticModel) ? true : null;
35
+ }
36
+ if (fusion !== null && fusion !== undefined)
37
+ return null;
38
+ const selected = modelName(selectedModel);
39
+ const actual = modelName(usage.model);
40
+ if (selected && actual === selected && usage.isAuto !== true && fusionFromSelectedModel(selected) === false) {
41
+ return false;
42
+ }
43
+ return null;
44
+ }
45
+ //# sourceMappingURL=fusion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fusion.js","sourceRoot":"","sources":["../../src/usage/fusion.ts"],"names":[],"mappings":"AAAA,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7E,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO,KAAK,KAAK,aAAa,IAAI,KAAK,KAAK,iBAAiB,CAAC;AAChE,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,kBAAkB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAAiB,EAAE,QAAiB;IAC1E,MAAM,SAAS,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC1D,OAAO,SAAS,IAAI,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,kBAAkB,CAAC,KAA8B,EAAE,aAAsB;IACvF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5E,MAAM,cAAc,GAAI,MAAkC,CAAC,cAAc,CAAC;QAC1E,OAAO,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;IACD,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,uBAAuB,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;QAC5G,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { type UsageEventPayload } from '@vidiemme/copilot-usage-contract';
2
+ import type { Logger } from '../collector/forwarder.js';
3
+ /**
4
+ * Il record CLI viene scritto dopo la risposta HTTP. Il primo evento resta
5
+ * indipendente dal log; un secondo invio con lo stesso requestId lo arricchisce.
6
+ */
7
+ export declare class FusionCorrelator {
8
+ private readonly stateDir;
9
+ private readonly onResolved;
10
+ private readonly logger;
11
+ private readonly pollIntervalMs;
12
+ private readonly waitMs;
13
+ private readonly pending;
14
+ private timer;
15
+ private inFlight;
16
+ private closed;
17
+ private readonly warnedSessions;
18
+ private readonly files;
19
+ /** Il record CLI puo' precedere di poco la registrazione della richiesta nel proxy. */
20
+ private readonly seen;
21
+ constructor(stateDir: string, onResolved: (event: UsageEventPayload & {
22
+ hydraFusion: boolean;
23
+ }) => void, logger: Pick<Logger, 'warn'>, pollIntervalMs?: number, waitMs?: number);
24
+ track(event: UsageEventPayload): void;
25
+ close(): Promise<void>;
26
+ private schedule;
27
+ private scan;
28
+ /**
29
+ * Legge solo le righe complete aggiunte dall'ultima scansione. Il modello
30
+ * selezionato resta noto finche' la lettura e' continua: dopo un salto torna
31
+ * sconosciuto, cosi' non si deduce una selezione diretta senza prove.
32
+ */
33
+ private readNewLines;
34
+ }
@@ -0,0 +1,182 @@
1
+ import { open, readdir, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { mergeHydraFusion } from '@vidiemme/copilot-usage-contract';
4
+ import { sessionCalls } from '../reload/copilotCli.js';
5
+ const MAX_LOG_BYTES = 4 * 1024 * 1024;
6
+ const MAX_PENDING = 10_000;
7
+ function missingFile(error) {
8
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
9
+ }
10
+ /**
11
+ * Il record CLI viene scritto dopo la risposta HTTP. Il primo evento resta
12
+ * indipendente dal log; un secondo invio con lo stesso requestId lo arricchisce.
13
+ */
14
+ export class FusionCorrelator {
15
+ stateDir;
16
+ onResolved;
17
+ logger;
18
+ pollIntervalMs;
19
+ waitMs;
20
+ pending = new Map();
21
+ timer;
22
+ inFlight;
23
+ closed = false;
24
+ warnedSessions = new Set();
25
+ files = new Map();
26
+ /** Il record CLI puo' precedere di poco la registrazione della richiesta nel proxy. */
27
+ seen = new Map();
28
+ constructor(stateDir, onResolved, logger, pollIntervalMs = 1000, waitMs = 15_000) {
29
+ this.stateDir = stateDir;
30
+ this.onResolved = onResolved;
31
+ this.logger = logger;
32
+ this.pollIntervalMs = pollIntervalMs;
33
+ this.waitMs = waitMs;
34
+ }
35
+ track(event) {
36
+ if (this.closed || event.hydraFusion != null)
37
+ return;
38
+ if (this.pending.size >= MAX_PENDING && !this.pending.has(event.requestId)) {
39
+ this.logger.warn({ pending: this.pending.size }, 'troppe richieste in attesa del routing Fusion');
40
+ return;
41
+ }
42
+ this.pending.set(event.requestId, { event, addedAt: Date.now(), reportedFalse: false });
43
+ this.schedule(0);
44
+ }
45
+ async close() {
46
+ this.closed = true;
47
+ if (this.timer)
48
+ clearTimeout(this.timer);
49
+ this.timer = undefined;
50
+ await this.inFlight;
51
+ this.pending.clear();
52
+ }
53
+ schedule(delay) {
54
+ if (this.closed || this.timer || this.inFlight || this.pending.size === 0)
55
+ return;
56
+ this.timer = setTimeout(() => {
57
+ this.timer = undefined;
58
+ const run = this.scan().catch((error) => {
59
+ this.logger.warn({ err: error }, 'lettura dei metadati Fusion fallita');
60
+ });
61
+ this.inFlight = run;
62
+ void run.then(() => {
63
+ this.inFlight = undefined;
64
+ this.schedule(this.pollIntervalMs);
65
+ });
66
+ }, delay);
67
+ this.timer.unref();
68
+ }
69
+ async scan() {
70
+ const now = Date.now();
71
+ for (const [id, pending] of this.pending) {
72
+ if (now - pending.addedAt >= this.waitMs)
73
+ this.pending.delete(id);
74
+ }
75
+ if (this.pending.size === 0)
76
+ return;
77
+ let entries;
78
+ try {
79
+ entries = await readdir(this.stateDir, { withFileTypes: true });
80
+ }
81
+ catch (error) {
82
+ if (!missingFile(error))
83
+ throw error;
84
+ return;
85
+ }
86
+ const oldest = Math.min(...[...this.pending.values()].map(({ addedAt }) => addedAt)) - 5000;
87
+ const present = new Set();
88
+ for (const entry of entries) {
89
+ if (!entry.isDirectory())
90
+ continue;
91
+ const file = join(this.stateDir, entry.name, 'events.jsonl');
92
+ present.add(file);
93
+ try {
94
+ await this.readNewLines(file, entry.name, oldest);
95
+ }
96
+ catch (error) {
97
+ if (!missingFile(error)) {
98
+ this.logger.warn({ err: error, sessionId: entry.name }, 'lettura della sessione Copilot fallita');
99
+ }
100
+ }
101
+ }
102
+ for (const file of this.files.keys()) {
103
+ if (!present.has(file))
104
+ this.files.delete(file);
105
+ }
106
+ for (const [requestId, seen] of this.seen) {
107
+ if (now - seen.at >= this.waitMs)
108
+ this.seen.delete(requestId);
109
+ }
110
+ for (const [requestId, pending] of this.pending) {
111
+ const hydraFusion = this.seen.get(requestId)?.hydraFusion;
112
+ if (hydraFusion === undefined)
113
+ continue;
114
+ if (hydraFusion) {
115
+ this.pending.delete(requestId);
116
+ this.onResolved({ ...pending.event, hydraFusion: true });
117
+ }
118
+ else if (!pending.reportedFalse) {
119
+ pending.reportedFalse = true;
120
+ this.onResolved({ ...pending.event, hydraFusion: false });
121
+ }
122
+ }
123
+ }
124
+ /**
125
+ * Legge solo le righe complete aggiunte dall'ultima scansione. Il modello
126
+ * selezionato resta noto finche' la lettura e' continua: dopo un salto torna
127
+ * sconosciuto, cosi' non si deduce una selezione diretta senza prove.
128
+ */
129
+ async readNewLines(file, sessionId, oldest) {
130
+ const info = await stat(file);
131
+ if (!info.isFile())
132
+ return;
133
+ let state = this.files.get(file);
134
+ if (state && info.size < state.offset)
135
+ state = undefined;
136
+ if (!state && info.mtimeMs < oldest)
137
+ return;
138
+ const from = state?.offset ?? 0;
139
+ if (!state || info.size - from > MAX_LOG_BYTES) {
140
+ const offset = Math.max(0, info.size - MAX_LOG_BYTES);
141
+ state = { offset, cursor: {}, skipPartialLine: offset > 0 };
142
+ this.files.set(file, state);
143
+ }
144
+ if (info.size === state.offset)
145
+ return;
146
+ const buffer = Buffer.alloc(info.size - state.offset);
147
+ const handle = await open(file, 'r');
148
+ let bytesRead;
149
+ try {
150
+ ({ bytesRead } = await handle.read(buffer, 0, buffer.length, state.offset));
151
+ }
152
+ finally {
153
+ await handle.close();
154
+ }
155
+ // Il newline e' un byte singolo anche in UTF-8: l'ultima riga incompleta si rilegge alla prossima scansione.
156
+ const end = buffer.subarray(0, bytesRead).lastIndexOf(0x0a);
157
+ if (end < 0)
158
+ return;
159
+ const lines = buffer.subarray(0, end).toString('utf-8').split('\n');
160
+ state.offset += end + 1;
161
+ if (state.skipPartialLine) {
162
+ lines.shift();
163
+ state.skipPartialLine = false;
164
+ }
165
+ const warnInvalid = () => {
166
+ if (this.warnedSessions.has(sessionId))
167
+ return;
168
+ this.warnedSessions.add(sessionId);
169
+ this.logger.warn({ sessionId }, 'record Copilot non valido: routing non determinabile');
170
+ };
171
+ const now = Date.now();
172
+ for (const call of sessionCalls(lines, '', sessionId, warnInvalid, state.cursor)) {
173
+ const hydraFusion = mergeHydraFusion(this.seen.get(call.requestId)?.hydraFusion, call.hydraFusion);
174
+ if (hydraFusion === null)
175
+ continue;
176
+ if (!this.seen.has(call.requestId) && this.seen.size >= MAX_PENDING)
177
+ continue;
178
+ this.seen.set(call.requestId, { hydraFusion, at: now });
179
+ }
180
+ }
181
+ }
182
+ //# sourceMappingURL=fusionCorrelator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fusionCorrelator.js","sourceRoot":"","sources":["../../src/usage/fusionCorrelator.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,8 @@ export { totalInputTokens };
16
16
  export interface ParsedUsage extends TokenUsage {
17
17
  /** Id modello dichiarato dalla risposta upstream, se presente. */
18
18
  model?: string;
19
+ /** Addebito GitHub della richiesta (`copilot_usage.total_nano_aiu`), se riportato. */
20
+ billedNanoAiu?: number;
19
21
  }
20
22
  export declare const EMPTY_USAGE: TokenUsage;
21
23
  export declare function isUsageEmpty(usage: TokenUsage): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/usage/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAsB,MAAM,kCAAkC,CAAC;AAgBxF,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAO5B,MAAM,CAAC,MAAM,WAAW,GAAe;IACrC,WAAW,EAAE,CAAC;IACd,iBAAiB,EAAE,CAAC;IACpB,gBAAgB,EAAE,CAAC;IACnB,YAAY,EAAE,CAAC;CAChB,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,OAAO,CACL,KAAK,CAAC,WAAW,KAAK,CAAC;QACvB,KAAK,CAAC,iBAAiB,KAAK,CAAC;QAC7B,KAAK,CAAC,gBAAgB,KAAK,CAAC;QAC5B,KAAK,CAAC,YAAY,KAAK,CAAC,CACzB,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,KAAc;IAC3B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,OAAO,EAAE,KAAK,IAAI,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/usage/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAsB,MAAM,kCAAkC,CAAC;AAgBxF,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAS5B,MAAM,CAAC,MAAM,WAAW,GAAe;IACrC,WAAW,EAAE,CAAC;IACd,iBAAiB,EAAE,CAAC;IACpB,gBAAgB,EAAE,CAAC;IACnB,YAAY,EAAE,CAAC;CAChB,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,OAAO,CACL,KAAK,CAAC,WAAW,KAAK,CAAC;QACvB,KAAK,CAAC,iBAAiB,KAAK,CAAC;QAC7B,KAAK,CAAC,gBAAgB,KAAK,CAAC;QAC5B,KAAK,CAAC,YAAY,KAAK,CAAC,CACzB,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,KAAc;IAC3B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,OAAO,EAAE,KAAK,IAAI,gBAAgB,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vidiemme/copilot-proxy",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "Proxy locale: misura il consumo di token di GitHub Copilot e lo attribuisce al progetto",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  "prepublishOnly": "npm run build"
39
39
  },
40
40
  "dependencies": {
41
- "@vidiemme/copilot-usage-contract": "^0.1.0",
41
+ "@vidiemme/copilot-usage-contract": "^0.1.4",
42
42
  "fastify": "^5.12.7",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "undici": "^8.12.3",