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.
Files changed (107) hide show
  1. package/README.md +37 -1
  2. package/bin/amalgm.js +8 -2
  3. package/lib/auth-store.js +223 -0
  4. package/lib/cli.js +1000 -0
  5. package/lib/paths.js +30 -0
  6. package/lib/supervisor.js +467 -0
  7. package/lib/tunnel-chat.js +328 -0
  8. package/lib/tunnel-events.js +499 -0
  9. package/package.json +29 -3
  10. package/runtime/README.md +4 -0
  11. package/runtime/lib/chatInput.js +306 -0
  12. package/runtime/lib/harnesses.js +988 -0
  13. package/runtime/lib/local/amalgmStore.js +128 -0
  14. package/runtime/lib/local/credentialResolver.js +425 -0
  15. package/runtime/lib/mcpApps/registry.js +619 -0
  16. package/runtime/package.json +5 -0
  17. package/runtime/scripts/amalgm-mcp/agents/rest.js +165 -0
  18. package/runtime/scripts/amalgm-mcp/agents/store.js +153 -0
  19. package/runtime/scripts/amalgm-mcp/agents/talk.js +1156 -0
  20. package/runtime/scripts/amalgm-mcp/agents/tools.js +210 -0
  21. package/runtime/scripts/amalgm-mcp/artifacts/advertise.js +132 -0
  22. package/runtime/scripts/amalgm-mcp/artifacts/rest.js +103 -0
  23. package/runtime/scripts/amalgm-mcp/artifacts/store.js +141 -0
  24. package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +402 -0
  25. package/runtime/scripts/amalgm-mcp/artifacts/tools.js +176 -0
  26. package/runtime/scripts/amalgm-mcp/browser/page.js +637 -0
  27. package/runtime/scripts/amalgm-mcp/browser/tools.js +688 -0
  28. package/runtime/scripts/amalgm-mcp/config.js +138 -0
  29. package/runtime/scripts/amalgm-mcp/credentials/rest.js +45 -0
  30. package/runtime/scripts/amalgm-mcp/deps.js +40 -0
  31. package/runtime/scripts/amalgm-mcp/email/inbound.js +215 -0
  32. package/runtime/scripts/amalgm-mcp/events/executor.js +179 -0
  33. package/runtime/scripts/amalgm-mcp/events/ingress.js +113 -0
  34. package/runtime/scripts/amalgm-mcp/events/matcher.js +125 -0
  35. package/runtime/scripts/amalgm-mcp/events/rest.js +200 -0
  36. package/runtime/scripts/amalgm-mcp/events/ring-buffer.js +19 -0
  37. package/runtime/scripts/amalgm-mcp/events/store.js +98 -0
  38. package/runtime/scripts/amalgm-mcp/events/tools.js +306 -0
  39. package/runtime/scripts/amalgm-mcp/events/webhook-url.js +28 -0
  40. package/runtime/scripts/amalgm-mcp/fs/rest.js +293 -0
  41. package/runtime/scripts/amalgm-mcp/index.js +100 -0
  42. package/runtime/scripts/amalgm-mcp/lib/chat-runner.js +167 -0
  43. package/runtime/scripts/amalgm-mcp/lib/email-md.js +288 -0
  44. package/runtime/scripts/amalgm-mcp/lib/mcp-resolver.js +63 -0
  45. package/runtime/scripts/amalgm-mcp/lib/prefs.js +393 -0
  46. package/runtime/scripts/amalgm-mcp/lib/storage.js +92 -0
  47. package/runtime/scripts/amalgm-mcp/lib/supabase.js +118 -0
  48. package/runtime/scripts/amalgm-mcp/lib/tool-result.js +177 -0
  49. package/runtime/scripts/amalgm-mcp/local/rest.js +80 -0
  50. package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +151 -0
  51. package/runtime/scripts/amalgm-mcp/notify/index.js +107 -0
  52. package/runtime/scripts/amalgm-mcp/server/http.js +335 -0
  53. package/runtime/scripts/amalgm-mcp/server/mcp.js +116 -0
  54. package/runtime/scripts/amalgm-mcp/slack/inbound.js +200 -0
  55. package/runtime/scripts/amalgm-mcp/tasks/executor.js +225 -0
  56. package/runtime/scripts/amalgm-mcp/tasks/rest.js +110 -0
  57. package/runtime/scripts/amalgm-mcp/tasks/schedule-normalization.js +85 -0
  58. package/runtime/scripts/amalgm-mcp/tasks/scheduler.js +105 -0
  59. package/runtime/scripts/amalgm-mcp/tasks/store.js +139 -0
  60. package/runtime/scripts/amalgm-mcp/tasks/tools.js +391 -0
  61. package/runtime/scripts/amalgm-mcp/user-api-keys/rest.js +105 -0
  62. package/runtime/scripts/amalgm-mcp/workspace/rest.js +389 -0
  63. package/runtime/scripts/chat-core/adapters/claude.js +163 -0
  64. package/runtime/scripts/chat-core/adapters/codex.js +313 -0
  65. package/runtime/scripts/chat-core/adapters/opencode.js +412 -0
  66. package/runtime/scripts/chat-core/auth.js +177 -0
  67. package/runtime/scripts/chat-core/contract.js +326 -0
  68. package/runtime/scripts/chat-core/credentials/store.js +212 -0
  69. package/runtime/scripts/chat-core/egress.js +87 -0
  70. package/runtime/scripts/chat-core/engine.js +195 -0
  71. package/runtime/scripts/chat-core/event-schema.js +231 -0
  72. package/runtime/scripts/chat-core/events.js +190 -0
  73. package/runtime/scripts/chat-core/index.js +11 -0
  74. package/runtime/scripts/chat-core/input.js +50 -0
  75. package/runtime/scripts/chat-core/normalizers/claude.js +450 -0
  76. package/runtime/scripts/chat-core/normalizers/codex.js +380 -0
  77. package/runtime/scripts/chat-core/normalizers/normalizer_spec.md +259 -0
  78. package/runtime/scripts/chat-core/normalizers/opencode.js +552 -0
  79. package/runtime/scripts/chat-core/normalizers/tool_contract.md +123 -0
  80. package/runtime/scripts/chat-core/normalizers/usage_contract.md +304 -0
  81. package/runtime/scripts/chat-core/parts.js +253 -0
  82. package/runtime/scripts/chat-core/recorder.js +65 -0
  83. package/runtime/scripts/chat-core/runtime.js +86 -0
  84. package/runtime/scripts/chat-core/server.js +163 -0
  85. package/runtime/scripts/chat-core/sse.js +196 -0
  86. package/runtime/scripts/chat-core/stores.js +100 -0
  87. package/runtime/scripts/chat-core/tool-display.js +149 -0
  88. package/runtime/scripts/chat-core/tool-shape.js +143 -0
  89. package/runtime/scripts/chat-core/tooling/mcp-bundle.js +161 -0
  90. package/runtime/scripts/chat-core/tooling/mcp-relay.js +97 -0
  91. package/runtime/scripts/chat-core/tooling/native-binaries.js +608 -0
  92. package/runtime/scripts/chat-core/tooling/system-prompt.js +20 -0
  93. package/runtime/scripts/chat-core/usage.js +343 -0
  94. package/runtime/scripts/chat-server/config.js +110 -0
  95. package/runtime/scripts/chat-server/db.js +529 -0
  96. package/runtime/scripts/chat-server/index.js +33 -0
  97. package/runtime/scripts/chat-server/model-catalog.js +327 -0
  98. package/runtime/scripts/chat-server.js +75 -0
  99. package/runtime/scripts/credential-adapter.js +129 -0
  100. package/runtime/scripts/fs-watcher.js +888 -0
  101. package/runtime/scripts/local-gateway.js +852 -0
  102. package/runtime/scripts/platform-context.txt +246 -0
  103. package/runtime/scripts/port-monitor.js +175 -0
  104. package/runtime/scripts/proxy-token-store.js +162 -0
  105. package/runtime/scripts/runtime-auth.js +163 -0
  106. package/runtime/scripts/test-claude-code-models.js +87 -0
  107. 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,11 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ ...require('./auth'),
5
+ ...require('./contract'),
6
+ ...require('./engine'),
7
+ ...require('./events'),
8
+ ...require('./runtime'),
9
+ ...require('./server'),
10
+ ...require('./stores'),
11
+ };
@@ -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
+ };