sneakoscope 4.0.7 → 4.0.8
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 +2 -2
- 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 +8 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/providers/glm/glm-bench.js +4 -4
- package/dist/core/providers/glm/glm-latency-trace.js +1 -1
- package/dist/core/providers/glm/glm-request-cache.js +10 -2
- package/dist/core/providers/glm/naruto/glm-naruto-artifacts.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-bench.js +68 -0
- package/dist/core/providers/glm/naruto/glm-naruto-budget.js +45 -0
- package/dist/core/providers/glm/naruto/glm-naruto-command.js +97 -0
- package/dist/core/providers/glm/naruto/glm-naruto-concurrency-governor.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-conflict-graph.js +74 -0
- package/dist/core/providers/glm/naruto/glm-naruto-decomposer.js +99 -0
- package/dist/core/providers/glm/naruto/glm-naruto-file-lease.js +23 -0
- package/dist/core/providers/glm/naruto/glm-naruto-finalizer.js +22 -0
- package/dist/core/providers/glm/naruto/glm-naruto-judge.js +84 -0
- package/dist/core/providers/glm/naruto/glm-naruto-merge-planner.js +57 -0
- package/dist/core/providers/glm/naruto/glm-naruto-orchestrator.js +224 -0
- package/dist/core/providers/glm/naruto/glm-naruto-patch-envelope.js +55 -0
- package/dist/core/providers/glm/naruto/glm-naruto-quorum.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-rate-limiter.js +18 -0
- package/dist/core/providers/glm/naruto/glm-naruto-repair-wave.js +21 -0
- package/dist/core/providers/glm/naruto/glm-naruto-shard-planner.js +32 -0
- package/dist/core/providers/glm/naruto/glm-naruto-trace.js +51 -0
- package/dist/core/providers/glm/naruto/glm-naruto-types.js +37 -0
- package/dist/core/providers/glm/naruto/glm-naruto-work-graph.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker-pool.js +79 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker-runtime.js +196 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worker.js +2 -0
- package/dist/core/providers/glm/naruto/glm-naruto-worktree.js +48 -0
- package/dist/core/providers/openrouter/openrouter-provider-health.js +46 -0
- package/dist/core/providers/openrouter/openrouter-secret-store.js +33 -0
- package/dist/core/providers/openrouter/openrouter-stream.js +73 -5
- package/dist/core/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { runPatchWorker } from './glm-naruto-worker-runtime.js';
|
|
2
|
+
import { checkAndApplyGlmPatch } from '../glm-patch-apply.js';
|
|
3
|
+
import { evaluateGlmSpeedGate } from '../glm-speed-gate.js';
|
|
4
|
+
import { decideConcurrency } from './glm-naruto-concurrency-governor.js';
|
|
5
|
+
import { planFileLeases } from './glm-naruto-file-lease.js';
|
|
6
|
+
export async function runPatchWorkerPool(input) {
|
|
7
|
+
const envelopes = [];
|
|
8
|
+
const traces = [];
|
|
9
|
+
const failedShardIds = [];
|
|
10
|
+
const concurrencyDecisions = [];
|
|
11
|
+
const shardPathMap = new Map();
|
|
12
|
+
for (const shard of input.shards) {
|
|
13
|
+
shardPathMap.set(shard.id, shard.target_paths);
|
|
14
|
+
}
|
|
15
|
+
const leases = planFileLeases(shardPathMap);
|
|
16
|
+
const mutableShards = input.shards.filter((s) => s.mutable);
|
|
17
|
+
const decision = decideConcurrency({
|
|
18
|
+
requestedClones: input.maxWorkers,
|
|
19
|
+
activeWorkers: Math.min(input.maxWorkers, mutableShards.length),
|
|
20
|
+
rateLimited429: 0,
|
|
21
|
+
ttftP90Ms: 0,
|
|
22
|
+
failureRate: 0,
|
|
23
|
+
operatorMax: input.maxWorkers
|
|
24
|
+
});
|
|
25
|
+
concurrencyDecisions.push(decision);
|
|
26
|
+
const workerTasks = [];
|
|
27
|
+
let workerIdx = 0;
|
|
28
|
+
for (const shard of mutableShards) {
|
|
29
|
+
const strategies = input.strategies.get(shard.id) || [shard.strategy];
|
|
30
|
+
for (const strategy of strategies) {
|
|
31
|
+
const workerId = `worker-${shard.id}-${strategy}-${workerIdx++}`;
|
|
32
|
+
const shardWithStrategy = { ...shard, strategy };
|
|
33
|
+
workerTasks.push(runPatchWorker({
|
|
34
|
+
apiKey: input.apiKey,
|
|
35
|
+
missionId: input.missionId,
|
|
36
|
+
workerId,
|
|
37
|
+
shard: shardWithStrategy,
|
|
38
|
+
contextSummary: input.contextSummary,
|
|
39
|
+
timeoutMs: input.workerTimeoutMs
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const results = await Promise.allSettled(workerTasks);
|
|
44
|
+
for (const result of results) {
|
|
45
|
+
if (result.status === 'fulfilled' && result.value.ok && result.value.envelope) {
|
|
46
|
+
const gate = evaluateGlmSpeedGate(result.value.envelope.patch);
|
|
47
|
+
let envelope = result.value.envelope;
|
|
48
|
+
if (gate.ok) {
|
|
49
|
+
const applyCheck = await checkAndApplyGlmPatch({
|
|
50
|
+
cwd: input.cwd,
|
|
51
|
+
patch: envelope.patch,
|
|
52
|
+
apply: false
|
|
53
|
+
});
|
|
54
|
+
envelope = applyCheck.ok
|
|
55
|
+
? { ...envelope, status: 'gate_passed' }
|
|
56
|
+
: { ...envelope, status: 'gate_failed', blockers: [applyCheck.error.code] };
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
envelope = {
|
|
60
|
+
...envelope,
|
|
61
|
+
status: 'gate_failed',
|
|
62
|
+
blockers: gate.checks.filter((c) => !c.ok).map((c) => c.reason || c.id)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
envelopes.push(envelope);
|
|
66
|
+
traces.push(result.value.trace);
|
|
67
|
+
}
|
|
68
|
+
else if (result.status === 'fulfilled') {
|
|
69
|
+
traces.push(result.value.trace);
|
|
70
|
+
failedShardIds.push(result.value.trace.shard_id);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// rejected promise
|
|
74
|
+
failedShardIds.push('unknown');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { envelopes, traces, failedShardIds, concurrencyDecisions };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=glm-naruto-worker-pool.js.map
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { nowIso } from '../../../fsx.js';
|
|
3
|
+
import { GLM_52_OPENROUTER_MODEL } from '../glm-52-settings.js';
|
|
4
|
+
import { buildGlm52Request } from '../glm-52-request.js';
|
|
5
|
+
import { sendOpenRouterChatCompletionStream } from '../../openrouter/openrouter-stream.js';
|
|
6
|
+
import { assertGlm52ActualModel } from '../glm-52-response-guard.js';
|
|
7
|
+
import { encodeGlmRequestWithCache } from '../glm-request-cache.js';
|
|
8
|
+
import { parsePatchCandidateOutput, createPatchEnvelope, digestPatch } from './glm-naruto-patch-envelope.js';
|
|
9
|
+
const STABLE_SYSTEM_PREFIX = `You are a SKS GLM Naruto patch worker. Model lock: ${GLM_52_OPENROUTER_MODEL}. No GPT/OpenAI fallback allowed. Output only <sks_patch_candidate>, <sks_need_context>, or <sks_blocked> envelopes. Use unified diff format for patches. Never write to main workspace directly. Follow proof-first mutation rules.`;
|
|
10
|
+
export async function runPatchWorker(input) {
|
|
11
|
+
const started = Date.now();
|
|
12
|
+
const sessionId = `sks-glm-naruto-${input.missionId}-${input.workerId}`;
|
|
13
|
+
const reasoningEffort = input.shard.reasoning;
|
|
14
|
+
const shardSuffix = JSON.stringify({
|
|
15
|
+
shard_id: input.shard.id,
|
|
16
|
+
task: input.shard.task,
|
|
17
|
+
target_paths: input.shard.target_paths,
|
|
18
|
+
forbidden_paths: input.shard.forbidden_paths,
|
|
19
|
+
base_digest: input.shard.base_digest,
|
|
20
|
+
strategy: input.shard.strategy,
|
|
21
|
+
context: input.contextSummary,
|
|
22
|
+
output_requirement: 'Produce a unified diff patch inside <sks_patch_candidate> tags with summary, target_paths, base_digest, strategy, and patch fields.'
|
|
23
|
+
});
|
|
24
|
+
const messages = [
|
|
25
|
+
{ role: 'system', content: STABLE_SYSTEM_PREFIX },
|
|
26
|
+
{ role: 'user', content: shardSuffix }
|
|
27
|
+
];
|
|
28
|
+
const request = buildGlm52Request({
|
|
29
|
+
profile: 'speed',
|
|
30
|
+
messages,
|
|
31
|
+
maxTokens: input.shard.max_tokens,
|
|
32
|
+
toolChoice: 'none',
|
|
33
|
+
parallelToolCalls: false,
|
|
34
|
+
providerSort: 'throughput'
|
|
35
|
+
});
|
|
36
|
+
const requestWithSession = { ...request, session_id: sessionId };
|
|
37
|
+
const encoded = encodeGlmRequestWithCache(requestWithSession);
|
|
38
|
+
const traceBase = {
|
|
39
|
+
worker_id: input.workerId,
|
|
40
|
+
shard_id: input.shard.id,
|
|
41
|
+
strategy: input.shard.strategy,
|
|
42
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
43
|
+
provider: 'openrouter',
|
|
44
|
+
session_id: sessionId,
|
|
45
|
+
ttft_ms: null,
|
|
46
|
+
total_ms: 0,
|
|
47
|
+
request_cache_hit: encoded.cacheHit,
|
|
48
|
+
output_digest: crypto.createHash('sha256').update(shardSuffix).digest('hex'),
|
|
49
|
+
patch_digest: null,
|
|
50
|
+
status: 'running'
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
const response = await sendOpenRouterChatCompletionStream({
|
|
54
|
+
apiKey: input.apiKey,
|
|
55
|
+
request: requestWithSession,
|
|
56
|
+
timeoutMs: input.timeoutMs
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
return {
|
|
60
|
+
envelope: null,
|
|
61
|
+
trace: { ...traceBase, total_ms: Date.now() - started, status: 'failed' },
|
|
62
|
+
ok: false,
|
|
63
|
+
error: response.error.code
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const modelGuard = assertGlm52ActualModel(response.value.model || GLM_52_OPENROUTER_MODEL);
|
|
67
|
+
if (!modelGuard.ok) {
|
|
68
|
+
return {
|
|
69
|
+
envelope: null,
|
|
70
|
+
trace: { ...traceBase, total_ms: Date.now() - started, status: 'blocked' },
|
|
71
|
+
ok: false,
|
|
72
|
+
error: `model_guard:${modelGuard.code}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const parsed = parsePatchCandidateOutput(response.value.content);
|
|
76
|
+
if (parsed.kind !== 'patch') {
|
|
77
|
+
return {
|
|
78
|
+
envelope: null,
|
|
79
|
+
trace: {
|
|
80
|
+
...traceBase,
|
|
81
|
+
ttft_ms: response.value.ttft_ms,
|
|
82
|
+
total_ms: Date.now() - started,
|
|
83
|
+
status: parsed.kind === 'blocked' ? 'blocked' : 'no_patch'
|
|
84
|
+
},
|
|
85
|
+
ok: false,
|
|
86
|
+
error: parsed.kind
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const envelope = createPatchEnvelope({
|
|
90
|
+
missionId: input.missionId,
|
|
91
|
+
workerId: input.workerId,
|
|
92
|
+
shardId: input.shard.id,
|
|
93
|
+
baseDigest: input.shard.base_digest,
|
|
94
|
+
patch: parsed.content,
|
|
95
|
+
strategy: input.shard.strategy,
|
|
96
|
+
reasoningEffort
|
|
97
|
+
});
|
|
98
|
+
const trace = {
|
|
99
|
+
...traceBase,
|
|
100
|
+
ttft_ms: response.value.ttft_ms,
|
|
101
|
+
total_ms: Date.now() - started,
|
|
102
|
+
patch_digest: digestPatch(envelope.patch),
|
|
103
|
+
status: 'completed'
|
|
104
|
+
};
|
|
105
|
+
return { envelope, trace, ok: true };
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
return {
|
|
109
|
+
envelope: null,
|
|
110
|
+
trace: { ...traceBase, total_ms: Date.now() - started, status: 'failed' },
|
|
111
|
+
ok: false,
|
|
112
|
+
error: err instanceof Error ? err.message : String(err)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export async function runVerifierWorker(input) {
|
|
117
|
+
const started = Date.now();
|
|
118
|
+
const sessionId = `sks-glm-naruto-verify-${input.missionId}-${input.workerId}`;
|
|
119
|
+
const messages = [
|
|
120
|
+
{ role: 'system', content: `You are a SKS GLM Naruto verifier. Model: ${GLM_52_OPENROUTER_MODEL}. No GPT fallback. Check if the patch is correct and safe. Output JSON: {"ok":true/false,"issues":["..."]}` },
|
|
121
|
+
{ role: 'user', content: JSON.stringify({ patch_sha256: input.envelope.patch_sha256, target_paths: input.envelope.target_paths, patch: input.envelope.patch.slice(0, 4000) }) }
|
|
122
|
+
];
|
|
123
|
+
const request = buildGlm52Request({
|
|
124
|
+
profile: 'speed',
|
|
125
|
+
messages,
|
|
126
|
+
maxTokens: 2048,
|
|
127
|
+
toolChoice: 'none',
|
|
128
|
+
parallelToolCalls: false
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
const response = await sendOpenRouterChatCompletionStream({
|
|
132
|
+
apiKey: input.apiKey,
|
|
133
|
+
request: { ...request, session_id: sessionId },
|
|
134
|
+
timeoutMs: input.timeoutMs
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
trace: {
|
|
140
|
+
worker_id: input.workerId,
|
|
141
|
+
shard_id: input.envelope.shard_id,
|
|
142
|
+
strategy: 'minimal_patch',
|
|
143
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
144
|
+
provider: 'openrouter',
|
|
145
|
+
session_id: sessionId,
|
|
146
|
+
ttft_ms: null,
|
|
147
|
+
total_ms: Date.now() - started,
|
|
148
|
+
request_cache_hit: false,
|
|
149
|
+
output_digest: '',
|
|
150
|
+
patch_digest: input.envelope.patch_sha256,
|
|
151
|
+
status: 'failed'
|
|
152
|
+
},
|
|
153
|
+
issues: [response.error.code]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
ok: true,
|
|
158
|
+
trace: {
|
|
159
|
+
worker_id: input.workerId,
|
|
160
|
+
shard_id: input.envelope.shard_id,
|
|
161
|
+
strategy: 'minimal_patch',
|
|
162
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
163
|
+
provider: 'openrouter',
|
|
164
|
+
session_id: sessionId,
|
|
165
|
+
ttft_ms: response.value.ttft_ms,
|
|
166
|
+
total_ms: Date.now() - started,
|
|
167
|
+
request_cache_hit: false,
|
|
168
|
+
output_digest: crypto.createHash('sha256').update(response.value.content).digest('hex'),
|
|
169
|
+
patch_digest: input.envelope.patch_sha256,
|
|
170
|
+
status: 'completed'
|
|
171
|
+
},
|
|
172
|
+
issues: []
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
return {
|
|
177
|
+
ok: false,
|
|
178
|
+
trace: {
|
|
179
|
+
worker_id: input.workerId,
|
|
180
|
+
shard_id: input.envelope.shard_id,
|
|
181
|
+
strategy: 'minimal_patch',
|
|
182
|
+
model: GLM_52_OPENROUTER_MODEL,
|
|
183
|
+
provider: 'openrouter',
|
|
184
|
+
session_id: sessionId,
|
|
185
|
+
ttft_ms: null,
|
|
186
|
+
total_ms: Date.now() - started,
|
|
187
|
+
request_cache_hit: false,
|
|
188
|
+
output_digest: '',
|
|
189
|
+
patch_digest: input.envelope.patch_sha256,
|
|
190
|
+
status: 'failed'
|
|
191
|
+
},
|
|
192
|
+
issues: [err instanceof Error ? err.message : String(err)]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=glm-naruto-worker-runtime.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function createWorktree(repoRoot, missionId, workerId, baseCommit) {
|
|
4
|
+
const branch = `sks-glm-naruto/${missionId}/${workerId}`;
|
|
5
|
+
const worktreePath = path.join(repoRoot, '.sneakoscope', 'glm-naruto', 'worktrees', missionId, workerId);
|
|
6
|
+
await runGit(repoRoot, ['worktree', 'add', '-b', branch, worktreePath, baseCommit || 'HEAD']);
|
|
7
|
+
return { path: worktreePath, workerId, branch };
|
|
8
|
+
}
|
|
9
|
+
export async function removeWorktree(repoRoot, worktree) {
|
|
10
|
+
try {
|
|
11
|
+
await runGit(repoRoot, ['worktree', 'remove', '--force', worktree.path]);
|
|
12
|
+
await runGit(repoRoot, ['branch', '-D', worktree.branch]);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// best-effort cleanup
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function applyPatchToWorktree(worktreePath, patch) {
|
|
19
|
+
try {
|
|
20
|
+
const result = await runGit(worktreePath, ['apply', '--whitespace=nowarn', '-'], patch);
|
|
21
|
+
return { ok: result.code === 0, stderr: result.stderr };
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
return { ok: false, stderr: String(err) };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function getWorktreeDiff(worktreePath) {
|
|
28
|
+
const result = await runGit(worktreePath, ['diff', 'HEAD']);
|
|
29
|
+
return result.stdout;
|
|
30
|
+
}
|
|
31
|
+
export async function resetWorktree(worktreePath) {
|
|
32
|
+
await runGit(worktreePath, ['checkout', '--', '.']);
|
|
33
|
+
}
|
|
34
|
+
function runGit(cwd, args, stdin) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const child = spawn('git', [...args], { cwd, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
37
|
+
let stdout = '';
|
|
38
|
+
let stderr = '';
|
|
39
|
+
child.stdout.on('data', (chunk) => { stdout += String(chunk); });
|
|
40
|
+
child.stderr.on('data', (chunk) => { stderr += String(chunk); });
|
|
41
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
|
42
|
+
if (stdin !== undefined)
|
|
43
|
+
child.stdin.end(stdin);
|
|
44
|
+
else
|
|
45
|
+
child.stdin.end();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=glm-naruto-worktree.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { nowIso, writeJsonAtomic } from '../../fsx.js';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function createProviderHealthTracker(providerSlug = 'openrouter', model = 'z-ai/glm-5.2') {
|
|
4
|
+
let health = {
|
|
5
|
+
schema: 'sks.openrouter-provider-health.v1',
|
|
6
|
+
provider_slug: providerSlug,
|
|
7
|
+
model,
|
|
8
|
+
p50_ttft_ms: 0,
|
|
9
|
+
p90_ttft_ms: 0,
|
|
10
|
+
p50_throughput: 0,
|
|
11
|
+
p90_throughput: 0,
|
|
12
|
+
count_429: 0,
|
|
13
|
+
count_5xx: 0,
|
|
14
|
+
last_success: null,
|
|
15
|
+
last_failure: null,
|
|
16
|
+
updated_at: nowIso()
|
|
17
|
+
};
|
|
18
|
+
const ttftSamples = [];
|
|
19
|
+
return {
|
|
20
|
+
record: (entry) => {
|
|
21
|
+
if (typeof entry.p50_ttft_ms === 'number')
|
|
22
|
+
ttftSamples.push(entry.p50_ttft_ms);
|
|
23
|
+
const p50 = ttftSamples.length > 0 ? (ttftSamples[Math.floor(ttftSamples.length * 0.5)] ?? 0) : 0;
|
|
24
|
+
const p90 = ttftSamples.length > 0 ? (ttftSamples[Math.floor(ttftSamples.length * 0.9)] ?? 0) : 0;
|
|
25
|
+
health = {
|
|
26
|
+
...health,
|
|
27
|
+
...entry,
|
|
28
|
+
p50_ttft_ms: p50,
|
|
29
|
+
p90_ttft_ms: p90,
|
|
30
|
+
count_429: health.count_429 + (entry.count_429 || 0),
|
|
31
|
+
count_5xx: health.count_5xx + (entry.count_5xx || 0),
|
|
32
|
+
last_success: entry.last_success || health.last_success,
|
|
33
|
+
last_failure: entry.last_failure || health.last_failure,
|
|
34
|
+
updated_at: nowIso()
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
getHealth: () => health,
|
|
38
|
+
snapshot: () => health
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function writeProviderHealth(root, health) {
|
|
42
|
+
const out = path.join(root, '.sneakoscope', 'glm-naruto', 'provider-health.json');
|
|
43
|
+
await writeJsonAtomic(out, health);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=openrouter-provider-health.js.map
|
|
@@ -110,4 +110,37 @@ async function ensureSecretDir(secretDir) {
|
|
|
110
110
|
await fs.mkdir(secretDir, { recursive: true, mode: 0o700 });
|
|
111
111
|
await fs.chmod(secretDir, 0o700).catch(() => undefined);
|
|
112
112
|
}
|
|
113
|
+
export async function promptForOpenRouterKeyHidden() {
|
|
114
|
+
const readline = await import('node:readline/promises');
|
|
115
|
+
const { stdin, stdout } = await import('node:process');
|
|
116
|
+
const rl = readline.createInterface({ input: stdin, output: stdout, terminal: true });
|
|
117
|
+
try {
|
|
118
|
+
// Use muted input for hidden key entry
|
|
119
|
+
let key = '';
|
|
120
|
+
const escaped = stdout.isTTY
|
|
121
|
+
? await new Promise((resolve) => {
|
|
122
|
+
const onData = (char) => {
|
|
123
|
+
const c = char.toString();
|
|
124
|
+
if (c === '\r' || c === '\n' || c === '\u0004') {
|
|
125
|
+
stdin.removeListener('data', onData);
|
|
126
|
+
resolve(key);
|
|
127
|
+
}
|
|
128
|
+
else if (c === '\u0003') {
|
|
129
|
+
stdin.removeListener('data', onData);
|
|
130
|
+
resolve('');
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
key += c;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
stdin.on('data', onData);
|
|
137
|
+
stdout.write('Enter OpenRouter API key (input hidden): ');
|
|
138
|
+
})
|
|
139
|
+
: await rl.question('Enter OpenRouter API key: ');
|
|
140
|
+
return escaped.trim() || null;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
rl.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
113
146
|
//# sourceMappingURL=openrouter-secret-store.js.map
|
|
@@ -22,10 +22,17 @@ export async function sendOpenRouterChatCompletionStream(input) {
|
|
|
22
22
|
});
|
|
23
23
|
if (timeout)
|
|
24
24
|
clearTimeout(timeout);
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const text = await response.text();
|
|
27
27
|
return { ok: false, error: normalizeOpenRouterError(response.status, text) };
|
|
28
|
-
|
|
28
|
+
}
|
|
29
|
+
// Real streaming via ReadableStream reader
|
|
30
|
+
if (response.body && typeof response.body.getReader === 'function') {
|
|
31
|
+
return { ok: true, value: await readRealStream(response.body, started) };
|
|
32
|
+
}
|
|
33
|
+
// Fallback: non-streaming response
|
|
34
|
+
const text = await response.text();
|
|
35
|
+
return { ok: true, value: parseOpenRouterStreamText(text, started, false) };
|
|
29
36
|
}
|
|
30
37
|
catch (err) {
|
|
31
38
|
if (timeout)
|
|
@@ -50,7 +57,67 @@ export async function sendOpenRouterChatCompletionStream(input) {
|
|
|
50
57
|
};
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
|
-
|
|
60
|
+
async function readRealStream(body, startedAtMs) {
|
|
61
|
+
const reader = body.getReader();
|
|
62
|
+
const decoder = new TextDecoder();
|
|
63
|
+
const events = [];
|
|
64
|
+
let content = '';
|
|
65
|
+
let model;
|
|
66
|
+
let usage;
|
|
67
|
+
let ttft = null;
|
|
68
|
+
let buffer = '';
|
|
69
|
+
let chunkCount = 0;
|
|
70
|
+
try {
|
|
71
|
+
while (true) {
|
|
72
|
+
const { done, value } = await reader.read();
|
|
73
|
+
if (done)
|
|
74
|
+
break;
|
|
75
|
+
buffer += decoder.decode(value, { stream: true });
|
|
76
|
+
const lines = buffer.split(/\r?\n/);
|
|
77
|
+
buffer = lines.pop() || '';
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (!line.startsWith('data:'))
|
|
80
|
+
continue;
|
|
81
|
+
const data = line.slice('data:'.length).trim();
|
|
82
|
+
if (!data || data === '[DONE]')
|
|
83
|
+
continue;
|
|
84
|
+
try {
|
|
85
|
+
const raw = JSON.parse(data);
|
|
86
|
+
const delta = raw?.choices?.[0]?.delta?.content;
|
|
87
|
+
if (typeof raw?.model === 'string')
|
|
88
|
+
model = raw.model;
|
|
89
|
+
if (raw?.usage)
|
|
90
|
+
usage = raw.usage;
|
|
91
|
+
if (typeof delta === 'string' && delta) {
|
|
92
|
+
if (ttft === null)
|
|
93
|
+
ttft = Math.max(0, Date.now() - startedAtMs);
|
|
94
|
+
content += delta;
|
|
95
|
+
chunkCount++;
|
|
96
|
+
events.push({ type: 'chunk', content_delta: delta, ...(model ? { model } : {}), raw });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
events.push({ type: 'error', raw: data });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
reader.releaseLock();
|
|
107
|
+
}
|
|
108
|
+
events.push({ type: 'done', ...(model ? { model } : {}), ...(usage ? { usage } : {}) });
|
|
109
|
+
return {
|
|
110
|
+
content,
|
|
111
|
+
...(model ? { model } : {}),
|
|
112
|
+
...(usage ? { usage } : {}),
|
|
113
|
+
ttft_ms: ttft,
|
|
114
|
+
total_ms: Math.max(0, Date.now() - startedAtMs),
|
|
115
|
+
chunk_count: chunkCount,
|
|
116
|
+
events,
|
|
117
|
+
real_stream: true
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export function parseOpenRouterStreamText(text, startedAtMs = Date.now(), realStream = false) {
|
|
54
121
|
const events = [];
|
|
55
122
|
let content = '';
|
|
56
123
|
let model;
|
|
@@ -88,7 +155,8 @@ export function parseOpenRouterStreamText(text, startedAtMs = Date.now()) {
|
|
|
88
155
|
ttft_ms: ttft,
|
|
89
156
|
total_ms: Math.max(0, Date.now() - startedAtMs),
|
|
90
157
|
chunk_count: events.filter((event) => event.type === 'chunk').length,
|
|
91
|
-
events
|
|
158
|
+
events,
|
|
159
|
+
real_stream: realStream
|
|
92
160
|
};
|
|
93
161
|
}
|
|
94
162
|
//# 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.8';
|
|
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.8",
|
|
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",
|