nemoris 0.1.0 → 0.1.2
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/.env.example +49 -49
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/SECURITY.md +59 -119
- package/bin/nemoris +46 -46
- package/config/agents/agent.toml.example +28 -28
- package/config/agents/content.toml +23 -0
- package/config/agents/default.toml +22 -22
- package/config/agents/heartbeat.toml +35 -0
- package/config/agents/iris.toml +23 -0
- package/config/agents/lab.toml +23 -0
- package/config/agents/main.toml +45 -0
- package/config/agents/nemo.toml +21 -0
- package/config/agents/ops.toml +38 -0
- package/config/agents/orchestrator.toml +18 -18
- package/config/agents/revenue.toml +23 -0
- package/config/agents/testyboo.toml +19 -0
- package/config/delivery.toml +73 -73
- package/config/embeddings.toml +5 -5
- package/config/identity/content-purpose.md +11 -0
- package/config/identity/content-soul.md +45 -0
- package/config/identity/default-purpose.md +1 -1
- package/config/identity/default-soul.md +3 -3
- package/config/identity/heartbeat-purpose.md +9 -0
- package/config/identity/heartbeat-soul.md +16 -0
- package/config/identity/iris-purpose.md +17 -0
- package/config/identity/iris-soul.md +68 -0
- package/config/identity/lab-purpose.md +10 -0
- package/config/identity/lab-soul.md +38 -0
- package/config/identity/main-purpose.md +17 -0
- package/config/identity/main-soul.md +66 -0
- package/config/identity/main-user.md +22 -0
- package/config/identity/ops-purpose.md +9 -0
- package/config/identity/ops-soul.md +16 -0
- package/config/identity/orchestrator-purpose.md +1 -1
- package/config/identity/orchestrator-soul.md +1 -1
- package/config/identity/revenue-purpose.md +9 -0
- package/config/identity/revenue-soul.md +41 -0
- package/config/identity/testyboo-purpose.md +13 -0
- package/config/identity/testyboo-soul.md +20 -0
- package/config/improvement-targets.toml +15 -15
- package/config/jobs/heartbeat-check.toml +30 -30
- package/config/jobs/memory-rollup.toml +46 -46
- package/config/jobs/workspace-health.toml +63 -63
- package/config/mcp.toml +16 -16
- package/config/output-contracts.toml +17 -17
- package/config/peers.toml +32 -32
- package/config/peers.toml.example +32 -32
- package/config/policies/memory-default.toml +10 -10
- package/config/policies/memory-heartbeat.toml +5 -5
- package/config/policies/memory-ops.toml +10 -10
- package/config/policies/tools-heartbeat-minimal.toml +8 -8
- package/config/policies/tools-interactive-safe.toml +8 -8
- package/config/policies/tools-ops-bounded.toml +8 -8
- package/config/policies/tools-orchestrator.toml +7 -7
- package/config/providers/anthropic.toml +15 -15
- package/config/providers/ollama.toml +5 -5
- package/config/providers/openai-codex.toml +9 -9
- package/config/providers/openrouter.toml +5 -5
- package/config/router.toml +22 -22
- package/config/runtime.toml +114 -114
- package/config/skills/self-improvement.toml +15 -15
- package/config/skills/telegram-onboarding-spec.md +240 -240
- package/config/skills/workspace-monitor.toml +15 -15
- package/config/task-router.toml +42 -42
- package/install.sh +50 -50
- package/package.json +91 -90
- package/src/auth/auth-profiles.js +169 -169
- package/src/auth/openai-codex-oauth.js +285 -285
- package/src/battle.js +449 -449
- package/src/cli/help.js +265 -265
- package/src/cli/output-filter.js +49 -49
- package/src/cli/runtime-control.js +704 -704
- package/src/cli-main.js +2763 -2763
- package/src/cli.js +78 -78
- package/src/config/loader.js +332 -332
- package/src/config/schema-validator.js +214 -214
- package/src/config/toml-lite.js +8 -8
- package/src/daemon/action-handlers.js +71 -71
- package/src/daemon/healing-tick.js +87 -87
- package/src/daemon/health-probes.js +90 -90
- package/src/daemon/notifier.js +57 -57
- package/src/daemon/nurse.js +218 -218
- package/src/daemon/repair-log.js +106 -106
- package/src/daemon/rule-staging.js +90 -90
- package/src/daemon/rules.js +29 -29
- package/src/daemon/telegram-commands.js +54 -54
- package/src/daemon/updater.js +85 -85
- package/src/jobs/job-runner.js +78 -78
- package/src/mcp/consumer.js +129 -129
- package/src/memory/active-recall.js +171 -171
- package/src/memory/backend-manager.js +97 -97
- package/src/memory/backends/file-backend.js +38 -38
- package/src/memory/backends/qmd-backend.js +219 -219
- package/src/memory/embedding-guards.js +24 -24
- package/src/memory/embedding-index.js +118 -118
- package/src/memory/embedding-service.js +179 -179
- package/src/memory/file-index.js +177 -177
- package/src/memory/memory-signature.js +5 -5
- package/src/memory/memory-store.js +648 -648
- package/src/memory/retrieval-planner.js +66 -66
- package/src/memory/scoring.js +145 -145
- package/src/memory/simhash.js +78 -78
- package/src/memory/sqlite-active-store.js +824 -824
- package/src/memory/write-policy.js +36 -36
- package/src/onboarding/aliases.js +33 -33
- package/src/onboarding/auth/api-key.js +224 -224
- package/src/onboarding/auth/ollama-detect.js +42 -42
- package/src/onboarding/clack-prompter.js +77 -77
- package/src/onboarding/doctor.js +530 -530
- package/src/onboarding/lock.js +42 -42
- package/src/onboarding/model-catalog.js +344 -344
- package/src/onboarding/phases/auth.js +576 -589
- package/src/onboarding/phases/build.js +130 -130
- package/src/onboarding/phases/choose.js +82 -82
- package/src/onboarding/phases/detect.js +98 -98
- package/src/onboarding/phases/hatch.js +216 -216
- package/src/onboarding/phases/identity.js +79 -79
- package/src/onboarding/phases/ollama.js +345 -345
- package/src/onboarding/phases/scaffold.js +99 -99
- package/src/onboarding/phases/telegram.js +377 -377
- package/src/onboarding/phases/validate.js +204 -204
- package/src/onboarding/phases/verify.js +206 -206
- package/src/onboarding/platform.js +482 -482
- package/src/onboarding/status-bar.js +95 -95
- package/src/onboarding/templates.js +794 -794
- package/src/onboarding/toml-writer.js +38 -38
- package/src/onboarding/tui.js +250 -250
- package/src/onboarding/uninstall.js +153 -153
- package/src/onboarding/wizard.js +516 -499
- package/src/providers/anthropic.js +168 -168
- package/src/providers/base.js +247 -247
- package/src/providers/circuit-breaker.js +136 -136
- package/src/providers/ollama.js +163 -163
- package/src/providers/openai-codex.js +149 -149
- package/src/providers/openrouter.js +136 -136
- package/src/providers/registry.js +36 -36
- package/src/providers/router.js +16 -16
- package/src/runtime/bootstrap-cache.js +47 -47
- package/src/runtime/capabilities-prompt.js +25 -25
- package/src/runtime/completion-ping.js +99 -99
- package/src/runtime/config-validator.js +121 -121
- package/src/runtime/context-ledger.js +360 -360
- package/src/runtime/cutover-readiness.js +42 -42
- package/src/runtime/daemon.js +729 -729
- package/src/runtime/delivery-ack.js +195 -195
- package/src/runtime/delivery-adapters/local-file.js +41 -41
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
- package/src/runtime/delivery-adapters/shadow.js +13 -13
- package/src/runtime/delivery-adapters/standalone-http.js +98 -98
- package/src/runtime/delivery-adapters/telegram.js +104 -104
- package/src/runtime/delivery-adapters/tui.js +128 -128
- package/src/runtime/delivery-manager.js +807 -807
- package/src/runtime/delivery-store.js +168 -168
- package/src/runtime/dependency-health.js +118 -118
- package/src/runtime/envelope.js +114 -114
- package/src/runtime/evaluation.js +1089 -1089
- package/src/runtime/exec-approvals.js +216 -216
- package/src/runtime/executor.js +500 -500
- package/src/runtime/failure-ping.js +67 -67
- package/src/runtime/flows.js +83 -83
- package/src/runtime/guards.js +45 -45
- package/src/runtime/handoff.js +51 -51
- package/src/runtime/identity-cache.js +28 -28
- package/src/runtime/improvement-engine.js +109 -109
- package/src/runtime/improvement-harness.js +581 -581
- package/src/runtime/input-sanitiser.js +72 -72
- package/src/runtime/interaction-contract.js +347 -347
- package/src/runtime/lane-readiness.js +226 -226
- package/src/runtime/migration.js +323 -323
- package/src/runtime/model-resolution.js +78 -78
- package/src/runtime/network.js +64 -64
- package/src/runtime/notification-store.js +97 -97
- package/src/runtime/notifier.js +256 -256
- package/src/runtime/orchestrator.js +53 -53
- package/src/runtime/orphan-reaper.js +41 -41
- package/src/runtime/output-contract-schema.js +139 -139
- package/src/runtime/output-contract-validator.js +439 -439
- package/src/runtime/peer-readiness.js +69 -69
- package/src/runtime/peer-registry.js +133 -133
- package/src/runtime/pilot-status.js +108 -108
- package/src/runtime/prompt-builder.js +261 -261
- package/src/runtime/provider-attempt.js +582 -582
- package/src/runtime/report-fallback.js +71 -71
- package/src/runtime/result-normalizer.js +183 -183
- package/src/runtime/retention.js +74 -74
- package/src/runtime/review.js +244 -244
- package/src/runtime/route-job.js +15 -15
- package/src/runtime/run-store.js +38 -38
- package/src/runtime/schedule.js +88 -88
- package/src/runtime/scheduler-state.js +434 -434
- package/src/runtime/scheduler.js +656 -656
- package/src/runtime/session-compactor.js +182 -182
- package/src/runtime/session-search.js +155 -155
- package/src/runtime/slack-inbound.js +249 -249
- package/src/runtime/ssrf.js +102 -102
- package/src/runtime/status-aggregator.js +330 -330
- package/src/runtime/task-contract.js +140 -140
- package/src/runtime/task-packet.js +107 -107
- package/src/runtime/task-router.js +140 -140
- package/src/runtime/telegram-inbound.js +1565 -1565
- package/src/runtime/token-counter.js +134 -134
- package/src/runtime/token-estimator.js +59 -59
- package/src/runtime/tool-loop.js +200 -200
- package/src/runtime/transport-server.js +311 -311
- package/src/runtime/tui-server.js +411 -411
- package/src/runtime/ulid.js +44 -44
- package/src/security/ssrf-check.js +197 -197
- package/src/setup.js +369 -369
- package/src/shadow/bridge.js +303 -303
- package/src/skills/loader.js +84 -84
- package/src/tools/catalog.json +49 -49
- package/src/tools/cli-delegate.js +44 -44
- package/src/tools/mcp-client.js +106 -106
- package/src/tools/micro/cancel-task.js +6 -6
- package/src/tools/micro/complete-task.js +6 -6
- package/src/tools/micro/fail-task.js +6 -6
- package/src/tools/micro/http-fetch.js +74 -74
- package/src/tools/micro/index.js +36 -36
- package/src/tools/micro/lcm-recall.js +60 -60
- package/src/tools/micro/list-dir.js +17 -17
- package/src/tools/micro/list-skills.js +46 -46
- package/src/tools/micro/load-skill.js +38 -38
- package/src/tools/micro/memory-search.js +45 -45
- package/src/tools/micro/read-file.js +11 -11
- package/src/tools/micro/session-search.js +54 -54
- package/src/tools/micro/shell-exec.js +43 -43
- package/src/tools/micro/trigger-job.js +79 -79
- package/src/tools/micro/web-search.js +58 -58
- package/src/tools/micro/workspace-paths.js +39 -39
- package/src/tools/micro/write-file.js +14 -14
- package/src/tools/micro/write-memory.js +41 -41
- package/src/tools/registry.js +348 -348
- package/src/tools/tool-result-contract.js +36 -36
- package/src/tui/chat.js +835 -835
- package/src/tui/renderer.js +175 -175
- package/src/tui/socket-client.js +217 -217
- package/src/utils/canonical-json.js +29 -29
- package/src/utils/compaction.js +30 -30
- package/src/utils/env-loader.js +5 -5
- package/src/utils/errors.js +80 -80
- package/src/utils/fs.js +101 -101
- package/src/utils/ids.js +5 -5
- package/src/utils/model-context-limits.js +30 -30
- package/src/utils/token-budget.js +74 -74
- package/src/utils/usage-cost.js +25 -25
- package/src/utils/usage-metrics.js +14 -14
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
const DEFAULT_TRANSIENT_CODES = new Set([408, 429, 500, 502, 503, 504]);
|
|
2
|
-
|
|
3
|
-
export function ensureBreakerSchema(db) {
|
|
4
|
-
db.exec(`
|
|
5
|
-
create table if not exists provider_breakers (
|
|
6
|
-
provider_key text primary key,
|
|
7
|
-
state text not null default 'closed',
|
|
8
|
-
failure_count integer not null default 0,
|
|
9
|
-
last_failure_at text,
|
|
10
|
-
last_success_at text,
|
|
11
|
-
opened_at text,
|
|
12
|
-
half_open_at text
|
|
13
|
-
);
|
|
14
|
-
`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class CircuitBreaker {
|
|
18
|
-
constructor(providerKey, db, config = {}) {
|
|
19
|
-
this._key = providerKey;
|
|
20
|
-
this._db = db;
|
|
21
|
-
this._failureThreshold = config.failureThreshold ?? 5;
|
|
22
|
-
this._resetTimeoutSeconds = config.resetTimeoutSeconds ?? 30;
|
|
23
|
-
this._transientCodes = new Set(config.transientCodes ?? DEFAULT_TRANSIENT_CODES);
|
|
24
|
-
|
|
25
|
-
ensureBreakerSchema(db);
|
|
26
|
-
this._load();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
static forProvider(providerKey, db, config = {}) {
|
|
30
|
-
return new CircuitBreaker(providerKey, db, config);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
_load() {
|
|
34
|
-
const row = this._db.prepare("select * from provider_breakers where provider_key = ?").get(this._key);
|
|
35
|
-
if (row) {
|
|
36
|
-
this._state = row.state;
|
|
37
|
-
this._failureCount = row.failure_count;
|
|
38
|
-
this._lastFailureAt = row.last_failure_at;
|
|
39
|
-
this._lastSuccessAt = row.last_success_at;
|
|
40
|
-
this._openedAt = row.opened_at;
|
|
41
|
-
this._halfOpenAt = row.half_open_at;
|
|
42
|
-
} else {
|
|
43
|
-
this._state = "closed";
|
|
44
|
-
this._failureCount = 0;
|
|
45
|
-
this._lastFailureAt = null;
|
|
46
|
-
this._lastSuccessAt = null;
|
|
47
|
-
this._openedAt = null;
|
|
48
|
-
this._halfOpenAt = null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
_persist() {
|
|
53
|
-
this._db.prepare(`
|
|
54
|
-
insert into provider_breakers (provider_key, state, failure_count, last_failure_at, last_success_at, opened_at, half_open_at)
|
|
55
|
-
values (?, ?, ?, ?, ?, ?, ?)
|
|
56
|
-
on conflict(provider_key) do update set
|
|
57
|
-
state = excluded.state,
|
|
58
|
-
failure_count = excluded.failure_count,
|
|
59
|
-
last_failure_at = excluded.last_failure_at,
|
|
60
|
-
last_success_at = excluded.last_success_at,
|
|
61
|
-
opened_at = excluded.opened_at,
|
|
62
|
-
half_open_at = excluded.half_open_at
|
|
63
|
-
`).run(
|
|
64
|
-
this._key,
|
|
65
|
-
this._state,
|
|
66
|
-
this._failureCount,
|
|
67
|
-
this._lastFailureAt,
|
|
68
|
-
this._lastSuccessAt,
|
|
69
|
-
this._openedAt,
|
|
70
|
-
this._halfOpenAt
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
_checkHalfOpen() {
|
|
75
|
-
if (this._state !== "open") return;
|
|
76
|
-
if (!this._openedAt) return;
|
|
77
|
-
const elapsed = (Date.now() - new Date(this._openedAt).getTime()) / 1000;
|
|
78
|
-
if (elapsed >= this._resetTimeoutSeconds) {
|
|
79
|
-
this._state = "half_open";
|
|
80
|
-
this._halfOpenAt = new Date().toISOString();
|
|
81
|
-
this._persist();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
isClosed() {
|
|
86
|
-
this._checkHalfOpen();
|
|
87
|
-
return this._state === "closed";
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
isOpen() {
|
|
91
|
-
this._checkHalfOpen();
|
|
92
|
-
return this._state === "open";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
isHalfOpen() {
|
|
96
|
-
this._checkHalfOpen();
|
|
97
|
-
return this._state === "half_open";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
failureCount() {
|
|
101
|
-
return this._failureCount;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
retryAfter() {
|
|
105
|
-
if (this._state !== "open" || !this._openedAt) return 0;
|
|
106
|
-
const elapsed = (Date.now() - new Date(this._openedAt).getTime()) / 1000;
|
|
107
|
-
return Math.max(0, this._resetTimeoutSeconds - elapsed);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
recordSuccess() {
|
|
111
|
-
this._state = "closed";
|
|
112
|
-
this._failureCount = 0;
|
|
113
|
-
this._lastSuccessAt = new Date().toISOString();
|
|
114
|
-
this._openedAt = null;
|
|
115
|
-
this._halfOpenAt = null;
|
|
116
|
-
this._persist();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
recordFailure(errorCode) {
|
|
120
|
-
if (!this._transientCodes.has(errorCode)) return;
|
|
121
|
-
|
|
122
|
-
this._failureCount += 1;
|
|
123
|
-
this._lastFailureAt = new Date().toISOString();
|
|
124
|
-
|
|
125
|
-
if (this._state === "half_open") {
|
|
126
|
-
this._state = "open";
|
|
127
|
-
this._openedAt = new Date().toISOString();
|
|
128
|
-
this._halfOpenAt = null;
|
|
129
|
-
} else if (this._failureCount >= this._failureThreshold) {
|
|
130
|
-
this._state = "open";
|
|
131
|
-
this._openedAt = new Date().toISOString();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
this._persist();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
1
|
+
const DEFAULT_TRANSIENT_CODES = new Set([408, 429, 500, 502, 503, 504]);
|
|
2
|
+
|
|
3
|
+
export function ensureBreakerSchema(db) {
|
|
4
|
+
db.exec(`
|
|
5
|
+
create table if not exists provider_breakers (
|
|
6
|
+
provider_key text primary key,
|
|
7
|
+
state text not null default 'closed',
|
|
8
|
+
failure_count integer not null default 0,
|
|
9
|
+
last_failure_at text,
|
|
10
|
+
last_success_at text,
|
|
11
|
+
opened_at text,
|
|
12
|
+
half_open_at text
|
|
13
|
+
);
|
|
14
|
+
`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class CircuitBreaker {
|
|
18
|
+
constructor(providerKey, db, config = {}) {
|
|
19
|
+
this._key = providerKey;
|
|
20
|
+
this._db = db;
|
|
21
|
+
this._failureThreshold = config.failureThreshold ?? 5;
|
|
22
|
+
this._resetTimeoutSeconds = config.resetTimeoutSeconds ?? 30;
|
|
23
|
+
this._transientCodes = new Set(config.transientCodes ?? DEFAULT_TRANSIENT_CODES);
|
|
24
|
+
|
|
25
|
+
ensureBreakerSchema(db);
|
|
26
|
+
this._load();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static forProvider(providerKey, db, config = {}) {
|
|
30
|
+
return new CircuitBreaker(providerKey, db, config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_load() {
|
|
34
|
+
const row = this._db.prepare("select * from provider_breakers where provider_key = ?").get(this._key);
|
|
35
|
+
if (row) {
|
|
36
|
+
this._state = row.state;
|
|
37
|
+
this._failureCount = row.failure_count;
|
|
38
|
+
this._lastFailureAt = row.last_failure_at;
|
|
39
|
+
this._lastSuccessAt = row.last_success_at;
|
|
40
|
+
this._openedAt = row.opened_at;
|
|
41
|
+
this._halfOpenAt = row.half_open_at;
|
|
42
|
+
} else {
|
|
43
|
+
this._state = "closed";
|
|
44
|
+
this._failureCount = 0;
|
|
45
|
+
this._lastFailureAt = null;
|
|
46
|
+
this._lastSuccessAt = null;
|
|
47
|
+
this._openedAt = null;
|
|
48
|
+
this._halfOpenAt = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_persist() {
|
|
53
|
+
this._db.prepare(`
|
|
54
|
+
insert into provider_breakers (provider_key, state, failure_count, last_failure_at, last_success_at, opened_at, half_open_at)
|
|
55
|
+
values (?, ?, ?, ?, ?, ?, ?)
|
|
56
|
+
on conflict(provider_key) do update set
|
|
57
|
+
state = excluded.state,
|
|
58
|
+
failure_count = excluded.failure_count,
|
|
59
|
+
last_failure_at = excluded.last_failure_at,
|
|
60
|
+
last_success_at = excluded.last_success_at,
|
|
61
|
+
opened_at = excluded.opened_at,
|
|
62
|
+
half_open_at = excluded.half_open_at
|
|
63
|
+
`).run(
|
|
64
|
+
this._key,
|
|
65
|
+
this._state,
|
|
66
|
+
this._failureCount,
|
|
67
|
+
this._lastFailureAt,
|
|
68
|
+
this._lastSuccessAt,
|
|
69
|
+
this._openedAt,
|
|
70
|
+
this._halfOpenAt
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_checkHalfOpen() {
|
|
75
|
+
if (this._state !== "open") return;
|
|
76
|
+
if (!this._openedAt) return;
|
|
77
|
+
const elapsed = (Date.now() - new Date(this._openedAt).getTime()) / 1000;
|
|
78
|
+
if (elapsed >= this._resetTimeoutSeconds) {
|
|
79
|
+
this._state = "half_open";
|
|
80
|
+
this._halfOpenAt = new Date().toISOString();
|
|
81
|
+
this._persist();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
isClosed() {
|
|
86
|
+
this._checkHalfOpen();
|
|
87
|
+
return this._state === "closed";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
isOpen() {
|
|
91
|
+
this._checkHalfOpen();
|
|
92
|
+
return this._state === "open";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
isHalfOpen() {
|
|
96
|
+
this._checkHalfOpen();
|
|
97
|
+
return this._state === "half_open";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
failureCount() {
|
|
101
|
+
return this._failureCount;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
retryAfter() {
|
|
105
|
+
if (this._state !== "open" || !this._openedAt) return 0;
|
|
106
|
+
const elapsed = (Date.now() - new Date(this._openedAt).getTime()) / 1000;
|
|
107
|
+
return Math.max(0, this._resetTimeoutSeconds - elapsed);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
recordSuccess() {
|
|
111
|
+
this._state = "closed";
|
|
112
|
+
this._failureCount = 0;
|
|
113
|
+
this._lastSuccessAt = new Date().toISOString();
|
|
114
|
+
this._openedAt = null;
|
|
115
|
+
this._halfOpenAt = null;
|
|
116
|
+
this._persist();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
recordFailure(errorCode) {
|
|
120
|
+
if (!this._transientCodes.has(errorCode)) return;
|
|
121
|
+
|
|
122
|
+
this._failureCount += 1;
|
|
123
|
+
this._lastFailureAt = new Date().toISOString();
|
|
124
|
+
|
|
125
|
+
if (this._state === "half_open") {
|
|
126
|
+
this._state = "open";
|
|
127
|
+
this._openedAt = new Date().toISOString();
|
|
128
|
+
this._halfOpenAt = null;
|
|
129
|
+
} else if (this._failureCount >= this._failureThreshold) {
|
|
130
|
+
this._state = "open";
|
|
131
|
+
this._openedAt = new Date().toISOString();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this._persist();
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/providers/ollama.js
CHANGED
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
import { ProviderAdapter } from "./base.js";
|
|
2
|
-
|
|
3
|
-
export class OllamaAdapter extends ProviderAdapter {
|
|
4
|
-
static providerId = "ollama";
|
|
5
|
-
|
|
6
|
-
getCapabilities() {
|
|
7
|
-
return {
|
|
8
|
-
supportsToolDefinitions: true,
|
|
9
|
-
supportsToolCalls: true,
|
|
10
|
-
structuredOutputMode: "prompt_contract",
|
|
11
|
-
supportsReasoningSchema: false,
|
|
12
|
-
tokenCountingMode: "heuristic",
|
|
13
|
-
toolReliabilityTier: "medium",
|
|
14
|
-
supportsVision: false
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
normalizeModel(model) {
|
|
19
|
-
if (typeof model !== "string") return model;
|
|
20
|
-
return model.startsWith("ollama/") ? model.slice("ollama/".length) : model;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
buildInvokePayload({ model, system, messages, tools, options }) {
|
|
24
|
-
const payload = {
|
|
25
|
-
model: this.normalizeModel(model),
|
|
26
|
-
stream: false,
|
|
27
|
-
system,
|
|
28
|
-
messages,
|
|
29
|
-
options
|
|
30
|
-
};
|
|
31
|
-
// Ollama /api/chat supports OpenAI-compatible tools array
|
|
32
|
-
if (tools?.length) payload.tools = tools;
|
|
33
|
-
return payload;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async invoke(input) {
|
|
37
|
-
return this.postJson("api/chat", this.buildInvokePayload(input), {}, {
|
|
38
|
-
timeoutMs: input.timeoutMs
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
normalizeResponse(raw) {
|
|
43
|
-
// Handle Ollama tool_calls (OpenAI-compatible format)
|
|
44
|
-
const toolCalls = raw?.message?.tool_calls;
|
|
45
|
-
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
46
|
-
// Convert to Anthropic-style tool_use blocks for executor compatibility
|
|
47
|
-
const content = toolCalls.map((tc) => ({
|
|
48
|
-
type: "tool_use",
|
|
49
|
-
id: tc.id || `ollama_tool_${Date.now()}`,
|
|
50
|
-
name: tc.function?.name || tc.name,
|
|
51
|
-
input: tc.function?.arguments
|
|
52
|
-
? (typeof tc.function.arguments === "string"
|
|
53
|
-
? JSON.parse(tc.function.arguments)
|
|
54
|
-
: tc.function.arguments)
|
|
55
|
-
: {}
|
|
56
|
-
}));
|
|
57
|
-
return {
|
|
58
|
-
summary: `Tool call: ${content.map(c => c.name).join(", ")}`,
|
|
59
|
-
output: null,
|
|
60
|
-
nextActions: [],
|
|
61
|
-
reasoning: null,
|
|
62
|
-
raw,
|
|
63
|
-
content
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const text = raw?.message?.content ? String(raw.message.content) : null;
|
|
68
|
-
const parsedJson = text ? this.parseStructuredJson(text) : null;
|
|
69
|
-
if (parsedJson) {
|
|
70
|
-
return {
|
|
71
|
-
summary: parsedJson.summary || "Provider returned structured JSON output.",
|
|
72
|
-
output: parsedJson.output || text,
|
|
73
|
-
nextActions: Array.isArray(parsedJson.nextActions) ? parsedJson.nextActions : [],
|
|
74
|
-
reasoning: parsedJson.analysis || null,
|
|
75
|
-
raw
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
if (text) {
|
|
79
|
-
return {
|
|
80
|
-
summary: text.slice(0, 200) || "Provider responded.",
|
|
81
|
-
output: text,
|
|
82
|
-
nextActions: [],
|
|
83
|
-
reasoning: null,
|
|
84
|
-
raw
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return super.normalizeResponse(raw);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async embed({ model, input }) {
|
|
92
|
-
const payload = {
|
|
93
|
-
model: this.normalizeModel(model),
|
|
94
|
-
input
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const result = await this.postJson("api/embed", payload, {}, {
|
|
99
|
-
timeoutMs: input.timeoutMs
|
|
100
|
-
});
|
|
101
|
-
if (Array.isArray(result.embeddings) && result.embeddings.length > 0) {
|
|
102
|
-
return {
|
|
103
|
-
embeddings: result.embeddings
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
if (Array.isArray(result.embedding)) {
|
|
107
|
-
return {
|
|
108
|
-
embeddings: [result.embedding]
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
return result;
|
|
112
|
-
} catch (error) {
|
|
113
|
-
if (!String(error.message || "").includes("404")) throw error;
|
|
114
|
-
const fallback = await this.postJson("api/embeddings", payload, {}, {
|
|
115
|
-
timeoutMs: input.timeoutMs
|
|
116
|
-
});
|
|
117
|
-
if (Array.isArray(fallback.embedding)) {
|
|
118
|
-
return {
|
|
119
|
-
embeddings: [fallback.embedding]
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
return fallback;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async healthCheck() {
|
|
127
|
-
// Ollama is intentionally local-only. fetchResponse enforces loopback-only
|
|
128
|
-
// on the configured baseUrl before any health probe leaves the process.
|
|
129
|
-
const response = await this.fetchResponse("api/tags", {
|
|
130
|
-
method: "GET"
|
|
131
|
-
});
|
|
132
|
-
return {
|
|
133
|
-
ok: response.ok,
|
|
134
|
-
status: response.status
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async listModels() {
|
|
139
|
-
const response = await this.fetchResponse("api/tags", {
|
|
140
|
-
method: "GET"
|
|
141
|
-
});
|
|
142
|
-
const text = await response.text();
|
|
143
|
-
let data = null;
|
|
144
|
-
if (text) {
|
|
145
|
-
try {
|
|
146
|
-
data = JSON.parse(text);
|
|
147
|
-
} catch {
|
|
148
|
-
data = null;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (!response.ok) {
|
|
152
|
-
throw new Error(`Provider error ${response.status}: ${typeof data === "string" ? data : JSON.stringify(data)}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const models = Array.isArray(data?.models)
|
|
156
|
-
? data.models
|
|
157
|
-
.map((item) => item?.name || item?.model || null)
|
|
158
|
-
.filter(Boolean)
|
|
159
|
-
: [];
|
|
160
|
-
|
|
161
|
-
return models;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
1
|
+
import { ProviderAdapter } from "./base.js";
|
|
2
|
+
|
|
3
|
+
export class OllamaAdapter extends ProviderAdapter {
|
|
4
|
+
static providerId = "ollama";
|
|
5
|
+
|
|
6
|
+
getCapabilities() {
|
|
7
|
+
return {
|
|
8
|
+
supportsToolDefinitions: true,
|
|
9
|
+
supportsToolCalls: true,
|
|
10
|
+
structuredOutputMode: "prompt_contract",
|
|
11
|
+
supportsReasoningSchema: false,
|
|
12
|
+
tokenCountingMode: "heuristic",
|
|
13
|
+
toolReliabilityTier: "medium",
|
|
14
|
+
supportsVision: false
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
normalizeModel(model) {
|
|
19
|
+
if (typeof model !== "string") return model;
|
|
20
|
+
return model.startsWith("ollama/") ? model.slice("ollama/".length) : model;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
buildInvokePayload({ model, system, messages, tools, options }) {
|
|
24
|
+
const payload = {
|
|
25
|
+
model: this.normalizeModel(model),
|
|
26
|
+
stream: false,
|
|
27
|
+
system,
|
|
28
|
+
messages,
|
|
29
|
+
options
|
|
30
|
+
};
|
|
31
|
+
// Ollama /api/chat supports OpenAI-compatible tools array
|
|
32
|
+
if (tools?.length) payload.tools = tools;
|
|
33
|
+
return payload;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async invoke(input) {
|
|
37
|
+
return this.postJson("api/chat", this.buildInvokePayload(input), {}, {
|
|
38
|
+
timeoutMs: input.timeoutMs
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
normalizeResponse(raw) {
|
|
43
|
+
// Handle Ollama tool_calls (OpenAI-compatible format)
|
|
44
|
+
const toolCalls = raw?.message?.tool_calls;
|
|
45
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
46
|
+
// Convert to Anthropic-style tool_use blocks for executor compatibility
|
|
47
|
+
const content = toolCalls.map((tc) => ({
|
|
48
|
+
type: "tool_use",
|
|
49
|
+
id: tc.id || `ollama_tool_${Date.now()}`,
|
|
50
|
+
name: tc.function?.name || tc.name,
|
|
51
|
+
input: tc.function?.arguments
|
|
52
|
+
? (typeof tc.function.arguments === "string"
|
|
53
|
+
? JSON.parse(tc.function.arguments)
|
|
54
|
+
: tc.function.arguments)
|
|
55
|
+
: {}
|
|
56
|
+
}));
|
|
57
|
+
return {
|
|
58
|
+
summary: `Tool call: ${content.map(c => c.name).join(", ")}`,
|
|
59
|
+
output: null,
|
|
60
|
+
nextActions: [],
|
|
61
|
+
reasoning: null,
|
|
62
|
+
raw,
|
|
63
|
+
content
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const text = raw?.message?.content ? String(raw.message.content) : null;
|
|
68
|
+
const parsedJson = text ? this.parseStructuredJson(text) : null;
|
|
69
|
+
if (parsedJson) {
|
|
70
|
+
return {
|
|
71
|
+
summary: parsedJson.summary || "Provider returned structured JSON output.",
|
|
72
|
+
output: parsedJson.output || text,
|
|
73
|
+
nextActions: Array.isArray(parsedJson.nextActions) ? parsedJson.nextActions : [],
|
|
74
|
+
reasoning: parsedJson.analysis || null,
|
|
75
|
+
raw
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (text) {
|
|
79
|
+
return {
|
|
80
|
+
summary: text.slice(0, 200) || "Provider responded.",
|
|
81
|
+
output: text,
|
|
82
|
+
nextActions: [],
|
|
83
|
+
reasoning: null,
|
|
84
|
+
raw
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return super.normalizeResponse(raw);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async embed({ model, input }) {
|
|
92
|
+
const payload = {
|
|
93
|
+
model: this.normalizeModel(model),
|
|
94
|
+
input
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await this.postJson("api/embed", payload, {}, {
|
|
99
|
+
timeoutMs: input.timeoutMs
|
|
100
|
+
});
|
|
101
|
+
if (Array.isArray(result.embeddings) && result.embeddings.length > 0) {
|
|
102
|
+
return {
|
|
103
|
+
embeddings: result.embeddings
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(result.embedding)) {
|
|
107
|
+
return {
|
|
108
|
+
embeddings: [result.embedding]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (!String(error.message || "").includes("404")) throw error;
|
|
114
|
+
const fallback = await this.postJson("api/embeddings", payload, {}, {
|
|
115
|
+
timeoutMs: input.timeoutMs
|
|
116
|
+
});
|
|
117
|
+
if (Array.isArray(fallback.embedding)) {
|
|
118
|
+
return {
|
|
119
|
+
embeddings: [fallback.embedding]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return fallback;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async healthCheck() {
|
|
127
|
+
// Ollama is intentionally local-only. fetchResponse enforces loopback-only
|
|
128
|
+
// on the configured baseUrl before any health probe leaves the process.
|
|
129
|
+
const response = await this.fetchResponse("api/tags", {
|
|
130
|
+
method: "GET"
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
ok: response.ok,
|
|
134
|
+
status: response.status
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async listModels() {
|
|
139
|
+
const response = await this.fetchResponse("api/tags", {
|
|
140
|
+
method: "GET"
|
|
141
|
+
});
|
|
142
|
+
const text = await response.text();
|
|
143
|
+
let data = null;
|
|
144
|
+
if (text) {
|
|
145
|
+
try {
|
|
146
|
+
data = JSON.parse(text);
|
|
147
|
+
} catch {
|
|
148
|
+
data = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw new Error(`Provider error ${response.status}: ${typeof data === "string" ? data : JSON.stringify(data)}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const models = Array.isArray(data?.models)
|
|
156
|
+
? data.models
|
|
157
|
+
.map((item) => item?.name || item?.model || null)
|
|
158
|
+
.filter(Boolean)
|
|
159
|
+
: [];
|
|
160
|
+
|
|
161
|
+
return models;
|
|
162
|
+
}
|
|
163
|
+
}
|