@dianshuv/copilot-api 0.16.0 → 0.18.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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.mjs +92 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -48,7 +48,7 @@ make down
48
48
  |---------|-------------|
49
49
  | `start` | Start the API server (handles auth if needed) |
50
50
  | `login` | Run GitHub authentication flow only |
51
- | `logout` | Remove stored GitHub token |
51
+ | `logout` | Remove the local GitHub token file (does not revoke authorization on GitHub) |
52
52
  | `debug` | Display diagnostic information |
53
53
 
54
54
  ### Start Command Options
package/dist/main.mjs CHANGED
@@ -997,6 +997,8 @@ async function runLogout() {
997
997
  throw error;
998
998
  }
999
999
  }
1000
+ consola.warn("This only removes the local token file. The OAuth App authorization is still active on GitHub and the token remains valid until revoked.");
1001
+ consola.info(`To fully revoke access, visit https://github.com/settings/connections/applications/${GITHUB_CLIENT_ID} and click "Revoke access".`);
1000
1002
  }
1001
1003
  const logout = defineCommand({
1002
1004
  meta: {
@@ -1010,7 +1012,7 @@ const logout = defineCommand({
1010
1012
 
1011
1013
  //#endregion
1012
1014
  //#region package.json
1013
- var version = "0.16.0";
1015
+ var version = "0.18.0";
1014
1016
 
1015
1017
  //#endregion
1016
1018
  //#region src/lib/event-loop-lag.ts
@@ -1592,6 +1594,8 @@ function captureRequest(params) {
1592
1594
  tool_count: params.toolCount
1593
1595
  };
1594
1596
  if (params.reasoningTokens !== void 0) properties.reasoning_tokens = params.reasoningTokens;
1597
+ if (params.cachedInputTokens !== void 0) properties.cached_input_tokens = params.cachedInputTokens;
1598
+ if (params.totalInputTokens !== void 0) properties.total_input_tokens = params.totalInputTokens;
1595
1599
  if (params.stopReason !== void 0) properties.stop_reason = params.stopReason;
1596
1600
  if (params.status !== void 0) properties.status = params.status;
1597
1601
  if (params.copilotErrorCode !== void 0) properties.copilot_error_code = params.copilotErrorCode;
@@ -2715,6 +2719,20 @@ function formatTokens(input, output) {
2715
2719
  return `${formatNumber(input)}/${formatNumber(output)}`;
2716
2720
  }
2717
2721
  /**
2722
+ * Cache-hit segment for the complete line, e.g. `cache=90%` (or
2723
+ * `cache=90% (1.6K/1.8K)` under --verbose). Returns undefined when there is no
2724
+ * usable denominator or the request had zero cache hits, so non-cached and
2725
+ * non-LLM requests stay terse (minimize-noise). `total` is the full prompt size
2726
+ * normalized across API families; see completeTracking / the per-path extraction.
2727
+ */
2728
+ function formatCacheHitRate(cached, total) {
2729
+ if (total === void 0 || total <= 0) return void 0;
2730
+ if (cached === void 0 || cached <= 0) return void 0;
2731
+ const pct = Math.round(cached / total * 100);
2732
+ if (consola.level >= 5) return `cache=${pct}% (${formatNumber(cached)}/${formatNumber(total)})`;
2733
+ return `cache=${pct}%`;
2734
+ }
2735
+ /**
2718
2736
  * Console renderer that shows request lifecycle with apt-get style footer
2719
2737
  *
2720
2738
  * Log format:
@@ -2816,7 +2834,7 @@ var ConsoleRenderer = class {
2816
2834
  * Format a complete log line with colored parts
2817
2835
  */
2818
2836
  formatLogLine(parts) {
2819
- const { prefix, time, method, path, model, status, duration, tokens, queueWait, phases, extra, isError, isDim } = parts;
2837
+ const { prefix, time, method, path, model, status, duration, tokens, cache, queueWait, phases, extra, isError, isDim } = parts;
2820
2838
  if (isDim) {
2821
2839
  const modelPart = model ? ` ${model}` : "";
2822
2840
  const extraPart = extra ? ` ${extra}` : "";
@@ -2830,6 +2848,7 @@ var ConsoleRenderer = class {
2830
2848
  if (duration) result += ` ${pc.yellow(duration)}`;
2831
2849
  if (queueWait) result += ` ${pc.dim(`(queued ${queueWait})`)}`;
2832
2850
  if (tokens) result += ` ${pc.blue(tokens)}`;
2851
+ if (cache) result += ` ${pc.dim(cache)}`;
2833
2852
  if (phases) result += ` ${pc.dim(phases)}`;
2834
2853
  if (extra) result += isError ? pc.red(extra) : extra;
2835
2854
  return result;
@@ -2901,6 +2920,7 @@ var ConsoleRenderer = class {
2901
2920
  const status = request.statusCode ?? 0;
2902
2921
  const isError = request.status === "error" || status >= 400;
2903
2922
  const tokens = request.model ? formatTokens(request.inputTokens, request.outputTokens) : void 0;
2923
+ const cache = formatCacheHitRate(request.cachedInputTokens, request.totalInputTokens);
2904
2924
  const queueWait = request.queueWaitMs && request.queueWaitMs > 100 ? formatDuration(request.queueWaitMs) : void 0;
2905
2925
  const message = this.formatLogLine({
2906
2926
  prefix: isError ? "[FAIL]" : "[ OK ]",
@@ -2912,6 +2932,7 @@ var ConsoleRenderer = class {
2912
2932
  duration: formatDuration(request.durationMs ?? 0),
2913
2933
  queueWait,
2914
2934
  tokens,
2935
+ cache,
2915
2936
  phases: this.formatPhases(request),
2916
2937
  extra: isError && request.error ? `: ${request.error}` : void 0,
2917
2938
  isError,
@@ -2980,6 +3001,8 @@ var RequestTracker = class {
2980
3001
  if (update.inputTokens !== void 0) request.inputTokens = update.inputTokens;
2981
3002
  if (update.outputTokens !== void 0) request.outputTokens = update.outputTokens;
2982
3003
  if (update.reasoningTokens !== void 0) request.reasoningTokens = update.reasoningTokens;
3004
+ if (update.cachedInputTokens !== void 0) request.cachedInputTokens = update.cachedInputTokens;
3005
+ if (update.totalInputTokens !== void 0) request.totalInputTokens = update.totalInputTokens;
2983
3006
  if (update.error !== void 0) request.error = update.error;
2984
3007
  if (update.queuePosition !== void 0) request.queuePosition = update.queuePosition;
2985
3008
  if (update.queueWaitMs !== void 0) request.queueWaitMs = update.queueWaitMs;
@@ -4147,13 +4170,15 @@ function updateTrackerStatus(trackingId, status) {
4147
4170
  requestTracker.updateRequest(trackingId, { status });
4148
4171
  }
4149
4172
  /** Complete TUI tracking and send PostHog analytics */
4150
- function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs, reasoningTokens, analytics, timings) {
4173
+ function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs, reasoningTokens, analytics, timings, cache) {
4151
4174
  if (!trackingId) return;
4152
4175
  requestTracker.updateRequest(trackingId, {
4153
4176
  inputTokens,
4154
4177
  outputTokens,
4155
4178
  queueWaitMs,
4156
4179
  reasoningTokens,
4180
+ cachedInputTokens: cache?.cachedInputTokens,
4181
+ totalInputTokens: cache?.totalInputTokens,
4157
4182
  ...timingsToUpdate(timings)
4158
4183
  });
4159
4184
  requestTracker.completeRequest(trackingId, 200, {
@@ -4170,6 +4195,8 @@ function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs, re
4170
4195
  stream: analytics.stream,
4171
4196
  toolCount: analytics.toolCount ?? 0,
4172
4197
  reasoningTokens,
4198
+ cachedInputTokens: cache?.cachedInputTokens,
4199
+ totalInputTokens: cache?.totalInputTokens,
4173
4200
  stopReason: analytics.stopReason
4174
4201
  });
4175
4202
  }
@@ -4265,6 +4292,9 @@ function formatClientTruncationMarker(result) {
4265
4292
  function getReasoningTokensFromOpenAIUsage(usage) {
4266
4293
  return usage?.completion_tokens_details?.reasoning_tokens;
4267
4294
  }
4295
+ function getCachedTokensFromOpenAIUsage(usage) {
4296
+ return usage?.prompt_tokens_details?.cached_tokens;
4297
+ }
4268
4298
  async function handleCompletion$1(c) {
4269
4299
  const rawPayload = await c.req.json();
4270
4300
  consola.debug("Request payload:", JSON.stringify(rawPayload).slice(-400));
@@ -4379,11 +4409,14 @@ function handleNonStreamingResponse$1(c, originalResponse, ctx, payload) {
4379
4409
  content: buildResponseContent(choice),
4380
4410
  toolCalls: extractToolCalls(choice)
4381
4411
  }, durationMs);
4412
+ const cachedInputTokens = usage ? getCachedTokensFromOpenAIUsage(usage) : void 0;
4382
4413
  if (ctx.trackingId && usage) requestTracker.updateRequest(ctx.trackingId, {
4383
4414
  inputTokens: usage.prompt_tokens,
4384
4415
  outputTokens: usage.completion_tokens,
4385
4416
  queueWaitMs: ctx.queueWaitMs,
4386
- reasoningTokens
4417
+ reasoningTokens,
4418
+ cachedInputTokens,
4419
+ totalInputTokens: usage.prompt_tokens
4387
4420
  });
4388
4421
  captureRequest({
4389
4422
  model: response.model,
@@ -4394,6 +4427,8 @@ function handleNonStreamingResponse$1(c, originalResponse, ctx, payload) {
4394
4427
  stream: false,
4395
4428
  toolCount: payload.tools?.length ?? 0,
4396
4429
  reasoningTokens,
4430
+ cachedInputTokens,
4431
+ totalInputTokens: usage?.prompt_tokens,
4397
4432
  stopReason: choice.finish_reason
4398
4433
  });
4399
4434
  return c.json(echoResponseBody(response, ctx));
@@ -4424,6 +4459,7 @@ function createStreamAccumulator() {
4424
4459
  model: "",
4425
4460
  inputTokens: 0,
4426
4461
  outputTokens: 0,
4462
+ cachedTokens: 0,
4427
4463
  reasoningTokens: 0,
4428
4464
  finishReason: "",
4429
4465
  content: "",
@@ -4467,7 +4503,10 @@ async function handleStreamingResponse$1(opts) {
4467
4503
  durationMs: Date.now() - ctx.startTime,
4468
4504
  stopReason: acc.finishReason || void 0,
4469
4505
  toolCount: payload.tools?.length ?? 0
4470
- }, ctx.timings);
4506
+ }, ctx.timings, {
4507
+ cachedInputTokens: acc.cachedTokens,
4508
+ totalInputTokens: acc.inputTokens
4509
+ });
4471
4510
  } catch (error) {
4472
4511
  if (isAbortError(error)) {
4473
4512
  consola.debug("[ChatCompletions] client disconnected mid-stream; upstream aborted");
@@ -4536,6 +4575,7 @@ function accumulateParsedChunk(parsed, acc, checkRepetition) {
4536
4575
  if (parsed.usage) {
4537
4576
  acc.inputTokens = parsed.usage.prompt_tokens;
4538
4577
  acc.outputTokens = parsed.usage.completion_tokens;
4578
+ acc.cachedTokens = parsed.usage.prompt_tokens_details?.cached_tokens ?? 0;
4539
4579
  acc.reasoningTokens = getReasoningTokensFromOpenAIUsage(parsed.usage) ?? 0;
4540
4580
  }
4541
4581
  const choice = parsed.choices?.[0];
@@ -7149,6 +7189,8 @@ function createAnthropicStreamAccumulator() {
7149
7189
  model: "",
7150
7190
  inputTokens: 0,
7151
7191
  outputTokens: 0,
7192
+ cacheReadInputTokens: 0,
7193
+ cacheCreationInputTokens: 0,
7152
7194
  stopReason: "",
7153
7195
  content: "",
7154
7196
  toolCalls: [],
@@ -7158,6 +7200,9 @@ function createAnthropicStreamAccumulator() {
7158
7200
  }
7159
7201
  function processAnthropicEvent(event, acc) {
7160
7202
  switch (event.type) {
7203
+ case "message_start":
7204
+ handleMessageStart(event.message.usage, acc);
7205
+ break;
7161
7206
  case "content_block_delta":
7162
7207
  handleContentBlockDelta(event.delta, acc);
7163
7208
  break;
@@ -7193,11 +7238,20 @@ function handleContentBlockStop(acc) {
7193
7238
  acc.currentToolCall = null;
7194
7239
  }
7195
7240
  }
7241
+ function applyPromptSideUsage(usage, acc) {
7242
+ if (usage.input_tokens !== void 0) acc.inputTokens = usage.input_tokens;
7243
+ if (usage.cache_read_input_tokens !== void 0) acc.cacheReadInputTokens = usage.cache_read_input_tokens;
7244
+ if (usage.cache_creation_input_tokens !== void 0) acc.cacheCreationInputTokens = usage.cache_creation_input_tokens;
7245
+ }
7246
+ function handleMessageStart(usage, acc) {
7247
+ if (!usage) return;
7248
+ applyPromptSideUsage(usage, acc);
7249
+ }
7196
7250
  function handleMessageDelta(delta, usage, acc) {
7197
7251
  if (delta.stop_reason) acc.stopReason = delta.stop_reason;
7198
7252
  if (usage) {
7199
- acc.inputTokens = usage.input_tokens ?? 0;
7200
7253
  acc.outputTokens = usage.output_tokens;
7254
+ applyPromptSideUsage(usage, acc);
7201
7255
  }
7202
7256
  }
7203
7257
  function recordAnthropicStreamingResponse(acc, fallbackModel, ctx) {
@@ -8400,10 +8454,15 @@ function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateRes
8400
8454
  },
8401
8455
  toolCalls: extractToolCallsFromContent(response.content)
8402
8456
  }, Date.now() - ctx.startTime);
8457
+ const cacheRead = response.usage.cache_read_input_tokens ?? 0;
8458
+ const cacheCreation = response.usage.cache_creation_input_tokens ?? 0;
8459
+ const totalInputTokens = response.usage.input_tokens + cacheRead + cacheCreation;
8403
8460
  if (ctx.trackingId) requestTracker.updateRequest(ctx.trackingId, {
8404
8461
  inputTokens: response.usage.input_tokens,
8405
8462
  outputTokens: response.usage.output_tokens,
8406
- queueWaitMs: ctx.queueWaitMs
8463
+ queueWaitMs: ctx.queueWaitMs,
8464
+ cachedInputTokens: cacheRead,
8465
+ totalInputTokens
8407
8466
  });
8408
8467
  captureRequest({
8409
8468
  model: response.model,
@@ -8413,6 +8472,8 @@ function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateRes
8413
8472
  success: true,
8414
8473
  stream: false,
8415
8474
  toolCount: payload.tools?.length ?? 0,
8475
+ cachedInputTokens: cacheRead,
8476
+ totalInputTokens,
8416
8477
  stopReason: response.stop_reason ?? void 0
8417
8478
  });
8418
8479
  let finalResponse = response;
@@ -8498,7 +8559,10 @@ async function handleDirectAnthropicStreamingResponse(opts) {
8498
8559
  durationMs: Date.now() - ctx.startTime,
8499
8560
  stopReason: acc.stopReason || void 0,
8500
8561
  toolCount: anthropicPayload.tools?.length ?? 0
8501
- }, ctx.timings);
8562
+ }, ctx.timings, {
8563
+ cachedInputTokens: acc.cacheReadInputTokens,
8564
+ totalInputTokens: acc.inputTokens + acc.cacheReadInputTokens + acc.cacheCreationInputTokens
8565
+ });
8502
8566
  } catch (error) {
8503
8567
  if (isAbortError(error)) {
8504
8568
  consola.debug("[Anthropic] client disconnected mid-stream; upstream aborted");
@@ -8713,10 +8777,15 @@ function handleNonStreamingResponse(opts) {
8713
8777
  },
8714
8778
  toolCalls: extractToolCallsFromContent(anthropicResponse.content)
8715
8779
  }, Date.now() - ctx.startTime);
8780
+ const cacheRead = anthropicResponse.usage.cache_read_input_tokens ?? 0;
8781
+ const cacheCreation = anthropicResponse.usage.cache_creation_input_tokens ?? 0;
8782
+ const totalInputTokens = anthropicResponse.usage.input_tokens + cacheRead + cacheCreation;
8716
8783
  if (ctx.trackingId) requestTracker.updateRequest(ctx.trackingId, {
8717
8784
  inputTokens: anthropicResponse.usage.input_tokens,
8718
8785
  outputTokens: anthropicResponse.usage.output_tokens,
8719
- queueWaitMs: ctx.queueWaitMs
8786
+ queueWaitMs: ctx.queueWaitMs,
8787
+ cachedInputTokens: cacheRead,
8788
+ totalInputTokens
8720
8789
  });
8721
8790
  captureRequest({
8722
8791
  model: anthropicResponse.model,
@@ -8726,6 +8795,8 @@ function handleNonStreamingResponse(opts) {
8726
8795
  success: true,
8727
8796
  stream: false,
8728
8797
  toolCount: anthropicPayload.tools?.length ?? 0,
8798
+ cachedInputTokens: cacheRead,
8799
+ totalInputTokens,
8729
8800
  stopReason: anthropicResponse.stop_reason ?? void 0
8730
8801
  });
8731
8802
  return c.json(echoResponseBody(anthropicResponse, ctx));
@@ -8762,7 +8833,10 @@ async function handleStreamingResponse(opts) {
8762
8833
  durationMs: Date.now() - ctx.startTime,
8763
8834
  stopReason: acc.stopReason || void 0,
8764
8835
  toolCount: anthropicPayload.tools?.length ?? 0
8765
- }, ctx.timings);
8836
+ }, ctx.timings, {
8837
+ cachedInputTokens: acc.cacheReadInputTokens,
8838
+ totalInputTokens: acc.inputTokens + acc.cacheReadInputTokens + acc.cacheCreationInputTokens
8839
+ });
8766
8840
  } catch (error) {
8767
8841
  if (isAbortError(error)) {
8768
8842
  consola.debug("[Translated] client disconnected mid-stream; upstream aborted");
@@ -9395,7 +9469,10 @@ const handleResponses = async (c) => {
9395
9469
  stream: true,
9396
9470
  durationMs: Date.now() - startTime,
9397
9471
  toolCount: tools.length
9398
- }, ctx.timings);
9472
+ }, ctx.timings, {
9473
+ cachedInputTokens: usage?.input_tokens_details?.cached_tokens ?? 0,
9474
+ totalInputTokens: usage?.input_tokens ?? 0
9475
+ });
9399
9476
  } else if (streamErrorMessage) {
9400
9477
  recordResponse(historyId, {
9401
9478
  success: false,
@@ -9446,7 +9523,10 @@ const handleResponses = async (c) => {
9446
9523
  stream: false,
9447
9524
  durationMs: Date.now() - startTime,
9448
9525
  toolCount: tools.length
9449
- }, ctx.timings);
9526
+ }, ctx.timings, {
9527
+ cachedInputTokens: usage?.input_tokens_details?.cached_tokens ?? 0,
9528
+ totalInputTokens: usage?.input_tokens ?? 0
9529
+ });
9450
9530
  consola.debug("Forwarding native Responses result:", JSON.stringify(result).slice(-400));
9451
9531
  return c.json(echoResponseBody(result, ctx));
9452
9532
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dianshuv/copilot-api",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
5
5
  "author": "dianshuv",
6
6
  "type": "module",