@apnex/network-adapter 0.1.0
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/dist/hub-error.d.ts +42 -0
- package/dist/hub-error.js +74 -0
- package/dist/hub-error.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/agent-client.d.ts +192 -0
- package/dist/kernel/agent-client.js +44 -0
- package/dist/kernel/agent-client.js.map +1 -0
- package/dist/kernel/event-router.d.ts +49 -0
- package/dist/kernel/event-router.js +150 -0
- package/dist/kernel/event-router.js.map +1 -0
- package/dist/kernel/handshake.d.ts +161 -0
- package/dist/kernel/handshake.js +257 -0
- package/dist/kernel/handshake.js.map +1 -0
- package/dist/kernel/instance.d.ts +40 -0
- package/dist/kernel/instance.js +79 -0
- package/dist/kernel/instance.js.map +1 -0
- package/dist/kernel/mcp-agent-client.d.ts +139 -0
- package/dist/kernel/mcp-agent-client.js +505 -0
- package/dist/kernel/mcp-agent-client.js.map +1 -0
- package/dist/kernel/poll-backstop.d.ts +108 -0
- package/dist/kernel/poll-backstop.js +243 -0
- package/dist/kernel/poll-backstop.js.map +1 -0
- package/dist/kernel/session-claim.d.ts +67 -0
- package/dist/kernel/session-claim.js +106 -0
- package/dist/kernel/session-claim.js.map +1 -0
- package/dist/kernel/state-sync.d.ts +43 -0
- package/dist/kernel/state-sync.js +85 -0
- package/dist/kernel/state-sync.js.map +1 -0
- package/dist/logger.d.ts +82 -0
- package/dist/logger.js +114 -0
- package/dist/logger.js.map +1 -0
- package/dist/notification-log.d.ts +29 -0
- package/dist/notification-log.js +66 -0
- package/dist/notification-log.js.map +1 -0
- package/dist/prompt-format.d.ts +38 -0
- package/dist/prompt-format.js +220 -0
- package/dist/prompt-format.js.map +1 -0
- package/dist/tool-manager/dispatcher.d.ts +180 -0
- package/dist/tool-manager/dispatcher.js +379 -0
- package/dist/tool-manager/dispatcher.js.map +1 -0
- package/dist/tool-manager/tool-catalog-cache.d.ts +85 -0
- package/dist/tool-manager/tool-catalog-cache.js +137 -0
- package/dist/tool-manager/tool-catalog-cache.js.map +1 -0
- package/dist/wire/mcp-transport.d.ts +120 -0
- package/dist/wire/mcp-transport.js +447 -0
- package/dist/wire/mcp-transport.js.map +1 -0
- package/dist/wire/transport.d.ts +174 -0
- package/dist/wire/transport.js +43 -0
- package/dist/wire/transport.js.map +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-catalog-cache.ts — per-WORK_DIR Hub tool catalog cache.
|
|
3
|
+
*
|
|
4
|
+
* Probe-safe ListTools support: when the host calls tools/list before
|
|
5
|
+
* the adapter's identityReady has resolved (e.g. `claude mcp list`
|
|
6
|
+
* spawning the adapter just to enumerate available tools), the
|
|
7
|
+
* dispatcher serves the catalog from a persisted cache without
|
|
8
|
+
* touching the Hub. Together with the lazy session-claim path this
|
|
9
|
+
* makes probes fully Hub-free against a warm cache.
|
|
10
|
+
*
|
|
11
|
+
* Storage: $WORK_DIR/.ois/tool-catalog.json
|
|
12
|
+
* {
|
|
13
|
+
* schemaVersion: 1,
|
|
14
|
+
* hubVersion: "1.0.0",
|
|
15
|
+
* fetchedAt: "2026-04-22T...Z",
|
|
16
|
+
* catalog: [...]
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* Invalidation: Hub-version mismatch only. No TTL — the catalog is
|
|
20
|
+
* static between Hub deploys; TTL would add noise without correctness
|
|
21
|
+
* value. Schema-version mismatch on read returns null (cache treated
|
|
22
|
+
* as invalid; future schema evolution bumps CATALOG_SCHEMA_VERSION).
|
|
23
|
+
*
|
|
24
|
+
* Atomicity: writeCache uses tmp-file + rename so partial writes on
|
|
25
|
+
* crash don't corrupt the cache. Parse errors on read also return
|
|
26
|
+
* null — the cache self-heals on next bootstrap.
|
|
27
|
+
*
|
|
28
|
+
* Failure modes (best-effort; readCache + writeCache never throw on
|
|
29
|
+
* the primary flow):
|
|
30
|
+
* - missing file: readCache returns null
|
|
31
|
+
* - parse error: readCache returns null + logs
|
|
32
|
+
* - schema-version mismatch: readCache returns null
|
|
33
|
+
* - $WORK_DIR readonly: writeCache logs + no-ops
|
|
34
|
+
* - disk full: writeCache logs + no-ops
|
|
35
|
+
*/
|
|
36
|
+
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync, unlinkSync, } from "node:fs";
|
|
37
|
+
import { join, dirname } from "node:path";
|
|
38
|
+
/**
|
|
39
|
+
* Bumping CATALOG_SCHEMA_VERSION forces all existing cache files to
|
|
40
|
+
* be treated as invalid + re-bootstrapped. Use when changing the
|
|
41
|
+
* cache file shape.
|
|
42
|
+
*/
|
|
43
|
+
export const CATALOG_SCHEMA_VERSION = 1;
|
|
44
|
+
/** Compute the canonical cache path for a given WORK_DIR. */
|
|
45
|
+
export function cachePathFor(workDir) {
|
|
46
|
+
return join(workDir, ".ois", "tool-catalog.json");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the cache file. Returns null on missing file, parse error,
|
|
50
|
+
* schema-version mismatch, or shape mismatch. Never throws — the
|
|
51
|
+
* primary ListTools flow always falls through to a live Hub fetch
|
|
52
|
+
* when readCache returns null.
|
|
53
|
+
*/
|
|
54
|
+
export function readCache(workDir, log) {
|
|
55
|
+
const path = cachePathFor(workDir);
|
|
56
|
+
if (!existsSync(path))
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
const raw = readFileSync(path, "utf8");
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (typeof parsed.schemaVersion !== "number" ||
|
|
62
|
+
parsed.schemaVersion !== CATALOG_SCHEMA_VERSION ||
|
|
63
|
+
typeof parsed.hubVersion !== "string" ||
|
|
64
|
+
typeof parsed.fetchedAt !== "string" ||
|
|
65
|
+
!Array.isArray(parsed.catalog)) {
|
|
66
|
+
log?.(`[tool-catalog-cache] readCache: cache invalid (schema/shape mismatch) at ${path}`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
schemaVersion: parsed.schemaVersion,
|
|
71
|
+
hubVersion: parsed.hubVersion,
|
|
72
|
+
fetchedAt: parsed.fetchedAt,
|
|
73
|
+
catalog: parsed.catalog,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
log?.(`[tool-catalog-cache] readCache: parse error at ${path}: ${err.message ?? err}`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Persist the catalog atomically. Writes a sibling tmp file then
|
|
83
|
+
* renames — so a crash mid-write leaves either the previous cache
|
|
84
|
+
* intact OR the new cache fully landed. Best-effort: failures log
|
|
85
|
+
* and return; the caller's primary flow continues.
|
|
86
|
+
*/
|
|
87
|
+
export function writeCache(workDir, catalog, hubVersion, log) {
|
|
88
|
+
const path = cachePathFor(workDir);
|
|
89
|
+
const tmpPath = `${path}.tmp.${process.pid}`;
|
|
90
|
+
const body = {
|
|
91
|
+
schemaVersion: CATALOG_SCHEMA_VERSION,
|
|
92
|
+
hubVersion,
|
|
93
|
+
fetchedAt: new Date().toISOString(),
|
|
94
|
+
catalog,
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
const dir = dirname(path);
|
|
98
|
+
if (!existsSync(dir))
|
|
99
|
+
mkdirSync(dir, { recursive: true });
|
|
100
|
+
writeFileSync(tmpPath, JSON.stringify(body), { encoding: "utf8" });
|
|
101
|
+
renameSync(tmpPath, path);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
log?.(`[tool-catalog-cache] writeCache: failed at ${path}: ${err.message ?? err} — cache will not populate this run`);
|
|
105
|
+
try {
|
|
106
|
+
if (existsSync(tmpPath))
|
|
107
|
+
unlinkSync(tmpPath);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* ignore */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check cache validity against the current Hub version.
|
|
116
|
+
*
|
|
117
|
+
* Two semantics:
|
|
118
|
+
* - currentHubVersion is a non-empty string: strict equality vs
|
|
119
|
+
* cached.hubVersion. Mismatch → invalid → caller re-bootstraps.
|
|
120
|
+
* - currentHubVersion is null/undefined/empty: caller doesn't know
|
|
121
|
+
* the current Hub version yet (e.g. /health fetch in flight at
|
|
122
|
+
* startup). Trust the cache (probe-friendly default) — worst
|
|
123
|
+
* case is serving a stale catalog ONCE until the next /health
|
|
124
|
+
* fetch completes and a real session refreshes the cache.
|
|
125
|
+
*
|
|
126
|
+
* Schema-version check is enforced inside readCache, so isCacheValid
|
|
127
|
+
* never sees a wrong-schema cached object.
|
|
128
|
+
*/
|
|
129
|
+
export function isCacheValid(cached, currentHubVersion) {
|
|
130
|
+
if (currentHubVersion === null ||
|
|
131
|
+
currentHubVersion === undefined ||
|
|
132
|
+
currentHubVersion === "") {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return cached.hubVersion === currentHubVersion;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=tool-catalog-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-catalog-cache.js","sourceRoot":"","sources":["../../src/tool-manager/tool-catalog-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* McpTransport — first `ITransport` implementation, backed by the MCP
|
|
3
|
+
* SDK's `Client` + `StreamableHTTPClientTransport`.
|
|
4
|
+
*
|
|
5
|
+
* Phase 2 of the L4/L7 refactor: extracted alongside the existing
|
|
6
|
+
* `McpConnectionManager`, which stays intact for the rest of the
|
|
7
|
+
* migration. Nothing in the current runtime uses this file yet — it
|
|
8
|
+
* exists so the Phase-2 gap tests can exercise the L4 surface in
|
|
9
|
+
* isolation and prove the split holds before Phase 3 relocates the
|
|
10
|
+
* session FSM.
|
|
11
|
+
*
|
|
12
|
+
* ## What this class owns (L4)
|
|
13
|
+
*
|
|
14
|
+
* - The MCP wire: SDK `Client` + `StreamableHTTPClientTransport`.
|
|
15
|
+
* - SSE liveness: first-keepalive deadline, mid-stream watchdog,
|
|
16
|
+
* heartbeat POST loop.
|
|
17
|
+
* - Wire-level reconnection: on SSE death or heartbeat failure it
|
|
18
|
+
* tears down the SDK client and rebuilds a fresh one. Callers see
|
|
19
|
+
* only `reconnecting` + `reconnected` wire events.
|
|
20
|
+
* - Untyped `request(method, params)` over MCP `callTool`.
|
|
21
|
+
* - `listMethods()` via MCP `listTools`.
|
|
22
|
+
*
|
|
23
|
+
* ## What this class deliberately does NOT own
|
|
24
|
+
*
|
|
25
|
+
* - `register_role`. The bare register_role call that the legacy
|
|
26
|
+
* `McpConnectionManager` fires during `connect()` is a session
|
|
27
|
+
* concern — it binds the MCP session to an engineer role on the
|
|
28
|
+
* Hub. `McpTransport.connect()` returns once the MCP wire is
|
|
29
|
+
* initialized, *without* calling register_role. The caller
|
|
30
|
+
* (future `IAgentClient`) issues the role call via
|
|
31
|
+
* `transport.request("register_role", {role})` itself.
|
|
32
|
+
* - Session FSM. No synchronizing / streaming vocabulary, no
|
|
33
|
+
* sync buffer, no `completeSync()`.
|
|
34
|
+
* - `session_invalid` classification and retry. Callers see the
|
|
35
|
+
* raw error from `request()` and reconnect at the session layer.
|
|
36
|
+
*
|
|
37
|
+
* ## Hub-event push delivery
|
|
38
|
+
*
|
|
39
|
+
* SSE notifications land on the MCP client's `LoggingMessageNotification`
|
|
40
|
+
* handler. This class splits them into two streams:
|
|
41
|
+
*
|
|
42
|
+
* - `logger === "keepalive"` frames feed the SSE watchdog and are
|
|
43
|
+
* NOT forwarded to subscribers — they are wire noise.
|
|
44
|
+
* - `logger === "hub-event"` frames are emitted as
|
|
45
|
+
* `{ type: "push", method: "hub-event", payload }` via the
|
|
46
|
+
* `onWireEvent` stream. Classification, dedup, and routing happen
|
|
47
|
+
* above this layer.
|
|
48
|
+
*/
|
|
49
|
+
import type { ITransport, TransportConfig, TransportMetrics, WireState, WireEventHandler } from "./transport.js";
|
|
50
|
+
/**
|
|
51
|
+
* Wire-level reconnect backoff curve. Pure function so the invariant
|
|
52
|
+
* (start at `baseDelay`, double each consecutive failure, clamp at
|
|
53
|
+
* `maxDelay`) is unit-testable without spinning a real wire.
|
|
54
|
+
*
|
|
55
|
+
* The cap on the exponent (`6`) matches the legacy manager: after six
|
|
56
|
+
* consecutive failures we're pegged at 60s and further failures don't
|
|
57
|
+
* push the delay higher — they just sit at the cap until a keepalive
|
|
58
|
+
* arrives and resets `consecutiveReconnects` to zero.
|
|
59
|
+
*/
|
|
60
|
+
export declare function computeReconnectBackoff(consecutiveReconnects: number, baseDelay: number, maxDelay?: number): number;
|
|
61
|
+
export declare class McpTransport implements ITransport {
|
|
62
|
+
private readonly cfg;
|
|
63
|
+
private readonly log;
|
|
64
|
+
private client;
|
|
65
|
+
private sdkTransport;
|
|
66
|
+
private _wireState;
|
|
67
|
+
private handlers;
|
|
68
|
+
private lastKeepaliveAt;
|
|
69
|
+
private sseVerified;
|
|
70
|
+
private heartbeatTimer;
|
|
71
|
+
private sseWatchdogTimer;
|
|
72
|
+
private firstKeepaliveTimer;
|
|
73
|
+
private reconnectTimer;
|
|
74
|
+
private totalReconnects;
|
|
75
|
+
private consecutiveReconnects;
|
|
76
|
+
private lastReconnectCause?;
|
|
77
|
+
private requestsInFlight;
|
|
78
|
+
private reconnecting;
|
|
79
|
+
private closed;
|
|
80
|
+
constructor(config: TransportConfig);
|
|
81
|
+
get wireState(): WireState;
|
|
82
|
+
connect(): Promise<void>;
|
|
83
|
+
close(): Promise<void>;
|
|
84
|
+
request(method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
85
|
+
listMethods(): Promise<string[]>;
|
|
86
|
+
/**
|
|
87
|
+
* Escape hatch for shims that need full MCP tool definitions
|
|
88
|
+
* (name + inputSchema + description), not just method names.
|
|
89
|
+
* Consumed by proxy shims that re-advertise the Hub's tool surface
|
|
90
|
+
* to another MCP client (e.g. Claude Code stdio proxy).
|
|
91
|
+
*/
|
|
92
|
+
listToolsRaw(): Promise<Array<Record<string, unknown>>>;
|
|
93
|
+
onWireEvent(handler: WireEventHandler): void;
|
|
94
|
+
getMetrics(): TransportMetrics;
|
|
95
|
+
/**
|
|
96
|
+
* Test-only helper. Session IDs are an MCP-specific concept exposed
|
|
97
|
+
* here so Phase-2 integration tests can assert that a wire reconnect
|
|
98
|
+
* rebuilds the session. Not part of `ITransport`.
|
|
99
|
+
*/
|
|
100
|
+
getSessionId(): string | undefined;
|
|
101
|
+
private createWire;
|
|
102
|
+
private teardownWire;
|
|
103
|
+
/**
|
|
104
|
+
* Wire-level reconnect loop. Entered on any wire-death signal
|
|
105
|
+
* (SSE drop, heartbeat failure, transport error). Emits
|
|
106
|
+
* `reconnecting` + (on success) `reconnected` wire events so the
|
|
107
|
+
* AgentClient above can re-run its L7 handshake.
|
|
108
|
+
*/
|
|
109
|
+
private reconnectWire;
|
|
110
|
+
private scheduleReconnectTimer;
|
|
111
|
+
private cancelReconnectTimer;
|
|
112
|
+
private startHeartbeat;
|
|
113
|
+
private stopHeartbeat;
|
|
114
|
+
private startSseWatchdog;
|
|
115
|
+
private stopSseWatchdog;
|
|
116
|
+
private startFirstKeepaliveDeadline;
|
|
117
|
+
private cancelFirstKeepaliveDeadline;
|
|
118
|
+
private transition;
|
|
119
|
+
private emit;
|
|
120
|
+
}
|