@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
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native model preparation.
|
|
3
|
+
*
|
|
4
|
+
* The public execution contract carries the selected provider-scoped identity.
|
|
5
|
+
* Preparation derives two different host facts from it: the gateway identity
|
|
6
|
+
* used for usage/auth and the exact token accepted by the selected CLI. These
|
|
7
|
+
* identities are deliberately never substituted for one another.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const GATEWAY_PROVIDERS = [
|
|
11
|
+
'prime-intellect',
|
|
12
|
+
'arcee-ai',
|
|
13
|
+
'alibaba',
|
|
14
|
+
'amazon',
|
|
15
|
+
'anthropic',
|
|
16
|
+
'bytedance',
|
|
17
|
+
'cohere',
|
|
18
|
+
'cursor',
|
|
19
|
+
'deepseek',
|
|
20
|
+
'google',
|
|
21
|
+
'inception',
|
|
22
|
+
'kwaipilot',
|
|
23
|
+
'meituan',
|
|
24
|
+
'meta',
|
|
25
|
+
'minimax',
|
|
26
|
+
'mistral',
|
|
27
|
+
'moonshotai',
|
|
28
|
+
'morph',
|
|
29
|
+
'nvidia',
|
|
30
|
+
'openai',
|
|
31
|
+
'perplexity',
|
|
32
|
+
'xiaomi',
|
|
33
|
+
'xai',
|
|
34
|
+
'zai',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const CLAUDE_ALIASES = {
|
|
38
|
+
haiku: 'anthropic/claude-haiku-4.5',
|
|
39
|
+
sonnet: 'anthropic/claude-sonnet-5',
|
|
40
|
+
'sonnet[1m]': 'anthropic/claude-sonnet-5',
|
|
41
|
+
opus: 'anthropic/claude-opus-4.8',
|
|
42
|
+
opus1m: 'anthropic/claude-opus-4.8-1m',
|
|
43
|
+
'opus[1m]': 'anthropic/claude-opus-4.8-1m',
|
|
44
|
+
fable: 'anthropic/claude-fable-5',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const CLAUDE_CLI_MODELS = {
|
|
48
|
+
'anthropic/claude-haiku-4.5': 'haiku',
|
|
49
|
+
'anthropic/claude-sonnet-5': 'sonnet',
|
|
50
|
+
'anthropic/claude-sonnet-4.6': 'sonnet',
|
|
51
|
+
'anthropic/claude-opus-4.8': 'opus',
|
|
52
|
+
'anthropic/claude-opus-4.8-1m': 'opus[1m]',
|
|
53
|
+
'anthropic/claude-opus-4.7': 'opus',
|
|
54
|
+
'anthropic/claude-opus-4.7-1m': 'opus[1m]',
|
|
55
|
+
'anthropic/claude-fable-5': 'claude-fable-5',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const CURSOR_CLI_ALIASES = {
|
|
59
|
+
'composer-2': 'composer-2.5',
|
|
60
|
+
'composer-2-fast': 'composer-2.5-fast',
|
|
61
|
+
'claude-fable-5': 'claude-fable-5-high',
|
|
62
|
+
'claude-fable-5-thinking': 'claude-fable-5-thinking-high',
|
|
63
|
+
'claude-opus-4-8': 'claude-opus-4-8-high',
|
|
64
|
+
'claude-opus-4-8-thinking': 'claude-opus-4-8-thinking-high',
|
|
65
|
+
'claude-opus-4-7': 'claude-opus-4-7-xhigh',
|
|
66
|
+
'claude-opus-4-7-thinking': 'claude-opus-4-7-thinking-xhigh',
|
|
67
|
+
'claude-opus-4-6': 'claude-4.6-opus-high',
|
|
68
|
+
'claude-opus-4-6-thinking': 'claude-4.6-opus-high-thinking',
|
|
69
|
+
'claude-opus-4-5': 'claude-4.5-opus-high',
|
|
70
|
+
'claude-opus-4-5-thinking': 'claude-4.5-opus-high-thinking',
|
|
71
|
+
'claude-sonnet-5': 'claude-sonnet-5-high',
|
|
72
|
+
'claude-sonnet-5-thinking': 'claude-sonnet-5-thinking-high',
|
|
73
|
+
'claude-sonnet-4-6': 'claude-4.6-sonnet-medium',
|
|
74
|
+
'claude-sonnet-4-6-thinking': 'claude-4.6-sonnet-medium-thinking',
|
|
75
|
+
'gpt-5.5': 'gpt-5.5-medium',
|
|
76
|
+
'gpt-5.4': 'gpt-5.4-medium',
|
|
77
|
+
'gpt-5.4-mini': 'gpt-5.4-mini-medium',
|
|
78
|
+
'gpt-5.4-nano': 'gpt-5.4-nano-medium',
|
|
79
|
+
'grok-4.5': 'grok-4.5-xhigh',
|
|
80
|
+
'glm-5.2': 'glm-5.2-high',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const CURSOR_CANONICAL_ALIASES = {
|
|
84
|
+
'cursor/composer-2': 'cursor/composer-2.5',
|
|
85
|
+
'cursor/composer-2-fast': 'cursor/composer-2.5-fast',
|
|
86
|
+
'anthropic/claude-fable-5': 'anthropic/claude-fable-5-high',
|
|
87
|
+
'anthropic/claude-fable-5-thinking': 'anthropic/claude-fable-5-thinking-high',
|
|
88
|
+
'anthropic/claude-opus-4.8': 'anthropic/claude-opus-4.8-high',
|
|
89
|
+
'anthropic/claude-opus-4.8-thinking': 'anthropic/claude-opus-4.8-thinking-high',
|
|
90
|
+
'anthropic/claude-opus-4.7': 'anthropic/claude-opus-4.7-xhigh',
|
|
91
|
+
'anthropic/claude-opus-4.7-thinking': 'anthropic/claude-opus-4.7-thinking-xhigh',
|
|
92
|
+
'anthropic/claude-opus-4.6': 'anthropic/claude-opus-4.6-high',
|
|
93
|
+
'anthropic/claude-opus-4.6-thinking': 'anthropic/claude-opus-4.6-high-thinking',
|
|
94
|
+
'anthropic/claude-opus-4.5': 'anthropic/claude-opus-4.5-high',
|
|
95
|
+
'anthropic/claude-opus-4.5-thinking': 'anthropic/claude-opus-4.5-high-thinking',
|
|
96
|
+
'anthropic/claude-sonnet-5': 'anthropic/claude-sonnet-5-high',
|
|
97
|
+
'anthropic/claude-sonnet-5-thinking': 'anthropic/claude-sonnet-5-thinking-high',
|
|
98
|
+
'anthropic/claude-sonnet-4.6': 'anthropic/claude-sonnet-4.6-medium',
|
|
99
|
+
'anthropic/claude-sonnet-4.6-thinking': 'anthropic/claude-sonnet-4.6-medium-thinking',
|
|
100
|
+
'openai/gpt-5.5': 'openai/gpt-5.5-medium',
|
|
101
|
+
'openai/gpt-5.4': 'openai/gpt-5.4-medium',
|
|
102
|
+
'openai/gpt-5.4-mini': 'openai/gpt-5.4-mini-medium',
|
|
103
|
+
'openai/gpt-5.4-nano': 'openai/gpt-5.4-nano-medium',
|
|
104
|
+
'google/gemini-3.1-pro-preview': 'google/gemini-3.1-pro',
|
|
105
|
+
'xai/grok-4.5': 'xai/grok-4.5-xhigh',
|
|
106
|
+
'zai/glm-5.2': 'zai/glm-5.2-high',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
function setting(settings, ...ids) {
|
|
110
|
+
for (const id of ids) {
|
|
111
|
+
const match = settings.find((candidate) => candidate.configId === id);
|
|
112
|
+
if (match) return match.value;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function selectedModelId(contract) {
|
|
118
|
+
const { adapterId } = contract.agent;
|
|
119
|
+
const { providerId, modelId } = contract.model;
|
|
120
|
+
if ((adapterId === 'opencode' || adapterId === 'pi' || adapterId === 'amp')
|
|
121
|
+
&& providerId === adapterId) {
|
|
122
|
+
return modelId;
|
|
123
|
+
}
|
|
124
|
+
return modelId.startsWith(`${providerId}/`) ? modelId : `${providerId}/${modelId}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function gatewayModelId(raw) {
|
|
128
|
+
let bare = String(raw || '').trim()
|
|
129
|
+
.replace(/^opencode-/, '')
|
|
130
|
+
.replace(/^pi-/, '')
|
|
131
|
+
.replace(/^vercel\//, '');
|
|
132
|
+
if (bare.includes('/')) return bare;
|
|
133
|
+
for (const provider of GATEWAY_PROVIDERS) {
|
|
134
|
+
const prefix = `${provider}-`;
|
|
135
|
+
if (bare.startsWith(prefix)) {
|
|
136
|
+
const model = bare.slice(prefix.length);
|
|
137
|
+
return `${provider}/${provider === 'anthropic'
|
|
138
|
+
? model.replace(/-(\d+)-(\d+)(?=$|-)/, '-$1.$2')
|
|
139
|
+
: model}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return bare;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function claudeModelId(raw) {
|
|
146
|
+
const clean = String(raw || '').trim().replace(/^vercel\//i, '').toLowerCase();
|
|
147
|
+
if (CLAUDE_ALIASES[clean]) return CLAUDE_ALIASES[clean];
|
|
148
|
+
if (clean.startsWith('claude-code-')) {
|
|
149
|
+
return claudeModelId(clean.slice('claude-code-'.length));
|
|
150
|
+
}
|
|
151
|
+
if (clean.startsWith('anthropic/')) {
|
|
152
|
+
return clean.replace(/-(\d+)-(\d+)(?=$|-)/, '-$1.$2');
|
|
153
|
+
}
|
|
154
|
+
if (clean.startsWith('claude-')) {
|
|
155
|
+
return `anthropic/${clean.replace(/-(\d+)-(\d+)(?=$|-)/, '-$1.$2')}`;
|
|
156
|
+
}
|
|
157
|
+
return clean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function claudeCliModel(modelId) {
|
|
161
|
+
const canonical = claudeModelId(modelId);
|
|
162
|
+
return CLAUDE_CLI_MODELS[canonical] || canonical.replace(/^anthropic\//, '');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function cursorAnthropicCliModel(model) {
|
|
166
|
+
const versioned = model.match(/^claude-(opus|sonnet)-(4(?:\.\d+)?)(?:-(.+))?$/);
|
|
167
|
+
if (!versioned) return model.replace(/-(\d+)\.(\d+)(?=$|-)/, '-$1-$2');
|
|
168
|
+
const [, family, version, rawSuffix] = versioned;
|
|
169
|
+
const suffix = rawSuffix || '';
|
|
170
|
+
if (family === 'sonnet' && version === '4.6') {
|
|
171
|
+
if (!suffix) return 'claude-4.6-sonnet-medium';
|
|
172
|
+
if (suffix === 'thinking') return 'claude-4.6-sonnet-medium-thinking';
|
|
173
|
+
return `claude-4.6-sonnet-${suffix}`;
|
|
174
|
+
}
|
|
175
|
+
if (family === 'sonnet' && (version === '4.5' || version === '4')) {
|
|
176
|
+
if (version === '4' && !suffix) return 'claude-sonnet-4';
|
|
177
|
+
return `claude-${version}-sonnet${suffix ? `-${suffix}` : ''}`;
|
|
178
|
+
}
|
|
179
|
+
if (family === 'opus' && version === '4.6') {
|
|
180
|
+
if (!suffix) return 'claude-4.6-opus-high';
|
|
181
|
+
if (suffix === 'thinking') return 'claude-4.6-opus-high-thinking';
|
|
182
|
+
return `claude-4.6-opus-${suffix}`;
|
|
183
|
+
}
|
|
184
|
+
if (family === 'opus' && version === '4.5') {
|
|
185
|
+
if (!suffix) return 'claude-4.5-opus-high';
|
|
186
|
+
if (suffix === 'thinking') return 'claude-4.5-opus-high-thinking';
|
|
187
|
+
return `claude-4.5-opus-${suffix}`;
|
|
188
|
+
}
|
|
189
|
+
return model.replace(/-(\d+)\.(\d+)(?=$|-)/, '-$1-$2');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function cursorCliModel(raw) {
|
|
193
|
+
const clean = String(raw || '').trim().replace(/^vercel\//i, '').toLowerCase();
|
|
194
|
+
const canonical = CURSOR_CANONICAL_ALIASES[clean] || clean;
|
|
195
|
+
if (canonical.startsWith('cursor/')) {
|
|
196
|
+
const model = canonical.slice('cursor/'.length);
|
|
197
|
+
return CURSOR_CLI_ALIASES[model] || model;
|
|
198
|
+
}
|
|
199
|
+
if (canonical.startsWith('openai/')) return canonical.slice('openai/'.length);
|
|
200
|
+
if (canonical.startsWith('google/')) return canonical.slice('google/'.length);
|
|
201
|
+
if (canonical.startsWith('moonshotai/')) return canonical.slice('moonshotai/'.length);
|
|
202
|
+
if (canonical.startsWith('zai/')) return canonical.slice('zai/'.length);
|
|
203
|
+
if (canonical.startsWith('xai/')) {
|
|
204
|
+
return canonical.slice('xai/'.length).replace(/^grok-4\.20/, 'grok-4-20');
|
|
205
|
+
}
|
|
206
|
+
if (canonical.startsWith('anthropic/')) {
|
|
207
|
+
return cursorAnthropicCliModel(canonical.slice('anthropic/'.length));
|
|
208
|
+
}
|
|
209
|
+
return CURSOR_CLI_ALIASES[canonical] || canonical;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function cursorUsageModelId(raw) {
|
|
213
|
+
const canonical = CURSOR_CANONICAL_ALIASES[String(raw || '').toLowerCase()] || String(raw || '').toLowerCase();
|
|
214
|
+
if (canonical.startsWith('cursor/')) return canonical;
|
|
215
|
+
if (canonical.startsWith('openai/')) {
|
|
216
|
+
const model = canonical.slice('openai/'.length);
|
|
217
|
+
for (const base of [
|
|
218
|
+
'gpt-5.5', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gpt-5.4',
|
|
219
|
+
'gpt-5.3-codex-spark', 'gpt-5.3-codex', 'gpt-5.2-codex', 'gpt-5.2',
|
|
220
|
+
'gpt-5.1-codex-max', 'gpt-5.1-codex-mini', 'gpt-5.1',
|
|
221
|
+
]) {
|
|
222
|
+
if (model.startsWith(base)) return `openai/${base}`;
|
|
223
|
+
}
|
|
224
|
+
return canonical;
|
|
225
|
+
}
|
|
226
|
+
if (canonical.startsWith('anthropic/')) {
|
|
227
|
+
const model = canonical.slice('anthropic/'.length);
|
|
228
|
+
for (const base of [
|
|
229
|
+
'claude-fable-5', 'claude-opus-4.8', 'claude-opus-4.7',
|
|
230
|
+
'claude-opus-4.6', 'claude-opus-4.5', 'claude-sonnet-5',
|
|
231
|
+
'claude-sonnet-4.6', 'claude-sonnet-4.5', 'claude-sonnet-4',
|
|
232
|
+
]) {
|
|
233
|
+
if (model.startsWith(base)) return `anthropic/${base}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (canonical.startsWith('moonshotai/kimi-k2.7')) return 'moonshotai/kimi-k2.7-code';
|
|
237
|
+
if (canonical.startsWith('xai/grok-4.5')) return 'xai/grok-4.5';
|
|
238
|
+
if (canonical.startsWith('zai/glm-5.2')) return 'zai/glm-5.2';
|
|
239
|
+
return canonical;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function codexFacts(selected, settings) {
|
|
243
|
+
let normalizedModelId = selected;
|
|
244
|
+
let base = selected.replace(/^openai\//, '');
|
|
245
|
+
let reasoningEffort = setting(settings, 'thought_level', 'reasoning_effort', 'reasoning');
|
|
246
|
+
const suffix = base.match(/(?:-thinking-|\/)(low|medium|high|xhigh|max|ultra)$/i);
|
|
247
|
+
if (suffix) {
|
|
248
|
+
reasoningEffort ||= suffix[1].toLowerCase();
|
|
249
|
+
base = base.slice(0, -suffix[0].length);
|
|
250
|
+
}
|
|
251
|
+
if (!normalizedModelId.includes('/')) normalizedModelId = `openai/${normalizedModelId}`;
|
|
252
|
+
return {
|
|
253
|
+
normalizedModelId,
|
|
254
|
+
usageModelId: `openai/${base}`,
|
|
255
|
+
cliModel: reasoningEffort ? `${base}/${reasoningEffort}` : base,
|
|
256
|
+
reasoningEffort: typeof reasoningEffort === 'string' ? reasoningEffort : null,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function resolveNativeModel(contract) {
|
|
261
|
+
const adapterId = contract.agent.adapterId;
|
|
262
|
+
const selected = selectedModelId(contract);
|
|
263
|
+
const settings = contract.model.settings || [];
|
|
264
|
+
const speed = setting(settings, 'speed', 'fast');
|
|
265
|
+
let normalizedModelId;
|
|
266
|
+
let usageModelId;
|
|
267
|
+
let cliModel;
|
|
268
|
+
let reasoningEffort = setting(settings, 'thought_level', 'reasoning_effort', 'reasoning');
|
|
269
|
+
|
|
270
|
+
if (adapterId === 'claude_code') {
|
|
271
|
+
normalizedModelId = claudeModelId(selected);
|
|
272
|
+
usageModelId = normalizedModelId === 'anthropic/claude-opus-4.8-1m'
|
|
273
|
+
? 'anthropic/claude-opus-4.8'
|
|
274
|
+
: normalizedModelId;
|
|
275
|
+
cliModel = claudeCliModel(normalizedModelId);
|
|
276
|
+
} else if (adapterId === 'codex') {
|
|
277
|
+
const codex = codexFacts(selected, settings);
|
|
278
|
+
({ normalizedModelId, usageModelId, cliModel, reasoningEffort } = codex);
|
|
279
|
+
} else if (adapterId === 'cursor') {
|
|
280
|
+
normalizedModelId = selected;
|
|
281
|
+
usageModelId = cursorUsageModelId(selected);
|
|
282
|
+
cliModel = cursorCliModel(selected);
|
|
283
|
+
} else if (adapterId === 'opencode') {
|
|
284
|
+
normalizedModelId = gatewayModelId(selected);
|
|
285
|
+
usageModelId = normalizedModelId;
|
|
286
|
+
cliModel = `vercel/${normalizedModelId}`;
|
|
287
|
+
} else if (adapterId === 'pi') {
|
|
288
|
+
normalizedModelId = gatewayModelId(selected);
|
|
289
|
+
usageModelId = normalizedModelId;
|
|
290
|
+
cliModel = normalizedModelId;
|
|
291
|
+
} else {
|
|
292
|
+
normalizedModelId = selected;
|
|
293
|
+
usageModelId = selected;
|
|
294
|
+
cliModel = contract.model.modelId;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const slash = usageModelId.indexOf('/');
|
|
298
|
+
return Object.freeze({
|
|
299
|
+
selectedModelId: selected,
|
|
300
|
+
normalizedModelId,
|
|
301
|
+
usageModelId,
|
|
302
|
+
cliModel,
|
|
303
|
+
providerId: slash > 0 ? usageModelId.slice(0, slash) : contract.model.providerId,
|
|
304
|
+
providerModelId: slash > 0 ? usageModelId.slice(slash + 1) : contract.model.modelId,
|
|
305
|
+
reasoningEffort: typeof reasoningEffort === 'string' ? reasoningEffort : null,
|
|
306
|
+
fastMode: speed === true || speed === 'fast',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Backward-compatible named surface for hosts that only need the CLI token. */
|
|
311
|
+
export function nativeModel(contract) {
|
|
312
|
+
return resolveNativeModel(contract).cliModel;
|
|
313
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { createHmac, randomBytes } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { authEnvelope } from './auth.js';
|
|
5
|
+
import { amalgmDir } from './lib/runtime-paths.js';
|
|
6
|
+
import { resolveNativeModel } from './model-resolution.js';
|
|
7
|
+
|
|
8
|
+
export { nativeModel } from './model-resolution.js';
|
|
9
|
+
|
|
10
|
+
const AUTH_METHOD = {
|
|
11
|
+
platform: 'amalgm',
|
|
12
|
+
subscription: 'provider_auth',
|
|
13
|
+
byok: 'byok',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function sessionAuth(auth, sessionId, localEgressSecret) {
|
|
17
|
+
if (auth.method !== 'amalgm') return auth;
|
|
18
|
+
const replace = (value) => typeof value === 'string'
|
|
19
|
+
? value.replaceAll(encodeURIComponent('__prepared__'), encodeURIComponent(sessionId))
|
|
20
|
+
: value;
|
|
21
|
+
return {
|
|
22
|
+
...auth,
|
|
23
|
+
baseUrl: replace(auth.baseUrl),
|
|
24
|
+
tokenRef: `session-local-${createHmac('sha256', localEgressSecret).update(sessionId).digest('base64url')}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function requireCurrentComputer(contract, currentComputerId) {
|
|
29
|
+
if (currentComputerId && contract.computerId !== currentComputerId) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Prepared execution targets ${contract.computerId}, but this runtime is ${currentComputerId}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class InvalidPreparedAuthError extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = 'InvalidPreparedAuthError';
|
|
40
|
+
this.code = 'INVALID_PREPARED_AUTH';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class NativeExecutionPreparer {
|
|
45
|
+
constructor(options = {}) {
|
|
46
|
+
this.currentComputerId = options.currentComputerId || null;
|
|
47
|
+
this.resolveTools = options.resolveTools || (async (tools) => {
|
|
48
|
+
if (tools.toolIds.length > 0) {
|
|
49
|
+
throw new Error('Native runtime requires resolveTools for a non-empty tool selection');
|
|
50
|
+
}
|
|
51
|
+
return [];
|
|
52
|
+
});
|
|
53
|
+
this.resolveAgent = options.resolveAgent || (async () => ({
|
|
54
|
+
agentConfig: {},
|
|
55
|
+
compiledAgentInstructions: '',
|
|
56
|
+
}));
|
|
57
|
+
this.validateModel = options.validateModel || (async () => undefined);
|
|
58
|
+
this.resolveModel = options.resolveModel || resolveNativeModel;
|
|
59
|
+
this.resolveAuth = options.resolveAuth || ((selection, contract, context) => authEnvelope({
|
|
60
|
+
harness: contract.agent.adapterId,
|
|
61
|
+
authMethod: context.authMethod,
|
|
62
|
+
sessionId: '__prepared__',
|
|
63
|
+
localBaseUrl: this.resolveLocalBaseUrl(),
|
|
64
|
+
proxyBaseUrl: this.proxyBaseUrl,
|
|
65
|
+
amalgmDir: this.amalgmDir,
|
|
66
|
+
runtimeHomeRoot: this.runtimeHomeRoot,
|
|
67
|
+
credentialId: selection.mode === 'byok' && selection.bindingId !== 'default'
|
|
68
|
+
? selection.bindingId
|
|
69
|
+
: null,
|
|
70
|
+
modelId: context.modelId,
|
|
71
|
+
agentConfigId: contract.agent.installationId,
|
|
72
|
+
}));
|
|
73
|
+
this.localBaseUrl = options.localBaseUrl || null;
|
|
74
|
+
this.amalgmDir = options.amalgmDir || amalgmDir();
|
|
75
|
+
this.runtimeHomeRoot = path.resolve(
|
|
76
|
+
options.runtimeHomeRoot || path.join(this.amalgmDir, 'cli-homes'),
|
|
77
|
+
);
|
|
78
|
+
this.proxyBaseUrl = options.proxyBaseUrl || process.env.AMALGM_PROXY_URL || '';
|
|
79
|
+
this.credentialBroker = options.credentialBroker || null;
|
|
80
|
+
this.localEgressSecret = options.localEgressSecret || randomBytes(32).toString('base64url');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
resolveLocalBaseUrl() {
|
|
84
|
+
const value = typeof this.localBaseUrl === 'function'
|
|
85
|
+
? this.localBaseUrl()
|
|
86
|
+
: this.localBaseUrl;
|
|
87
|
+
return typeof value === 'string' && value.trim() ? value.replace(/\/$/, '') : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async resolvePreparedAuth(selection, contract, context) {
|
|
91
|
+
try {
|
|
92
|
+
return await this.resolveAuth(selection, contract, context);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof InvalidPreparedAuthError) throw error;
|
|
95
|
+
throw new InvalidPreparedAuthError(
|
|
96
|
+
error instanceof Error ? error.message : 'Unable to resolve the prepared auth binding',
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async prepare(contract) {
|
|
102
|
+
requireCurrentComputer(contract, this.currentComputerId);
|
|
103
|
+
if (!fs.statSync(contract.cwd.path).isDirectory()) {
|
|
104
|
+
throw new Error(`Prepared cwd is not a directory: ${contract.cwd.path}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const authMethod = AUTH_METHOD[contract.auth.mode];
|
|
108
|
+
const model = await this.resolveModel(contract);
|
|
109
|
+
const [tools, agent, , auth] = await Promise.all([
|
|
110
|
+
this.resolveTools(contract.tools, contract),
|
|
111
|
+
this.resolveAgent(contract.agent, contract),
|
|
112
|
+
this.validateModel(contract.model, contract.agent),
|
|
113
|
+
this.resolvePreparedAuth(contract.auth, contract, { authMethod, modelId: model.usageModelId }),
|
|
114
|
+
]);
|
|
115
|
+
if (!Array.isArray(tools)) throw new Error('resolveTools must return ACP/native MCP server configs');
|
|
116
|
+
if (!auth || typeof auth !== 'object') {
|
|
117
|
+
throw new InvalidPreparedAuthError('resolveAuth must return a prepared auth binding');
|
|
118
|
+
}
|
|
119
|
+
if (auth.method !== authMethod) {
|
|
120
|
+
throw new InvalidPreparedAuthError(
|
|
121
|
+
`${contract.agent.adapterId} does not support ${contract.auth.mode} auth; no fallback is allowed`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const preparedAuth = Object.freeze({
|
|
126
|
+
...auth,
|
|
127
|
+
mode: contract.auth.mode,
|
|
128
|
+
bindingId: contract.auth.bindingId,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return Object.freeze({
|
|
132
|
+
adapterId: contract.agent.adapterId,
|
|
133
|
+
base: Object.freeze({
|
|
134
|
+
userId: 'prepared-user',
|
|
135
|
+
computerId: contract.computerId,
|
|
136
|
+
agentId: contract.agent.installationId,
|
|
137
|
+
agentConfigId: contract.agent.installationId,
|
|
138
|
+
agentRevisionId: contract.agent.revisionId,
|
|
139
|
+
agentRevision: contract.agent,
|
|
140
|
+
agentConfig: agent?.agentConfig || {},
|
|
141
|
+
acpLaunch: agent?.acpLaunch || null,
|
|
142
|
+
harness: contract.agent.adapterId,
|
|
143
|
+
authMethod,
|
|
144
|
+
selectedModelId: model.selectedModelId,
|
|
145
|
+
modelId: model.normalizedModelId,
|
|
146
|
+
providerId: model.providerId,
|
|
147
|
+
providerModelId: model.providerModelId,
|
|
148
|
+
modelSettings: contract.model.settings,
|
|
149
|
+
usageModelId: model.usageModelId,
|
|
150
|
+
cliModel: model.cliModel,
|
|
151
|
+
reasoningEffort: model.reasoningEffort,
|
|
152
|
+
fastMode: model.fastMode,
|
|
153
|
+
contextWindow: null,
|
|
154
|
+
maxTokens: null,
|
|
155
|
+
cwd: contract.cwd.path,
|
|
156
|
+
localBaseUrl: this.resolveLocalBaseUrl(),
|
|
157
|
+
mcpServers: tools,
|
|
158
|
+
toolSelection: contract.tools,
|
|
159
|
+
compiledAgentInstructions: agent?.compiledAgentInstructions || '',
|
|
160
|
+
permissionMode: contract.permissionMode,
|
|
161
|
+
executionComputerId: contract.computerId,
|
|
162
|
+
usageOwner: contract.auth.mode === 'platform' ? 'platform_proxy' : 'local_user',
|
|
163
|
+
auth: preparedAuth,
|
|
164
|
+
}),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
platformProxy() {
|
|
169
|
+
if (!this.proxyBaseUrl || !this.credentialBroker) {
|
|
170
|
+
throw new InvalidPreparedAuthError('Platform proxy is not configured');
|
|
171
|
+
}
|
|
172
|
+
return Object.freeze({ baseUrl: this.proxyBaseUrl, credentialBroker: this.credentialBroker });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
forSession(binding, sessionId, turnId, providerSessionId = null) {
|
|
176
|
+
return {
|
|
177
|
+
...binding.base,
|
|
178
|
+
sessionId,
|
|
179
|
+
assistantMessageId: turnId,
|
|
180
|
+
userMessageId: null,
|
|
181
|
+
providerSessionId,
|
|
182
|
+
auth: sessionAuth(binding.base.auth, sessionId, this.localEgressSecret),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { toAcpSessionUpdate } from '../dist/acp/index.js';
|
|
2
|
+
import { ClaudeAdapter } from './adapters/claude.js';
|
|
3
|
+
import { CodexAdapter } from './adapters/codex.js';
|
|
4
|
+
import { CursorAdapter } from './adapters/cursor.js';
|
|
5
|
+
import { OpenCodeAdapter } from './adapters/opencode.js';
|
|
6
|
+
import { PiAdapter } from './adapters/pi.js';
|
|
7
|
+
import { AcpAdapter } from './adapters/acp.js';
|
|
8
|
+
import { assertExecutionInput } from './adapters/input-capabilities.js';
|
|
9
|
+
import { NativeExecutionPreparer } from './native-contract.js';
|
|
10
|
+
import { forwardPlatformEgress } from './platform-egress.js';
|
|
11
|
+
import { forwardMcpRelay } from './tooling/mcp-relay.js';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_ADAPTERS = {
|
|
14
|
+
acp: (options) => new AcpAdapter(options),
|
|
15
|
+
claude_code: (options) => new ClaudeAdapter(options),
|
|
16
|
+
codex: (options) => new CodexAdapter(options),
|
|
17
|
+
cursor: (options) => new CursorAdapter(options),
|
|
18
|
+
opencode: (options) => new OpenCodeAdapter(options),
|
|
19
|
+
pi: (options) => new PiAdapter(options),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class NativeChatRuntime {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.adapterFactories = { ...DEFAULT_ADAPTERS, ...(options.adapters || {}) };
|
|
25
|
+
this.preparer = options.preparer || new NativeExecutionPreparer(options);
|
|
26
|
+
this.adapterOptions = Object.freeze({
|
|
27
|
+
requestPermission: options.requestPermission || null,
|
|
28
|
+
});
|
|
29
|
+
this.sessions = new Map();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async prepareExecution(contract) {
|
|
33
|
+
const factory = this.adapterFactories[contract.agent.adapterId];
|
|
34
|
+
if (!factory) throw new Error(`Native adapter is not installed: ${contract.agent.adapterId}`);
|
|
35
|
+
const adapter = factory(this.adapterOptions);
|
|
36
|
+
assertExecutionInput(contract.agent.adapterId, contract, adapter.inputCapabilities);
|
|
37
|
+
const prepared = await this.preparer.prepare(contract);
|
|
38
|
+
adapter.validatePrepared?.(prepared.base);
|
|
39
|
+
return Object.freeze({ ...prepared, adapter });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async startSession({ sessionId, execution }) {
|
|
43
|
+
const contract = this.preparer.forSession(execution.binding, sessionId, '__session_start__');
|
|
44
|
+
const session = {
|
|
45
|
+
adapter: execution.binding.adapter,
|
|
46
|
+
native: null,
|
|
47
|
+
contract,
|
|
48
|
+
executionRevision: execution.revision,
|
|
49
|
+
};
|
|
50
|
+
this.sessions.set(sessionId, session);
|
|
51
|
+
try {
|
|
52
|
+
session.native = await execution.binding.adapter.create(contract);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
this.sessions.delete(sessionId);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
return session;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async resumeSession({ sessionId, execution, checkpoint, messages }) {
|
|
61
|
+
const providerSessionId = typeof checkpoint?.providerSessionId === 'string'
|
|
62
|
+
? checkpoint.providerSessionId
|
|
63
|
+
: null;
|
|
64
|
+
const contract = this.preparer.forSession(
|
|
65
|
+
execution.binding,
|
|
66
|
+
sessionId,
|
|
67
|
+
'__session_resume__',
|
|
68
|
+
providerSessionId,
|
|
69
|
+
);
|
|
70
|
+
const session = {
|
|
71
|
+
adapter: execution.binding.adapter,
|
|
72
|
+
native: null,
|
|
73
|
+
contract,
|
|
74
|
+
executionRevision: execution.revision,
|
|
75
|
+
recoveryHistory: providerSessionId ? null : messages,
|
|
76
|
+
};
|
|
77
|
+
this.sessions.set(sessionId, session);
|
|
78
|
+
try {
|
|
79
|
+
session.native = await execution.binding.adapter.create(contract);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this.sessions.delete(sessionId);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
return session;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async *prompt(request) {
|
|
88
|
+
let state = request.session;
|
|
89
|
+
if (state.executionRevision !== request.execution.revision) {
|
|
90
|
+
// Opaque provider checkpoints are scoped to the execution that created
|
|
91
|
+
// them. Reuse after an auth, agent, tool, cwd, or placement change needs
|
|
92
|
+
// an adapter-specific continuation proof; the reference host has none.
|
|
93
|
+
await state.adapter.destroy?.(state.native);
|
|
94
|
+
const contract = this.preparer.forSession(
|
|
95
|
+
request.execution.binding,
|
|
96
|
+
request.sessionId,
|
|
97
|
+
request.turnId,
|
|
98
|
+
null,
|
|
99
|
+
);
|
|
100
|
+
const adapter = request.execution.binding.adapter;
|
|
101
|
+
state.adapter = adapter;
|
|
102
|
+
state.contract = contract;
|
|
103
|
+
state.executionRevision = request.execution.revision;
|
|
104
|
+
state.native = await adapter.create(contract);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const contract = this.preparer.forSession(
|
|
108
|
+
request.execution.binding,
|
|
109
|
+
request.sessionId,
|
|
110
|
+
request.turnId,
|
|
111
|
+
state.native?.providerSessionId || state.contract.providerSessionId,
|
|
112
|
+
);
|
|
113
|
+
state.contract = contract;
|
|
114
|
+
const prompt = state.recoveryHistory
|
|
115
|
+
? promptWithRecoveryHistory(state.recoveryHistory, request.prompt)
|
|
116
|
+
: request.prompt;
|
|
117
|
+
state.recoveryHistory = null;
|
|
118
|
+
let sequence = 0;
|
|
119
|
+
for await (const event of state.adapter.prompt(state.native, prompt, contract)) {
|
|
120
|
+
if (state.adapter.outputType === 'acp') {
|
|
121
|
+
yield event;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const update = toAcpSessionUpdate(event, {
|
|
125
|
+
conversationId: request.sessionId,
|
|
126
|
+
turnId: request.turnId,
|
|
127
|
+
sequence: sequence++,
|
|
128
|
+
});
|
|
129
|
+
if (update) yield update;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async interruptTurn({ session }) {
|
|
134
|
+
await session.adapter.stop?.(session.native);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
checkpointSession({ session }) {
|
|
138
|
+
const providerSessionId = session.native?.providerSessionId || session.contract?.providerSessionId;
|
|
139
|
+
return providerSessionId ? { providerSessionId } : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async closeSession(_sessionId, session) {
|
|
143
|
+
this.sessions.delete(_sessionId);
|
|
144
|
+
await session.adapter.destroy?.(session.native);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async forwardEgress(request, response, input) {
|
|
148
|
+
const session = this.sessions.get(input.sessionId);
|
|
149
|
+
if (!session || session.contract.authMethod !== 'amalgm') {
|
|
150
|
+
response.writeHead(404, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
151
|
+
response.end(JSON.stringify({ error: 'No platform-auth Chat session' }));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
return forwardPlatformEgress({
|
|
155
|
+
request,
|
|
156
|
+
response,
|
|
157
|
+
contract: session.contract,
|
|
158
|
+
proxy: this.preparer.platformProxy(),
|
|
159
|
+
upstreamPath: input.upstreamPath,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async forwardMcp(request, response, input) {
|
|
164
|
+
const session = this.sessions.get(input.sessionId);
|
|
165
|
+
if (!session) {
|
|
166
|
+
response.writeHead(404, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
167
|
+
response.end(JSON.stringify({ error: 'No active Chat session' }));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
return forwardMcpRelay({
|
|
171
|
+
req: request,
|
|
172
|
+
res: response,
|
|
173
|
+
contract: session.contract,
|
|
174
|
+
serverName: input.serverName,
|
|
175
|
+
search: input.search,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function promptWithRecoveryHistory(messages, prompt) {
|
|
181
|
+
const lines = [];
|
|
182
|
+
for (const message of messages.slice(-40)) {
|
|
183
|
+
const label = message.role === 'user' ? 'Human' : 'Assistant';
|
|
184
|
+
const text = typeof message.contentPlain === 'string' ? message.contentPlain.trim() : '';
|
|
185
|
+
if (text) lines.push(`${label}: ${text}`);
|
|
186
|
+
}
|
|
187
|
+
if (lines.length === 0) return prompt;
|
|
188
|
+
return {
|
|
189
|
+
parts: [
|
|
190
|
+
{ type: 'text', text: `<conversation_history>\n${lines.join('\n')}\n</conversation_history>\n\n` },
|
|
191
|
+
...prompt.parts,
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
}
|