sneakoscope 4.0.4 → 4.0.6
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 +9 -9
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/core/codex-app/glm-profile-schema.js +5 -1
- package/dist/core/commands/glm-command.js +51 -6
- package/dist/core/commands/mad-sks-command.js +65 -9
- package/dist/core/fsx.js +1 -1
- package/dist/core/perf/lru-cache.js +33 -0
- package/dist/core/providers/glm/glm-52-profile.js +14 -7
- package/dist/core/providers/glm/glm-52-request.js +43 -12
- package/dist/core/providers/glm/glm-52-response-guard.js +1 -2
- package/dist/core/providers/glm/glm-52-settings.js +50 -8
- package/dist/core/providers/glm/glm-bench.js +127 -0
- package/dist/core/providers/glm/glm-context-budget.js +15 -0
- package/dist/core/providers/glm/glm-context-cache.js +9 -0
- package/dist/core/providers/glm/glm-direct-run.js +140 -0
- package/dist/core/providers/glm/glm-interactive-launch.js +5 -0
- package/dist/core/providers/glm/glm-latency-trace.js +40 -0
- package/dist/core/providers/glm/glm-loop-guard.js +31 -0
- package/dist/core/providers/glm/glm-mad-launch.js +18 -3
- package/dist/core/providers/glm/glm-mad-mode.js +48 -20
- package/dist/core/providers/glm/glm-model-meta-cache.js +19 -0
- package/dist/core/providers/glm/glm-patch-apply.js +58 -0
- package/dist/core/providers/glm/glm-patch-parser.js +19 -0
- package/dist/core/providers/glm/glm-profile-resolver.js +104 -0
- package/dist/core/providers/glm/glm-readiness.js +5 -0
- package/dist/core/providers/glm/glm-reasoning-policy.js +15 -0
- package/dist/core/providers/glm/glm-request-cache.js +64 -0
- package/dist/core/providers/glm/glm-run-controller.js +66 -0
- package/dist/core/providers/glm/glm-run-state.js +11 -0
- package/dist/core/providers/glm/glm-run-timeout.js +31 -0
- package/dist/core/providers/glm/glm-speed-context.js +82 -0
- package/dist/core/providers/glm/glm-speed-gate.js +40 -0
- package/dist/core/providers/glm/glm-speed-output-parser.js +40 -0
- package/dist/core/providers/glm/glm-tool-schema-cache.js +19 -0
- package/dist/core/providers/openrouter/openrouter-client.js +21 -1
- package/dist/core/providers/openrouter/openrouter-stream.js +94 -0
- package/dist/core/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { parseUnifiedDiffPatch } from './glm-patch-parser.js';
|
|
3
|
+
const PROTECTED_PATH = /(^|\/)(\.github|dist|node_modules)(\/|$)/;
|
|
4
|
+
export async function checkAndApplyGlmPatch(input) {
|
|
5
|
+
const parsed = parseUnifiedDiffPatch(input.patch);
|
|
6
|
+
if (parsed.empty) {
|
|
7
|
+
return issue('glm_patch_empty', 'GLM output did not contain a non-empty unified diff.');
|
|
8
|
+
}
|
|
9
|
+
const blockedPath = parsed.touchedPaths.find((file) => PROTECTED_PATH.test(file));
|
|
10
|
+
if (blockedPath) {
|
|
11
|
+
return issue('glm_patch_protected_path', `GLM patch touched protected path: ${blockedPath}`);
|
|
12
|
+
}
|
|
13
|
+
const check = await runGitApply(input.cwd, input.patch, ['apply', '--check', '--whitespace=nowarn', '-']);
|
|
14
|
+
if (check.code !== 0) {
|
|
15
|
+
return issue('glm_patch_gate_failed', check.stderr || check.stdout || 'git apply --check failed.');
|
|
16
|
+
}
|
|
17
|
+
if (!input.apply) {
|
|
18
|
+
return {
|
|
19
|
+
ok: true,
|
|
20
|
+
value: {
|
|
21
|
+
checked: true,
|
|
22
|
+
applied: false,
|
|
23
|
+
touchedPaths: parsed.touchedPaths,
|
|
24
|
+
stdout: check.stdout,
|
|
25
|
+
stderr: check.stderr
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const applied = await runGitApply(input.cwd, input.patch, ['apply', '--whitespace=nowarn', '-']);
|
|
30
|
+
if (applied.code !== 0) {
|
|
31
|
+
return issue('glm_patch_apply_failed', applied.stderr || applied.stdout || 'git apply failed.');
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
value: {
|
|
36
|
+
checked: true,
|
|
37
|
+
applied: true,
|
|
38
|
+
touchedPaths: parsed.touchedPaths,
|
|
39
|
+
stdout: applied.stdout,
|
|
40
|
+
stderr: applied.stderr
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function issue(code, message) {
|
|
45
|
+
return { ok: false, error: { code, message, severity: 'blocked' } };
|
|
46
|
+
}
|
|
47
|
+
function runGitApply(cwd, patch, args) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
const child = spawn('git', [...args], { cwd, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
50
|
+
let stdout = '';
|
|
51
|
+
let stderr = '';
|
|
52
|
+
child.stdout.on('data', (chunk) => { stdout += String(chunk); });
|
|
53
|
+
child.stderr.on('data', (chunk) => { stderr += String(chunk); });
|
|
54
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
|
55
|
+
child.stdin.end(patch);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=glm-patch-apply.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function parseUnifiedDiffPatch(patch) {
|
|
2
|
+
const touched = new Set();
|
|
3
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
4
|
+
const diff = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
|
|
5
|
+
if (diff?.[1])
|
|
6
|
+
touched.add(diff[1]);
|
|
7
|
+
if (diff?.[2])
|
|
8
|
+
touched.add(diff[2]);
|
|
9
|
+
const file = line.match(/^(?:---|\+\+\+) [ab]\/(.+)$/);
|
|
10
|
+
if (file?.[1] && file[1] !== '/dev/null')
|
|
11
|
+
touched.add(file[1]);
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
patch,
|
|
15
|
+
touchedPaths: [...touched],
|
|
16
|
+
empty: !patch.trim() || !/^diff --git /m.test(patch)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=glm-patch-parser.js.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readOption } from '../../../cli/args.js';
|
|
2
|
+
import { GLM_DEEP_MODE, GLM_DEEP_PROFILE, GLM_SPEED_MODE, GLM_SPEED_PROFILE, GLM_STRICT_MODE, GLM_STRICT_PROFILE, GLM_XHIGH_MODE, GLM_XHIGH_PROFILE } from './glm-52-settings.js';
|
|
3
|
+
export function resolveGlmProfileFromArgs(args = []) {
|
|
4
|
+
const list = args.map(String);
|
|
5
|
+
const exactProvider = readOption(list, '--exact-provider', null);
|
|
6
|
+
const providerBlockers = exactProvider && !isValidOpenRouterProviderSlug(exactProvider)
|
|
7
|
+
? [`invalid_openrouter_provider_slug:${exactProvider}`]
|
|
8
|
+
: [];
|
|
9
|
+
const base = list.includes('--xhigh')
|
|
10
|
+
? profileFromConst('xhigh')
|
|
11
|
+
: list.includes('--strict')
|
|
12
|
+
? profileFromConst('strict')
|
|
13
|
+
: list.includes('--deep')
|
|
14
|
+
? profileFromConst('deep')
|
|
15
|
+
: profileFromConst('speed');
|
|
16
|
+
const provider = exactProvider && !providerBlockers.length
|
|
17
|
+
? {
|
|
18
|
+
allow_fallbacks: false,
|
|
19
|
+
require_parameters: base.provider.require_parameters,
|
|
20
|
+
order: [exactProvider]
|
|
21
|
+
}
|
|
22
|
+
: list.includes('--ttft')
|
|
23
|
+
? {
|
|
24
|
+
...base.provider,
|
|
25
|
+
sort: 'latency',
|
|
26
|
+
preferred_max_latency: { p50: 1.5, p90: 4 }
|
|
27
|
+
}
|
|
28
|
+
: base.provider;
|
|
29
|
+
return {
|
|
30
|
+
...base,
|
|
31
|
+
provider,
|
|
32
|
+
blockers: providerBlockers
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function profileFromConst(name) {
|
|
36
|
+
if (name === 'deep') {
|
|
37
|
+
return {
|
|
38
|
+
name,
|
|
39
|
+
mode: GLM_DEEP_MODE,
|
|
40
|
+
stream: GLM_DEEP_PROFILE.stream,
|
|
41
|
+
max_tokens: GLM_DEEP_PROFILE.max_tokens,
|
|
42
|
+
temperature: GLM_DEEP_PROFILE.temperature,
|
|
43
|
+
top_p: GLM_DEEP_PROFILE.top_p,
|
|
44
|
+
tool_choice: GLM_DEEP_PROFILE.tool_choice,
|
|
45
|
+
parallel_tool_calls: GLM_DEEP_PROFILE.parallel_tool_calls,
|
|
46
|
+
provider: GLM_DEEP_PROFILE.provider,
|
|
47
|
+
reasoning_effort: GLM_DEEP_PROFILE.reasoning_effort,
|
|
48
|
+
blockers: []
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (name === 'xhigh') {
|
|
52
|
+
return {
|
|
53
|
+
...profileFromConst('deep'),
|
|
54
|
+
name,
|
|
55
|
+
mode: GLM_XHIGH_MODE,
|
|
56
|
+
max_tokens: GLM_XHIGH_PROFILE.max_tokens,
|
|
57
|
+
reasoning_effort: GLM_XHIGH_PROFILE.reasoning_effort,
|
|
58
|
+
blockers: []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (name === 'strict') {
|
|
62
|
+
return {
|
|
63
|
+
...profileFromConst('deep'),
|
|
64
|
+
name,
|
|
65
|
+
mode: GLM_STRICT_MODE,
|
|
66
|
+
response_format: {
|
|
67
|
+
type: 'json_schema',
|
|
68
|
+
json_schema: {
|
|
69
|
+
name: 'sks_glm_strict_proof',
|
|
70
|
+
strict: true,
|
|
71
|
+
schema: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
additionalProperties: false,
|
|
74
|
+
properties: {
|
|
75
|
+
summary: { type: 'string' },
|
|
76
|
+
patch: { type: 'string' },
|
|
77
|
+
proof: { type: 'object' }
|
|
78
|
+
},
|
|
79
|
+
required: ['summary', 'patch', 'proof']
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
blockers: []
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
name,
|
|
88
|
+
mode: GLM_SPEED_MODE,
|
|
89
|
+
stream: GLM_SPEED_PROFILE.stream,
|
|
90
|
+
max_tokens: GLM_SPEED_PROFILE.max_tokens,
|
|
91
|
+
temperature: GLM_SPEED_PROFILE.temperature,
|
|
92
|
+
top_p: GLM_SPEED_PROFILE.top_p,
|
|
93
|
+
tool_choice: GLM_SPEED_PROFILE.tool_choice,
|
|
94
|
+
parallel_tool_calls: GLM_SPEED_PROFILE.parallel_tool_calls,
|
|
95
|
+
provider: GLM_SPEED_PROFILE.provider,
|
|
96
|
+
reasoning_effort: GLM_SPEED_PROFILE.reasoning_effort,
|
|
97
|
+
stop: ['</sks_patch>'],
|
|
98
|
+
blockers: []
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function isValidOpenRouterProviderSlug(value) {
|
|
102
|
+
return /^(?!.*(?:^|\/)\.\.?(?:\/|$))[a-z0-9][a-z0-9._-]{0,63}(?:\/[a-z0-9][a-z0-9._-]{0,63}){0,3}$/i.test(value);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=glm-profile-resolver.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const FAST_REASONING_ORDER = ['none', 'minimal', 'low'];
|
|
2
|
+
export function buildFastReasoningConfig(meta = null) {
|
|
3
|
+
if (meta?.mandatory === true)
|
|
4
|
+
return { exclude: true };
|
|
5
|
+
const supported = new Set(meta?.supported_efforts || []);
|
|
6
|
+
for (const effort of FAST_REASONING_ORDER) {
|
|
7
|
+
if (supported.has(effort))
|
|
8
|
+
return { effort, exclude: true };
|
|
9
|
+
}
|
|
10
|
+
return { exclude: true };
|
|
11
|
+
}
|
|
12
|
+
export function buildDeepReasoningConfig(effort) {
|
|
13
|
+
return { effort, exclude: true };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=glm-reasoning-policy.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { SksLruCache } from '../../perf/lru-cache.js';
|
|
3
|
+
export function createGlmEncodedRequestCache(maxEntries = 128) {
|
|
4
|
+
return new SksLruCache(maxEntries);
|
|
5
|
+
}
|
|
6
|
+
export function encodeGlmRequestWithCache(request, cache = defaultEncodedRequestCache) {
|
|
7
|
+
const key = digestRequestForCache(request);
|
|
8
|
+
const hit = cache.get(key);
|
|
9
|
+
const body = JSON.stringify(request);
|
|
10
|
+
if (hit)
|
|
11
|
+
return { body: hit.bodyStored ? hit.body : body, entry: hit, cacheHit: true };
|
|
12
|
+
if (containsSecretLikeContent(body)) {
|
|
13
|
+
const entry = {
|
|
14
|
+
key,
|
|
15
|
+
body: '',
|
|
16
|
+
bodySha256: crypto.createHash('sha256').update(body).digest('hex'),
|
|
17
|
+
byteLength: Buffer.byteLength(body),
|
|
18
|
+
createdAt: Date.now(),
|
|
19
|
+
bodyStored: false,
|
|
20
|
+
skippedReason: 'secret_like_request_body_not_cached'
|
|
21
|
+
};
|
|
22
|
+
return { body, entry, cacheHit: false };
|
|
23
|
+
}
|
|
24
|
+
const entry = {
|
|
25
|
+
key,
|
|
26
|
+
body,
|
|
27
|
+
bodySha256: crypto.createHash('sha256').update(body).digest('hex'),
|
|
28
|
+
byteLength: Buffer.byteLength(body),
|
|
29
|
+
createdAt: Date.now(),
|
|
30
|
+
bodyStored: true
|
|
31
|
+
};
|
|
32
|
+
cache.set(key, entry);
|
|
33
|
+
return { body, entry, cacheHit: false };
|
|
34
|
+
}
|
|
35
|
+
export function digestRequestForCache(request) {
|
|
36
|
+
const safe = {
|
|
37
|
+
model: request.model,
|
|
38
|
+
messages: request.messages,
|
|
39
|
+
tools: request.tools || null,
|
|
40
|
+
response_format: request.response_format || null,
|
|
41
|
+
provider: request.provider || null,
|
|
42
|
+
max_tokens: request.max_tokens || null,
|
|
43
|
+
temperature: request.temperature || null,
|
|
44
|
+
top_p: request.top_p || null,
|
|
45
|
+
tool_choice: request.tool_choice || null,
|
|
46
|
+
parallel_tool_calls: request.parallel_tool_calls || null,
|
|
47
|
+
reasoning: request.reasoning || null,
|
|
48
|
+
session_id: request.session_id || null
|
|
49
|
+
};
|
|
50
|
+
return crypto.createHash('sha256').update(stableStringify(safe)).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
function containsSecretLikeContent(body) {
|
|
53
|
+
return /\b(?:Bearer\s+[A-Za-z0-9._~+/-]+|sk-(?:or-)?[A-Za-z0-9_-]{12,}|OPENROUTER_API_KEY|SKS_OPENROUTER_API_KEY)\b/.test(body);
|
|
54
|
+
}
|
|
55
|
+
function stableStringify(value) {
|
|
56
|
+
if (!value || typeof value !== 'object')
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
if (Array.isArray(value))
|
|
59
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
60
|
+
const object = value;
|
|
61
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(',')}}`;
|
|
62
|
+
}
|
|
63
|
+
const defaultEncodedRequestCache = createGlmEncodedRequestCache();
|
|
64
|
+
//# sourceMappingURL=glm-request-cache.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, randomId, writeJsonAtomic } from '../../fsx.js';
|
|
3
|
+
import { GLM_SPEED_LIMITS } from './glm-run-timeout.js';
|
|
4
|
+
import { isGlmTerminalPhase } from './glm-run-state.js';
|
|
5
|
+
export function createGlmRunController(input = {}) {
|
|
6
|
+
const clock = input.now || nowIso;
|
|
7
|
+
const startedAt = clock();
|
|
8
|
+
const startedMs = Date.now();
|
|
9
|
+
let state = {
|
|
10
|
+
run_id: input.runId || `glm-${startedAt.replace(/[:.]/g, '-')}-${randomId(6)}`,
|
|
11
|
+
...(input.missionId ? { mission_id: input.missionId } : {}),
|
|
12
|
+
phase: 'idle',
|
|
13
|
+
started_at: startedAt,
|
|
14
|
+
updated_at: startedAt,
|
|
15
|
+
turn_count: 0,
|
|
16
|
+
tool_round_count: 0,
|
|
17
|
+
no_progress_count: 0,
|
|
18
|
+
repeated_output_count: 0,
|
|
19
|
+
terminal: false
|
|
20
|
+
};
|
|
21
|
+
const limits = input.limits || GLM_SPEED_LIMITS;
|
|
22
|
+
return {
|
|
23
|
+
state: () => state,
|
|
24
|
+
transition: (phase, reason) => {
|
|
25
|
+
if (state.terminal || isGlmTerminalPhase(state.phase))
|
|
26
|
+
return state;
|
|
27
|
+
state = {
|
|
28
|
+
...state,
|
|
29
|
+
phase,
|
|
30
|
+
updated_at: clock(),
|
|
31
|
+
terminal: isGlmTerminalPhase(phase),
|
|
32
|
+
...(reason ? { terminal_reason: reason } : {})
|
|
33
|
+
};
|
|
34
|
+
return state;
|
|
35
|
+
},
|
|
36
|
+
terminate: (phase, reason, blockers = [], warnings = []) => {
|
|
37
|
+
state = {
|
|
38
|
+
...state,
|
|
39
|
+
phase,
|
|
40
|
+
updated_at: clock(),
|
|
41
|
+
terminal: true,
|
|
42
|
+
terminal_reason: reason
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
schema: 'sks.glm-run-termination.v1',
|
|
46
|
+
run_id: state.run_id,
|
|
47
|
+
terminal: true,
|
|
48
|
+
phase,
|
|
49
|
+
reason,
|
|
50
|
+
turn_count: state.turn_count,
|
|
51
|
+
wall_clock_ms: Math.min(Date.now() - startedMs, limits.max_wall_clock_ms),
|
|
52
|
+
blockers,
|
|
53
|
+
warnings
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export async function writeGlmRunArtifacts(input) {
|
|
59
|
+
const dir = path.join(input.cwd, '.sneakoscope', 'glm', 'runs', input.state.run_id);
|
|
60
|
+
await writeJsonAtomic(path.join(dir, 'run-state.json'), input.state);
|
|
61
|
+
await writeJsonAtomic(path.join(dir, 'termination.json'), input.termination);
|
|
62
|
+
await writeJsonAtomic(path.join(dir, 'loop-guard.json'), input.loopGuard || { schema: 'sks.glm-loop-guard.v1', ok: true });
|
|
63
|
+
await writeJsonAtomic(path.join(dir, 'context-omissions.json'), input.contextOmissions || { omitted: [] });
|
|
64
|
+
return dir;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=glm-run-controller.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const GLM_SPEED_LIMITS = {
|
|
2
|
+
max_turns: 2,
|
|
3
|
+
max_tool_rounds: 0,
|
|
4
|
+
max_wall_clock_ms: 90_000,
|
|
5
|
+
request_timeout_ms: 45_000,
|
|
6
|
+
idle_timeout_ms: 15_000,
|
|
7
|
+
max_no_progress_iterations: 1,
|
|
8
|
+
max_repeated_output: 1,
|
|
9
|
+
max_patch_retries: 1,
|
|
10
|
+
max_context_requests: 1
|
|
11
|
+
};
|
|
12
|
+
export const GLM_DEEP_LIMITS = {
|
|
13
|
+
max_turns: 4,
|
|
14
|
+
max_tool_rounds: 4,
|
|
15
|
+
max_wall_clock_ms: 240_000,
|
|
16
|
+
request_timeout_ms: 120_000,
|
|
17
|
+
idle_timeout_ms: 30_000,
|
|
18
|
+
max_no_progress_iterations: 2,
|
|
19
|
+
max_repeated_output: 2,
|
|
20
|
+
max_patch_retries: 2,
|
|
21
|
+
max_context_requests: 2
|
|
22
|
+
};
|
|
23
|
+
export function createRequestAbortController(timeoutMs) {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timeout = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));
|
|
26
|
+
return {
|
|
27
|
+
controller,
|
|
28
|
+
clear: () => clearTimeout(timeout)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=glm-run-timeout.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { estimateGlmTokens, GLM_SPEED_CONTEXT_HARD_CAP_TOKENS, GLM_SPEED_CONTEXT_TARGET_TOKENS, trimToEstimatedTokens } from './glm-context-budget.js';
|
|
4
|
+
const GENERATED_PATH = /(^|\/)(dist|node_modules|coverage|\.git)(\/|$)|(\.generated\.|\.map$)/;
|
|
5
|
+
const DEFAULT_MAX_FILE_BYTES = 64 * 1024;
|
|
6
|
+
export async function buildGlmSpeedContext(input) {
|
|
7
|
+
const maxTokens = Math.min(input.maxTokens || GLM_SPEED_CONTEXT_TARGET_TOKENS, GLM_SPEED_CONTEXT_HARD_CAP_TOKENS);
|
|
8
|
+
const maxFileBytes = Math.max(1024, input.maxFileBytes || DEFAULT_MAX_FILE_BYTES);
|
|
9
|
+
const sections = [];
|
|
10
|
+
const omitted = [];
|
|
11
|
+
addSection(sections, 'task', input.task);
|
|
12
|
+
addSection(sections, 'constraints', 'GLM speed mode: compact context, one-shot patch, no GPT/OpenAI fallback, no full TriWiki/proof-bank/repo dump.');
|
|
13
|
+
if (input.gitStatus)
|
|
14
|
+
addSection(sections, 'git_status', input.gitStatus);
|
|
15
|
+
if (input.lastError)
|
|
16
|
+
addSection(sections, 'error', trimToEstimatedTokens(input.lastError, 2000));
|
|
17
|
+
const readFile = input.readFile || (async () => null);
|
|
18
|
+
for (const mentioned of input.mentionedPaths || []) {
|
|
19
|
+
const normalized = mentioned.replace(/\\/g, '/');
|
|
20
|
+
if (GENERATED_PATH.test(normalized)) {
|
|
21
|
+
omitted.push({ kind: 'generated_or_large_path', path: mentioned, reason: 'speed_context_excludes_generated_or_vendor_paths' });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const absolute = path.isAbsolute(mentioned) ? mentioned : path.join(input.cwd, mentioned);
|
|
25
|
+
const rawText = input.readFileSnippet ? await input.readFileSnippet(absolute, maxFileBytes) : await readFile(absolute);
|
|
26
|
+
const text = rawText === null ? null : trimToUtf8Bytes(rawText, maxFileBytes);
|
|
27
|
+
if (text === null) {
|
|
28
|
+
omitted.push({ kind: 'file_snippet', path: mentioned, reason: 'unreadable_or_missing' });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (Buffer.byteLength(rawText || '', 'utf8') > maxFileBytes) {
|
|
32
|
+
omitted.push({ kind: 'file_snippet_tail', path: mentioned, reason: 'speed_context_file_byte_budget' });
|
|
33
|
+
}
|
|
34
|
+
addSection(sections, 'file_snippet', trimToEstimatedTokens(text, 2400), path.relative(input.cwd, absolute) || mentioned);
|
|
35
|
+
}
|
|
36
|
+
const compact = enforceBudget(sections, omitted, maxTokens);
|
|
37
|
+
return {
|
|
38
|
+
schema: 'sks.glm-speed-context.v1',
|
|
39
|
+
digest: digestJson({ sections: compact.sections, omitted: compact.omitted }),
|
|
40
|
+
estimatedTokens: compact.sections.reduce((sum, section) => sum + section.tokenEstimate, 0),
|
|
41
|
+
sections: compact.sections,
|
|
42
|
+
omitted: compact.omitted
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function addSection(sections, kind, content, sectionPath) {
|
|
46
|
+
sections.push({
|
|
47
|
+
kind,
|
|
48
|
+
...(sectionPath ? { path: sectionPath } : {}),
|
|
49
|
+
content,
|
|
50
|
+
tokenEstimate: estimateGlmTokens(content)
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function enforceBudget(sections, omitted, maxTokens) {
|
|
54
|
+
const kept = [];
|
|
55
|
+
const nextOmitted = [...omitted];
|
|
56
|
+
let total = 0;
|
|
57
|
+
for (const section of sections) {
|
|
58
|
+
if (total + section.tokenEstimate <= maxTokens) {
|
|
59
|
+
kept.push(section);
|
|
60
|
+
total += section.tokenEstimate;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
nextOmitted.push({
|
|
64
|
+
kind: section.kind,
|
|
65
|
+
...(section.path ? { path: section.path } : {}),
|
|
66
|
+
reason: 'speed_context_token_budget'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { sections: kept, omitted: nextOmitted };
|
|
70
|
+
}
|
|
71
|
+
function digestJson(value) {
|
|
72
|
+
return crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
73
|
+
}
|
|
74
|
+
function trimToUtf8Bytes(text, maxBytes) {
|
|
75
|
+
if (Buffer.byteLength(text, 'utf8') <= maxBytes)
|
|
76
|
+
return text;
|
|
77
|
+
let end = Math.min(text.length, maxBytes);
|
|
78
|
+
while (end > 0 && Buffer.byteLength(text.slice(0, end), 'utf8') > maxBytes)
|
|
79
|
+
end -= 1;
|
|
80
|
+
return text.slice(0, end);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=glm-speed-context.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { parseGlmSpeedOutput } from './glm-speed-output-parser.js';
|
|
2
|
+
const FORBIDDEN_TOUCHED_PATH = /(^|\/)(\.github|dist|node_modules)(\/|$)/;
|
|
3
|
+
export function evaluateGlmSpeedGate(output) {
|
|
4
|
+
const started = Date.now();
|
|
5
|
+
const checks = [];
|
|
6
|
+
const parsed = parseGlmSpeedOutput(output);
|
|
7
|
+
checks.push(check('patch_parse', parsed.kind === 'patch', parsed.kind === 'patch' ? undefined : parsed.reason || parsed.kind));
|
|
8
|
+
const paths = parsed.kind === 'patch' ? touchedPaths(parsed.content) : [];
|
|
9
|
+
checks.push(check('touched_path_allowlist', paths.every((file) => !FORBIDDEN_TOUCHED_PATH.test(file)), paths.find((file) => FORBIDDEN_TOUCHED_PATH.test(file))));
|
|
10
|
+
checks.push(check('patch_apply_dry_run_ready', parsed.kind === 'patch' && /^diff --git /m.test(parsed.content), parsed.kind === 'patch' ? undefined : 'no_patch'));
|
|
11
|
+
const ok = checks.every((row) => row.ok);
|
|
12
|
+
return {
|
|
13
|
+
schema: 'sks.glm-speed-gate.v1',
|
|
14
|
+
ok,
|
|
15
|
+
gate_ms: Date.now() - started,
|
|
16
|
+
deterministic: true,
|
|
17
|
+
checks,
|
|
18
|
+
requiresDeepEscalation: !ok
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function touchedPaths(patch) {
|
|
22
|
+
const paths = [];
|
|
23
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
24
|
+
const match = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
|
|
25
|
+
if (match?.[1])
|
|
26
|
+
paths.push(match[1]);
|
|
27
|
+
if (match?.[2])
|
|
28
|
+
paths.push(match[2]);
|
|
29
|
+
}
|
|
30
|
+
return [...new Set(paths)];
|
|
31
|
+
}
|
|
32
|
+
function check(id, ok, reason) {
|
|
33
|
+
return {
|
|
34
|
+
id,
|
|
35
|
+
ok,
|
|
36
|
+
ms: 0,
|
|
37
|
+
...(reason ? { reason } : {})
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=glm-speed-gate.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const ENVELOPES = {
|
|
2
|
+
patch: ['<sks_patch>', '</sks_patch>'],
|
|
3
|
+
need_context: ['<sks_need_context>', '</sks_need_context>'],
|
|
4
|
+
blocked: ['<sks_blocked>', '</sks_blocked>']
|
|
5
|
+
};
|
|
6
|
+
export function parseGlmSpeedOutput(text) {
|
|
7
|
+
for (const kind of ['patch', 'need_context', 'blocked']) {
|
|
8
|
+
const extracted = extractEnvelope(text, ENVELOPES[kind][0], ENVELOPES[kind][1]);
|
|
9
|
+
if (extracted !== null) {
|
|
10
|
+
return {
|
|
11
|
+
kind,
|
|
12
|
+
content: extracted,
|
|
13
|
+
...(kind === 'need_context' ? { paths: parsePaths(extracted) } : {}),
|
|
14
|
+
...(kind === 'blocked' ? { reason: parseReason(extracted) } : {})
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const cleaned = text.trim();
|
|
19
|
+
return { kind: 'malformed', content: cleaned, reason: 'missing_glm_speed_output_envelope' };
|
|
20
|
+
}
|
|
21
|
+
function extractEnvelope(text, start, end) {
|
|
22
|
+
const startIndex = text.indexOf(start);
|
|
23
|
+
if (startIndex < 0)
|
|
24
|
+
return null;
|
|
25
|
+
const contentStart = startIndex + start.length;
|
|
26
|
+
const endIndex = text.indexOf(end, contentStart);
|
|
27
|
+
if (endIndex < 0)
|
|
28
|
+
return null;
|
|
29
|
+
return text.slice(contentStart, endIndex).trim();
|
|
30
|
+
}
|
|
31
|
+
function parsePaths(content) {
|
|
32
|
+
return content
|
|
33
|
+
.split(/\r?\n/)
|
|
34
|
+
.map((line) => line.match(/^\s*-\s*(.+?)\s*$/)?.[1])
|
|
35
|
+
.filter((value) => Boolean(value));
|
|
36
|
+
}
|
|
37
|
+
function parseReason(content) {
|
|
38
|
+
return content.match(/reason:\s*(.+)/i)?.[1]?.trim() || content.trim();
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=glm-speed-output-parser.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { SksLruCache } from '../../perf/lru-cache.js';
|
|
3
|
+
export function createGlmToolSchemaCache(maxEntries = 64) {
|
|
4
|
+
const cache = new SksLruCache(maxEntries);
|
|
5
|
+
return {
|
|
6
|
+
get(toolsetVersion) {
|
|
7
|
+
return cache.get(toolsetVersion);
|
|
8
|
+
},
|
|
9
|
+
set(toolsetVersion, tools) {
|
|
10
|
+
const entry = { key: toolsetVersion, tools, createdAt: Date.now() };
|
|
11
|
+
cache.set(toolsetVersion, entry);
|
|
12
|
+
return entry;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function digestToolset(tools) {
|
|
17
|
+
return crypto.createHash('sha256').update(JSON.stringify(tools)).digest('hex');
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=glm-tool-schema-cache.js.map
|
|
@@ -1,24 +1,44 @@
|
|
|
1
1
|
import { OPENROUTER_CHAT_COMPLETIONS_URL } from './openrouter-types.js';
|
|
2
2
|
import { invalidOpenRouterResponseIssue, normalizeOpenRouterError } from './openrouter-error.js';
|
|
3
3
|
import { redactOpenRouterString } from '../../security/redact-secrets.js';
|
|
4
|
+
import { encodeGlmRequestWithCache } from '../glm/glm-request-cache.js';
|
|
4
5
|
export async function sendOpenRouterChatCompletion(input) {
|
|
5
6
|
try {
|
|
6
7
|
const doFetch = input.fetchImpl || fetch;
|
|
8
|
+
const encoded = encodeGlmRequestWithCache(input.request);
|
|
9
|
+
const controller = input.timeoutMs ? new AbortController() : null;
|
|
10
|
+
const timeout = controller
|
|
11
|
+
? setTimeout(() => controller.abort(), Math.max(1, input.timeoutMs || 0))
|
|
12
|
+
: null;
|
|
13
|
+
const signal = input.signal || controller?.signal;
|
|
7
14
|
const response = await doFetch(input.endpoint || OPENROUTER_CHAT_COMPLETIONS_URL, {
|
|
8
15
|
method: 'POST',
|
|
16
|
+
...(signal ? { signal } : {}),
|
|
9
17
|
headers: {
|
|
10
18
|
Authorization: `Bearer ${input.apiKey}`,
|
|
11
19
|
'Content-Type': 'application/json',
|
|
12
20
|
'X-OpenRouter-Title': 'Sneakoscope-Codex'
|
|
13
21
|
},
|
|
14
|
-
body:
|
|
22
|
+
body: encoded.body
|
|
15
23
|
});
|
|
24
|
+
if (timeout)
|
|
25
|
+
clearTimeout(timeout);
|
|
16
26
|
const text = await response.text();
|
|
17
27
|
if (!response.ok)
|
|
18
28
|
return { ok: false, error: normalizeOpenRouterError(response.status, text) };
|
|
19
29
|
return parseOpenRouterResponse(text);
|
|
20
30
|
}
|
|
21
31
|
catch (err) {
|
|
32
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: {
|
|
36
|
+
code: 'glm_request_timeout',
|
|
37
|
+
message: `OpenRouter request aborted after ${input.timeoutMs || 'external'}ms.`,
|
|
38
|
+
severity: 'failed'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
22
42
|
return {
|
|
23
43
|
ok: false,
|
|
24
44
|
error: {
|