@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,70 @@
|
|
|
1
|
+
/** The four billable token categories Amalgm normalizes for every model step. */
|
|
2
|
+
export interface UsageTokenCounts {
|
|
3
|
+
/** Non-cached input tokens. */
|
|
4
|
+
inputTokens: number | null;
|
|
5
|
+
outputTokens: number | null;
|
|
6
|
+
cacheReadTokens: number | null;
|
|
7
|
+
cacheWriteTokens: number | null;
|
|
8
|
+
}
|
|
9
|
+
export type UsageExactness = 'exact' | 'derived' | 'partial';
|
|
10
|
+
export interface StepUsageModel {
|
|
11
|
+
adapter: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
model: string;
|
|
14
|
+
}
|
|
15
|
+
/** One immutable usage record for one provider model invocation. */
|
|
16
|
+
export interface StepUsage extends UsageTokenCounts {
|
|
17
|
+
type: 'usage.step';
|
|
18
|
+
conversationId: string;
|
|
19
|
+
turnId: string;
|
|
20
|
+
stepId: string;
|
|
21
|
+
/** Zero-based model-invocation order within the turn. */
|
|
22
|
+
ordinal: number;
|
|
23
|
+
model: StepUsageModel;
|
|
24
|
+
source: string;
|
|
25
|
+
exactness: UsageExactness;
|
|
26
|
+
raw: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface StepUsageInput {
|
|
29
|
+
conversationId: string;
|
|
30
|
+
turnId: string;
|
|
31
|
+
stepId: string;
|
|
32
|
+
ordinal: number;
|
|
33
|
+
model: StepUsageModel;
|
|
34
|
+
inputTokens?: unknown;
|
|
35
|
+
outputTokens?: unknown;
|
|
36
|
+
cacheReadTokens?: unknown;
|
|
37
|
+
cacheWriteTokens?: unknown;
|
|
38
|
+
source: string;
|
|
39
|
+
exactness: UsageExactness;
|
|
40
|
+
raw?: unknown;
|
|
41
|
+
}
|
|
42
|
+
export interface StepUsageLedger {
|
|
43
|
+
conversationId: string;
|
|
44
|
+
turnId: string;
|
|
45
|
+
steps: readonly StepUsage[];
|
|
46
|
+
}
|
|
47
|
+
export interface TurnUsage extends UsageTokenCounts {
|
|
48
|
+
type: 'usage.turn';
|
|
49
|
+
conversationId: string;
|
|
50
|
+
turnId: string;
|
|
51
|
+
stepIds: readonly string[];
|
|
52
|
+
exactness: UsageExactness;
|
|
53
|
+
source: 'step_sum';
|
|
54
|
+
}
|
|
55
|
+
/** Normalize native usage without turning missing values into zero. */
|
|
56
|
+
export declare function createStepUsage(input: StepUsageInput): StepUsage;
|
|
57
|
+
export declare function createStepUsageLedger(conversationId: string, turnId: string): StepUsageLedger;
|
|
58
|
+
/**
|
|
59
|
+
* Finalize a step exactly once. Identical replay is idempotent; conflicting
|
|
60
|
+
* facts for the same step or ordinal are rejected rather than double-counted.
|
|
61
|
+
*/
|
|
62
|
+
export declare function recordStepUsage(ledger: StepUsageLedger, usage: StepUsage): StepUsageLedger;
|
|
63
|
+
/** Derive the turn record from its ordered, finalized model steps. */
|
|
64
|
+
export declare function aggregateTurnUsage(ledger: StepUsageLedger): TurnUsage;
|
|
65
|
+
/**
|
|
66
|
+
* Convert a cumulative meter into a delta only when a prior baseline exists.
|
|
67
|
+
* Counter resets remain unknown instead of being clamped into plausible data.
|
|
68
|
+
*/
|
|
69
|
+
export declare function usageDeltaFromCumulative(current: UsageTokenCounts, previous: UsageTokenCounts | null): UsageTokenCounts;
|
|
70
|
+
//# sourceMappingURL=steps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../../src/usage/steps.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAE7D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,MAAM,WAAW,SAAU,SAAQ,gBAAgB;IACjD,IAAI,EAAE,YAAY,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,cAAc,CAAC;IAC1B,GAAG,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,cAAc,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,cAAc,CAAC;IAC1B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,SAAU,SAAQ,gBAAgB;IACjD,IAAI,EAAE,YAAY,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,UAAU,CAAC;CACpB;AA6BD,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAoBhE;AAED,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,GACb,eAAe,CAMjB;AAsBD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,SAAS,GACf,eAAe,CAoBjB;AAqBD,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAarE;AAOD;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAChC,gBAAgB,CAelB"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
function nonEmpty(value, label) {
|
|
2
|
+
const normalized = value.trim();
|
|
3
|
+
if (normalized.length === 0)
|
|
4
|
+
throw new TypeError(`${label} must be a non-empty string`);
|
|
5
|
+
return normalized;
|
|
6
|
+
}
|
|
7
|
+
function tokenCount(value, label) {
|
|
8
|
+
if (value === null || value === undefined)
|
|
9
|
+
return null;
|
|
10
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
11
|
+
throw new TypeError(`${label} must be a non-negative safe integer or null`);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function usageExactness(value) {
|
|
16
|
+
if (value === 'exact' || value === 'derived' || value === 'partial')
|
|
17
|
+
return value;
|
|
18
|
+
throw new TypeError('exactness must be exact, derived, or partial');
|
|
19
|
+
}
|
|
20
|
+
function normalizeModel(model) {
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
adapter: nonEmpty(model.adapter, 'model.adapter'),
|
|
23
|
+
provider: nonEmpty(model.provider, 'model.provider'),
|
|
24
|
+
model: nonEmpty(model.model, 'model.model'),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Normalize native usage without turning missing values into zero. */
|
|
28
|
+
export function createStepUsage(input) {
|
|
29
|
+
if (!Number.isSafeInteger(input.ordinal) || input.ordinal < 0) {
|
|
30
|
+
throw new TypeError('ordinal must be a non-negative safe integer');
|
|
31
|
+
}
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
type: 'usage.step',
|
|
34
|
+
conversationId: nonEmpty(input.conversationId, 'conversationId'),
|
|
35
|
+
turnId: nonEmpty(input.turnId, 'turnId'),
|
|
36
|
+
stepId: nonEmpty(input.stepId, 'stepId'),
|
|
37
|
+
ordinal: input.ordinal,
|
|
38
|
+
model: normalizeModel(input.model),
|
|
39
|
+
inputTokens: tokenCount(input.inputTokens, 'inputTokens'),
|
|
40
|
+
outputTokens: tokenCount(input.outputTokens, 'outputTokens'),
|
|
41
|
+
cacheReadTokens: tokenCount(input.cacheReadTokens, 'cacheReadTokens'),
|
|
42
|
+
cacheWriteTokens: tokenCount(input.cacheWriteTokens, 'cacheWriteTokens'),
|
|
43
|
+
source: nonEmpty(input.source, 'source'),
|
|
44
|
+
exactness: usageExactness(input.exactness),
|
|
45
|
+
raw: Object.hasOwn(input, 'raw') ? input.raw : null,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function createStepUsageLedger(conversationId, turnId) {
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
conversationId: nonEmpty(conversationId, 'conversationId'),
|
|
51
|
+
turnId: nonEmpty(turnId, 'turnId'),
|
|
52
|
+
steps: Object.freeze([]),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function sameModel(left, right) {
|
|
56
|
+
return left.adapter === right.adapter
|
|
57
|
+
&& left.provider === right.provider
|
|
58
|
+
&& left.model === right.model;
|
|
59
|
+
}
|
|
60
|
+
function sameFinalizedFacts(left, right) {
|
|
61
|
+
return left.conversationId === right.conversationId
|
|
62
|
+
&& left.turnId === right.turnId
|
|
63
|
+
&& left.stepId === right.stepId
|
|
64
|
+
&& left.ordinal === right.ordinal
|
|
65
|
+
&& sameModel(left.model, right.model)
|
|
66
|
+
&& left.inputTokens === right.inputTokens
|
|
67
|
+
&& left.outputTokens === right.outputTokens
|
|
68
|
+
&& left.cacheReadTokens === right.cacheReadTokens
|
|
69
|
+
&& left.cacheWriteTokens === right.cacheWriteTokens
|
|
70
|
+
&& left.source === right.source
|
|
71
|
+
&& left.exactness === right.exactness;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Finalize a step exactly once. Identical replay is idempotent; conflicting
|
|
75
|
+
* facts for the same step or ordinal are rejected rather than double-counted.
|
|
76
|
+
*/
|
|
77
|
+
export function recordStepUsage(ledger, usage) {
|
|
78
|
+
if (usage.conversationId !== ledger.conversationId || usage.turnId !== ledger.turnId) {
|
|
79
|
+
throw new Error('step usage belongs to a different conversation or turn');
|
|
80
|
+
}
|
|
81
|
+
const existing = ledger.steps.find((step) => step.stepId === usage.stepId);
|
|
82
|
+
if (existing !== undefined) {
|
|
83
|
+
if (!sameFinalizedFacts(existing, usage)) {
|
|
84
|
+
throw new Error(`conflicting finalized usage for step ${usage.stepId}`);
|
|
85
|
+
}
|
|
86
|
+
return ledger;
|
|
87
|
+
}
|
|
88
|
+
const ordinalOwner = ledger.steps.find((step) => step.ordinal === usage.ordinal);
|
|
89
|
+
if (ordinalOwner !== undefined) {
|
|
90
|
+
throw new Error(`step ordinal ${usage.ordinal} already belongs to ${ordinalOwner.stepId}`);
|
|
91
|
+
}
|
|
92
|
+
const steps = Object.freeze([...ledger.steps, usage].sort((a, b) => a.ordinal - b.ordinal));
|
|
93
|
+
return Object.freeze({ ...ledger, steps });
|
|
94
|
+
}
|
|
95
|
+
function sumKnown(steps, field) {
|
|
96
|
+
let sum = 0;
|
|
97
|
+
for (const step of steps) {
|
|
98
|
+
const value = step[field];
|
|
99
|
+
if (value === null)
|
|
100
|
+
return null;
|
|
101
|
+
sum += value;
|
|
102
|
+
if (!Number.isSafeInteger(sum))
|
|
103
|
+
throw new RangeError(`${field} sum exceeds safe integer range`);
|
|
104
|
+
}
|
|
105
|
+
return steps.length === 0 ? null : sum;
|
|
106
|
+
}
|
|
107
|
+
function aggregateExactness(steps) {
|
|
108
|
+
if (steps.length === 0 || steps.some((step) => step.exactness === 'partial'))
|
|
109
|
+
return 'partial';
|
|
110
|
+
return steps.every((step) => step.exactness === 'exact') ? 'exact' : 'derived';
|
|
111
|
+
}
|
|
112
|
+
/** Derive the turn record from its ordered, finalized model steps. */
|
|
113
|
+
export function aggregateTurnUsage(ledger) {
|
|
114
|
+
return Object.freeze({
|
|
115
|
+
type: 'usage.turn',
|
|
116
|
+
conversationId: ledger.conversationId,
|
|
117
|
+
turnId: ledger.turnId,
|
|
118
|
+
stepIds: Object.freeze(ledger.steps.map((step) => step.stepId)),
|
|
119
|
+
inputTokens: sumKnown(ledger.steps, 'inputTokens'),
|
|
120
|
+
outputTokens: sumKnown(ledger.steps, 'outputTokens'),
|
|
121
|
+
cacheReadTokens: sumKnown(ledger.steps, 'cacheReadTokens'),
|
|
122
|
+
cacheWriteTokens: sumKnown(ledger.steps, 'cacheWriteTokens'),
|
|
123
|
+
exactness: aggregateExactness(ledger.steps),
|
|
124
|
+
source: 'step_sum',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function cumulativeDelta(current, previous) {
|
|
128
|
+
if (current === null || previous === null || current < previous)
|
|
129
|
+
return null;
|
|
130
|
+
return current - previous;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Convert a cumulative meter into a delta only when a prior baseline exists.
|
|
134
|
+
* Counter resets remain unknown instead of being clamped into plausible data.
|
|
135
|
+
*/
|
|
136
|
+
export function usageDeltaFromCumulative(current, previous) {
|
|
137
|
+
if (previous === null) {
|
|
138
|
+
return {
|
|
139
|
+
inputTokens: null,
|
|
140
|
+
outputTokens: null,
|
|
141
|
+
cacheReadTokens: null,
|
|
142
|
+
cacheWriteTokens: null,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
inputTokens: cumulativeDelta(current.inputTokens, previous.inputTokens),
|
|
147
|
+
outputTokens: cumulativeDelta(current.outputTokens, previous.outputTokens),
|
|
148
|
+
cacheReadTokens: cumulativeDelta(current.cacheReadTokens, previous.cacheReadTokens),
|
|
149
|
+
cacheWriteTokens: cumulativeDelta(current.cacheWriteTokens, previous.cacheWriteTokens),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=steps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../src/usage/steps.ts"],"names":[],"mappings":"AA6DA,SAAS,QAAQ,CAAC,KAAa,EAAE,KAAa;IAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,GAAG,KAAK,6BAA6B,CAAC,CAAC;IACxF,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,UAAU,CAAC,KAAc,EAAE,KAAa;IAC/C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,SAAS,CAAC,GAAG,KAAK,8CAA8C,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAClF,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,cAAc,CAAC,KAAqB;IAC3C,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,eAAe,CAAC;QACjD,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,CAAC;QACpD,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;KAC5C,CAAC,CAAC;AACL,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,eAAe,CAAC,KAAqB;IACnD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,SAAS,CAAC,6CAA6C,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,YAAY;QAClB,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC,cAAc,EAAE,gBAAgB,CAAC;QAChE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;QACxC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;QACxC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC;QAClC,WAAW,EAAE,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,aAAa,CAAC;QACzD,YAAY,EAAE,UAAU,CAAC,KAAK,CAAC,YAAY,EAAE,cAAc,CAAC;QAC5D,eAAe,EAAE,UAAU,CAAC,KAAK,CAAC,eAAe,EAAE,iBAAiB,CAAC;QACrE,gBAAgB,EAAE,UAAU,CAAC,KAAK,CAAC,gBAAgB,EAAE,kBAAkB,CAAC;QACxE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;QACxC,SAAS,EAAE,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC;QAC1C,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;KACpD,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,cAAsB,EACtB,MAAc;IAEd,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,cAAc,EAAE,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAC;QAC1D,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;QAClC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;KACzB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,IAAoB,EAAE,KAAqB;IAC5D,OAAO,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;WAChC,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;WAChC,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC;AAClC,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAe,EAAE,KAAgB;IAC3D,OAAO,IAAI,CAAC,cAAc,KAAK,KAAK,CAAC,cAAc;WAC9C,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;WAC5B,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;WAC5B,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;WAC9B,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;WAClC,IAAI,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW;WACtC,IAAI,CAAC,YAAY,KAAK,KAAK,CAAC,YAAY;WACxC,IAAI,CAAC,eAAe,KAAK,KAAK,CAAC,eAAe;WAC9C,IAAI,CAAC,gBAAgB,KAAK,KAAK,CAAC,gBAAgB;WAChD,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;WAC5B,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAuB,EACvB,KAAgB;IAEhB,IAAI,KAAK,CAAC,cAAc,KAAK,MAAM,CAAC,cAAc,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QACrF,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3E,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CAAC,wCAAwC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC;IACjF,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,gBAAgB,KAAK,CAAC,OAAO,uBAAuB,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5F,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,QAAQ,CACf,KAA2B,EAC3B,KAA6B;IAE7B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAChC,GAAG,IAAI,KAAK,CAAC;QACb,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,GAAG,KAAK,iCAAiC,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;AACzC,CAAC;AAED,SAAS,kBAAkB,CAAC,KAA2B;IACrD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/F,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACjF,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,YAAY;QAClB,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/D,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC;QAClD,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,cAAc,CAAC;QACpD,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAC1D,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,kBAAkB,CAAC;QAC5D,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC;QAC3C,MAAM,EAAE,UAAU;KACnB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,OAAsB,EAAE,QAAuB;IACtE,IAAI,OAAO,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,GAAG,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC7E,OAAO,OAAO,GAAG,QAAQ,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAyB,EACzB,QAAiC;IAEjC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO;YACL,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,IAAI;YACrB,gBAAgB,EAAE,IAAI;SACvB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,WAAW,EAAE,eAAe,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC;QACvE,YAAY,EAAE,eAAe,CAAC,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC;QAC1E,eAAe,EAAE,eAAe,CAAC,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC,eAAe,CAAC;QACnF,gBAAgB,EAAE,eAAe,CAAC,OAAO,CAAC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,CAAC;KACvF,CAAC;AACJ,CAAC"}
|
package/dist/usage/tokens.d.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Token counting primitives
|
|
3
|
-
* ported byte-exact. Owned by the usage module; the wire module imports
|
|
4
|
-
* `totalUsageTokens` for PromptUsage projection.
|
|
2
|
+
* Token counting primitives owned by the usage module.
|
|
5
3
|
*/
|
|
6
|
-
/**
|
|
4
|
+
/** Coerce to a finite number, else 0. */
|
|
7
5
|
export declare function n(value: unknown): number;
|
|
8
6
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* relying on "absent" must pass `undefined`.
|
|
7
|
+
* Return the numeric value when finite and non-negative, otherwise null.
|
|
8
|
+
* `Number(null)` is zero, so callers representing absence pass `undefined`.
|
|
12
9
|
*/
|
|
13
10
|
export declare function nonNegativeOrNull(value: unknown): number | null;
|
|
14
11
|
/** The six-bucket token list every normalized usage record carries. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/usage/tokens.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/usage/tokens.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yCAAyC;AACzC,wBAAgB,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGxC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAG/D;AAED,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,GAAE,cAAmB,GAAG,SAAS,CAoBxE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,OAAO,CAAC,MAAM,CAAC,MAAM,SAAS,EAAE,OAAO,CAAC,CAAM,GAAG,OAAO,CAGtG;AAED,uDAAuD;AACvD,wBAAgB,aAAa,IAAI,SAAS,CAEzC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,GAAE,OAAO,CAAC,SAAS,CAAM,EAAE,CAAC,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,SAAS,CAQ/F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,GAAE,OAAO,CAAC,SAAS,CAAM,EAAE,MAAM,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,SAAS,CAQ7G;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,SAAc,GAAG,MAAM,CAM9D"}
|
package/dist/usage/tokens.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Token counting primitives
|
|
3
|
-
* ported byte-exact. Owned by the usage module; the wire module imports
|
|
4
|
-
* `totalUsageTokens` for PromptUsage projection.
|
|
2
|
+
* Token counting primitives owned by the usage module.
|
|
5
3
|
*/
|
|
6
|
-
/**
|
|
4
|
+
/** Coerce to a finite number, else 0. */
|
|
7
5
|
export function n(value) {
|
|
8
6
|
const parsed = Number(value || 0);
|
|
9
7
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
10
8
|
}
|
|
11
9
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* relying on "absent" must pass `undefined`.
|
|
10
|
+
* Return the numeric value when finite and non-negative, otherwise null.
|
|
11
|
+
* `Number(null)` is zero, so callers representing absence pass `undefined`.
|
|
15
12
|
*/
|
|
16
13
|
export function nonNegativeOrNull(value) {
|
|
17
14
|
const parsed = Number(value);
|
package/dist/usage/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/usage/tokens.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/usage/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# ACP and step usage contract
|
|
2
|
+
|
|
3
|
+
## Decision
|
|
4
|
+
|
|
5
|
+
The public agent/client shape is ACP v1 from `@agentclientprotocol/sdk`.
|
|
6
|
+
Prompts are official ACP content blocks and provider output is official ACP
|
|
7
|
+
`SessionUpdate`. Chat does not maintain a second browser or provider event
|
|
8
|
+
grammar.
|
|
9
|
+
|
|
10
|
+
Amalgm durability metadata lives only at `_meta.amalgm`:
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
conversationId · turnId · sequence · native event type · timestamp · raw event
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The sequence is assigned by the durable journal before fanout. It is the replay
|
|
17
|
+
cursor, not a provider stream index.
|
|
18
|
+
|
|
19
|
+
## Output
|
|
20
|
+
|
|
21
|
+
| Part | ACP update |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| assistant text | `agent_message_chunk` |
|
|
24
|
+
| reasoning | `agent_thought_chunk` |
|
|
25
|
+
| tool start | `tool_call` |
|
|
26
|
+
| tool progress/result | `tool_call_update` |
|
|
27
|
+
| plan, mode, config, commands, session info, context | their official ACP session updates |
|
|
28
|
+
|
|
29
|
+
Adapters may reject unsupported input with a typed error. They may not silently
|
|
30
|
+
drop it or disguise non-text state as text.
|
|
31
|
+
|
|
32
|
+
## Step usage
|
|
33
|
+
|
|
34
|
+
Stable ACP context updates describe occupancy, not a complete billing ledger.
|
|
35
|
+
Chat therefore owns one separate usage extension for a model invocation.
|
|
36
|
+
|
|
37
|
+
1. A step is one provider model invocation.
|
|
38
|
+
2. `(turnId, stepId)` is its idempotency key.
|
|
39
|
+
3. Every bucket is a non-negative integer or `null`; missing and measured zero
|
|
40
|
+
are different facts.
|
|
41
|
+
4. Re-recording identical final facts is a no-op. Conflicting final facts are
|
|
42
|
+
rejected.
|
|
43
|
+
5. A turn bucket is summed only when every contributing step reports it.
|
|
44
|
+
6. A cumulative counter becomes a delta only against a proven prior baseline.
|
|
45
|
+
7. Raw provider evidence and provenance survive normalization.
|
|
46
|
+
8. Billing settlement remains owned by the billing authority, not ACP context
|
|
47
|
+
telemetry.
|
|
48
|
+
|
|
49
|
+
The usage algebra and idempotency laws are fully tested. Provider adapters may
|
|
50
|
+
still report incomplete native evidence; those fields remain `null` until the
|
|
51
|
+
provider proves them.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Conversation persistence and durable turns
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A conversation keeps one identity across subscribers, reconnects, host
|
|
6
|
+
restarts, and execution-computer changes. Live delivery, replay, and reload
|
|
7
|
+
reduce to the same transcript.
|
|
8
|
+
|
|
9
|
+
## Stored primitives
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
conversations private identity and header
|
|
13
|
+
turns accepted prompt, execution snapshot, status, result
|
|
14
|
+
turn_updates append-only official ACP update journal
|
|
15
|
+
messages user/assistant transcript projection
|
|
16
|
+
provider_checkpoints exact-execution native continuation evidence
|
|
17
|
+
schema_migrations explicit local schema version
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
One owner-scoped database contains many conversations across installed agents.
|
|
21
|
+
Agent Homes remain isolated by installation and are not folded into this
|
|
22
|
+
database.
|
|
23
|
+
|
|
24
|
+
## Laws
|
|
25
|
+
|
|
26
|
+
1. The host supplies one owner when constructing Chat.
|
|
27
|
+
2. A conversation belongs to one agent installation. A provider session,
|
|
28
|
+
process, socket, subscriber, filesystem path, or computer is not identity.
|
|
29
|
+
3. Accepting a turn commits its prompt, execution snapshot, user message, and
|
|
30
|
+
running state before provider invocation.
|
|
31
|
+
4. Every provider update commits before fanout.
|
|
32
|
+
5. `(turnId, sequence)` is unique and sequence increases by one.
|
|
33
|
+
6. One runtime pump owns provider iteration; subscribers are disposable cursor
|
|
34
|
+
readers.
|
|
35
|
+
7. Subscription registers for live delivery before replaying the committed
|
|
36
|
+
tail, then removes duplicates by sequence.
|
|
37
|
+
8. Terminal state, assistant transcript, and conversation activity seal once
|
|
38
|
+
in one transaction.
|
|
39
|
+
9. Restart marks uncertain unsealed work interrupted unless an adapter proves
|
|
40
|
+
reattachment. Chat never silently repeats external effects.
|
|
41
|
+
10. Provider checkpoints resume only the exact prepared execution revision
|
|
42
|
+
that created them.
|
|
43
|
+
11. SQLite WAL is the local working authority. Synchronization, when added,
|
|
44
|
+
moves logical records and immutable batches, never a live SQLite file.
|
|
45
|
+
|
|
46
|
+
The canonical application projection uses camelCase `ConversationRecord`,
|
|
47
|
+
`ConversationMessage`, and `ConversationTurn` values from this package.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Prompt input and prepared execution
|
|
2
|
+
|
|
3
|
+
This is the public input contract for `@amalgm/chat`. It is deliberately small:
|
|
4
|
+
prompt content is ACP content, while every choice needed to execute that prompt
|
|
5
|
+
is resolved into an immutable handle before the runtime starts a turn.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
A developer should be able to start a session and send the same prompt through
|
|
10
|
+
any registered agent without learning provider payloads. Preparation owns
|
|
11
|
+
discovery and resolution; start and send consume its result.
|
|
12
|
+
|
|
13
|
+
## Primitives
|
|
14
|
+
|
|
15
|
+
- A **prompt** is a non-empty, ordered list of official ACP content blocks.
|
|
16
|
+
- An **attachment** is any non-text ACP content block. It is already embedded or
|
|
17
|
+
addressable when the prompt is accepted.
|
|
18
|
+
- An **execution contract** is the normalized set of choices for one future
|
|
19
|
+
turn: tools, auth, cwd, permission mode, agent revision, provider-scoped
|
|
20
|
+
model and settings, and execution computer.
|
|
21
|
+
- A **prepared execution** is an execution contract plus an opaque host binding.
|
|
22
|
+
The host binding contains the already-resolved effects required by the
|
|
23
|
+
adapter. It is never serialized by the SDK.
|
|
24
|
+
- An **execution handle** is `{ id, revision }`. Send carries this handle and
|
|
25
|
+
prompt content only.
|
|
26
|
+
|
|
27
|
+
## Axioms
|
|
28
|
+
|
|
29
|
+
1. Prompt parts use the official ACP `ContentBlock` type and retain their order.
|
|
30
|
+
2. Chat never fetches, uploads, reads, or rewrites an attachment on the send
|
|
31
|
+
path. A host prepares attachment bytes or references before enabling send.
|
|
32
|
+
3. Every execution field is explicit. Missing auth, provider, model, agent
|
|
33
|
+
revision, cwd, permission mode, or computer is an error; there are no silent
|
|
34
|
+
defaults or fallback auth modes.
|
|
35
|
+
4. A model is identified by `(agent revision, provider id, model id)`. Model
|
|
36
|
+
settings are ACP session-config selections scoped inside that model choice.
|
|
37
|
+
5. Ordinary tools and MCP tools are selected through one immutable toolset
|
|
38
|
+
revision. `amalgm-tools` resolves that revision to the complete official ACP
|
|
39
|
+
MCP server configuration during preparation; Chat adds no ambient server.
|
|
40
|
+
6. Secrets are not execution-contract values. Auth names an opaque binding; the
|
|
41
|
+
authorized host resolves secrets into the opaque prepared binding.
|
|
42
|
+
7. A cwd is an absolute, already-materialized path on the selected computer. A
|
|
43
|
+
Live workspace id and materialization revision are either both present or
|
|
44
|
+
both absent.
|
|
45
|
+
8. Preparation validates provider/model availability, agent capabilities,
|
|
46
|
+
permission support, tools, auth, cwd, and computer placement. Send performs
|
|
47
|
+
one in-memory `{id, revision}` lookup and never calls preparation.
|
|
48
|
+
9. A prepared revision is immutable. Any execution-relevant change creates a
|
|
49
|
+
different revision.
|
|
50
|
+
10. Execution is selected per turn, not frozen to an Amalgm conversation.
|
|
51
|
+
Changing auth or computer keeps the conversation id and may cause the host
|
|
52
|
+
adapter to replace its native provider session.
|
|
53
|
+
11. A native provider checkpoint is scoped to the execution that created it.
|
|
54
|
+
An adapter may reuse it after an execution change only with an explicit
|
|
55
|
+
continuation proof; the reference host starts a new native session.
|
|
56
|
+
12. Every selected input is applied exactly or rejected with a typed error
|
|
57
|
+
before prompting. No adapter may silently ignore an attachment, auth mode,
|
|
58
|
+
tool, permission mode, model setting, provider/model choice, cwd, agent
|
|
59
|
+
revision, or computer placement.
|
|
60
|
+
|
|
61
|
+
## Public path
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const execution = await chat.prepareExecution("composer", {
|
|
65
|
+
agent: {
|
|
66
|
+
adapterId: "codex",
|
|
67
|
+
installationId: "agent-installation",
|
|
68
|
+
revisionId: "agent-revision",
|
|
69
|
+
},
|
|
70
|
+
model: {
|
|
71
|
+
providerId: "openai",
|
|
72
|
+
modelId: "gpt-5.6-codex",
|
|
73
|
+
settings: [{ configId: "thought_level", value: "high" }],
|
|
74
|
+
},
|
|
75
|
+
auth: { mode: "subscription", bindingId: "codex-login" },
|
|
76
|
+
tools: { revisionId: null, toolIds: [] },
|
|
77
|
+
cwd: {
|
|
78
|
+
path: "/workspace/project",
|
|
79
|
+
workspaceId: null,
|
|
80
|
+
materializationRevision: null,
|
|
81
|
+
},
|
|
82
|
+
permissionMode: "ask",
|
|
83
|
+
computerId: "computer-1",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const session = await chat.startSession({ execution });
|
|
87
|
+
const updates = await session.send({
|
|
88
|
+
execution,
|
|
89
|
+
prompt: { parts: [{ type: "text", text: "Hello" }] },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
for await (const update of updates) render(update);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`prepareExecution` is the control plane. `startSession` and `send` are the hot
|
|
96
|
+
path. Tests must prove that neither hot-path operation can call the preparation
|
|
97
|
+
port.
|
|
98
|
+
|
|
99
|
+
## Adapter input contract
|
|
100
|
+
|
|
101
|
+
The scalable adapter set is five native adapters plus one generic ACP adapter.
|
|
102
|
+
Amp, Gemini CLI, Kiro, and any other compliant ACP agent enter through the
|
|
103
|
+
generic adapter; they do not get product-name branches.
|
|
104
|
+
|
|
105
|
+
| Adapter | Auth | Model settings | Tools | Attachment rule |
|
|
106
|
+
| --- | --- | --- | --- | --- |
|
|
107
|
+
| Codex | platform, subscription, BYOK | reasoning and speed | selected MCP servers | native text/image/audio/file projection |
|
|
108
|
+
| Claude Code | platform, subscription, BYOK | reasoning and speed | selected MCP servers | native text/image/document/PDF projection; audio rejects |
|
|
109
|
+
| Cursor | subscription, BYOK Cursor API key; platform rejects | exact reasoning and speed encoded by an advertised Cursor model id | selected ACP MCP servers | ACP capability negotiation |
|
|
110
|
+
| OpenCode | platform, subscription, BYOK | one exact model-catalog `variant`; other settings reject | selected MCP servers | native file parts |
|
|
111
|
+
| Pi | platform, subscription, BYOK | reasoning only; speed rejects | selected MCP servers through the Amalgm Pi extension | native text/image projection |
|
|
112
|
+
| Generic ACP | modes declared by the immutable agent revision | advertised ACP config options | selected ACP MCP servers | ACP capability negotiation |
|
|
113
|
+
|
|
114
|
+
All six paths accept the same prepared cwd, permission mode, immutable agent
|
|
115
|
+
revision, provider-scoped model, and execution computer. The host validates
|
|
116
|
+
the selected revision/model and placement during preparation. `ask` uses one
|
|
117
|
+
common permission port; `full_access` is explicit rather than a missing
|
|
118
|
+
permission policy.
|
|
119
|
+
|
|
120
|
+
The generic ACP revision contributes launch facts, never UI conditionals:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
type AcpLaunch = {
|
|
124
|
+
command: string;
|
|
125
|
+
args: string[];
|
|
126
|
+
supportedAuthModes: ("platform" | "subscription" | "byok")[];
|
|
127
|
+
env?: Record<string, string>;
|
|
128
|
+
auth?: {
|
|
129
|
+
platform?: { tokenEnv: string; baseUrlEnv?: string };
|
|
130
|
+
byok?: { tokenEnv: string; baseUrlEnv?: string };
|
|
131
|
+
};
|
|
132
|
+
authMethodIds?: Partial<Record<AuthMode, string>>;
|
|
133
|
+
modelConfiguredAtLaunch?: boolean;
|
|
134
|
+
};
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
At runtime it negotiates ACP protocol version, prompt capabilities, MCP
|
|
138
|
+
transports, load/resume support, config options, permissions, cancel, and
|
|
139
|
+
close. A missing capability becomes `UNSUPPORTED_EXECUTION_INPUT`; it never
|
|
140
|
+
becomes a best-effort fallback.
|
|
141
|
+
|
|
142
|
+
## Native attachment projection
|
|
143
|
+
|
|
144
|
+
The SDK never changes the canonical ACP prompt. A host adapter projects it at
|
|
145
|
+
the final native boundary:
|
|
146
|
+
|
|
147
|
+
| Adapter | Native projection |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| ACP / Cursor | ACP content blocks unchanged after capability validation |
|
|
150
|
+
| Codex | text, URL/base64 image or audio, local image/audio, or file mention |
|
|
151
|
+
| Claude Code | structured text, image, plain-text document, or PDF blocks |
|
|
152
|
+
| OpenCode | text and URL/data-URL file parts |
|
|
153
|
+
| Pi | text plus its RPC image channel |
|
|
154
|
+
|
|
155
|
+
When a native protocol lacks a content type, the adapter throws
|
|
156
|
+
`UNSUPPORTED_PROMPT_PART` with the adapter, part index, and part type. It never
|
|
157
|
+
silently drops the part or pretends a missing binary is an attached file.
|
|
158
|
+
|
|
159
|
+
## Real acceptance
|
|
160
|
+
|
|
161
|
+
The public `Chat` path has run real text-plus-image turns for all five native
|
|
162
|
+
adapters. Claude Code and Pi additionally passed one combined image, selected
|
|
163
|
+
stdio MCP tool, `ask` permission, tool-result, and streamed-response turn.
|
|
164
|
+
Codex, Cursor, and OpenCode have equivalent real image acceptance; Cursor and
|
|
165
|
+
OpenCode have real selected-tool and permission acceptance. The generic ACP
|
|
166
|
+
adapter has passed text and selected-tool turns against the live Cursor ACP
|
|
167
|
+
server.
|
|
168
|
+
|
|
169
|
+
OpenCode accepts a model-scoped variant id from its prepared catalog. A live
|
|
170
|
+
`high`-variant session passed OpenCode's model resolution and reached the
|
|
171
|
+
selected Vercel model; that provider then rejected the request for account
|
|
172
|
+
credits, which is external billing state rather than input translation.
|
|
173
|
+
|
|
174
|
+
Cursor's own API key is BYOK and reaches `cursor-agent acp` only as
|
|
175
|
+
`CURSOR_API_KEY`. Cursor platform auth remains an explicit typed rejection:
|
|
176
|
+
the native product has no Amalgm proxy/billing path. Subscription uses the
|
|
177
|
+
native Cursor login.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Platform authorization contract
|
|
2
|
+
|
|
3
|
+
Chat never owns a user or machine credential. For platform-auth turns, the
|
|
4
|
+
host injects one live credential broker into the native runtime. Every local
|
|
5
|
+
egress request asks that broker for audience `amalgm-api-proxy` and the exact
|
|
6
|
+
provider scope selected by the frozen route.
|
|
7
|
+
|
|
8
|
+
The broker returns a short-lived DPoP access token plus a fresh proof for the
|
|
9
|
+
request method and full upstream URL. Chat forwards both, never stores them,
|
|
10
|
+
and retries exactly once when the proxy returns `401` after invalidating the
|
|
11
|
+
rejected access token. There is no fallback to a durable bearer or environment
|
|
12
|
+
variable.
|
|
13
|
+
|
|
14
|
+
If issuance or proof creation fails, egress returns `503` without contacting
|
|
15
|
+
the provider. The provider adapter propagates failure through the ordinary
|
|
16
|
+
runtime pump, and Chat seals the turn as `failed`; stable host error codes are
|
|
17
|
+
preserved in the durable turn error.
|
|
18
|
+
|
|
19
|
+
The local relay accepts only its session capability, removes caller-supplied
|
|
20
|
+
credentials and Amalgm metadata, bounds buffered request bytes, rejects
|
|
21
|
+
redirects, and applies response backpressure. These are part of the egress
|
|
22
|
+
boundary, not provider-specific adapter behavior.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { UnsupportedExecutionInputError } from './input-capabilities.js';
|
|
2
|
+
|
|
3
|
+
/** Validate optional ACP inputs against the capabilities negotiated at initialize. */
|
|
4
|
+
export function assertAcpMcpCapabilities(adapterId, capabilities, servers) {
|
|
5
|
+
const mcp = capabilities?.mcpCapabilities || {};
|
|
6
|
+
for (const server of servers) {
|
|
7
|
+
if ((server.type === 'http' || server.type === 'sse') && mcp[server.type] !== true) {
|
|
8
|
+
throw new UnsupportedExecutionInputError(
|
|
9
|
+
adapterId,
|
|
10
|
+
'tools',
|
|
11
|
+
`agent does not advertise ACP ${server.type} MCP support`,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function assertAcpPromptCapabilities(adapterId, capabilities, prompt) {
|
|
18
|
+
const supported = capabilities?.promptCapabilities || {};
|
|
19
|
+
for (const part of prompt.parts) {
|
|
20
|
+
if (part.type === 'image' && supported.image !== true) {
|
|
21
|
+
throw new UnsupportedExecutionInputError(adapterId, 'attachments', 'agent does not advertise image prompts');
|
|
22
|
+
}
|
|
23
|
+
if (part.type === 'audio' && supported.audio !== true) {
|
|
24
|
+
throw new UnsupportedExecutionInputError(adapterId, 'attachments', 'agent does not advertise audio prompts');
|
|
25
|
+
}
|
|
26
|
+
if (part.type === 'resource' && supported.embeddedContext !== true) {
|
|
27
|
+
throw new UnsupportedExecutionInputError(adapterId, 'attachments', 'agent does not advertise embedded context');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|