@amalgm/chat 0.2.8 → 0.2.9-main.34746990150
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/PURPOSE.md +94 -41
- package/README.md +17 -4
- package/dist/acp/projection.d.ts +8 -0
- package/dist/acp/projection.d.ts.map +1 -1
- package/dist/acp/projection.js +10 -3
- package/dist/acp/projection.js.map +1 -1
- package/dist/conversations/index.d.ts +1 -0
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/index.js +1 -0
- package/dist/conversations/index.js.map +1 -1
- package/dist/conversations/transcript-parts.d.ts +38 -0
- package/dist/conversations/transcript-parts.d.ts.map +1 -0
- package/dist/conversations/transcript-parts.js +2 -0
- package/dist/conversations/transcript-parts.js.map +1 -0
- package/dist/conversations/transcript-tools.d.ts +11 -0
- package/dist/conversations/transcript-tools.d.ts.map +1 -0
- package/dist/conversations/transcript-tools.js +47 -0
- package/dist/conversations/transcript-tools.js.map +1 -0
- package/dist/conversations/transcript.d.ts +5 -4
- package/dist/conversations/transcript.d.ts.map +1 -1
- package/dist/conversations/transcript.js +43 -143
- package/dist/conversations/transcript.js.map +1 -1
- package/dist/conversations/types.d.ts +19 -0
- package/dist/conversations/types.d.ts.map +1 -1
- package/dist/conversations/types.js.map +1 -1
- package/dist/events/constructors.d.ts.map +1 -1
- package/dist/events/constructors.js +2 -52
- package/dist/events/constructors.js.map +1 -1
- package/dist/events/schema.d.ts +72 -72
- package/dist/events/schema.js +16 -16
- package/dist/events/schema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/input/prompt.js +1 -1
- package/dist/input/prompt.js.map +1 -1
- package/dist/mcp/server.js +13 -13
- package/dist/mcp/server.js.map +1 -1
- package/dist/normalizers/claude.d.ts +142 -142
- package/dist/normalizers/claude.d.ts.map +1 -1
- package/dist/normalizers/claude.js +23 -37
- package/dist/normalizers/claude.js.map +1 -1
- package/dist/normalizers/codex.d.ts +71 -71
- package/dist/normalizers/codex.d.ts.map +1 -1
- package/dist/normalizers/codex.js +12 -24
- package/dist/normalizers/codex.js.map +1 -1
- package/dist/normalizers/index.d.ts +0 -1
- package/dist/normalizers/index.d.ts.map +1 -1
- package/dist/normalizers/index.js +0 -1
- package/dist/normalizers/index.js.map +1 -1
- package/dist/normalizers/opencode.d.ts.map +1 -1
- package/dist/normalizers/opencode.js +9 -6
- package/dist/normalizers/opencode.js.map +1 -1
- package/dist/normalizers/pi.d.ts.map +1 -1
- package/dist/normalizers/pi.js +27 -14
- package/dist/normalizers/pi.js.map +1 -1
- package/dist/sessions/chat.d.ts +5 -5
- package/dist/sessions/chat.d.ts.map +1 -1
- package/dist/sessions/chat.js +51 -19
- package/dist/sessions/chat.js.map +1 -1
- package/dist/sessions/index.d.ts +1 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +1 -1
- package/dist/sessions/index.js.map +1 -1
- package/dist/usage/acp.d.ts +4 -0
- package/dist/usage/acp.d.ts.map +1 -0
- package/dist/usage/acp.js +20 -0
- package/dist/usage/acp.js.map +1 -0
- package/dist/usage/finalize.d.ts +1 -11
- package/dist/usage/finalize.d.ts.map +1 -1
- package/dist/usage/finalize.js +29 -47
- package/dist/usage/finalize.js.map +1 -1
- package/dist/usage/index.d.ts +2 -1
- package/dist/usage/index.d.ts.map +1 -1
- package/dist/usage/index.js +2 -1
- package/dist/usage/index.js.map +1 -1
- package/dist/usage/normalize.d.ts +8 -9
- package/dist/usage/normalize.d.ts.map +1 -1
- package/dist/usage/normalize.js +20 -20
- package/dist/usage/normalize.js.map +1 -1
- package/dist/usage/ports.d.ts +4 -4
- package/dist/usage/ports.d.ts.map +1 -1
- package/dist/usage/ports.js +5 -5
- package/dist/usage/ports.js.map +1 -1
- package/dist/usage/tokens.d.ts +13 -38
- package/dist/usage/tokens.d.ts.map +1 -1
- package/dist/usage/tokens.js +39 -72
- package/dist/usage/tokens.js.map +1 -1
- package/host/adapters/acp-prompt.js +36 -0
- package/host/adapters/acp.js +50 -71
- package/host/adapters/attachments.js +37 -0
- package/host/adapters/claude.js +9 -2
- package/host/adapters/codex.js +36 -18
- package/host/adapters/cursor.js +72 -99
- package/host/adapters/opencode.js +39 -25
- package/host/adapters/pi.js +24 -19
- package/host/adapters/prompt.js +13 -21
- package/host/adapters/provider-url.js +8 -0
- package/host/auth.js +10 -9
- package/host/http.js +3 -2
- package/host/model-resolution.js +7 -0
- package/host/native-contract.js +4 -3
- package/host/native-runtime.js +25 -11
- package/host/sqlite/index.d.ts +2 -0
- package/host/sqlite/index.js +48 -4
- package/host/tooling/runtime-home.js +1 -1
- package/host/tooling/system-instructions.js +7 -6
- package/host/tooling/system-prompt.js +12 -18
- package/package.json +26 -2
- package/dist/normalizers/cursor.d.ts +0 -33
- package/dist/normalizers/cursor.d.ts.map +0 -1
- package/dist/normalizers/cursor.js +0 -196
- package/dist/normalizers/cursor.js.map +0 -1
package/dist/usage/tokens.js
CHANGED
|
@@ -1,95 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token counting primitives owned by the usage module.
|
|
3
3
|
*/
|
|
4
|
-
/** Coerce to a finite number, else 0. */
|
|
5
|
-
export function n(value) {
|
|
6
|
-
const parsed = Number(value || 0);
|
|
7
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
8
|
-
}
|
|
9
4
|
/**
|
|
10
5
|
* Return the numeric value when finite and non-negative, otherwise null.
|
|
11
|
-
*
|
|
6
|
+
* Absence is never a measured zero.
|
|
12
7
|
*/
|
|
13
8
|
export function nonNegativeOrNull(value) {
|
|
9
|
+
if ((typeof value !== 'number' && typeof value !== 'string') || value === '')
|
|
10
|
+
return null;
|
|
14
11
|
const parsed = Number(value);
|
|
15
12
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
16
13
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Engine `normalizeTokenList()`: coerce any of the accepted key spellings
|
|
19
|
-
* into the canonical six-bucket list. `total` prefers an explicit
|
|
20
|
-
* non-negative total (nested or flat), else the computed sum of the four
|
|
21
|
-
* context buckets — `thought` is informational and never counted into the
|
|
22
|
-
* computed total.
|
|
23
|
-
*/
|
|
14
|
+
/** Unknown buckets stay unknown; reasoning is included in output, not added again. */
|
|
24
15
|
export function normalizeTokenList(input = {}) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
output: outputTokens,
|
|
39
|
-
cacheRead: cacheReadTokens,
|
|
40
|
-
cacheWrite: cacheWriteTokens,
|
|
41
|
-
thought: thoughtTokens,
|
|
42
|
-
total,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Engine `hasTokenListBuckets()`: true when any bucket coerces to a finite
|
|
47
|
-
* number. Mirrors the engine's `Number()` coercion exactly — `null`
|
|
48
|
-
* coerces to 0 (finite → true), `undefined` to NaN (→ false).
|
|
49
|
-
*/
|
|
50
|
-
export function hasTokenListBuckets(tokenList = {}) {
|
|
16
|
+
const list = (input.tokenList && typeof input.tokenList === 'object' ? input.tokenList : {});
|
|
17
|
+
const inputTokens = nonNegativeOrNull(list['input'] ?? list['inputTokens'] ?? input.inputTokens);
|
|
18
|
+
const output = nonNegativeOrNull(list['output'] ?? list['outputTokens'] ?? input.outputTokens);
|
|
19
|
+
const cacheRead = nonNegativeOrNull(list['cacheRead'] ?? list['cacheReadTokens'] ?? input.cacheReadTokens);
|
|
20
|
+
const cacheWrite = nonNegativeOrNull(list['cacheWrite'] ?? list['cacheWriteTokens'] ?? input.cacheWriteTokens);
|
|
21
|
+
const thought = nonNegativeOrNull(list['thought'] ?? list['thoughtTokens'] ?? input.thoughtTokens);
|
|
22
|
+
const explicitTotal = nonNegativeOrNull(list['total'] ?? list['totalTokens'] ?? input.totalTokens);
|
|
23
|
+
const buckets = [inputTokens, output, cacheRead, cacheWrite];
|
|
24
|
+
const total = explicitTotal ?? (buckets.every(value => value !== null)
|
|
25
|
+
? buckets.reduce((sum, value) => sum + value, 0) : null);
|
|
26
|
+
return { input: inputTokens, output, cacheRead, cacheWrite, thought, total };
|
|
27
|
+
}
|
|
28
|
+
export function hasTokenListBuckets(list = {}) {
|
|
51
29
|
return ['input', 'output', 'cacheRead', 'cacheWrite', 'thought', 'total']
|
|
52
|
-
.some(
|
|
30
|
+
.some(key => nonNegativeOrNull(list[key]) !== null);
|
|
53
31
|
}
|
|
54
|
-
/**
|
|
32
|
+
/** Additive identity, used only for a proven empty accumulator. */
|
|
55
33
|
export function zeroTokenList() {
|
|
56
34
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, thought: 0, total: 0 };
|
|
57
35
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
36
|
+
function combine(a, b, subtract) {
|
|
37
|
+
a = normalizeTokenList({ tokenList: a });
|
|
38
|
+
b = normalizeTokenList({ tokenList: b });
|
|
39
|
+
const bucket = (key) => {
|
|
40
|
+
const left = nonNegativeOrNull(a[key]);
|
|
41
|
+
const right = nonNegativeOrNull(b[key]);
|
|
42
|
+
if (left === null || right === null || (subtract && left < right))
|
|
43
|
+
return null;
|
|
44
|
+
return subtract ? left - right : left + right;
|
|
45
|
+
};
|
|
46
|
+
return { input: bucket('input'), output: bucket('output'), cacheRead: bucket('cacheRead'),
|
|
47
|
+
cacheWrite: bucket('cacheWrite'), thought: bucket('thought'), total: bucket('total') };
|
|
48
|
+
}
|
|
62
49
|
export function addTokenLists(a = {}, b = {}) {
|
|
63
|
-
return
|
|
64
|
-
inputTokens: n(a.input) + n(b.input),
|
|
65
|
-
outputTokens: n(a.output) + n(b.output),
|
|
66
|
-
cacheReadTokens: n(a.cacheRead) + n(b.cacheRead),
|
|
67
|
-
cacheWriteTokens: n(a.cacheWrite) + n(b.cacheWrite),
|
|
68
|
-
thoughtTokens: n(a.thought) + n(b.thought),
|
|
69
|
-
});
|
|
50
|
+
return combine(a, b, false);
|
|
70
51
|
}
|
|
71
|
-
/**
|
|
72
|
-
* Engine `subtractTokenLists()`: bucket-wise `after - before`, clamped at
|
|
73
|
-
* 0 per bucket — a cumulative meter that reset (e.g. across compaction)
|
|
74
|
-
* can never produce a negative delta.
|
|
75
|
-
*/
|
|
52
|
+
/** A missing baseline or a counter reset cannot prove a delta. */
|
|
76
53
|
export function subtractTokenLists(after = {}, before = {}) {
|
|
77
|
-
return
|
|
78
|
-
inputTokens: Math.max(0, n(after.input) - n(before.input)),
|
|
79
|
-
outputTokens: Math.max(0, n(after.output) - n(before.output)),
|
|
80
|
-
cacheReadTokens: Math.max(0, n(after.cacheRead) - n(before.cacheRead)),
|
|
81
|
-
cacheWriteTokens: Math.max(0, n(after.cacheWrite) - n(before.cacheWrite)),
|
|
82
|
-
thoughtTokens: Math.max(0, n(after.thought) - n(before.thought)),
|
|
83
|
-
});
|
|
54
|
+
return combine(after, before, true);
|
|
84
55
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Total tokens for a usage record: tokenList total, else context size, else
|
|
87
|
-
* the sum of the four buckets. Precedence uses `||`, exactly as the engine —
|
|
88
|
-
* a zero total falls through to the next source.
|
|
89
|
-
*/
|
|
56
|
+
/** Explicit totals and measured context are distinct from unknown token buckets. */
|
|
90
57
|
export function totalUsageTokens(usage = {}) {
|
|
91
|
-
return (
|
|
92
|
-
|
|
93
|
-
|
|
58
|
+
return nonNegativeOrNull(usage.tokenList?.total)
|
|
59
|
+
?? nonNegativeOrNull(usage.inputContextSize)
|
|
60
|
+
?? normalizeTokenList(usage).total;
|
|
94
61
|
}
|
|
95
62
|
//# sourceMappingURL=tokens.js.map
|
package/dist/usage/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/usage/tokens.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/usage/tokens.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC1F,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAChE,CAAC;AA0BD,sFAAsF;AACtF,MAAM,UAAU,kBAAkB,CAAC,QAAwB,EAAE;IAC3D,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;IACxH,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IACjG,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/F,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IAC3G,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC/G,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC;IACnG,MAAM,aAAa,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IACnG,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,aAAa,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC;QACpE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAS,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACpE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAkD,EAAE;IACtF,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC;SACtE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAsB,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC3E,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,aAAa;IAC3B,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AACpF,CAAC;AAED,SAAS,OAAO,CAAC,CAAqB,EAAE,CAAqB,EAAE,QAAiB;IAC9E,CAAC,GAAG,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,GAAG,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,CAAC,GAAoB,EAAiB,EAAE;QACrD,MAAM,IAAI,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACxC,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,GAAG,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC/E,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;IAChD,CAAC,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC;QACvF,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;AAC3F,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAwB,EAAE,EAAE,IAAwB,EAAE;IAClF,OAAO,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,kBAAkB,CAAC,QAA4B,EAAE,EAAE,SAA6B,EAAE;IAChG,OAAO,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAWD,oFAAoF;AACpF,MAAM,UAAU,gBAAgB,CAAC,QAAmB,EAAE;IACpD,OAAO,iBAAiB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC;WAC3C,iBAAiB,CAAC,KAAK,CAAC,gBAAgB,CAAC;WACzC,kBAAkB,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** One ACP prompt loop preserves official updates and drains them before completion. */
|
|
2
|
+
export async function* runAcpPrompt(session, prompt, { onUpdate, onResult } = {}) {
|
|
3
|
+
const queue = [];
|
|
4
|
+
let wake = null;
|
|
5
|
+
let finished = false;
|
|
6
|
+
let failure = null;
|
|
7
|
+
const wakeLoop = () => { if (wake) { wake(); wake = null; } };
|
|
8
|
+
const off = session.client.onNotification((message) => {
|
|
9
|
+
if (message.method !== 'session/update') return;
|
|
10
|
+
if (message.params?.sessionId && message.params.sessionId !== session.providerSessionId) return;
|
|
11
|
+
const update = message.params?.update;
|
|
12
|
+
if (!update) return;
|
|
13
|
+
onUpdate?.(message);
|
|
14
|
+
if (update.sessionUpdate === 'error') {
|
|
15
|
+
failure = new Error(update.message || 'ACP agent failed');
|
|
16
|
+
} else queue.push(update);
|
|
17
|
+
wakeLoop();
|
|
18
|
+
});
|
|
19
|
+
session.cancelRequested = false;
|
|
20
|
+
session.activePrompt = true;
|
|
21
|
+
session.client.request('session/prompt', { sessionId: session.providerSessionId, prompt }, 0)
|
|
22
|
+
.then(async result => { await onResult?.(result); })
|
|
23
|
+
.catch(error => { if (!session.cancelRequested) failure = error; })
|
|
24
|
+
.finally(() => { finished = true; wakeLoop(); });
|
|
25
|
+
try {
|
|
26
|
+
while (!finished || queue.length) {
|
|
27
|
+
if (queue.length) yield queue.shift();
|
|
28
|
+
else await new Promise(resolve => { wake = resolve; });
|
|
29
|
+
}
|
|
30
|
+
if (failure) throw failure;
|
|
31
|
+
} finally {
|
|
32
|
+
off();
|
|
33
|
+
session.activePrompt = false;
|
|
34
|
+
session.cancelRequested = false;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/host/adapters/acp.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { usageFromAcpPrompt } from '../../dist/usage/index.js';
|
|
2
|
+
import { runAcpPrompt } from './acp-prompt.js';
|
|
1
3
|
/** Generic ACP agent adapter. Agent-specific launch data comes from the resolved revision. */
|
|
2
4
|
|
|
3
5
|
import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk';
|
|
@@ -155,47 +157,52 @@ export class AcpAdapter {
|
|
|
155
157
|
permissionMode: contract.permissionMode,
|
|
156
158
|
activePrompt: false,
|
|
157
159
|
};
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
try {
|
|
161
|
+
client.onRequest((method, params) => this.handleRequest(session, method, params));
|
|
162
|
+
const initialized = await client.request('initialize', {
|
|
163
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
164
|
+
clientInfo: { name: 'amalgm-chat', version: '0.1.2' },
|
|
165
|
+
clientCapabilities: {
|
|
166
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
167
|
+
session: { configOptions: { boolean: {} } },
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
if (initialized?.protocolVersion !== PROTOCOL_VERSION) {
|
|
171
|
+
throw new UnsupportedExecutionInputError('acp', 'protocol', `agent negotiated version ${initialized?.protocolVersion}`);
|
|
172
|
+
}
|
|
173
|
+
session.capabilities = initialized.agentCapabilities || {};
|
|
174
|
+
const methodId = record(launch.authMethodIds)?.[contract.auth.mode];
|
|
175
|
+
if (methodId) await client.request('authenticate', { methodId }, 120000);
|
|
173
176
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
const mcpServers = toAcpMcpServers(contract);
|
|
178
|
+
assertAcpMcpCapabilities('acp', session.capabilities, mcpServers);
|
|
179
|
+
const sessionParams = acpSessionParams('acp', session.capabilities, contract, mcpServers);
|
|
180
|
+
let response;
|
|
181
|
+
if (session.providerSessionId) {
|
|
182
|
+
if (session.capabilities.loadSession === true) {
|
|
183
|
+
response = await client.request('session/load', {
|
|
184
|
+
...sessionParams,
|
|
185
|
+
sessionId: session.providerSessionId,
|
|
186
|
+
}, 600000);
|
|
187
|
+
} else if (session.capabilities.sessionCapabilities?.resume != null) {
|
|
188
|
+
response = await client.request('session/resume', {
|
|
189
|
+
...sessionParams,
|
|
190
|
+
sessionId: session.providerSessionId,
|
|
191
|
+
}, 600000);
|
|
192
|
+
} else {
|
|
193
|
+
throw new UnsupportedExecutionInputError('acp', 'resume', 'agent cannot load or resume its provider session');
|
|
194
|
+
}
|
|
189
195
|
} else {
|
|
190
|
-
|
|
196
|
+
response = await client.request('session/new', sessionParams, 600000);
|
|
197
|
+
session.providerSessionId = response?.sessionId || null;
|
|
191
198
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
session
|
|
199
|
+
if (!session.providerSessionId) throw new Error('ACP agent did not return a sessionId');
|
|
200
|
+
await applyConfiguration(client, session.providerSessionId, response, contract, launch);
|
|
201
|
+
return session;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
client.close();
|
|
204
|
+
throw error;
|
|
195
205
|
}
|
|
196
|
-
if (!session.providerSessionId) throw new Error('ACP agent did not return a sessionId');
|
|
197
|
-
await applyConfiguration(client, session.providerSessionId, response, contract, launch);
|
|
198
|
-
return session;
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
async handleRequest(session, method, params) {
|
|
@@ -221,6 +228,7 @@ export class AcpAdapter {
|
|
|
221
228
|
}
|
|
222
229
|
|
|
223
230
|
async stop(session) {
|
|
231
|
+
session.cancelRequested = true;
|
|
224
232
|
if (session.activePrompt) {
|
|
225
233
|
session.client.notify('session/cancel', { sessionId: session.providerSessionId });
|
|
226
234
|
}
|
|
@@ -233,41 +241,12 @@ export class AcpAdapter {
|
|
|
233
241
|
session.client.close();
|
|
234
242
|
}
|
|
235
243
|
|
|
236
|
-
async *prompt(session, prompt, contract) {
|
|
244
|
+
async *prompt(session, prompt, contract, effects = {}) {
|
|
237
245
|
assertAcpPromptCapabilities('acp', session.capabilities, prompt);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const wakeLoop = () => { if (wake) { wake(); wake = null; } };
|
|
243
|
-
const off = session.client.onNotification((message) => {
|
|
244
|
-
if (message.method !== 'session/update') return;
|
|
245
|
-
if (message.params?.sessionId && message.params.sessionId !== session.providerSessionId) return;
|
|
246
|
-
if (message.params?.update) queue.push(message.params.update);
|
|
247
|
-
wakeLoop();
|
|
248
|
-
});
|
|
249
|
-
session.activePrompt = true;
|
|
250
|
-
session.client.request('session/prompt', {
|
|
251
|
-
sessionId: session.providerSessionId,
|
|
252
|
-
prompt: acpPromptParts(prompt),
|
|
253
|
-
}, 0).then(() => {
|
|
254
|
-
finished = true;
|
|
255
|
-
wakeLoop();
|
|
256
|
-
}).catch((error) => {
|
|
257
|
-
failure = error;
|
|
258
|
-
finished = true;
|
|
259
|
-
wakeLoop();
|
|
260
|
-
});
|
|
261
|
-
try {
|
|
262
|
-
while (!finished || queue.length) {
|
|
263
|
-
if (queue.length) yield queue.shift();
|
|
264
|
-
else await new Promise((resolve) => { wake = resolve; });
|
|
265
|
-
}
|
|
266
|
-
if (failure) throw failure;
|
|
267
|
-
} finally {
|
|
268
|
-
off();
|
|
269
|
-
session.activePrompt = false;
|
|
270
|
-
}
|
|
246
|
+
yield* runAcpPrompt(session, acpPromptParts(prompt), { onResult: async result => {
|
|
247
|
+
const usage = usageFromAcpPrompt(result?.usage);
|
|
248
|
+
if (usage) await effects.recordUsage?.({ usage, raw: { source: 'acp.prompt_result', payload: result } });
|
|
249
|
+
} });
|
|
271
250
|
}
|
|
272
251
|
}
|
|
273
252
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
/** Materialize file resources for native harnesses; generic ACP retains bytes. */
|
|
7
|
+
export async function materializePromptAttachments(prompt, root) {
|
|
8
|
+
const parts = [];
|
|
9
|
+
for (const part of prompt.parts) {
|
|
10
|
+
if (part.type !== 'resource'
|
|
11
|
+
|| /^(image|audio)\//.test(part.resource.mimeType || '')) {
|
|
12
|
+
parts.push(part);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const bytes = typeof part.resource.text === 'string'
|
|
16
|
+
? Buffer.from(part.resource.text, 'utf8')
|
|
17
|
+
: Buffer.from(part.resource.blob, 'base64');
|
|
18
|
+
const hash = createHash('sha256').update(bytes).digest('hex');
|
|
19
|
+
let basename = part.resource.uri.split('/').pop() || 'attachment';
|
|
20
|
+
try { basename = decodeURIComponent(basename); } catch { /* A URI need not contain percent-encoded text. */ }
|
|
21
|
+
const safeName = path.basename(basename).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
22
|
+
const name = !safeName || /^\.+$/.test(safeName) ? 'attachment' : safeName;
|
|
23
|
+
const directory = path.join(root, 'attachments', hash);
|
|
24
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
25
|
+
const destination = path.join(directory, name);
|
|
26
|
+
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
27
|
+
try {
|
|
28
|
+
await writeFile(temporary, bytes, { flag: 'wx', mode: 0o600 });
|
|
29
|
+
await rename(temporary, destination);
|
|
30
|
+
} finally {
|
|
31
|
+
await rm(temporary, { force: true });
|
|
32
|
+
}
|
|
33
|
+
parts.push({ type: 'resource_link', uri: pathToFileURL(destination).href,
|
|
34
|
+
name: basename, ...(part.resource.mimeType ? { mimeType: part.resource.mimeType } : {}), size: bytes.length });
|
|
35
|
+
}
|
|
36
|
+
return { parts };
|
|
37
|
+
}
|
package/host/adapters/claude.js
CHANGED
|
@@ -118,7 +118,7 @@ class ClaudeAdapter {
|
|
|
118
118
|
? { debug: true, debugFile: path.join(runtime.runtimeHome, 'claude-debug.log') }
|
|
119
119
|
: {}),
|
|
120
120
|
settingSources: [],
|
|
121
|
-
strictMcpConfig:
|
|
121
|
+
strictMcpConfig: true,
|
|
122
122
|
...extra,
|
|
123
123
|
};
|
|
124
124
|
}
|
|
@@ -151,6 +151,7 @@ class ClaudeAdapter {
|
|
|
151
151
|
claudeUsageSnapshotFingerprints: new Set(),
|
|
152
152
|
};
|
|
153
153
|
const stderrChunks = [];
|
|
154
|
+
let latestContextSize = null;
|
|
154
155
|
const captureStderr = (chunk) => {
|
|
155
156
|
const text = redactSecrets(String(chunk || ''));
|
|
156
157
|
if (!text) return;
|
|
@@ -184,7 +185,12 @@ class ClaudeAdapter {
|
|
|
184
185
|
}
|
|
185
186
|
const normalized = normalizeClaudeMessage(message, state);
|
|
186
187
|
if (normalized.length > 0) {
|
|
187
|
-
for (const e of normalized)
|
|
188
|
+
for (const e of normalized) {
|
|
189
|
+
if (e.type === 'usage.final' && !message.parent_tool_use_id && e.usage?.inputContextSize != null) {
|
|
190
|
+
latestContextSize = e.usage.inputContextSize;
|
|
191
|
+
}
|
|
192
|
+
yield e;
|
|
193
|
+
}
|
|
188
194
|
continue;
|
|
189
195
|
}
|
|
190
196
|
if (message.type === 'result') {
|
|
@@ -195,6 +201,7 @@ class ClaudeAdapter {
|
|
|
195
201
|
for (const [index, usageRecord] of usageRecords.entries()) {
|
|
196
202
|
yield usageFinal({
|
|
197
203
|
...usageRecord,
|
|
204
|
+
inputContextSize: usageRecord.inputContextSize ?? latestContextSize,
|
|
198
205
|
costUsd: usageRecords.length === 1 ? message.total_cost_usd ?? null : null,
|
|
199
206
|
}, {
|
|
200
207
|
providerSessionId: state.providerSessionId,
|
package/host/adapters/codex.js
CHANGED
|
@@ -14,10 +14,11 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import os from 'node:os';
|
|
16
16
|
import path from 'node:path';
|
|
17
|
+
import { zeroTokenList } from '../../dist/usage/index.js';
|
|
17
18
|
import { createEventConstructors } from '../../dist/events/index.js';
|
|
18
19
|
import { codexErrorMessage, createCodexNormalizer } from '../../dist/normalizers/codex.js';
|
|
19
20
|
import { recordNativeEvent } from '../recorder.js';
|
|
20
|
-
import {
|
|
21
|
+
import { toCodexMcpToml } from '../tooling/mcp-bundle.js';
|
|
21
22
|
import { bundledCodexBinary, bundledCodexPathDirs, executableExists, findOnPath } from '../tooling/native-binaries.js';
|
|
22
23
|
import { syncCodexProviderAuth } from '../tooling/native-config.js';
|
|
23
24
|
import { prepareHarnessRuntime } from '../tooling/runtime-home.js';
|
|
@@ -50,6 +51,7 @@ class JsonLineRpc {
|
|
|
50
51
|
this.id = 1;
|
|
51
52
|
this.pending = new Map();
|
|
52
53
|
this.handlers = new Set();
|
|
54
|
+
this.failureHandlers = new Set();
|
|
53
55
|
this.requestHandler = null;
|
|
54
56
|
this.buffer = '';
|
|
55
57
|
this.closed = false;
|
|
@@ -81,10 +83,12 @@ class JsonLineRpc {
|
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
failAll(err) {
|
|
86
|
+
if (this.closed) return;
|
|
84
87
|
this.closed = true;
|
|
85
88
|
this.closeError = err;
|
|
86
89
|
for (const pending of this.pending.values()) pending.reject(err);
|
|
87
90
|
this.pending.clear();
|
|
91
|
+
for (const handler of this.failureHandlers) handler(err);
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
rejectPending(id, err) {
|
|
@@ -187,6 +191,12 @@ class JsonLineRpc {
|
|
|
187
191
|
this.requestHandler = handler;
|
|
188
192
|
}
|
|
189
193
|
|
|
194
|
+
onFailure(handler) {
|
|
195
|
+
this.failureHandlers.add(handler);
|
|
196
|
+
if (this.closeError) handler(this.closeError);
|
|
197
|
+
return () => this.failureHandlers.delete(handler);
|
|
198
|
+
}
|
|
199
|
+
|
|
190
200
|
close() {
|
|
191
201
|
try { this.child.stdin.end(); } catch {}
|
|
192
202
|
try {
|
|
@@ -446,10 +456,6 @@ function generatedCodexHookTrust(contract, renderers = defaultRenderers) {
|
|
|
446
456
|
return lines.join('\n');
|
|
447
457
|
}
|
|
448
458
|
|
|
449
|
-
function generatedMcpSectionNames(contract) {
|
|
450
|
-
return relayedMcpServers(contract).map((server) => codexMcpSectionName(server.name));
|
|
451
|
-
}
|
|
452
|
-
|
|
453
459
|
function buildCodexConfig(contract, existingConfig, syncInfo, renderers = defaultRenderers) {
|
|
454
460
|
const mcpToml = toCodexMcpToml(contract);
|
|
455
461
|
const topLevelKeys = [
|
|
@@ -459,8 +465,11 @@ function buildCodexConfig(contract, existingConfig, syncInfo, renderers = defaul
|
|
|
459
465
|
let config = removeTopLevelKeys(existingConfig, topLevelKeys);
|
|
460
466
|
config = removeTomlSections(config, [
|
|
461
467
|
...(contract.authMethod === 'amalgm' ? ['model_providers.amalgm'] : []),
|
|
462
|
-
|
|
468
|
+
'mcp_servers',
|
|
463
469
|
]);
|
|
470
|
+
for (const feature of ['apps', 'plugins', 'remote_plugin']) {
|
|
471
|
+
config = ensureFeatureBoolean(config, feature, false);
|
|
472
|
+
}
|
|
464
473
|
if (renderers.hasHooks(contract.agentConfig)) {
|
|
465
474
|
config = ensureFeatureBoolean(config, 'codex_hooks', true);
|
|
466
475
|
}
|
|
@@ -533,6 +542,7 @@ class CodexAdapter {
|
|
|
533
542
|
writeConfig(contract, runtime.syncInfo, this.renderers);
|
|
534
543
|
const binary = resolveBinary();
|
|
535
544
|
const client = new JsonLineRpc({ binary, cwd: contract.cwd, env: codexEnv(runtime.env, binary) });
|
|
545
|
+
try {
|
|
536
546
|
await client.request('initialize', { clientInfo: { name: 'amalgm-chat', version: '1.0.0' }, capabilities: { experimentalApi: true } });
|
|
537
547
|
client.notify('initialized');
|
|
538
548
|
const fullAccess = contract.permissionMode === 'full_access';
|
|
@@ -552,8 +562,8 @@ class CodexAdapter {
|
|
|
552
562
|
permissionMode: contract.permissionMode,
|
|
553
563
|
activeTurnId: null,
|
|
554
564
|
activeTurnHadAssistant: false,
|
|
555
|
-
cancelCurrentTurn: null,
|
|
556
565
|
cancelRequested: false,
|
|
566
|
+
codexCumulativeTokenList: contract.providerSessionId ? null : zeroTokenList(),
|
|
557
567
|
};
|
|
558
568
|
client.onRequest((method, params) => this.handleServerRequest(session, method, params));
|
|
559
569
|
const thread = contract.providerSessionId
|
|
@@ -565,6 +575,10 @@ class CodexAdapter {
|
|
|
565
575
|
: await client.request('thread/start', threadOptions);
|
|
566
576
|
session.providerSessionId = thread.thread.id;
|
|
567
577
|
return session;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
client.close();
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
568
582
|
}
|
|
569
583
|
|
|
570
584
|
async handleServerRequest(session, method, params) {
|
|
@@ -629,9 +643,10 @@ class CodexAdapter {
|
|
|
629
643
|
|
|
630
644
|
async stop(session) {
|
|
631
645
|
session.cancelRequested = true;
|
|
632
|
-
|
|
646
|
+
// A stop during dispatch must target the turn that dispatch actually accepts.
|
|
647
|
+
try { await session.startRequest; } catch { return; }
|
|
633
648
|
if (!session.activeTurnId) return;
|
|
634
|
-
await session.client.request('turn/interrupt', { threadId: session.providerSessionId, turnId: session.activeTurnId })
|
|
649
|
+
await session.client.request('turn/interrupt', { threadId: session.providerSessionId, turnId: session.activeTurnId });
|
|
635
650
|
}
|
|
636
651
|
|
|
637
652
|
async destroy(session) { session.client.close(); }
|
|
@@ -644,11 +659,8 @@ class CodexAdapter {
|
|
|
644
659
|
const wakeLoop = () => { if (wake) { wake(); wake = null; } };
|
|
645
660
|
const push = (e) => { queue.push(e); wakeLoop(); };
|
|
646
661
|
session.cancelRequested = false;
|
|
647
|
-
session.
|
|
648
|
-
|
|
649
|
-
complete = true;
|
|
650
|
-
push(done({ providerSessionId: session.providerSessionId, stopReason: 'cancelled' }));
|
|
651
|
-
};
|
|
662
|
+
session.activeTurnId = null;
|
|
663
|
+
session.activeTurnHadAssistant = false;
|
|
652
664
|
// Subscription can synchronously deliver already-buffered provider
|
|
653
665
|
// notifications. Everything the handler reads must therefore be fixed
|
|
654
666
|
// before `on` opens that door.
|
|
@@ -689,8 +701,11 @@ class CodexAdapter {
|
|
|
689
701
|
}, 500);
|
|
690
702
|
}
|
|
691
703
|
});
|
|
692
|
-
session.
|
|
693
|
-
|
|
704
|
+
const offFailure = session.client.onFailure((error) => {
|
|
705
|
+
if (complete) return;
|
|
706
|
+
complete = true;
|
|
707
|
+
push(errorEvent(error.message, { providerSessionId: session.providerSessionId }));
|
|
708
|
+
});
|
|
694
709
|
const startRequest = compactCommand
|
|
695
710
|
? session.client.request('thread/compact/start', { threadId: session.providerSessionId })
|
|
696
711
|
: session.client.request('turn/start', {
|
|
@@ -702,7 +717,7 @@ class CodexAdapter {
|
|
|
702
717
|
serviceTier: serviceTierForContract(contract),
|
|
703
718
|
approvalPolicy: contract.permissionMode === 'full_access' ? 'never' : 'on-request',
|
|
704
719
|
});
|
|
705
|
-
startRequest.then((result) => {
|
|
720
|
+
session.startRequest = startRequest.then((result) => {
|
|
706
721
|
if (compactCommand) return;
|
|
707
722
|
session.activeTurnId = result?.turn?.id || session.activeTurnId;
|
|
708
723
|
const resultError = result?.error || result?.turn?.error || result?.turn?.lastError;
|
|
@@ -710,6 +725,7 @@ class CodexAdapter {
|
|
|
710
725
|
push(errorEvent(codexErrorMessage(resultError), { providerSessionId: session.providerSessionId }));
|
|
711
726
|
push(done({ providerSessionId: session.providerSessionId, stopReason: 'error' }));
|
|
712
727
|
complete = true;
|
|
728
|
+
return result;
|
|
713
729
|
}).catch((err) => {
|
|
714
730
|
if (session.cancelRequested) {
|
|
715
731
|
push(done({ providerSessionId: session.providerSessionId, stopReason: 'cancelled' }));
|
|
@@ -728,7 +744,9 @@ class CodexAdapter {
|
|
|
728
744
|
} finally {
|
|
729
745
|
if (compactCompletionTimer) clearTimeout(compactCompletionTimer);
|
|
730
746
|
off();
|
|
731
|
-
|
|
747
|
+
offFailure();
|
|
748
|
+
session.startRequest = null;
|
|
749
|
+
session.activeTurnId = null;
|
|
732
750
|
session.cancelRequested = false;
|
|
733
751
|
}
|
|
734
752
|
}
|