@openhoo/hoopilot 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,3 +1,44 @@
1
+ /** Content-Type for the Prometheus text exposition format (version 0.0.4). */
2
+ declare const PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
3
+ /**
4
+ * In-process metrics for the running proxy. Counters are monotonic for the life
5
+ * of the process and reset on restart, which Prometheus handles natively. The
6
+ * registry is intentionally allocation-light and synchronous; the single-
7
+ * threaded event loop makes its mutations atomic with respect to each request.
8
+ */
9
+ declare class MetricsRegistry {
10
+ #private;
11
+ constructor(options?: {
12
+ now?: () => number;
13
+ });
14
+ /** Mark a request as started; pair with exactly one {@link observe}. */
15
+ startRequest(): void;
16
+ /** Record a completed request and clear its in-flight slot. */
17
+ observe(observation: RequestObservation): void;
18
+ /** Accumulate token counts for a model from one upstream completion. */
19
+ recordTokens(model: string, usage: TokenUsage): void;
20
+ /** Record one upstream Copilot call and whether it succeeded. */
21
+ recordUpstream(path: string, ok: boolean): void;
22
+ /** Store the latest Copilot quota so /metrics can expose it as gauges. */
23
+ recordCopilotQuota(usage: CopilotUsage): void;
24
+ /** A JSON-friendly view of the current counters. */
25
+ snapshot(now?: () => number): MetricsSnapshot;
26
+ /** Render the Prometheus text exposition format (version 0.0.4). */
27
+ renderPrometheus(now?: () => number): string;
28
+ }
29
+ /**
30
+ * Tee `response`'s body so the client receives an unchanged copy while a
31
+ * background reader extracts token usage. Returns a new Response carrying the
32
+ * client-facing branch and the original status/headers. Usage extraction never
33
+ * throws into the client stream: a parse failure or an aborted client simply
34
+ * yields no usage. When the body is absent the response is returned untouched.
35
+ *
36
+ * Pass the request's `signal` so a client disconnect cancels the observer
37
+ * branch; combined with the runtime cancelling the client branch, that releases
38
+ * the shared upstream connection instead of draining it in the background.
39
+ */
40
+ declare function observeResponseUsage(response: Response, fallbackModel: string, onUsage: (model: string, usage: TokenUsage) => void, signal?: AbortSignal): Response;
41
+
1
42
  type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
