@amalgm/chat 0.1.2 → 0.2.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/AGENTS.md +98 -0
- package/PURPOSE.md +143 -112
- package/README.md +87 -54
- package/dist/acp/index.d.ts +7 -0
- package/dist/acp/index.d.ts.map +1 -0
- package/dist/acp/index.js +7 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/acp/projection.d.ts +30 -0
- package/dist/acp/projection.d.ts.map +1 -0
- package/dist/acp/projection.js +147 -0
- package/dist/acp/projection.js.map +1 -0
- package/dist/api/conversations.d.ts +66 -0
- package/dist/api/conversations.d.ts.map +1 -0
- package/dist/api/conversations.js +202 -0
- package/dist/api/conversations.js.map +1 -0
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -2
- package/dist/api/index.js.map +1 -1
- package/dist/conversations/index.d.ts +3 -0
- package/dist/conversations/index.d.ts.map +1 -0
- package/dist/conversations/index.js +3 -0
- package/dist/conversations/index.js.map +1 -0
- package/dist/conversations/transcript.d.ts +13 -0
- package/dist/conversations/transcript.d.ts.map +1 -0
- package/dist/conversations/transcript.js +171 -0
- package/dist/conversations/transcript.js.map +1 -0
- package/dist/conversations/types.d.ts +147 -0
- package/dist/conversations/types.d.ts.map +1 -0
- package/dist/conversations/types.js +9 -0
- package/dist/conversations/types.js.map +1 -0
- package/dist/events/constructors.d.ts +9 -15
- package/dist/events/constructors.d.ts.map +1 -1
- package/dist/events/constructors.js +6 -12
- package/dist/events/constructors.js.map +1 -1
- package/dist/events/index.d.ts +2 -3
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -3
- package/dist/events/index.js.map +1 -1
- package/dist/events/schema.d.ts +2 -5
- package/dist/events/schema.d.ts.map +1 -1
- package/dist/events/schema.js +2 -5
- package/dist/events/schema.js.map +1 -1
- package/dist/execution/contract.d.ts +51 -0
- package/dist/execution/contract.d.ts.map +1 -0
- package/dist/execution/contract.js +127 -0
- package/dist/execution/contract.js.map +1 -0
- package/dist/execution/index.d.ts +6 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +4 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/revision.d.ts +5 -0
- package/dist/execution/revision.d.ts.map +1 -0
- package/dist/execution/revision.js +17 -0
- package/dist/execution/revision.js.map +1 -0
- package/dist/execution/store.d.ts +26 -0
- package/dist/execution/store.d.ts.map +1 -0
- package/dist/execution/store.js +38 -0
- package/dist/execution/store.js.map +1 -0
- package/dist/index.d.ts +8 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -25
- package/dist/index.js.map +1 -1
- package/dist/input/index.d.ts +3 -0
- package/dist/input/index.d.ts.map +1 -0
- package/dist/input/index.js +2 -0
- package/dist/input/index.js.map +1 -0
- package/dist/input/prompt.d.ts +17 -0
- package/dist/input/prompt.d.ts.map +1 -0
- package/dist/input/prompt.js +104 -0
- package/dist/input/prompt.js.map +1 -0
- package/dist/normalizers/claude.d.ts +6 -9
- package/dist/normalizers/claude.d.ts.map +1 -1
- package/dist/normalizers/claude.js +5 -8
- package/dist/normalizers/claude.js.map +1 -1
- package/dist/normalizers/codex.d.ts +5 -8
- package/dist/normalizers/codex.d.ts.map +1 -1
- package/dist/normalizers/codex.js +4 -7
- package/dist/normalizers/codex.js.map +1 -1
- package/dist/normalizers/cursor.d.ts +1 -2
- package/dist/normalizers/cursor.d.ts.map +1 -1
- package/dist/normalizers/cursor.js +2 -3
- package/dist/normalizers/cursor.js.map +1 -1
- package/dist/normalizers/index.d.ts +3 -5
- package/dist/normalizers/index.d.ts.map +1 -1
- package/dist/normalizers/index.js +3 -5
- package/dist/normalizers/index.js.map +1 -1
- package/dist/normalizers/opencode.d.ts +2 -4
- package/dist/normalizers/opencode.d.ts.map +1 -1
- package/dist/normalizers/opencode.js +2 -4
- package/dist/normalizers/opencode.js.map +1 -1
- package/dist/normalizers/pi.d.ts +3 -4
- package/dist/normalizers/pi.d.ts.map +1 -1
- package/dist/normalizers/pi.js +3 -4
- package/dist/normalizers/pi.js.map +1 -1
- package/dist/sessions/chat.d.ts +176 -0
- package/dist/sessions/chat.d.ts.map +1 -0
- package/dist/sessions/chat.js +701 -0
- package/dist/sessions/chat.js.map +1 -0
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/transcript/index.d.ts +1 -1
- package/dist/transcript/index.js +1 -1
- package/dist/transcript/message-reducer.d.ts +1 -1
- package/dist/transcript/message-reducer.js +1 -1
- package/dist/transcript/part-accumulator.d.ts +1 -1
- package/dist/transcript/part-accumulator.js +1 -1
- package/dist/transcript/tool-display.d.ts +1 -1
- package/dist/transcript/tool-display.js +1 -1
- package/dist/transcript/tool-shape.d.ts +1 -1
- package/dist/transcript/tool-shape.js +1 -1
- package/dist/usage/finalize.d.ts +7 -9
- package/dist/usage/finalize.d.ts.map +1 -1
- package/dist/usage/finalize.js +11 -13
- package/dist/usage/finalize.js.map +1 -1
- package/dist/usage/index.d.ts +2 -3
- package/dist/usage/index.d.ts.map +1 -1
- package/dist/usage/index.js +2 -3
- package/dist/usage/index.js.map +1 -1
- package/dist/usage/model-catalog.d.ts +2 -3
- package/dist/usage/model-catalog.d.ts.map +1 -1
- package/dist/usage/model-catalog.js +10 -3
- package/dist/usage/model-catalog.js.map +1 -1
- package/dist/usage/normalize.d.ts +5 -7
- package/dist/usage/normalize.d.ts.map +1 -1
- package/dist/usage/normalize.js +3 -5
- package/dist/usage/normalize.js.map +1 -1
- package/dist/usage/ports.d.ts +14 -24
- package/dist/usage/ports.d.ts.map +1 -1
- package/dist/usage/ports.js +9 -17
- package/dist/usage/ports.js.map +1 -1
- package/dist/usage/steps.d.ts +70 -0
- package/dist/usage/steps.d.ts.map +1 -0
- package/dist/usage/steps.js +152 -0
- package/dist/usage/steps.js.map +1 -0
- package/dist/usage/tokens.d.ts +4 -7
- package/dist/usage/tokens.d.ts.map +1 -1
- package/dist/usage/tokens.js +4 -7
- package/dist/usage/tokens.js.map +1 -1
- package/docs/contracts/acp-and-step-usage.md +51 -0
- package/docs/contracts/conversation-persistence.md +47 -0
- package/docs/contracts/input-and-execution.md +177 -0
- package/docs/contracts/platform-authorization.md +22 -0
- package/host/adapters/acp-capabilities.js +30 -0
- package/host/adapters/acp-client.js +156 -0
- package/host/adapters/acp.js +284 -0
- package/host/adapters/claude.js +235 -0
- package/host/adapters/codex.js +733 -0
- package/host/adapters/cursor.js +347 -0
- package/host/adapters/input-capabilities.js +89 -0
- package/host/adapters/opencode.js +486 -0
- package/host/adapters/pi.js +519 -0
- package/host/adapters/prompt.js +212 -0
- package/host/auth.js +257 -0
- package/host/credentials/store.js +209 -0
- package/host/http.d.ts +32 -0
- package/host/http.js +238 -0
- package/host/index.d.ts +84 -0
- package/host/index.js +59 -0
- package/host/lib/runtime-paths.js +44 -0
- package/host/model-resolution.js +313 -0
- package/host/native-contract.js +185 -0
- package/host/native-runtime.js +194 -0
- package/host/permissions.js +27 -0
- package/host/platform-egress.js +146 -0
- package/host/recorder.js +61 -0
- package/host/sqlite/index.d.ts +69 -0
- package/host/sqlite/index.js +704 -0
- package/host/tooling/mcp-bundle.js +214 -0
- package/host/tooling/mcp-relay.js +83 -0
- package/host/tooling/native-binaries.js +667 -0
- package/host/tooling/native-config.js +460 -0
- package/host/tooling/package-import.js +111 -0
- package/host/tooling/runtime-home.js +26 -0
- package/host/tooling/system-instructions.js +69 -0
- package/host/tooling/system-prompt.js +35 -0
- package/package.json +45 -26
- package/skills/chat/SKILL.md +97 -0
- package/skills/chat/agents/openai.yaml +4 -0
- package/skills/chat/references/contracts.md +131 -0
- package/dist/api/chat-payloads.d.ts +0 -10
- package/dist/api/chat-payloads.d.ts.map +0 -1
- package/dist/api/chat-payloads.js +0 -57
- package/dist/api/chat-payloads.js.map +0 -1
- package/dist/contract/chat-input.d.ts +0 -144
- package/dist/contract/chat-input.d.ts.map +0 -1
- package/dist/contract/chat-input.js +0 -261
- package/dist/contract/chat-input.js.map +0 -1
- package/dist/contract/create.d.ts +0 -191
- package/dist/contract/create.d.ts.map +0 -1
- package/dist/contract/create.js +0 -249
- package/dist/contract/create.js.map +0 -1
- package/dist/contract/freeze.d.ts +0 -85
- package/dist/contract/freeze.d.ts.map +0 -1
- package/dist/contract/freeze.js +0 -151
- package/dist/contract/freeze.js.map +0 -1
- package/dist/contract/harnesses.d.ts +0 -21
- package/dist/contract/harnesses.d.ts.map +0 -1
- package/dist/contract/harnesses.js +0 -39
- package/dist/contract/harnesses.js.map +0 -1
- package/dist/contract/index.d.ts +0 -17
- package/dist/contract/index.d.ts.map +0 -1
- package/dist/contract/index.js +0 -17
- package/dist/contract/index.js.map +0 -1
- package/dist/contract/input.d.ts +0 -45
- package/dist/contract/input.d.ts.map +0 -1
- package/dist/contract/input.js +0 -78
- package/dist/contract/input.js.map +0 -1
- package/dist/contract/models.d.ts +0 -57
- package/dist/contract/models.d.ts.map +0 -1
- package/dist/contract/models.js +0 -380
- package/dist/contract/models.js.map +0 -1
- package/dist/contract/payload-merge.d.ts +0 -76
- package/dist/contract/payload-merge.d.ts.map +0 -1
- package/dist/contract/payload-merge.js +0 -215
- package/dist/contract/payload-merge.js.map +0 -1
- package/dist/events/tool-display.d.ts +0 -22
- package/dist/events/tool-display.d.ts.map +0 -1
- package/dist/events/tool-display.js +0 -60
- package/dist/events/tool-display.js.map +0 -1
- package/dist/events/tool-shape.d.ts +0 -48
- package/dist/events/tool-shape.d.ts.map +0 -1
- package/dist/events/tool-shape.js +0 -225
- package/dist/events/tool-shape.js.map +0 -1
- package/dist/relay/index.d.ts +0 -65
- package/dist/relay/index.d.ts.map +0 -1
- package/dist/relay/index.js +0 -197
- package/dist/relay/index.js.map +0 -1
- package/dist/turns/index.d.ts +0 -11
- package/dist/turns/index.d.ts.map +0 -1
- package/dist/turns/index.js +0 -11
- package/dist/turns/index.js.map +0 -1
- package/dist/turns/queries.d.ts +0 -36
- package/dist/turns/queries.d.ts.map +0 -1
- package/dist/turns/queries.js +0 -37
- package/dist/turns/queries.js.map +0 -1
- package/dist/turns/replay.d.ts +0 -105
- package/dist/turns/replay.d.ts.map +0 -1
- package/dist/turns/replay.js +0 -163
- package/dist/turns/replay.js.map +0 -1
- package/dist/turns/status.d.ts +0 -49
- package/dist/turns/status.d.ts.map +0 -1
- package/dist/turns/status.js +0 -56
- package/dist/turns/status.js.map +0 -1
- package/dist/turns/turn-store.d.ts +0 -109
- package/dist/turns/turn-store.d.ts.map +0 -1
- package/dist/turns/turn-store.js +0 -169
- package/dist/turns/turn-store.js.map +0 -1
- package/dist/warm/client-cache.d.ts +0 -47
- package/dist/warm/client-cache.d.ts.map +0 -1
- package/dist/warm/client-cache.js +0 -48
- package/dist/warm/client-cache.js.map +0 -1
- package/dist/warm/envelope-cache.d.ts +0 -79
- package/dist/warm/envelope-cache.d.ts.map +0 -1
- package/dist/warm/envelope-cache.js +0 -85
- package/dist/warm/envelope-cache.js.map +0 -1
- package/dist/warm/index.d.ts +0 -18
- package/dist/warm/index.d.ts.map +0 -1
- package/dist/warm/index.js +0 -12
- package/dist/warm/index.js.map +0 -1
- package/dist/warm/payload.d.ts +0 -137
- package/dist/warm/payload.d.ts.map +0 -1
- package/dist/warm/payload.js +0 -112
- package/dist/warm/payload.js.map +0 -1
- package/dist/warm/revision.d.ts +0 -70
- package/dist/warm/revision.d.ts.map +0 -1
- package/dist/warm/revision.js +0 -63
- package/dist/warm/revision.js.map +0 -1
- package/dist/warm/store-policy.d.ts +0 -57
- package/dist/warm/store-policy.d.ts.map +0 -1
- package/dist/warm/store-policy.js +0 -89
- package/dist/warm/store-policy.js.map +0 -1
- package/dist/warm/store.d.ts +0 -42
- package/dist/warm/store.d.ts.map +0 -1
- package/dist/warm/store.js +0 -69
- package/dist/warm/store.js.map +0 -1
- package/dist/wire/frames.d.ts +0 -208
- package/dist/wire/frames.d.ts.map +0 -1
- package/dist/wire/frames.js +0 -245
- package/dist/wire/frames.js.map +0 -1
- package/dist/wire/index.d.ts +0 -7
- package/dist/wire/index.d.ts.map +0 -1
- package/dist/wire/index.js +0 -7
- package/dist/wire/index.js.map +0 -1
package/host/http.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { Chat } from '../dist/sessions/index.js';
|
|
3
|
+
|
|
4
|
+
export interface ChatHttpHandlerOptions<Binding = unknown, RuntimeSession = unknown> {
|
|
5
|
+
readonly chat: Chat<Binding, RuntimeSession>;
|
|
6
|
+
readonly prefix?: string;
|
|
7
|
+
readonly bodyLimitBytes?: number;
|
|
8
|
+
readonly forwardEgress?: ChatAuxiliaryHandler<{
|
|
9
|
+
readonly sessionId: string;
|
|
10
|
+
readonly upstreamPath: string;
|
|
11
|
+
}>;
|
|
12
|
+
readonly forwardMcp?: ChatAuxiliaryHandler<{
|
|
13
|
+
readonly sessionId: string;
|
|
14
|
+
readonly serverName: string;
|
|
15
|
+
readonly search: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ChatAuxiliaryHandler<Input> = (
|
|
20
|
+
request: IncomingMessage,
|
|
21
|
+
response: ServerResponse,
|
|
22
|
+
input: Input,
|
|
23
|
+
) => Promise<void> | void;
|
|
24
|
+
|
|
25
|
+
export type ChatHttpHandler = (
|
|
26
|
+
request: IncomingMessage,
|
|
27
|
+
response: ServerResponse,
|
|
28
|
+
) => Promise<boolean>;
|
|
29
|
+
|
|
30
|
+
export function createChatHttpHandler<Binding = unknown, RuntimeSession = unknown>(
|
|
31
|
+
options: ChatHttpHandlerOptions<Binding, RuntimeSession>,
|
|
32
|
+
): ChatHttpHandler;
|
package/host/http.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const JSON_HEADERS = Object.freeze({
|
|
2
|
+
'content-type': 'application/json; charset=utf-8',
|
|
3
|
+
'cache-control': 'no-store',
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
function object(value) {
|
|
7
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function readJson(request, limit) {
|
|
11
|
+
const chunks = [];
|
|
12
|
+
let size = 0;
|
|
13
|
+
for await (const chunk of request) {
|
|
14
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
15
|
+
size += buffer.length;
|
|
16
|
+
if (size > limit) throw Object.assign(new Error(`Request body exceeds ${limit} bytes`), {
|
|
17
|
+
code: 'REQUEST_TOO_LARGE',
|
|
18
|
+
});
|
|
19
|
+
chunks.push(buffer);
|
|
20
|
+
}
|
|
21
|
+
if (chunks.length === 0) return {};
|
|
22
|
+
const value = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
23
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
24
|
+
throw Object.assign(new Error('Request body must be a JSON object'), { code: 'INVALID_REQUEST' });
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function statusFor(error) {
|
|
30
|
+
if (error?.code === 'SESSION_NOT_FOUND') return 404;
|
|
31
|
+
if (error?.code === 'INTERRUPT_UNSUPPORTED') return 501;
|
|
32
|
+
if (['SESSION_ALREADY_EXISTS', 'SESSION_BUSY', 'SESSION_AGENT_MISMATCH',
|
|
33
|
+
'EXECUTION_REVISION_MISMATCH'].includes(error?.code)) return 409;
|
|
34
|
+
if (error instanceof SyntaxError || String(error?.code || '').startsWith('INVALID_')
|
|
35
|
+
|| error?.code === 'EXECUTION_NOT_PREPARED') return 400;
|
|
36
|
+
if (error?.code === 'REQUEST_TOO_LARGE') return 413;
|
|
37
|
+
return 500;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function errorBody(error) {
|
|
41
|
+
return {
|
|
42
|
+
error: {
|
|
43
|
+
code: typeof error?.code === 'string' ? error.code : 'CHAT_ERROR',
|
|
44
|
+
message: error instanceof Error ? error.message : String(error),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sendJson(response, status, value) {
|
|
50
|
+
const body = JSON.stringify(value);
|
|
51
|
+
response.writeHead(status, { ...JSON_HEADERS, 'content-length': Buffer.byteLength(body) });
|
|
52
|
+
response.end(body);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeSse(response, event, value, id) {
|
|
56
|
+
if (response.destroyed) return false;
|
|
57
|
+
if (id !== undefined) response.write(`id: ${id}\n`);
|
|
58
|
+
response.write(`event: ${event}\n`);
|
|
59
|
+
response.write(`data: ${JSON.stringify(value)}\n\n`);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function updateSequence(update) {
|
|
64
|
+
const value = update?._meta?.amalgm?.sequence;
|
|
65
|
+
return Number.isFinite(value) ? Number(value) : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function listOptions(url) {
|
|
69
|
+
const number = (name) => {
|
|
70
|
+
const value = url.searchParams.get(name);
|
|
71
|
+
return value === null ? undefined : Number(value);
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
agentInstallationId: url.searchParams.get('agentInstallationId') || undefined,
|
|
75
|
+
origin: url.searchParams.get('origin') || undefined,
|
|
76
|
+
originId: url.searchParams.get('originId') || undefined,
|
|
77
|
+
includeArchived: url.searchParams.get('includeArchived') === 'true',
|
|
78
|
+
offset: number('offset'),
|
|
79
|
+
limit: number('limit'),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function streamTurn(response, updates, completion) {
|
|
84
|
+
response.writeHead(200, {
|
|
85
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
86
|
+
'cache-control': 'no-store',
|
|
87
|
+
connection: 'keep-alive',
|
|
88
|
+
});
|
|
89
|
+
response.flushHeaders?.();
|
|
90
|
+
try {
|
|
91
|
+
for await (const update of updates) {
|
|
92
|
+
if (!writeSse(response, 'update', update, updateSequence(update))) return;
|
|
93
|
+
}
|
|
94
|
+
const turn = await (typeof completion === 'function' ? completion() : completion);
|
|
95
|
+
writeSse(response, 'complete', turn);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
writeSse(response, 'error', errorBody(error).error);
|
|
98
|
+
} finally {
|
|
99
|
+
if (!response.destroyed) response.end();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Node HTTP adapter over the public Chat capability. */
|
|
104
|
+
export function createChatHttpHandler(options) {
|
|
105
|
+
if (!options?.chat) throw new TypeError('createChatHttpHandler requires chat');
|
|
106
|
+
const chat = options.chat;
|
|
107
|
+
const prefix = String(options.prefix || '/chat').replace(/\/$/, '');
|
|
108
|
+
const bodyLimit = options.bodyLimitBytes || 2_000_000;
|
|
109
|
+
|
|
110
|
+
return async function handleChatHttp(request, response) {
|
|
111
|
+
const url = new URL(request.url || '/', 'http://localhost');
|
|
112
|
+
if (url.pathname !== prefix && !url.pathname.startsWith(`${prefix}/`)) return false;
|
|
113
|
+
const path = url.pathname.slice(prefix.length).split('/').filter(Boolean).map(decodeURIComponent);
|
|
114
|
+
const method = request.method || 'GET';
|
|
115
|
+
try {
|
|
116
|
+
if (path[0] === 'egress' && path[1]) {
|
|
117
|
+
if (typeof options.forwardEgress !== 'function') {
|
|
118
|
+
sendJson(response, 501, errorBody(Object.assign(new Error('Platform egress is not configured'), {
|
|
119
|
+
code: 'EGRESS_NOT_CONFIGURED',
|
|
120
|
+
})));
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
await options.forwardEgress(request, response, {
|
|
124
|
+
sessionId: path[1],
|
|
125
|
+
upstreamPath: `/${path.slice(2).map(encodeURIComponent).join('/')}${url.search}`,
|
|
126
|
+
});
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (path[0] === 'mcp' && path[1] && path[2]) {
|
|
130
|
+
if (typeof options.forwardMcp !== 'function') {
|
|
131
|
+
sendJson(response, 501, errorBody(Object.assign(new Error('MCP relay is not configured'), {
|
|
132
|
+
code: 'MCP_RELAY_NOT_CONFIGURED',
|
|
133
|
+
})));
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
await options.forwardMcp(request, response, {
|
|
137
|
+
sessionId: path[1],
|
|
138
|
+
serverName: path[2],
|
|
139
|
+
search: url.search,
|
|
140
|
+
});
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (method === 'POST' && path[0] === 'executions' && path.length === 1) {
|
|
144
|
+
const body = await readJson(request, bodyLimit);
|
|
145
|
+
const execution = await chat.prepareExecution(body.id, body.execution);
|
|
146
|
+
sendJson(response, 201, { execution });
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
if (path[0] !== 'conversations') {
|
|
150
|
+
sendJson(response, 404, errorBody(Object.assign(new Error('Route not found'), { code: 'NOT_FOUND' })));
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (method === 'GET' && path.length === 1) {
|
|
154
|
+
sendJson(response, 200, { conversations: await chat.listConversations(listOptions(url)) });
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (method === 'POST' && path.length === 1) {
|
|
158
|
+
const body = await readJson(request, bodyLimit);
|
|
159
|
+
const session = await chat.startSession({
|
|
160
|
+
sessionId: body.conversationId,
|
|
161
|
+
execution: body.execution,
|
|
162
|
+
title: body.title,
|
|
163
|
+
origin: body.origin,
|
|
164
|
+
originId: body.originId,
|
|
165
|
+
metadata: object(body.metadata),
|
|
166
|
+
});
|
|
167
|
+
sendJson(response, 201, { conversation: await session.inspect() });
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
const conversationId = path[1];
|
|
171
|
+
if (!conversationId) throw Object.assign(new Error('conversation id is required'), { code: 'INVALID_REQUEST' });
|
|
172
|
+
if (method === 'POST' && path[2] === 'resume' && path.length === 3) {
|
|
173
|
+
const body = await readJson(request, bodyLimit);
|
|
174
|
+
const result = body.execution
|
|
175
|
+
? { session: await chat.resumeSession({ sessionId: conversationId, execution: body.execution }) }
|
|
176
|
+
: await chat.resumeStoredSession(conversationId);
|
|
177
|
+
sendJson(response, 200, { conversation: await result.session.inspect(), execution: result.execution });
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
if (method === 'POST' && path[2] === 'turns' && path.length === 3) {
|
|
181
|
+
const body = await readJson(request, bodyLimit);
|
|
182
|
+
const stream = await chat.send(conversationId, {
|
|
183
|
+
execution: body.execution,
|
|
184
|
+
prompt: body.prompt,
|
|
185
|
+
clientTurnId: body.clientTurnId,
|
|
186
|
+
userMessageId: body.userMessageId,
|
|
187
|
+
assistantMessageId: body.assistantMessageId,
|
|
188
|
+
});
|
|
189
|
+
await streamTurn(response, stream, stream.completion);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
if (method === 'GET' && path[2] === 'turns' && path[3] && path[4] === 'events') {
|
|
193
|
+
const after = Number(url.searchParams.get('afterSequence') || request.headers['last-event-id'] || 0);
|
|
194
|
+
const updates = chat.reconnectTurn(conversationId, path[3], after);
|
|
195
|
+
await streamTurn(response, updates, async () => (await chat.replayTurn(
|
|
196
|
+
conversationId,
|
|
197
|
+
path[3],
|
|
198
|
+
after,
|
|
199
|
+
)).turn);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (method === 'POST' && path[2] === 'interrupt' && path.length === 3) {
|
|
203
|
+
sendJson(response, 200, await chat.interruptSession(conversationId));
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
if (method === 'GET' && path[2] === 'active' && path.length === 3) {
|
|
207
|
+
sendJson(response, 200, { turn: await chat.activeTurn(conversationId) });
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (method === 'GET' && path.length === 2) {
|
|
211
|
+
sendJson(response, 200, {
|
|
212
|
+
conversation: await chat.inspectSession(conversationId),
|
|
213
|
+
messages: await chat.listMessages(conversationId),
|
|
214
|
+
latestTurn: await chat.latestTurn(conversationId),
|
|
215
|
+
});
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
if (method === 'PATCH' && path.length === 2) {
|
|
219
|
+
const body = await readJson(request, bodyLimit);
|
|
220
|
+
const conversation = body.newMessages === false
|
|
221
|
+
? await chat.markSeen(conversationId)
|
|
222
|
+
: await chat.updateTitle(conversationId, body.title);
|
|
223
|
+
sendJson(response, 200, { conversation });
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
if (method === 'DELETE' && path.length === 2) {
|
|
227
|
+
sendJson(response, 200, { conversation: await chat.archiveSession(conversationId) });
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
sendJson(response, 404, errorBody(Object.assign(new Error('Route not found'), { code: 'NOT_FOUND' })));
|
|
231
|
+
return true;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (!response.headersSent) sendJson(response, statusFor(error), errorBody(error));
|
|
234
|
+
else if (!response.destroyed) response.end();
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
package/host/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Chat,
|
|
3
|
+
ChatRuntimePort,
|
|
4
|
+
ClockIsoPort,
|
|
5
|
+
RandomUuidPort,
|
|
6
|
+
Sha256HexPort,
|
|
7
|
+
} from '../dist/index.js';
|
|
8
|
+
import type { ConversationStorePort, ConversationTurn } from '../dist/conversations/index.js';
|
|
9
|
+
import type { ChatHttpHandler } from './http.js';
|
|
10
|
+
|
|
11
|
+
export interface PlatformCredentialBroker {
|
|
12
|
+
authorize(request: Readonly<{
|
|
13
|
+
audience: 'amalgm-api-proxy';
|
|
14
|
+
scopes: readonly string[];
|
|
15
|
+
method: string;
|
|
16
|
+
url: string;
|
|
17
|
+
}>): Promise<Readonly<{
|
|
18
|
+
accessToken: string;
|
|
19
|
+
authorization: string;
|
|
20
|
+
dpop: string;
|
|
21
|
+
}>>;
|
|
22
|
+
invalidate(credential: Readonly<{ accessToken: string }>): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CreateChatHostOptions<Binding = unknown, RuntimeSession = unknown> {
|
|
26
|
+
readonly ownerUserId: string;
|
|
27
|
+
readonly databasePath?: string;
|
|
28
|
+
readonly conversations?: ConversationStorePort;
|
|
29
|
+
readonly runtime?: ChatRuntimePort<Binding, RuntimeSession>;
|
|
30
|
+
readonly native?: Readonly<Record<string, unknown>> & {
|
|
31
|
+
readonly credentialBroker?: PlatformCredentialBroker;
|
|
32
|
+
};
|
|
33
|
+
readonly uuid?: RandomUuidPort;
|
|
34
|
+
readonly clockIso?: ClockIsoPort;
|
|
35
|
+
readonly sha256Hex?: Sha256HexPort;
|
|
36
|
+
readonly subscriberBufferSize?: number;
|
|
37
|
+
readonly journalBatch?: false | {
|
|
38
|
+
readonly maxUpdates: number;
|
|
39
|
+
readonly maxDelayMs: number;
|
|
40
|
+
readonly wait: (milliseconds: number) => Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ComposedChatHost<Binding = unknown, RuntimeSession = unknown> {
|
|
45
|
+
readonly chat: Chat<Binding, RuntimeSession>;
|
|
46
|
+
readonly runtime: ChatRuntimePort<Binding, RuntimeSession>;
|
|
47
|
+
readonly conversations: ConversationStorePort;
|
|
48
|
+
readonly recoveredTurns: readonly ConversationTurn[];
|
|
49
|
+
readonly handleHttp: ChatHttpHandler;
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createChatHost<Binding = unknown, RuntimeSession = unknown>(
|
|
54
|
+
options: CreateChatHostOptions<Binding, RuntimeSession>,
|
|
55
|
+
): Promise<ComposedChatHost<Binding, RuntimeSession>>;
|
|
56
|
+
|
|
57
|
+
export { createChatHttpHandler } from './http.js';
|
|
58
|
+
export type { ChatHttpHandler, ChatHttpHandlerOptions } from './http.js';
|
|
59
|
+
|
|
60
|
+
export class NativeChatRuntime implements ChatRuntimePort<unknown, unknown> {
|
|
61
|
+
constructor(options?: Readonly<Record<string, unknown>>);
|
|
62
|
+
prepareExecution(contract: import('../dist/execution/index.js').ExecutionContract): Promise<unknown>;
|
|
63
|
+
startSession(request: import('../dist/sessions/index.js').RuntimeStartRequest<unknown>): Promise<unknown>;
|
|
64
|
+
resumeSession(request: import('../dist/sessions/index.js').RuntimeResumeRequest<unknown>): Promise<unknown>;
|
|
65
|
+
prompt(request: import('../dist/sessions/index.js').RuntimePromptRequest<unknown, unknown>): AsyncIterable<import('@agentclientprotocol/sdk').SessionUpdate>;
|
|
66
|
+
interruptTurn(request: import('../dist/sessions/index.js').RuntimeInterruptRequest<unknown, unknown>): Promise<void>;
|
|
67
|
+
checkpointSession(request: { readonly session: unknown }): unknown;
|
|
68
|
+
closeSession(sessionId: string, session: unknown): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class NativeExecutionPreparer {
|
|
72
|
+
constructor(options?: Readonly<Record<string, unknown>>);
|
|
73
|
+
prepare(contract: import('../dist/execution/index.js').ExecutionContract): Promise<unknown>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class InvalidPreparedAuthError extends Error {
|
|
77
|
+
readonly code: 'INVALID_PREPARED_AUTH';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
SqliteConversationStore,
|
|
82
|
+
SqliteConversationStoreError,
|
|
83
|
+
createSqliteJournalBatchPolicy,
|
|
84
|
+
} from './sqlite/index.js';
|
package/host/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { Chat } from '../dist/sessions/index.js';
|
|
4
|
+
import { NativeChatRuntime } from './native-runtime.js';
|
|
5
|
+
import { createChatHttpHandler } from './http.js';
|
|
6
|
+
import {
|
|
7
|
+
SqliteConversationStore,
|
|
8
|
+
createSqliteJournalBatchPolicy,
|
|
9
|
+
} from './sqlite/index.js';
|
|
10
|
+
|
|
11
|
+
/** Compose the public Chat capability with Node host effects. */
|
|
12
|
+
export async function createChatHost(options) {
|
|
13
|
+
if (!options || typeof options !== 'object') {
|
|
14
|
+
throw new TypeError('createChatHost options are required');
|
|
15
|
+
}
|
|
16
|
+
const conversations = options.conversations || new SqliteConversationStore({
|
|
17
|
+
databasePath: options.databasePath,
|
|
18
|
+
});
|
|
19
|
+
const runtime = options.runtime || new NativeChatRuntime(options.native || {});
|
|
20
|
+
const chat = new Chat({
|
|
21
|
+
runtime,
|
|
22
|
+
ownerUserId: options.ownerUserId,
|
|
23
|
+
conversations,
|
|
24
|
+
uuid: options.uuid || randomUUID,
|
|
25
|
+
clockIso: options.clockIso || (() => new Date().toISOString()),
|
|
26
|
+
sha256Hex: options.sha256Hex || ((value) => createHash('sha256').update(value).digest('hex')),
|
|
27
|
+
subscriberBufferSize: options.subscriberBufferSize,
|
|
28
|
+
journalBatch: options.journalBatch === false
|
|
29
|
+
? undefined
|
|
30
|
+
: options.journalBatch || createSqliteJournalBatchPolicy(),
|
|
31
|
+
});
|
|
32
|
+
const recoveredTurns = await chat.recoverInterruptedTurns();
|
|
33
|
+
const handleHttp = createChatHttpHandler({
|
|
34
|
+
chat,
|
|
35
|
+
...(typeof runtime.forwardEgress === 'function'
|
|
36
|
+
? { forwardEgress: runtime.forwardEgress.bind(runtime) }
|
|
37
|
+
: {}),
|
|
38
|
+
...(typeof runtime.forwardMcp === 'function'
|
|
39
|
+
? { forwardMcp: runtime.forwardMcp.bind(runtime) }
|
|
40
|
+
: {}),
|
|
41
|
+
});
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
chat,
|
|
44
|
+
runtime,
|
|
45
|
+
conversations,
|
|
46
|
+
recoveredTurns,
|
|
47
|
+
handleHttp,
|
|
48
|
+
close: () => chat.close(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { NativeChatRuntime } from './native-runtime.js';
|
|
53
|
+
export { createChatHttpHandler } from './http.js';
|
|
54
|
+
export { NativeExecutionPreparer, InvalidPreparedAuthError } from './native-contract.js';
|
|
55
|
+
export {
|
|
56
|
+
SqliteConversationStore,
|
|
57
|
+
SqliteConversationStoreError,
|
|
58
|
+
createSqliteJournalBatchPolicy,
|
|
59
|
+
} from './sqlite/index.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small documented port of engine `runtime/scripts/lib/runtime-paths.js`
|
|
3
|
+
* (other subsystem — see HOST CONVENTIONS). Only the subset the host layer
|
|
4
|
+
* consumes is ported: `amalgmDir`, `runtimeLabel`, `runtimeStateDir`.
|
|
5
|
+
*
|
|
6
|
+
* Engine default for `amalgmDir()` when `AMALGM_DIR` is unset: resolve the
|
|
7
|
+
* Amalgm home via `amalgm-mcp/lib/layout.resolveAmalgmHome` and pick the
|
|
8
|
+
* per-user directory (`<home>/users/<scope>`) via user-scope discovery
|
|
9
|
+
* (AMALGM_RUNTIME_USER_ID / computer.json / auth.json / layout user entries).
|
|
10
|
+
* That discovery lives in amalgm-mcp (another repo's material), so this host
|
|
11
|
+
* port simplifies to `~/.amalgm` — orchestrators set `AMALGM_DIR` explicitly
|
|
12
|
+
* (engine: entrypoint.sh / electron/main.ts), which is the path every
|
|
13
|
+
* production deployment takes.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
|
|
19
|
+
/** Engine `normalizeRuntimeLabel` (`lib/runtime-paths.js:7-12`), byte-faithful. */
|
|
20
|
+
function normalizeRuntimeLabel(value) {
|
|
21
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
22
|
+
if (['preview', 'canary', 'native-chat', 'staging'].includes(raw)) return 'preview';
|
|
23
|
+
if (['local', 'dev', 'development'].includes(raw)) return 'local';
|
|
24
|
+
return 'main';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function amalgmDir() {
|
|
28
|
+
if (process.env.AMALGM_DIR) return process.env.AMALGM_DIR;
|
|
29
|
+
// Engine default: layout.resolveUserDir(layout.resolveAmalgmHome(env), scope).
|
|
30
|
+
return path.join(os.homedir(), '.amalgm');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Engine `runtimeLabel` (`lib/runtime-paths.js:77-79`). */
|
|
34
|
+
export function runtimeLabel() {
|
|
35
|
+
return normalizeRuntimeLabel(process.env.AMALGM_RUNTIME_LABEL || process.env.AMALGM_BRANCH);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Engine `runtimeStateDir` (`lib/runtime-paths.js:81-86`). */
|
|
39
|
+
export function runtimeStateDir() {
|
|
40
|
+
return (
|
|
41
|
+
process.env.AMALGM_RUNTIME_STATE_DIR
|
|
42
|
+
|| path.join(amalgmDir(), 'runtimes', runtimeLabel())
|
|
43
|
+
);
|
|
44
|
+
}
|