mio-agent-runtime 0.5.23 → 0.7.0
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 +524 -85
- package/bin/mio.js +2861 -758
- package/observe/observer.js +73 -15
- package/package.json +45 -45
- package/scripts/check-syntax.cjs +72 -0
- package/server/agent-store.js +140 -0
- package/server/evolution-report.js +311 -0
- package/server/experience-store.js +214 -0
- package/server/insight-store.js +118 -0
- package/server/llm-client.js +161 -0
- package/server/memory-store.js +505 -3
- package/server/mio-intelligence-mcp/index.js +129 -1521
- package/server/observer-store.js +228 -0
- package/server/policy-store.js +263 -0
- package/server/query-log.js +66 -0
- package/server/subscription-store.js +213 -0
- package/server/task-store.js +385 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Shared LLM client: the single implementation of chatJson, consumed by the MCP
|
|
4
|
+
// server (creativity engine + insight store) and by the CLI
|
|
5
|
+
// (`mio creativity generate` / `ferment`). Extracted from
|
|
6
|
+
// mio-intelligence-mcp/index.js so both entry points call the same model with
|
|
7
|
+
// the same configuration, and so "is an LLM configured?" has one answer.
|
|
8
|
+
//
|
|
9
|
+
// Configuration resolution order (highest first):
|
|
10
|
+
// 1. Environment variables
|
|
11
|
+
// LLM_API_URL OpenAI-compatible endpoint (default: opencode zen)
|
|
12
|
+
// LLM_KEY bearer token; empty means unauthenticated (common for
|
|
13
|
+
// a local Ollama / llama.cpp server)
|
|
14
|
+
// LLM_CHAT_MODEL model id, falls back to LLM_MODEL
|
|
15
|
+
// 2. config.json under MIO_HOME (written by `mio config llm`) — the `llm`
|
|
16
|
+
// object's apiUrl / apiKey / model keys
|
|
17
|
+
// 3. Built-in defaults
|
|
18
|
+
//
|
|
19
|
+
// Environment wins so a shell-level override can always beat a stored config
|
|
20
|
+
// without editing files. The config file is read lazily and cached, so a long
|
|
21
|
+
// running MCP process does not re-stat it on every call.
|
|
22
|
+
|
|
23
|
+
const fs = require('fs')
|
|
24
|
+
const os = require('os')
|
|
25
|
+
const path = require('path')
|
|
26
|
+
|
|
27
|
+
const DEFAULT_API_URL = 'https://opencode.ai/zen/go/v1/chat/completions'
|
|
28
|
+
const DEFAULT_MODEL = 'deepseek-v4-flash'
|
|
29
|
+
|
|
30
|
+
// Resolve the directory holding config.json. The CLI and the MCP server use
|
|
31
|
+
// different data dirs by design (MIO_HOME vs MIO_DATA_DIR||cwd/.mio-intelligence),
|
|
32
|
+
// so we honour whichever is set and fall back to the CLI default. No third
|
|
33
|
+
// convention is introduced here on purpose.
|
|
34
|
+
function homeDir() {
|
|
35
|
+
return (
|
|
36
|
+
process.env.MIO_HOME ||
|
|
37
|
+
process.env.MIO_DATA_DIR ||
|
|
38
|
+
path.join(os.homedir(), '.mio-intelligence')
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let cachedFileConfig
|
|
43
|
+
let cachedFor = null
|
|
44
|
+
|
|
45
|
+
// Read the `llm` block from config.json. Any failure (missing file, bad JSON,
|
|
46
|
+
// wrong shape) degrades to {} — configuration must never crash a caller.
|
|
47
|
+
function fileConfig() {
|
|
48
|
+
const target = homeDir()
|
|
49
|
+
if (cachedFor === target) return cachedFileConfig
|
|
50
|
+
cachedFor = target
|
|
51
|
+
cachedFileConfig = {}
|
|
52
|
+
try {
|
|
53
|
+
const raw = fs.readFileSync(path.join(target, 'config.json'), 'utf8')
|
|
54
|
+
const parsed = JSON.parse(raw)
|
|
55
|
+
const llm = parsed && typeof parsed.llm === 'object' && parsed.llm !== null ? parsed.llm : {}
|
|
56
|
+
const pick = (v) => (typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined)
|
|
57
|
+
cachedFileConfig = {
|
|
58
|
+
apiUrl: pick(llm.apiUrl),
|
|
59
|
+
apiKey: pick(llm.apiKey),
|
|
60
|
+
model: pick(llm.model),
|
|
61
|
+
}
|
|
62
|
+
} catch (_) {
|
|
63
|
+
cachedFileConfig = {}
|
|
64
|
+
}
|
|
65
|
+
return cachedFileConfig
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Exposed so writers (`mio config llm`) can drop the cache after editing the
|
|
69
|
+
// file, and so tests can reset between cases.
|
|
70
|
+
function resetLlmConfigCache() {
|
|
71
|
+
cachedFor = null
|
|
72
|
+
cachedFileConfig = {}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function llmConfig() {
|
|
76
|
+
const file = fileConfig()
|
|
77
|
+
return {
|
|
78
|
+
apiUrl: process.env.LLM_API_URL || file.apiUrl || DEFAULT_API_URL,
|
|
79
|
+
apiKey: process.env.LLM_KEY || file.apiKey || '',
|
|
80
|
+
model: process.env.LLM_CHAT_MODEL || process.env.LLM_MODEL || file.model || DEFAULT_MODEL,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// True when the user has pointed us somewhere other than the default hosted
|
|
85
|
+
// endpoint, or supplied a key. Used to warn (not block) before spending a call.
|
|
86
|
+
// "Configured" counts either source: an env var or a stored config.json entry.
|
|
87
|
+
function isLlmConfigured() {
|
|
88
|
+
const file = fileConfig()
|
|
89
|
+
return Boolean(
|
|
90
|
+
process.env.LLM_API_URL ||
|
|
91
|
+
process.env.LLM_KEY ||
|
|
92
|
+
file.apiUrl ||
|
|
93
|
+
file.apiKey ||
|
|
94
|
+
file.model
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Which source each field came from, for `mio config show` to display honestly.
|
|
99
|
+
// Reporting a value without its origin is how users end up editing config.json
|
|
100
|
+
// and wondering why nothing changed (an env var was shadowing them).
|
|
101
|
+
function llmConfigSources() {
|
|
102
|
+
const file = fileConfig()
|
|
103
|
+
return {
|
|
104
|
+
apiUrl: process.env.LLM_API_URL ? 'env' : file.apiUrl ? 'config' : 'default',
|
|
105
|
+
apiKey: process.env.LLM_KEY ? 'env' : file.apiKey ? 'config' : 'unset',
|
|
106
|
+
model: process.env.LLM_CHAT_MODEL
|
|
107
|
+
? 'env'
|
|
108
|
+
: process.env.LLM_MODEL
|
|
109
|
+
? 'env'
|
|
110
|
+
: file.model
|
|
111
|
+
? 'config'
|
|
112
|
+
: 'default',
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function chatJson(userText, opts = {}) {
|
|
117
|
+
const { apiUrl, apiKey, model } = llmConfig()
|
|
118
|
+
const system = opts.system || 'You are a creative AI assistant. Output JSON.'
|
|
119
|
+
const temperature = opts.temperature ?? 0.3
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const resp = await fetch(apiUrl, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: {
|
|
125
|
+
'Content-Type': 'application/json',
|
|
126
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
127
|
+
},
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
model,
|
|
130
|
+
temperature,
|
|
131
|
+
messages: [
|
|
132
|
+
{ role: 'system', content: system },
|
|
133
|
+
{ role: 'user', content: userText },
|
|
134
|
+
],
|
|
135
|
+
response_format: { type: 'json_object' },
|
|
136
|
+
}),
|
|
137
|
+
signal: AbortSignal.timeout(opts.timeoutMs || 30000),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if (!resp.ok) return { error: `HTTP ${resp.status}` }
|
|
141
|
+
const json = await resp.json()
|
|
142
|
+
const text = json.choices?.[0]?.message?.content || ''
|
|
143
|
+
try {
|
|
144
|
+
return { data: JSON.parse(text) }
|
|
145
|
+
} catch {
|
|
146
|
+
return { data: text }
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return { error: String(err) }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
chatJson,
|
|
155
|
+
llmConfig,
|
|
156
|
+
isLlmConfigured,
|
|
157
|
+
llmConfigSources,
|
|
158
|
+
resetLlmConfigCache,
|
|
159
|
+
DEFAULT_API_URL,
|
|
160
|
+
DEFAULT_MODEL,
|
|
161
|
+
}
|