github-router 0.3.248 → 0.3.249

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.
@@ -19919,6 +19919,20 @@ function detectAgentCall(input) {
19919
19919
  //#endregion
19920
19920
  //#region src/services/copilot/endpoint.ts
19921
19921
  /**
19922
+ * Catalog spellings that mean each of our two clients. Copilot is not
19923
+ * self-consistent about the `/v1` prefix — the live catalog advertises
19924
+ * `/v1/messages` prefixed but `/chat/completions` bare, and this repo's own
19925
+ * fixtures carry both forms — so an exact-match on the bare spelling alone
19926
+ * silently misses a real shape. `src/lib/model-validation.ts` already
19927
+ * normalizes the same way (`ENDPOINT_ALIASES`); this keeps the two agreeing.
19928
+ *
19929
+ * Matching is EXACT against this set, never a suffix/`includes` test: a
19930
+ * `ws:/responses` (websocket transport) entry is NOT the `/responses` HTTP
19931
+ * client and must keep resolving to "serves neither".
19932
+ */
19933
+ const CHAT_ENDPOINTS = /* @__PURE__ */ new Set(["/chat/completions", "/v1/chat/completions"]);
19934
+ const RESPONSES_ENDPOINTS = /* @__PURE__ */ new Set(["/responses", "/v1/responses"]);
19935
+ /**
19922
19936
  * Decide which endpoint to call for a model from its catalog
19923
19937
  * `supported_endpoints`. Prefers `/chat/completions` when available (the
19924
19938
  * simpler, more widely-supported shape) and falls back to `/responses` for
@@ -19934,19 +19948,37 @@ function detectAgentCall(input) {
19934
19948
  function pickEndpoint(model) {
19935
19949
  const eps = model.supported_endpoints;
19936
19950
  if (!eps || eps.length === 0) return "chat";
19937
- if (eps.includes("/chat/completions")) return "chat";
19938
- if (eps.includes("/responses")) return "responses";
19951
+ if (eps.some((e) => CHAT_ENDPOINTS.has(e))) return "chat";
19952
+ if (eps.some((e) => RESPONSES_ENDPOINTS.has(e))) return "responses";
19939
19953
  }
19940
19954
  /**
19941
- * `pickEndpoint` by model id against the live catalog. Returns "chat" when
19942
- * the id isn't in the catalog (unknown models default to the chat shape,
19943
- * matching the field-absent rule above) — callers that need a hard
19944
- * presence check should look the model up themselves.
19955
+ * `pickEndpoint` by model id against the live catalog, WITHOUT collapsing
19956
+ * "absent from the catalog" into "serves neither of our endpoints".
19957
+ *
19958
+ * This function deliberately has no default. The predecessor
19959
+ * (`endpointForModelId`) returned `pickEndpoint(found) ?? "chat"`, which
19960
+ * coerced both cases to "chat" — defensible for an unknown id, silently wrong
19961
+ * for a catalog model serving only, say, `/v1/messages`: the caller would drive
19962
+ * it through the chat client and get an opaque upstream 400 with no local
19963
+ * signal about the real cause. `src/lib/browser-mcp/compressor.ts` already
19964
+ * treats that case correctly (`if (!endpoint) continue`); this makes the same
19965
+ * distinction available to callers that resolve by id.
19966
+ *
19967
+ * Callers that legitimately want the chat default for an unknown id can still
19968
+ * have it — they just have to write it, per case, on purpose.
19945
19969
  */
19946
- function endpointForModelId(id) {
19970
+ function resolveEndpointForModelId(id) {
19947
19971
  const found = state.models?.data?.find((m) => m.id === id);
19948
- if (!found) return "chat";
19949
- return pickEndpoint(found) ?? "chat";
19972
+ if (!found) return { kind: "unknown-model" };
19973
+ const endpoint = pickEndpoint(found);
19974
+ if (endpoint) return {
19975
+ kind: "endpoint",
19976
+ endpoint
19977
+ };
19978
+ return {
19979
+ kind: "unreachable",
19980
+ endpoints: found.supported_endpoints ?? []
19981
+ };
19950
19982
  }
19951
19983
  //#endregion
19952
19984
  //#region src/lib/browser-mcp/compressor.ts
@@ -24169,7 +24201,12 @@ async function runStreamLoop(stream, context, opts, options) {
24169
24201
  return;
24170
24202
  }
24171
24203
  }
24172
- if (endpointForModelId(resolved.modelId) === "responses") {
24204
+ const resolution = resolveEndpointForModelId(resolved.modelId);
24205
+ if (resolution.kind === "unreachable") {
24206
+ pushUndrivableModelDiagnostic(stream, resolved, resolution.endpoints);
24207
+ return;
24208
+ }
24209
+ if (resolution.kind === "endpoint" && resolution.endpoint === "responses") {
24173
24210
  await runResponsesStreamLoop(stream, context, opts, options);
24174
24211
  return;
24175
24212
  }
@@ -24993,6 +25030,33 @@ function pushBackstopDiagnostic(stream, resolved, assembledTokens, limitTokens)
24993
25030
  error: final
24994
25031
  });
