amalgm 0.0.0 → 0.0.1
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 +37 -1
- package/bin/amalgm.js +8 -2
- package/lib/auth-store.js +223 -0
- package/lib/cli.js +1000 -0
- package/lib/paths.js +30 -0
- package/lib/supervisor.js +467 -0
- package/lib/tunnel-chat.js +328 -0
- package/lib/tunnel-events.js +499 -0
- package/package.json +29 -3
- package/runtime/README.md +4 -0
- package/runtime/lib/chatInput.js +306 -0
- package/runtime/lib/harnesses.js +988 -0
- package/runtime/lib/local/amalgmStore.js +128 -0
- package/runtime/lib/local/credentialResolver.js +425 -0
- package/runtime/lib/mcpApps/registry.js +619 -0
- package/runtime/package.json +5 -0
- package/runtime/scripts/amalgm-mcp/agents/rest.js +165 -0
- package/runtime/scripts/amalgm-mcp/agents/store.js +153 -0
- package/runtime/scripts/amalgm-mcp/agents/talk.js +1156 -0
- package/runtime/scripts/amalgm-mcp/agents/tools.js +210 -0
- package/runtime/scripts/amalgm-mcp/artifacts/advertise.js +132 -0
- package/runtime/scripts/amalgm-mcp/artifacts/rest.js +103 -0
- package/runtime/scripts/amalgm-mcp/artifacts/store.js +141 -0
- package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +402 -0
- package/runtime/scripts/amalgm-mcp/artifacts/tools.js +176 -0
- package/runtime/scripts/amalgm-mcp/browser/page.js +637 -0
- package/runtime/scripts/amalgm-mcp/browser/tools.js +688 -0
- package/runtime/scripts/amalgm-mcp/config.js +138 -0
- package/runtime/scripts/amalgm-mcp/credentials/rest.js +45 -0
- package/runtime/scripts/amalgm-mcp/deps.js +40 -0
- package/runtime/scripts/amalgm-mcp/email/inbound.js +215 -0
- package/runtime/scripts/amalgm-mcp/events/executor.js +179 -0
- package/runtime/scripts/amalgm-mcp/events/ingress.js +113 -0
- package/runtime/scripts/amalgm-mcp/events/matcher.js +125 -0
- package/runtime/scripts/amalgm-mcp/events/rest.js +200 -0
- package/runtime/scripts/amalgm-mcp/events/ring-buffer.js +19 -0
- package/runtime/scripts/amalgm-mcp/events/store.js +98 -0
- package/runtime/scripts/amalgm-mcp/events/tools.js +306 -0
- package/runtime/scripts/amalgm-mcp/events/webhook-url.js +28 -0
- package/runtime/scripts/amalgm-mcp/fs/rest.js +293 -0
- package/runtime/scripts/amalgm-mcp/index.js +100 -0
- package/runtime/scripts/amalgm-mcp/lib/chat-runner.js +167 -0
- package/runtime/scripts/amalgm-mcp/lib/email-md.js +288 -0
- package/runtime/scripts/amalgm-mcp/lib/mcp-resolver.js +63 -0
- package/runtime/scripts/amalgm-mcp/lib/prefs.js +393 -0
- package/runtime/scripts/amalgm-mcp/lib/storage.js +92 -0
- package/runtime/scripts/amalgm-mcp/lib/supabase.js +118 -0
- package/runtime/scripts/amalgm-mcp/lib/tool-result.js +177 -0
- package/runtime/scripts/amalgm-mcp/local/rest.js +80 -0
- package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +151 -0
- package/runtime/scripts/amalgm-mcp/notify/index.js +107 -0
- package/runtime/scripts/amalgm-mcp/server/http.js +335 -0
- package/runtime/scripts/amalgm-mcp/server/mcp.js +116 -0
- package/runtime/scripts/amalgm-mcp/slack/inbound.js +200 -0
- package/runtime/scripts/amalgm-mcp/tasks/executor.js +225 -0
- package/runtime/scripts/amalgm-mcp/tasks/rest.js +110 -0
- package/runtime/scripts/amalgm-mcp/tasks/schedule-normalization.js +85 -0
- package/runtime/scripts/amalgm-mcp/tasks/scheduler.js +105 -0
- package/runtime/scripts/amalgm-mcp/tasks/store.js +139 -0
- package/runtime/scripts/amalgm-mcp/tasks/tools.js +391 -0
- package/runtime/scripts/amalgm-mcp/user-api-keys/rest.js +105 -0
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +389 -0
- package/runtime/scripts/chat-core/adapters/claude.js +163 -0
- package/runtime/scripts/chat-core/adapters/codex.js +313 -0
- package/runtime/scripts/chat-core/adapters/opencode.js +412 -0
- package/runtime/scripts/chat-core/auth.js +177 -0
- package/runtime/scripts/chat-core/contract.js +326 -0
- package/runtime/scripts/chat-core/credentials/store.js +212 -0
- package/runtime/scripts/chat-core/egress.js +87 -0
- package/runtime/scripts/chat-core/engine.js +195 -0
- package/runtime/scripts/chat-core/event-schema.js +231 -0
- package/runtime/scripts/chat-core/events.js +190 -0
- package/runtime/scripts/chat-core/index.js +11 -0
- package/runtime/scripts/chat-core/input.js +50 -0
- package/runtime/scripts/chat-core/normalizers/claude.js +450 -0
- package/runtime/scripts/chat-core/normalizers/codex.js +380 -0
- package/runtime/scripts/chat-core/normalizers/normalizer_spec.md +259 -0
- package/runtime/scripts/chat-core/normalizers/opencode.js +552 -0
- package/runtime/scripts/chat-core/normalizers/tool_contract.md +123 -0
- package/runtime/scripts/chat-core/normalizers/usage_contract.md +304 -0
- package/runtime/scripts/chat-core/parts.js +253 -0
- package/runtime/scripts/chat-core/recorder.js +65 -0
- package/runtime/scripts/chat-core/runtime.js +86 -0
- package/runtime/scripts/chat-core/server.js +163 -0
- package/runtime/scripts/chat-core/sse.js +196 -0
- package/runtime/scripts/chat-core/stores.js +100 -0
- package/runtime/scripts/chat-core/tool-display.js +149 -0
- package/runtime/scripts/chat-core/tool-shape.js +143 -0
- package/runtime/scripts/chat-core/tooling/mcp-bundle.js +161 -0
- package/runtime/scripts/chat-core/tooling/mcp-relay.js +97 -0
- package/runtime/scripts/chat-core/tooling/native-binaries.js +608 -0
- package/runtime/scripts/chat-core/tooling/system-prompt.js +20 -0
- package/runtime/scripts/chat-core/usage.js +343 -0
- package/runtime/scripts/chat-server/config.js +110 -0
- package/runtime/scripts/chat-server/db.js +529 -0
- package/runtime/scripts/chat-server/index.js +33 -0
- package/runtime/scripts/chat-server/model-catalog.js +327 -0
- package/runtime/scripts/chat-server.js +75 -0
- package/runtime/scripts/credential-adapter.js +129 -0
- package/runtime/scripts/fs-watcher.js +888 -0
- package/runtime/scripts/local-gateway.js +852 -0
- package/runtime/scripts/platform-context.txt +246 -0
- package/runtime/scripts/port-monitor.js +175 -0
- package/runtime/scripts/proxy-token-store.js +162 -0
- package/runtime/scripts/runtime-auth.js +163 -0
- package/runtime/scripts/test-claude-code-models.js +87 -0
- package/runtime/tsconfig.json +15 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { PartAccumulator } = require('./parts');
|
|
4
|
+
const { assertSameContract, createContract } = require('./contract');
|
|
5
|
+
const { done, errorEvent } = require('./events');
|
|
6
|
+
const { normalizeInput } = require('./input');
|
|
7
|
+
const { frameFor, titleFrame } = require('./sse');
|
|
8
|
+
const { beginProxyTurn, logTurnUsage } = require('./usage');
|
|
9
|
+
|
|
10
|
+
function assertDbSaved(result, label) {
|
|
11
|
+
if (result === false || result === null) {
|
|
12
|
+
throw new Error(`${label} was not saved; keeping temp/raw stream for reconnect`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class ChatCore {
|
|
17
|
+
constructor({ db, runtime, turns, options = {} }) {
|
|
18
|
+
this.db = db;
|
|
19
|
+
this.runtime = runtime;
|
|
20
|
+
this.turns = turns;
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.beginTurn = options.beginTurn || beginProxyTurn;
|
|
23
|
+
this.contracts = new Map();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
contractFor(payload) {
|
|
27
|
+
const next = createContract(payload, this.options);
|
|
28
|
+
const existing = this.contracts.get(next.sessionId);
|
|
29
|
+
if (existing) assertSameContract(existing, next);
|
|
30
|
+
this.contracts.set(next.sessionId, next);
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
active(sessionId) {
|
|
35
|
+
const entry = this.turns.active(sessionId);
|
|
36
|
+
return {
|
|
37
|
+
active: !!entry && ['streaming', 'cancelling'].includes(entry.status),
|
|
38
|
+
codeSessionId: sessionId,
|
|
39
|
+
assistantMessageId: entry?.assistantMessageId || null,
|
|
40
|
+
status: entry?.status || 'idle',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async stop(sessionId) {
|
|
45
|
+
const entry = this.turns.active(sessionId);
|
|
46
|
+
if (!entry) return false;
|
|
47
|
+
this.turns.mark(entry.turnId, 'cancelling');
|
|
48
|
+
await this.runtime.stop(sessionId);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async destroy(sessionId) {
|
|
53
|
+
this.contracts.delete(sessionId);
|
|
54
|
+
return this.runtime.destroy(sessionId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async runTurn(payload, writeFrame) {
|
|
58
|
+
const contract = this.contractFor(payload);
|
|
59
|
+
await this.beginTurn(contract);
|
|
60
|
+
const input = normalizeInput(payload);
|
|
61
|
+
let runtimeInput = input;
|
|
62
|
+
const isReturningToSession = !!payload.resumeSessionId && payload.resumeSessionId !== 'new';
|
|
63
|
+
const runtimeWasWarm = !!this.runtime.get(contract.sessionId);
|
|
64
|
+
const turnId = contract.assistantMessageId;
|
|
65
|
+
const msgConfig = {
|
|
66
|
+
...payload,
|
|
67
|
+
prompt: input.text,
|
|
68
|
+
userParts: input.parts,
|
|
69
|
+
codeSessionId: contract.sessionId,
|
|
70
|
+
assistantMessageId: contract.assistantMessageId,
|
|
71
|
+
modelId: contract.usageModelId || contract.modelId,
|
|
72
|
+
};
|
|
73
|
+
assertDbSaved(await this.db.saveUserMessage(msgConfig), 'User message');
|
|
74
|
+
if (isReturningToSession && !runtimeWasWarm && !contract.providerSessionId && typeof this.db.loadConversationHistory === 'function') {
|
|
75
|
+
try {
|
|
76
|
+
const history = await this.db.loadConversationHistory(contract.sessionId, contract.userMessageId);
|
|
77
|
+
if (history) {
|
|
78
|
+
const text = `${history}${input.text || ''}`.trim();
|
|
79
|
+
runtimeInput = {
|
|
80
|
+
...input,
|
|
81
|
+
text,
|
|
82
|
+
parts: [{ type: 'text', text }],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.warn('[ChatCore] Failed to load cold-resume conversation history:', err.message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
assertDbSaved(await this.db.ensureAssistantMessage(msgConfig), 'Assistant placeholder');
|
|
90
|
+
await this.db.mergeSessionMetadata(contract.sessionId, {
|
|
91
|
+
contract: {
|
|
92
|
+
userId: contract.userId,
|
|
93
|
+
computerId: contract.computerId,
|
|
94
|
+
harness: contract.harness,
|
|
95
|
+
authMethod: contract.authMethod,
|
|
96
|
+
modelId: contract.modelId,
|
|
97
|
+
usageModelId: contract.usageModelId,
|
|
98
|
+
providerSessionId: contract.providerSessionId,
|
|
99
|
+
cliModel: contract.cliModel,
|
|
100
|
+
reasoningEffort: contract.reasoningEffort,
|
|
101
|
+
cwd: contract.cwd,
|
|
102
|
+
runtimeKey: contract.runtimeKey,
|
|
103
|
+
systemPromptConfigured: !!contract.systemPrompt,
|
|
104
|
+
mcpServers: contract.mcpServers.map((server) => ({
|
|
105
|
+
name: server?.name,
|
|
106
|
+
type: server?.type,
|
|
107
|
+
url: server?.url,
|
|
108
|
+
hasHeaders: Array.isArray(server?.headers) && server.headers.length > 0,
|
|
109
|
+
})),
|
|
110
|
+
auth: {
|
|
111
|
+
baseUrl: contract.auth.baseUrl,
|
|
112
|
+
tokenFingerprint: contract.auth.tokenFingerprint,
|
|
113
|
+
runtimeHome: contract.auth.runtimeHome,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
}).catch(() => {});
|
|
117
|
+
|
|
118
|
+
const entry = this.turns.start({
|
|
119
|
+
sessionId: contract.sessionId,
|
|
120
|
+
turnId,
|
|
121
|
+
assistantMessageId: contract.assistantMessageId,
|
|
122
|
+
userMessageId: contract.userMessageId,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (typeof this.db.generateAndSaveTitle === 'function' && input.text) {
|
|
126
|
+
this.db.generateAndSaveTitle(contract.sessionId, input.text, (title) => {
|
|
127
|
+
const frame = titleFrame({ sessionId: contract.sessionId, title });
|
|
128
|
+
this.turns.addChunk(turnId, frame);
|
|
129
|
+
if (writeFrame) writeFrame(frame);
|
|
130
|
+
}).catch((err) => {
|
|
131
|
+
console.warn('[ChatCore] Title generation failed:', err.message);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const parts = new PartAccumulator();
|
|
136
|
+
let usage = null;
|
|
137
|
+
const usageRecords = [];
|
|
138
|
+
let providerSessionId = contract.providerSessionId || null;
|
|
139
|
+
let stopReason = 'end_turn';
|
|
140
|
+
let sawDone = false;
|
|
141
|
+
|
|
142
|
+
const append = (e) => {
|
|
143
|
+
if (e.providerSessionId) providerSessionId = e.providerSessionId;
|
|
144
|
+
if (e.type === 'usage.final') {
|
|
145
|
+
usage = e.usage;
|
|
146
|
+
usageRecords.push(e.usage);
|
|
147
|
+
}
|
|
148
|
+
if (e.type === 'done') {
|
|
149
|
+
sawDone = true;
|
|
150
|
+
stopReason = e.stopReason || stopReason;
|
|
151
|
+
}
|
|
152
|
+
parts.apply(e);
|
|
153
|
+
const frame = frameFor(e, { sessionId: contract.sessionId, providerSessionId, usage });
|
|
154
|
+
this.turns.addChunk(turnId, frame);
|
|
155
|
+
if (writeFrame) writeFrame(frame);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const stream = await this.runtime.prompt(contract, runtimeInput);
|
|
160
|
+
for await (const e of stream) append(e);
|
|
161
|
+
const current = this.turns.get(turnId);
|
|
162
|
+
if (!sawDone) {
|
|
163
|
+
append(done({
|
|
164
|
+
providerSessionId,
|
|
165
|
+
stopReason: current?.status === 'cancelling' ? 'cancelled' : stopReason,
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
append(errorEvent(err.message, { providerSessionId }));
|
|
170
|
+
append(done({ providerSessionId, stopReason: 'error' }));
|
|
171
|
+
stopReason = 'error';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const savedParts = parts.finalize();
|
|
175
|
+
this.turns.setParts(turnId, savedParts);
|
|
176
|
+
try {
|
|
177
|
+
assertDbSaved(await this.db.saveAssistantMessage(msgConfig, savedParts, providerSessionId, usage), 'Assistant message');
|
|
178
|
+
} catch (saveErr) {
|
|
179
|
+
this.turns.mark(turnId, 'save_failed');
|
|
180
|
+
throw saveErr;
|
|
181
|
+
}
|
|
182
|
+
this.turns.mark(turnId, stopReason === 'error' ? 'error' : stopReason === 'cancelled' ? 'cancelled' : 'complete');
|
|
183
|
+
this.turns.clear(turnId);
|
|
184
|
+
await logTurnUsage({
|
|
185
|
+
db: this.db,
|
|
186
|
+
contract,
|
|
187
|
+
messageId: contract.assistantMessageId,
|
|
188
|
+
usage,
|
|
189
|
+
usageRecords,
|
|
190
|
+
});
|
|
191
|
+
return { providerSessionId, usage, parts: savedParts, stopReason };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = { ChatCore };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { z } = require('zod');
|
|
4
|
+
|
|
5
|
+
const positiveNumber = z.number().finite().positive();
|
|
6
|
+
const nonNegativeNumber = z.number().finite().min(0);
|
|
7
|
+
const nullablePositiveNumber = positiveNumber.nullable();
|
|
8
|
+
const providerSessionId = z.string().nullable().optional();
|
|
9
|
+
const unknownRecord = z.record(z.string(), z.unknown());
|
|
10
|
+
|
|
11
|
+
const rawEnvelope = z.object({
|
|
12
|
+
source: z.string().min(1),
|
|
13
|
+
method: z.string().min(1).optional(),
|
|
14
|
+
payload: z.unknown().optional(),
|
|
15
|
+
}).strict();
|
|
16
|
+
|
|
17
|
+
const toolKind = z.enum([
|
|
18
|
+
'web_search',
|
|
19
|
+
'browser',
|
|
20
|
+
'file_read',
|
|
21
|
+
'file_write',
|
|
22
|
+
'file_edit',
|
|
23
|
+
'bash',
|
|
24
|
+
'subagent',
|
|
25
|
+
'mcp',
|
|
26
|
+
'question',
|
|
27
|
+
'todo',
|
|
28
|
+
'unknown',
|
|
29
|
+
// Legacy ACP-ish kinds still emitted by older callers.
|
|
30
|
+
'read',
|
|
31
|
+
'edit',
|
|
32
|
+
'delete',
|
|
33
|
+
'move',
|
|
34
|
+
'search',
|
|
35
|
+
'execute',
|
|
36
|
+
'think',
|
|
37
|
+
'fetch',
|
|
38
|
+
'switch_mode',
|
|
39
|
+
'other',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const toolTiming = z.object({
|
|
43
|
+
startedAt: z.union([z.string(), z.number()]).optional(),
|
|
44
|
+
completedAt: z.union([z.string(), z.number()]).optional(),
|
|
45
|
+
elapsedMs: nonNegativeNumber.optional(),
|
|
46
|
+
}).passthrough();
|
|
47
|
+
|
|
48
|
+
const toolError = z.object({
|
|
49
|
+
message: z.string(),
|
|
50
|
+
code: z.string().optional(),
|
|
51
|
+
details: z.unknown().optional(),
|
|
52
|
+
}).passthrough();
|
|
53
|
+
|
|
54
|
+
const baseEvent = z.object({
|
|
55
|
+
ts: z.number().finite(),
|
|
56
|
+
providerSessionId,
|
|
57
|
+
raw: rawEnvelope.optional(),
|
|
58
|
+
}).passthrough();
|
|
59
|
+
|
|
60
|
+
const tokenList = z.object({
|
|
61
|
+
input: nonNegativeNumber,
|
|
62
|
+
output: nonNegativeNumber,
|
|
63
|
+
cacheRead: nonNegativeNumber,
|
|
64
|
+
cacheWrite: nonNegativeNumber,
|
|
65
|
+
thought: nonNegativeNumber,
|
|
66
|
+
total: nonNegativeNumber,
|
|
67
|
+
}).strict();
|
|
68
|
+
|
|
69
|
+
const usageSnapshot = z.object({
|
|
70
|
+
tokenList,
|
|
71
|
+
cumulativeTokenList: tokenList.nullable(),
|
|
72
|
+
previousCumulativeTokenList: tokenList.nullable(),
|
|
73
|
+
inputTokens: nonNegativeNumber,
|
|
74
|
+
outputTokens: nonNegativeNumber,
|
|
75
|
+
cacheReadTokens: nonNegativeNumber,
|
|
76
|
+
cacheWriteTokens: nonNegativeNumber,
|
|
77
|
+
thoughtTokens: nonNegativeNumber,
|
|
78
|
+
inputContextSize: nonNegativeNumber,
|
|
79
|
+
contextUsedTokens: nullablePositiveNumber,
|
|
80
|
+
contextLimit: nullablePositiveNumber,
|
|
81
|
+
usedTokens: nullablePositiveNumber,
|
|
82
|
+
lastUsedTokens: nullablePositiveNumber,
|
|
83
|
+
maxTokens: nullablePositiveNumber,
|
|
84
|
+
totalProcessedTokens: nullablePositiveNumber,
|
|
85
|
+
costUsd: z.number().finite().nullable(),
|
|
86
|
+
marketCostUsd: z.number().finite().nullable(),
|
|
87
|
+
operation: z.string().min(1),
|
|
88
|
+
billable: z.boolean(),
|
|
89
|
+
source: z.string().min(1),
|
|
90
|
+
exact: z.boolean(),
|
|
91
|
+
}).strict();
|
|
92
|
+
|
|
93
|
+
const textDeltaEvent = baseEvent.extend({
|
|
94
|
+
type: z.literal('text.delta'),
|
|
95
|
+
text: z.string(),
|
|
96
|
+
streamKind: z.literal('text').optional(),
|
|
97
|
+
}).strict();
|
|
98
|
+
|
|
99
|
+
const reasoningDeltaEvent = baseEvent.extend({
|
|
100
|
+
type: z.literal('reasoning.delta'),
|
|
101
|
+
text: z.string(),
|
|
102
|
+
streamKind: z.union([z.literal('reasoning'), z.literal('plan')]).optional(),
|
|
103
|
+
}).strict();
|
|
104
|
+
|
|
105
|
+
const toolStartedEvent = baseEvent.extend({
|
|
106
|
+
type: z.literal('tool.started'),
|
|
107
|
+
toolCallId: z.string(),
|
|
108
|
+
toolName: z.string().min(1),
|
|
109
|
+
name: z.string().min(1).optional(),
|
|
110
|
+
kind: toolKind.optional(),
|
|
111
|
+
title: z.string().optional(),
|
|
112
|
+
detail: z.string().optional(),
|
|
113
|
+
input: unknownRecord,
|
|
114
|
+
metadata: unknownRecord.optional(),
|
|
115
|
+
timing: toolTiming.optional(),
|
|
116
|
+
data: z.unknown().optional(),
|
|
117
|
+
itemType: z.literal('tool').optional(),
|
|
118
|
+
}).strict();
|
|
119
|
+
|
|
120
|
+
const toolUpdatedEvent = baseEvent.extend({
|
|
121
|
+
type: z.literal('tool.updated'),
|
|
122
|
+
toolCallId: z.string(),
|
|
123
|
+
toolName: z.string().optional(),
|
|
124
|
+
name: z.string().optional(),
|
|
125
|
+
kind: toolKind.optional(),
|
|
126
|
+
title: z.string().optional(),
|
|
127
|
+
detail: z.string().optional(),
|
|
128
|
+
inputDelta: unknownRecord.optional(),
|
|
129
|
+
outputDelta: z.unknown().optional(),
|
|
130
|
+
output: z.unknown().optional(),
|
|
131
|
+
error: toolError.optional(),
|
|
132
|
+
metadata: unknownRecord.optional(),
|
|
133
|
+
timing: toolTiming.optional(),
|
|
134
|
+
data: z.unknown().optional(),
|
|
135
|
+
status: z.enum(['in_progress', 'completed', 'failed', 'error']).optional(),
|
|
136
|
+
itemType: z.literal('tool').optional(),
|
|
137
|
+
}).strict();
|
|
138
|
+
|
|
139
|
+
const toolCompletedEvent = baseEvent.extend({
|
|
140
|
+
type: z.literal('tool.completed'),
|
|
141
|
+
toolCallId: z.string(),
|
|
142
|
+
toolName: z.string().optional(),
|
|
143
|
+
name: z.string().optional(),
|
|
144
|
+
kind: toolKind.optional(),
|
|
145
|
+
title: z.string().optional(),
|
|
146
|
+
detail: z.string().optional(),
|
|
147
|
+
input: unknownRecord.optional(),
|
|
148
|
+
output: z.unknown().optional(),
|
|
149
|
+
result: z.unknown().optional(),
|
|
150
|
+
error: toolError.optional(),
|
|
151
|
+
metadata: unknownRecord.optional(),
|
|
152
|
+
timing: toolTiming.optional(),
|
|
153
|
+
data: z.unknown().optional(),
|
|
154
|
+
isError: z.boolean(),
|
|
155
|
+
itemType: z.literal('tool').optional(),
|
|
156
|
+
}).strict();
|
|
157
|
+
|
|
158
|
+
const usageFinalEvent = baseEvent.extend({
|
|
159
|
+
type: z.literal('usage.final'),
|
|
160
|
+
usage: usageSnapshot,
|
|
161
|
+
}).strict();
|
|
162
|
+
|
|
163
|
+
const warningEvent = baseEvent.extend({
|
|
164
|
+
type: z.literal('warning'),
|
|
165
|
+
message: z.string(),
|
|
166
|
+
requestedModel: z.string().optional(),
|
|
167
|
+
}).strict();
|
|
168
|
+
|
|
169
|
+
const errorEvent = baseEvent.extend({
|
|
170
|
+
type: z.literal('error'),
|
|
171
|
+
message: z.string(),
|
|
172
|
+
}).strict();
|
|
173
|
+
|
|
174
|
+
const doneEvent = baseEvent.extend({
|
|
175
|
+
type: z.literal('done'),
|
|
176
|
+
stopReason: z.string(),
|
|
177
|
+
usage: usageSnapshot.optional(),
|
|
178
|
+
}).strict();
|
|
179
|
+
|
|
180
|
+
const compactionTrigger = z.enum(['auto', 'manual', 'model_triggered']);
|
|
181
|
+
|
|
182
|
+
const compactionStartedEvent = baseEvent.extend({
|
|
183
|
+
type: z.literal('compaction.started'),
|
|
184
|
+
trigger: compactionTrigger,
|
|
185
|
+
}).strict();
|
|
186
|
+
|
|
187
|
+
const compactionFinishedEvent = baseEvent.extend({
|
|
188
|
+
type: z.literal('compaction.finished'),
|
|
189
|
+
trigger: compactionTrigger,
|
|
190
|
+
preTokens: nullablePositiveNumber.optional(),
|
|
191
|
+
postTokens: nullablePositiveNumber.optional(),
|
|
192
|
+
durationMs: nullablePositiveNumber.optional(),
|
|
193
|
+
}).strict();
|
|
194
|
+
|
|
195
|
+
const amalgmEventSchema = z.discriminatedUnion('type', [
|
|
196
|
+
textDeltaEvent,
|
|
197
|
+
reasoningDeltaEvent,
|
|
198
|
+
toolStartedEvent,
|
|
199
|
+
toolUpdatedEvent,
|
|
200
|
+
toolCompletedEvent,
|
|
201
|
+
usageFinalEvent,
|
|
202
|
+
compactionStartedEvent,
|
|
203
|
+
compactionFinishedEvent,
|
|
204
|
+
warningEvent,
|
|
205
|
+
errorEvent,
|
|
206
|
+
doneEvent,
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
function formatZodIssue(issue) {
|
|
210
|
+
return `${issue.path.join('.') || '<event>'}: ${issue.message}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function validateAmalgmEvent(value) {
|
|
214
|
+
const parsed = amalgmEventSchema.safeParse(value);
|
|
215
|
+
if (parsed.success) return parsed.data;
|
|
216
|
+
|
|
217
|
+
const type = value && typeof value === 'object' ? value.type : typeof value;
|
|
218
|
+
const details = parsed.error.issues.map(formatZodIssue).join('; ');
|
|
219
|
+
throw new Error(`Invalid Amalgm event ${String(type || 'unknown')}: ${details}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function validateAmalgmEvents(events) {
|
|
223
|
+
return events.map(validateAmalgmEvent);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
amalgmEventSchema,
|
|
228
|
+
usageSnapshot,
|
|
229
|
+
validateAmalgmEvent,
|
|
230
|
+
validateAmalgmEvents,
|
|
231
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { validateAmalgmEvent } = require('./event-schema');
|
|
4
|
+
const { withActionDescriptor, withOutputDescriptor } = require('./tool-display');
|
|
5
|
+
const { mcpMetadataFromName, normalizeError, titleForTool, toolKind } = require('./tool-shape');
|
|
6
|
+
|
|
7
|
+
function now(ts) {
|
|
8
|
+
return Number.isFinite(ts) ? ts : Date.now();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function event(type, data = {}) {
|
|
12
|
+
const { ts, ...rest } = data;
|
|
13
|
+
const next = { type, ts: now(ts), ...rest };
|
|
14
|
+
if (process.env.CHAT_CORE_VALIDATE_EVENTS === '0') return next;
|
|
15
|
+
return validateAmalgmEvent(next);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function textDelta(text, data = {}) {
|
|
19
|
+
return event('text.delta', { streamKind: 'text', ...data, text: String(text || '') });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function reasoningDelta(text, data = {}) {
|
|
23
|
+
return event('reasoning.delta', { streamKind: 'reasoning', ...data, text: String(text || '') });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function reasoningStarted(data = {}) {
|
|
27
|
+
return reasoningDelta('', data);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toolStarted(id, data = {}) {
|
|
31
|
+
const toolName = data.toolName || data.title || 'unknown';
|
|
32
|
+
const input = withActionDescriptor(data.input, data.detail);
|
|
33
|
+
const metadata = { ...mcpMetadataFromName(toolName), ...(data.metadata || {}) };
|
|
34
|
+
return event('tool.started', {
|
|
35
|
+
...data,
|
|
36
|
+
itemType: 'tool',
|
|
37
|
+
toolCallId: String(id || ''),
|
|
38
|
+
toolName,
|
|
39
|
+
name: toolName,
|
|
40
|
+
kind: data.kind || toolKind(toolName, input, metadata),
|
|
41
|
+
title: titleForTool({ name: toolName, input, metadata, fallback: data.title || data.detail }),
|
|
42
|
+
input,
|
|
43
|
+
metadata,
|
|
44
|
+
timing: data.timing,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toolUpdated(id, data = {}) {
|
|
49
|
+
const toolName = data.toolName || data.title || 'unknown';
|
|
50
|
+
const inputDelta = data.inputDelta ? withActionDescriptor(data.inputDelta, data.detail) : undefined;
|
|
51
|
+
const metadata = { ...mcpMetadataFromName(toolName), ...(data.metadata || {}) };
|
|
52
|
+
return event('tool.updated', {
|
|
53
|
+
itemType: 'tool',
|
|
54
|
+
...data,
|
|
55
|
+
toolCallId: String(id || ''),
|
|
56
|
+
toolName,
|
|
57
|
+
name: toolName,
|
|
58
|
+
kind: data.kind || toolKind(toolName, inputDelta || {}, metadata),
|
|
59
|
+
title: titleForTool({ name: toolName, input: inputDelta || {}, output: data.output, metadata, fallback: data.title || data.detail }),
|
|
60
|
+
metadata,
|
|
61
|
+
timing: data.timing,
|
|
62
|
+
...(inputDelta ? { inputDelta } : {}),
|
|
63
|
+
...(data.output !== undefined ? { output: withOutputDescriptor(data.output, data.detail) } : {}),
|
|
64
|
+
...(data.error ? { error: normalizeError(data.error) } : {}),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toolCompleted(id, data = {}) {
|
|
69
|
+
const toolName = data.toolName || data.title || 'unknown';
|
|
70
|
+
const input = data.input ? withActionDescriptor(data.input, data.detail) : undefined;
|
|
71
|
+
const output = data.output !== undefined ? withOutputDescriptor(data.output, data.detail) : undefined;
|
|
72
|
+
const metadata = { ...mcpMetadataFromName(toolName), ...(data.metadata || {}) };
|
|
73
|
+
const error = data.error || (data.isError === true ? data.output ?? data.result ?? data.detail : undefined);
|
|
74
|
+
return event('tool.completed', {
|
|
75
|
+
...data,
|
|
76
|
+
itemType: 'tool',
|
|
77
|
+
toolCallId: String(id || ''),
|
|
78
|
+
toolName,
|
|
79
|
+
name: toolName,
|
|
80
|
+
kind: data.kind || toolKind(toolName, input || {}, metadata),
|
|
81
|
+
title: titleForTool({ name: toolName, input: input || {}, output, error, metadata, fallback: data.title || data.detail }),
|
|
82
|
+
...(input ? { input } : {}),
|
|
83
|
+
...(output !== undefined ? { output } : {}),
|
|
84
|
+
...(error ? { error: normalizeError(error) } : {}),
|
|
85
|
+
metadata,
|
|
86
|
+
timing: data.timing,
|
|
87
|
+
isError: data.isError === true,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function usageFinal(usage, data = {}) {
|
|
92
|
+
const tokenList = usageTokenList(usage);
|
|
93
|
+
const inputContextSize = usageInputContextSize(usage, tokenList);
|
|
94
|
+
return event('usage.final', {
|
|
95
|
+
...data,
|
|
96
|
+
usage: {
|
|
97
|
+
tokenList,
|
|
98
|
+
cumulativeTokenList: usage?.cumulativeTokenList || null,
|
|
99
|
+
previousCumulativeTokenList: usage?.previousCumulativeTokenList || null,
|
|
100
|
+
inputTokens: tokenList.input,
|
|
101
|
+
outputTokens: tokenList.output,
|
|
102
|
+
cacheReadTokens: tokenList.cacheRead,
|
|
103
|
+
cacheWriteTokens: tokenList.cacheWrite,
|
|
104
|
+
thoughtTokens: tokenList.thought,
|
|
105
|
+
inputContextSize,
|
|
106
|
+
contextUsedTokens: finiteOrNull(usage?.contextUsedTokens ?? usage?.usedTokens ?? inputContextSize),
|
|
107
|
+
contextLimit: finiteOrNull(usage?.contextLimit ?? usage?.maxTokens ?? usage?.modelContextWindow),
|
|
108
|
+
usedTokens: finiteOrNull(usage?.usedTokens ?? usage?.contextUsedTokens ?? inputContextSize),
|
|
109
|
+
lastUsedTokens: finiteOrNull(usage?.lastUsedTokens ?? usage?.contextUsedTokens ?? usage?.usedTokens ?? inputContextSize),
|
|
110
|
+
maxTokens: finiteOrNull(usage?.maxTokens ?? usage?.contextLimit ?? usage?.modelContextWindow),
|
|
111
|
+
totalProcessedTokens: finiteOrNull(usage?.totalProcessedTokens),
|
|
112
|
+
costUsd: usage?.costUsd ?? null,
|
|
113
|
+
marketCostUsd: usage?.marketCostUsd ?? null,
|
|
114
|
+
operation: String(usage?.operation || 'chat_step'),
|
|
115
|
+
billable: usage?.billable !== false,
|
|
116
|
+
source: usage?.source || 'unknown',
|
|
117
|
+
exact: usage?.exact === true,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function usageTokenList(usage = {}) {
|
|
123
|
+
const source = usage?.tokenList && typeof usage.tokenList === 'object' ? usage.tokenList : {};
|
|
124
|
+
const input = finiteNonNegativeOrNull(source.input ?? source.inputTokens ?? usage?.inputTokens) ?? 0;
|
|
125
|
+
const output = finiteNonNegativeOrNull(source.output ?? source.outputTokens ?? usage?.outputTokens) ?? 0;
|
|
126
|
+
const cacheRead = finiteNonNegativeOrNull(source.cacheRead ?? source.cacheReadTokens ?? usage?.cacheReadTokens) ?? 0;
|
|
127
|
+
const cacheWrite = finiteNonNegativeOrNull(source.cacheWrite ?? source.cacheWriteTokens ?? usage?.cacheWriteTokens) ?? 0;
|
|
128
|
+
const thought = finiteNonNegativeOrNull(source.thought ?? source.thoughtTokens ?? usage?.thoughtTokens) ?? 0;
|
|
129
|
+
const total = finiteNonNegativeOrNull(source.total ?? source.totalTokens)
|
|
130
|
+
?? input + output + cacheRead + cacheWrite;
|
|
131
|
+
return { input, output, cacheRead, cacheWrite, thought, total };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function usageInputContextSize(usage = {}, tokenList = usageTokenList(usage)) {
|
|
135
|
+
const explicit = finiteNonNegativeOrNull(usage?.inputContextSize ?? usage?.input_context_size);
|
|
136
|
+
if (explicit !== null) return explicit;
|
|
137
|
+
return tokenList.total;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function finiteNonNegativeOrNull(value) {
|
|
141
|
+
const parsed = Number(value);
|
|
142
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function finiteOrNull(value) {
|
|
146
|
+
const parsed = Number(value);
|
|
147
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function errorEvent(message, data = {}) {
|
|
151
|
+
return event('error', { ...data, message: String(message || 'Unknown error') });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function warningEvent(message, data = {}) {
|
|
155
|
+
return event('warning', { ...data, message: String(message || 'Runtime warning') });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function done(data = {}) {
|
|
159
|
+
return event('done', { ...data, stopReason: data.stopReason || 'end_turn' });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function compactionStarted(data = {}) {
|
|
163
|
+
return event('compaction.started', { ...data, trigger: data.trigger || 'auto' });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function compactionFinished(data = {}) {
|
|
167
|
+
return event('compaction.finished', {
|
|
168
|
+
...data,
|
|
169
|
+
trigger: data.trigger || 'auto',
|
|
170
|
+
preTokens: finiteOrNull(data.preTokens),
|
|
171
|
+
postTokens: finiteOrNull(data.postTokens),
|
|
172
|
+
durationMs: finiteOrNull(data.durationMs),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
compactionFinished,
|
|
178
|
+
compactionStarted,
|
|
179
|
+
done,
|
|
180
|
+
errorEvent,
|
|
181
|
+
event,
|
|
182
|
+
reasoningDelta,
|
|
183
|
+
reasoningStarted,
|
|
184
|
+
textDelta,
|
|
185
|
+
toolCompleted,
|
|
186
|
+
toolStarted,
|
|
187
|
+
toolUpdated,
|
|
188
|
+
usageFinal,
|
|
189
|
+
warningEvent,
|
|
190
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function textFromPart(part) {
|
|
4
|
+
if (!part || typeof part !== 'object') return '';
|
|
5
|
+
if (part.type === 'text' && typeof part.text === 'string') return part.text;
|
|
6
|
+
if (typeof part.content === 'string') return part.content;
|
|
7
|
+
return '';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizeInput(payload) {
|
|
11
|
+
const chatInput = payload.chatInput && typeof payload.chatInput === 'object' ? payload.chatInput : null;
|
|
12
|
+
const parts = Array.isArray(payload.userParts) && payload.userParts.length > 0
|
|
13
|
+
? payload.userParts
|
|
14
|
+
: Array.isArray(chatInput?.parts) && chatInput.parts.length > 0
|
|
15
|
+
? chatInput.parts
|
|
16
|
+
: [{ type: 'text', text: payload.prompt || '' }];
|
|
17
|
+
const text = String(payload.prompt || parts.map(textFromPart).filter(Boolean).join('\n') || '').trim();
|
|
18
|
+
return {
|
|
19
|
+
text,
|
|
20
|
+
parts,
|
|
21
|
+
files: Array.isArray(chatInput?.files) ? chatInput.files : [],
|
|
22
|
+
systemPrompt: payload.systemPrompt || chatInput?.agent?.systemPrompt || '',
|
|
23
|
+
env: process.env,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function promptText(input, contract) {
|
|
28
|
+
const chunks = [];
|
|
29
|
+
if (input.files?.length) {
|
|
30
|
+
chunks.push(`<files>\n${input.files.map((file) => JSON.stringify(file)).join('\n')}\n</files>`);
|
|
31
|
+
}
|
|
32
|
+
chunks.push(input.text || '');
|
|
33
|
+
return chunks.filter(Boolean).join('\n\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function openCodeParts(input) {
|
|
37
|
+
const text = input.text || input.parts.map(textFromPart).filter(Boolean).join('\n');
|
|
38
|
+
const out = [];
|
|
39
|
+
if (text) out.push({ type: 'text', text });
|
|
40
|
+
for (const file of input.files || []) {
|
|
41
|
+
if (file?.path) out.push({ type: 'file', path: file.path });
|
|
42
|
+
}
|
|
43
|
+
return out.length ? out : [{ type: 'text', text: '' }];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
normalizeInput,
|
|
48
|
+
openCodeParts,
|
|
49
|
+
promptText,
|
|
50
|
+
};
|