@rynfar/meridian 1.21.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 +410 -0
- package/assets/banner.svg +46 -0
- package/assets/how-it-works.svg +87 -0
- package/assets/icon.svg +16 -0
- package/assets/logo.svg +15 -0
- package/dist/cli-zyn4cqp2.js +24392 -0
- package/dist/cli.js +46 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/mcpTools.d.ts +15 -0
- package/dist/mcpTools.d.ts.map +1 -0
- package/dist/proxy/adapter.d.ts +78 -0
- package/dist/proxy/adapter.d.ts.map +1 -0
- package/dist/proxy/adapters/crush.d.ts +22 -0
- package/dist/proxy/adapters/crush.d.ts.map +1 -0
- package/dist/proxy/adapters/detect.d.ts +18 -0
- package/dist/proxy/adapters/detect.d.ts.map +1 -0
- package/dist/proxy/adapters/droid.d.ts +19 -0
- package/dist/proxy/adapters/droid.d.ts.map +1 -0
- package/dist/proxy/adapters/opencode.d.ts +9 -0
- package/dist/proxy/adapters/opencode.d.ts.map +1 -0
- package/dist/proxy/agentDefs.d.ts +51 -0
- package/dist/proxy/agentDefs.d.ts.map +1 -0
- package/dist/proxy/agentMatch.d.ts +18 -0
- package/dist/proxy/agentMatch.d.ts.map +1 -0
- package/dist/proxy/errors.d.ts +25 -0
- package/dist/proxy/errors.d.ts.map +1 -0
- package/dist/proxy/messages.d.ts +24 -0
- package/dist/proxy/messages.d.ts.map +1 -0
- package/dist/proxy/models.d.ts +46 -0
- package/dist/proxy/models.d.ts.map +1 -0
- package/dist/proxy/passthroughTools.d.ts +30 -0
- package/dist/proxy/passthroughTools.d.ts.map +1 -0
- package/dist/proxy/query.d.ts +105 -0
- package/dist/proxy/query.d.ts.map +1 -0
- package/dist/proxy/server.d.ts +10 -0
- package/dist/proxy/server.d.ts.map +1 -0
- package/dist/proxy/session/cache.d.ts +32 -0
- package/dist/proxy/session/cache.d.ts.map +1 -0
- package/dist/proxy/session/fingerprint.d.ts +31 -0
- package/dist/proxy/session/fingerprint.d.ts.map +1 -0
- package/dist/proxy/session/lineage.d.ts +103 -0
- package/dist/proxy/session/lineage.d.ts.map +1 -0
- package/dist/proxy/sessionStore.d.ts +36 -0
- package/dist/proxy/sessionStore.d.ts.map +1 -0
- package/dist/proxy/tools.d.ts +26 -0
- package/dist/proxy/tools.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +27 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/server.js +18 -0
- package/dist/telemetry/dashboard.d.ts +6 -0
- package/dist/telemetry/dashboard.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +7 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/landing.d.ts +7 -0
- package/dist/telemetry/landing.d.ts.map +1 -0
- package/dist/telemetry/logStore.d.ts +50 -0
- package/dist/telemetry/logStore.d.ts.map +1 -0
- package/dist/telemetry/routes.d.ts +11 -0
- package/dist/telemetry/routes.d.ts.map +1 -0
- package/dist/telemetry/store.d.ts +39 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/types.d.ts +93 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/utils/lruMap.d.ts +28 -0
- package/dist/utils/lruMap.d.ts.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation fingerprinting and client working directory extraction.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: extractClientCwd is OpenCode-specific (parses <env> blocks).
|
|
5
|
+
* When the adapter pattern is implemented, this will move to the
|
|
6
|
+
* OpenCode adapter. getConversationFingerprint is agent-agnostic.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extract the client's working directory from the system prompt.
|
|
10
|
+
* OpenCode embeds it inside an <env> block:
|
|
11
|
+
* <env>
|
|
12
|
+
* Working directory: /path/to/project
|
|
13
|
+
* ...
|
|
14
|
+
* </env>
|
|
15
|
+
*
|
|
16
|
+
* Returns the path if found, or undefined to fall back to server defaults.
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractClientCwd(body: any): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Hash the first user message + working directory to fingerprint a conversation.
|
|
21
|
+
* Used to find a cached session when no session header is present.
|
|
22
|
+
* Includes workingDirectory (stable per project, unlike systemContext which
|
|
23
|
+
* contains dynamic file trees/diagnostics that change every request).
|
|
24
|
+
* This prevents cross-project collisions when different projects start
|
|
25
|
+
* with the same first message.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getConversationFingerprint(messages: Array<{
|
|
28
|
+
role: string;
|
|
29
|
+
content: any;
|
|
30
|
+
}>, workingDirectory?: string): string;
|
|
31
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAc9D;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAW7H"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lineage verification.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for hashing messages and classifying mutations
|
|
5
|
+
* (continuation, compaction, undo, diverged).
|
|
6
|
+
*/
|
|
7
|
+
/** Minimum suffix overlap (stored messages found at the end of incoming)
|
|
8
|
+
* required to classify a mutation as compaction rather than a branch. */
|
|
9
|
+
export declare const MIN_SUFFIX_FOR_COMPACTION = 2;
|
|
10
|
+
export interface SessionState {
|
|
11
|
+
claudeSessionId: string;
|
|
12
|
+
lastAccess: number;
|
|
13
|
+
messageCount: number;
|
|
14
|
+
/** Hash of messages[0..messageCount-1] for fast-path lineage verification.
|
|
15
|
+
* When the full prefix matches, the conversation is a strict continuation
|
|
16
|
+
* and we skip the per-message diff entirely. */
|
|
17
|
+
lineageHash: string;
|
|
18
|
+
/** Per-message content hashes from the last stored request.
|
|
19
|
+
* Used for precise diff-based mutation classification when the aggregate
|
|
20
|
+
* lineageHash mismatches. */
|
|
21
|
+
messageHashes?: string[];
|
|
22
|
+
/** SDK assistant message UUIDs indexed by message position.
|
|
23
|
+
* Only assistant messages have UUIDs (user messages are null).
|
|
24
|
+
* Used to find the rollback point for undo. */
|
|
25
|
+
sdkMessageUuids?: Array<string | null>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Result of lineage verification — classifies the mutation and provides
|
|
29
|
+
* the information needed to take the correct SDK action.
|
|
30
|
+
*/
|
|
31
|
+
export type LineageResult = {
|
|
32
|
+
type: "continuation";
|
|
33
|
+
session: SessionState;
|
|
34
|
+
} | {
|
|
35
|
+
type: "compaction";
|
|
36
|
+
session: SessionState;
|
|
37
|
+
} | {
|
|
38
|
+
type: "undo";
|
|
39
|
+
session: SessionState;
|
|
40
|
+
prefixOverlap: number;
|
|
41
|
+
rollbackUuid: string | undefined;
|
|
42
|
+
} | {
|
|
43
|
+
type: "diverged";
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Compute a lineage hash of an ordered message array.
|
|
47
|
+
* Used as a fast-path check: if the aggregate hash matches, the messages
|
|
48
|
+
* are an exact prefix-extension and we skip the per-message diff.
|
|
49
|
+
*/
|
|
50
|
+
export declare function computeLineageHash(messages: Array<{
|
|
51
|
+
role: string;
|
|
52
|
+
content: any;
|
|
53
|
+
}>): string;
|
|
54
|
+
/**
|
|
55
|
+
* Compute a content hash for a single message (role + normalised content).
|
|
56
|
+
* Used to build per-message hash arrays for precise diff-based verification.
|
|
57
|
+
*/
|
|
58
|
+
export declare function hashMessage(message: {
|
|
59
|
+
role: string;
|
|
60
|
+
content: any;
|
|
61
|
+
}): string;
|
|
62
|
+
/**
|
|
63
|
+
* Compute per-message hashes for an entire message array.
|
|
64
|
+
*/
|
|
65
|
+
export declare function computeMessageHashes(messages: Array<{
|
|
66
|
+
role: string;
|
|
67
|
+
content: any;
|
|
68
|
+
}>): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Measure how many stored hashes match from the START of the stored array
|
|
71
|
+
* against the incoming hashes (order-preserving).
|
|
72
|
+
*
|
|
73
|
+
* Prefix overlap means the beginning of the conversation is intact (undo
|
|
74
|
+
* changes the end but preserves the beginning).
|
|
75
|
+
*/
|
|
76
|
+
export declare function measurePrefixOverlap(storedHashes: string[], incomingSet: Set<string>): number;
|
|
77
|
+
/**
|
|
78
|
+
* Measure how many stored hashes match from the END of the stored array
|
|
79
|
+
* against the incoming hashes (order-preserving).
|
|
80
|
+
*
|
|
81
|
+
* Suffix overlap means the recent conversation is intact (compaction
|
|
82
|
+
* changes the beginning but preserves the end).
|
|
83
|
+
*/
|
|
84
|
+
export declare function measureSuffixOverlap(storedHashes: string[], incomingSet: Set<string>): number;
|
|
85
|
+
/** Cache-like interface for verifyLineage — only needs get/set/delete */
|
|
86
|
+
export interface SessionCacheLike {
|
|
87
|
+
delete(key: string): boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Verify that incoming messages are a valid continuation of a cached session.
|
|
91
|
+
* Uses per-message hash comparison to deterministically classify mutations.
|
|
92
|
+
*
|
|
93
|
+
* Decision matrix:
|
|
94
|
+
* Full prefix match (fast-path) → continuation (resume normally)
|
|
95
|
+
* Suffix overlap >= MIN_SUFFIX → compaction (resume normally)
|
|
96
|
+
* Prefix overlap > 0, no suffix → undo (fork at rollback point)
|
|
97
|
+
* No overlap → diverged (start fresh)
|
|
98
|
+
*/
|
|
99
|
+
export declare function verifyLineage(cached: SessionState, messages: Array<{
|
|
100
|
+
role: string;
|
|
101
|
+
content: any;
|
|
102
|
+
}>, cacheKey: string, cache: SessionCacheLike): LineageResult;
|
|
103
|
+
//# sourceMappingURL=lineage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/lineage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;0EAC0E;AAC1E,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB;;qDAEiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB;;kCAE8B;IAC9B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;oDAEgD;IAChD,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CACvC;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,YAAY,CAAC;IAAG,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAS,OAAO,EAAE,YAAY,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACxG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAA;AAIxB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,CAI1F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,GAAG,MAAM,CAK3E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,MAAM,EAAE,CAG9F;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO7F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO7F;AAID,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,gBAAgB,GACtB,aAAa,CAqFf"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based session store for cross-proxy session resume.
|
|
3
|
+
*
|
|
4
|
+
* When running per-terminal proxies (each on a different port),
|
|
5
|
+
* sessions need to be shared so you can resume a conversation
|
|
6
|
+
* started in one terminal from another. This stores session
|
|
7
|
+
* mappings in a JSON file that all proxy instances read/write.
|
|
8
|
+
*
|
|
9
|
+
* Format: { [key]: { claudeSessionId, createdAt, lastUsedAt } }
|
|
10
|
+
* Keys are either OpenCode session IDs or conversation fingerprints.
|
|
11
|
+
*/
|
|
12
|
+
export interface StoredSession {
|
|
13
|
+
claudeSessionId: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
lastUsedAt: number;
|
|
16
|
+
messageCount: number;
|
|
17
|
+
/** Hash of messages[0..messageCount-1] for conversation lineage verification */
|
|
18
|
+
lineageHash?: string;
|
|
19
|
+
/** Per-message content hashes for precise diff-based compaction detection */
|
|
20
|
+
messageHashes?: string[];
|
|
21
|
+
/** Per-message SDK assistant UUIDs for undo rollback (null for user messages) */
|
|
22
|
+
sdkMessageUuids?: Array<string | null>;
|
|
23
|
+
}
|
|
24
|
+
/** Set an explicit session store directory. Takes priority over env var.
|
|
25
|
+
* Pass null to clear. For testing only.
|
|
26
|
+
* @param opts.skipLocking — skip file locking (default true for test isolation) */
|
|
27
|
+
export declare function setSessionStoreDir(dir: string | null, opts?: {
|
|
28
|
+
skipLocking?: boolean;
|
|
29
|
+
}): void;
|
|
30
|
+
export declare function lookupSharedSession(key: string): StoredSession | undefined;
|
|
31
|
+
export declare function storeSharedSession(key: string, claudeSessionId: string, messageCount?: number, lineageHash?: string, messageHashes?: string[], sdkMessageUuids?: Array<string | null>): void;
|
|
32
|
+
/** Remove a single session from the shared file store.
|
|
33
|
+
* Used when a session is detected as stale (e.g. expired upstream). */
|
|
34
|
+
export declare function evictSharedSession(key: string): void;
|
|
35
|
+
export declare function clearSharedSessions(): void;
|
|
36
|
+
//# sourceMappingURL=sessionStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionStore.d.ts","sourceRoot":"","sources":["../../src/proxy/sessionStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CACvC;AA0DD;;oFAEoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAG7F;AAsED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAG1E;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAqC5L;AAED;wEACwE;AACxE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAkBpD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAO1C"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool blocking lists and MCP tool configuration.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: These lists are currently OpenCode-specific. When the adapter pattern
|
|
5
|
+
* is implemented, these will move into the OpenCode adapter and become
|
|
6
|
+
* configurable per-agent. See DEFERRED.md.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Block SDK built-in tools so Claude only uses MCP tools
|
|
10
|
+
* (which have correct param names for the calling agent).
|
|
11
|
+
*/
|
|
12
|
+
export declare const BLOCKED_BUILTIN_TOOLS: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Claude Code SDK tools that have NO equivalent in the calling agent (OpenCode).
|
|
15
|
+
* Only block these — everything else either has an agent equivalent
|
|
16
|
+
* or is handled by the agent's own tool system.
|
|
17
|
+
*
|
|
18
|
+
* Tools where the agent has an equivalent but with a DIFFERENT name/schema
|
|
19
|
+
* are blocked so Claude uses the agent's version instead of the SDK's.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CLAUDE_CODE_ONLY_TOOLS: string[];
|
|
22
|
+
/** MCP server name used by the calling agent */
|
|
23
|
+
export declare const MCP_SERVER_NAME = "opencode";
|
|
24
|
+
/** MCP tools that are allowed through the proxy's tool filter */
|
|
25
|
+
export declare const ALLOWED_MCP_TOOLS: string[];
|
|
26
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/proxy/tools.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,eAAO,MAAM,qBAAqB,UAIjC,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,UAoBlC,CAAA;AAED,gDAAgD;AAChD,eAAO,MAAM,eAAe,aAAa,CAAA;AAEzC,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,UAO7B,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
export interface ProxyConfig {
|
|
3
|
+
port: number;
|
|
4
|
+
host: string;
|
|
5
|
+
debug: boolean;
|
|
6
|
+
idleTimeoutSeconds: number;
|
|
7
|
+
silent: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ProxyInstance {
|
|
10
|
+
/** The underlying http.Server */
|
|
11
|
+
server: Server;
|
|
12
|
+
/** The resolved proxy configuration */
|
|
13
|
+
config: ProxyConfig;
|
|
14
|
+
/** Gracefully shut down the proxy server and clean up resources */
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/** Return type of createProxyServer — avoids leaking Hono internals to consumers */
|
|
18
|
+
export interface ProxyServer {
|
|
19
|
+
/** The HTTP app — pass `app.fetch` to your server of choice */
|
|
20
|
+
app: {
|
|
21
|
+
fetch: (request: Request, ...rest: any[]) => Response | Promise<Response>;
|
|
22
|
+
};
|
|
23
|
+
/** The resolved proxy configuration */
|
|
24
|
+
config: ProxyConfig;
|
|
25
|
+
}
|
|
26
|
+
export declare const DEFAULT_PROXY_CONFIG: ProxyConfig;
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/proxy/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAEvC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,kBAAkB,EAAE,MAAM,CAAA;IAC1B,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,uCAAuC;IACvC,MAAM,EAAE,WAAW,CAAA;IACnB,mEAAmE;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,GAAG,EAAE;QAAE,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;IAClF,uCAAuC;IACvC,MAAM,EAAE,WAAW,CAAA;CACpB;AAED,eAAO,MAAM,oBAAoB,EAAE,WAMlC,CAAA"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearSessionCache,
|
|
3
|
+
computeLineageHash,
|
|
4
|
+
computeMessageHashes,
|
|
5
|
+
createProxyServer,
|
|
6
|
+
getMaxSessionsLimit,
|
|
7
|
+
hashMessage,
|
|
8
|
+
startProxyServer
|
|
9
|
+
} from "./cli-zyn4cqp2.js";
|
|
10
|
+
export {
|
|
11
|
+
startProxyServer,
|
|
12
|
+
hashMessage,
|
|
13
|
+
getMaxSessionsLimit,
|
|
14
|
+
createProxyServer,
|
|
15
|
+
computeMessageHashes,
|
|
16
|
+
computeLineageHash,
|
|
17
|
+
clearSessionCache
|
|
18
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline HTML dashboard for telemetry.
|
|
3
|
+
* No framework, no build step, no CDN. Single self-contained page.
|
|
4
|
+
*/
|
|
5
|
+
export declare const dashboardHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Meridian \u2014 Telemetry</title>\n<style>\n :root {\n --bg: #0d1117; --surface: #161b22; --border: #30363d;\n --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;\n --green: #3fb950; --yellow: #d29922; --red: #f85149;\n --blue: #58a6ff; --purple: #bc8cff;\n --queue: #d29922; --ttfb: #58a6ff; --upstream: #3fb950; --total: #bc8cff;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\n background: var(--bg); color: var(--text); padding: 24px; line-height: 1.5; }\n h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }\n .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }\n .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }\n .card-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }\n .card-value { font-size: 28px; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }\n .card-detail { font-size: 12px; color: var(--muted); margin-top: 2px; }\n .section { margin-bottom: 24px; }\n .section-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--muted);\n text-transform: uppercase; letter-spacing: 0.5px; }\n table { width: 100%; border-collapse: collapse; background: var(--surface);\n border: 1px solid var(--border); border-radius: 8px; overflow: hidden; font-size: 13px; }\n th { text-align: left; padding: 10px 12px; background: var(--bg); color: var(--muted);\n font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }\n td { padding: 8px 12px; border-top: 1px solid var(--border); font-variant-numeric: tabular-nums; }\n tr:hover td { background: rgba(88,166,255,0.04); }\n .waterfall { display: flex; align-items: center; height: 18px; min-width: 200px; position: relative; }\n .waterfall-seg { height: 100%; border-radius: 2px; min-width: 2px; }\n .waterfall-seg.queue { background: var(--queue); }\n .waterfall-seg.overhead { background: var(--yellow); }\n .waterfall-seg.ttfb { background: var(--ttfb); }\n .waterfall-seg.response { background: var(--upstream); }\n .legend { display: flex; gap: 16px; margin-bottom: 12px; font-size: 12px; color: var(--muted); }\n .legend-dot { width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-right: 4px; vertical-align: middle; }\n .status-ok { color: var(--green); }\n .status-err { color: var(--red); }\n .pct-table td:first-child { font-weight: 500; }\n .pct-table .phase-dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }\n .mono { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; }\n .refresh-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }\n .refresh-bar select, .refresh-bar button {\n background: var(--surface); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer;\n }\n .refresh-bar button:hover { border-color: var(--accent); }\n .refresh-indicator { font-size: 11px; color: var(--muted); }\n .empty { text-align: center; padding: 48px; color: var(--muted); }\n\n /* Tabs */\n .tabs { display: flex; gap: 0; margin-bottom: 20px; border-bottom: 1px solid var(--border); }\n .tab { padding: 10px 20px; font-size: 13px; font-weight: 500; color: var(--muted); cursor: pointer;\n border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 0.15s, border-color 0.15s;\n user-select: none; }\n .tab:hover { color: var(--text); }\n .tab.active { color: var(--accent); border-bottom-color: var(--accent); }\n .tab-badge { font-size: 10px; padding: 1px 6px; border-radius: 10px; margin-left: 6px;\n background: var(--border); color: var(--muted); font-variant-numeric: tabular-nums; }\n .tab.active .tab-badge { background: rgba(88,166,255,0.15); color: var(--accent); }\n .tab-panel { display: none; }\n .tab-panel.active { display: block; }\n\n /* Log filters */\n .log-filters { display: flex; gap: 8px; margin-bottom: 12px; }\n .log-filter { font-size: 11px; padding: 3px 10px; border-radius: 12px; cursor: pointer;\n border: 1px solid var(--border); background: var(--surface); color: var(--muted);\n transition: all 0.15s; }\n .log-filter:hover { border-color: var(--accent); color: var(--text); }\n .log-filter.active { background: rgba(88,166,255,0.1); border-color: var(--accent); color: var(--accent); }\n</style>\n</head>\n<body>\n<h1>Meridian</h1>\n<div class=\"subtitle\">Request Performance Telemetry</div>\n\n<div class=\"refresh-bar\">\n <select id=\"window\">\n <option value=\"300000\">Last 5 min</option>\n <option value=\"900000\">Last 15 min</option>\n <option value=\"3600000\" selected>Last 1 hour</option>\n <option value=\"86400000\">Last 24 hours</option>\n </select>\n <button onclick=\"refresh()\">Refresh</button>\n <label><input type=\"checkbox\" id=\"autoRefresh\" checked> Auto (5s)</label>\n <span class=\"refresh-indicator\" id=\"lastUpdate\"></span>\n</div>\n\n<div id=\"content\"><div class=\"empty\">Loading\u2026</div></div>\n\n<script>\nconst $ = s => document.querySelector(s);\nconst $$ = s => document.querySelectorAll(s);\nlet timer;\nlet activeTab = 'requests';\nlet activeLogFilter = 'all';\n\nfunction ms(v) {\n if (v == null) return '\u2014';\n if (v < 1000) return v + 'ms';\n return (v / 1000).toFixed(1) + 's';\n}\n\nfunction ago(ts) {\n const s = Math.floor((Date.now() - ts) / 1000);\n if (s < 60) return s + 's ago';\n if (s < 3600) return Math.floor(s/60) + 'm ago';\n return Math.floor(s/3600) + 'h ago';\n}\n\nfunction pctRow(label, color, phase) {\n return '<tr>'\n + '<td><span class=\"phase-dot\" style=\"background:' + color + '\"></span>' + label + '</td>'\n + '<td class=\"mono\">' + ms(phase.p50) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p95) + '</td>'\n + '<td class=\"mono\">' + ms(phase.p99) + '</td>'\n + '<td class=\"mono\">' + ms(phase.min) + '</td>'\n + '<td class=\"mono\">' + ms(phase.max) + '</td>'\n + '<td class=\"mono\">' + ms(phase.avg) + '</td>'\n + '</tr>';\n}\n\nfunction switchTab(tab) {\n activeTab = tab;\n $$('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));\n $$('.tab-panel').forEach(p => p.classList.toggle('active', p.id === 'panel-' + tab));\n}\n\nfunction setLogFilter(filter) {\n activeLogFilter = filter;\n $$('.log-filter').forEach(f => f.classList.toggle('active', f.dataset.filter === filter));\n $$('.log-row').forEach(r => {\n r.style.display = (filter === 'all' || r.dataset.category === filter) ? '' : 'none';\n });\n}\n\nasync function refresh() {\n const w = $('#window').value;\n try {\n const [summary, reqs, logs] = await Promise.all([\n fetch('/telemetry/summary?window=' + w).then(r => r.json()),\n fetch('/telemetry/requests?limit=50&since=' + (Date.now() - Number(w))).then(r => r.json()),\n fetch('/telemetry/logs?limit=200&since=' + (Date.now() - Number(w))).then(r => r.json()),\n ]);\n render(summary, reqs, logs);\n $('#lastUpdate').textContent = 'Updated ' + new Date().toLocaleTimeString();\n } catch (e) {\n $('#content').innerHTML = '<div class=\"empty\">Failed to load telemetry</div>';\n }\n}\n\nfunction render(s, reqs, logs) {\n if (s.totalRequests === 0 && (!logs || logs.length === 0)) {\n $('#content').innerHTML = '<div class=\"empty\">No requests recorded yet. Send a request through the proxy to see telemetry.</div>';\n return;\n }\n\n // Count lineage types for badges\n const lineageCounts = {};\n for (const r of reqs) { const t = r.lineageType || 'unknown'; lineageCounts[t] = (lineageCounts[t] || 0) + 1; }\n const logCounts = { session: 0, lineage: 0, error: 0 };\n for (const l of logs) { if (logCounts[l.category] !== undefined) logCounts[l.category]++; }\n\n // Tabs\n let html = '<div class=\"tabs\">'\n + '<div class=\"tab' + (activeTab === 'overview' ? ' active' : '') + '\" data-tab=\"overview\" onclick=\"switchTab('overview')\">Overview</div>'\n + '<div class=\"tab' + (activeTab === 'requests' ? ' active' : '') + '\" data-tab=\"requests\" onclick=\"switchTab('requests')\">'\n + 'Requests<span class=\"tab-badge\">' + reqs.length + '</span></div>'\n + '<div class=\"tab' + (activeTab === 'logs' ? ' active' : '') + '\" data-tab=\"logs\" onclick=\"switchTab('logs')\">'\n + 'Logs<span class=\"tab-badge\">' + logs.length + '</span></div>'\n + '</div>';\n\n // ==================== Overview tab ====================\n html += '<div id=\"panel-overview\" class=\"tab-panel' + (activeTab === 'overview' ? ' active' : '') + '\">';\n\n // Summary cards\n html += '<div class=\"cards\">'\n + card('Requests', s.totalRequests, s.requestsPerMinute.toFixed(1) + ' req/min')\n + card('Errors', s.errorCount, s.totalRequests > 0 ? ((s.errorCount/s.totalRequests)*100).toFixed(1) + '% error rate' : '')\n + card('Median Total', ms(s.totalDuration.p50), 'p95: ' + ms(s.totalDuration.p95))\n + card('Median TTFB', ms(s.ttfb.p50), 'p95: ' + ms(s.ttfb.p95))\n + card('Proxy Overhead', ms(s.proxyOverhead.p50), 'p95: ' + ms(s.proxyOverhead.p95))\n + card('Queue Wait', ms(s.queueWait.p50), 'p95: ' + ms(s.queueWait.p95))\n + '</div>';\n\n // Model breakdown\n const models = Object.entries(s.byModel);\n if (models.length > 0) {\n html += '<div class=\"cards\">';\n for (const [name, data] of models) {\n html += card(name, data.count + ' reqs', 'avg ' + ms(data.avgTotalMs));\n }\n html += '</div>';\n }\n\n // Lineage breakdown\n if (Object.keys(lineageCounts).length > 0) {\n html += '<div class=\"cards\">';\n const lineageColors = {continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'};\n for (const [type, count] of Object.entries(lineageCounts)) {\n html += '<div class=\"card\"><div class=\"card-label\">Lineage: ' + type + '</div>'\n + '<div class=\"card-value\" style=\"color:' + (lineageColors[type] || 'var(--text)') + '\">' + count + '</div></div>';\n }\n html += '</div>';\n }\n\n // Percentile table\n html += '<div class=\"section\"><div class=\"section-title\">Percentiles</div>'\n + '<table class=\"pct-table\"><thead><tr><th>Phase</th><th>p50</th><th>p95</th><th>p99</th><th>Min</th><th>Max</th><th>Avg</th></tr></thead><tbody>'\n + pctRow('Queue Wait', 'var(--queue)', s.queueWait)\n + pctRow('Proxy Overhead', 'var(--yellow)', s.proxyOverhead)\n + pctRow('TTFB', 'var(--ttfb)', s.ttfb)\n + pctRow('Upstream', 'var(--upstream)', s.upstreamDuration)\n + pctRow('Total', 'var(--purple)', s.totalDuration)\n + '</tbody></table></div>';\n\n html += '</div>'; // end overview panel\n\n // ==================== Requests tab ====================\n html += '<div id=\"panel-requests\" class=\"tab-panel' + (activeTab === 'requests' ? ' active' : '') + '\">';\n\n html += '<div class=\"legend\">'\n + '<span><span class=\"legend-dot\" style=\"background:var(--queue)\"></span>Queue</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--yellow)\"></span>Proxy</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--ttfb)\"></span>TTFB</span>'\n + '<span><span class=\"legend-dot\" style=\"background:var(--upstream)\"></span>Response</span>'\n + '</div>'\n + '<table><thead><tr><th>Time</th><th>Model</th><th>Mode</th><th>Session</th><th>Status</th>'\n + '<th>Queue</th><th>Proxy</th><th>TTFB</th><th>Total</th><th>Waterfall</th></tr></thead><tbody>';\n\n const maxTotal = Math.max(...reqs.map(r => r.totalDurationMs), 1);\n\n for (const r of reqs) {\n const statusClass = r.error ? 'status-err' : 'status-ok';\n const statusText = r.error ? r.error : r.status;\n const scale = 280 / maxTotal;\n const qW = Math.max(r.queueWaitMs * scale, 2);\n const ohW = Math.max((r.proxyOverheadMs || 0) * scale, 0);\n const ttfbW = Math.max((r.ttfbMs || 0) * scale, 0);\n const respW = Math.max((r.upstreamDurationMs - (r.ttfbMs || 0)) * scale, 2);\n\n const lineageBadge = r.lineageType ? '<span style=\"font-size:10px;padding:1px 5px;border-radius:3px;background:' + ({continuation:'var(--green)',compaction:'var(--yellow)',undo:'var(--purple)',diverged:'var(--red)',new:'var(--muted)'}[r.lineageType] || 'var(--muted)') + ';color:var(--bg)\">' + r.lineageType + '</span>' : '';\n const sessionShort = r.sdkSessionId ? r.sdkSessionId.slice(0, 8) : '\u2014';\n const msgCount = r.messageCount != null ? r.messageCount : '?';\n\n html += '<tr>'\n + '<td class=\"mono\">' + ago(r.timestamp) + '</td>'\n + '<td>' + (r.requestModel || r.model) + '<br><span style=\"font-size:10px;color:var(--muted)\">' + r.model + '</span></td>'\n + '<td>' + r.mode + '</td>'\n + '<td class=\"mono\">' + sessionShort + ' ' + lineageBadge + '<br><span style=\"font-size:10px;color:var(--muted)\">' + msgCount + ' msgs</span></td>'\n + '<td class=\"' + statusClass + '\">' + statusText + '</td>'\n + '<td class=\"mono\">' + ms(r.queueWaitMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.proxyOverheadMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.ttfbMs) + '</td>'\n + '<td class=\"mono\">' + ms(r.totalDurationMs) + '</td>'\n + '<td><div class=\"waterfall\">'\n + '<div class=\"waterfall-seg queue\" style=\"width:' + qW + 'px\"></div>'\n + '<div class=\"waterfall-seg overhead\" style=\"width:' + ohW + 'px\"></div>'\n + '<div class=\"waterfall-seg ttfb\" style=\"width:' + ttfbW + 'px\"></div>'\n + '<div class=\"waterfall-seg response\" style=\"width:' + respW + 'px\"></div>'\n + '</div></td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n html += '</div>'; // end requests panel\n\n // ==================== Logs tab ====================\n html += '<div id=\"panel-logs\" class=\"tab-panel' + (activeTab === 'logs' ? ' active' : '') + '\">';\n\n // Filter buttons\n html += '<div class=\"log-filters\">'\n + '<span class=\"log-filter' + (activeLogFilter === 'all' ? ' active' : '') + '\" data-filter=\"all\" onclick=\"setLogFilter('all')\">All<span class=\"tab-badge\">' + logs.length + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'session' ? ' active' : '') + '\" data-filter=\"session\" onclick=\"setLogFilter('session')\" style=\"--accent:var(--blue)\">Session<span class=\"tab-badge\">' + logCounts.session + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'lineage' ? ' active' : '') + '\" data-filter=\"lineage\" onclick=\"setLogFilter('lineage')\" style=\"--accent:var(--purple)\">Lineage<span class=\"tab-badge\">' + logCounts.lineage + '</span></span>'\n + '<span class=\"log-filter' + (activeLogFilter === 'error' ? ' active' : '') + '\" data-filter=\"error\" onclick=\"setLogFilter('error')\" style=\"--accent:var(--red)\">Error<span class=\"tab-badge\">' + logCounts.error + '</span></span>'\n + '</div>';\n\n if (logs.length === 0) {\n html += '<div class=\"empty\">No diagnostic logs in this time window.</div>';\n } else {\n html += '<table><thead><tr>'\n + '<th style=\"width:80px\">Time</th><th style=\"width:55px\">Level</th><th style=\"width:70px\">Category</th><th>Message</th>'\n + '</tr></thead><tbody>';\n\n for (const log of logs) {\n const levelColor = {info:'var(--green)',warn:'var(--yellow)',error:'var(--red)'}[log.level] || 'var(--muted)';\n const catColor = {session:'var(--blue)',lineage:'var(--purple)',error:'var(--red)',lifecycle:'var(--muted)'}[log.category] || 'var(--muted)';\n const display = (activeLogFilter === 'all' || log.category === activeLogFilter) ? '' : 'display:none';\n html += '<tr class=\"log-row\" data-category=\"' + log.category + '\" style=\"' + display + '\">'\n + '<td class=\"mono\">' + ago(log.timestamp) + '</td>'\n + '<td><span style=\"color:' + levelColor + '\">' + log.level + '</span></td>'\n + '<td><span style=\"color:' + catColor + '\">' + log.category + '</span></td>'\n + '<td class=\"mono\" style=\"word-break:break-all\">' + log.message + '</td>'\n + '</tr>';\n }\n html += '</tbody></table>';\n }\n html += '</div>'; // end logs panel\n\n $('#content').innerHTML = html;\n}\n\nfunction card(label, value, detail) {\n return '<div class=\"card\"><div class=\"card-label\">' + label + '</div>'\n + '<div class=\"card-value\">' + value + '</div>'\n + (detail ? '<div class=\"card-detail\">' + detail + '</div>' : '')\n + '</div>';\n}\n\n$('#autoRefresh').addEventListener('change', function() {\n clearInterval(timer);\n if (this.checked) timer = setInterval(refresh, 5000);\n});\n$('#window').addEventListener('change', refresh);\n\nrefresh();\ntimer = setInterval(refresh, 5000);\n</script>\n</body>\n</html>";
|
|
6
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/telemetry/dashboard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,aAAa,mjiBAoUlB,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { telemetryStore, TelemetryStore } from "./store";
|
|
2
|
+
export { diagnosticLog, DiagnosticLogStore } from "./logStore";
|
|
3
|
+
export { createTelemetryRoutes } from "./routes";
|
|
4
|
+
export { landingHtml } from "./landing";
|
|
5
|
+
export type { RequestMetric, TelemetrySummary, PhaseTiming } from "./types";
|
|
6
|
+
export type { DiagnosticLog } from "./logStore";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC3E,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meridian landing page.
|
|
3
|
+
* Shows system status, account info, quick stats, and agent setup snippets.
|
|
4
|
+
* Fetches /health and /telemetry/summary client-side for live data.
|
|
5
|
+
*/
|
|
6
|
+
export declare const landingHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Meridian</title>\n<style>\n :root {\n --bg: #0f0b1a; --surface: #1a1030; --surface2: #221840; --border: #2d2545;\n --text: #e0e7ff; --muted: #8b8aa0; --accent: #8b5cf6; --accent2: #6366f1;\n --green: #3fb950; --yellow: #d29922; --red: #f85149;\n --violet: #a78bfa; --lavender: #c4b5fd;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\n background: var(--bg); color: var(--text); line-height: 1.6; min-height: 100vh; }\n .container { max-width: 960px; margin: 0 auto; padding: 32px 24px; }\n\n .header { display: flex; align-items: center; gap: 16px; margin-bottom: 6px; }\n .header h1 { font-size: 28px; font-weight: 700; letter-spacing: 3px; }\n .tagline { color: var(--muted); font-size: 14px; margin-bottom: 32px; letter-spacing: 0.5px; }\n\n .status-banner { display: flex; align-items: center; gap: 12px; padding: 16px 20px;\n background: var(--surface); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 24px; }\n .status-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }\n .status-dot.healthy { background: var(--green); box-shadow: 0 0 8px rgba(63,185,80,0.4); }\n .status-dot.degraded { background: var(--yellow); }\n .status-dot.unhealthy { background: var(--red); }\n .status-text { font-size: 14px; font-weight: 500; }\n .status-detail { font-size: 12px; color: var(--muted); margin-left: auto; }\n\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }\n .card-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; font-weight: 500; }\n .card-value { font-size: 32px; font-weight: 700; margin-top: 4px; font-variant-numeric: tabular-nums; }\n .card-value.green { color: var(--green); }\n .card-value.violet { color: var(--violet); }\n .card-detail { font-size: 12px; color: var(--muted); margin-top: 4px; }\n\n .section { margin-bottom: 24px; }\n .section-title { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase;\n letter-spacing: 1px; margin-bottom: 12px; }\n .info-grid { display: grid; grid-template-columns: 120px 1fr; gap: 8px 16px; font-size: 13px;\n background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 20px; }\n .info-label { color: var(--muted); }\n .info-value { color: var(--text); font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 12px; }\n\n .snippet { background: var(--surface); border: 1px solid var(--border); border-radius: 12px;\n padding: 16px 20px; margin-top: 12px; }\n .snippet code { display: block; font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;\n font-size: 12px; color: var(--lavender); line-height: 1.8; white-space: pre-wrap; word-break: break-all; }\n .snippet-tabs { display: flex; gap: 0; margin-bottom: 12px; }\n .snippet-tab { padding: 6px 14px; font-size: 11px; font-weight: 500; cursor: pointer;\n color: var(--muted); background: var(--surface); border: 1px solid var(--border); border-bottom: none; }\n .snippet-tab:first-child { border-radius: 8px 0 0 0; }\n .snippet-tab:last-child { border-radius: 0 8px 0 0; }\n .snippet-tab.active { color: var(--violet); background: var(--surface2); border-color: var(--accent); }\n\n .links { display: flex; gap: 12px; margin-top: 32px; flex-wrap: wrap; }\n .link { padding: 10px 20px; background: var(--surface2); border: 1px solid var(--border);\n border-radius: 8px; color: var(--violet); text-decoration: none; font-size: 13px; font-weight: 500;\n transition: border-color 0.2s; }\n .link:hover { border-color: var(--accent); }\n\n .footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--border);\n font-size: 11px; color: var(--muted); text-align: center; }\n .footer a { color: var(--violet); text-decoration: none; }\n</style>\n</head>\n<body>\n<div class=\"container\">\n <div class=\"header\">\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" rx=\"14\" fill=\"#1C1830\"/>\n <line x1=\"32\" y1=\"10\" x2=\"32\" y2=\"54\" stroke=\"#8B7CF6\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>\n <path d=\"M16 20 A18 18 0 0 1 48 20\" fill=\"none\" stroke=\"#C4B5FD\" stroke-width=\"1.2\" opacity=\"0.4\"/>\n <path d=\"M16 44 A18 18 0 0 0 48 44\" fill=\"none\" stroke=\"#C4B5FD\" stroke-width=\"1.2\" opacity=\"0.4\"/>\n <path d=\"M20 30 A14 14 0 0 1 44 30\" fill=\"none\" stroke=\"#C4B5FD\" stroke-width=\"0.8\" opacity=\"0.2\"/>\n <path d=\"M20 34 A14 14 0 0 0 44 34\" fill=\"none\" stroke=\"#C4B5FD\" stroke-width=\"0.8\" opacity=\"0.2\"/>\n <circle cx=\"32\" cy=\"10\" r=\"3.5\" fill=\"#C4B5FD\"/><circle cx=\"32\" cy=\"54\" r=\"3.5\" fill=\"#C4B5FD\"/>\n <circle cx=\"32\" cy=\"32\" r=\"3\" fill=\"#8B7CF6\"/>\n </svg>\n <h1>MERIDIAN</h1>\n </div>\n <div class=\"tagline\">Harness Claude, your way.</div>\n <div id=\"content\"><div style=\"color:var(--muted);padding:40px;text-align:center\">Loading\u2026</div></div>\n</div>\n<script>\nfunction ms(v){if(v==null||v===0)return '\u2014';return v<1000?v+'ms':(v/1000).toFixed(1)+'s'}\nfunction card(l,v,d,c){return '<div class=\"card\"><div class=\"card-label\">'+l+'</div><div class=\"card-value '+(c||'')+'\">'+v+'</div>'+(d?'<div class=\"card-detail\">'+d+'</div>':'')+'</div>'}\n\nasync function refresh(){\n try{\n const [health,stats]=await Promise.all([fetch('/health').then(r=>r.json()),fetch('/telemetry/summary?window=86400000').then(r=>r.json())]);\n render(health,stats);\n }catch(e){document.getElementById('content').innerHTML='<div style=\"color:var(--red);padding:40px;text-align:center\">Could not connect</div>'}\n}\n\nfunction render(h,s){\n const st=h.status||'unknown',dot=st==='healthy'?'healthy':st==='degraded'?'degraded':'unhealthy';\n let o='';\n o+='<div class=\"status-banner\"><div class=\"status-dot '+dot+'\"></div><span class=\"status-text\">'+(st==='healthy'?'Operational':st==='degraded'?'Degraded':'Offline')+'</span><span class=\"status-detail\">Port '+location.port+' \u00B7 '+(h.mode||'internal')+' mode</span></div>';\n const er=s.totalRequests>0?((s.errorCount/s.totalRequests)*100).toFixed(1):'0';\n o+='<div class=\"grid\">'+card('Requests (24h)',s.totalRequests,'','violet')+card('Median Response',ms(s.totalDuration?.p50),'p95: '+ms(s.totalDuration?.p95),'')+card('Median TTFB',ms(s.ttfb?.p50),'p95: '+ms(s.ttfb?.p95),'')+card('Error Rate',er+'%',s.errorCount+' errors',parseFloat(er)>5?'':'green')+'</div>';\n o+='<div class=\"section\"><div class=\"section-title\">Account</div>';\n if(h.auth?.loggedIn){o+='<div class=\"info-grid\"><span class=\"info-label\">Email</span><span class=\"info-value\">'+(h.auth.email||'\u2014')+'</span><span class=\"info-label\">Subscription</span><span class=\"info-value\">'+(h.auth.subscriptionType||'\u2014')+'</span><span class=\"info-label\">Mode</span><span class=\"info-value\">'+(h.mode||'internal')+'</span><span class=\"info-label\">Endpoint</span><span class=\"info-value\">http://'+location.host+'</span></div>'}\n else{o+='<div class=\"info-grid\"><span class=\"info-label\">Status</span><span class=\"info-value\" style=\"color:var(--yellow)\">'+(h.error||'Not authenticated')+'</span></div>'}\n o+='</div>';\n if(s.byModel&&Object.keys(s.byModel).length>0){o+='<div class=\"section\"><div class=\"section-title\">Models (24h)</div><div class=\"grid\">';for(const[n,d]of Object.entries(s.byModel))o+=card(n,d.count,'avg '+ms(d.avgTotalMs),'');o+='</div></div>'}\n o+='<div class=\"section\"><div class=\"section-title\">Connect an Agent</div><div class=\"snippet\"><div class=\"snippet-tabs\"><div class=\"snippet-tab active\" onclick=\"showTab(this,'opencode')\">OpenCode</div><div class=\"snippet-tab\" onclick=\"showTab(this,'crush')\">Crush</div><div class=\"snippet-tab\" onclick=\"showTab(this,'generic')\">Any Tool</div></div><div id=\"tab-opencode\"><code>ANTHROPIC_API_KEY=x ANTHROPIC_BASE_URL=http://'+location.host+' opencode</code></div><div id=\"tab-crush\" style=\"display:none\"><code>'+JSON.stringify({providers:{meridian:{type:\"anthropic\",base_url:\"http://\"+location.host,api_key:\"x\",models:[{id:\"claude-sonnet-4-5-20250514\",name:\"Sonnet 4.5\"}]}}},null,2)+'</code></div><div id=\"tab-generic\" style=\"display:none\"><code>export ANTHROPIC_API_KEY=x\\nexport ANTHROPIC_BASE_URL=http://'+location.host+'</code></div></div></div>';\n o+='<div class=\"links\"><a href=\"/telemetry\" class=\"link\">\uD83D\uDCCA Telemetry</a><a href=\"/health\" class=\"link\">\uD83E\uDE7A Health</a><a href=\"/telemetry/summary\" class=\"link\">\uD83D\uDCC8 Stats API</a><a href=\"https://github.com/rynfar/meridian\" class=\"link\">\u2699\uFE0F GitHub</a></div>';\n o+='<div class=\"footer\">Meridian \u00B7 Built on the <a href=\"https://github.com/anthropics/claude-code-sdk-js\">Claude Code SDK</a></div>';\n document.getElementById('content').innerHTML=o;\n}\nfunction showTab(el,id){document.querySelectorAll('.snippet-tab').forEach(t=>t.classList.remove('active'));el.classList.add('active');document.querySelectorAll('[id^=\"tab-\"]').forEach(t=>t.style.display='none');document.getElementById('tab-'+id).style.display='block'}\nrefresh();setInterval(refresh,10000);\n</script>\n</body>\n</html>";
|
|
7
|
+
//# sourceMappingURL=landing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"landing.d.ts","sourceRoot":"","sources":["../../src/telemetry/landing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,WAAW,0jTAsHhB,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory ring buffer for diagnostic log messages.
|
|
3
|
+
*
|
|
4
|
+
* Captures session management events (compaction, undo, diverged, resume)
|
|
5
|
+
* and surfaces them via the telemetry API and dashboard. Replaces the need
|
|
6
|
+
* for users to dig through stderr to report issues.
|
|
7
|
+
*/
|
|
8
|
+
export interface DiagnosticLog {
|
|
9
|
+
/** Unix timestamp */
|
|
10
|
+
timestamp: number;
|
|
11
|
+
/** Log level */
|
|
12
|
+
level: "info" | "warn" | "error";
|
|
13
|
+
/** Log category for filtering */
|
|
14
|
+
category: "session" | "lineage" | "error" | "lifecycle";
|
|
15
|
+
/** Request ID (if associated with a request) */
|
|
16
|
+
requestId?: string;
|
|
17
|
+
/** Human-readable message */
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class DiagnosticLogStore {
|
|
21
|
+
private buffer;
|
|
22
|
+
private head;
|
|
23
|
+
private count;
|
|
24
|
+
private readonly capacity;
|
|
25
|
+
constructor(capacity?: number);
|
|
26
|
+
/** Append a log entry. */
|
|
27
|
+
log(entry: Omit<DiagnosticLog, "timestamp">): void;
|
|
28
|
+
/** Convenience: log a session event. */
|
|
29
|
+
session(message: string, requestId?: string): void;
|
|
30
|
+
/** Convenience: log a lineage event (compaction, undo, diverged). */
|
|
31
|
+
lineage(message: string, requestId?: string): void;
|
|
32
|
+
/** Convenience: log an error. */
|
|
33
|
+
error(message: string, requestId?: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Retrieve recent logs, newest first.
|
|
36
|
+
* @param options.limit - Max entries (default: 100)
|
|
37
|
+
* @param options.since - Only entries after this timestamp
|
|
38
|
+
* @param options.category - Filter by category
|
|
39
|
+
*/
|
|
40
|
+
getRecent(options?: {
|
|
41
|
+
limit?: number;
|
|
42
|
+
since?: number;
|
|
43
|
+
category?: string;
|
|
44
|
+
}): DiagnosticLog[];
|
|
45
|
+
/** Clear all stored logs. */
|
|
46
|
+
clear(): void;
|
|
47
|
+
}
|
|
48
|
+
/** Singleton instance used by the proxy. */
|
|
49
|
+
export declare const diagnosticLog: DiagnosticLogStore;
|
|
50
|
+
//# sourceMappingURL=logStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logStore.d.ts","sourceRoot":"","sources":["../../src/telemetry/logStore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB;IAChB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAChC,iCAAiC;IACjC,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAAA;IACvD,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAA;CAChB;AAID,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B,0BAA0B;IAC1B,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,GAAG,IAAI;IAMlD,wCAAwC;IACxC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlD,qEAAqE;IACrE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlD,iCAAiC;IACjC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIhD;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,aAAa,EAAE;IAgB/F,6BAA6B;IAC7B,KAAK,IAAI,IAAI;CAKd;AAED,4CAA4C;AAC5C,eAAO,MAAM,aAAa,oBAA2B,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry API routes.
|
|
3
|
+
*
|
|
4
|
+
* GET /telemetry — HTML dashboard
|
|
5
|
+
* GET /telemetry/requests — Recent request metrics (JSON)
|
|
6
|
+
* GET /telemetry/summary — Aggregate statistics (JSON)
|
|
7
|
+
* GET /telemetry/logs — Diagnostic logs (JSON)
|
|
8
|
+
*/
|
|
9
|
+
import { Hono } from "hono";
|
|
10
|
+
export declare function createTelemetryRoutes(): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
11
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/telemetry/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAK3B,wBAAgB,qBAAqB,+EA+CpC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory ring buffer for telemetry metrics.
|
|
3
|
+
*
|
|
4
|
+
* Append-only, fixed capacity, oldest entries overwritten.
|
|
5
|
+
* No disk I/O in the hot path. Data resets on proxy restart.
|
|
6
|
+
*/
|
|
7
|
+
import type { RequestMetric, TelemetrySummary } from "./types";
|
|
8
|
+
export declare class TelemetryStore {
|
|
9
|
+
private buffer;
|
|
10
|
+
private head;
|
|
11
|
+
private count;
|
|
12
|
+
private readonly capacity;
|
|
13
|
+
constructor(capacity?: number);
|
|
14
|
+
/** Record a completed request metric. */
|
|
15
|
+
record(metric: RequestMetric): void;
|
|
16
|
+
/** Get the total number of stored metrics. */
|
|
17
|
+
get size(): number;
|
|
18
|
+
/**
|
|
19
|
+
* Retrieve recent metrics, newest first.
|
|
20
|
+
* @param options.limit - Max entries to return (default: 50)
|
|
21
|
+
* @param options.since - Only entries after this timestamp
|
|
22
|
+
* @param options.model - Filter by model name
|
|
23
|
+
*/
|
|
24
|
+
getRecent(options?: {
|
|
25
|
+
limit?: number;
|
|
26
|
+
since?: number;
|
|
27
|
+
model?: string;
|
|
28
|
+
}): RequestMetric[];
|
|
29
|
+
/**
|
|
30
|
+
* Compute aggregate statistics over a time window.
|
|
31
|
+
* @param windowMs - Time window in ms (default: 1 hour)
|
|
32
|
+
*/
|
|
33
|
+
summarize(windowMs?: number): TelemetrySummary;
|
|
34
|
+
/** Clear all stored metrics. */
|
|
35
|
+
clear(): void;
|
|
36
|
+
}
|
|
37
|
+
/** Singleton store instance used by the proxy. */
|
|
38
|
+
export declare const telemetryStore: TelemetryStore;
|
|
39
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAe,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAY3E,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B,yCAAyC;IACzC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMnC,8CAA8C;IAC9C,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;OAKG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,aAAa,EAAE;IAiB5F;;;OAGG;IACH,SAAS,CAAC,QAAQ,GAAE,MAAuB,GAAG,gBAAgB;IAwE9D,gCAAgC;IAChC,KAAK,IAAI,IAAI;CAKd;AAkBD,kDAAkD;AAClD,eAAO,MAAM,cAAc,gBAAuB,CAAA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry types for request performance tracking.
|
|
3
|
+
*
|
|
4
|
+
* Each proxy request produces a RequestMetric capturing timing for every phase:
|
|
5
|
+
*
|
|
6
|
+
* queueEnter → queueStart → requestStart ──→ upstreamStart → firstChunk → done
|
|
7
|
+
* ├─ queueWait ─┤ ├─ proxyOverhead ─┤ │ │
|
|
8
|
+
* │ ├──── TTFB ────┤ │
|
|
9
|
+
* │ ├── upstream duration ──────┤
|
|
10
|
+
* ├──────────────────── total duration ────────────────────────────────────┤
|
|
11
|
+
*/
|
|
12
|
+
export interface RequestMetric {
|
|
13
|
+
/** Unique request identifier */
|
|
14
|
+
requestId: string;
|
|
15
|
+
/** When this metric was recorded */
|
|
16
|
+
timestamp: number;
|
|
17
|
+
/** Model used for SDK query (sonnet, opus, haiku, sonnet[1m], etc.) */
|
|
18
|
+
model: string;
|
|
19
|
+
/** Original model string from the client request (e.g. "claude-sonnet-4-6-20250312") */
|
|
20
|
+
requestModel?: string;
|
|
21
|
+
/** Streaming or non-streaming */
|
|
22
|
+
mode: "stream" | "non-stream";
|
|
23
|
+
/** Whether the request used session resume */
|
|
24
|
+
isResume: boolean;
|
|
25
|
+
/** Whether passthrough mode was active */
|
|
26
|
+
isPassthrough: boolean;
|
|
27
|
+
/** Session lineage classification: how the incoming messages related to the stored session.
|
|
28
|
+
* - continuation: normal follow-up (prefix matched)
|
|
29
|
+
* - compaction: older messages rewritten, recent preserved (suffix matched)
|
|
30
|
+
* - undo: user undid recent messages (prefix preserved, suffix changed) → SDK fork
|
|
31
|
+
* - diverged: no overlap with stored session → fresh start
|
|
32
|
+
* - new: first request, no stored session to compare */
|
|
33
|
+
lineageType?: "continuation" | "compaction" | "undo" | "diverged" | "new";
|
|
34
|
+
/** Number of messages in the request */
|
|
35
|
+
messageCount?: number;
|
|
36
|
+
/** SDK session ID used for this request (for correlating across turns) */
|
|
37
|
+
sdkSessionId?: string;
|
|
38
|
+
/** HTTP status code returned to the client */
|
|
39
|
+
status: number;
|
|
40
|
+
/** Time spent waiting in the concurrency queue (ms) */
|
|
41
|
+
queueWaitMs: number;
|
|
42
|
+
/** Time spent in proxy processing before SDK call — request parsing,
|
|
43
|
+
* session lookup, prompt building (ms). If this is high, the proxy
|
|
44
|
+
* is the bottleneck. Typically <50ms. */
|
|
45
|
+
proxyOverheadMs: number;
|
|
46
|
+
/** Time from SDK query start to first content chunk (ms) */
|
|
47
|
+
ttfbMs: number | null;
|
|
48
|
+
/** Total time the SDK query took (ms) */
|
|
49
|
+
upstreamDurationMs: number;
|
|
50
|
+
/** Total time from request received to response sent (ms) */
|
|
51
|
+
totalDurationMs: number;
|
|
52
|
+
/** Number of content blocks in the response */
|
|
53
|
+
contentBlocks: number;
|
|
54
|
+
/** Number of text stream events forwarded (streaming only) */
|
|
55
|
+
textEvents: number;
|
|
56
|
+
/** Error type if the request failed, null if successful */
|
|
57
|
+
error: string | null;
|
|
58
|
+
}
|
|
59
|
+
export interface PhaseTiming {
|
|
60
|
+
p50: number;
|
|
61
|
+
p95: number;
|
|
62
|
+
p99: number;
|
|
63
|
+
min: number;
|
|
64
|
+
max: number;
|
|
65
|
+
avg: number;
|
|
66
|
+
}
|
|
67
|
+
export interface TelemetrySummary {
|
|
68
|
+
/** Time window these stats cover */
|
|
69
|
+
windowMs: number;
|
|
70
|
+
/** Total requests in the window */
|
|
71
|
+
totalRequests: number;
|
|
72
|
+
/** Requests that returned an error */
|
|
73
|
+
errorCount: number;
|
|
74
|
+
/** Requests per minute */
|
|
75
|
+
requestsPerMinute: number;
|
|
76
|
+
/** Timing breakdowns by phase */
|
|
77
|
+
queueWait: PhaseTiming;
|
|
78
|
+
proxyOverhead: PhaseTiming;
|
|
79
|
+
ttfb: PhaseTiming;
|
|
80
|
+
upstreamDuration: PhaseTiming;
|
|
81
|
+
totalDuration: PhaseTiming;
|
|
82
|
+
/** Breakdown by model */
|
|
83
|
+
byModel: Record<string, {
|
|
84
|
+
count: number;
|
|
85
|
+
avgTotalMs: number;
|
|
86
|
+
}>;
|
|
87
|
+
/** Breakdown by mode */
|
|
88
|
+
byMode: Record<string, {
|
|
89
|
+
count: number;
|
|
90
|
+
avgTotalMs: number;
|
|
91
|
+
}>;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/telemetry/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;IAEjB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;IAEjB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IAEb,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,iCAAiC;IACjC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAA;IAE7B,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAA;IAEjB,0CAA0C;IAC1C,aAAa,EAAE,OAAO,CAAA;IAEtB;;;;;sEAKkE;IAClE,WAAW,CAAC,EAAE,cAAc,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,CAAA;IAEzE,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IAEd,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAA;IAEnB;;8CAE0C;IAC1C,eAAe,EAAE,MAAM,CAAA;IAEvB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAErB,yCAAyC;IACzC,kBAAkB,EAAE,MAAM,CAAA;IAE1B,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAA;IAEvB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAA;IAErB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAElB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,aAAa,EAAE,MAAM,CAAA;IACrB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,0BAA0B;IAC1B,iBAAiB,EAAE,MAAM,CAAA;IAEzB,iCAAiC;IACjC,SAAS,EAAE,WAAW,CAAA;IACtB,aAAa,EAAE,WAAW,CAAA;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,gBAAgB,EAAE,WAAW,CAAA;IAC7B,aAAa,EAAE,WAAW,CAAA;IAE1B,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9D,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC9D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Map with LRU (Least Recently Used) eviction.
|
|
3
|
+
*
|
|
4
|
+
* Entries are evicted oldest-first when the map exceeds maxSize.
|
|
5
|
+
* Both get() and set() refresh an entry's recency.
|
|
6
|
+
* An optional onEvict callback fires when entries are automatically evicted.
|
|
7
|
+
*
|
|
8
|
+
* Note: delete() does NOT fire onEvict — only automatic eviction from set() does.
|
|
9
|
+
*/
|
|
10
|
+
export declare class LRUMap<K, V> implements Iterable<[K, V]> {
|
|
11
|
+
private readonly maxSize;
|
|
12
|
+
private readonly onEvict?;
|
|
13
|
+
private readonly map;
|
|
14
|
+
constructor(maxSize: number, onEvict?: ((key: K, value: V) => void) | undefined);
|
|
15
|
+
get size(): number;
|
|
16
|
+
get(key: K): V | undefined;
|
|
17
|
+
set(key: K, value: V): this;
|
|
18
|
+
has(key: K): boolean;
|
|
19
|
+
delete(key: K): boolean;
|
|
20
|
+
clear(): void;
|
|
21
|
+
entries(): MapIterator<[K, V]>;
|
|
22
|
+
keys(): MapIterator<K>;
|
|
23
|
+
values(): MapIterator<V>;
|
|
24
|
+
forEach(callbackfn: (value: V, key: K, map: LRUMap<K, V>) => void): void;
|
|
25
|
+
[Symbol.iterator](): MapIterator<[K, V]>;
|
|
26
|
+
private evictOldest;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=lruMap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lruMap.d.ts","sourceRoot":"","sources":["../../src/utils/lruMap.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,qBAAa,MAAM,CAAC,CAAC,EAAE,CAAC,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAIjD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAJ3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;gBAGnB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,GAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,aAAA;IAGvD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAS1B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAW3B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAIpB,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAIvB,KAAK,IAAI,IAAI;IAIb,OAAO,IAAI,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAI9B,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC;IAItB,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC;IAIxB,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAIxE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAIxC,OAAO,CAAC,WAAW;CAUpB"}
|