ghc-proxy 0.5.6 → 0.5.7

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 CHANGED
@@ -489,3 +489,20 @@ bun run matrix:live --stateful-only --json --model=gpt-5.2-codex
489
489
  > - `--stateful-only`: run follow-up/resource probes such as `previous_response_id`, `input_tokens`, and `input_items`
490
490
  > - `--all-responses-models`: scan every model that advertises `/responses`
491
491
  > - `--model=<id>`: pin the Responses scan to one specific model
492
+
493
+ ### Tool Support Probe
494
+
495
+ Tests which server-side tool types (bash, text_editor, web_search, memory, etc.) each Copilot model actually accepts. Useful for tracking backend changes over time.
496
+
497
+ ```bash
498
+ bun scripts/probe-all-copilot-tools.ts # human-readable table
499
+ bun scripts/probe-all-copilot-tools.ts --json # JSON snapshot to stdout
500
+ bun scripts/probe-all-copilot-tools.ts --model=claude-opus-4.6 # single model
501
+ ```
502
+
503
+ The JSON output is designed for weekly diffing — `generatedAt` is the only volatile field:
504
+
505
+ ```bash
506
+ # Compare two weekly snapshots
507
+ diff <(jq -S 'del(.generatedAt)' week1.json) <(jq -S 'del(.generatedAt)' week2.json)
508
+ ```
package/dist/main.mjs CHANGED
@@ -6340,6 +6340,33 @@ async function cacheVSCodeVersion() {
6340
6340
  consola.debug(`Using VSCode version: ${response}`);
6341
6341
  }
6342
6342
 
6343
+ //#endregion
6344
+ //#region src/lib/retry.ts
6345
+ /**
6346
+ * Retry an async operation with exponential backoff.
6347
+ * Delay schedule: baseDelayMs * 2^attempt (0-indexed), e.g. 5s, 10s, 20s, 40s.
6348
+ */
6349
+ async function retryWithBackoff(fn, options) {
6350
+ const maxRetries = options?.maxRetries ?? 4;
6351
+ const baseDelayMs = options?.baseDelayMs ?? 5e3;
6352
+ const shouldRetry = options?.shouldRetry ?? (() => true);
6353
+ const onRetry = options?.onRetry;
6354
+ let lastError;
6355
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
6356
+ return await fn();
6357
+ } catch (error) {
6358
+ lastError = error;
6359
+ if (!shouldRetry(error) || attempt >= maxRetries) throw error;
6360
+ const delay = baseDelayMs * 2 ** attempt;
6361
+ onRetry?.(error, attempt, delay);
6362
+ await sleep(delay);
6363
+ }
6364
+ throw lastError;
6365
+ }
6366
+ function formatErrorMessage(error) {
6367
+ return error instanceof Error ? error.message : String(error);
6368
+ }
6369
+
6343
6370
  //#endregion
6344
6371
  //#region src/lib/token.ts
6345
6372
  const TRAILING_SLASHES_RE = /\/+$/;
