@newheap/platform-ai-chat 0.9.0 → 0.11.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/admin/index.d.ts +117 -9
- package/agent-runs/index.d.ts +52 -8
- package/fesm2022/newheap-platform-ai-chat-admin.mjs +337 -33
- package/fesm2022/newheap-platform-ai-chat-admin.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat-agent-runs.mjs +154 -28
- package/fesm2022/newheap-platform-ai-chat-agent-runs.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat-testing.mjs +233 -15
- package/fesm2022/newheap-platform-ai-chat-testing.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat.mjs +446 -33
- package/fesm2022/newheap-platform-ai-chat.mjs.map +1 -1
- package/i18n/en.json +175 -11
- package/i18n/nl.json +175 -11
- package/index.d.ts +357 -15
- package/package.json +1 -1
- package/testing/index.d.ts +7 -0
|
@@ -10,6 +10,7 @@ const responseLengths = ['short', 'normal', 'long'];
|
|
|
10
10
|
const autonomyLevels = ['observe', 'explain', 'propose', 'simulate', 'execute'];
|
|
11
11
|
const authModes = ['none', 'bearer', 'api-key', 'forward-user-token'];
|
|
12
12
|
const maxInstructions = 20_000;
|
|
13
|
+
const maxStateReason = 500;
|
|
13
14
|
const maxCustomInstructions = 1_000;
|
|
14
15
|
const defaultPreferences = {
|
|
15
16
|
style: 'default',
|
|
@@ -28,15 +29,35 @@ class NhAssistantMockAdmin {
|
|
|
28
29
|
this.now = now;
|
|
29
30
|
this.agents = new Map();
|
|
30
31
|
this.servers = new Map();
|
|
32
|
+
this.replays = new Map();
|
|
31
33
|
this.contextVersions = [];
|
|
32
34
|
const admin = scenario.admin ?? {};
|
|
33
35
|
this.canAdminister = admin.canAdminister ?? true;
|
|
34
36
|
this.preferences = { ...defaultPreferences, ...scenario.preferences };
|
|
35
37
|
this.contextVersions.push({ version: 1, hash: hashText(admin.context ?? ''), updatedAt: now(), updatedBy: null, text: admin.context ?? '' });
|
|
36
|
-
const agents = admin.agents
|
|
37
|
-
|
|
38
|
+
const agents = admin.agents
|
|
39
|
+
?? scenario.agents.map(agent => ({ ...codeAgentFromSummary(agent), source: 'code' }));
|
|
40
|
+
for (const { source, skills, shadowOf, ...input } of agents) {
|
|
38
41
|
this.agents.set(input.id, {
|
|
39
|
-
agent:
|
|
42
|
+
agent: {
|
|
43
|
+
...this.toAgent(input, source, 1, false),
|
|
44
|
+
shadowOf: shadowOf ?? null,
|
|
45
|
+
skills: (skills ?? []).map((skill) => ({
|
|
46
|
+
skillId: skill.skillId,
|
|
47
|
+
description: skill.description,
|
|
48
|
+
runState: 'active',
|
|
49
|
+
changedAt: null,
|
|
50
|
+
changedBy: null,
|
|
51
|
+
reason: null,
|
|
52
|
+
waitingInputs: 0,
|
|
53
|
+
toolCallsToday: 0,
|
|
54
|
+
tokensToday: 0,
|
|
55
|
+
dailyToolCalls: null,
|
|
56
|
+
dailyTokens: null,
|
|
57
|
+
cancelledRuns: 0,
|
|
58
|
+
startedRuns: 0
|
|
59
|
+
}))
|
|
60
|
+
},
|
|
40
61
|
codeDefinition: source === 'code' ? structuredClone(input) : null
|
|
41
62
|
});
|
|
42
63
|
}
|
|
@@ -62,11 +83,11 @@ class NhAssistantMockAdmin {
|
|
|
62
83
|
stored.remoteTools = structuredClone(tools);
|
|
63
84
|
}
|
|
64
85
|
}
|
|
65
|
-
/** Chat agents:
|
|
86
|
+
/** Chat agents: code agents keep their keys, admin agents carry literal text; stopped agents are left out. */
|
|
66
87
|
chatAgents() {
|
|
67
88
|
const result = [];
|
|
68
89
|
for (const { agent } of this.agents.values()) {
|
|
69
|
-
if (
|
|
90
|
+
if (agent.runState === 'stopped' || agent.runState === 'configured-off') {
|
|
70
91
|
continue;
|
|
71
92
|
}
|
|
72
93
|
const summary = this.scenario.agents.find(item => item.id === agent.id);
|
|
@@ -104,7 +125,7 @@ class NhAssistantMockAdmin {
|
|
|
104
125
|
};
|
|
105
126
|
return { status: 200, body: this.preferences };
|
|
106
127
|
}
|
|
107
|
-
handleAdmin(method, segments, body) {
|
|
128
|
+
handleAdmin(method, segments, body, query = new URLSearchParams()) {
|
|
108
129
|
if (!this.canAdminister) {
|
|
109
130
|
return failure(403, 'assistant-forbidden');
|
|
110
131
|
}
|
|
@@ -118,6 +139,15 @@ class NhAssistantMockAdmin {
|
|
|
118
139
|
if (id !== undefined && action === 'feedback') {
|
|
119
140
|
return this.agentFeedback(method, id, child, segments.length);
|
|
120
141
|
}
|
|
142
|
+
if (id !== undefined && action === 'evaluation') {
|
|
143
|
+
return this.agentEvaluation(method, id, child, segments.length, query);
|
|
144
|
+
}
|
|
145
|
+
if (id !== undefined && (action === 'replay' || action === 'replays') && segments.length === 4) {
|
|
146
|
+
return this.agentReplay(method, id, action, body);
|
|
147
|
+
}
|
|
148
|
+
if (id !== undefined && action === 'skills' && child !== undefined && segments[5] === 'state' && segments.length === 6) {
|
|
149
|
+
return this.skillStateEndpoint(method, id, child, body);
|
|
150
|
+
}
|
|
121
151
|
return this.agentsEndpoint(method, id, action, body, segments.length);
|
|
122
152
|
case 'mcp-servers':
|
|
123
153
|
return this.serversEndpoint(method, id, action, child, body, segments.length);
|
|
@@ -192,9 +222,22 @@ class NhAssistantMockAdmin {
|
|
|
192
222
|
if (!stored.codeDefinition) {
|
|
193
223
|
return failure(409, 'assistant-agent-not-code');
|
|
194
224
|
}
|
|
195
|
-
stored.agent = this.toAgent(stored.codeDefinition, 'code', stored.agent.version + 1, false);
|
|
225
|
+
stored.agent = this.toAgent(stored.codeDefinition, 'code', stored.agent.version + 1, false, stored.agent);
|
|
196
226
|
return { status: 200, body: stored.agent };
|
|
197
227
|
}
|
|
228
|
+
if (length === 4 && action === 'state') {
|
|
229
|
+
return this.agentStateEndpoint(stored, method, body);
|
|
230
|
+
}
|
|
231
|
+
if (length === 4 && action === 'kill-switch' && method === 'POST') {
|
|
232
|
+
const engaged = body?.engaged;
|
|
233
|
+
if (typeof engaged !== 'boolean') {
|
|
234
|
+
return validationFailure();
|
|
235
|
+
}
|
|
236
|
+
const changed = this.agentStateEndpoint(stored, 'POST', { state: engaged ? 'stopped' : 'active' });
|
|
237
|
+
return changed.status === 200
|
|
238
|
+
? { status: 200, body: { agentId: id, enabled: stored.agent.runState !== 'stopped', cancelledRuns: 0 } }
|
|
239
|
+
: changed;
|
|
240
|
+
}
|
|
198
241
|
if (length !== 3) {
|
|
199
242
|
return failure(404, 'assistant-not-found');
|
|
200
243
|
}
|
|
@@ -208,7 +251,7 @@ class NhAssistantMockAdmin {
|
|
|
208
251
|
return failure(409, 'assistant-version-conflict');
|
|
209
252
|
}
|
|
210
253
|
const { expectedVersion, ...definition } = input;
|
|
211
|
-
stored.agent = this.toAgent(definition, stored.agent.source, stored.agent.version + 1, stored.agent.source === 'code');
|
|
254
|
+
stored.agent = this.toAgent(definition, stored.agent.source, stored.agent.version + 1, stored.agent.source === 'code', stored.agent);
|
|
212
255
|
return { status: 200, body: stored.agent };
|
|
213
256
|
}
|
|
214
257
|
if (method === 'DELETE') {
|
|
@@ -220,6 +263,68 @@ class NhAssistantMockAdmin {
|
|
|
220
263
|
}
|
|
221
264
|
return { status: 405 };
|
|
222
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* `POST admin/agents/{id}/skills/{skillId}/state`: the skill's state, next to the agent's.
|
|
268
|
+
*/
|
|
269
|
+
skillStateEndpoint(method, agentId, skillId, body) {
|
|
270
|
+
const stored = this.agents.get(agentId);
|
|
271
|
+
const skill = stored?.agent.skills?.find(item => item.skillId === skillId);
|
|
272
|
+
if (!stored || !skill) {
|
|
273
|
+
return failure(404, 'assistant-skill-not-found');
|
|
274
|
+
}
|
|
275
|
+
if (method !== 'POST') {
|
|
276
|
+
return { status: 405 };
|
|
277
|
+
}
|
|
278
|
+
const request = body;
|
|
279
|
+
if (request?.state !== 'active' && request?.state !== 'paused' && request?.state !== 'stopped') {
|
|
280
|
+
return validationFailure();
|
|
281
|
+
}
|
|
282
|
+
const reason = typeof request.reason === 'string' && request.reason.trim().length > 0 ? request.reason.trim() : null;
|
|
283
|
+
const changed = skill.runState === request.state
|
|
284
|
+
? skill
|
|
285
|
+
: { ...skill, runState: request.state, changedAt: this.now(), changedBy: 'current-user', reason };
|
|
286
|
+
stored.agent = { ...stored.agent, skills: stored.agent.skills.map(item => item.skillId === skillId ? changed : item) };
|
|
287
|
+
return { status: 200, body: changed };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* `GET` and `POST admin/agents/{id}/state`. The mock runs no agents on their own, so a pause
|
|
291
|
+
* collects no waiting events and a stop cancels no runs; the version never changes.
|
|
292
|
+
*/
|
|
293
|
+
agentStateEndpoint(stored, method, body) {
|
|
294
|
+
if (method === 'GET') {
|
|
295
|
+
return { status: 200, body: toAgentState(stored.agent) };
|
|
296
|
+
}
|
|
297
|
+
if (method !== 'POST') {
|
|
298
|
+
return { status: 405 };
|
|
299
|
+
}
|
|
300
|
+
const request = body;
|
|
301
|
+
if (request?.state === undefined || request.state === null) {
|
|
302
|
+
return validationFailure();
|
|
303
|
+
}
|
|
304
|
+
if (request.state !== 'active' && request.state !== 'paused' && request.state !== 'stopped') {
|
|
305
|
+
return validationFailure();
|
|
306
|
+
}
|
|
307
|
+
const reason = typeof request.reason === 'string' && request.reason.trim().length > 0 ? request.reason.trim() : null;
|
|
308
|
+
if (reason !== null && reason.length > maxStateReason) {
|
|
309
|
+
return validationFailure();
|
|
310
|
+
}
|
|
311
|
+
if (stored.agent.runState === 'configured-off') {
|
|
312
|
+
return request.state === 'active'
|
|
313
|
+
? failure(409, 'assistant-agent-configured-off')
|
|
314
|
+
: { status: 200, body: toAgentState(stored.agent) };
|
|
315
|
+
}
|
|
316
|
+
if (stored.agent.runState !== request.state) {
|
|
317
|
+
stored.agent = {
|
|
318
|
+
...stored.agent,
|
|
319
|
+
isEnabled: request.state !== 'stopped',
|
|
320
|
+
runState: request.state,
|
|
321
|
+
stateChangedAt: this.now(),
|
|
322
|
+
stateChangedBy: 'current-user',
|
|
323
|
+
stateReason: reason
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return { status: 200, body: toAgentState(stored.agent) };
|
|
327
|
+
}
|
|
223
328
|
/**
|
|
224
329
|
* The feedback report of an agent. The mock runs no agents on their own and records no proposals,
|
|
225
330
|
* so the summary, the list and the JSON Lines export are empty.
|
|
@@ -244,6 +349,92 @@ class NhAssistantMockAdmin {
|
|
|
244
349
|
}
|
|
245
350
|
return failure(404, 'assistant-not-found');
|
|
246
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* The comparison of a shadow variant with its live agent. The mock runs no agents on their own,
|
|
354
|
+
* so it pairs no runs and the export is empty.
|
|
355
|
+
*/
|
|
356
|
+
agentEvaluation(method, agentId, child, length, query) {
|
|
357
|
+
if (!this.agents.has(agentId)) {
|
|
358
|
+
return failure(404, 'assistant-agent-not-found');
|
|
359
|
+
}
|
|
360
|
+
if (method !== 'GET') {
|
|
361
|
+
return { status: 405 };
|
|
362
|
+
}
|
|
363
|
+
const variantId = query.get('variant') ?? '';
|
|
364
|
+
if (this.agents.get(variantId)?.agent.shadowOf !== agentId) {
|
|
365
|
+
return failure(400, 'assistant-evaluation-not-variant');
|
|
366
|
+
}
|
|
367
|
+
if (length === 5 && child === 'export') {
|
|
368
|
+
return { status: 200 };
|
|
369
|
+
}
|
|
370
|
+
if (length !== 4) {
|
|
371
|
+
return failure(404, 'assistant-not-found');
|
|
372
|
+
}
|
|
373
|
+
const to = query.get('to') ?? this.now();
|
|
374
|
+
const from = query.get('from') ?? new Date(Date.parse(to) - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
375
|
+
const noCost = {
|
|
376
|
+
runs: 0, modelCalls: 0, tokens: 0, toolCalls: 0,
|
|
377
|
+
averageModelCalls: 0, averageTokens: 0, averageToolCalls: 0,
|
|
378
|
+
p95ModelCalls: 0, p95Tokens: 0, p95ToolCalls: 0
|
|
379
|
+
};
|
|
380
|
+
return {
|
|
381
|
+
status: 200,
|
|
382
|
+
body: {
|
|
383
|
+
agentId,
|
|
384
|
+
variantId,
|
|
385
|
+
from,
|
|
386
|
+
to,
|
|
387
|
+
runs: { live: 0, variant: 0, paired: 0, replayed: 0, liveUnpaired: 0, variantUnpaired: 0, truncated: false },
|
|
388
|
+
tools: [],
|
|
389
|
+
liveCost: noCost,
|
|
390
|
+
variantCost: noCost,
|
|
391
|
+
routing: [],
|
|
392
|
+
differences: []
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Replays into a shadow variant. The mock records no runs, so a replay completes at once
|
|
398
|
+
* without replaying any.
|
|
399
|
+
*/
|
|
400
|
+
agentReplay(method, agentId, action, body) {
|
|
401
|
+
const stored = this.agents.get(agentId);
|
|
402
|
+
if (!stored) {
|
|
403
|
+
return failure(404, 'assistant-agent-not-found');
|
|
404
|
+
}
|
|
405
|
+
if (action === 'replays') {
|
|
406
|
+
return method === 'GET' ? { status: 200, body: { items: [...(this.replays.get(agentId) ?? [])].reverse() } } : { status: 405 };
|
|
407
|
+
}
|
|
408
|
+
if (method !== 'POST') {
|
|
409
|
+
return { status: 405 };
|
|
410
|
+
}
|
|
411
|
+
if (!stored.agent.shadowOf) {
|
|
412
|
+
return failure(400, 'assistant-replay-not-shadow');
|
|
413
|
+
}
|
|
414
|
+
const request = (body ?? {});
|
|
415
|
+
const maxRuns = request.maxRuns ?? 100;
|
|
416
|
+
if (maxRuns < 1 || maxRuns > 500) {
|
|
417
|
+
return failure(400, 'assistant-message-invalid');
|
|
418
|
+
}
|
|
419
|
+
const now = this.now();
|
|
420
|
+
const to = request.to ?? now;
|
|
421
|
+
const replay = {
|
|
422
|
+
id: crypto.randomUUID(),
|
|
423
|
+
variantAgentId: agentId,
|
|
424
|
+
sourceAgentId: request.sourceAgentId ?? stored.agent.shadowOf,
|
|
425
|
+
from: request.from ?? new Date(Date.parse(to) - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
426
|
+
to,
|
|
427
|
+
maxRuns,
|
|
428
|
+
status: 'completed',
|
|
429
|
+
stopReason: null,
|
|
430
|
+
runsReplayed: 0,
|
|
431
|
+
runsSkipped: 0,
|
|
432
|
+
createdAt: now,
|
|
433
|
+
endedAt: now
|
|
434
|
+
};
|
|
435
|
+
this.replays.set(agentId, [...(this.replays.get(agentId) ?? []), replay]);
|
|
436
|
+
return { status: 202, body: replay };
|
|
437
|
+
}
|
|
247
438
|
serversEndpoint(method, id, action, child, body, length) {
|
|
248
439
|
if (id === undefined) {
|
|
249
440
|
if (method === 'GET') {
|
|
@@ -418,16 +609,29 @@ class NhAssistantMockAdmin {
|
|
|
418
609
|
Array.isArray(input.mcpServerIds) && input.mcpServerIds.every(serverId => this.servers.has(serverId)) &&
|
|
419
610
|
(input.requiredPolicy === null || (typeof input.requiredPolicy === 'string' && (!policies || policies.includes(input.requiredPolicy)))) &&
|
|
420
611
|
autonomyLevels.includes(input.autonomy) &&
|
|
421
|
-
typeof input.isEnabled === 'boolean';
|
|
612
|
+
(input.isEnabled === undefined || typeof input.isEnabled === 'boolean');
|
|
422
613
|
}
|
|
423
|
-
|
|
614
|
+
/**
|
|
615
|
+
* The stored agent. `isEnabled` in the input is ignored, as by the server: the state comes from
|
|
616
|
+
* `previous`, and a new agent starts active.
|
|
617
|
+
*/
|
|
618
|
+
toAgent(input, source, version, isOverridden, previous) {
|
|
619
|
+
const { isEnabled, ...definition } = structuredClone(input);
|
|
424
620
|
return {
|
|
425
|
-
...
|
|
621
|
+
...definition,
|
|
622
|
+
isEnabled: previous?.isEnabled ?? true,
|
|
426
623
|
version,
|
|
427
624
|
source,
|
|
428
625
|
isOverridden,
|
|
429
626
|
instructionsHash: hashText(input.instructions),
|
|
430
|
-
updatedAt: this.now()
|
|
627
|
+
updatedAt: this.now(),
|
|
628
|
+
runState: previous?.runState ?? 'active',
|
|
629
|
+
stateChangedAt: previous?.stateChangedAt ?? null,
|
|
630
|
+
stateChangedBy: previous?.stateChangedBy ?? null,
|
|
631
|
+
stateReason: previous?.stateReason ?? null,
|
|
632
|
+
waitingInputs: previous?.waitingInputs ?? 0,
|
|
633
|
+
skills: previous?.skills ?? [],
|
|
634
|
+
shadowOf: previous?.shadowOf ?? null
|
|
431
635
|
};
|
|
432
636
|
}
|
|
433
637
|
toServer(stored) {
|
|
@@ -449,8 +653,22 @@ function codeAgentFromSummary(agent) {
|
|
|
449
653
|
toolSelectors: [],
|
|
450
654
|
mcpServerIds: [],
|
|
451
655
|
requiredPolicy: null,
|
|
452
|
-
autonomy: agent.canMutate ? 'execute' : 'explain'
|
|
453
|
-
|
|
656
|
+
autonomy: agent.canMutate ? 'execute' : 'explain'
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function toAgentState(agent) {
|
|
660
|
+
return {
|
|
661
|
+
agentId: agent.id,
|
|
662
|
+
runState: agent.runState,
|
|
663
|
+
active: agent.runState === 'active',
|
|
664
|
+
changedAt: agent.stateChangedAt,
|
|
665
|
+
changedBy: agent.stateChangedBy,
|
|
666
|
+
reason: agent.stateReason,
|
|
667
|
+
waitingInputs: agent.waitingInputs,
|
|
668
|
+
cancelledRuns: 0,
|
|
669
|
+
startedRuns: 0,
|
|
670
|
+
skills: agent.skills ?? [],
|
|
671
|
+
shadowOf: agent.shadowOf ?? null
|
|
454
672
|
};
|
|
455
673
|
}
|
|
456
674
|
function toServerFields(input) {
|
|
@@ -655,7 +873,7 @@ class NhAssistantMockBackend {
|
|
|
655
873
|
return this.error(404, 'assistant-push-unavailable');
|
|
656
874
|
}
|
|
657
875
|
if (segments[0] === 'admin') {
|
|
658
|
-
return this.result(this.admin.handleAdmin(method, segments, body));
|
|
876
|
+
return this.result(this.admin.handleAdmin(method, segments, body, query));
|
|
659
877
|
}
|
|
660
878
|
if (segments[0] !== 'conversations') {
|
|
661
879
|
return this.empty(404);
|