sneakoscope 2.0.2 → 2.0.5
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/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +86 -8
- package/dist/commands/doctor.js +14 -0
- package/dist/core/agents/agent-orchestrator.js +70 -4
- package/dist/core/agents/agent-patch-proof.js +5 -0
- package/dist/core/agents/agent-proof-evidence.js +61 -0
- package/dist/core/agents/agent-roster.js +35 -6
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/agents/native-worker-backend-router.js +31 -9
- package/dist/core/agents/ollama-worker-config.js +164 -15
- package/dist/core/codex/codex-0-137-compat.js +119 -0
- package/dist/core/codex-control/codex-control-proof.js +4 -1
- package/dist/core/codex-control/codex-fake-sdk-adapter.js +20 -0
- package/dist/core/codex-control/codex-output-schemas.js +5 -1
- package/dist/core/codex-control/codex-sdk-capability.js +1 -1
- package/dist/core/codex-control/codex-task-runner.js +329 -5
- package/dist/core/codex-control/gpt-final-arbiter.js +160 -0
- package/dist/core/codex-control/gpt-final-context-compressor.js +17 -0
- package/dist/core/codex-control/gpt-final-proof-pack.js +120 -0
- package/dist/core/codex-control/gpt-final-review-schema.js +71 -0
- package/dist/core/codex-control/python-codex-sdk-adapter.js +197 -0
- package/dist/core/codex-control/python-codex-sdk-event-translator.js +14 -0
- package/dist/core/commands/local-model-command.js +79 -18
- package/dist/core/commands/naruto-command.js +195 -12
- package/dist/core/commands/run-command.js +6 -2
- package/dist/core/doctor/doctor-readiness-matrix.js +34 -0
- package/dist/core/feature-fixtures.js +4 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/git-simple.js +143 -4
- package/dist/core/local-llm/local-collaboration-policy.js +93 -0
- package/dist/core/local-llm/local-llm-backpressure.js +20 -0
- package/dist/core/local-llm/local-llm-capability.js +29 -0
- package/dist/core/local-llm/local-llm-client.js +100 -0
- package/dist/core/local-llm/local-llm-config.js +20 -0
- package/dist/core/local-llm/local-llm-context-cache.js +21 -0
- package/dist/core/local-llm/local-llm-control-adapter.js +101 -0
- package/dist/core/local-llm/local-llm-json-repair.js +52 -0
- package/dist/core/local-llm/local-llm-metrics.js +42 -0
- package/dist/core/local-llm/local-llm-ollama-client.js +67 -0
- package/dist/core/local-llm/local-llm-openai-compatible-client.js +30 -0
- package/dist/core/local-llm/local-llm-prompt-cache.js +12 -0
- package/dist/core/local-llm/local-llm-scheduler.js +29 -0
- package/dist/core/local-llm/local-llm-schema-enforcer.js +15 -0
- package/dist/core/local-llm/local-llm-smoke.js +83 -0
- package/dist/core/local-llm/local-llm-warmup.js +20 -0
- package/dist/core/local-llm/local-worker-eligibility.js +27 -0
- package/dist/core/naruto/hardware-capacity-probe.js +36 -0
- package/dist/core/naruto/naruto-active-pool.js +118 -0
- package/dist/core/naruto/naruto-backpressure.js +13 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +65 -0
- package/dist/core/naruto/naruto-finalizer.js +18 -0
- package/dist/core/naruto/naruto-generation-scheduler.js +18 -0
- package/dist/core/naruto/naruto-gpt-final-pack.js +49 -0
- package/dist/core/naruto/naruto-parallel-patch-apply.js +95 -0
- package/dist/core/naruto/naruto-patch-transaction-batch.js +42 -0
- package/dist/core/naruto/naruto-role-policy.js +107 -0
- package/dist/core/naruto/naruto-verification-dag.js +42 -0
- package/dist/core/naruto/naruto-verification-pool.js +18 -0
- package/dist/core/naruto/naruto-work-graph.js +198 -0
- package/dist/core/naruto/naruto-work-item.js +40 -0
- package/dist/core/naruto/naruto-work-stealing.js +11 -0
- package/dist/core/naruto/resource-pressure-monitor.js +32 -0
- package/dist/core/pipeline/final-gpt-patch-stage.js +31 -0
- package/dist/core/pipeline/final-gpt-review-stage.js +5 -0
- package/dist/core/pipeline/finalize-pipeline-result.js +58 -0
- package/dist/core/pipeline/gpt-final-required.js +12 -0
- package/dist/core/prompt/prompt-placeholder-guard.js +30 -0
- package/dist/core/router/capability-card.js +13 -0
- package/dist/core/router/route-cache.js +3 -0
- package/dist/core/router/ultra-router.js +2 -1
- package/dist/core/routes.js +4 -4
- package/dist/core/safety/mutation-guard.js +2 -0
- package/dist/core/update-check.js +60 -25
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-lane-runtime.js +2 -2
- package/dist/core/zellij/zellij-naruto-dashboard.js +36 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +4 -4
- package/dist/scripts/blackbox-command-import-smoke.js +10 -1
- package/dist/scripts/check-package-boundary.js +12 -3
- package/dist/scripts/codex-0-137-compat-check.js +27 -0
- package/dist/scripts/codex-environment-scoped-approvals-check.js +10 -0
- package/dist/scripts/codex-plugin-list-json-check.js +8 -0
- package/dist/scripts/codex-sdk-team-naruto-agent-pipeline-check.js +2 -1
- package/dist/scripts/codex-thread-runtime-choice-check.js +10 -0
- package/dist/scripts/gpt-final-arbiter-check.js +63 -0
- package/dist/scripts/gpt-final-arbiter-performance-check.js +36 -0
- package/dist/scripts/local-collab-all-pipelines-final-gpt-check.js +21 -0
- package/dist/scripts/local-collab-gpt-final-availability-check.js +58 -0
- package/dist/scripts/local-collab-no-local-only-final-check.js +27 -0
- package/dist/scripts/local-collab-policy-check.js +17 -0
- package/dist/scripts/local-llm-all-pipelines-check.js +11 -0
- package/dist/scripts/local-llm-cache-performance-check.js +10 -0
- package/dist/scripts/local-llm-capability-check.js +14 -0
- package/dist/scripts/local-llm-smoke-check.js +23 -0
- package/dist/scripts/local-llm-structured-output-check.js +11 -0
- package/dist/scripts/local-llm-throughput-check.js +10 -0
- package/dist/scripts/local-llm-tool-call-repair-check.js +10 -0
- package/dist/scripts/local-llm-warmup-check.js +11 -0
- package/dist/scripts/naruto-active-pool-check.js +27 -0
- package/dist/scripts/naruto-concurrency-governor-check.js +52 -0
- package/dist/scripts/naruto-gpt-final-pack-check.js +34 -0
- package/dist/scripts/naruto-parallel-patch-apply-check.js +41 -0
- package/dist/scripts/naruto-real-local-gpt-final-smoke.js +16 -0
- package/dist/scripts/naruto-role-distribution-check.js +23 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +6 -0
- package/dist/scripts/naruto-verification-pool-check.js +36 -0
- package/dist/scripts/naruto-work-graph-check.js +24 -0
- package/dist/scripts/naruto-zellij-massive-ui-check.js +23 -0
- package/dist/scripts/prompt-placeholder-guard-check.js +33 -0
- package/dist/scripts/python-codex-sdk-all-pipelines-check.js +47 -0
- package/dist/scripts/python-codex-sdk-capability-check.js +75 -0
- package/dist/scripts/python-codex-sdk-sandbox-policy-check.js +10 -0
- package/dist/scripts/python-codex-sdk-stream-bridge-check.js +12 -0
- package/dist/scripts/release-parallel-check.js +1 -1
- package/dist/scripts/release-real-check.js +5 -0
- package/dist/scripts/zellij-worker-pane-manager-check.js +1 -1
- package/package.json +38 -4
- package/schemas/local-llm/local-collaboration-policy.schema.json +57 -0
- package/schemas/local-llm/local-model-config.schema.json +74 -0
- package/schemas/naruto/naruto-concurrency-governor.schema.json +21 -0
- package/schemas/naruto/naruto-work-graph.schema.json +22 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { nowIso } from '../fsx.js';
|
|
2
|
+
export const LOCAL_COLLABORATION_POLICY_SCHEMA = 'sks.local-collaboration-policy.v1';
|
|
3
|
+
export const LOCAL_COLLABORATION_FINAL_GATE_SCHEMA = 'sks.local-collaboration-final-gate.v1';
|
|
4
|
+
export const DEFAULT_LOCAL_COLLABORATION_MODE = 'local-parallel-gpt-final';
|
|
5
|
+
export const LOCAL_COLLABORATION_MODES = [
|
|
6
|
+
'disabled',
|
|
7
|
+
'local-draft-gpt-final',
|
|
8
|
+
'local-worker-gpt-orchestrator',
|
|
9
|
+
'local-parallel-gpt-final',
|
|
10
|
+
'local-only-draft'
|
|
11
|
+
];
|
|
12
|
+
export function resolveLocalCollaborationPolicy(input = {}) {
|
|
13
|
+
const env = input.env || process.env;
|
|
14
|
+
const requested = firstText(input.mode, env.SKS_LOCAL_COLLAB_MODE, DEFAULT_LOCAL_COLLABORATION_MODE);
|
|
15
|
+
const mode = normalizeLocalCollaborationMode(requested);
|
|
16
|
+
const invalid = mode ? [] : [`invalid_local_collaboration_mode:${requested}`];
|
|
17
|
+
const resolvedMode = mode || DEFAULT_LOCAL_COLLABORATION_MODE;
|
|
18
|
+
return {
|
|
19
|
+
schema: LOCAL_COLLABORATION_POLICY_SCHEMA,
|
|
20
|
+
generated_at: nowIso(),
|
|
21
|
+
mode: resolvedMode,
|
|
22
|
+
default_mode: DEFAULT_LOCAL_COLLABORATION_MODE,
|
|
23
|
+
local_llm_role: resolvedMode === 'disabled' ? 'disabled' : 'draft_worker',
|
|
24
|
+
gpt_final_required: resolvedMode !== 'disabled' && resolvedMode !== 'local-only-draft',
|
|
25
|
+
gpt_final_backend_must_be_remote: resolvedMode !== 'disabled',
|
|
26
|
+
local_only_draft: resolvedMode === 'local-only-draft',
|
|
27
|
+
final_accepted_statuses: ['approved', 'modified'],
|
|
28
|
+
final_patch_source_when_enabled: 'gpt_final_arbiter',
|
|
29
|
+
blockers: [
|
|
30
|
+
...invalid,
|
|
31
|
+
...(resolvedMode === 'local-only-draft' ? ['needs_gpt_final_review'] : [])
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function evaluateLocalCollaborationFinalGate(input = {}) {
|
|
36
|
+
const policy = input.policy || resolveLocalCollaborationPolicy(input.mode === undefined ? {} : { mode: input.mode });
|
|
37
|
+
const localParticipated = input.localParticipated !== false && policy.mode !== 'disabled';
|
|
38
|
+
const status = normalizeGptFinalStatus(input.gptFinalStatus);
|
|
39
|
+
const requiresGptFinal = policy.gpt_final_required && localParticipated;
|
|
40
|
+
const gptBackend = String(input.gptFinalBackend || '');
|
|
41
|
+
const remoteBackendOk = !gptBackend || !isLocalBackendName(gptBackend);
|
|
42
|
+
const blockers = [
|
|
43
|
+
...policy.blockers,
|
|
44
|
+
...(requiresGptFinal && input.gptFinalAvailable === false ? ['gpt_final_arbiter_unavailable'] : []),
|
|
45
|
+
...(requiresGptFinal && !status ? ['gpt_final_arbiter_missing'] : []),
|
|
46
|
+
...(requiresGptFinal && status && !policy.final_accepted_statuses.includes(status) ? [`gpt_final_status_not_accepted:${status}`] : []),
|
|
47
|
+
...(requiresGptFinal && !remoteBackendOk ? ['gpt_final_backend_must_not_be_local_llm'] : []),
|
|
48
|
+
...(policy.local_only_draft && input.applyPatches === true ? ['local_only_draft_apply_blocked'] : [])
|
|
49
|
+
];
|
|
50
|
+
const accepted = blockers.length === 0 && (!requiresGptFinal || status === 'approved' || status === 'modified');
|
|
51
|
+
return {
|
|
52
|
+
schema: LOCAL_COLLABORATION_FINAL_GATE_SCHEMA,
|
|
53
|
+
generated_at: nowIso(),
|
|
54
|
+
ok: accepted,
|
|
55
|
+
mode: policy.mode,
|
|
56
|
+
local_participated: localParticipated,
|
|
57
|
+
gpt_final_required: requiresGptFinal,
|
|
58
|
+
gpt_final_status: status,
|
|
59
|
+
gpt_final_backend: gptBackend || null,
|
|
60
|
+
final_status: accepted ? 'accepted' : policy.local_only_draft ? 'draft_only' : 'blocked',
|
|
61
|
+
apply_allowed: accepted && policy.local_only_draft !== true,
|
|
62
|
+
release_proof_allowed: accepted,
|
|
63
|
+
final_patch_source: accepted && policy.mode !== 'disabled' ? policy.final_patch_source_when_enabled : 'not_applicable',
|
|
64
|
+
blockers
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function localCollaborationParticipated(results = []) {
|
|
68
|
+
return results.some((result) => {
|
|
69
|
+
const backend = String(result?.backend_router_report?.selected_backend || result?.backend || '').toLowerCase();
|
|
70
|
+
return backend === 'ollama' || backend === 'local-llm' || backend === 'local_llm';
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function normalizeLocalCollaborationMode(value) {
|
|
74
|
+
const text = String(value ?? '').trim();
|
|
75
|
+
return LOCAL_COLLABORATION_MODES.includes(text) ? text : null;
|
|
76
|
+
}
|
|
77
|
+
export function normalizeGptFinalStatus(value) {
|
|
78
|
+
const text = String(value ?? '').trim();
|
|
79
|
+
return text === 'approved' || text === 'modified' || text === 'rejected' || text === 'needs_more_work' ? text : null;
|
|
80
|
+
}
|
|
81
|
+
function firstText(...values) {
|
|
82
|
+
for (const value of values) {
|
|
83
|
+
const text = String(value ?? '').trim();
|
|
84
|
+
if (text)
|
|
85
|
+
return text;
|
|
86
|
+
}
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
function isLocalBackendName(value) {
|
|
90
|
+
const text = value.toLowerCase();
|
|
91
|
+
return text === 'ollama' || text === 'local-llm' || text === 'local_llm';
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=local-collaboration-policy.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function classifyLocalLlmBackpressure(input) {
|
|
2
|
+
const max = Math.max(1, Math.floor(Number(input.maxParallelRequests || 1)));
|
|
3
|
+
const active = Math.max(0, Math.floor(Number(input.activeRequests || 0)));
|
|
4
|
+
const queue = Math.max(0, Math.floor(Number(input.queueDepth || 0)));
|
|
5
|
+
const p95 = Math.max(0, Number(input.p95LatencyMs || 0));
|
|
6
|
+
const state = active >= max && queue >= max
|
|
7
|
+
? 'saturated'
|
|
8
|
+
: active >= max || queue > max || p95 > 10_000
|
|
9
|
+
? 'throttled'
|
|
10
|
+
: 'normal';
|
|
11
|
+
return {
|
|
12
|
+
schema: 'sks.local-llm-backpressure.v1',
|
|
13
|
+
state,
|
|
14
|
+
active_requests: active,
|
|
15
|
+
max_parallel_requests: max,
|
|
16
|
+
queue_depth: queue,
|
|
17
|
+
p95_latency_ms: p95
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=local-llm-backpressure.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { listLocalLlmModels, probeLocalLlmEndpoint } from './local-llm-client.js';
|
|
2
|
+
export async function detectLocalLlmCapability(config) {
|
|
3
|
+
const version = await probeLocalLlmEndpoint(config);
|
|
4
|
+
const tags = version.ok ? await listLocalLlmModels(config) : { ok: false, models: [] };
|
|
5
|
+
const modelInstalled = tags.models.includes(config.model);
|
|
6
|
+
const capability = {
|
|
7
|
+
api_reachable: version.ok,
|
|
8
|
+
model_installed: modelInstalled,
|
|
9
|
+
supports_streaming: true,
|
|
10
|
+
supports_json_schema: config.provider === 'ollama',
|
|
11
|
+
supports_tools: false,
|
|
12
|
+
supports_images: false,
|
|
13
|
+
context_window: config.capability.context_window || 32768,
|
|
14
|
+
max_parallel_requests: config.capability.max_parallel_requests || 4
|
|
15
|
+
};
|
|
16
|
+
const blockers = [
|
|
17
|
+
...(version.ok ? [] : ['local_model_endpoint_unreachable']),
|
|
18
|
+
...(modelInstalled ? [] : ['local_model_missing'])
|
|
19
|
+
];
|
|
20
|
+
return {
|
|
21
|
+
ok: blockers.length === 0,
|
|
22
|
+
provider: config.provider,
|
|
23
|
+
model: config.model,
|
|
24
|
+
endpoint: config.base_url,
|
|
25
|
+
capability,
|
|
26
|
+
blockers
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=local-llm-capability.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { callOllamaGenerate, listOllamaModels, ollamaTokensPerSecond, probeOllamaVersion } from './local-llm-ollama-client.js';
|
|
2
|
+
import { callOpenAiCompatibleLocalChat } from './local-llm-openai-compatible-client.js';
|
|
3
|
+
export async function probeLocalLlmEndpoint(config) {
|
|
4
|
+
if (config.provider === 'ollama')
|
|
5
|
+
return probeOllamaVersion(config.base_url, Math.min(5000, Number(config.timeout_ms || 3000)));
|
|
6
|
+
return probeOpenAiCompatibleModels(config.base_url, Math.min(5000, Number(config.timeout_ms || 3000)));
|
|
7
|
+
}
|
|
8
|
+
export async function listLocalLlmModels(config) {
|
|
9
|
+
if (config.provider === 'ollama')
|
|
10
|
+
return listOllamaModels(config.base_url, Math.min(5000, Number(config.timeout_ms || 5000)));
|
|
11
|
+
return listOpenAiCompatibleModels(config.base_url, Math.min(5000, Number(config.timeout_ms || 5000)));
|
|
12
|
+
}
|
|
13
|
+
export async function callLocalLlmGenerate(config, request) {
|
|
14
|
+
if (config.provider === 'ollama')
|
|
15
|
+
return callOllamaGenerate(config, request);
|
|
16
|
+
const response = await callOpenAiCompatibleLocalChat({
|
|
17
|
+
endpoint: config.base_url,
|
|
18
|
+
model: request.model,
|
|
19
|
+
messages: request.messages || [{ role: 'user', content: request.prompt }],
|
|
20
|
+
temperature: Number((request.options || {}).temperature ?? config.temperature ?? 0)
|
|
21
|
+
}, Number(config.timeout_ms || 20_000));
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
return { ok: false, status: response.status, error: `http_${response.status}:${String(response.error || '').slice(0, 500)}` };
|
|
24
|
+
const text = extractOpenAiCompatibleText(response.data);
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
data: {
|
|
28
|
+
provider: config.provider,
|
|
29
|
+
model: request.model,
|
|
30
|
+
response: text,
|
|
31
|
+
raw: response.data
|
|
32
|
+
},
|
|
33
|
+
text
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function localLlmTokensPerSecond(data, fallbackText = '', latencyMs = 0) {
|
|
37
|
+
return ollamaTokensPerSecond(data, fallbackText, latencyMs);
|
|
38
|
+
}
|
|
39
|
+
export async function detectInstalledLocalModelCandidate(input = {}) {
|
|
40
|
+
const timeoutMs = input.timeoutMs || 3000;
|
|
41
|
+
const endpoints = [
|
|
42
|
+
{ provider: 'mlx-lm', base_url: trimTrailingSlash(input.mlxBaseUrl || process.env.SKS_MLX_LM_BASE_URL || process.env.SKS_LOCAL_LLM_BASE_URL || 'http://127.0.0.1:8080'), source: 'mlx_lm_server_v1_models' },
|
|
43
|
+
{ provider: 'openai-compatible', base_url: trimTrailingSlash(input.openAiCompatibleBaseUrl || process.env.SKS_OPENAI_COMPATIBLE_BASE_URL || process.env.SKS_LOCAL_OPENAI_COMPATIBLE_BASE_URL || process.env.LM_STUDIO_BASE_URL || 'http://127.0.0.1:1234'), source: 'openai_compatible_v1_models' },
|
|
44
|
+
{ provider: 'ollama', base_url: trimTrailingSlash(input.ollamaBaseUrl || process.env.SKS_OLLAMA_BASE_URL || 'http://127.0.0.1:11434'), source: 'ollama_api_tags' }
|
|
45
|
+
];
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
for (const endpoint of endpoints) {
|
|
48
|
+
if (!endpoint.base_url)
|
|
49
|
+
continue;
|
|
50
|
+
const key = `${endpoint.provider}:${endpoint.base_url}`;
|
|
51
|
+
if (seen.has(key))
|
|
52
|
+
continue;
|
|
53
|
+
seen.add(key);
|
|
54
|
+
const listed = await listLocalLlmModels({ ...endpoint, timeout_ms: timeoutMs }).catch(() => ({ ok: false, models: [] }));
|
|
55
|
+
if (!listed.ok || listed.models.length === 0)
|
|
56
|
+
continue;
|
|
57
|
+
const model = chooseModel(listed.models, input.preferredModel);
|
|
58
|
+
return { ...endpoint, endpoint: endpoint.base_url, model, models: listed.models };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
async function probeOpenAiCompatibleModels(baseUrl, timeoutMs = 3000) {
|
|
63
|
+
const models = await listOpenAiCompatibleModels(baseUrl, timeoutMs);
|
|
64
|
+
return { ...models, data: models.ok ? { models: models.models } : null };
|
|
65
|
+
}
|
|
66
|
+
async function listOpenAiCompatibleModels(baseUrl, timeoutMs = 5000) {
|
|
67
|
+
try {
|
|
68
|
+
const response = await fetch(`${trimTrailingSlash(baseUrl)}/v1/models`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
69
|
+
const text = await response.text();
|
|
70
|
+
const data = response.ok ? JSON.parse(text) : null;
|
|
71
|
+
const models = Array.isArray(data?.data) ? data.data.map((model) => String(model?.id || '')).filter(Boolean) : [];
|
|
72
|
+
return { ok: response.ok, status: response.status, models, data, error: response.ok ? null : text.slice(0, 500) };
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return { ok: false, status: 0, models: [], data: null, error: error instanceof Error ? error.message : String(error) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function extractOpenAiCompatibleText(data) {
|
|
79
|
+
const choice = Array.isArray(data?.choices) ? data.choices[0] : null;
|
|
80
|
+
if (typeof choice?.message?.content === 'string')
|
|
81
|
+
return choice.message.content;
|
|
82
|
+
if (typeof choice?.text === 'string')
|
|
83
|
+
return choice.text;
|
|
84
|
+
if (typeof data?.response === 'string')
|
|
85
|
+
return data.response;
|
|
86
|
+
if (typeof data?.content === 'string')
|
|
87
|
+
return data.content;
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
function chooseModel(models, preferredModel = '') {
|
|
91
|
+
const preferred = String(preferredModel || '').trim();
|
|
92
|
+
if (preferred && models.includes(preferred))
|
|
93
|
+
return preferred;
|
|
94
|
+
const qwen = models.find((model) => /qwen/i.test(model));
|
|
95
|
+
return qwen || models[0] || '';
|
|
96
|
+
}
|
|
97
|
+
function trimTrailingSlash(value) {
|
|
98
|
+
return String(value || '').replace(/\/+$/, '');
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=local-llm-client.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolveOllamaWorkerConfig } from '../agents/ollama-worker-config.js';
|
|
2
|
+
export async function resolveLocalLlmConfig(input = {}) {
|
|
3
|
+
const config = await resolveOllamaWorkerConfig(input);
|
|
4
|
+
return {
|
|
5
|
+
schema: 'sks.local-llm-config.v2',
|
|
6
|
+
ok: config.ok,
|
|
7
|
+
enabled: config.enabled,
|
|
8
|
+
status: config.status,
|
|
9
|
+
provider: config.provider,
|
|
10
|
+
model: config.model,
|
|
11
|
+
endpoint: config.endpoint,
|
|
12
|
+
base_url: config.base_url,
|
|
13
|
+
worker_only: true,
|
|
14
|
+
requires_gpt_final: config.policy.requires_gpt_final,
|
|
15
|
+
capability: config.capability,
|
|
16
|
+
last_smoke: config.last_smoke,
|
|
17
|
+
blockers: config.blockers
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=local-llm-config.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { sha256 } from '../fsx.js';
|
|
2
|
+
const SECRET_PATTERNS = [/api[_-]?key/i, /token/i, /secret/i, /password/i, /authorization/i];
|
|
3
|
+
export function buildLocalLlmContextCacheKey(parts) {
|
|
4
|
+
const redacted = redactSecrets(parts);
|
|
5
|
+
return {
|
|
6
|
+
schema: 'sks.local-llm-context-cache-key.v1',
|
|
7
|
+
key: sha256(JSON.stringify(redacted)),
|
|
8
|
+
redacted
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function redactSecrets(value) {
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return value.map(redactSecrets);
|
|
14
|
+
if (!value || typeof value !== 'object')
|
|
15
|
+
return value;
|
|
16
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [
|
|
17
|
+
key,
|
|
18
|
+
SECRET_PATTERNS.some((pattern) => pattern.test(key)) ? '[redacted]' : redactSecrets(child)
|
|
19
|
+
]));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=local-llm-context-cache.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { nowIso, sha256 } from '../fsx.js';
|
|
2
|
+
import { evaluateLocalWorkerEligibility } from './local-worker-eligibility.js';
|
|
3
|
+
import { callLocalLlmGenerate, localLlmTokensPerSecond } from './local-llm-client.js';
|
|
4
|
+
import { enforceLocalLlmJsonSchema } from './local-llm-schema-enforcer.js';
|
|
5
|
+
export async function runLocalLlmTask(input, opts) {
|
|
6
|
+
const started = Date.now();
|
|
7
|
+
const requestId = `local-llm:${sha256(`${nowIso()}:${input.missionId}:${input.workItemId || ''}:${input.slotId || ''}`).slice(0, 16)}`;
|
|
8
|
+
const eligibility = evaluateLocalWorkerEligibility(input, opts.config);
|
|
9
|
+
if (!eligibility.ok) {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
backend: 'local-llm',
|
|
13
|
+
backendFamily: 'local-llm',
|
|
14
|
+
requestId,
|
|
15
|
+
events: [],
|
|
16
|
+
finalResponse: '',
|
|
17
|
+
structuredOutput: null,
|
|
18
|
+
structuredOutputValid: false,
|
|
19
|
+
proof: buildProof(input, opts.config, requestId, started, eligibility, null, ['local_llm_eligibility_blocked', ...eligibility.blockers]),
|
|
20
|
+
blockers: ['local_llm_eligibility_blocked', ...eligibility.blockers]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const prompt = buildLocalLlmTaskPrompt(input, opts.outputSchema);
|
|
24
|
+
const response = await callLocalLlmGenerate(opts.config, {
|
|
25
|
+
model: opts.config.model,
|
|
26
|
+
prompt,
|
|
27
|
+
stream: false,
|
|
28
|
+
format: 'json',
|
|
29
|
+
think: opts.config.think,
|
|
30
|
+
keep_alive: opts.config.keep_alive,
|
|
31
|
+
options: { temperature: opts.config.temperature }
|
|
32
|
+
});
|
|
33
|
+
const latencyMs = Math.max(0, Date.now() - started);
|
|
34
|
+
const enforced = response.ok ? enforceLocalLlmJsonSchema(response.text, opts.outputSchema) : { ok: false, value: null, schema_valid: false, issues: [response.error] };
|
|
35
|
+
const blockers = [
|
|
36
|
+
...(response.ok ? [] : ['local_llm_generate_failed', response.error]),
|
|
37
|
+
...(enforced.ok ? [] : ['local_llm_structured_output_invalid', ...(enforced.issues || []).map((issue) => `schema:${String(issue)}`)])
|
|
38
|
+
];
|
|
39
|
+
const event = {
|
|
40
|
+
schema: 'sks.local-llm-event.v1',
|
|
41
|
+
generated_at: nowIso(),
|
|
42
|
+
type: response.ok ? 'local_llm_generate_completed' : 'local_llm_generate_failed',
|
|
43
|
+
request_id: requestId,
|
|
44
|
+
latency_ms: latencyMs,
|
|
45
|
+
schema_valid: enforced.schema_valid === true
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
ok: blockers.length === 0,
|
|
49
|
+
backend: 'local-llm',
|
|
50
|
+
backendFamily: 'local-llm',
|
|
51
|
+
requestId,
|
|
52
|
+
events: [event],
|
|
53
|
+
finalResponse: JSON.stringify(enforced.value || {}),
|
|
54
|
+
structuredOutput: enforced.value,
|
|
55
|
+
structuredOutputValid: enforced.schema_valid === true,
|
|
56
|
+
proof: buildProof(input, opts.config, requestId, started, eligibility, response.ok ? response.data : null, blockers, latencyMs, response.ok ? localLlmTokensPerSecond(response.data, response.text, latencyMs) : 0),
|
|
57
|
+
blockers
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function buildLocalLlmTaskPrompt(input, outputSchema) {
|
|
61
|
+
return [
|
|
62
|
+
'You are an SKS local LLM worker backend. You are worker-only.',
|
|
63
|
+
'You must not make strategy, planning, safety, verification, integration, or final acceptance decisions.',
|
|
64
|
+
'Return JSON only. Natural language outside JSON is invalid.',
|
|
65
|
+
'Your output is a draft and requires GPT Final Arbiter before final acceptance or patch application.',
|
|
66
|
+
JSON.stringify({
|
|
67
|
+
route: input.route,
|
|
68
|
+
mission_id: input.missionId,
|
|
69
|
+
work_item_id: input.workItemId || null,
|
|
70
|
+
slot_id: input.slotId || null,
|
|
71
|
+
sandbox_policy: input.sandboxPolicy,
|
|
72
|
+
prompt: input.prompt,
|
|
73
|
+
output_schema: outputSchema
|
|
74
|
+
}, null, 2)
|
|
75
|
+
].join('\n');
|
|
76
|
+
}
|
|
77
|
+
function buildProof(input, config, requestId, started, eligibility, rawResponse, blockers, latencyMs = Math.max(0, Date.now() - started), tokensPerSecond = 0) {
|
|
78
|
+
return {
|
|
79
|
+
schema: 'sks.local-llm-proof.v1',
|
|
80
|
+
generated_at: nowIso(),
|
|
81
|
+
ok: blockers.length === 0,
|
|
82
|
+
request_id: requestId,
|
|
83
|
+
provider: config.provider,
|
|
84
|
+
model: config.model,
|
|
85
|
+
endpoint: config.base_url,
|
|
86
|
+
backend: 'local-llm',
|
|
87
|
+
backend_family: 'local-llm',
|
|
88
|
+
route: input.route,
|
|
89
|
+
mission_id: input.missionId,
|
|
90
|
+
work_item_id: input.workItemId || null,
|
|
91
|
+
slot_id: input.slotId || null,
|
|
92
|
+
safety_scope: 'worker_only_requires_gpt_final',
|
|
93
|
+
latency_ms: latencyMs,
|
|
94
|
+
tokens_per_second: tokensPerSecond,
|
|
95
|
+
schema_valid: blockers.length === 0,
|
|
96
|
+
eligibility,
|
|
97
|
+
raw_response: rawResponse,
|
|
98
|
+
blockers
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=local-llm-control-adapter.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function parseOrRepairLocalLlmJson(text) {
|
|
2
|
+
const raw = String(text || '').trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
|
|
3
|
+
const direct = tryParse(raw);
|
|
4
|
+
if (direct.ok)
|
|
5
|
+
return { ok: true, value: direct.value, repaired: false, attempts: 0 };
|
|
6
|
+
const extracted = extractFirstJsonObject(raw);
|
|
7
|
+
if (extracted) {
|
|
8
|
+
const parsed = tryParse(extracted);
|
|
9
|
+
if (parsed.ok)
|
|
10
|
+
return { ok: true, value: parsed.value, repaired: true, attempts: 1 };
|
|
11
|
+
}
|
|
12
|
+
return { ok: false, value: null, repaired: false, attempts: 1, error: direct.error || 'json_parse_failed' };
|
|
13
|
+
}
|
|
14
|
+
function tryParse(text) {
|
|
15
|
+
try {
|
|
16
|
+
return { ok: true, value: JSON.parse(text) };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function extractFirstJsonObject(text) {
|
|
23
|
+
const start = text.indexOf('{');
|
|
24
|
+
if (start < 0)
|
|
25
|
+
return '';
|
|
26
|
+
let depth = 0;
|
|
27
|
+
let inString = false;
|
|
28
|
+
let escaped = false;
|
|
29
|
+
for (let i = start; i < text.length; i += 1) {
|
|
30
|
+
const ch = text[i];
|
|
31
|
+
if (inString) {
|
|
32
|
+
if (escaped)
|
|
33
|
+
escaped = false;
|
|
34
|
+
else if (ch === '\\')
|
|
35
|
+
escaped = true;
|
|
36
|
+
else if (ch === '"')
|
|
37
|
+
inString = false;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === '"')
|
|
41
|
+
inString = true;
|
|
42
|
+
else if (ch === '{')
|
|
43
|
+
depth += 1;
|
|
44
|
+
else if (ch === '}') {
|
|
45
|
+
depth -= 1;
|
|
46
|
+
if (depth === 0)
|
|
47
|
+
return text.slice(start, i + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=local-llm-json-repair.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { classifyLocalLlmBackpressure } from './local-llm-backpressure.js';
|
|
2
|
+
export function buildLocalLlmMetrics(input) {
|
|
3
|
+
const latencies = numeric(input.latencyMsSamples);
|
|
4
|
+
const metrics = {
|
|
5
|
+
schema: 'sks.local-llm-metrics.v1',
|
|
6
|
+
provider: input.provider || 'ollama',
|
|
7
|
+
model: input.model || 'unknown',
|
|
8
|
+
active_requests: Math.max(0, Math.floor(Number(input.activeRequests || 0))),
|
|
9
|
+
max_parallel_requests: Math.max(1, Math.floor(Number(input.maxParallelRequests || 1))),
|
|
10
|
+
queue_depth: Math.max(0, Math.floor(Number(input.queueDepth || 0))),
|
|
11
|
+
avg_first_token_ms: average(numeric(input.firstTokenMsSamples)),
|
|
12
|
+
avg_tokens_per_second: average(numeric(input.tokenPerSecondSamples)),
|
|
13
|
+
p95_latency_ms: percentile(latencies, 0.95)
|
|
14
|
+
};
|
|
15
|
+
const backpressureInput = {
|
|
16
|
+
activeRequests: metrics.active_requests,
|
|
17
|
+
maxParallelRequests: metrics.max_parallel_requests,
|
|
18
|
+
queueDepth: metrics.queue_depth
|
|
19
|
+
};
|
|
20
|
+
if (metrics.p95_latency_ms !== undefined)
|
|
21
|
+
backpressureInput.p95LatencyMs = metrics.p95_latency_ms;
|
|
22
|
+
return {
|
|
23
|
+
...metrics,
|
|
24
|
+
backpressure: classifyLocalLlmBackpressure(backpressureInput).state
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function numeric(values = []) {
|
|
28
|
+
return values.map(Number).filter((value) => Number.isFinite(value) && value >= 0);
|
|
29
|
+
}
|
|
30
|
+
function average(values) {
|
|
31
|
+
if (!values.length)
|
|
32
|
+
return 0;
|
|
33
|
+
return Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(2));
|
|
34
|
+
}
|
|
35
|
+
function percentile(values, p) {
|
|
36
|
+
if (!values.length)
|
|
37
|
+
return 0;
|
|
38
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
39
|
+
const index = Math.min(sorted.length - 1, Math.ceil(sorted.length * p) - 1);
|
|
40
|
+
return sorted[index];
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=local-llm-metrics.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export async function probeOllamaVersion(baseUrl, timeoutMs = 3000) {
|
|
2
|
+
try {
|
|
3
|
+
const response = await fetch(`${trimTrailingSlash(baseUrl)}/api/version`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
4
|
+
const text = await response.text();
|
|
5
|
+
return { ok: response.ok, status: response.status, data: response.ok ? JSON.parse(text) : null, error: response.ok ? null : text.slice(0, 500) };
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
return { ok: false, status: 0, data: null, error: error instanceof Error ? error.message : String(error) };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function listOllamaModels(baseUrl, timeoutMs = 5000) {
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(`${trimTrailingSlash(baseUrl)}/api/tags`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
14
|
+
const text = await response.text();
|
|
15
|
+
const data = response.ok ? JSON.parse(text) : null;
|
|
16
|
+
const models = Array.isArray(data?.models) ? data.models.map((model) => String(model?.name || '')).filter(Boolean) : [];
|
|
17
|
+
return { ok: response.ok, status: response.status, models, data, error: response.ok ? null : text.slice(0, 500) };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return { ok: false, status: 0, models: [], data: null, error: error instanceof Error ? error.message : String(error) };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function callOllamaGenerate(config, request) {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timer = setTimeout(() => controller.abort(), Math.max(1, Number(config.timeout_ms || 20_000)));
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(`${trimTrailingSlash(config.base_url)}/api/generate`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify(request),
|
|
31
|
+
signal: controller.signal
|
|
32
|
+
});
|
|
33
|
+
const text = await response.text();
|
|
34
|
+
if (!response.ok)
|
|
35
|
+
return { ok: false, status: response.status, error: `http_${response.status}:${text.slice(0, 500)}` };
|
|
36
|
+
const data = JSON.parse(text);
|
|
37
|
+
return { ok: true, data, text: extractOllamaText(data) };
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function extractOllamaText(data) {
|
|
47
|
+
if (typeof data.response === 'string')
|
|
48
|
+
return data.response;
|
|
49
|
+
if (typeof data.message?.content === 'string')
|
|
50
|
+
return data.message.content;
|
|
51
|
+
if (typeof data.content === 'string')
|
|
52
|
+
return data.content;
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
export function ollamaTokensPerSecond(data, fallbackText = '', latencyMs = 0) {
|
|
56
|
+
const evalCount = Number(data.eval_count || 0);
|
|
57
|
+
const evalDurationNs = Number(data.eval_duration || 0);
|
|
58
|
+
if (evalCount > 0 && evalDurationNs > 0)
|
|
59
|
+
return Number((evalCount / (evalDurationNs / 1_000_000_000)).toFixed(2));
|
|
60
|
+
const approximateTokens = Math.max(1, Math.ceil(fallbackText.length / 4));
|
|
61
|
+
const seconds = Math.max(0.001, latencyMs / 1000);
|
|
62
|
+
return Number((approximateTokens / seconds).toFixed(2));
|
|
63
|
+
}
|
|
64
|
+
function trimTrailingSlash(value) {
|
|
65
|
+
return String(value || '').replace(/\/+$/, '');
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=local-llm-ollama-client.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export async function callOpenAiCompatibleLocalChat(input, timeoutMs = 20_000) {
|
|
2
|
+
try {
|
|
3
|
+
const response = await fetch(`${input.endpoint.replace(/\/+$/, '')}/v1/chat/completions`, {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6
|
+
body: JSON.stringify({
|
|
7
|
+
model: input.model,
|
|
8
|
+
messages: input.messages,
|
|
9
|
+
temperature: input.temperature ?? 0
|
|
10
|
+
}),
|
|
11
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
12
|
+
});
|
|
13
|
+
const text = await response.text();
|
|
14
|
+
return {
|
|
15
|
+
ok: response.ok,
|
|
16
|
+
status: response.status,
|
|
17
|
+
data: response.ok ? JSON.parse(text) : null,
|
|
18
|
+
error: response.ok ? null : text.slice(0, 500)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
status: 0,
|
|
25
|
+
data: null,
|
|
26
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=local-llm-openai-compatible-client.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { buildLocalLlmContextCacheKey } from './local-llm-context-cache.js';
|
|
2
|
+
export function buildLocalLlmPromptCacheRecord(input) {
|
|
3
|
+
const key = buildLocalLlmContextCacheKey(input);
|
|
4
|
+
return {
|
|
5
|
+
schema: 'sks.local-llm-prompt-cache.v1',
|
|
6
|
+
cache_key: key.key,
|
|
7
|
+
source_hashes: input,
|
|
8
|
+
cacheable: Object.values(input).every(Boolean),
|
|
9
|
+
forbidden_material: ['secrets', 'auth tokens', 'raw private config']
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=local-llm-prompt-cache.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildLocalLlmMetrics } from './local-llm-metrics.js';
|
|
2
|
+
export function planLocalLlmSchedule(input) {
|
|
3
|
+
const max = Math.max(1, Math.floor(Number(input.maxParallelRequests || 1)));
|
|
4
|
+
const active = Math.max(0, Math.floor(Number(input.activeRequests || 0)));
|
|
5
|
+
const available = Math.max(0, max - active);
|
|
6
|
+
const queueDepth = Math.max(0, input.workItems.length - available);
|
|
7
|
+
const dispatch = input.workItems.slice(0, available);
|
|
8
|
+
const queued = input.workItems.slice(available);
|
|
9
|
+
const metrics = buildLocalLlmMetrics({
|
|
10
|
+
activeRequests: active + dispatch.length,
|
|
11
|
+
maxParallelRequests: max,
|
|
12
|
+
queueDepth,
|
|
13
|
+
latencyMsSamples: input.latencyMsSamples || []
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
schema: 'sks.local-llm-scheduler-plan.v1',
|
|
17
|
+
ok: metrics.active_requests <= max,
|
|
18
|
+
max_parallel_requests: max,
|
|
19
|
+
active_requests: metrics.active_requests,
|
|
20
|
+
dispatch_count: dispatch.length,
|
|
21
|
+
queued_count: queued.length,
|
|
22
|
+
backpressure: metrics.backpressure,
|
|
23
|
+
dispatch,
|
|
24
|
+
queued,
|
|
25
|
+
metrics,
|
|
26
|
+
blockers: metrics.active_requests <= max ? [] : ['local_llm_active_requests_exceeded']
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=local-llm-scheduler.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { validateJsonSchemaRecursive } from '../json-schema-validator.js';
|
|
2
|
+
import { parseOrRepairLocalLlmJson } from './local-llm-json-repair.js';
|
|
3
|
+
export function enforceLocalLlmJsonSchema(text, schema) {
|
|
4
|
+
const parsed = parseOrRepairLocalLlmJson(text);
|
|
5
|
+
const validation = parsed.ok ? validateJsonSchemaRecursive(parsed.value, schema) : { ok: false, issues: [parsed.error || 'json_parse_failed'] };
|
|
6
|
+
return {
|
|
7
|
+
ok: parsed.ok && validation.ok,
|
|
8
|
+
value: parsed.ok ? parsed.value : null,
|
|
9
|
+
repaired: parsed.repaired,
|
|
10
|
+
repair_attempts: parsed.attempts,
|
|
11
|
+
schema_valid: validation.ok,
|
|
12
|
+
issues: validation.issues
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=local-llm-schema-enforcer.js.map
|