blun-king-cli 9.1.475 → 9.1.476
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.
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { createAgentApiServiceHost } from './agent-api-service-host.mjs';
|
|
4
|
+
|
|
5
|
+
const LOOPBACK_HOST = '127.0.0.1';
|
|
6
|
+
const PROVIDER_NAME = 'privateAgentProvider';
|
|
7
|
+
const PUBLIC_MODEL = 'king';
|
|
8
|
+
|
|
9
|
+
function requireEnvironment(env) {
|
|
10
|
+
if (env === null || typeof env !== 'object' || Array.isArray(env)) {
|
|
11
|
+
throw new TypeError('env must be an object');
|
|
12
|
+
}
|
|
13
|
+
return env;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function requiredValue(env, name, options = {}) {
|
|
17
|
+
const value = env[name];
|
|
18
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
19
|
+
throw new TypeError(`${name} is required`);
|
|
20
|
+
}
|
|
21
|
+
const normalized = options.preserve === true ? value : value.trim();
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function optionalValue(env, name) {
|
|
26
|
+
if (!Object.hasOwn(env, name)) return undefined;
|
|
27
|
+
const value = env[name];
|
|
28
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
29
|
+
throw new TypeError(`${name} must be a non-empty string`);
|
|
30
|
+
}
|
|
31
|
+
return value.trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function integerValue(value, name, options = {}) {
|
|
35
|
+
if (!/^\d+$/u.test(value)) {
|
|
36
|
+
throw new TypeError(`${name} must be a positive safe integer`);
|
|
37
|
+
}
|
|
38
|
+
const parsed = Number(value);
|
|
39
|
+
const minimum = options.minimum ?? 1;
|
|
40
|
+
const maximum = options.maximum ?? Number.MAX_SAFE_INTEGER;
|
|
41
|
+
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
|
|
42
|
+
if (options.rangeMessage !== undefined) {
|
|
43
|
+
throw new TypeError(`${name} ${options.rangeMessage}`);
|
|
44
|
+
}
|
|
45
|
+
if (minimum === 1024) {
|
|
46
|
+
throw new TypeError(`${name} must be an integer of at least 1024`);
|
|
47
|
+
}
|
|
48
|
+
throw new TypeError(`${name} must be a positive safe integer`);
|
|
49
|
+
}
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requireSecret(env, name) {
|
|
54
|
+
const value = requiredValue(env, name, { preserve: true });
|
|
55
|
+
if (Buffer.byteLength(value, 'utf8') < 32) {
|
|
56
|
+
throw new TypeError(`${name} must contain at least 32 bytes`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function requireAbsoluteRoot(env) {
|
|
62
|
+
const value = requiredValue(env, 'BLUN_AGENT_API_ROOT_DIR');
|
|
63
|
+
if (!path.isAbsolute(value)) {
|
|
64
|
+
throw new TypeError('BLUN_AGENT_API_ROOT_DIR must be an absolute path');
|
|
65
|
+
}
|
|
66
|
+
return path.resolve(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function requireProviderBaseUrl(env) {
|
|
70
|
+
const value = requiredValue(env, 'BLUN_AGENT_API_PROVIDER_BASE_URL');
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = new URL(value);
|
|
74
|
+
} catch {
|
|
75
|
+
throw new TypeError('BLUN_AGENT_API_PROVIDER_BASE_URL must be a valid URL');
|
|
76
|
+
}
|
|
77
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
78
|
+
throw new TypeError('BLUN_AGENT_API_PROVIDER_BASE_URL must use http or https');
|
|
79
|
+
}
|
|
80
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) {
|
|
81
|
+
throw new TypeError('BLUN_AGENT_API_PROVIDER_BASE_URL must not contain credentials');
|
|
82
|
+
}
|
|
83
|
+
if (parsed.search.length > 0 || parsed.hash.length > 0) {
|
|
84
|
+
throw new TypeError('BLUN_AGENT_API_PROVIDER_BASE_URL must not contain a query or fragment');
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function loadAgentApiServiceEnvironment(input = process.env) {
|
|
90
|
+
const env = requireEnvironment(input);
|
|
91
|
+
const rootDir = requireAbsoluteRoot(env);
|
|
92
|
+
const internalSecret = requireSecret(env, 'BLUN_AGENT_API_INTERNAL_SECRET');
|
|
93
|
+
const isolationSecret = requireSecret(env, 'BLUN_AGENT_API_ISOLATION_SECRET');
|
|
94
|
+
if (internalSecret === isolationSecret) {
|
|
95
|
+
throw new TypeError('Agent API internal and isolation secrets must be different');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const port = integerValue(
|
|
99
|
+
requiredValue(env, 'BLUN_AGENT_API_PORT'),
|
|
100
|
+
'BLUN_AGENT_API_PORT',
|
|
101
|
+
{ maximum: 65_535, rangeMessage: 'must be an integer from 1 through 65535' },
|
|
102
|
+
);
|
|
103
|
+
const providerBaseUrl = requireProviderBaseUrl(env);
|
|
104
|
+
const providerApiKey = requiredValue(
|
|
105
|
+
env,
|
|
106
|
+
'BLUN_AGENT_API_PROVIDER_API_KEY',
|
|
107
|
+
{ preserve: true },
|
|
108
|
+
);
|
|
109
|
+
const providerModel = requiredValue(env, 'BLUN_AGENT_API_PROVIDER_MODEL');
|
|
110
|
+
const maxContextSize = integerValue(
|
|
111
|
+
requiredValue(env, 'BLUN_AGENT_API_MAX_CONTEXT_TOKENS'),
|
|
112
|
+
'BLUN_AGENT_API_MAX_CONTEXT_TOKENS',
|
|
113
|
+
);
|
|
114
|
+
const maxOutputSize = integerValue(
|
|
115
|
+
requiredValue(env, 'BLUN_AGENT_API_MAX_OUTPUT_TOKENS'),
|
|
116
|
+
'BLUN_AGENT_API_MAX_OUTPUT_TOKENS',
|
|
117
|
+
);
|
|
118
|
+
if (maxOutputSize > maxContextSize) {
|
|
119
|
+
throw new TypeError(
|
|
120
|
+
'BLUN_AGENT_API_MAX_OUTPUT_TOKENS must not exceed BLUN_AGENT_API_MAX_CONTEXT_TOKENS',
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const maxBodyBytes = integerValue(
|
|
124
|
+
requiredValue(env, 'BLUN_AGENT_API_MAX_BODY_BYTES'),
|
|
125
|
+
'BLUN_AGENT_API_MAX_BODY_BYTES',
|
|
126
|
+
{ minimum: 1024 },
|
|
127
|
+
);
|
|
128
|
+
const maxPrincipals = integerValue(
|
|
129
|
+
requiredValue(env, 'BLUN_AGENT_API_MAX_PRINCIPALS'),
|
|
130
|
+
'BLUN_AGENT_API_MAX_PRINCIPALS',
|
|
131
|
+
);
|
|
132
|
+
const maxSessionsPerPrincipal = integerValue(
|
|
133
|
+
requiredValue(env, 'BLUN_AGENT_API_MAX_SESSIONS_PER_PRINCIPAL'),
|
|
134
|
+
'BLUN_AGENT_API_MAX_SESSIONS_PER_PRINCIPAL',
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const idleTimeoutRaw = optionalValue(env, 'BLUN_AGENT_API_SESSION_IDLE_TIMEOUT_MS');
|
|
138
|
+
const idleSweepRaw = optionalValue(env, 'BLUN_AGENT_API_IDLE_SWEEP_INTERVAL_MS');
|
|
139
|
+
const sessionIdleTimeoutMs = idleTimeoutRaw === undefined
|
|
140
|
+
? 0
|
|
141
|
+
: integerValue(idleTimeoutRaw, 'BLUN_AGENT_API_SESSION_IDLE_TIMEOUT_MS');
|
|
142
|
+
if (idleSweepRaw !== undefined && sessionIdleTimeoutMs === 0) {
|
|
143
|
+
throw new TypeError(
|
|
144
|
+
'BLUN_AGENT_API_IDLE_SWEEP_INTERVAL_MS requires BLUN_AGENT_API_SESSION_IDLE_TIMEOUT_MS',
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const idleSweepIntervalMs = idleSweepRaw === undefined
|
|
148
|
+
? undefined
|
|
149
|
+
: integerValue(idleSweepRaw, 'BLUN_AGENT_API_IDLE_SWEEP_INTERVAL_MS');
|
|
150
|
+
|
|
151
|
+
const providerConfig = Object.freeze({
|
|
152
|
+
type: 'blun',
|
|
153
|
+
apiKey: providerApiKey,
|
|
154
|
+
baseUrl: providerBaseUrl,
|
|
155
|
+
defaultModel: providerModel,
|
|
156
|
+
});
|
|
157
|
+
const modelConfig = Object.freeze({
|
|
158
|
+
provider: PROVIDER_NAME,
|
|
159
|
+
model: providerModel,
|
|
160
|
+
maxContextSize,
|
|
161
|
+
maxOutputSize,
|
|
162
|
+
capabilities: Object.freeze(['tool_use']),
|
|
163
|
+
});
|
|
164
|
+
const harnessConfig = Object.freeze({
|
|
165
|
+
providers: Object.freeze({ [PROVIDER_NAME]: providerConfig }),
|
|
166
|
+
models: Object.freeze({ [PUBLIC_MODEL]: modelConfig }),
|
|
167
|
+
defaultModel: PUBLIC_MODEL,
|
|
168
|
+
});
|
|
169
|
+
const hostOptions = Object.freeze({
|
|
170
|
+
rootDir,
|
|
171
|
+
internalSecret,
|
|
172
|
+
isolationSecret,
|
|
173
|
+
harnessConfig,
|
|
174
|
+
maxBodyBytes,
|
|
175
|
+
maxPrincipals,
|
|
176
|
+
maxSessionsPerPrincipal,
|
|
177
|
+
sessionIdleTimeoutMs,
|
|
178
|
+
idleSweepIntervalMs,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return Object.freeze({
|
|
182
|
+
listen: Object.freeze({ host: LOOPBACK_HOST, port }),
|
|
183
|
+
hostOptions,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function startAgentApiServiceFromEnvironment(options = {}) {
|
|
188
|
+
const createServiceHost = options.createServiceHost === undefined
|
|
189
|
+
? createAgentApiServiceHost
|
|
190
|
+
: options.createServiceHost;
|
|
191
|
+
if (typeof createServiceHost !== 'function') {
|
|
192
|
+
throw new TypeError('createServiceHost must be a function');
|
|
193
|
+
}
|
|
194
|
+
const config = loadAgentApiServiceEnvironment(
|
|
195
|
+
options.env === undefined ? process.env : options.env,
|
|
196
|
+
);
|
|
197
|
+
const service = createServiceHost(config.hostOptions);
|
|
198
|
+
if (service === null || typeof service !== 'object'
|
|
199
|
+
|| typeof service.listen !== 'function'
|
|
200
|
+
|| typeof service.close !== 'function') {
|
|
201
|
+
throw new TypeError('Agent API service host must provide listen and close');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let address;
|
|
205
|
+
try {
|
|
206
|
+
address = await service.listen(config.listen);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
try {
|
|
209
|
+
await service.close();
|
|
210
|
+
} catch {
|
|
211
|
+
// The listen failure is the actionable error; cleanup remains best-effort.
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let closePromise;
|
|
217
|
+
return Object.freeze({
|
|
218
|
+
address: Object.freeze({
|
|
219
|
+
address: address.address,
|
|
220
|
+
family: address.family,
|
|
221
|
+
port: address.port,
|
|
222
|
+
}),
|
|
223
|
+
close() {
|
|
224
|
+
closePromise ??= Promise.resolve().then(() => service.close());
|
|
225
|
+
return closePromise;
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export {
|
|
231
|
+
LOOPBACK_HOST,
|
|
232
|
+
PROVIDER_NAME,
|
|
233
|
+
PUBLIC_MODEL,
|
|
234
|
+
loadAgentApiServiceEnvironment,
|
|
235
|
+
startAgentApiServiceFromEnvironment,
|
|
236
|
+
};
|