24995
25032
  }
25033
+ /**
25034
+ * Terminal diagnostic for a model the worker cannot drive AT ALL: it is in the
25035
+ * live catalog, but its `supported_endpoints` name neither `/chat/completions`
25036
+ * nor `/responses` — our only two clients. Fails here, locally, naming the
25037
+ * model and what it actually serves, instead of coercing it onto the chat
25038
+ * client and surfacing an opaque upstream `unsupported_api_for_model` 400.
25039
+ * Carried as assistant TEXT so the engine surfaces it as an `isError` result
25040
+ * (same shape as the request-boundary backstop).
25041
+ */
25042
+ function pushUndrivableModelDiagnostic(stream, resolved, endpoints) {
25043
+ const served = endpoints.length > 0 ? endpoints.join(", ") : "(none advertised)";
25044
+ const text = `Cannot run: ${resolved.modelId} is in the Copilot catalog but serves neither /chat/completions nor /responses — the only two APIs a worker can drive. Its catalog supported_endpoints are: ${served}. Pick a different model for this call, or change the mode default via worker_defaults (a zero-arg worker_defaults call lists the models a worker can be pointed at).`;
25045
+ const final = {
25046
+ ...makeBaseMessage(resolved),
25047
+ content: [{
25048
+ type: "text",
25049
+ text
25050
+ }],
25051
+ stopReason: "error",
25052
+ errorMessage: `model ${resolved.modelId} serves no worker-drivable endpoint (${served})`
25053
+ };
25054
+ stream.push({
25055
+ type: "error",
25056
+ reason: "error",
25057
+ error: final
25058
+ });
25059
+ }
24996
25060
  function describeError(err) {
24997
25061
  if (err instanceof HTTPError) return `${err.message} (status ${err.response.status})`;
24998
25062
  if (err instanceof Error) return err.message;
@@ -30775,6 +30839,36 @@ function extractAssistantText(content) {
30775
30839
  for (const part of content) if (part.type === "text") out += part.text;
30776
30840
  return out;
30777
30841
  }
30842
+ /**
30843
+ * Banner that prefixes text salvaged from an EARLIER assistant turn when the
30844
+ * run ends without a usable final answer.
30845
+ *
30846
+ * It is deliberately loud. Recovered text is partial work — the model was
30847
+ * mid-investigation when it went quiet — and returning it bare would let the
30848
+ * caller read an interim note as a conclusion. That is a different bug from
30849
+ * the one this recovery fixes, and a worse one: silently wrong beats loudly
30850
+ * missing only for the model that produced it.
30851
+ */
30852
+ const RECOVERED_TEXT_BANNER = "[recovered from an earlier turn — this run ended without a final answer, so the text below is partial work in progress, NOT a conclusion. Treat it as leads to verify, not as the worker's answer.]";
30853
+ /**
30854
+ * Compose the recovered-text block for a run that is ending with nothing
30855
+ * usable in its final turn.
30856
+ *
30857
+ * Returns `""` in the two cases where recovery would be noise: the run DID
30858
+ * produce live text (nothing was lost), or no turn ever produced any.
30859
+ *
30860
+ * Scope note: `highWater` is the LAST non-empty assistant text, not a
30861
+ * concatenation of every non-empty turn. Accumulating them would turn a
30862
+ * recovered result into a transcript dump — mostly interim narration — where
30863
+ * the last turn is both the most complete and the one the model was building
30864
+ * toward when it stalled.
30865
+ */
30866
+ function recoveredBlock(liveText, highWater) {
30867
+ if (liveText.trim()) return "";
30868
+ const recovered = highWater.trim();
30869
+ if (!recovered) return "";
30870
+ return `${RECOVERED_TEXT_BANNER}\n\n${recovered}`;
30871
+ }
30778
30872
  const MAX_EMPTY_OUTPUT_NUDGES = 3;
30779
30873
  const EMPTY_OUTPUT_NUDGES = [
30780
30874
  "Summarize your findings so far.",
@@ -30951,6 +31045,7 @@ async function runWorkerAgentOnce(opts) {
30951
31045
  const abortHandler = () => agent.abort();
30952
31046
  if (opts.signal) opts.signal.addEventListener("abort", abortHandler, { once: true });
30953
31047
  let finalText = "";
31048
+ let lastNonEmptyText = "";
30954
31049
  let lastStopReason = null;
30955
31050
  let nudgeCount = 0;
30956
31051
  const maxEmptyOutputNudges = resolveMaxEmptyOutputNudges();
@@ -30977,6 +31072,7 @@ async function runWorkerAgentOnce(opts) {
30977
31072
  const content = msg.content;
30978
31073
  if (!Array.isArray(content)) return;
30979
31074
  finalText = extractAssistantText(content);
31075
+ if (finalText.trim()) lastNonEmptyText = finalText;
30980
31076
  const sr = msg.stopReason;
30981
31077
  if (typeof sr === "string") lastStopReason = sr;
30982
31078
  });
@@ -30999,30 +31095,41 @@ async function runWorkerAgentOnce(opts) {
30999
31095
  try {
31000
31096
  await ws.remove();
31001
31097
  } catch {}
31002
- const text = isBrowse ? terminalText ?? finalText : diff ? `${finalText}\n\n${diff}` : finalText;
31098
+ const liveAnswer = isBrowse ? terminalText ?? finalText : finalText;
31099
+ const recovered = recoveredBlock(liveAnswer, lastNonEmptyText);
31100
+ const text = isBrowse ? liveAnswer : diff ? `${finalText}\n\n${diff}` : finalText;
31003
31101
  if (lastStopReason === "error" || lastStopReason === "aborted") {
31004
- const diag = (terminalText ?? finalText).trim();
31102
+ const diag = liveAnswer.trim();
31005
31103
  let diagnostic;
31006
31104
  if (lastStopReason === "aborted") diagnostic = wallClockExpired ? "[halted: wallclock]" : "[halted: cancelled]";
31007
31105
  else diagnostic = diag || "Worker run failed before producing an answer — the model's input likely overflowed (a large tool result), or the upstream errored. Retry with a narrower task: target a specific section / file / element rather than reading everything at once.";
31008
31106
  return {
31009
31107
  text: lastStopReason === "aborted" ? [
31010
31108
  diag,
31109
+ recovered,
31011
31110
  diff,
31012
31111
  diagnostic
31013
- ].filter(Boolean).join("\n\n") : [diagnostic, diff].filter(Boolean).join("\n\n"),
31112
+ ].filter(Boolean).join("\n\n") : [
31113
+ diagnostic,
31114
+ recovered,
31115
+ diff
31116
+ ].filter(Boolean).join("\n\n"),
31014
31117
  isError: true
31015
31118
  };
31016
31119
  }
31017
31120
  if (budget.hardStopReason) return {
31018
- text: [text, `[halted: ${budget.hardStopReason}]`].filter(Boolean).join("\n\n"),
31121
+ text: [
31122
+ text,
31123
+ recovered,
31124
+ `[halted: ${budget.hardStopReason}]`
31125
+ ].filter(Boolean).join("\n\n"),
31019
31126
  isError: true
31020
31127
  };
31021
31128
  if (!text.trim()) return {
31022
- text: `${NO_OUTPUT_PREFIX} after ${nudgeCount} nudges (stopReason=${lastStopReason ?? "unknown"}, turns=${budget.turns}, elapsed=${budget.elapsedMs}ms)]; retry with a different model via worker_defaults, or narrow/split the task.`,
31129
+ text: [`${NO_OUTPUT_PREFIX} after ${nudgeCount} nudges (stopReason=${lastStopReason ?? "unknown"}, turns=${budget.turns}, elapsed=${budget.elapsedMs}ms)]; retry with a different model via worker_defaults, or narrow/split the task.`, recovered].filter(Boolean).join("\n\n"),
31023
31130
  isError: true
31024
31131
  };
31025
- return { text };
31132
+ return { text: [text, recovered].filter(Boolean).join("\n\n") };
31026
31133
  } catch (err) {
31027
31134
  let diff = "";
31028
31135
  try {
@@ -31035,7 +31142,12 @@ async function runWorkerAgentOnce(opts) {
31035
31142
  } catch {}
31036
31143
  const haltOrErr = err instanceof Error ? err.message : String(err);
31037
31144
  const parts = [];
31038
- if (finalText) parts.push(finalText);
31145
+ const liveAnswer = isBrowse ? terminalText ?? finalText : finalText;
31146
+ if (liveAnswer.trim()) parts.push(liveAnswer);
31147
+ else {
31148
+ const recovered = recoveredBlock(liveAnswer, lastNonEmptyText);
31149
+ if (recovered) parts.push(recovered);
31150
+ }
31039
31151
  if (diff) parts.push(diff);
31040
31152
  parts.push(haltOrErr);
31041
31153
  return {
@@ -35064,4 +35176,4 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
35064
35176
  //#endregion
35065
35177
  export { searchWeb as $, DEFAULT_CLAUDE_MODEL_FALLBACKS as $t, trustRepo as A, state as An, getTokenCount as At, TEST_DEFAULT_MODEL as B, hasSupportedBrowserInstalled as Bt, fileLastPromptStore as C, fetchWithTransientRetry as Cn, scoutModel as Ct, repoRoot as D, copilotBaseUrl as Dn, shimDefaultsToXhigh as Dt, repoFingerprint as E, GITHUB_API_BASE_URL as En, workerToolsEnabled as Et, EXPLORE_DEFAULT_MODEL as F, createChatCompletions as Ft, buildEnv as G, CONDENSED_OPERATING_SEQUENCE as Gt, resolveModeDefaults as H, extractTarGzMember as Ht, EXPLORE_DEFAULT_THINKING as I, MAX_RESPONSE_BODY_BYTES as It, toolbeltEnabled as J, ArtifactClient as Jt, availableToolCommands as K, DEFINITION_OF_GREATNESS as Kt, IMPLEMENT_DEFAULT_MODEL as L, readResponseBodyCapped as Lt, resolveSealedGate as M, resolveMcpToolTimeoutMs as Mt, BROWSE_DEFAULT_MODEL as N, pickEndpoint as Nt, stopGateEnabledForRepo as O, copilotHeaders as On, countTokens as Ot, DEFAULT_MODEL as P, createResponses as Pt, assetFor as Q, toolbeltPathOverride as Qt, PLAN_DEFAULT_MODEL as R, parseJsonOrDiagnose as Rt, fileFindingsStore as S, getGitHubUser as Sn, reviewerModel as St, isSubagentContext as T, forwardError as Tn, standInToolEnabled as Tt, resolveWorkerRunOpts as U, extractZipMember as Ut, appendPlanReminder as V, provisionAndIndexColbert as Vt, runWorkerAgent as W, warmTreeSitterPool as Wt, vscodeRipgrepPath as X, buildWorkspaceHeaderJson as Xt, toolbeltSkipSet as Y, buildWorkspaceHeaderHelperCommand as Yt, TOOLBELT_TOOLS$1 as Z, collapsePathKeys as Zt, stopGateDisabled as _, isNullish as _n, browserCompoundToolsEnabled as _t, buildPeerAwarenessSnippet as a, generateRandomPort as an, buildAnthropicErrorEvent as at, stopReviewEnabled as b, sleep$3 as bn, geminiAvailable as bt, personasFor as c, withInstallLock as cn, logStreamError as ct, buildStopHookCommand as d, setupGitHubToken as dn, handleMcpDelete as dt, DEFAULT_CODEX_MODEL as en, ADVISOR_INTERNAL_TOOL_NAME as et, captureLaunchBaseline as f, tryRefreshAndRetry as fn, handleMcpPost as ft, launchBaselineKey as g, filterBetaHeader as gn, browseAgentEnabled as gt, injectStopHookIntoSettingsFile as h, cacheVSCodeVersion as hn, brainstormModel as ht, buildAgentPrompt as i, UPSTREAM_INACTIVITY_TIMEOUT_MS as in, isAdvisorRequested as it, liveExec as j, assembleResponsesPayload as jt, stopReviewStateDir as k, githubHeaders as kn, createMessages as kt, buildArtifactOpenHookCommand as l, setupCopilotToken as ln, readIteratorWithTimeout as lt, fileBlockBudget as m, cacheModels as mn, artifactToolsEnabled as mt, MCP_GROUPS as n, DEFAULT_PORT as nn, buildAdvisorStream as nt, buildPeerAwarenessSummary as o, pickClaudeDefault as on, buildOpenAIErrorEvent as ot, decideStopHook as p, cacheCopilotVersion as pn, agentToolsEnabled as pt, buildToolbeltAwareness as q, shouldUseInsecureTls as qt, assertMcpToolSurfaceConsistent as r, UPSTREAM_FETCH_TIMEOUT_MS as rn, injectAdvisorTool as rt, enumerateInjectedMcpToolNames as s, getPackageVersion as sn, isControllerClosedError as st, GROUP_META as t, DEFAULT_CODEX_MODEL_FALLBACKS as tn, ADVISOR_TOOL_INSTRUCTIONS as tt, buildSessionBindHookCommand as u, setupGitHubAgentToken as un, relayAnthropicStream as ut, stopGateId as v, resolveCodexModel as vn, browserToolsEnabled as vt, fileReviewDebounce as w, HTTPError as wn, scribeModel as wt, fileBaselineStore as x, getModels as xn, nativeSubagentModel as xt, stopGatePlanMode as y, resolveModel as yn, fleetToolsEnabled as yt, REVIEW_DEFAULT_MODEL as z, provisionBrowserAssets as zt };
35066
35178
 
35067
- //# sourceMappingURL=peer-mcp-personas-HWhxZUvf.js.map
35179
+ //# sourceMappingURL=peer-mcp-personas-DJzLpfDJ.js.map