@7h3/protocol 0.5.0 → 0.5.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/README.md +6 -1317
- package/agentAdapter.d.ts +26 -0
- package/auditLog.d.ts +54 -0
- package/capability.d.ts +67 -0
- package/cborCodec.d.ts +34 -0
- package/conformanceVectors.d.ts +20 -0
- package/encryption.d.ts +85 -0
- package/envelopeCbor.d.ts +33 -0
- package/frameworkAdapters.d.ts +72 -0
- package/gateway.d.ts +53 -0
- package/grpcBinding.d.ts +29 -0
- package/httpBinding.d.ts +50 -0
- package/index.d.ts +39 -0
- package/index.js +3662 -0
- package/keyInfra.d.ts +64 -0
- package/keyRegistry.d.ts +9 -0
- package/keyRotation.d.ts +20 -0
- package/mcpGateway.d.ts +37 -0
- package/mcpTransports.d.ts +62 -0
- package/mcpWrapper.d.ts +83 -0
- package/otel.d.ts +65 -0
- package/package.json +13 -121
- package/policyEnforcer.d.ts +50 -0
- package/policyTelemetryFeedback.d.ts +11 -0
- package/{src/protocol.d.ts → protocol.d.ts} +1 -2
- package/protocolAgent.d.ts +58 -0
- package/protocolBinary.d.ts +8 -0
- package/protocolCapabilities.d.ts +24 -0
- package/protocolReplay.d.ts +35 -0
- package/protocolTransport.d.ts +73 -0
- package/queueBinding.d.ts +43 -0
- package/rateLimiter.d.ts +18 -0
- package/{src/redisClient.ts → redisClient.d.ts} +25 -75
- package/replayStores.d.ts +98 -0
- package/revocation.d.ts +71 -0
- package/routePolicy.d.ts +30 -0
- package/runtimePolicy.d.ts +24 -0
- package/runtimePolicyManager.d.ts +15 -0
- package/runtimePolicyPresets.d.ts +11 -0
- package/signedResponse.d.ts +21 -0
- package/stream.d.ts +59 -0
- package/telemetry.d.ts +79 -0
- package/webhookBinding.d.ts +30 -0
- package/wsBinding.d.ts +52 -0
- package/.dockerignore +0 -19
- package/.github/dependabot.yml +0 -32
- package/.github/workflows/ci.yml +0 -31
- package/.github/workflows/publish.yml +0 -59
- package/.github/workflows/scorecard.yml +0 -37
- package/7h3.example.yaml +0 -125
- package/CHANGELOG.md +0 -152
- package/CONTRIBUTING.md +0 -82
- package/Dockerfile +0 -73
- package/GOVERNANCE.md +0 -62
- package/SECURITY.md +0 -70
- package/bench-results/replay-cache-full-1777891033256.json +0 -10
- package/bench-results/replay-cache-full-1777896317488.json +0 -10
- package/bench-results/replay-cache-full-1777900993184.json +0 -10
- package/bench-results/replay-cache-full-1777901019285.json +0 -10
- package/bench-results/replay-cache-quick-1777870170126.json +0 -10
- package/bench-results/signature-profiles-quick-1775875160079.json +0 -85
- package/bench-results/signature-profiles-quick-1775983539716.json +0 -85
- package/bench-results/signature-profiles-quick-1776237913190.json +0 -85
- package/bench-results/wire-codecs-full-1777891019803.json +0 -93
- package/bench-results/wire-codecs-full-1777896260964.json +0 -93
- package/bench-results/wire-codecs-full-1777901004247.json +0 -93
- package/bench-results/wire-codecs-quick-1775972879056.json +0 -93
- package/bench-results/wire-codecs-quick-1775983541111.json +0 -93
- package/bench-results/wire-codecs-quick-1776237914299.json +0 -93
- package/bench-results/wire-codecs-quick-1777841285236.json +0 -93
- package/bench-results/wire-codecs-quick-1777841321772.json +0 -93
- package/bench-results/wire-codecs-quick-1777841330408.json +0 -93
- package/bench-results/wire-codecs-quick-1777852886082.json +0 -93
- package/bench-results/wire-codecs-quick-1777852988773.json +0 -93
- package/bench-results/wire-codecs-quick-1777870188095.json +0 -93
- package/bench-results/wire-codecs-quick-1777870263918.json +0 -93
- package/bench-results/wire-codecs-quick-1777870455034.json +0 -93
- package/bench-results/wire-codecs-quick-1778816163081.json +0 -93
- package/bench-results/wire-codecs-quick-1778843936130.json +0 -93
- package/bin/7h3.ts +0 -406
- package/conformance/7h3_v0_1.json +0 -77
- package/conformance/7h3_v0_1_binary.json +0 -20
- package/conformance/aip_v0_1_binary.json +0 -20
- package/docker-compose.yaml +0 -77
- package/docs/ADOPTION_PLAN.md +0 -120
- package/docs/AGENTS.md +0 -77
- package/docs/AIP_RFC_v0.1.md +0 -97
- package/docs/AI_DECISION_CARD.md +0 -122
- package/docs/AI_RUNTIME_POLICY.json +0 -126
- package/docs/AI_RUNTIME_POLICY.yaml +0 -110
- package/docs/BACKPRESSURE_TUNING.md +0 -65
- package/docs/BENCHMARK_CLAIM_MATRIX.md +0 -42
- package/docs/BENCHMARK_REPORT_TEMPLATE.md +0 -169
- package/docs/BINARY_CODEC_BENCH.md +0 -23
- package/docs/CLEAN_CLONE_RUNBOOK.md +0 -36
- package/docs/CLOCK_SKEW_POLICY.md +0 -30
- package/docs/DISTRIBUTED_REPLAY.md +0 -142
- package/docs/FUZZ_CAMPAIGN.md +0 -121
- package/docs/GATEWAY.md +0 -195
- package/docs/KEY_MANAGEMENT_POLICY.md +0 -53
- package/docs/KEY_REVOCATION.md +0 -69
- package/docs/MCP_WRAPPER.md +0 -159
- package/docs/MIGRATION_GUIDE.md +0 -40
- package/docs/OPERATORS.md +0 -184
- package/docs/PERF_REGRESSION_POLICY.md +0 -34
- package/docs/PROJECT_EXAMINATION_2026-05-31.md +0 -219
- package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +0 -135
- package/docs/RELEASE_GATE.md +0 -25
- package/docs/RELEASE_NOTES_v0.1.0.md +0 -54
- package/docs/SECURITY_REVIEW_2026-06-05.md +0 -165
- package/docs/TELEMETRY.md +0 -41
- package/docs/THREAT_MODEL.md +0 -89
- package/docs/VERSIONING_POLICY.md +0 -30
- package/docs/assets/banner-github.png +0 -0
- package/docs/assets/banner.png +0 -0
- package/docs/assets/banner.svg +0 -123
- package/eslint.config.js +0 -15
- package/fuzz/ts/harness-decode.ts +0 -136
- package/fuzz/ts/harness-verify.ts +0 -121
- package/fuzz/ts/run.ts +0 -35
- package/mcp-server/README.md +0 -38
- package/mcp-server/package-lock.json +0 -1187
- package/mcp-server/package.json +0 -35
- package/mcp-server/src/index.ts +0 -236
- package/mcp-server/tsconfig.json +0 -14
- package/scripts/aip-framework-quickstart.ts +0 -110
- package/scripts/aip-mcp-gateway.ts +0 -38
- package/scripts/aip-mcp-wrap-demo.ts +0 -72
- package/scripts/aip-quickstart.ts +0 -60
- package/scripts/bench-diff.ts +0 -118
- package/scripts/bench-protocol-e2e.ts +0 -937
- package/scripts/bench-protocol-openloop.ts +0 -1397
- package/scripts/bench-replay-cache.ts +0 -76
- package/scripts/bench-signature-profiles.ts +0 -180
- package/scripts/bench-wire-codecs.ts +0 -161
- package/scripts/build-binary-conformance.ts +0 -36
- package/scripts/build-release-dashboard.ts +0 -175
- package/scripts/canary-rollout.ts +0 -38
- package/scripts/mcpGatewayCli.test.ts +0 -116
- package/scripts/prepare-aip-package.ts +0 -88
- package/scripts/regen-conformance-sigs.ts +0 -18
- package/scripts/release-gate.ts +0 -19
- package/scripts/validate-runtime-policy.ts +0 -18
- package/sdk/browser/index.test.ts +0 -162
- package/sdk/browser/index.ts +0 -257
- package/sdk/browser/package.json +0 -13
- package/sdk/go/cbor.go +0 -551
- package/sdk/go/cbor_test.go +0 -232
- package/sdk/go/encryption.go +0 -280
- package/sdk/go/encryption_test.go +0 -318
- package/sdk/go/go.mod +0 -7
- package/sdk/go/go.sum +0 -4
- package/sdk/go/http.go +0 -135
- package/sdk/go/protocol.go +0 -324
- package/sdk/go/protocol_test.go +0 -334
- package/sdk/go/replay.go +0 -121
- package/sdk/go/replay_test.go +0 -149
- package/sdk/go/webhook.go +0 -136
- package/sdk/pq/package-lock.json +0 -1358
- package/sdk/pq/package.json +0 -42
- package/sdk/pq/src/index.test.ts +0 -143
- package/sdk/pq/src/index.ts +0 -166
- package/sdk/pq/tsconfig.json +0 -14
- package/sdk/pq/vitest.config.ts +0 -7
- package/sdk/python/README.md +0 -18
- package/sdk/python/protocol_7h3/__init__.py +0 -46
- package/sdk/python/protocol_7h3/encryption.py +0 -252
- package/sdk/python/protocol_7h3/http.py +0 -212
- package/sdk/python/protocol_7h3/keys.py +0 -149
- package/sdk/python/protocol_7h3/pq.py +0 -244
- package/sdk/python/protocol_7h3/protocol.py +0 -525
- package/sdk/python/protocol_7h3/queue.py +0 -118
- package/sdk/python/protocol_7h3/replay.py +0 -98
- package/sdk/python/protocol_7h3/webhook.py +0 -116
- package/sdk/python/pyproject.toml +0 -40
- package/sdk/python/tests/test_conformance.py +0 -110
- package/sdk/python/tests/test_encryption.py +0 -206
- package/sdk/python/tests/test_http.py +0 -305
- package/sdk/python/tests/test_keys.py +0 -417
- package/sdk/python/tests/test_queue.py +0 -120
- package/sdk/python/tests/test_webhook.py +0 -345
- package/sdk/rust/Cargo.lock +0 -371
- package/sdk/rust/Cargo.toml +0 -25
- package/sdk/rust/README.md +0 -31
- package/sdk/rust/fuzz/Cargo.toml +0 -29
- package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +0 -46
- package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +0 -11
- package/sdk/rust/src/bin/aip_mcp_gateway.rs +0 -59
- package/sdk/rust/src/http.rs +0 -145
- package/sdk/rust/src/keys.rs +0 -161
- package/sdk/rust/src/lib.rs +0 -688
- package/sdk/rust/src/queue.rs +0 -79
- package/sdk/rust/src/webhook.rs +0 -86
- package/sdk/rust/tests/conformance.rs +0 -148
- package/sdk/rust/tests/gateway.rs +0 -130
- package/sdk/rust/tests/http_webhook_queue.rs +0 -201
- package/sdk/rust/tests/keys.rs +0 -189
- package/sdk/threshold/index.d.ts +0 -68
- package/sdk/threshold/index.d.ts.map +0 -1
- package/sdk/threshold/index.js +0 -254
- package/sdk/threshold/package-lock.json +0 -1361
- package/sdk/threshold/package.json +0 -39
- package/sdk/threshold/src/index.d.ts +0 -68
- package/sdk/threshold/src/index.d.ts.map +0 -1
- package/sdk/threshold/src/index.js +0 -254
- package/sdk/threshold/src/index.test.ts +0 -238
- package/sdk/threshold/src/index.ts +0 -355
- package/sdk/threshold/tsconfig.json +0 -19
- package/sdk/threshold/vitest.config.ts +0 -12
- package/src/agentAdapter.test.ts +0 -48
- package/src/agentAdapter.ts +0 -56
- package/src/auditLog.test.ts +0 -145
- package/src/auditLog.ts +0 -147
- package/src/capability.test.ts +0 -504
- package/src/capability.ts +0 -380
- package/src/cborCodec.test.ts +0 -263
- package/src/cborCodec.ts +0 -339
- package/src/conformance.test.ts +0 -136
- package/src/conformanceVectors.ts +0 -99
- package/src/encryption.test.ts +0 -206
- package/src/encryption.ts +0 -245
- package/src/envelopeCbor.ts +0 -140
- package/src/frameworkAdapters.test.ts +0 -290
- package/src/frameworkAdapters.ts +0 -261
- package/src/gateway.test.ts +0 -343
- package/src/gateway.ts +0 -246
- package/src/grpcBinding.test.ts +0 -211
- package/src/grpcBinding.ts +0 -103
- package/src/httpBinding.test.ts +0 -376
- package/src/httpBinding.ts +0 -189
- package/src/index.ts +0 -39
- package/src/keyInfra.test.ts +0 -278
- package/src/keyInfra.ts +0 -228
- package/src/keyRegistry.ts +0 -59
- package/src/keyRotation.test.ts +0 -78
- package/src/keyRotation.ts +0 -72
- package/src/mcpGateway.test.ts +0 -129
- package/src/mcpGateway.ts +0 -250
- package/src/mcpTransports.test.ts +0 -92
- package/src/mcpTransports.ts +0 -169
- package/src/mcpWrapper.test.ts +0 -179
- package/src/mcpWrapper.ts +0 -206
- package/src/otel.ts +0 -136
- package/src/policyEnforcer.test.ts +0 -99
- package/src/policyEnforcer.ts +0 -169
- package/src/policyTelemetryFeedback.test.ts +0 -25
- package/src/policyTelemetryFeedback.ts +0 -38
- package/src/protocol.bench.ts +0 -37
- package/src/protocol.d.ts.map +0 -1
- package/src/protocol.js +0 -294
- package/src/protocol.test.ts +0 -155
- package/src/protocol.ts +0 -414
- package/src/protocolAgent.test.ts +0 -105
- package/src/protocolAgent.ts +0 -169
- package/src/protocolBinary.test.ts +0 -165
- package/src/protocolBinary.ts +0 -312
- package/src/protocolCapabilities.ts +0 -70
- package/src/protocolFuzz.advanced.test.ts +0 -235
- package/src/protocolFuzz.test.ts +0 -111
- package/src/protocolNegative.test.ts +0 -97
- package/src/protocolReplay.test.ts +0 -71
- package/src/protocolReplay.ts +0 -194
- package/src/protocolTransport.test.ts +0 -556
- package/src/protocolTransport.ts +0 -483
- package/src/queueBinding.test.ts +0 -130
- package/src/queueBinding.ts +0 -102
- package/src/rateLimiter.test.ts +0 -96
- package/src/rateLimiter.ts +0 -46
- package/src/redisIntegration.test.ts +0 -134
- package/src/replayStores.test.ts +0 -273
- package/src/replayStores.ts +0 -215
- package/src/revocation.test.ts +0 -98
- package/src/revocation.ts +0 -0
- package/src/routePolicy.test.ts +0 -87
- package/src/routePolicy.ts +0 -72
- package/src/runtimePolicy.test.ts +0 -49
- package/src/runtimePolicy.ts +0 -81
- package/src/runtimePolicyManager.test.ts +0 -29
- package/src/runtimePolicyManager.ts +0 -50
- package/src/runtimePolicyPresets.ts +0 -43
- package/src/signedResponse.test.ts +0 -111
- package/src/signedResponse.ts +0 -83
- package/src/stream.test.ts +0 -254
- package/src/stream.ts +0 -417
- package/src/telemetry.test.ts +0 -251
- package/src/telemetry.ts +0 -299
- package/src/webhookBinding.test.ts +0 -144
- package/src/webhookBinding.ts +0 -115
- package/src/wsBinding.test.ts +0 -221
- package/src/wsBinding.ts +0 -200
- package/tsconfig.json +0 -15
- package/tsconfig.lib.json +0 -23
- package/vite.lib.config.ts +0 -16
- package/vitest.config.ts +0 -11
|
@@ -1,937 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises'
|
|
2
|
-
import http2 from 'node:http2'
|
|
3
|
-
import { performance } from 'node:perf_hooks'
|
|
4
|
-
import process from 'node:process'
|
|
5
|
-
import { WebSocket, WebSocketServer } from 'ws'
|
|
6
|
-
import {
|
|
7
|
-
canonicalizeEnvelope,
|
|
8
|
-
signCanonicalPayloadHmac,
|
|
9
|
-
type ProtocolEnvelope,
|
|
10
|
-
type ProtocolHeader,
|
|
11
|
-
} from '../src/protocol'
|
|
12
|
-
import { InMemoryReplayCache } from '../src/protocolReplay'
|
|
13
|
-
import { InMemoryVerificationMaterialCache, decodeEnvelope, encodeEnvelope, receiveEnvelope } from '../src/protocolTransport'
|
|
14
|
-
|
|
15
|
-
type ProfileName = 'quick' | 'full'
|
|
16
|
-
type Mode = 'inproc' | 'http' | 'ws' | 'agent-loop'
|
|
17
|
-
|
|
18
|
-
interface Profile {
|
|
19
|
-
warmupMs: number
|
|
20
|
-
measureMs: number
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface Scenario {
|
|
24
|
-
mode: Mode
|
|
25
|
-
payloadBytes: number
|
|
26
|
-
concurrency: number
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface StageTotals {
|
|
30
|
-
buildMs: number
|
|
31
|
-
canonicalizeMs: number
|
|
32
|
-
macMs: number
|
|
33
|
-
queueMs: number
|
|
34
|
-
transportMs: number
|
|
35
|
-
verifyMs: number
|
|
36
|
-
decodeMs: number
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface ScenarioResult {
|
|
40
|
-
mode: Mode
|
|
41
|
-
payloadBytes: number
|
|
42
|
-
concurrency: number
|
|
43
|
-
ops: number
|
|
44
|
-
opsPerSecond: number
|
|
45
|
-
p50Ms: number
|
|
46
|
-
p95Ms: number
|
|
47
|
-
p99Ms: number
|
|
48
|
-
cpuMsPer10kOps: number
|
|
49
|
-
heapDeltaMb: number
|
|
50
|
-
rssDeltaMb: number
|
|
51
|
-
allocBytesPerOp: number
|
|
52
|
-
stageBuildMsPerOp: number
|
|
53
|
-
stageCanonicalizeMsPerOp: number
|
|
54
|
-
stageMacMsPerOp: number
|
|
55
|
-
stageQueueMsPerOp: number
|
|
56
|
-
stageTransportMsPerOp: number
|
|
57
|
-
stageVerifyMsPerOp: number
|
|
58
|
-
stageDecodeMsPerOp: number
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
interface TransportTrace {
|
|
62
|
-
queueMs: number
|
|
63
|
-
transportMs: number
|
|
64
|
-
verifyMs: number
|
|
65
|
-
decodeMs: number
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
interface TransportAdapter {
|
|
69
|
-
start(): Promise<void>
|
|
70
|
-
send(rawEnvelope: string): Promise<TransportTrace>
|
|
71
|
-
stop(): Promise<void>
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface BenchEnvelopeTemplate {
|
|
75
|
-
headerBase: Omit<ProtocolHeader, 'messageId' | 'timestampMs' | 'nonce'>
|
|
76
|
-
intent: 'TASK'
|
|
77
|
-
capability: string
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
interface ServerAck {
|
|
81
|
-
ok: boolean
|
|
82
|
-
queueMs: number
|
|
83
|
-
verifyMs: number
|
|
84
|
-
decodeMs: number
|
|
85
|
-
error?: string
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const ACK_HEADER_OK = 'x-aip-ok'
|
|
89
|
-
const ACK_HEADER_QUEUE_MS = 'x-aip-queue-ms'
|
|
90
|
-
const ACK_HEADER_VERIFY_MS = 'x-aip-verify-ms'
|
|
91
|
-
const ACK_HEADER_DECODE_MS = 'x-aip-decode-ms'
|
|
92
|
-
|
|
93
|
-
const SHARED_SECRET = 'bench-shared-secret'
|
|
94
|
-
const SHARED_SECRET_RESOLVER = async (): Promise<string> => SHARED_SECRET
|
|
95
|
-
const PROFILES: Record<ProfileName, Profile> = {
|
|
96
|
-
quick: {
|
|
97
|
-
warmupMs: 300,
|
|
98
|
-
measureMs: 1500,
|
|
99
|
-
},
|
|
100
|
-
full: {
|
|
101
|
-
warmupMs: 1200,
|
|
102
|
-
measureMs: 8000,
|
|
103
|
-
},
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const PAYLOAD_SIZES = [256, 1024, 4096, 16384]
|
|
107
|
-
const CONCURRENCY_LEVELS = [1, 10, 100, 1000]
|
|
108
|
-
const MAX_LATENCY_SAMPLES = 200_000
|
|
109
|
-
|
|
110
|
-
function parseProfile(argv: string[]): ProfileName {
|
|
111
|
-
const idx = argv.indexOf('--profile')
|
|
112
|
-
if (idx >= 0) {
|
|
113
|
-
const value = argv[idx + 1]
|
|
114
|
-
if (value === 'quick' || value === 'full') return value
|
|
115
|
-
}
|
|
116
|
-
return 'quick'
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function parseCsvArg(flag: string, argv: string[]): string[] {
|
|
120
|
-
const idx = argv.indexOf(flag)
|
|
121
|
-
if (idx < 0) return []
|
|
122
|
-
const raw = argv[idx + 1]
|
|
123
|
-
if (!raw) return []
|
|
124
|
-
return raw
|
|
125
|
-
.split(',')
|
|
126
|
-
.map((part) => part.trim())
|
|
127
|
-
.filter((part) => part.length > 0)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function parseModeSelection(argv: string[], fallback: Mode[]): Mode[] {
|
|
131
|
-
const rawModes = parseCsvArg('--modes', argv)
|
|
132
|
-
if (rawModes.length === 0) return fallback
|
|
133
|
-
const allowed = new Set<Mode>(['inproc', 'http', 'ws', 'agent-loop'])
|
|
134
|
-
const parsed = rawModes.filter((raw): raw is Mode => allowed.has(raw as Mode))
|
|
135
|
-
return parsed.length > 0 ? parsed : fallback
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function parseNumberSelection(argv: string[], flag: string, fallback: number[]): number[] {
|
|
139
|
-
const rawValues = parseCsvArg(flag, argv)
|
|
140
|
-
if (rawValues.length === 0) return fallback
|
|
141
|
-
const parsed = rawValues
|
|
142
|
-
.map((raw) => Number(raw))
|
|
143
|
-
.filter((value) => Number.isFinite(value) && value > 0)
|
|
144
|
-
.map((value) => Math.floor(value))
|
|
145
|
-
return parsed.length > 0 ? parsed : fallback
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function payloadOfSize(bytes: number): string {
|
|
149
|
-
if (bytes <= 0) return ''
|
|
150
|
-
return 'x'.repeat(bytes)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function percentile(sorted: number[], p: number): number {
|
|
154
|
-
if (sorted.length === 0) return 0
|
|
155
|
-
const index = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length))
|
|
156
|
-
return sorted[index] ?? 0
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function round3(value: number): number {
|
|
160
|
-
return Number(value.toFixed(3))
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function nowMs(): number {
|
|
164
|
-
return Date.now()
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function maybeGc(): void {
|
|
168
|
-
const withGc = globalThis as unknown as { gc?: () => void }
|
|
169
|
-
withGc.gc?.()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function emptyStageTotals(): StageTotals {
|
|
173
|
-
return {
|
|
174
|
-
buildMs: 0,
|
|
175
|
-
canonicalizeMs: 0,
|
|
176
|
-
macMs: 0,
|
|
177
|
-
queueMs: 0,
|
|
178
|
-
transportMs: 0,
|
|
179
|
-
verifyMs: 0,
|
|
180
|
-
decodeMs: 0,
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function addStageTotals(target: StageTotals, delta: TransportTrace & { buildMs: number; canonicalizeMs: number; macMs: number }): void {
|
|
185
|
-
target.buildMs += delta.buildMs
|
|
186
|
-
target.canonicalizeMs += delta.canonicalizeMs
|
|
187
|
-
target.macMs += delta.macMs
|
|
188
|
-
target.queueMs += delta.queueMs
|
|
189
|
-
target.transportMs += delta.transportMs
|
|
190
|
-
target.verifyMs += delta.verifyMs
|
|
191
|
-
target.decodeMs += delta.decodeMs
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function buildEnvelope(template: BenchEnvelopeTemplate, payload: string, sequence: number): Omit<ProtocolEnvelope, 'signature'> {
|
|
195
|
-
return {
|
|
196
|
-
header: {
|
|
197
|
-
...template.headerBase,
|
|
198
|
-
messageId: `m-${sequence}`,
|
|
199
|
-
timestampMs: nowMs(),
|
|
200
|
-
nonce: `n-${sequence}`,
|
|
201
|
-
},
|
|
202
|
-
body: {
|
|
203
|
-
intent: template.intent,
|
|
204
|
-
content: payload,
|
|
205
|
-
capability: template.capability,
|
|
206
|
-
correlationId: `corr-${sequence}`,
|
|
207
|
-
},
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function parseServerAck(raw: string): ServerAck {
|
|
212
|
-
const parsed = JSON.parse(raw) as Partial<ServerAck>
|
|
213
|
-
return {
|
|
214
|
-
ok: parsed.ok === true,
|
|
215
|
-
queueMs: parsed.queueMs ?? 0,
|
|
216
|
-
verifyMs: parsed.verifyMs ?? 0,
|
|
217
|
-
decodeMs: parsed.decodeMs ?? 0,
|
|
218
|
-
error: parsed.error,
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
class InProcessAdapter implements TransportAdapter {
|
|
223
|
-
private readonly replayCache = new InMemoryReplayCache(1_000_000)
|
|
224
|
-
private readonly verificationMaterialCache = new InMemoryVerificationMaterialCache()
|
|
225
|
-
|
|
226
|
-
async start(): Promise<void> {}
|
|
227
|
-
|
|
228
|
-
async send(rawEnvelope: string): Promise<TransportTrace> {
|
|
229
|
-
const queueStart = performance.now()
|
|
230
|
-
await new Promise<void>((resolve) => setImmediate(resolve))
|
|
231
|
-
const queueMs = performance.now() - queueStart
|
|
232
|
-
|
|
233
|
-
const decodeStart = performance.now()
|
|
234
|
-
const decoded = decodeEnvelope(rawEnvelope)
|
|
235
|
-
const decodeMs = performance.now() - decodeStart
|
|
236
|
-
if (!decoded.ok || !decoded.envelope) {
|
|
237
|
-
throw new Error(decoded.diagnostics.map((d) => d.message).join('; '))
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const verifyStart = performance.now()
|
|
241
|
-
const result = await receiveEnvelope(decoded.envelope, {
|
|
242
|
-
nowMs: nowMs(),
|
|
243
|
-
replayCache: this.replayCache,
|
|
244
|
-
verificationMaterialCache: this.verificationMaterialCache,
|
|
245
|
-
secretResolver: SHARED_SECRET_RESOLVER,
|
|
246
|
-
})
|
|
247
|
-
const verifyMs = performance.now() - verifyStart
|
|
248
|
-
if (!result.ok) {
|
|
249
|
-
throw new Error(result.diagnostics.map((d) => d.message).join('; '))
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
queueMs,
|
|
254
|
-
transportMs: 0,
|
|
255
|
-
verifyMs,
|
|
256
|
-
decodeMs,
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async stop(): Promise<void> {}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
class HttpAdapter implements TransportAdapter {
|
|
264
|
-
private readonly replayCache = new InMemoryReplayCache(1_000_000)
|
|
265
|
-
private readonly verificationMaterialCache = new InMemoryVerificationMaterialCache()
|
|
266
|
-
private server: http2.Http2Server | null = null
|
|
267
|
-
private client: http2.ClientHttp2Session | null = null
|
|
268
|
-
private port = 0
|
|
269
|
-
private readonly fastAck: boolean
|
|
270
|
-
|
|
271
|
-
constructor(fastAck = true) {
|
|
272
|
-
this.fastAck = fastAck
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async start(): Promise<void> {
|
|
276
|
-
this.server = http2.createServer({
|
|
277
|
-
settings: {
|
|
278
|
-
maxConcurrentStreams: 4096,
|
|
279
|
-
},
|
|
280
|
-
})
|
|
281
|
-
this.server.on('stream', (stream, headers) => {
|
|
282
|
-
if (headers[':method'] !== 'POST') {
|
|
283
|
-
stream.respond({ ':status': 405 })
|
|
284
|
-
stream.end()
|
|
285
|
-
return
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const queueStart = performance.now()
|
|
289
|
-
const chunks: Buffer[] = []
|
|
290
|
-
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)))
|
|
291
|
-
stream.on('end', async () => {
|
|
292
|
-
const queueMs = performance.now() - queueStart
|
|
293
|
-
const raw = Buffer.concat(chunks).toString('utf8')
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
const decodeStart = performance.now()
|
|
297
|
-
const decoded = decodeEnvelope(raw)
|
|
298
|
-
const decodeMs = performance.now() - decodeStart
|
|
299
|
-
if (!decoded.ok || !decoded.envelope) {
|
|
300
|
-
stream.respond({ ':status': 400, 'content-type': 'application/json' })
|
|
301
|
-
stream.end(JSON.stringify({ ok: false, queueMs, decodeMs, verifyMs: 0, error: 'decode failed' }))
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const verifyStart = performance.now()
|
|
306
|
-
const result = await receiveEnvelope(decoded.envelope, {
|
|
307
|
-
nowMs: nowMs(),
|
|
308
|
-
replayCache: this.replayCache,
|
|
309
|
-
verificationMaterialCache: this.verificationMaterialCache,
|
|
310
|
-
secretResolver: SHARED_SECRET_RESOLVER,
|
|
311
|
-
})
|
|
312
|
-
const verifyMs = performance.now() - verifyStart
|
|
313
|
-
const ack: ServerAck = {
|
|
314
|
-
ok: result.ok,
|
|
315
|
-
queueMs,
|
|
316
|
-
decodeMs,
|
|
317
|
-
verifyMs,
|
|
318
|
-
error: result.ok ? undefined : result.diagnostics.map((d) => d.message).join('; '),
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (result.ok && this.fastAck) {
|
|
322
|
-
stream.respond({
|
|
323
|
-
':status': 204,
|
|
324
|
-
[ACK_HEADER_OK]: '1',
|
|
325
|
-
[ACK_HEADER_QUEUE_MS]: String(queueMs),
|
|
326
|
-
[ACK_HEADER_VERIFY_MS]: String(verifyMs),
|
|
327
|
-
[ACK_HEADER_DECODE_MS]: String(decodeMs),
|
|
328
|
-
})
|
|
329
|
-
stream.end()
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
stream.respond({ ':status': result.ok ? 200 : 400, 'content-type': 'application/json' })
|
|
334
|
-
stream.end(JSON.stringify(ack))
|
|
335
|
-
} catch {
|
|
336
|
-
stream.respond({ ':status': 500, 'content-type': 'application/json' })
|
|
337
|
-
stream.end(JSON.stringify({ ok: false, queueMs, decodeMs: 0, verifyMs: 0, error: 'internal http receive error' }))
|
|
338
|
-
}
|
|
339
|
-
})
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
await new Promise<void>((resolve, reject) => {
|
|
343
|
-
this.server?.once('error', reject)
|
|
344
|
-
this.server?.listen(0, '127.0.0.1', () => resolve())
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
const address = this.server.address()
|
|
348
|
-
if (!address || typeof address === 'string') {
|
|
349
|
-
throw new Error('Could not resolve HTTP server address')
|
|
350
|
-
}
|
|
351
|
-
this.port = address.port
|
|
352
|
-
|
|
353
|
-
this.client = http2.connect(`http://127.0.0.1:${this.port}`)
|
|
354
|
-
await new Promise<void>((resolve, reject) => {
|
|
355
|
-
const client = this.client
|
|
356
|
-
if (!client) {
|
|
357
|
-
reject(new Error('Failed to initialize HTTP/2 client'))
|
|
358
|
-
return
|
|
359
|
-
}
|
|
360
|
-
client.once('error', reject)
|
|
361
|
-
client.once('connect', () => {
|
|
362
|
-
client.off('error', reject)
|
|
363
|
-
resolve()
|
|
364
|
-
})
|
|
365
|
-
})
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async send(rawEnvelope: string): Promise<TransportTrace> {
|
|
369
|
-
const transportStart = performance.now()
|
|
370
|
-
|
|
371
|
-
if (!this.client) {
|
|
372
|
-
throw new Error('HTTP/2 client not started')
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const response = await new Promise<{ body: string; headers: http2.IncomingHttpHeaders }>((resolve, reject) => {
|
|
376
|
-
const request = this.client!.request({
|
|
377
|
-
':method': 'POST',
|
|
378
|
-
':path': '/envelope',
|
|
379
|
-
'content-type': 'application/json',
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
let statusCode = 500
|
|
383
|
-
let responseHeaders: http2.IncomingHttpHeaders = {}
|
|
384
|
-
const chunks: Buffer[] = []
|
|
385
|
-
|
|
386
|
-
request.on('response', (headers) => {
|
|
387
|
-
const code = headers[':status']
|
|
388
|
-
statusCode = typeof code === 'number' ? code : Number(code ?? 500)
|
|
389
|
-
responseHeaders = headers
|
|
390
|
-
})
|
|
391
|
-
request.on('data', (chunk) => chunks.push(Buffer.from(chunk)))
|
|
392
|
-
request.on('end', () => {
|
|
393
|
-
const body = Buffer.concat(chunks).toString('utf8')
|
|
394
|
-
if (statusCode >= 400) {
|
|
395
|
-
reject(new Error(`HTTP status ${statusCode}: ${body}`))
|
|
396
|
-
return
|
|
397
|
-
}
|
|
398
|
-
resolve({ body, headers: responseHeaders })
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
request.on('error', reject)
|
|
402
|
-
request.end(rawEnvelope)
|
|
403
|
-
})
|
|
404
|
-
const transportMs = performance.now() - transportStart
|
|
405
|
-
|
|
406
|
-
const ack = this.fastAck && readHeaderString(response.headers, ACK_HEADER_OK) === '1' ? ackFromHeaders(response.headers) : parseServerAck(response.body)
|
|
407
|
-
if (!ack.ok) {
|
|
408
|
-
throw new Error(ack.error ?? 'HTTP adapter received non-ok response')
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
return {
|
|
412
|
-
queueMs: ack.queueMs,
|
|
413
|
-
transportMs,
|
|
414
|
-
verifyMs: ack.verifyMs,
|
|
415
|
-
decodeMs: ack.decodeMs,
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
async stop(): Promise<void> {
|
|
420
|
-
if (this.client) {
|
|
421
|
-
await new Promise<void>((resolve) => {
|
|
422
|
-
const client = this.client
|
|
423
|
-
if (!client) {
|
|
424
|
-
resolve()
|
|
425
|
-
return
|
|
426
|
-
}
|
|
427
|
-
client.once('close', () => resolve())
|
|
428
|
-
client.close()
|
|
429
|
-
})
|
|
430
|
-
this.client = null
|
|
431
|
-
}
|
|
432
|
-
if (!this.server) return
|
|
433
|
-
await new Promise<void>((resolve, reject) => {
|
|
434
|
-
this.server?.close((error) => (error ? reject(error) : resolve()))
|
|
435
|
-
})
|
|
436
|
-
this.server = null
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function readHeaderString(headers: http2.IncomingHttpHeaders, key: string): string | undefined {
|
|
441
|
-
const value = headers[key]
|
|
442
|
-
if (typeof value === 'string') return value
|
|
443
|
-
if (typeof value === 'number') return String(value)
|
|
444
|
-
if (Array.isArray(value)) {
|
|
445
|
-
const first = value[0]
|
|
446
|
-
return typeof first === 'string' ? first : undefined
|
|
447
|
-
}
|
|
448
|
-
return undefined
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function readHeaderNumber(headers: http2.IncomingHttpHeaders, key: string): number {
|
|
452
|
-
const raw = readHeaderString(headers, key)
|
|
453
|
-
if (!raw) return 0
|
|
454
|
-
const parsed = Number(raw)
|
|
455
|
-
return Number.isFinite(parsed) ? parsed : 0
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function ackFromHeaders(headers: http2.IncomingHttpHeaders): ServerAck {
|
|
459
|
-
return {
|
|
460
|
-
ok: readHeaderString(headers, ACK_HEADER_OK) === '1',
|
|
461
|
-
queueMs: readHeaderNumber(headers, ACK_HEADER_QUEUE_MS),
|
|
462
|
-
verifyMs: readHeaderNumber(headers, ACK_HEADER_VERIFY_MS),
|
|
463
|
-
decodeMs: readHeaderNumber(headers, ACK_HEADER_DECODE_MS),
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
class WsAdapter implements TransportAdapter {
|
|
468
|
-
private readonly replayCache = new InMemoryReplayCache(1_000_000)
|
|
469
|
-
private readonly verificationMaterialCache = new InMemoryVerificationMaterialCache()
|
|
470
|
-
private server: WebSocketServer | null = null
|
|
471
|
-
private client: WebSocket | null = null
|
|
472
|
-
private nextId = 1
|
|
473
|
-
private readonly pending = new Map<number, { resolve: (trace: TransportTrace) => void; reject: (error: Error) => void }>()
|
|
474
|
-
|
|
475
|
-
async start(): Promise<void> {
|
|
476
|
-
this.server = new WebSocketServer({ port: 0, host: '127.0.0.1' })
|
|
477
|
-
|
|
478
|
-
this.server.on('connection', (socket) => {
|
|
479
|
-
socket.on('message', async (data) => {
|
|
480
|
-
const queueStart = performance.now()
|
|
481
|
-
let message: { id: number; payload: string }
|
|
482
|
-
try {
|
|
483
|
-
message = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data)) as {
|
|
484
|
-
id: number
|
|
485
|
-
payload: string
|
|
486
|
-
}
|
|
487
|
-
} catch {
|
|
488
|
-
socket.send(Buffer.from(JSON.stringify({ id: 0, ok: false, error: 'invalid frame' }), 'utf8'))
|
|
489
|
-
return
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const queueMs = performance.now() - queueStart
|
|
493
|
-
try {
|
|
494
|
-
const decodeStart = performance.now()
|
|
495
|
-
const decoded = decodeEnvelope(message.payload)
|
|
496
|
-
const decodeMs = performance.now() - decodeStart
|
|
497
|
-
if (!decoded.ok || !decoded.envelope) {
|
|
498
|
-
socket.send(
|
|
499
|
-
Buffer.from(
|
|
500
|
-
JSON.stringify({ id: message.id, ok: false, queueMs, decodeMs, verifyMs: 0, error: 'decode failed' }),
|
|
501
|
-
'utf8',
|
|
502
|
-
),
|
|
503
|
-
)
|
|
504
|
-
return
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const verifyStart = performance.now()
|
|
508
|
-
const result = await receiveEnvelope(decoded.envelope, {
|
|
509
|
-
nowMs: nowMs(),
|
|
510
|
-
replayCache: this.replayCache,
|
|
511
|
-
verificationMaterialCache: this.verificationMaterialCache,
|
|
512
|
-
secretResolver: SHARED_SECRET_RESOLVER,
|
|
513
|
-
})
|
|
514
|
-
const verifyMs = performance.now() - verifyStart
|
|
515
|
-
|
|
516
|
-
socket.send(
|
|
517
|
-
Buffer.from(
|
|
518
|
-
JSON.stringify({
|
|
519
|
-
id: message.id,
|
|
520
|
-
ok: result.ok,
|
|
521
|
-
queueMs,
|
|
522
|
-
decodeMs,
|
|
523
|
-
verifyMs,
|
|
524
|
-
error: result.ok ? undefined : result.diagnostics.map((d) => d.message).join('; '),
|
|
525
|
-
}),
|
|
526
|
-
'utf8',
|
|
527
|
-
),
|
|
528
|
-
)
|
|
529
|
-
} catch {
|
|
530
|
-
socket.send(
|
|
531
|
-
Buffer.from(
|
|
532
|
-
JSON.stringify({ id: message.id, ok: false, queueMs, decodeMs: 0, verifyMs: 0, error: 'internal ws receive error' }),
|
|
533
|
-
'utf8',
|
|
534
|
-
),
|
|
535
|
-
)
|
|
536
|
-
}
|
|
537
|
-
})
|
|
538
|
-
})
|
|
539
|
-
|
|
540
|
-
const port = await new Promise<number>((resolve, reject) => {
|
|
541
|
-
this.server?.once('error', reject)
|
|
542
|
-
this.server?.once('listening', () => {
|
|
543
|
-
const address = this.server?.address()
|
|
544
|
-
if (!address || typeof address === 'string') {
|
|
545
|
-
reject(new Error('Could not resolve WS server address'))
|
|
546
|
-
return
|
|
547
|
-
}
|
|
548
|
-
resolve(address.port)
|
|
549
|
-
})
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
this.client = await new Promise<WebSocket>((resolve, reject) => {
|
|
553
|
-
const socket = new WebSocket(`ws://127.0.0.1:${port}`)
|
|
554
|
-
socket.once('open', () => resolve(socket))
|
|
555
|
-
socket.once('error', reject)
|
|
556
|
-
})
|
|
557
|
-
|
|
558
|
-
this.client.on('message', (data) => {
|
|
559
|
-
let frame: { id: number; ok: boolean; queueMs: number; verifyMs: number; decodeMs: number; error?: string }
|
|
560
|
-
try {
|
|
561
|
-
frame = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data)) as {
|
|
562
|
-
id: number
|
|
563
|
-
ok: boolean
|
|
564
|
-
queueMs: number
|
|
565
|
-
verifyMs: number
|
|
566
|
-
decodeMs: number
|
|
567
|
-
error?: string
|
|
568
|
-
}
|
|
569
|
-
} catch {
|
|
570
|
-
return
|
|
571
|
-
}
|
|
572
|
-
const pending = this.pending.get(frame.id)
|
|
573
|
-
if (!pending) return
|
|
574
|
-
this.pending.delete(frame.id)
|
|
575
|
-
if (!frame.ok) {
|
|
576
|
-
pending.reject(new Error(frame.error ?? 'ws adapter received non-ok response'))
|
|
577
|
-
return
|
|
578
|
-
}
|
|
579
|
-
pending.resolve({
|
|
580
|
-
queueMs: frame.queueMs,
|
|
581
|
-
transportMs: 0,
|
|
582
|
-
verifyMs: frame.verifyMs,
|
|
583
|
-
decodeMs: frame.decodeMs,
|
|
584
|
-
})
|
|
585
|
-
})
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
async send(rawEnvelope: string): Promise<TransportTrace> {
|
|
589
|
-
if (!this.client || this.client.readyState !== WebSocket.OPEN) {
|
|
590
|
-
throw new Error('WebSocket client is not open')
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const id = this.nextId
|
|
594
|
-
this.nextId += 1
|
|
595
|
-
const sendStart = performance.now()
|
|
596
|
-
|
|
597
|
-
const completion = new Promise<TransportTrace>((resolve, reject) => {
|
|
598
|
-
this.pending.set(id, { resolve, reject })
|
|
599
|
-
})
|
|
600
|
-
|
|
601
|
-
this.client.send(Buffer.from(JSON.stringify({ id, payload: rawEnvelope }), 'utf8'))
|
|
602
|
-
const trace = await completion
|
|
603
|
-
return {
|
|
604
|
-
...trace,
|
|
605
|
-
transportMs: performance.now() - sendStart,
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
async stop(): Promise<void> {
|
|
610
|
-
if (this.client) {
|
|
611
|
-
await new Promise<void>((resolve) => {
|
|
612
|
-
this.client?.once('close', () => resolve())
|
|
613
|
-
this.client?.close()
|
|
614
|
-
})
|
|
615
|
-
this.client = null
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
if (this.server) {
|
|
619
|
-
await new Promise<void>((resolve, reject) => {
|
|
620
|
-
this.server?.close((error) => (error ? reject(error) : resolve()))
|
|
621
|
-
})
|
|
622
|
-
this.server = null
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
async function runAgentLoop(payload: string): Promise<void> {
|
|
628
|
-
const input = payload
|
|
629
|
-
const plan = `plan:${input.length}`
|
|
630
|
-
const toolResult = await Promise.resolve(plan.length + input.length)
|
|
631
|
-
const memoryUpdate = `${plan}:${toolResult}`
|
|
632
|
-
const response = `ok:${memoryUpdate}`
|
|
633
|
-
if (response.length === 0) {
|
|
634
|
-
throw new Error('unexpected empty response')
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
async function buildSignAndEncodeEnvelope(
|
|
639
|
-
template: BenchEnvelopeTemplate,
|
|
640
|
-
payload: string,
|
|
641
|
-
sequence: number,
|
|
642
|
-
): Promise<{
|
|
643
|
-
raw: string
|
|
644
|
-
buildMs: number
|
|
645
|
-
canonicalizeMs: number
|
|
646
|
-
macMs: number
|
|
647
|
-
}> {
|
|
648
|
-
const buildStart = performance.now()
|
|
649
|
-
const unsigned = buildEnvelope(template, payload, sequence)
|
|
650
|
-
const buildMs = performance.now() - buildStart
|
|
651
|
-
|
|
652
|
-
const canonicalizeStart = performance.now()
|
|
653
|
-
const canonicalPayload = canonicalizeEnvelope(unsigned)
|
|
654
|
-
const canonicalizeMs = performance.now() - canonicalizeStart
|
|
655
|
-
|
|
656
|
-
const macStart = performance.now()
|
|
657
|
-
const signature = await signCanonicalPayloadHmac(canonicalPayload, SHARED_SECRET)
|
|
658
|
-
const macMs = performance.now() - macStart
|
|
659
|
-
|
|
660
|
-
const signedEnvelope: ProtocolEnvelope = {
|
|
661
|
-
...unsigned,
|
|
662
|
-
signature: {
|
|
663
|
-
alg: 'HS256',
|
|
664
|
-
keyId: 'bench-k1',
|
|
665
|
-
value: signature,
|
|
666
|
-
},
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
return {
|
|
670
|
-
raw: encodeEnvelope(signedEnvelope, 'compact'),
|
|
671
|
-
buildMs,
|
|
672
|
-
canonicalizeMs,
|
|
673
|
-
macMs,
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
async function runPhase(options: {
|
|
678
|
-
durationMs: number
|
|
679
|
-
concurrency: number
|
|
680
|
-
payload: string
|
|
681
|
-
execute: (payload: string) => Promise<TransportTrace & { buildMs: number; canonicalizeMs: number; macMs: number }>
|
|
682
|
-
collectLatencies: boolean
|
|
683
|
-
}): Promise<{ latencies: number[]; completed: number; stages: StageTotals }> {
|
|
684
|
-
const latencies: number[] = []
|
|
685
|
-
const stages = emptyStageTotals()
|
|
686
|
-
const deadline = performance.now() + options.durationMs
|
|
687
|
-
let inFlight = 0
|
|
688
|
-
let completed = 0
|
|
689
|
-
|
|
690
|
-
return new Promise((resolve, reject) => {
|
|
691
|
-
let settled = false
|
|
692
|
-
|
|
693
|
-
const finish = () => {
|
|
694
|
-
if (settled) return
|
|
695
|
-
settled = true
|
|
696
|
-
resolve({ latencies, completed, stages })
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const maybeLaunch = () => {
|
|
700
|
-
while (inFlight < options.concurrency && performance.now() < deadline) {
|
|
701
|
-
inFlight += 1
|
|
702
|
-
const opStart = performance.now()
|
|
703
|
-
|
|
704
|
-
options
|
|
705
|
-
.execute(options.payload)
|
|
706
|
-
.then((trace) => {
|
|
707
|
-
completed += 1
|
|
708
|
-
addStageTotals(stages, trace)
|
|
709
|
-
if (options.collectLatencies) {
|
|
710
|
-
const elapsed = performance.now() - opStart
|
|
711
|
-
if (latencies.length < MAX_LATENCY_SAMPLES) {
|
|
712
|
-
latencies.push(elapsed)
|
|
713
|
-
} else {
|
|
714
|
-
const sampleIndex = Math.floor(Math.random() * completed)
|
|
715
|
-
if (sampleIndex < MAX_LATENCY_SAMPLES) {
|
|
716
|
-
latencies[sampleIndex] = elapsed
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
})
|
|
721
|
-
.catch((error: unknown) => {
|
|
722
|
-
if (settled) return
|
|
723
|
-
settled = true
|
|
724
|
-
reject(error)
|
|
725
|
-
})
|
|
726
|
-
.finally(() => {
|
|
727
|
-
inFlight -= 1
|
|
728
|
-
if (settled) return
|
|
729
|
-
if (performance.now() < deadline) {
|
|
730
|
-
maybeLaunch()
|
|
731
|
-
return
|
|
732
|
-
}
|
|
733
|
-
if (inFlight === 0) {
|
|
734
|
-
finish()
|
|
735
|
-
}
|
|
736
|
-
})
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
maybeLaunch()
|
|
741
|
-
if (inFlight === 0) {
|
|
742
|
-
finish()
|
|
743
|
-
}
|
|
744
|
-
})
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
async function runScenario(profile: Profile, scenario: Scenario): Promise<ScenarioResult> {
|
|
748
|
-
const payload = payloadOfSize(scenario.payloadBytes)
|
|
749
|
-
const template: BenchEnvelopeTemplate = {
|
|
750
|
-
headerBase: {
|
|
751
|
-
version: '7h3/0.1',
|
|
752
|
-
ttlMs: 120_000,
|
|
753
|
-
sender: 'bench.sender',
|
|
754
|
-
recipient: 'bench.receiver',
|
|
755
|
-
},
|
|
756
|
-
intent: 'TASK',
|
|
757
|
-
capability: 'task.plan',
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
const adapter: TransportAdapter | null =
|
|
761
|
-
scenario.mode === 'inproc' ? new InProcessAdapter() : scenario.mode === 'http' ? new HttpAdapter() : scenario.mode === 'ws' ? new WsAdapter() : null
|
|
762
|
-
|
|
763
|
-
try {
|
|
764
|
-
if (adapter) await adapter.start()
|
|
765
|
-
|
|
766
|
-
let sequence = 0
|
|
767
|
-
const execute = async (content: string): Promise<TransportTrace & { buildMs: number; canonicalizeMs: number; macMs: number }> => {
|
|
768
|
-
if (scenario.mode === 'agent-loop') {
|
|
769
|
-
const buildStart = performance.now()
|
|
770
|
-
await runAgentLoop(content)
|
|
771
|
-
return {
|
|
772
|
-
buildMs: performance.now() - buildStart,
|
|
773
|
-
canonicalizeMs: 0,
|
|
774
|
-
macMs: 0,
|
|
775
|
-
queueMs: 0,
|
|
776
|
-
transportMs: 0,
|
|
777
|
-
verifyMs: 0,
|
|
778
|
-
decodeMs: 0,
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
sequence += 1
|
|
783
|
-
const built = await buildSignAndEncodeEnvelope(template, content, sequence)
|
|
784
|
-
const trace = await adapter!.send(built.raw)
|
|
785
|
-
return {
|
|
786
|
-
...trace,
|
|
787
|
-
buildMs: built.buildMs,
|
|
788
|
-
canonicalizeMs: built.canonicalizeMs,
|
|
789
|
-
macMs: built.macMs,
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
await runPhase({
|
|
794
|
-
durationMs: profile.warmupMs,
|
|
795
|
-
concurrency: scenario.concurrency,
|
|
796
|
-
payload,
|
|
797
|
-
execute,
|
|
798
|
-
collectLatencies: false,
|
|
799
|
-
})
|
|
800
|
-
|
|
801
|
-
maybeGc()
|
|
802
|
-
const cpuStart = process.cpuUsage()
|
|
803
|
-
const memStart = process.memoryUsage()
|
|
804
|
-
|
|
805
|
-
const measured = await runPhase({
|
|
806
|
-
durationMs: profile.measureMs,
|
|
807
|
-
concurrency: scenario.concurrency,
|
|
808
|
-
payload,
|
|
809
|
-
execute,
|
|
810
|
-
collectLatencies: true,
|
|
811
|
-
})
|
|
812
|
-
|
|
813
|
-
const cpuEnd = process.cpuUsage(cpuStart)
|
|
814
|
-
const memEnd = process.memoryUsage()
|
|
815
|
-
const elapsedSeconds = profile.measureMs / 1000
|
|
816
|
-
|
|
817
|
-
const sortedLatencies = measured.latencies.slice().sort((a, b) => a - b)
|
|
818
|
-
const completed = measured.completed
|
|
819
|
-
const opsPerSecond = completed / elapsedSeconds
|
|
820
|
-
const cpuMs = (cpuEnd.user + cpuEnd.system) / 1000
|
|
821
|
-
const stageScale = completed > 0 ? 1 / completed : 0
|
|
822
|
-
|
|
823
|
-
const cpuMsPer10kOps = completed > 0 ? cpuMs / (completed / 10_000) : 0
|
|
824
|
-
const heapDeltaBytes = memEnd.heapUsed - memStart.heapUsed
|
|
825
|
-
const rssDeltaBytes = memEnd.rss - memStart.rss
|
|
826
|
-
|
|
827
|
-
return {
|
|
828
|
-
mode: scenario.mode,
|
|
829
|
-
payloadBytes: scenario.payloadBytes,
|
|
830
|
-
concurrency: scenario.concurrency,
|
|
831
|
-
ops: completed,
|
|
832
|
-
opsPerSecond: round3(opsPerSecond),
|
|
833
|
-
p50Ms: round3(percentile(sortedLatencies, 50)),
|
|
834
|
-
p95Ms: round3(percentile(sortedLatencies, 95)),
|
|
835
|
-
p99Ms: round3(percentile(sortedLatencies, 99)),
|
|
836
|
-
cpuMsPer10kOps: round3(cpuMsPer10kOps),
|
|
837
|
-
heapDeltaMb: round3(heapDeltaBytes / (1024 * 1024)),
|
|
838
|
-
rssDeltaMb: round3(rssDeltaBytes / (1024 * 1024)),
|
|
839
|
-
allocBytesPerOp: round3(completed > 0 ? heapDeltaBytes / completed : 0),
|
|
840
|
-
stageBuildMsPerOp: round3(measured.stages.buildMs * stageScale),
|
|
841
|
-
stageCanonicalizeMsPerOp: round3(measured.stages.canonicalizeMs * stageScale),
|
|
842
|
-
stageMacMsPerOp: round3(measured.stages.macMs * stageScale),
|
|
843
|
-
stageQueueMsPerOp: round3(measured.stages.queueMs * stageScale),
|
|
844
|
-
stageTransportMsPerOp: round3(measured.stages.transportMs * stageScale),
|
|
845
|
-
stageVerifyMsPerOp: round3(measured.stages.verifyMs * stageScale),
|
|
846
|
-
stageDecodeMsPerOp: round3(measured.stages.decodeMs * stageScale),
|
|
847
|
-
}
|
|
848
|
-
} finally {
|
|
849
|
-
if (adapter) await adapter.stop()
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
function renderMarkdown(profileName: ProfileName, profile: Profile, rows: ScenarioResult[]): string {
|
|
854
|
-
const lines: string[] = []
|
|
855
|
-
lines.push(`# Protocol E2E Benchmark (${profileName})`)
|
|
856
|
-
lines.push('')
|
|
857
|
-
lines.push(`- Warmup: ${profile.warmupMs}ms`)
|
|
858
|
-
lines.push(`- Measure: ${profile.measureMs}ms`)
|
|
859
|
-
lines.push('- Pipeline: build -> canonicalize -> MAC -> queue -> transport -> verify -> decode')
|
|
860
|
-
lines.push('')
|
|
861
|
-
lines.push('| mode | payload | concurrency | ops/s | p50 ms | p95 ms | p99 ms | cpu/10k ms | build ms/op | canon ms/op | mac ms/op | queue ms/op | transport ms/op | verify ms/op | decode ms/op |')
|
|
862
|
-
lines.push('| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |')
|
|
863
|
-
|
|
864
|
-
for (const row of rows) {
|
|
865
|
-
lines.push(
|
|
866
|
-
`| ${row.mode} | ${row.payloadBytes} | ${row.concurrency} | ${row.opsPerSecond} | ${row.p50Ms} | ${row.p95Ms} | ${row.p99Ms} | ${row.cpuMsPer10kOps} | ${row.stageBuildMsPerOp} | ${row.stageCanonicalizeMsPerOp} | ${row.stageMacMsPerOp} | ${row.stageQueueMsPerOp} | ${row.stageTransportMsPerOp} | ${row.stageVerifyMsPerOp} | ${row.stageDecodeMsPerOp} |`,
|
|
867
|
-
)
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
return `${lines.join('\n')}\n`
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
async function main(): Promise<void> {
|
|
874
|
-
const profileName = parseProfile(process.argv)
|
|
875
|
-
const profile = PROFILES[profileName]
|
|
876
|
-
if (!profile) {
|
|
877
|
-
throw new Error(`Unknown profile '${profileName}'`)
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
const modes = parseModeSelection(process.argv, ['inproc', 'http', 'ws', 'agent-loop'])
|
|
881
|
-
const payloadSizes = parseNumberSelection(process.argv, '--payloads', PAYLOAD_SIZES)
|
|
882
|
-
const concurrencyLevels = parseNumberSelection(process.argv, '--concurrency', CONCURRENCY_LEVELS)
|
|
883
|
-
const scenarios: Scenario[] = []
|
|
884
|
-
for (const mode of modes) {
|
|
885
|
-
for (const payloadBytes of payloadSizes) {
|
|
886
|
-
for (const concurrency of concurrencyLevels) {
|
|
887
|
-
scenarios.push({ mode, payloadBytes, concurrency })
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
const rows: ScenarioResult[] = []
|
|
893
|
-
for (const scenario of scenarios) {
|
|
894
|
-
const label = `[${scenario.mode}] payload=${scenario.payloadBytes}B concurrency=${scenario.concurrency}`
|
|
895
|
-
console.log(`Running ${label}`)
|
|
896
|
-
const result = await runScenario(profile, scenario)
|
|
897
|
-
rows.push(result)
|
|
898
|
-
console.log(
|
|
899
|
-
` ops/s=${result.opsPerSecond} p99=${result.p99Ms}ms build=${result.stageBuildMsPerOp}ms canon=${result.stageCanonicalizeMsPerOp}ms mac=${result.stageMacMsPerOp}ms transport=${result.stageTransportMsPerOp}ms verify=${result.stageVerifyMsPerOp}ms`,
|
|
900
|
-
)
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
904
|
-
const outputDir = 'dist/bench'
|
|
905
|
-
await mkdir(outputDir, { recursive: true })
|
|
906
|
-
|
|
907
|
-
const jsonPath = `${outputDir}/protocol-e2e.${profileName}.${timestamp}.json`
|
|
908
|
-
const mdPath = `${outputDir}/protocol-e2e.${profileName}.${timestamp}.md`
|
|
909
|
-
const latestJsonPath = `${outputDir}/protocol-e2e.${profileName}.latest.json`
|
|
910
|
-
const latestMdPath = `${outputDir}/protocol-e2e.${profileName}.latest.md`
|
|
911
|
-
|
|
912
|
-
const payload = {
|
|
913
|
-
profile: profileName,
|
|
914
|
-
warmupMs: profile.warmupMs,
|
|
915
|
-
measureMs: profile.measureMs,
|
|
916
|
-
results: rows,
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
await writeFile(jsonPath, JSON.stringify(payload, null, 2), 'utf8')
|
|
920
|
-
await writeFile(latestJsonPath, JSON.stringify(payload, null, 2), 'utf8')
|
|
921
|
-
|
|
922
|
-
const markdown = renderMarkdown(profileName, profile, rows)
|
|
923
|
-
await writeFile(mdPath, markdown, 'utf8')
|
|
924
|
-
await writeFile(latestMdPath, markdown, 'utf8')
|
|
925
|
-
|
|
926
|
-
console.log('')
|
|
927
|
-
console.log(`Wrote ${jsonPath}`)
|
|
928
|
-
console.log(`Wrote ${mdPath}`)
|
|
929
|
-
console.log(`Updated ${latestJsonPath}`)
|
|
930
|
-
console.log(`Updated ${latestMdPath}`)
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
main().catch((error: unknown) => {
|
|
934
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error)
|
|
935
|
-
console.error(message)
|
|
936
|
-
process.exitCode = 1
|
|
937
|
-
})
|