2
43
  interface Logger {
3
44
  info(message: string): void;
@@ -35,6 +76,7 @@ interface CopilotAuthOptions {
35
76
  copilotApiBaseUrl?: string;
36
77
  env?: NodeJS.ProcessEnv;
37
78
  fetch?: FetchLike;
79
+ githubApiBaseUrl?: string;
38
80
  }
39
81
  interface CopilotAccess {
40
82
  apiBaseUrl: string;
@@ -49,6 +91,7 @@ interface HoopilotServerOptions extends CopilotAuthOptions {
49
91
  logger?: HoopilotLogger;
50
92
  logFormat?: LogFormat | string;
51
93
  logLevel?: LogLevel | string;
94
+ metrics?: MetricsRegistry;
52
95
  port?: number;
53
96
  }
54
97
  interface StartedHoopilotServer {
@@ -56,6 +99,71 @@ interface StartedHoopilotServer {
56
99
  url: string;
57
100
  }
58
101
  type JsonObject = Record<string, unknown>;
102
+ /** Normalized token usage extracted from an upstream OpenAI/Copilot response. */
103
+ interface TokenUsage {
104
+ cachedTokens?: number;
105
+ completionTokens: number;
106
+ promptTokens: number;
107
+ reasoningTokens?: number;
108
+ totalTokens: number;
109
+ }
110
+ /** Per-model token totals accumulated by the metrics registry. */
111
+ interface ModelTokenTotals {
112
+ cached: number;
113
+ completion: number;
114
+ prompt: number;
115
+ reasoning: number;
116
+ requests: number;
117
+ total: number;
118
+ }
119
+ /** A single completed request's facts, recorded into the metrics registry. */
120
+ interface RequestObservation {
121
+ durationMs: number;
122
+ method: string;
123
+ route: string;
124
+ status: number;
125
+ }
126
+ /** One quota category (chat, completions, or premium_interactions/credits). */
127
+ interface CopilotQuota {
128
+ entitlement?: number;
129
+ overageCount?: number;
130
+ overagePermitted?: boolean;
131
+ percentRemaining?: number;
132
+ remaining?: number;
133
+ unlimited?: boolean;
134
+ used?: number;
135
+ }
136
+ /** A GitHub Copilot account's plan and quota snapshot. */
137
+ interface CopilotUsage {
138
+ accessTypeSku?: string;
139
+ chatEnabled?: boolean;
140
+ plan?: string;
141
+ quotaResetDate?: string;
142
+ quotas: Record<string, CopilotQuota>;
143
+ }
144
+ /** A point-in-time JSON view of the in-process metrics. */
145
+ interface MetricsSnapshot {
146
+ inFlight: number;
147
+ requests: {
148
+ byRoute: Record<string, number>;
149
+ byStatus: Record<string, number>;
150
+ total: number;
151
+ };
152
+ startedAt: string;
153
+ tokens: {
154
+ byModel: Record<string, ModelTokenTotals>;
155
+ cached: number;
156
+ completion: number;
157
+ prompt: number;
158
+ reasoning: number;
159
+ total: number;
160
+ };
161
+ upstream: {
162
+ errors: number;
163
+ total: number;
164
+ };
165
+ uptimeSeconds: number;
166
+ }
59
167
 
60
168
  declare class CopilotAuthError extends Error {
61
169
  constructor(message: string);
@@ -77,14 +185,50 @@ declare function authStorePath(env?: NodeJS.ProcessEnv): string;
77
185
  declare function readStoredCopilotAuth(path?: string): StoredCopilotAuth | undefined;
78
186
  declare function writeStoredCopilotAuth(auth: StoredCopilotAuth, path?: string): void;
79
187
 
188
+ /** Default GitHub REST host that serves the `copilot_internal/user` quota route. */
189
+ declare const DEFAULT_GITHUB_API_BASE_URL = "https://api.github.com";
190
+ /**
191
+ * API version sent to the GitHub `copilot_internal` endpoints. This is a
192
+ * different surface from the Copilot completions API (`x-github-api-version`
193
+ * `2026-06-01`), so it is pinned separately and bumped independently.
194
+ */
195
+ declare const COPILOT_USAGE_API_VERSION = "2025-04-01";
196
+ /**
197
+ * Set the GitHub Copilot API request headers on `headers`, leaving any
198
+ * caller-provided `accept` intact. Single source of truth for the pinned
199
+ * integration id, editor/plugin versions, and API version so the proxy client
200
+ * and the login-time verification call cannot drift apart.
201
+ */
202
+ declare function applyCopilotHeaders(headers: Headers, token: string): Headers;
203
+ /**
204
+ * Set headers for the GitHub REST `copilot_internal/user` quota call. This host
205
+ * is `api.github.com` (not the Copilot API host) and expects the `token` auth
206
+ * scheme with the raw stored OAuth token — not the `Bearer` scheme used by the
207
+ * Copilot completion endpoints.
208
+ */
209
+ declare function applyGithubApiHeaders(headers: Headers, token: string): Headers;
80
210
  declare class CopilotClient {
81
211
  #private;
82
212
  constructor(options?: CopilotAuthOptions);
213
+ /**
214
+ * Fetch the Copilot account's quota / premium-request usage from the GitHub
215
+ * REST `copilot_internal/user` endpoint. The stored device-flow OAuth token is
216
+ * accepted directly here — no Copilot token exchange is required to read quota.
217
+ */
218
+ usage(signal?: AbortSignal): Promise<Response>;
83
219
  chatCompletions(body: JsonObject, signal?: AbortSignal): Promise<Response>;
84
220
  responses(body: string, signal?: AbortSignal): Promise<Response>;
85
221
  models(signal?: AbortSignal): Promise<Response>;
86
222
  fetchCopilot(path: string, init: RequestInit): Promise<Response>;
87
223
  }
224
+ /**
225
+ * Normalize a `copilot_internal/user` response into {@link CopilotUsage}. Handles
226
+ * both the paid-plan shape (`quota_snapshots.{chat,completions,premium_interactions}`)
227
+ * and the free-plan shape (`limited_user_quotas` remaining + `monthly_quotas`
228
+ * allowance). `remaining` may be fractional and negative under permitted overage,
229
+ * so `used` is derived as `max(0, entitlement - remaining)`.
230
+ */
231
+ declare function normalizeCopilotUsage(body: unknown): CopilotUsage;
88
232
 
89
233
  interface GithubCopilotDeviceLoginOptions {
90
234
  clientId?: string;
@@ -122,8 +266,16 @@ declare function chatCompletionToCompletion(completion: JsonObject): JsonObject;
122
266
  declare function normalizeModelsResponse(upstream: unknown): JsonObject;
123
267
  declare function fallbackModels(): Array<JsonObject>;
124
268
  declare function responsesStreamFromChatStream(chatStream: ReadableStream<Uint8Array>, options: ResponseStreamOptions): ReadableStream<Uint8Array>;
269
+ /**
270
+ * Normalize an upstream `usage` object into {@link TokenUsage}. Accepts both the
271
+ * Chat Completions shape (`prompt_tokens`/`completion_tokens`) and the Responses
272
+ * shape (`input_tokens`/`output_tokens`), and pulls nested reasoning/cached
273
+ * details when present. Returns undefined when no token counts are available so
274
+ * callers can distinguish "no usage reported" from "zero tokens".
275
+ */
276
+ declare function extractTokenUsage(usage: unknown): TokenUsage | undefined;
125
277
 
126
278
  declare function createHoopilotHandler(options?: HoopilotServerOptions): (request: Request) => Promise<Response>;
127
279
  declare function startHoopilotServer(options?: HoopilotServerOptions): StartedHoopilotServer;
128
280
 
129
- export { type CopilotAccess, CopilotAuth, CopilotAuthError, type CopilotAuthOptions, CopilotClient, DEFAULT_LOG_FORMAT, DEFAULT_LOG_LEVEL, DEFAULT_MODEL, type FetchLike, type HoopilotLogger, type HoopilotLoggerOptions, type HoopilotServerOptions, type JsonObject, type LogFields, type LogFormat, type LogLevel, type LogMethod, type Logger, type StartedHoopilotServer, authStorePath, chatCompletionToCompletion, chatCompletionToResponse, completionsRequestToChatCompletion, createHoopilotHandler, createHoopilotLogger, fallbackModels, githubCopilotDeviceLogin, noopLogger, normalizeChatCompletionRequest, normalizeModelsResponse, normalizeRequestedModel, parseLogFormat, parseLogLevel, readStoredCopilotAuth, responsesRequestToChatCompletion, responsesStreamFromChatStream, startHoopilotServer, writeStoredCopilotAuth };
281
+ export { COPILOT_USAGE_API_VERSION, type CopilotAccess, CopilotAuth, CopilotAuthError, type CopilotAuthOptions, CopilotClient, type CopilotQuota, type CopilotUsage, DEFAULT_GITHUB_API_BASE_URL, DEFAULT_LOG_FORMAT, DEFAULT_LOG_LEVEL, DEFAULT_MODEL, type FetchLike, type HoopilotLogger, type HoopilotLoggerOptions, type HoopilotServerOptions, type JsonObject, type LogFields, type LogFormat, type LogLevel, type LogMethod, type Logger, MetricsRegistry, type MetricsSnapshot, type ModelTokenTotals, PROMETHEUS_CONTENT_TYPE, type RequestObservation, type StartedHoopilotServer, type TokenUsage, applyCopilotHeaders, applyGithubApiHeaders, authStorePath, chatCompletionToCompletion, chatCompletionToResponse, completionsRequestToChatCompletion, createHoopilotHandler, createHoopilotLogger, extractTokenUsage, fallbackModels, githubCopilotDeviceLogin, noopLogger, normalizeChatCompletionRequest, normalizeCopilotUsage, normalizeModelsResponse, normalizeRequestedModel, observeResponseUsage, parseLogFormat, parseLogLevel, readStoredCopilotAuth, responsesRequestToChatCompletion, responsesStreamFromChatStream, startHoopilotServer, writeStoredCopilotAuth };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,44 @@
1
+ /** Content-Type for the Prometheus text exposition format (version 0.0.4). */
2
+ declare const PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
3
+ /**
4
+ * In-process metrics for the running proxy. Counters are monotonic for the life
5
+ * of the process and reset on restart, which Prometheus handles natively. The
6
+ * registry is intentionally allocation-light and synchronous; the single-
7
+ * threaded event loop makes its mutations atomic with respect to each request.
8
+ */
9
+ declare class MetricsRegistry {
10
+ #private;
11
+ constructor(options?: {
12
+ now?: () => number;
13
+ });
14
+ /** Mark a request as started; pair with exactly one {@link observe}. */
15
+ startRequest(): void;
16
+ /** Record a completed request and clear its in-flight slot. */
17
+ observe(observation: RequestObservation): void;
18
+ /** Accumulate token counts for a model from one upstream completion. */
19
+ recordTokens(model: string, usage: TokenUsage): void;
20
+ /** Record one upstream Copilot call and whether it succeeded. */
21
+ recordUpstream(path: string, ok: boolean): void;
22
+ /** Store the latest Copilot quota so /metrics can expose it as gauges. */
23
+ recordCopilotQuota(usage: CopilotUsage): void;
24
+ /** A JSON-friendly view of the current counters. */
25
+ snapshot(now?: () => number): MetricsSnapshot;
26
+ /** Render the Prometheus text exposition format (version 0.0.4). */
27
+ renderPrometheus(now?: () => number): string;
28
+ }
29
+ /**
30
+ * Tee `response`'s body so the client receives an unchanged copy while a
31
+ * background reader extracts token usage. Returns a new Response carrying the
32
+ * client-facing branch and the original status/headers. Usage extraction never
33
+ * throws into the client stream: a parse failure or an aborted client simply
34
+ * yields no usage. When the body is absent the response is returned untouched.
35
+ *
36
+ * Pass the request's `signal` so a client disconnect cancels the observer
37
+ * branch; combined with the runtime cancelling the client branch, that releases
38
+ * the shared upstream connection instead of draining it in the background.
39
+ */
40
+ declare function observeResponseUsage(response: Response, fallbackModel: string, onUsage: (model: string, usage: TokenUsage) => void, signal?: AbortSignal): Response;
41
+
1
42
  type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
2
43
  interface Logger {
3
44
  info(message: string): void;
@@ -35,6 +76,7 @@ interface CopilotAuthOptions {
35
76
  copilotApiBaseUrl?: string;
36
77
  env?: NodeJS.ProcessEnv;
37
78
  fetch?: FetchLike;
79
+ githubApiBaseUrl?: string;
38
80
  }
39
81
  interface CopilotAccess {
40
82
  apiBaseUrl: string;
@@ -49,6 +91,7 @@ interface HoopilotServerOptions extends CopilotAuthOptions {
49
91
  logger?: HoopilotLogger;
50
92
  logFormat?: LogFormat | string;
51
93
  logLevel?: LogLevel | string;
94
+ metrics?: MetricsRegistry;
52
95
  port?: number;
53
96
  }
54
97
  interface StartedHoopilotServer {
@@ -56,6 +99,71 @@ interface StartedHoopilotServer {
56
99
  url: string;
57
100
  }
58
101
  type JsonObject = Record<string, unknown>;
102
+ /** Normalized token usage extracted from an upstream OpenAI/Copilot response. */
103
+ interface TokenUsage {
104
+ cachedTokens?: number;
105
+ completionTokens: number;
106
+ promptTokens: number;
107
+ reasoningTokens?: number;
108
+ totalTokens: number;
109
+ }
110
+ /** Per-model token totals accumulated by the metrics registry. */
111
+ interface ModelTokenTotals {
112
+ cached: number;
113
+ completion: number;
114
+ prompt: number;
115
+ reasoning: number;
116
+ requests: number;
117
+ total: number;
118
+ }
119
+ /** A single completed request's facts, recorded into the metrics registry. */
120
+ interface RequestObservation {
121
+ durationMs: number;
122
+ method: string;
123
+ route: string;
124
+ status: number;
125
+ }
126
+ /** One quota category (chat, completions, or premium_interactions/credits). */
127
+ interface CopilotQuota {
128
+ entitlement?: number;
129
+ overageCount?: number;
130
+ overagePermitted?: boolean;
131
+ percentRemaining?: number;
132
+ remaining?: number;
133
+ unlimited?: boolean;
134
+ used?: number;
135
+ }
136
+ /** A GitHub Copilot account's plan and quota snapshot. */
137
+ interface CopilotUsage {
138
+ accessTypeSku?: string;
139
+ chatEnabled?: boolean;
140
+ plan?: string;
141
+ quotaResetDate?: string;
142
+ quotas: Record<string, CopilotQuota>;
143
+ }
144
+ /** A point-in-time JSON view of the in-process metrics. */
145
+ interface MetricsSnapshot {
146
+ inFlight: number;
147
+ requests: {
148
+ byRoute: Record<string, number>;
149
+ byStatus: Record<string, number>;
150
+ total: number;
151
+ };
152
+ startedAt: string;
153
+ tokens: {
154
+ byModel: Record<string, ModelTokenTotals>;
155
+ cached: number;
156
+ completion: number;
157
+ prompt: number;
158
+ reasoning: number;
159
+ total: number;
160
+ };
161
+ upstream: {
162
+ errors: number;
163
+ total: number;
164
+ };
165
+ uptimeSeconds: number;
166
+ }
59
167
 
60
168
  declare class CopilotAuthError extends Error {
61
169
  constructor(message: string);
@@ -77,14 +185,50 @@ declare function authStorePath(env?: NodeJS.ProcessEnv): string;
77
185
  declare function readStoredCopilotAuth(path?: string): StoredCopilotAuth | undefined;
78
186
  declare function writeStoredCopilotAuth(auth: StoredCopilotAuth, path?: string): void;
79
187
 
188
+ /** Default GitHub REST host that serves the `copilot_internal/user` quota route. */
189
+ declare const DEFAULT_GITHUB_API_BASE_URL = "https://api.github.com";
190
+ /**
191
+ * API version sent to the GitHub `copilot_internal` endpoints. This is a
192
+ * different surface from the Copilot completions API (`x-github-api-version`
193
+ * `2026-06-01`), so it is pinned separately and bumped independently.
194
+ */
195
+ declare const COPILOT_USAGE_API_VERSION = "2025-04-01";
196
+ /**
197
+ * Set the GitHub Copilot API request headers on `headers`, leaving any
198
+ * caller-provided `accept` intact. Single source of truth for the pinned
199
+ * integration id, editor/plugin versions, and API version so the proxy client
200
+ * and the login-time verification call cannot drift apart.
201
+ */
202
+ declare function applyCopilotHeaders(headers: Headers, token: string): Headers;
203
+ /**
204
+ * Set headers for the GitHub REST `copilot_internal/user` quota call. This host
205
+ * is `api.github.com` (not the Copilot API host) and expects the `token` auth
206
+ * scheme with the raw stored OAuth token — not the `Bearer` scheme used by the
207
+ * Copilot completion endpoints.
208
+ */
209
+ declare function applyGithubApiHeaders(headers: Headers, token: string): Headers;
80
210
  declare class CopilotClient {
81
211
  #private;
82
212
  constructor(options?: CopilotAuthOptions);
213
+ /**
214
+ * Fetch the Copilot account's quota / premium-request usage from the GitHub
215
+ * REST `copilot_internal/user` endpoint. The stored device-flow OAuth token is
216
+ * accepted directly here — no Copilot token exchange is required to read quota.
217
+ */
218
+ usage(signal?: AbortSignal): Promise<Response>;
83
219
  chatCompletions(body: JsonObject, signal?: AbortSignal): Promise<Response>;
84
220
  responses(body: string, signal?: AbortSignal): Promise<Response>;
85
221
  models(signal?: AbortSignal): Promise<Response>;
86
222
  fetchCopilot(path: string, init: RequestInit): Promise<Response>;
87
223
  }
224
+ /**
225
+ * Normalize a `copilot_internal/user` response into {@link CopilotUsage}. Handles
226
+ * both the paid-plan shape (`quota_snapshots.{chat,completions,premium_interactions}`)
227
+ * and the free-plan shape (`limited_user_quotas` remaining + `monthly_quotas`
228
+ * allowance). `remaining` may be fractional and negative under permitted overage,
229
+ * so `used` is derived as `max(0, entitlement - remaining)`.
230
+ */
231
+ declare function normalizeCopilotUsage(body: unknown): CopilotUsage;
88
232
 
89
233
  interface GithubCopilotDeviceLoginOptions {
90
234
  clientId?: string;
@@ -122,8 +266,16 @@ declare function chatCompletionToCompletion(completion: JsonObject): JsonObject;
122
266
  declare function normalizeModelsResponse(upstream: unknown): JsonObject;
123
267
  declare function fallbackModels(): Array<JsonObject>;
124
268
  declare function responsesStreamFromChatStream(chatStream: ReadableStream<Uint8Array>, options: ResponseStreamOptions): ReadableStream<Uint8Array>;
269
+ /**
270
+ * Normalize an upstream `usage` object into {@link TokenUsage}. Accepts both the
271
+ * Chat Completions shape (`prompt_tokens`/`completion_tokens`) and the Responses
272
+ * shape (`input_tokens`/`output_tokens`), and pulls nested reasoning/cached
273
+ * details when present. Returns undefined when no token counts are available so
274
+ * callers can distinguish "no usage reported" from "zero tokens".
275
+ */
276
+ declare function extractTokenUsage(usage: unknown): TokenUsage | undefined;
125
277
 
126
278
  declare function createHoopilotHandler(options?: HoopilotServerOptions): (request: Request) => Promise<Response>;
127
279
  declare function startHoopilotServer(options?: HoopilotServerOptions): StartedHoopilotServer;
128
280
 
129
- export { type CopilotAccess, CopilotAuth, CopilotAuthError, type CopilotAuthOptions, CopilotClient, DEFAULT_LOG_FORMAT, DEFAULT_LOG_LEVEL, DEFAULT_MODEL, type FetchLike, type HoopilotLogger, type HoopilotLoggerOptions, type HoopilotServerOptions, type JsonObject, type LogFields, type LogFormat, type LogLevel, type LogMethod, type Logger, type StartedHoopilotServer, authStorePath, chatCompletionToCompletion, chatCompletionToResponse, completionsRequestToChatCompletion, createHoopilotHandler, createHoopilotLogger, fallbackModels, githubCopilotDeviceLogin, noopLogger, normalizeChatCompletionRequest, normalizeModelsResponse, normalizeRequestedModel, parseLogFormat, parseLogLevel, readStoredCopilotAuth, responsesRequestToChatCompletion, responsesStreamFromChatStream, startHoopilotServer, writeStoredCopilotAuth };
281
+ export { COPILOT_USAGE_API_VERSION, type CopilotAccess, CopilotAuth, CopilotAuthError, type CopilotAuthOptions, CopilotClient, type CopilotQuota, type CopilotUsage, DEFAULT_GITHUB_API_BASE_URL, DEFAULT_LOG_FORMAT, DEFAULT_LOG_LEVEL, DEFAULT_MODEL, type FetchLike, type HoopilotLogger, type HoopilotLoggerOptions, type HoopilotServerOptions, type JsonObject, type LogFields, type LogFormat, type LogLevel, type LogMethod, type Logger, MetricsRegistry, type MetricsSnapshot, type ModelTokenTotals, PROMETHEUS_CONTENT_TYPE, type RequestObservation, type StartedHoopilotServer, type TokenUsage, applyCopilotHeaders, applyGithubApiHeaders, authStorePath, chatCompletionToCompletion, chatCompletionToResponse, completionsRequestToChatCompletion, createHoopilotHandler, createHoopilotLogger, extractTokenUsage, fallbackModels, githubCopilotDeviceLogin, noopLogger, normalizeChatCompletionRequest, normalizeCopilotUsage, normalizeModelsResponse, normalizeRequestedModel, observeResponseUsage, parseLogFormat, parseLogLevel, readStoredCopilotAuth, responsesRequestToChatCompletion, responsesStreamFromChatStream, startHoopilotServer, writeStoredCopilotAuth };