@@ -6354,20 +6381,28 @@ async function setupCopilotToken() {
6354
6381
  consola.debug("GitHub Copilot Token fetched successfully!");
6355
6382
  if (state.config.showToken) consola.info("Copilot token:", response.token);
6356
6383
  const refreshInterval = (response.refresh_in - 60) * 1e3;
6357
- const refreshCopilotToken = async () => {
6358
- consola.debug("Refreshing Copilot token");
6359
- try {
6360
- const refreshed = await githubClient.getCopilotToken();
6361
- applyCopilotTokenState(refreshed);
6362
- consola.debug("Copilot token refreshed");
6363
- if (state.config.showToken) consola.info("Refreshed Copilot token:", refreshed.token);
6364
- } catch (error) {
6365
- consola.error("Failed to refresh Copilot token:", error);
6366
- }
6384
+ const scheduleRefresh = () => {
6385
+ setTimeout(() => {
6386
+ refreshCopilotToken(githubClient).then(scheduleRefresh);
6387
+ }, refreshInterval);
6367
6388
  };
6368
- setInterval(() => {
6369
- refreshCopilotToken();
6370
- }, refreshInterval);
6389
+ scheduleRefresh();
6390
+ }
6391
+ async function refreshCopilotToken(githubClient) {
6392
+ consola.debug("Refreshing Copilot token");
6393
+ try {
6394
+ const refreshed = await retryWithBackoff(() => githubClient.getCopilotToken(), {
6395
+ shouldRetry: (error) => !(error instanceof HTTPError) || isTransientHttpError(error),
6396
+ onRetry: (error, attempt, delayMs) => {
6397
+ consola.warn(`Token refresh failed (attempt ${attempt + 1}), retrying in ${delayMs / 1e3}s:`, formatErrorMessage(error));
6398
+ }
6399
+ });
6400
+ applyCopilotTokenState(refreshed);
6401
+ consola.debug("Copilot token refreshed");
6402
+ if (state.config.showToken) consola.info("Refreshed Copilot token:", refreshed.token);
6403
+ } catch (error) {
6404
+ consola.error("Failed to refresh Copilot token:", error);
6405
+ }
6371
6406
  }
6372
6407
  async function setupGitHubToken(options) {
6373
6408
  try {
@@ -6416,6 +6451,17 @@ async function setupGitHubToken(options) {
6416
6451
  function isAuthError(error) {
6417
6452
  return error instanceof HTTPError && (error.status === 401 || error.status === 403);
6418
6453
  }
6454
+ const TRANSIENT_HTTP_STATUSES = new Set([
6455
+ 408,
6456
+ 429,
6457
+ 500,
6458
+ 502,
6459
+ 503,
6460
+ 504
6461
+ ]);
6462
+ function isTransientHttpError(error) {
6463
+ return TRANSIENT_HTTP_STATUSES.has(error.status);
6464
+ }
6419
6465
  async function logUser() {
6420
6466
  const user = await createGitHubClient().getGitHubUser();
6421
6467
  state.cache.githubLogin = user.login;
@@ -6533,7 +6579,7 @@ const checkUsage = defineCommand({
6533
6579
 
6534
6580
  //#endregion
6535
6581
  //#region src/lib/version.ts
6536
- const VERSION = "0.5.6";
6582
+ const VERSION = "0.5.7";
6537
6583
 
6538
6584
  //#endregion
6539
6585
  //#region src/debug.ts
@@ -46879,20 +46925,29 @@ const methodColors = {
46879
46925
  function colorizeMethod(method) {
46880
46926
  return colorize(methodColors[method] ?? "white", method);
46881
46927
  }
46928
+ function getEffectiveModel(info) {
46929
+ return info.steps.length > 0 ? info.steps.at(-1).result : info.originalModel ?? "-";
46930
+ }
46931
+ function appendModelStep(info, tag, newModel) {
46932
+ if (newModel === getEffectiveModel(info)) return info;
46933
+ return {
46934
+ originalModel: info.originalModel,
46935
+ steps: [...info.steps, {
46936
+ tag,
46937
+ result: newModel
46938
+ }]
46939
+ };
46940
+ }
46882
46941
  function formatModelMapping(info) {
46883
46942
  if (!info) return "";
46884
- const { originalModel, rewrittenModel, mappedModel } = info;
46885
- if (!originalModel && !rewrittenModel && !mappedModel) return "";
46886
- const parts = [];
46887
- const displayOriginal = originalModel ?? "-";
46888
- parts.push(colorize("blueBright", displayOriginal));
46889
- if (rewrittenModel && rewrittenModel !== displayOriginal) {
46890
- parts.push(colorize("dim", "~>"));
46891
- parts.push(colorize("cyanBright", rewrittenModel));
46892
- }
46893
- if (mappedModel && mappedModel !== (rewrittenModel ?? displayOriginal)) {
46894
- parts.push(colorize("dim", "→"));
46895
- parts.push(colorize("greenBright", mappedModel));
46943
+ const { originalModel, steps } = info;
46944
+ if (!originalModel && steps.length === 0) return "";
46945
+ const parts = [colorize("blueBright", originalModel ?? "-")];
46946
+ for (let i = 0; i < steps.length; i++) {
46947
+ const step = steps[i];
46948
+ const isLast = i === steps.length - 1;
46949
+ parts.push(colorize("dim", `-[${step.tag}]->`));
46950
+ parts.push(colorize(isLast ? "greenBright" : "cyanBright", step.result));
46896
46951
  }
46897
46952
  return ` ${colorize("dim", "model=")}${parts.join(" ")}`;
46898
46953
  }
@@ -48569,13 +48624,15 @@ function rewriteModel(modelId) {
48569
48624
  if (userRules) {
48570
48625
  for (const rule of userRules) if (matchesGlob(rule.from, modelId)) return {
48571
48626
  originalModel: modelId,
48572
- model: normalizeToKnownModel(rule.to) ?? rule.to
48627
+ model: normalizeToKnownModel(rule.to) ?? rule.to,
48628
+ reason: "CONFIG_REWRITE"
48573
48629
  };
48574
48630
  }
48575
48631
  const normalized = normalizeToKnownModel(modelId);
48576
48632
  if (normalized && normalized !== modelId) return {
48577
48633
  originalModel: modelId,
48578
- model: normalized
48634
+ model: normalized,
48635
+ reason: "AUTO_CORRECT"
48579
48636
  };
48580
48637
  return {
48581
48638
  originalModel: modelId,
@@ -49279,8 +49336,13 @@ const responsesReasoningSummarySchema = object({
49279
49336
  const responsesReasoningInputSchema = object({
49280
49337
  id: string().optional(),
49281
49338
  type: literal("reasoning"),
49282
- summary: array(responsesReasoningSummarySchema),
49283
- encrypted_content: string().min(1)
49339
+ summary: array(responsesReasoningSummarySchema).optional(),
49340
+ encrypted_content: string().nullable().optional(),
49341
+ status: _enum([
49342
+ "in_progress",
49343
+ "completed",
49344
+ "incomplete"
49345
+ ]).optional()
49284
49346
  }).loose();
49285
49347
  const responsesCompactionInputSchema = object({
49286
49348
  id: string().min(1),
@@ -49513,7 +49575,11 @@ async function handleCompletionCore({ body, signal, headers }) {
49513
49575
  const requestContext = normalizeChatRequestContext(payload, headers);
49514
49576
  consola.debug("Request payload:", JSON.stringify(payload).slice(-400));
49515
49577
  const rewrite = applyModelRewrite(payload);
49516
- const originalModel = rewrite.originalModel;
49578
+ const steps = [];
49579
+ if (rewrite.reason) steps.push({
49580
+ tag: rewrite.reason,
49581
+ result: rewrite.model
49582
+ });
49517
49583
  const selectedModel = findModelById(payload.model);
49518
49584
  try {
49519
49585
  if (selectedModel) {
@@ -49532,11 +49598,10 @@ async function handleCompletionCore({ body, signal, headers }) {
49532
49598
  }
49533
49599
  const upstreamSignal = createUpstreamSignalFromConfig(signal);
49534
49600
  const plan = adapter.toCapiPlan(payload, { requestContext });
49535
- const modelMapping = {
49536
- originalModel,
49537
- rewrittenModel: rewrite.model,
49538
- mappedModel: plan.resolvedModel
49539
- };
49601
+ const modelMapping = appendModelStep({
49602
+ originalModel: rewrite.originalModel,
49603
+ steps
49604
+ }, "MODEL_RESOLVE", plan.resolvedModel);
49540
49605
  const transport = new CopilotTransport(createCopilotClient());
49541
49606
  consola.debug("Streaming response");
49542
49607
  return {
@@ -50913,10 +50978,6 @@ const responsesApiEntry = {
50913
50978
  if (error instanceof TranslationFailure) throw fromTranslationFailure(error);
50914
50979
  throw error;
50915
50980
  }
50916
- const modelMapping = {
50917
- originalModel: ctx.modelMapping.originalModel,
50918
- mappedModel: responsesPayload.model
50919
- };
50920
50981
  applyContextManagement(responsesPayload, ctx.selectedModel?.capabilities.limits.max_prompt_tokens);
50921
50982
  compactInputByLatestCompaction(responsesPayload);
50922
50983
  const { vision, initiator } = getResponsesRequestOptions(responsesPayload);
@@ -50927,7 +50988,7 @@ const responsesApiEntry = {
50927
50988
  signal: ctx.upstreamSignal.signal,
50928
50989
  requestContext: ctx.requestContext
50929
50990
  }), ctx.upstreamSignal),
50930
- modelMapping
50991
+ modelMapping: ctx.modelMapping
50931
50992
  };
50932
50993
  }
50933
50994
  };
@@ -50943,10 +51004,7 @@ const chatCompletionsEntry = {
50943
51004
  if (error instanceof TranslationFailure) throw fromTranslationFailure(error);
50944
51005
  throw error;
50945
51006
  }
50946
- const modelMapping = {
50947
- originalModel: ctx.modelMapping.originalModel,
50948
- mappedModel: plan.resolvedModel
50949
- };
51007
+ const modelMapping = appendModelStep(ctx.modelMapping, "MODEL_RESOLVE", plan.resolvedModel);
50950
51008
  consola.debug("Claude Code requested model:", ctx.anthropicPayload.model, "-> Copilot model:", plan.resolvedModel);
50951
51009
  if (consola.level >= 4) consola.debug("Planned Copilot request payload:", JSON.stringify(plan.payload));
50952
51010
  return {
@@ -50996,17 +51054,33 @@ async function handleMessagesCore({ body, signal, headers }) {
50996
51054
  const requestContext = normalizeAnthropicRequestContext(anthropicPayload, headers);
50997
51055
  if (consola.level >= 4) consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
50998
51056
  const rewrite = applyModelRewrite(anthropicPayload);
51057
+ const steps = [];
51058
+ if (rewrite.reason) steps.push({
51059
+ tag: rewrite.reason,
51060
+ result: rewrite.model
51061
+ });
50999
51062
  const betaResult = processAnthropicBetaHeader(headers.get("anthropic-beta"), anthropicPayload.model);
51000
51063
  if (betaResult.upgradeTarget) {
51001
51064
  consola.debug(`Beta header context upgrade: ${anthropicPayload.model} → ${betaResult.upgradeTarget}`);
51002
51065
  anthropicPayload.model = betaResult.upgradeTarget;
51066
+ steps.push({
51067
+ tag: "BETA_UPGRADE",
51068
+ result: betaResult.upgradeTarget
51069
+ });
51003
51070
  }
51004
51071
  const anthropicBetaHeader = betaResult.header;
51005
51072
  const modelRouting = applyMessagesModelPolicy(anthropicPayload, { betaUpgraded: !!betaResult.upgradeTarget });
51073
+ if (modelRouting.reason === "context-upgrade") steps.push({
51074
+ tag: "CONTEXT_UPGRADE",
51075
+ result: modelRouting.routedModel
51076
+ });
51077
+ else if (modelRouting.reason === "compact") steps.push({
51078
+ tag: "COMPACT",
51079
+ result: modelRouting.routedModel
51080
+ });
51006
51081
  const modelMapping = {
51007
51082
  originalModel: rewrite.originalModel,
51008
- rewrittenModel: rewrite.model,
51009
- mappedModel: modelRouting.routedModel
51083
+ steps
51010
51084
  };
51011
51085
  if (modelRouting.reason) consola.debug(`Routed anthropic request via ${modelRouting.reason}:`, `${modelRouting.originalModel} -> ${modelRouting.routedModel}`);
51012
51086
  const selectedModel = findModelById(anthropicPayload.model);
@@ -51040,8 +51114,10 @@ async function handleMessagesCore({ body, signal, headers }) {
51040
51114
  upstreamSignal: retrySignal,
51041
51115
  modelMapping: {
51042
51116
  originalModel: rewrite.originalModel,
51043
- rewrittenModel: rewrite.model,
51044
- mappedModel: upgradeTarget
51117
+ steps: [...steps, {
51118
+ tag: "RETRY_UPGRADE",
51119
+ result: upgradeTarget
51120
+ }]
51045
51121
  }
51046
51122
  });
51047
51123
  }
@@ -51458,8 +51534,10 @@ async function handleResponsesCore({ body, signal, headers }) {
51458
51534
  result,
51459
51535
  modelMapping: {
51460
51536
  originalModel: rewrite.originalModel,
51461
- rewrittenModel: rewrite.model,
51462
- mappedModel: effectivePayload.model
51537
+ steps: rewrite.reason ? [{
51538
+ tag: rewrite.reason,
51539
+ result: rewrite.model
51540
+ }] : []
51463
51541
  }
51464
51542
  };
51465
51543
  }
@@ -51705,7 +51783,7 @@ function createServer(options) {
51705
51783
  const model = body && typeof body === "object" && "model" in body ? body.model : void 0;
51706
51784
  if (typeof model === "string") setRequestModelMapping(request, {
51707
51785
  originalModel: model,
51708
- mappedModel: model
51786
+ steps: []
51709
51787
  });
51710
51788
  }).onAfterResponse(({ request, requestStart, set }) => {
51711
51789
  const elapsed = formatElapsed(requestStart);