sneakoscope 4.0.5 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- 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/commands/glm-command.js +38 -7
- package/dist/core/fsx.js +1 -1
- package/dist/core/providers/glm/glm-52-profile.js +2 -2
- package/dist/core/providers/glm/glm-52-request.js +5 -2
- package/dist/core/providers/glm/glm-52-settings.js +3 -3
- package/dist/core/providers/glm/glm-bench.js +40 -3
- 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 +1 -1
- package/dist/core/providers/glm/glm-loop-guard.js +31 -0
- package/dist/core/providers/glm/glm-mad-launch.js +1 -1
- package/dist/core/providers/glm/glm-mad-mode.js +1 -1
- 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-readiness.js +5 -0
- package/dist/core/providers/glm/glm-request-cache.js +22 -5
- 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/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
package/README.md
CHANGED
|
@@ -35,16 +35,20 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
|
|
|
35
35
|
|
|
36
36
|
## 🚀 Current Release
|
|
37
37
|
|
|
38
|
-
SKS **4.0.
|
|
38
|
+
SKS **4.0.6** makes the GLM 5.2 MAD path bounded by default: `sks --mad --glm` now returns readiness/status and exits when no task is supplied, while task forms use a direct GLM-only speed path with loop guards, request timeouts, and deterministic patch gates. Ordinary `sks --mad`, Naruto/Team, and non-GLM Codex paths keep their existing defaults.
|
|
39
39
|
|
|
40
|
-
What changed in 4.0.
|
|
40
|
+
What changed in 4.0.7:
|
|
41
41
|
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- **No
|
|
47
|
-
- **
|
|
42
|
+
- **`--open` alias for interactive GLM launch.** `sks --mad --glm --open` now opens the GLM interactive Zellij runtime, equivalent to `sks --mad --glm --interactive`.
|
|
43
|
+
|
|
44
|
+
What changed in 4.0.6:
|
|
45
|
+
|
|
46
|
+
- **No default long-lived GLM launch.** Bare `sks --mad --glm` no longer falls through to MAD/Zellij; `--interactive`, `--open`, `--zellij`, or `session` is required for that path.
|
|
47
|
+
- **Fast GLM speed profile.** Speed mode keeps OpenRouter locked to `z-ai/glm-5.2`, disables GPT/model fallback, avoids high/xhigh reasoning by default, and uses `provider.require_parameters: false` with throughput-first routing.
|
|
48
|
+
- **Bounded direct task runs.** `sks --mad --glm run "task"` and `sks --mad --glm "task"` use a one-shot GLM speed run with max-turn, wall-clock, request-timeout, no-progress, repeated-output, and terminal-state guards.
|
|
49
|
+
- **Deterministic mutation gate.** GLM still returns patch envelopes; SKS parses the unified diff, blocks protected paths, runs `git apply --check`, and applies only after the gate passes.
|
|
50
|
+
- **OpenRouter speed plumbing.** Encoded request bodies are cached without Authorization headers, request timeout/abort is wired, streaming TTFT/usage capture is scaffolded, and synthetic `--bench` remains network-free by default.
|
|
51
|
+
- **Loop regression tests.** Routing, speed-profile, cache, loop-guard, patch-gate, and OpenRouter key handling are covered by targeted tests.
|
|
48
52
|
|
|
49
53
|
SKS **3.1.16** was a launch-reliability patch on the 3.1.15 doctor-reliability release. It made `sks --mad` self-bootstrap a fresh project instead of dead-ending on a missing Codex config.
|
|
50
54
|
|
|
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
|
|
|
4
4
|
fn main() {
|
|
5
5
|
let mut args = std::env::args().skip(1);
|
|
6
6
|
match args.next().as_deref() {
|
|
7
|
-
Some("--version") => println!("sks-rs 4.0.
|
|
7
|
+
Some("--version") => println!("sks-rs 4.0.6"),
|
|
8
8
|
Some("compact-info") => {
|
|
9
9
|
let mut input = String::new();
|
|
10
10
|
let _ = io::stdin().read_to_string(&mut input);
|
package/dist/bin/sks.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { flag } from '../../cli/args.js';
|
|
3
|
-
import { madHighCommand } from './mad-sks-command.js';
|
|
1
|
+
import { flag, positionalArgs } from '../../cli/args.js';
|
|
4
2
|
import { runGlmBench } from '../providers/glm/glm-bench.js';
|
|
5
3
|
import { printJson } from '../../cli/output.js';
|
|
4
|
+
import { runGlmDirectSpeedRun } from '../providers/glm/glm-direct-run.js';
|
|
5
|
+
import { runGlmReadinessAndExit } from '../providers/glm/glm-readiness.js';
|
|
6
|
+
import { runGlmInteractiveLaunch } from '../providers/glm/glm-interactive-launch.js';
|
|
6
7
|
export async function glmCommand(args = []) {
|
|
7
8
|
if (flag(args, '--bench')) {
|
|
8
9
|
const result = await runGlmBench(process.cwd(), args);
|
|
@@ -16,9 +17,39 @@ export async function glmCommand(args = []) {
|
|
|
16
17
|
console.log(`GLM bench: dry-run p50=${result.summary.speed_p50_total_ms}ms ratio=${result.summary.speed_vs_deep_ratio}`);
|
|
17
18
|
return result;
|
|
18
19
|
}
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const task = extractGlmTask(args);
|
|
21
|
+
const interactive = flag(args, '--interactive') || flag(args, '--open') || flag(args, '--zellij') || positionalArgs(args)[0] === 'session';
|
|
22
|
+
if (interactive) {
|
|
23
|
+
const readiness = await runGlmReadinessAndExit(args);
|
|
24
|
+
if (!readiness.ok)
|
|
25
|
+
return readiness;
|
|
26
|
+
return runGlmInteractiveLaunch(args, readiness);
|
|
27
|
+
}
|
|
28
|
+
if (!task || flag(args, '--repair') || flag(args, '--status')) {
|
|
29
|
+
return runGlmReadinessAndExit(args);
|
|
30
|
+
}
|
|
31
|
+
const result = await runGlmDirectSpeedRun({
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
task,
|
|
34
|
+
args,
|
|
35
|
+
dryRun: flag(args, '--dry-run')
|
|
36
|
+
});
|
|
37
|
+
if (flag(args, '--json'))
|
|
38
|
+
printJson(result);
|
|
39
|
+
else if (result.ok)
|
|
40
|
+
console.log(`GLM direct run completed: ${result.termination_reason}`);
|
|
41
|
+
else
|
|
42
|
+
console.error(`GLM direct run ${result.status}: ${result.blockers.join(', ') || result.termination_reason}`);
|
|
43
|
+
if (!result.ok)
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function extractGlmTask(args) {
|
|
48
|
+
const positional = positionalArgs(args).map(String);
|
|
49
|
+
if (positional[0] === 'run')
|
|
50
|
+
return positional.slice(1).join(' ').trim() || null;
|
|
51
|
+
if (positional[0] === 'session')
|
|
52
|
+
return null;
|
|
53
|
+
return positional.join(' ').trim() || null;
|
|
23
54
|
}
|
|
24
55
|
//# sourceMappingURL=glm-command.js.map
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '4.0.
|
|
8
|
+
export const PACKAGE_VERSION = '4.0.6';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GLM_52_OPENROUTER_MODEL, GLM_MAD_MODE } from './glm-52-settings.js';
|
|
2
2
|
import { profileFromConst } from './glm-profile-resolver.js';
|
|
3
3
|
export const GLM_CODEX_APP_PROFILE_ID = 'sks/glm-5.2-mad';
|
|
4
|
-
export const GLM_CODEX_APP_PROFILE_LABEL = 'GLM 5.2 (MAD
|
|
4
|
+
export const GLM_CODEX_APP_PROFILE_LABEL = 'GLM 5.2 (MAD Speed / OpenRouter)';
|
|
5
5
|
export function buildGlmCodexAppModelProfile() {
|
|
6
6
|
const speed = profileFromConst('speed');
|
|
7
7
|
return {
|
|
@@ -18,7 +18,7 @@ export function buildGlmCodexAppModelProfile() {
|
|
|
18
18
|
defaultSettings: {
|
|
19
19
|
temperature: speed.temperature,
|
|
20
20
|
top_p: speed.top_p,
|
|
21
|
-
reasoning_effort:
|
|
21
|
+
reasoning_effort: speed.reasoning_effort || null,
|
|
22
22
|
tool_choice: speed.tool_choice,
|
|
23
23
|
parallel_tool_calls: speed.parallel_tool_calls,
|
|
24
24
|
max_tokens: speed.max_tokens,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GLM_52_DEFAULT_REQUEST_SETTINGS, GLM_52_OPENROUTER_MODEL, clampGlm52MaxTokens } from './glm-52-settings.js';
|
|
2
|
-
import { buildDeepReasoningConfig } from './glm-reasoning-policy.js';
|
|
2
|
+
import { buildDeepReasoningConfig, buildFastReasoningConfig } from './glm-reasoning-policy.js';
|
|
3
3
|
import { profileFromConst, resolveGlmProfileFromArgs } from './glm-profile-resolver.js';
|
|
4
4
|
export function buildGlm52Request(input) {
|
|
5
5
|
const profile = resolveInputProfile(input.profile, input.args, input.reasoningEffort);
|
|
@@ -8,8 +8,11 @@ export function buildGlm52Request(input) {
|
|
|
8
8
|
}
|
|
9
9
|
const strictOrDeepEffort = profile.reasoning_effort || (input.reasoningEffort === 'high' || input.reasoningEffort === 'xhigh' ? input.reasoningEffort : undefined);
|
|
10
10
|
const reasoning = profile.name === 'speed'
|
|
11
|
-
?
|
|
11
|
+
? buildFastReasoningConfig(input.reasoningMeta)
|
|
12
12
|
: buildDeepReasoningConfig(strictOrDeepEffort || 'high');
|
|
13
|
+
if (profile.name === 'speed' && (reasoning.effort === 'high' || reasoning.effort === 'xhigh')) {
|
|
14
|
+
throw new Error(`GLM speed profile invariant violated: forbidden reasoning effort ${reasoning.effort}`);
|
|
15
|
+
}
|
|
13
16
|
const request = {
|
|
14
17
|
model: GLM_52_OPENROUTER_MODEL,
|
|
15
18
|
messages: input.messages,
|
|
@@ -21,7 +21,7 @@ export const GLM_SPEED_PROFILE = {
|
|
|
21
21
|
stream: true,
|
|
22
22
|
provider: {
|
|
23
23
|
allow_fallbacks: false,
|
|
24
|
-
require_parameters:
|
|
24
|
+
require_parameters: false,
|
|
25
25
|
sort: 'throughput',
|
|
26
26
|
preferred_min_throughput: { p50: 80, p90: 40 },
|
|
27
27
|
preferred_max_latency: { p50: 2, p90: 5 }
|
|
@@ -29,8 +29,8 @@ export const GLM_SPEED_PROFILE = {
|
|
|
29
29
|
tool_choice: 'none',
|
|
30
30
|
parallel_tool_calls: false,
|
|
31
31
|
max_tokens: GLM_52_MAX_TOKENS_SPEED,
|
|
32
|
-
reasoning_effort:
|
|
33
|
-
reasoning_default: '
|
|
32
|
+
reasoning_effort: null,
|
|
33
|
+
reasoning_default: 'off-or-minimal-speed'
|
|
34
34
|
};
|
|
35
35
|
export const GLM_DEEP_PROFILE = {
|
|
36
36
|
model: GLM_52_OPENROUTER_MODEL,
|
|
@@ -9,11 +9,12 @@ const SYNTHETIC_CASES = Object.freeze([
|
|
|
9
9
|
benchCase('simple config edit', 'config_edit', 390, 930)
|
|
10
10
|
]);
|
|
11
11
|
export async function runGlmBench(root, args = []) {
|
|
12
|
+
const live = args.includes('--live');
|
|
12
13
|
const execute = args.includes('--execute');
|
|
13
|
-
if (execute) {
|
|
14
|
+
if (execute && !live) {
|
|
14
15
|
const blocked = {
|
|
15
16
|
schema: 'sks.glm-bench-result.v1',
|
|
16
|
-
version: '4.0.
|
|
17
|
+
version: '4.0.6',
|
|
17
18
|
generated_at: nowIso(),
|
|
18
19
|
status: 'blocked',
|
|
19
20
|
dry_run: true,
|
|
@@ -28,11 +29,47 @@ export async function runGlmBench(root, args = []) {
|
|
|
28
29
|
await writeJsonAtomic(path.join(root, '.sneakoscope', 'glm', 'bench-blocked.json'), blocked);
|
|
29
30
|
return blocked;
|
|
30
31
|
}
|
|
32
|
+
if (live) {
|
|
33
|
+
const blocked = {
|
|
34
|
+
schema: 'sks.glm-bench-result.v1',
|
|
35
|
+
version: '4.0.6',
|
|
36
|
+
generated_at: nowIso(),
|
|
37
|
+
status: 'blocked',
|
|
38
|
+
dry_run: false,
|
|
39
|
+
cases: [],
|
|
40
|
+
summary: {
|
|
41
|
+
speed_p50_total_ms: 0,
|
|
42
|
+
speed_p90_total_ms: 0,
|
|
43
|
+
speed_p50_ttft_ms: null
|
|
44
|
+
},
|
|
45
|
+
warnings: ['live_openrouter_bench_requires_explicit_network_runner_not_enabled_in_this_build']
|
|
46
|
+
};
|
|
47
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'glm', 'bench-live-blocked.json'), blocked);
|
|
48
|
+
return blocked;
|
|
49
|
+
}
|
|
50
|
+
if (execute) {
|
|
51
|
+
const blocked = {
|
|
52
|
+
schema: 'sks.glm-bench-result.v1',
|
|
53
|
+
version: '4.0.6',
|
|
54
|
+
generated_at: nowIso(),
|
|
55
|
+
status: 'blocked',
|
|
56
|
+
dry_run: true,
|
|
57
|
+
cases: [],
|
|
58
|
+
summary: {
|
|
59
|
+
speed_p50_total_ms: 0,
|
|
60
|
+
speed_p90_total_ms: 0,
|
|
61
|
+
speed_p50_ttft_ms: null
|
|
62
|
+
},
|
|
63
|
+
warnings: ['execute_requested_without_live_flag_uses_no_network_dry_run_policy']
|
|
64
|
+
};
|
|
65
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'glm', 'bench-blocked.json'), blocked);
|
|
66
|
+
return blocked;
|
|
67
|
+
}
|
|
31
68
|
const speedTotals = SYNTHETIC_CASES.map((row) => row.speed.total_ms);
|
|
32
69
|
const deepTotals = SYNTHETIC_CASES.map((row) => row.deep.total_ms);
|
|
33
70
|
const result = {
|
|
34
71
|
schema: 'sks.glm-bench-result.v1',
|
|
35
|
-
version: '4.0.
|
|
72
|
+
version: '4.0.6',
|
|
36
73
|
generated_at: nowIso(),
|
|
37
74
|
status: 'dry_run',
|
|
38
75
|
dry_run: true,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nowIso, writeJsonAtomic } from '../../fsx.js';
|
|
4
|
+
import { resolveOpenRouterApiKey } from '../openrouter/openrouter-secret-store.js';
|
|
5
|
+
import { sendOpenRouterChatCompletionStream } from '../openrouter/openrouter-stream.js';
|
|
6
|
+
import { assertGlm52ActualModel } from './glm-52-response-guard.js';
|
|
7
|
+
import { GLM_52_OPENROUTER_MODEL } from './glm-52-settings.js';
|
|
8
|
+
import { buildGlm52Request } from './glm-52-request.js';
|
|
9
|
+
import { buildGlmSpeedContext } from './glm-speed-context.js';
|
|
10
|
+
import { parseGlmSpeedOutput } from './glm-speed-output-parser.js';
|
|
11
|
+
import { evaluateGlmSpeedGate } from './glm-speed-gate.js';
|
|
12
|
+
import { checkAndApplyGlmPatch } from './glm-patch-apply.js';
|
|
13
|
+
import { createGlmRunController, writeGlmRunArtifacts } from './glm-run-controller.js';
|
|
14
|
+
import { GLM_SPEED_LIMITS } from './glm-run-timeout.js';
|
|
15
|
+
import { recordGlmLoopIteration } from './glm-loop-guard.js';
|
|
16
|
+
export async function runGlmDirectSpeedRun(input) {
|
|
17
|
+
if (process.env.SKS_GLM_WRAPPER_ACTIVE === '1') {
|
|
18
|
+
return blocked('glm-recursive-blocked', input.task, 'glm_recursive_launch_blocked', ['glm_recursive_launch_blocked']);
|
|
19
|
+
}
|
|
20
|
+
const controller = createGlmRunController({ limits: GLM_SPEED_LIMITS });
|
|
21
|
+
controller.transition('preflight');
|
|
22
|
+
const key = await resolveOpenRouterApiKey({ env: process.env });
|
|
23
|
+
if (!key.key) {
|
|
24
|
+
const termination = controller.terminate('blocked', 'glm_patch_gate_failed', key.blockers, [
|
|
25
|
+
'set_OPENROUTER_API_KEY_or_run_sks_--mad_--glm_--repair'
|
|
26
|
+
]);
|
|
27
|
+
const artifactDir = await writeGlmRunArtifacts({ cwd: input.cwd, state: controller.state(), termination });
|
|
28
|
+
return result('blocked', controller.state().run_id, input.task, termination.reason, artifactDir, [], key.blockers, termination.warnings);
|
|
29
|
+
}
|
|
30
|
+
controller.transition('context');
|
|
31
|
+
const gitStatus = await readGitStatus(input.cwd);
|
|
32
|
+
const context = await buildGlmSpeedContext({
|
|
33
|
+
cwd: input.cwd,
|
|
34
|
+
task: input.task,
|
|
35
|
+
mentionedPaths: extractMentionedPaths(input.task),
|
|
36
|
+
readFile: async (file) => fs.readFile(file, 'utf8').catch(() => null),
|
|
37
|
+
...(gitStatus ? { gitStatus } : {})
|
|
38
|
+
});
|
|
39
|
+
controller.transition('request');
|
|
40
|
+
const request = buildGlm52Request({
|
|
41
|
+
profile: 'speed',
|
|
42
|
+
messages: [
|
|
43
|
+
{ role: 'system', content: 'Return only <sks_patch>, <sks_need_context>, or <sks_blocked>. Use unified diff for patches.' },
|
|
44
|
+
{ role: 'user', content: JSON.stringify({ task: input.task, context }) }
|
|
45
|
+
],
|
|
46
|
+
maxTokens: 4096
|
|
47
|
+
});
|
|
48
|
+
const response = await sendOpenRouterChatCompletionStream({
|
|
49
|
+
apiKey: key.key,
|
|
50
|
+
request: {
|
|
51
|
+
...request,
|
|
52
|
+
session_id: `sks-${controller.state().run_id}`
|
|
53
|
+
},
|
|
54
|
+
timeoutMs: GLM_SPEED_LIMITS.request_timeout_ms
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const reason = response.error.code === 'glm_request_timeout' ? 'glm_request_timeout' : 'glm_patch_gate_failed';
|
|
58
|
+
const termination = controller.terminate(reason === 'glm_request_timeout' ? 'timeout' : 'failed', reason, [response.error.code]);
|
|
59
|
+
const artifactDir = await writeGlmRunArtifacts({ cwd: input.cwd, state: controller.state(), termination, contextOmissions: context.omitted });
|
|
60
|
+
return result(reason === 'glm_request_timeout' ? 'timeout' : 'failed', controller.state().run_id, input.task, termination.reason, artifactDir, [], [response.error.code], []);
|
|
61
|
+
}
|
|
62
|
+
controller.transition('model_guard');
|
|
63
|
+
const modelGuard = assertGlm52ActualModel(response.value.model || GLM_52_OPENROUTER_MODEL);
|
|
64
|
+
if (!modelGuard.ok) {
|
|
65
|
+
const termination = controller.terminate('blocked', 'glm_model_mismatch', [modelGuard.code]);
|
|
66
|
+
const artifactDir = await writeGlmRunArtifacts({ cwd: input.cwd, state: controller.state(), termination, contextOmissions: context.omitted });
|
|
67
|
+
return result('blocked', controller.state().run_id, input.task, termination.reason, artifactDir, [], [modelGuard.code], []);
|
|
68
|
+
}
|
|
69
|
+
controller.transition('parse_output');
|
|
70
|
+
const parsed = parseGlmSpeedOutput(response.value.content);
|
|
71
|
+
const guard = recordGlmLoopIteration({
|
|
72
|
+
state: controller.state(),
|
|
73
|
+
limits: GLM_SPEED_LIMITS,
|
|
74
|
+
output: response.value.content,
|
|
75
|
+
madeProgress: parsed.kind === 'patch',
|
|
76
|
+
nowIso: nowIso()
|
|
77
|
+
});
|
|
78
|
+
if (!guard.ok) {
|
|
79
|
+
const termination = controller.terminate('blocked', guard.reason === 'glm_loop_repeated_output' ? 'glm_loop_repeated_output' : 'glm_loop_no_progress', [guard.reason || 'glm_loop_blocked']);
|
|
80
|
+
const artifactDir = await writeGlmRunArtifacts({ cwd: input.cwd, state: controller.state(), termination, loopGuard: guard, contextOmissions: context.omitted });
|
|
81
|
+
return result('blocked', controller.state().run_id, input.task, termination.reason, artifactDir, [], termination.blockers, []);
|
|
82
|
+
}
|
|
83
|
+
if (parsed.kind === 'blocked' || parsed.kind === 'need_context' || parsed.kind === 'malformed') {
|
|
84
|
+
const termination = controller.terminate('blocked', parsed.kind === 'malformed' ? 'glm_loop_no_progress' : 'completed_noop', [parsed.reason || parsed.kind]);
|
|
85
|
+
const artifactDir = await writeGlmRunArtifacts({ cwd: input.cwd, state: controller.state(), termination, loopGuard: guard, contextOmissions: context.omitted });
|
|
86
|
+
return result('blocked', controller.state().run_id, input.task, termination.reason, artifactDir, [], termination.blockers, []);
|
|
87
|
+
}
|
|
88
|
+
controller.transition('patch_gate');
|
|
89
|
+
const gate = evaluateGlmSpeedGate(response.value.content);
|
|
90
|
+
if (!gate.ok) {
|
|
91
|
+
const termination = controller.terminate('blocked', 'glm_patch_gate_failed', gate.checks.filter((row) => !row.ok).map((row) => row.reason || row.id));
|
|
92
|
+
const artifactDir = await writeGlmRunArtifacts({ cwd: input.cwd, state: controller.state(), termination, loopGuard: guard, contextOmissions: context.omitted });
|
|
93
|
+
return result('blocked', controller.state().run_id, input.task, termination.reason, artifactDir, [], termination.blockers, []);
|
|
94
|
+
}
|
|
95
|
+
controller.transition('apply_patch');
|
|
96
|
+
const applied = await checkAndApplyGlmPatch({ cwd: input.cwd, patch: parsed.content, apply: !input.dryRun });
|
|
97
|
+
if (!applied.ok) {
|
|
98
|
+
const termination = controller.terminate('blocked', 'glm_patch_gate_failed', [applied.error.code]);
|
|
99
|
+
const artifactDir = await writeGlmRunArtifacts({ cwd: input.cwd, state: controller.state(), termination, loopGuard: guard, contextOmissions: context.omitted });
|
|
100
|
+
return result('blocked', controller.state().run_id, input.task, termination.reason, artifactDir, [], termination.blockers, []);
|
|
101
|
+
}
|
|
102
|
+
controller.transition('verify');
|
|
103
|
+
const termination = controller.terminate('completed', input.dryRun ? 'completed_noop' : 'completed_patch_applied');
|
|
104
|
+
const artifactDir = await writeGlmRunArtifacts({ cwd: input.cwd, state: controller.state(), termination, loopGuard: guard, contextOmissions: context.omitted });
|
|
105
|
+
await writeJsonAtomic(path.join(artifactDir, 'direct-run.json'), { request_model: request.model, stream: true, gate, applied: applied.value });
|
|
106
|
+
return result('completed', controller.state().run_id, input.task, termination.reason, artifactDir, applied.value.touchedPaths, [], []);
|
|
107
|
+
}
|
|
108
|
+
function result(status, runId, task, terminationReason, artifactDir, touchedPaths, blockers, warnings) {
|
|
109
|
+
return {
|
|
110
|
+
schema: 'sks.glm-direct-run-result.v1',
|
|
111
|
+
ok: status === 'completed',
|
|
112
|
+
status,
|
|
113
|
+
run_id: runId,
|
|
114
|
+
task,
|
|
115
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
116
|
+
gpt_fallback_allowed: false,
|
|
117
|
+
termination_reason: terminationReason,
|
|
118
|
+
...(artifactDir ? { artifact_dir: artifactDir } : {}),
|
|
119
|
+
touched_paths: touchedPaths,
|
|
120
|
+
blockers,
|
|
121
|
+
warnings
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function blocked(runId, task, reason, blockers) {
|
|
125
|
+
return result('blocked', runId, task, reason, undefined, [], blockers, []);
|
|
126
|
+
}
|
|
127
|
+
async function readGitStatus(cwd) {
|
|
128
|
+
const { spawn } = await import('node:child_process');
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
const child = spawn('git', ['status', '--short'], { cwd, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
131
|
+
let stdout = '';
|
|
132
|
+
child.stdout.on('data', (chunk) => { stdout += String(chunk); });
|
|
133
|
+
child.on('close', () => resolve(stdout.trim() || undefined));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function extractMentionedPaths(task) {
|
|
137
|
+
const matches = task.match(/(?:^|\s|[`"'])([A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+\.[A-Za-z0-9]+)(?:\s|[`"']|$)/g) || [];
|
|
138
|
+
return [...new Set(matches.map((value) => value.trim().replace(/^[`"']|[`"']$/g, '')))];
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=glm-direct-run.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { madHighCommand } from '../../commands/mad-sks-command.js';
|
|
2
|
+
export async function runGlmInteractiveLaunch(args, readiness) {
|
|
3
|
+
return madHighCommand(['--glm', '--no-swarm', ...args], { glmReadiness: readiness, glmArgs: args });
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=glm-interactive-launch.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { isGlmTerminalPhase } from './glm-run-state.js';
|
|
3
|
+
export function recordGlmLoopIteration(input) {
|
|
4
|
+
if (isGlmTerminalPhase(input.state.phase) || input.state.terminal) {
|
|
5
|
+
return { ok: false, reason: 'terminal_state', state: input.state };
|
|
6
|
+
}
|
|
7
|
+
const outputDigest = input.output ? digestNormalizedOutput(input.output) : input.state.last_output_digest;
|
|
8
|
+
const repeated = Boolean(outputDigest && outputDigest === input.state.last_output_digest);
|
|
9
|
+
const nextState = {
|
|
10
|
+
...input.state,
|
|
11
|
+
updated_at: input.nowIso,
|
|
12
|
+
turn_count: input.state.turn_count + 1,
|
|
13
|
+
no_progress_count: input.madeProgress ? 0 : input.state.no_progress_count + 1,
|
|
14
|
+
repeated_output_count: repeated ? input.state.repeated_output_count + 1 : 0,
|
|
15
|
+
...(outputDigest ? { last_output_digest: outputDigest } : {})
|
|
16
|
+
};
|
|
17
|
+
if (nextState.turn_count > input.limits.max_turns) {
|
|
18
|
+
return { ok: false, reason: 'glm_loop_max_turns', state: nextState };
|
|
19
|
+
}
|
|
20
|
+
if (nextState.repeated_output_count >= input.limits.max_repeated_output) {
|
|
21
|
+
return { ok: false, reason: 'glm_loop_repeated_output', state: nextState };
|
|
22
|
+
}
|
|
23
|
+
if (nextState.no_progress_count > input.limits.max_no_progress_iterations) {
|
|
24
|
+
return { ok: false, reason: 'glm_loop_no_progress', state: nextState };
|
|
25
|
+
}
|
|
26
|
+
return { ok: true, state: nextState };
|
|
27
|
+
}
|
|
28
|
+
export function digestNormalizedOutput(output) {
|
|
29
|
+
return crypto.createHash('sha256').update(output.replace(/\s+/g, ' ').trim()).digest('hex');
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=glm-loop-guard.js.map
|
|
@@ -204,7 +204,7 @@ async function writeGlmModeArtifacts(cwd, result, profile, selectedProfile, gene
|
|
|
204
204
|
profile: selectedProfile.name,
|
|
205
205
|
temperature: selectedProfile.temperature,
|
|
206
206
|
top_p: selectedProfile.top_p,
|
|
207
|
-
reasoning_effort: selectedProfile.reasoning_effort ||
|
|
207
|
+
reasoning_effort: selectedProfile.reasoning_effort || null,
|
|
208
208
|
max_tokens: selectedProfile.max_tokens,
|
|
209
209
|
tool_choice: selectedProfile.tool_choice,
|
|
210
210
|
parallel_tool_calls: selectedProfile.parallel_tool_calls,
|
|
@@ -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
|
|
@@ -6,18 +6,31 @@ export function createGlmEncodedRequestCache(maxEntries = 128) {
|
|
|
6
6
|
export function encodeGlmRequestWithCache(request, cache = defaultEncodedRequestCache) {
|
|
7
7
|
const key = digestRequestForCache(request);
|
|
8
8
|
const hit = cache.get(key);
|
|
9
|
-
if (hit)
|
|
10
|
-
return { entry: hit, cacheHit: true };
|
|
11
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
|
+
}
|
|
12
24
|
const entry = {
|
|
13
25
|
key,
|
|
26
|
+
body,
|
|
14
27
|
bodySha256: crypto.createHash('sha256').update(body).digest('hex'),
|
|
15
28
|
byteLength: Buffer.byteLength(body),
|
|
16
29
|
createdAt: Date.now(),
|
|
17
|
-
bodyStored:
|
|
30
|
+
bodyStored: true
|
|
18
31
|
};
|
|
19
32
|
cache.set(key, entry);
|
|
20
|
-
return { entry, cacheHit: false };
|
|
33
|
+
return { body, entry, cacheHit: false };
|
|
21
34
|
}
|
|
22
35
|
export function digestRequestForCache(request) {
|
|
23
36
|
const safe = {
|
|
@@ -31,10 +44,14 @@ export function digestRequestForCache(request) {
|
|
|
31
44
|
top_p: request.top_p || null,
|
|
32
45
|
tool_choice: request.tool_choice || null,
|
|
33
46
|
parallel_tool_calls: request.parallel_tool_calls || null,
|
|
34
|
-
reasoning: request.reasoning || null
|
|
47
|
+
reasoning: request.reasoning || null,
|
|
48
|
+
session_id: request.session_id || null
|
|
35
49
|
};
|
|
36
50
|
return crypto.createHash('sha256').update(stableStringify(safe)).digest('hex');
|
|
37
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
|
+
}
|
|
38
55
|
function stableStringify(value) {
|
|
39
56
|
if (!value || typeof value !== 'object')
|
|
40
57
|
return JSON.stringify(value);
|
|
@@ -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
|
|
@@ -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: {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { redactOpenRouterString } from '../../security/redact-secrets.js';
|
|
2
|
+
import { OPENROUTER_CHAT_COMPLETIONS_URL } from './openrouter-types.js';
|
|
3
|
+
import { normalizeOpenRouterError } from './openrouter-error.js';
|
|
4
|
+
import { encodeGlmRequestWithCache } from '../glm/glm-request-cache.js';
|
|
5
|
+
export async function sendOpenRouterChatCompletionStream(input) {
|
|
6
|
+
const started = Date.now();
|
|
7
|
+
const controller = input.timeoutMs ? new AbortController() : null;
|
|
8
|
+
const timeout = controller ? setTimeout(() => controller.abort(), Math.max(1, input.timeoutMs || 0)) : null;
|
|
9
|
+
try {
|
|
10
|
+
const request = { ...input.request, stream: true };
|
|
11
|
+
const encoded = encodeGlmRequestWithCache(request);
|
|
12
|
+
const signal = input.signal || controller?.signal;
|
|
13
|
+
const response = await (input.fetchImpl || fetch)(OPENROUTER_CHAT_COMPLETIONS_URL, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
...(signal ? { signal } : {}),
|
|
16
|
+
headers: {
|
|
17
|
+
Authorization: `Bearer ${input.apiKey}`,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'X-OpenRouter-Title': 'Sneakoscope-Codex'
|
|
20
|
+
},
|
|
21
|
+
body: encoded.body
|
|
22
|
+
});
|
|
23
|
+
if (timeout)
|
|
24
|
+
clearTimeout(timeout);
|
|
25
|
+
const text = await response.text();
|
|
26
|
+
if (!response.ok)
|
|
27
|
+
return { ok: false, error: normalizeOpenRouterError(response.status, text) };
|
|
28
|
+
return { ok: true, value: parseOpenRouterStreamText(text, started) };
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (timeout)
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
error: {
|
|
37
|
+
code: 'glm_request_timeout',
|
|
38
|
+
message: `OpenRouter stream aborted after ${input.timeoutMs || 'external'}ms.`,
|
|
39
|
+
severity: 'failed'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
error: {
|
|
46
|
+
code: 'glm_openrouter_stream_failed',
|
|
47
|
+
message: redactOpenRouterString(err instanceof Error ? err.message : String(err)),
|
|
48
|
+
severity: 'failed'
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function parseOpenRouterStreamText(text, startedAtMs = Date.now()) {
|
|
54
|
+
const events = [];
|
|
55
|
+
let content = '';
|
|
56
|
+
let model;
|
|
57
|
+
let usage;
|
|
58
|
+
let ttft = null;
|
|
59
|
+
for (const line of text.split(/\r?\n/)) {
|
|
60
|
+
if (!line.startsWith('data:'))
|
|
61
|
+
continue;
|
|
62
|
+
const data = line.slice('data:'.length).trim();
|
|
63
|
+
if (!data || data === '[DONE]')
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
const raw = JSON.parse(data);
|
|
67
|
+
const delta = raw?.choices?.[0]?.delta?.content;
|
|
68
|
+
if (typeof raw?.model === 'string')
|
|
69
|
+
model = raw.model;
|
|
70
|
+
if (raw?.usage)
|
|
71
|
+
usage = raw.usage;
|
|
72
|
+
if (typeof delta === 'string' && delta) {
|
|
73
|
+
if (ttft === null)
|
|
74
|
+
ttft = Math.max(0, Date.now() - startedAtMs);
|
|
75
|
+
content += delta;
|
|
76
|
+
events.push({ type: 'chunk', content_delta: delta, ...(model ? { model } : {}), raw });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
events.push({ type: 'error', raw: data });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
events.push({ type: 'done', ...(model ? { model } : {}), ...(usage ? { usage } : {}) });
|
|
84
|
+
return {
|
|
85
|
+
content,
|
|
86
|
+
...(model ? { model } : {}),
|
|
87
|
+
...(usage ? { usage } : {}),
|
|
88
|
+
ttft_ms: ttft,
|
|
89
|
+
total_ms: Math.max(0, Date.now() - startedAtMs),
|
|
90
|
+
chunk_count: events.filter((event) => event.type === 'chunk').length,
|
|
91
|
+
events
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=openrouter-stream.js.map
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '4.0.
|
|
1
|
+
export const PACKAGE_VERSION = '4.0.6';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.7",
|
|
5
5
|
"description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|