@code-yeongyu/senpi-ai 2026.9.28 → 2026.9.29
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/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +76 -24
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/api/bedrock-converse-stream.js +6 -1
- package/dist/api/bedrock-converse-stream.js.map +1 -1
- package/dist/api/github-copilot-endpoint.d.ts +19 -0
- package/dist/api/github-copilot-endpoint.d.ts.map +1 -0
- package/dist/api/github-copilot-endpoint.js +48 -0
- package/dist/api/github-copilot-endpoint.js.map +1 -0
- package/dist/api/github-copilot-errors.d.ts +9 -0
- package/dist/api/github-copilot-errors.d.ts.map +1 -0
- package/dist/api/github-copilot-errors.js +78 -0
- package/dist/api/github-copilot-errors.js.map +1 -0
- package/dist/api/github-copilot-headers.d.ts +1 -0
- package/dist/api/github-copilot-headers.d.ts.map +1 -1
- package/dist/api/github-copilot-headers.js +3 -0
- package/dist/api/github-copilot-headers.js.map +1 -1
- package/dist/api/openai-completions.d.ts.map +1 -1
- package/dist/api/openai-completions.js +18 -12
- package/dist/api/openai-completions.js.map +1 -1
- package/dist/api/openai-responses.d.ts.map +1 -1
- package/dist/api/openai-responses.js +38 -2
- package/dist/api/openai-responses.js.map +1 -1
- package/dist/auth/helpers.d.ts +1 -0
- package/dist/auth/helpers.d.ts.map +1 -1
- package/dist/auth/helpers.js +1 -0
- package/dist/auth/helpers.js.map +1 -1
- package/dist/auth/oauth/github-copilot-model-catalog.d.ts +7 -0
- package/dist/auth/oauth/github-copilot-model-catalog.d.ts.map +1 -0
- package/dist/auth/oauth/github-copilot-model-catalog.js +46 -0
- package/dist/auth/oauth/github-copilot-model-catalog.js.map +1 -0
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/dist/auth/oauth/github-copilot.js +24 -77
- package/dist/auth/oauth/github-copilot.js.map +1 -1
- package/dist/auth/pool/slots.d.ts.map +1 -1
- package/dist/auth/pool/slots.js +21 -3
- package/dist/auth/pool/slots.js.map +1 -1
- package/dist/auth/resolve.d.ts +6 -0
- package/dist/auth/resolve.d.ts.map +1 -1
- package/dist/auth/resolve.js +6 -5
- package/dist/auth/resolve.js.map +1 -1
- package/dist/auth/types.d.ts +14 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/cursor/context-limit-store.d.ts.map +1 -1
- package/dist/cursor/context-limit-store.js +23 -18
- package/dist/cursor/context-limit-store.js.map +1 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +3 -2
- package/dist/models.js.map +1 -1
- package/dist/openai-responses-compat.d.ts +6 -0
- package/dist/openai-responses-compat.d.ts.map +1 -1
- package/dist/openai-responses-compat.js.map +1 -1
- package/dist/providers/amazon-bedrock.js +13 -13
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/anthropic.json +1 -1
- package/dist/providers/data/azure-openai-responses.json +1 -1
- package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/dist/providers/data/mistral.json +1 -1
- package/dist/providers/data/openai.json +1 -1
- package/dist/providers/data/opencode-go.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/opengateway.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/venice.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/devin.models.d.ts +5 -3
- package/dist/providers/devin.models.d.ts.map +1 -1
- package/dist/providers/devin.models.js +6 -5
- package/dist/providers/devin.models.js.map +1 -1
- package/dist/providers/github-copilot-limits.d.ts +11 -0
- package/dist/providers/github-copilot-limits.d.ts.map +1 -0
- package/dist/providers/github-copilot-limits.js +65 -0
- package/dist/providers/github-copilot-limits.js.map +1 -0
- package/dist/providers/github-copilot.d.ts.map +1 -1
- package/dist/providers/github-copilot.js +24 -3
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +3 -5
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/session-resources.d.ts.map +1 -1
- package/dist/session-resources.js +2 -1
- package/dist/session-resources.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/block-symbols.d.ts +4 -0
- package/dist/utils/block-symbols.d.ts.map +1 -1
- package/dist/utils/block-symbols.js +10 -6
- package/dist/utils/block-symbols.js.map +1 -1
- package/dist/utils/claude-code-version-cache.d.ts +12 -0
- package/dist/utils/claude-code-version-cache.d.ts.map +1 -0
- package/dist/utils/claude-code-version-cache.js +60 -0
- package/dist/utils/claude-code-version-cache.js.map +1 -0
- package/dist/utils/claude-code-version.d.ts +78 -0
- package/dist/utils/claude-code-version.d.ts.map +1 -0
- package/dist/utils/claude-code-version.js +203 -0
- package/dist/utils/claude-code-version.js.map +1 -0
- package/dist/utils/cursor-context-limit.d.ts.map +1 -1
- package/dist/utils/cursor-context-limit.js +51 -45
- package/dist/utils/cursor-context-limit.js.map +1 -1
- package/dist/utils/github-copilot-tool-limit.d.ts +10 -0
- package/dist/utils/github-copilot-tool-limit.d.ts.map +1 -0
- package/dist/utils/github-copilot-tool-limit.js +75 -0
- package/dist/utils/github-copilot-tool-limit.js.map +1 -0
- package/dist/utils/overflow.d.ts.map +1 -1
- package/dist/utils/overflow.js +3 -1
- package/dist/utils/overflow.js.map +1 -1
- package/dist/utils/process-singleton.d.ts +10 -0
- package/dist/utils/process-singleton.d.ts.map +1 -0
- package/dist/utils/process-singleton.js +18 -0
- package/dist/utils/process-singleton.js.map +1 -0
- package/dist/utils/prompt-cache-ttl.d.ts +3 -1
- package/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
- package/dist/utils/prompt-cache-ttl.js +4 -2
- package/dist/utils/prompt-cache-ttl.js.map +1 -1
- package/dist/utils/retry.d.ts +3 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +15 -3
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/tool-choice-fallback.d.ts +29 -0
- package/dist/utils/tool-choice-fallback.d.ts.map +1 -1
- package/dist/utils/tool-choice-fallback.js +50 -2
- package/dist/utils/tool-choice-fallback.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { getProviderEnvValue } from "./provider-env.js";
|
|
2
|
+
/** Exact `X.Y.Z` pins the fingerprint and skips the network lookup. */
|
|
3
|
+
export const CLAUDE_CODE_VERSION_PIN_ENV = "PI_CLAUDE_CODE_VERSION";
|
|
4
|
+
/**
|
|
5
|
+
* Anthropic's native-installer release channel. `stable` lags the npm package during a staged
|
|
6
|
+
* rollout, so `latest` is the channel that matches what a freshly updated Claude Code reports.
|
|
7
|
+
*/
|
|
8
|
+
export const CLAUDE_RELEASE_CHANNEL_URL = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest";
|
|
9
|
+
export const CLAUDE_NPM_DIST_TAG_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
|
|
10
|
+
const REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
11
|
+
const FETCH_TIMEOUT_MS = 5_000;
|
|
12
|
+
const EXACT_SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
13
|
+
const EMBEDDED_SEMVER = /(\d+\.\d+\.\d+)/;
|
|
14
|
+
const TOO_OLD_REJECTION = /claude_code_version_too_old|does not support this model; version (\d+\.\d+\.\d+) or newer/i;
|
|
15
|
+
export function compareClaudeCodeVersions(a, b) {
|
|
16
|
+
const left = a.split(".").map(Number);
|
|
17
|
+
const right = b.split(".").map(Number);
|
|
18
|
+
for (let i = 0; i < 3; i++) {
|
|
19
|
+
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
20
|
+
if (diff !== 0)
|
|
21
|
+
return diff;
|
|
22
|
+
}
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
function isExactSemver(value) {
|
|
26
|
+
return typeof value === "string" && EXACT_SEMVER.test(value);
|
|
27
|
+
}
|
|
28
|
+
function highest(candidates) {
|
|
29
|
+
let best;
|
|
30
|
+
for (const candidate of candidates) {
|
|
31
|
+
if (!isExactSemver(candidate))
|
|
32
|
+
continue;
|
|
33
|
+
if (best === undefined || compareClaudeCodeVersions(candidate, best) > 0)
|
|
34
|
+
best = candidate;
|
|
35
|
+
}
|
|
36
|
+
return best;
|
|
37
|
+
}
|
|
38
|
+
async function fetchText(fetchImpl, url) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetchImpl(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
41
|
+
return response.ok ? await response.text() : undefined;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Offline, DNS failure, or timeout: the caller keeps the floor or the cached value.
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function fetchReleaseChannel(fetchImpl) {
|
|
49
|
+
const text = await fetchText(fetchImpl, CLAUDE_RELEASE_CHANNEL_URL);
|
|
50
|
+
return text === undefined ? undefined : EMBEDDED_SEMVER.exec(text.trim())?.[1];
|
|
51
|
+
}
|
|
52
|
+
async function fetchNpmDistTag(fetchImpl) {
|
|
53
|
+
const text = await fetchText(fetchImpl, CLAUDE_NPM_DIST_TAG_URL);
|
|
54
|
+
if (text === undefined)
|
|
55
|
+
return undefined;
|
|
56
|
+
try {
|
|
57
|
+
const data = JSON.parse(text);
|
|
58
|
+
const version = typeof data === "object" && data !== null && "version" in data ? data.version : undefined;
|
|
59
|
+
return typeof version === "string" ? EMBEDDED_SEMVER.exec(version)?.[1] : undefined;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// A registry error page is not a dist-tag document.
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Latest published Claude Code version: the higher of the native-installer `latest` channel and
|
|
68
|
+
* the npm dist-tag, so one lagging or unavailable source cannot hold the fingerprint back.
|
|
69
|
+
*/
|
|
70
|
+
export async function fetchLatestClaudeCodeVersion(fetchImpl = fetch) {
|
|
71
|
+
return highest(await Promise.all([fetchReleaseChannel(fetchImpl), fetchNpmDistTag(fetchImpl)]));
|
|
72
|
+
}
|
|
73
|
+
export function requiredClaudeCodeVersionFromError(message) {
|
|
74
|
+
const match = TOO_OLD_REJECTION.exec(message);
|
|
75
|
+
if (!match)
|
|
76
|
+
return undefined;
|
|
77
|
+
return match[1] ?? /version (\d+\.\d+\.\d+) or newer/i.exec(message)?.[1];
|
|
78
|
+
}
|
|
79
|
+
export function isClaudeCodeVersionTooOldError(error) {
|
|
80
|
+
return error instanceof Error && "status" in error && error.status === 400 && TOO_OLD_REJECTION.test(error.message);
|
|
81
|
+
}
|
|
82
|
+
export function createClaudeCodeVersionResolver(options) {
|
|
83
|
+
const { floor, store } = options;
|
|
84
|
+
const offline = options.offline === true;
|
|
85
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
86
|
+
const now = options.now ?? Date.now;
|
|
87
|
+
const refreshIntervalMs = options.refreshIntervalMs ?? REFRESH_INTERVAL_MS;
|
|
88
|
+
const pinned = isExactSemver(options.pinned) ? options.pinned : undefined;
|
|
89
|
+
let loaded = false;
|
|
90
|
+
let resolved;
|
|
91
|
+
let checkedAt = 0;
|
|
92
|
+
let inflight;
|
|
93
|
+
const current = () => resolved !== undefined && compareClaudeCodeVersions(resolved, floor) > 0 ? resolved : floor;
|
|
94
|
+
const loadCache = () => {
|
|
95
|
+
if (loaded || store === null)
|
|
96
|
+
return;
|
|
97
|
+
loaded = true;
|
|
98
|
+
const record = store.load();
|
|
99
|
+
if (record && isExactSemver(record.version) && Number.isFinite(record.checkedAt)) {
|
|
100
|
+
resolved = record.version;
|
|
101
|
+
checkedAt = record.checkedAt;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const persist = () => {
|
|
105
|
+
if (store !== null && resolved !== undefined)
|
|
106
|
+
store.save({ version: resolved, checkedAt });
|
|
107
|
+
};
|
|
108
|
+
const refresh = () => {
|
|
109
|
+
if (pinned !== undefined || store === null)
|
|
110
|
+
return Promise.resolve(pinned ?? current());
|
|
111
|
+
loadCache();
|
|
112
|
+
if (offline)
|
|
113
|
+
return Promise.resolve(current());
|
|
114
|
+
inflight ??= (async () => {
|
|
115
|
+
try {
|
|
116
|
+
const latest = await fetchLatestClaudeCodeVersion(fetchImpl);
|
|
117
|
+
// A failed lookup still counts as a check: an offline host waits a full interval
|
|
118
|
+
// before trying again instead of retrying on every request.
|
|
119
|
+
checkedAt = now();
|
|
120
|
+
if (latest !== undefined && (resolved === undefined || compareClaudeCodeVersions(latest, resolved) > 0)) {
|
|
121
|
+
resolved = latest;
|
|
122
|
+
}
|
|
123
|
+
persist();
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
inflight = undefined;
|
|
127
|
+
}
|
|
128
|
+
return current();
|
|
129
|
+
})();
|
|
130
|
+
return inflight;
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
get() {
|
|
134
|
+
if (pinned !== undefined)
|
|
135
|
+
return pinned;
|
|
136
|
+
if (store === null)
|
|
137
|
+
return current();
|
|
138
|
+
loadCache();
|
|
139
|
+
if (!offline && inflight === undefined && now() - checkedAt >= refreshIntervalMs)
|
|
140
|
+
void refresh();
|
|
141
|
+
return current();
|
|
142
|
+
},
|
|
143
|
+
refresh,
|
|
144
|
+
raise(version) {
|
|
145
|
+
if (pinned !== undefined)
|
|
146
|
+
return pinned;
|
|
147
|
+
loadCache();
|
|
148
|
+
if (isExactSemver(version) && compareClaudeCodeVersions(version, current()) > 0) {
|
|
149
|
+
resolved = version;
|
|
150
|
+
persist();
|
|
151
|
+
}
|
|
152
|
+
return current();
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
let installedStore = null;
|
|
157
|
+
let installedOffline = false;
|
|
158
|
+
let defaultResolver;
|
|
159
|
+
let defaultFloor;
|
|
160
|
+
/**
|
|
161
|
+
* Hosts with a filesystem install their cache here once at startup (see
|
|
162
|
+
* `utils/claude-code-version-cache.ts`); until then, and in browsers, the resolver answers
|
|
163
|
+
* the floor without touching the network.
|
|
164
|
+
*/
|
|
165
|
+
export function installClaudeCodeVersionStore(store, options = {}) {
|
|
166
|
+
installedStore = store;
|
|
167
|
+
installedOffline = options.offline === true;
|
|
168
|
+
defaultResolver = undefined;
|
|
169
|
+
}
|
|
170
|
+
function resolver(floor) {
|
|
171
|
+
if (defaultResolver === undefined || defaultFloor !== floor) {
|
|
172
|
+
defaultFloor = floor;
|
|
173
|
+
defaultResolver = createClaudeCodeVersionResolver({ floor, store: installedStore, offline: installedOffline });
|
|
174
|
+
}
|
|
175
|
+
return defaultResolver;
|
|
176
|
+
}
|
|
177
|
+
function pinnedClaudeCodeVersion(env) {
|
|
178
|
+
const pin = getProviderEnvValue(CLAUDE_CODE_VERSION_PIN_ENV, env)?.trim();
|
|
179
|
+
return isExactSemver(pin) ? pin : undefined;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The version the next OAuth request signs with. `floor` is the bundled Claude Code version the
|
|
183
|
+
* caller declares (the `claudeCodeVersion` constant in `api/anthropic-messages.ts`).
|
|
184
|
+
*/
|
|
185
|
+
export function getClaudeCodeVersion(floor, env) {
|
|
186
|
+
return pinnedClaudeCodeVersion(env) ?? resolver(floor).get();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* After a `claude_code_version_too_old` rejection: adopt the version the error names, or the
|
|
190
|
+
* latest published one, and report the fingerprint to retry with; `undefined` when nothing
|
|
191
|
+
* newer is available (pinned, or already at the required version).
|
|
192
|
+
*/
|
|
193
|
+
export async function recoverClaudeCodeVersion(error, floor, advertised, env) {
|
|
194
|
+
if (pinnedClaudeCodeVersion(env) !== undefined)
|
|
195
|
+
return undefined;
|
|
196
|
+
const required = requiredClaudeCodeVersionFromError(error.message);
|
|
197
|
+
const next = required === undefined ? await resolver(floor).refresh() : resolver(floor).raise(required);
|
|
198
|
+
return compareClaudeCodeVersions(next, advertised) > 0 ? next : undefined;
|
|
199
|
+
}
|
|
200
|
+
export function claudeCodeVersionTooOldHint(advertised) {
|
|
201
|
+
return `senpi advertised claude-cli/${advertised}; Anthropic requires a newer Claude Code for this model. senpi refreshes the version automatically when online; set ${CLAUDE_CODE_VERSION_PIN_ENV}=X.Y.Z to advertise a specific one.`;
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=claude-code-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code-version.js","sourceRoot":"","sources":["../../src/utils/claude-code-version.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Claude Code version the Anthropic OAuth `claude-cli/<version>` fingerprint advertises.\n *\n * Anthropic gates new models behind a minimum Claude Code version and answers an older\n * fingerprint with HTTP 400 `claude_code_version_too_old`. A build-time constant goes stale\n * between releases, so the fingerprint is the higher of the bundled floor and the latest\n * published Claude Code release, resolved in the background and cached by the host through\n * `ClaudeCodeVersionStore`. A request never waits on the lookup: it signs with whatever is\n * known at that instant, and a too-old rejection raises the version at once.\n */\nimport type { ProviderEnv } from \"../types.ts\";\nimport { getProviderEnvValue } from \"./provider-env.ts\";\n\n/** Exact `X.Y.Z` pins the fingerprint and skips the network lookup. */\nexport const CLAUDE_CODE_VERSION_PIN_ENV = \"PI_CLAUDE_CODE_VERSION\";\n\n/**\n * Anthropic's native-installer release channel. `stable` lags the npm package during a staged\n * rollout, so `latest` is the channel that matches what a freshly updated Claude Code reports.\n */\nexport const CLAUDE_RELEASE_CHANNEL_URL =\n\t\"https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest\";\nexport const CLAUDE_NPM_DIST_TAG_URL = \"https://registry.npmjs.org/@anthropic-ai/claude-code/latest\";\n\nconst REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;\nconst FETCH_TIMEOUT_MS = 5_000;\nconst EXACT_SEMVER = /^(\\d+)\\.(\\d+)\\.(\\d+)$/;\nconst EMBEDDED_SEMVER = /(\\d+\\.\\d+\\.\\d+)/;\nconst TOO_OLD_REJECTION = /claude_code_version_too_old|does not support this model; version (\\d+\\.\\d+\\.\\d+) or newer/i;\n\nexport interface CachedClaudeCodeVersion {\n\treadonly version: string;\n\treadonly checkedAt: number;\n}\n\nexport interface ClaudeCodeVersionStore {\n\tload(): CachedClaudeCodeVersion | undefined;\n\tsave(record: CachedClaudeCodeVersion): void;\n}\n\nexport interface ClaudeCodeVersionResolverOptions {\n\treadonly floor: string;\n\treadonly pinned?: string | undefined;\n\t/** `null` disables caching and network resolution: the resolver answers the floor or a raised version. */\n\treadonly store: ClaudeCodeVersionStore | null;\n\t/** Serve the cache but never fetch (host offline mode). */\n\treadonly offline?: boolean;\n\treadonly fetch?: typeof fetch;\n\treadonly now?: () => number;\n\treadonly refreshIntervalMs?: number;\n}\n\nexport interface ClaudeCodeVersionResolver {\n\t/** Current fingerprint; schedules one background refresh when the cache is stale. */\n\tget(): string;\n\t/** Refresh now (deduplicated); resolves with the fingerprint afterwards. */\n\trefresh(): Promise<string>;\n\t/** Adopt `version` at once when it is newer than the current fingerprint; returns the fingerprint. */\n\traise(version: string): string;\n}\n\nexport function compareClaudeCodeVersions(a: string, b: string): number {\n\tconst left = a.split(\".\").map(Number);\n\tconst right = b.split(\".\").map(Number);\n\tfor (let i = 0; i < 3; i++) {\n\t\tconst diff = (left[i] ?? 0) - (right[i] ?? 0);\n\t\tif (diff !== 0) return diff;\n\t}\n\treturn 0;\n}\n\nfunction isExactSemver(value: unknown): value is string {\n\treturn typeof value === \"string\" && EXACT_SEMVER.test(value);\n}\n\nfunction highest(candidates: ReadonlyArray<string | undefined>): string | undefined {\n\tlet best: string | undefined;\n\tfor (const candidate of candidates) {\n\t\tif (!isExactSemver(candidate)) continue;\n\t\tif (best === undefined || compareClaudeCodeVersions(candidate, best) > 0) best = candidate;\n\t}\n\treturn best;\n}\n\nasync function fetchText(fetchImpl: typeof fetch, url: string): Promise<string | undefined> {\n\ttry {\n\t\tconst response = await fetchImpl(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n\t\treturn response.ok ? await response.text() : undefined;\n\t} catch {\n\t\t// Offline, DNS failure, or timeout: the caller keeps the floor or the cached value.\n\t\treturn undefined;\n\t}\n}\n\nasync function fetchReleaseChannel(fetchImpl: typeof fetch): Promise<string | undefined> {\n\tconst text = await fetchText(fetchImpl, CLAUDE_RELEASE_CHANNEL_URL);\n\treturn text === undefined ? undefined : EMBEDDED_SEMVER.exec(text.trim())?.[1];\n}\n\nasync function fetchNpmDistTag(fetchImpl: typeof fetch): Promise<string | undefined> {\n\tconst text = await fetchText(fetchImpl, CLAUDE_NPM_DIST_TAG_URL);\n\tif (text === undefined) return undefined;\n\ttry {\n\t\tconst data: unknown = JSON.parse(text);\n\t\tconst version = typeof data === \"object\" && data !== null && \"version\" in data ? data.version : undefined;\n\t\treturn typeof version === \"string\" ? EMBEDDED_SEMVER.exec(version)?.[1] : undefined;\n\t} catch {\n\t\t// A registry error page is not a dist-tag document.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Latest published Claude Code version: the higher of the native-installer `latest` channel and\n * the npm dist-tag, so one lagging or unavailable source cannot hold the fingerprint back.\n */\nexport async function fetchLatestClaudeCodeVersion(fetchImpl: typeof fetch = fetch): Promise<string | undefined> {\n\treturn highest(await Promise.all([fetchReleaseChannel(fetchImpl), fetchNpmDistTag(fetchImpl)]));\n}\n\nexport function requiredClaudeCodeVersionFromError(message: string): string | undefined {\n\tconst match = TOO_OLD_REJECTION.exec(message);\n\tif (!match) return undefined;\n\treturn match[1] ?? /version (\\d+\\.\\d+\\.\\d+) or newer/i.exec(message)?.[1];\n}\n\nexport function isClaudeCodeVersionTooOldError(error: unknown): error is Error & { status: 400 } {\n\treturn error instanceof Error && \"status\" in error && error.status === 400 && TOO_OLD_REJECTION.test(error.message);\n}\n\nexport function createClaudeCodeVersionResolver(options: ClaudeCodeVersionResolverOptions): ClaudeCodeVersionResolver {\n\tconst { floor, store } = options;\n\tconst offline = options.offline === true;\n\tconst fetchImpl = options.fetch ?? fetch;\n\tconst now = options.now ?? Date.now;\n\tconst refreshIntervalMs = options.refreshIntervalMs ?? REFRESH_INTERVAL_MS;\n\tconst pinned = isExactSemver(options.pinned) ? options.pinned : undefined;\n\n\tlet loaded = false;\n\tlet resolved: string | undefined;\n\tlet checkedAt = 0;\n\tlet inflight: Promise<string> | undefined;\n\n\tconst current = (): string =>\n\t\tresolved !== undefined && compareClaudeCodeVersions(resolved, floor) > 0 ? resolved : floor;\n\n\tconst loadCache = (): void => {\n\t\tif (loaded || store === null) return;\n\t\tloaded = true;\n\t\tconst record = store.load();\n\t\tif (record && isExactSemver(record.version) && Number.isFinite(record.checkedAt)) {\n\t\t\tresolved = record.version;\n\t\t\tcheckedAt = record.checkedAt;\n\t\t}\n\t};\n\n\tconst persist = (): void => {\n\t\tif (store !== null && resolved !== undefined) store.save({ version: resolved, checkedAt });\n\t};\n\n\tconst refresh = (): Promise<string> => {\n\t\tif (pinned !== undefined || store === null) return Promise.resolve(pinned ?? current());\n\t\tloadCache();\n\t\tif (offline) return Promise.resolve(current());\n\t\tinflight ??= (async () => {\n\t\t\ttry {\n\t\t\t\tconst latest = await fetchLatestClaudeCodeVersion(fetchImpl);\n\t\t\t\t// A failed lookup still counts as a check: an offline host waits a full interval\n\t\t\t\t// before trying again instead of retrying on every request.\n\t\t\t\tcheckedAt = now();\n\t\t\t\tif (latest !== undefined && (resolved === undefined || compareClaudeCodeVersions(latest, resolved) > 0)) {\n\t\t\t\t\tresolved = latest;\n\t\t\t\t}\n\t\t\t\tpersist();\n\t\t\t} finally {\n\t\t\t\tinflight = undefined;\n\t\t\t}\n\t\t\treturn current();\n\t\t})();\n\t\treturn inflight;\n\t};\n\n\treturn {\n\t\tget() {\n\t\t\tif (pinned !== undefined) return pinned;\n\t\t\tif (store === null) return current();\n\t\t\tloadCache();\n\t\t\tif (!offline && inflight === undefined && now() - checkedAt >= refreshIntervalMs) void refresh();\n\t\t\treturn current();\n\t\t},\n\t\trefresh,\n\t\traise(version) {\n\t\t\tif (pinned !== undefined) return pinned;\n\t\t\tloadCache();\n\t\t\tif (isExactSemver(version) && compareClaudeCodeVersions(version, current()) > 0) {\n\t\t\t\tresolved = version;\n\t\t\t\tpersist();\n\t\t\t}\n\t\t\treturn current();\n\t\t},\n\t};\n}\n\nlet installedStore: ClaudeCodeVersionStore | null = null;\nlet installedOffline = false;\nlet defaultResolver: ClaudeCodeVersionResolver | undefined;\nlet defaultFloor: string | undefined;\n\n/**\n * Hosts with a filesystem install their cache here once at startup (see\n * `utils/claude-code-version-cache.ts`); until then, and in browsers, the resolver answers\n * the floor without touching the network.\n */\nexport function installClaudeCodeVersionStore(\n\tstore: ClaudeCodeVersionStore | null,\n\toptions: { readonly offline?: boolean } = {},\n): void {\n\tinstalledStore = store;\n\tinstalledOffline = options.offline === true;\n\tdefaultResolver = undefined;\n}\n\nfunction resolver(floor: string): ClaudeCodeVersionResolver {\n\tif (defaultResolver === undefined || defaultFloor !== floor) {\n\t\tdefaultFloor = floor;\n\t\tdefaultResolver = createClaudeCodeVersionResolver({ floor, store: installedStore, offline: installedOffline });\n\t}\n\treturn defaultResolver;\n}\n\nfunction pinnedClaudeCodeVersion(env?: ProviderEnv): string | undefined {\n\tconst pin = getProviderEnvValue(CLAUDE_CODE_VERSION_PIN_ENV, env)?.trim();\n\treturn isExactSemver(pin) ? pin : undefined;\n}\n\n/**\n * The version the next OAuth request signs with. `floor` is the bundled Claude Code version the\n * caller declares (the `claudeCodeVersion` constant in `api/anthropic-messages.ts`).\n */\nexport function getClaudeCodeVersion(floor: string, env?: ProviderEnv): string {\n\treturn pinnedClaudeCodeVersion(env) ?? resolver(floor).get();\n}\n\n/**\n * After a `claude_code_version_too_old` rejection: adopt the version the error names, or the\n * latest published one, and report the fingerprint to retry with; `undefined` when nothing\n * newer is available (pinned, or already at the required version).\n */\nexport async function recoverClaudeCodeVersion(\n\terror: Error,\n\tfloor: string,\n\tadvertised: string,\n\tenv?: ProviderEnv,\n): Promise<string | undefined> {\n\tif (pinnedClaudeCodeVersion(env) !== undefined) return undefined;\n\tconst required = requiredClaudeCodeVersionFromError(error.message);\n\tconst next = required === undefined ? await resolver(floor).refresh() : resolver(floor).raise(required);\n\treturn compareClaudeCodeVersions(next, advertised) > 0 ? next : undefined;\n}\n\nexport function claudeCodeVersionTooOldHint(advertised: string): string {\n\treturn `senpi advertised claude-cli/${advertised}; Anthropic requires a newer Claude Code for this model. senpi refreshes the version automatically when online; set ${CLAUDE_CODE_VERSION_PIN_ENV}=X.Y.Z to advertise a specific one.`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor-context-limit.d.ts","sourceRoot":"","sources":["../../src/utils/cursor-context-limit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cursor-context-limit.d.ts","sourceRoot":"","sources":["../../src/utils/cursor-context-limit.ts"],"names":[],"mappings":"AAsBA;;;GAGG;AACH,wBAAgB,kCAAkC,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAO/F;AAsDD,sFAAsF;AACtF,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAG7F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGzE;AAED,qFAAqF;AACrF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAGzF;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAG1D"}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { dirname } from "node:path";
|
|
13
13
|
import { installCursorContextLimitPersistence, getCursorContextLimit as readObservedLimit, recordCursorContextLimit as recordObservedLimit, resetCursorContextLimitStoreForTest as resetObservedLimits, resolveCursorContextWindow as resolveObservedWindow, } from "../cursor/context-limit-store.js";
|
|
14
|
+
import { processSingleton } from "./process-singleton.js";
|
|
14
15
|
/**
|
|
15
16
|
* Same resolution as the conversation rotation store: an explicit override
|
|
16
17
|
* first, then the agent directory the host configured, then the default one.
|
|
@@ -22,53 +23,58 @@ export function resolveCursorContextLimitStorePath(env = process.env) {
|
|
|
22
23
|
const agentDir = env.SENPI_CODING_AGENT_DIR ?? env.CODING_AGENT_DIR ?? `${(env.HOME ?? ".").replace(/\/$/, "")}/.senpi/agent`;
|
|
23
24
|
return `${agentDir.replace(/\/$/, "")}/cursor-context-limits.json`;
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
// One port per process: every bundle copy of this module installs the same port, so the store
|
|
27
|
+
// does not re-hydrate from disk whenever a different copy is called.
|
|
28
|
+
const filePersistence = processSingleton("@earendil-works/pi-ai:cursor-context-limit-file", () => {
|
|
29
|
+
const port = {
|
|
30
|
+
savingEnabled: true,
|
|
31
|
+
load: () => {
|
|
32
|
+
const limits = new Map();
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(readFileSync(resolveCursorContextLimitStorePath(), "utf8"));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
// No file on first run, and a truncated or hand-edited one is the same
|
|
39
|
+
// situation for a cache: the catalog window is the documented fallback.
|
|
40
|
+
if (error instanceof Error)
|
|
41
|
+
return limits;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
37
45
|
return limits;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
for (const [modelId, maxTokens] of Object.entries(parsed)) {
|
|
47
|
+
if (typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0) {
|
|
48
|
+
limits.set(modelId, maxTokens);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
41
51
|
return limits;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
},
|
|
53
|
+
save: (limits) => {
|
|
54
|
+
if (!port.savingEnabled)
|
|
55
|
+
return;
|
|
56
|
+
const path = resolveCursorContextLimitStorePath();
|
|
57
|
+
const record = {};
|
|
58
|
+
for (const [modelId, maxTokens] of limits)
|
|
59
|
+
record[modelId] = maxTokens;
|
|
60
|
+
try {
|
|
61
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
62
|
+
const temporaryPath = `${path}.${process.pid}.tmp`;
|
|
63
|
+
writeFileSync(temporaryPath, `${JSON.stringify(record, null, 2)}\n`);
|
|
64
|
+
renameSync(temporaryPath, path);
|
|
45
65
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const temporaryPath = `${path}.${process.pid}.tmp`;
|
|
59
|
-
writeFileSync(temporaryPath, `${JSON.stringify(record, null, 2)}\n`);
|
|
60
|
-
renameSync(temporaryPath, path);
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
// This file is a cache for the next process. A read-only agent directory
|
|
64
|
-
// must not fail the live turn that observed the limit, and one failure
|
|
65
|
-
// disables saving so a broken path cannot cost a write per checkpoint.
|
|
66
|
-
if (!(error instanceof Error))
|
|
67
|
-
throw error;
|
|
68
|
-
savingEnabled = false;
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
};
|
|
66
|
+
catch (error) {
|
|
67
|
+
// This file is a cache for the next process. A read-only agent directory
|
|
68
|
+
// must not fail the live turn that observed the limit, and one failure
|
|
69
|
+
// disables saving so a broken path cannot cost a write per checkpoint.
|
|
70
|
+
if (!(error instanceof Error))
|
|
71
|
+
throw error;
|
|
72
|
+
port.savingEnabled = false;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
return port;
|
|
77
|
+
});
|
|
72
78
|
function ensurePersistenceInstalled() {
|
|
73
79
|
installCursorContextLimitPersistence(filePersistence);
|
|
74
80
|
}
|
|
@@ -87,7 +93,7 @@ export function resolveCursorContextWindow(modelId, catalogWindow) {
|
|
|
87
93
|
return resolveObservedWindow(modelId, catalogWindow);
|
|
88
94
|
}
|
|
89
95
|
export function resetCursorContextLimitStoreForTest() {
|
|
90
|
-
savingEnabled = true;
|
|
96
|
+
filePersistence.savingEnabled = true;
|
|
91
97
|
resetObservedLimits();
|
|
92
98
|
}
|
|
93
99
|
//# sourceMappingURL=cursor-context-limit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor-context-limit.js","sourceRoot":"","sources":["../../src/utils/cursor-context-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAEN,oCAAoC,EACpC,qBAAqB,IAAI,iBAAiB,EAC1C,wBAAwB,IAAI,mBAAmB,EAC/C,mCAAmC,IAAI,mBAAmB,EAC1D,0BAA0B,IAAI,qBAAqB,GACnD,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"cursor-context-limit.js","sourceRoot":"","sources":["../../src/utils/cursor-context-limit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Node entry point for the observed Cursor context limits.\n *\n * The store itself (`cursor/context-limit-store.ts`) is browser-safe because the\n * catalog builders that read it are bundled for the browser. Persistence needs\n * `node:fs`, so it lives here and is installed on first use rather than as an\n * import side effect, which a bundler is free to drop for a package declared\n * side-effect-free. Node callers - the `cursor-agent` stream and the coding\n * agent - use this module; browser-facing code uses the store directly.\n */\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport {\n\ttype CursorContextLimitPersistence,\n\tinstallCursorContextLimitPersistence,\n\tgetCursorContextLimit as readObservedLimit,\n\trecordCursorContextLimit as recordObservedLimit,\n\tresetCursorContextLimitStoreForTest as resetObservedLimits,\n\tresolveCursorContextWindow as resolveObservedWindow,\n} from \"../cursor/context-limit-store.ts\";\nimport { processSingleton } from \"./process-singleton.ts\";\n\n/**\n * Same resolution as the conversation rotation store: an explicit override\n * first, then the agent directory the host configured, then the default one.\n */\nexport function resolveCursorContextLimitStorePath(env: NodeJS.ProcessEnv = process.env): string {\n\tif (env.CURSOR_CONTEXT_LIMIT_STORE) {\n\t\treturn env.CURSOR_CONTEXT_LIMIT_STORE;\n\t}\n\tconst agentDir =\n\t\tenv.SENPI_CODING_AGENT_DIR ?? env.CODING_AGENT_DIR ?? `${(env.HOME ?? \".\").replace(/\\/$/, \"\")}/.senpi/agent`;\n\treturn `${agentDir.replace(/\\/$/, \"\")}/cursor-context-limits.json`;\n}\n\ntype FilePersistence = CursorContextLimitPersistence & { savingEnabled: boolean };\n\n// One port per process: every bundle copy of this module installs the same port, so the store\n// does not re-hydrate from disk whenever a different copy is called.\nconst filePersistence = processSingleton<FilePersistence>(\"@earendil-works/pi-ai:cursor-context-limit-file\", () => {\n\tconst port: FilePersistence = {\n\t\tsavingEnabled: true,\n\t\tload: () => {\n\t\t\tconst limits = new Map<string, number>();\n\t\t\tlet parsed: unknown;\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(readFileSync(resolveCursorContextLimitStorePath(), \"utf8\"));\n\t\t\t} catch (error) {\n\t\t\t\t// No file on first run, and a truncated or hand-edited one is the same\n\t\t\t\t// situation for a cache: the catalog window is the documented fallback.\n\t\t\t\tif (error instanceof Error) return limits;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return limits;\n\t\t\tfor (const [modelId, maxTokens] of Object.entries(parsed)) {\n\t\t\t\tif (typeof maxTokens === \"number\" && Number.isFinite(maxTokens) && maxTokens > 0) {\n\t\t\t\t\tlimits.set(modelId, maxTokens);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn limits;\n\t\t},\n\t\tsave: (limits) => {\n\t\t\tif (!port.savingEnabled) return;\n\t\t\tconst path = resolveCursorContextLimitStorePath();\n\t\t\tconst record: Record<string, number> = {};\n\t\t\tfor (const [modelId, maxTokens] of limits) record[modelId] = maxTokens;\n\t\t\ttry {\n\t\t\t\tmkdirSync(dirname(path), { recursive: true });\n\t\t\t\tconst temporaryPath = `${path}.${process.pid}.tmp`;\n\t\t\t\twriteFileSync(temporaryPath, `${JSON.stringify(record, null, 2)}\\n`);\n\t\t\t\trenameSync(temporaryPath, path);\n\t\t\t} catch (error) {\n\t\t\t\t// This file is a cache for the next process. A read-only agent directory\n\t\t\t\t// must not fail the live turn that observed the limit, and one failure\n\t\t\t\t// disables saving so a broken path cannot cost a write per checkpoint.\n\t\t\t\tif (!(error instanceof Error)) throw error;\n\t\t\t\tport.savingEnabled = false;\n\t\t\t}\n\t\t},\n\t};\n\treturn port;\n});\n\nfunction ensurePersistenceInstalled(): void {\n\tinstallCursorContextLimitPersistence(filePersistence);\n}\n\n/** Records the ceiling Cursor reported for `modelId`; ignores non-positive values. */\nexport function recordCursorContextLimit(modelId: string, maxTokens: number | undefined): void {\n\tensurePersistenceInstalled();\n\trecordObservedLimit(modelId, maxTokens);\n}\n\nexport function getCursorContextLimit(modelId: string): number | undefined {\n\tensurePersistenceInstalled();\n\treturn readObservedLimit(modelId);\n}\n\n/** The window to trust for `modelId`: what the server reported, else the catalog. */\nexport function resolveCursorContextWindow(modelId: string, catalogWindow: number): number {\n\tensurePersistenceInstalled();\n\treturn resolveObservedWindow(modelId, catalogWindow);\n}\n\nexport function resetCursorContextLimitStoreForTest(): void {\n\tfilePersistence.savingEnabled = true;\n\tresetObservedLimits();\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AssistantMessage, ProviderId } from "../types.ts";
|
|
2
|
+
export declare const GITHUB_COPILOT_TOOL_LIMIT = 128;
|
|
3
|
+
export declare const GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC = "github_copilot_tool_limit";
|
|
4
|
+
export declare function limitGitHubCopilotTools<T>(provider: ProviderId, tools: T[] | undefined, toolChoice?: unknown): {
|
|
5
|
+
tools: T[] | undefined;
|
|
6
|
+
omittedCount: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function recordGitHubCopilotToolLimit(message: AssistantMessage, omittedCount: number): void;
|
|
9
|
+
export declare function formatGitHubCopilotToolLimitError(message: AssistantMessage, errorMessage: string): string;
|
|
10
|
+
//# sourceMappingURL=github-copilot-tool-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-copilot-tool-limit.d.ts","sourceRoot":"","sources":["../../src/utils/github-copilot-tool-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGhE,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,oCAAoC,8BAA8B,CAAC;AA2BhF,wBAAgB,uBAAuB,CAAC,CAAC,EACxC,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,CAAC,EAAE,GAAG,SAAS,EACtB,UAAU,CAAC,EAAE,OAAO,GAClB;IAAE,KAAK,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAmBlD;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAgBlG;AAED,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAazG"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { appendAssistantMessageDiagnostic } from "./diagnostics.js";
|
|
2
|
+
export const GITHUB_COPILOT_TOOL_LIMIT = 128;
|
|
3
|
+
export const GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC = "github_copilot_tool_limit";
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null;
|
|
6
|
+
}
|
|
7
|
+
function selectedToolName(toolChoice) {
|
|
8
|
+
if (!isRecord(toolChoice))
|
|
9
|
+
return undefined;
|
|
10
|
+
if (toolChoice.type === "function") {
|
|
11
|
+
if (isRecord(toolChoice.function) && typeof toolChoice.function.name === "string") {
|
|
12
|
+
return toolChoice.function.name;
|
|
13
|
+
}
|
|
14
|
+
return typeof toolChoice.name === "string" ? toolChoice.name : undefined;
|
|
15
|
+
}
|
|
16
|
+
if (toolChoice.type !== "custom" && toolChoice.type !== "tool")
|
|
17
|
+
return undefined;
|
|
18
|
+
return typeof toolChoice.name === "string" ? toolChoice.name : undefined;
|
|
19
|
+
}
|
|
20
|
+
function toolName(tool) {
|
|
21
|
+
if (!isRecord(tool))
|
|
22
|
+
return undefined;
|
|
23
|
+
if (tool.type === "function") {
|
|
24
|
+
if (isRecord(tool.function) && typeof tool.function.name === "string")
|
|
25
|
+
return tool.function.name;
|
|
26
|
+
return typeof tool.name === "string" ? tool.name : undefined;
|
|
27
|
+
}
|
|
28
|
+
return typeof tool.name === "string" ? tool.name : undefined;
|
|
29
|
+
}
|
|
30
|
+
export function limitGitHubCopilotTools(provider, tools, toolChoice) {
|
|
31
|
+
if (provider !== "github-copilot" || tools === undefined || tools.length <= GITHUB_COPILOT_TOOL_LIMIT) {
|
|
32
|
+
return { tools, omittedCount: 0 };
|
|
33
|
+
}
|
|
34
|
+
const forcedName = selectedToolName(toolChoice);
|
|
35
|
+
const forcedIndex = forcedName === undefined ? -1 : tools.findIndex((tool) => toolName(tool) === forcedName);
|
|
36
|
+
if (forcedIndex >= GITHUB_COPILOT_TOOL_LIMIT) {
|
|
37
|
+
const forcedTool = tools[forcedIndex];
|
|
38
|
+
if (forcedTool !== undefined) {
|
|
39
|
+
return {
|
|
40
|
+
tools: [...tools.slice(0, GITHUB_COPILOT_TOOL_LIMIT - 1), forcedTool],
|
|
41
|
+
omittedCount: tools.length - GITHUB_COPILOT_TOOL_LIMIT,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
tools: tools.slice(0, GITHUB_COPILOT_TOOL_LIMIT),
|
|
47
|
+
omittedCount: tools.length - GITHUB_COPILOT_TOOL_LIMIT,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function recordGitHubCopilotToolLimit(message, omittedCount) {
|
|
51
|
+
if (omittedCount === 0 ||
|
|
52
|
+
message.diagnostics?.some((diagnostic) => diagnostic.type === GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
appendAssistantMessageDiagnostic(message, {
|
|
56
|
+
type: GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC,
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
details: {
|
|
59
|
+
limit: GITHUB_COPILOT_TOOL_LIMIT,
|
|
60
|
+
omittedCount,
|
|
61
|
+
message: `GitHub Copilot accepts at most ${GITHUB_COPILOT_TOOL_LIMIT} tools on endpoints without tool search; senpi omitted ${omittedCount} excess tool definition${omittedCount === 1 ? "" : "s"}.`,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export function formatGitHubCopilotToolLimitError(message, errorMessage) {
|
|
66
|
+
const toolLimitApplied = message.diagnostics?.some((diagnostic) => diagnostic.type === GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC);
|
|
67
|
+
if (message.provider !== "github-copilot" ||
|
|
68
|
+
!toolLimitApplied ||
|
|
69
|
+
message.providerDiagnostic?.httpStatus !== 400 ||
|
|
70
|
+
!/400 Bad Request$/i.test(errorMessage)) {
|
|
71
|
+
return errorMessage;
|
|
72
|
+
}
|
|
73
|
+
return `GitHub Copilot rejected the request after senpi limited its tool list to ${GITHUB_COPILOT_TOOL_LIMIT}. The endpoint may enforce a lower limit or count additional hosted tools. (${errorMessage})`;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=github-copilot-tool-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-copilot-tool-limit.js","sourceRoot":"","sources":["../../src/utils/github-copilot-tool-limit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage, ProviderId } from \"../types.ts\";\nimport { appendAssistantMessageDiagnostic } from \"./diagnostics.ts\";\n\nexport const GITHUB_COPILOT_TOOL_LIMIT = 128;\nexport const GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC = \"github_copilot_tool_limit\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction selectedToolName(toolChoice: unknown): string | undefined {\n\tif (!isRecord(toolChoice)) return undefined;\n\tif (toolChoice.type === \"function\") {\n\t\tif (isRecord(toolChoice.function) && typeof toolChoice.function.name === \"string\") {\n\t\t\treturn toolChoice.function.name;\n\t\t}\n\t\treturn typeof toolChoice.name === \"string\" ? toolChoice.name : undefined;\n\t}\n\tif (toolChoice.type !== \"custom\" && toolChoice.type !== \"tool\") return undefined;\n\treturn typeof toolChoice.name === \"string\" ? toolChoice.name : undefined;\n}\n\nfunction toolName(tool: unknown): string | undefined {\n\tif (!isRecord(tool)) return undefined;\n\tif (tool.type === \"function\") {\n\t\tif (isRecord(tool.function) && typeof tool.function.name === \"string\") return tool.function.name;\n\t\treturn typeof tool.name === \"string\" ? tool.name : undefined;\n\t}\n\treturn typeof tool.name === \"string\" ? tool.name : undefined;\n}\n\nexport function limitGitHubCopilotTools<T>(\n\tprovider: ProviderId,\n\ttools: T[] | undefined,\n\ttoolChoice?: unknown,\n): { tools: T[] | undefined; omittedCount: number } {\n\tif (provider !== \"github-copilot\" || tools === undefined || tools.length <= GITHUB_COPILOT_TOOL_LIMIT) {\n\t\treturn { tools, omittedCount: 0 };\n\t}\n\tconst forcedName = selectedToolName(toolChoice);\n\tconst forcedIndex = forcedName === undefined ? -1 : tools.findIndex((tool) => toolName(tool) === forcedName);\n\tif (forcedIndex >= GITHUB_COPILOT_TOOL_LIMIT) {\n\t\tconst forcedTool = tools[forcedIndex];\n\t\tif (forcedTool !== undefined) {\n\t\t\treturn {\n\t\t\t\ttools: [...tools.slice(0, GITHUB_COPILOT_TOOL_LIMIT - 1), forcedTool],\n\t\t\t\tomittedCount: tools.length - GITHUB_COPILOT_TOOL_LIMIT,\n\t\t\t};\n\t\t}\n\t}\n\treturn {\n\t\ttools: tools.slice(0, GITHUB_COPILOT_TOOL_LIMIT),\n\t\tomittedCount: tools.length - GITHUB_COPILOT_TOOL_LIMIT,\n\t};\n}\n\nexport function recordGitHubCopilotToolLimit(message: AssistantMessage, omittedCount: number): void {\n\tif (\n\t\tomittedCount === 0 ||\n\t\tmessage.diagnostics?.some((diagnostic) => diagnostic.type === GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC)\n\t) {\n\t\treturn;\n\t}\n\tappendAssistantMessageDiagnostic(message, {\n\t\ttype: GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC,\n\t\ttimestamp: Date.now(),\n\t\tdetails: {\n\t\t\tlimit: GITHUB_COPILOT_TOOL_LIMIT,\n\t\t\tomittedCount,\n\t\t\tmessage: `GitHub Copilot accepts at most ${GITHUB_COPILOT_TOOL_LIMIT} tools on endpoints without tool search; senpi omitted ${omittedCount} excess tool definition${omittedCount === 1 ? \"\" : \"s\"}.`,\n\t\t},\n\t});\n}\n\nexport function formatGitHubCopilotToolLimitError(message: AssistantMessage, errorMessage: string): string {\n\tconst toolLimitApplied = message.diagnostics?.some(\n\t\t(diagnostic) => diagnostic.type === GITHUB_COPILOT_TOOL_LIMIT_DIAGNOSTIC,\n\t);\n\tif (\n\t\tmessage.provider !== \"github-copilot\" ||\n\t\t!toolLimitApplied ||\n\t\tmessage.providerDiagnostic?.httpStatus !== 400 ||\n\t\t!/400 Bad Request$/i.test(errorMessage)\n\t) {\n\t\treturn errorMessage;\n\t}\n\treturn `GitHub Copilot rejected the request after senpi limited its tool list to ${GITHUB_COPILOT_TOOL_LIMIT}. The endpoint may enforce a lower limit or count additional hosted tools. (${errorMessage})`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overflow.d.ts","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"overflow.d.ts","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA+GpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAwC5F;AACD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAEhG;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAYD,wBAAgB,gCAAgC,CAC/C,OAAO,EAAE;IACR,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3G,EACD,eAAe,EAAE,MAAM,GACrB,OAAO,CAKT;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC7C,OAAO,EAAE;IACR,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3G,EACD,aAAa,EAAE,MAAM,GACnB,OAAO,CAST;AAED,wBAAgB,kCAAkC,CAAC,OAAO,EAAE;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3G,GAAG,OAAO,CAKV;AAED,wBAAgB,yCAAyC,CAAC,OAAO,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,GAAG,OAAO,CAErH;AAED,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAGnG;AAED,wBAAgB,gCAAgC,CAAC,CAAC,SAAS;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAE,EACrH,QAAQ,EAAE,CAAC,EACX,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,GACxB,CAAC,CAIH"}
|
package/dist/utils/overflow.js
CHANGED
|
@@ -36,9 +36,11 @@
|
|
|
36
36
|
* - DashScope/Qwen: "Range of input length should be [1, X]" (HTTP 400 invalid_parameter_error)
|
|
37
37
|
* - Ollama: Some deployments truncate silently, others return errors like "prompt too long; exceeded max context length by X tokens"
|
|
38
38
|
* - pi-ai pre-flight guard (api/context-room.ts): "Context window exhausted: the conversation is estimated at X of Y tokens, ..." - raised before any provider call
|
|
39
|
+
* - anthropic-subscription cold-seed budget: "The conversation is too long to resend (about X tokens, limit Y). Compacting it and retrying." - raised before the re-send is dispatched
|
|
39
40
|
*/
|
|
40
41
|
const OVERFLOW_PATTERNS = [
|
|
41
42
|
/^Context window exhausted: /, // pi-ai pre-flight guard: no answer room left, provider never called
|
|
43
|
+
/^The conversation is too long to resend \(about \d+ tokens, limit \d+\)/, // anthropic-subscription cold-seed budget: re-send refused before dispatch
|
|
42
44
|
/prompt is too long/i, // Anthropic token overflow
|
|
43
45
|
/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)
|
|
44
46
|
/input is too long for requested model/i, // Amazon Bedrock
|
|
@@ -50,7 +52,7 @@ const OVERFLOW_PATTERNS = [
|
|
|
50
52
|
/maximum context length is \d+ tokens/i, // OpenRouter (most backends)
|
|
51
53
|
/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i, // OpenRouter/Poolside
|
|
52
54
|
/input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i, // Together AI
|
|
53
|
-
/exceeds the limit of \d+/i, // GitHub Copilot
|
|
55
|
+
/model_max_prompt_tokens_exceeded|exceeds the limit of \d+/i, // GitHub Copilot
|
|
54
56
|
/exceeds the available context size/i, // llama.cpp server
|
|
55
57
|
/greater than the context length/i, // LM Studio
|
|
56
58
|
/context window exceeds limit/i, // MiniMax
|