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.
Files changed (39) hide show
  1. package/README.md +2 -2
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/core/commands/glm-command.js +8 -1
  7. package/dist/core/fsx.js +1 -1
  8. package/dist/core/providers/glm/glm-bench.js +4 -4
  9. package/dist/core/providers/glm/glm-latency-trace.js +1 -1
  10. package/dist/core/providers/glm/glm-request-cache.js +10 -2
  11. package/dist/core/providers/glm/naruto/glm-naruto-artifacts.js +2 -0
  12. package/dist/core/providers/glm/naruto/glm-naruto-bench.js +68 -0
  13. package/dist/core/providers/glm/naruto/glm-naruto-budget.js +45 -0
  14. package/dist/core/providers/glm/naruto/glm-naruto-command.js +97 -0
  15. package/dist/core/providers/glm/naruto/glm-naruto-concurrency-governor.js +37 -0
  16. package/dist/core/providers/glm/naruto/glm-naruto-conflict-graph.js +74 -0
  17. package/dist/core/providers/glm/naruto/glm-naruto-decomposer.js +99 -0
  18. package/dist/core/providers/glm/naruto/glm-naruto-file-lease.js +23 -0
  19. package/dist/core/providers/glm/naruto/glm-naruto-finalizer.js +22 -0
  20. package/dist/core/providers/glm/naruto/glm-naruto-judge.js +84 -0
  21. package/dist/core/providers/glm/naruto/glm-naruto-merge-planner.js +57 -0
  22. package/dist/core/providers/glm/naruto/glm-naruto-orchestrator.js +224 -0
  23. package/dist/core/providers/glm/naruto/glm-naruto-patch-envelope.js +55 -0
  24. package/dist/core/providers/glm/naruto/glm-naruto-quorum.js +37 -0
  25. package/dist/core/providers/glm/naruto/glm-naruto-rate-limiter.js +18 -0
  26. package/dist/core/providers/glm/naruto/glm-naruto-repair-wave.js +21 -0
  27. package/dist/core/providers/glm/naruto/glm-naruto-shard-planner.js +32 -0
  28. package/dist/core/providers/glm/naruto/glm-naruto-trace.js +51 -0
  29. package/dist/core/providers/glm/naruto/glm-naruto-types.js +37 -0
  30. package/dist/core/providers/glm/naruto/glm-naruto-work-graph.js +2 -0
  31. package/dist/core/providers/glm/naruto/glm-naruto-worker-pool.js +79 -0
  32. package/dist/core/providers/glm/naruto/glm-naruto-worker-runtime.js +196 -0
  33. package/dist/core/providers/glm/naruto/glm-naruto-worker.js +2 -0
  34. package/dist/core/providers/glm/naruto/glm-naruto-worktree.js +48 -0
  35. package/dist/core/providers/openrouter/openrouter-provider-health.js +46 -0
  36. package/dist/core/providers/openrouter/openrouter-secret-store.js +33 -0
  37. package/dist/core/providers/openrouter/openrouter-stream.js +73 -5
  38. package/dist/core/version.js +1 -1
  39. 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,2 @@
1
+ export { runPatchWorker, runVerifierWorker } from './glm-naruto-worker-runtime.js';
2
+ //# sourceMappingURL=glm-naruto-worker.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
- const text = await response.text();
26
- if (!response.ok)
25
+ if (!response.ok) {
26
+ const text = await response.text();
27
27
  return { ok: false, error: normalizeOpenRouterError(response.status, text) };
28
- return { ok: true, value: parseOpenRouterStreamText(text, started) };
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
- export function parseOpenRouterStreamText(text, startedAtMs = Date.now()) {
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
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '4.0.6';
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.7",
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",