@rulemetric/proxy 0.7.4 → 0.7.5

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/package.json CHANGED
@@ -1,9 +1,7 @@
1
1
  {
2
2
  "name": "@rulemetric/proxy",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "type": "module",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
7
5
  "publishConfig": {
8
6
  "access": "public"
9
7
  },
@@ -13,22 +11,9 @@
13
11
  "!addon/.pytest_cache",
14
12
  "!addon/.agents",
15
13
  "!addon/test_*.py",
16
- "dist",
17
- "Dockerfile",
18
- "!dist/**/*.map"
14
+ "Dockerfile"
19
15
  ],
20
- "dependencies": {
21
- "zod": "^3.24.0"
22
- },
23
- "devDependencies": {
24
- "@types/node": "^22.0.0",
25
- "typescript": "^5.9.0",
26
- "vitest": "^3.2.0",
27
- "@rulemetric/typescript-config": "0.0.0"
28
- },
29
16
  "scripts": {
30
- "build": "tsc",
31
- "test": "vitest run",
32
- "type-check": "tsc --noEmit"
17
+ "test:python": "python3 -m pytest addon -q"
33
18
  }
34
19
  }
package/dist/index.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export * from './types.js';
2
- export { BaseAdapter } from './providers/base.js';
3
- export { ProviderRegistry, detectProvider } from './providers/index.js';
4
- export { AnthropicAdapter } from './providers/anthropic.js';
5
- export { OpenAIAdapter } from './providers/openai.js';
6
- export { GeminiAdapter } from './providers/gemini.js';
7
- export { BedrockAdapter } from './providers/bedrock.js';
8
- export { findActiveSession } from './session-linker.js';
9
- export type { ActiveSession } from './session-linker.js';
10
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js DELETED
@@ -1,9 +0,0 @@
1
- export * from './types.js';
2
- export { BaseAdapter } from './providers/base.js';
3
- export { ProviderRegistry, detectProvider } from './providers/index.js';
4
- export { AnthropicAdapter } from './providers/anthropic.js';
5
- export { OpenAIAdapter } from './providers/openai.js';
6
- export { GeminiAdapter } from './providers/gemini.js';
7
- export { BedrockAdapter } from './providers/bedrock.js';
8
- export { findActiveSession } from './session-linker.js';
9
- //# sourceMappingURL=index.js.map
@@ -1,8 +0,0 @@
1
- import type { ContextSnapshot, Provider } from '../types.js';
2
- import { BaseAdapter } from './base.js';
3
- export declare class AnthropicAdapter extends BaseAdapter {
4
- provider: Provider;
5
- canHandle(host: string, path: string): boolean;
6
- parse(body: string): ContextSnapshot | null;
7
- }
8
- //# sourceMappingURL=anthropic.d.ts.map
@@ -1,135 +0,0 @@
1
- import { BaseAdapter } from './base.js';
2
- export class AnthropicAdapter extends BaseAdapter {
3
- provider = 'anthropic';
4
- canHandle(host, path) {
5
- return host.includes('anthropic') && path.startsWith('/v1/messages');
6
- }
7
- parse(body) {
8
- const parsed = this.safeJsonParse(body);
9
- if (!parsed || typeof parsed !== 'object')
10
- return null;
11
- const raw = parsed;
12
- // Model
13
- const model = typeof raw.model === 'string' ? raw.model : 'unknown';
14
- // System blocks
15
- const systemBlocks = [];
16
- if (typeof raw.system === 'string') {
17
- systemBlocks.push({ text: raw.system });
18
- }
19
- else if (Array.isArray(raw.system)) {
20
- for (const block of raw.system) {
21
- if (block && typeof block === 'object') {
22
- const b = block;
23
- const sb = { text: typeof b.text === 'string' ? b.text : '' };
24
- if (b.cache_control !== undefined) {
25
- sb.cacheControl = b.cache_control;
26
- }
27
- systemBlocks.push(sb);
28
- }
29
- }
30
- }
31
- // System prompt = concatenation of all system block texts
32
- const systemPrompt = systemBlocks.map((b) => b.text).join('\n');
33
- // Messages
34
- const messages = [];
35
- const rawMessages = Array.isArray(raw.messages) ? raw.messages : [];
36
- for (const msg of rawMessages) {
37
- if (!msg || typeof msg !== 'object')
38
- continue;
39
- const m = msg;
40
- const role = m.role;
41
- if (typeof m.content === 'string') {
42
- messages.push({
43
- role: role === 'user' ? 'user' : 'assistant',
44
- content: m.content,
45
- });
46
- }
47
- else if (Array.isArray(m.content)) {
48
- const textParts = [];
49
- let hasToolCalls = false;
50
- let isToolResult = false;
51
- for (const block of m.content) {
52
- if (!block || typeof block !== 'object')
53
- continue;
54
- const bl = block;
55
- if (bl.type === 'text' && typeof bl.text === 'string') {
56
- textParts.push(bl.text);
57
- }
58
- else if (bl.type === 'tool_use') {
59
- hasToolCalls = true;
60
- }
61
- else if (bl.type === 'tool_result') {
62
- isToolResult = true;
63
- // Extract text from tool_result content
64
- if (typeof bl.content === 'string') {
65
- textParts.push(bl.content);
66
- }
67
- else if (Array.isArray(bl.content)) {
68
- for (const inner of bl.content) {
69
- if (inner && typeof inner === 'object') {
70
- const ib = inner;
71
- if (ib.type === 'text' && typeof ib.text === 'string') {
72
- textParts.push(ib.text);
73
- }
74
- }
75
- }
76
- }
77
- }
78
- // type: "image" → skip
79
- }
80
- const normalized = {
81
- role: isToolResult ? 'tool' : role === 'user' ? 'user' : 'assistant',
82
- content: textParts.join('\n'),
83
- };
84
- if (hasToolCalls) {
85
- normalized.hasToolCalls = true;
86
- }
87
- messages.push(normalized);
88
- }
89
- }
90
- // Tools
91
- const tools = [];
92
- if (Array.isArray(raw.tools)) {
93
- for (const tool of raw.tools) {
94
- if (!tool || typeof tool !== 'object')
95
- continue;
96
- const t = tool;
97
- tools.push({
98
- name: typeof t.name === 'string' ? t.name : '',
99
- description: typeof t.description === 'string' ? t.description : '',
100
- parameters: t.input_schema ?? {},
101
- });
102
- }
103
- }
104
- // Injected instructions: extract from system prompt and user messages only
105
- const injectedInstructions = [];
106
- injectedInstructions.push(...this.extractInjectedInstructions(systemPrompt));
107
- for (const msg of messages) {
108
- if (msg.role === 'user') {
109
- injectedInstructions.push(...this.extractInjectedInstructions(msg.content));
110
- }
111
- }
112
- // Build snapshot
113
- const snapshot = {
114
- provider: 'anthropic',
115
- model,
116
- systemPrompt,
117
- systemBlocks,
118
- messages,
119
- tools,
120
- maxTokens: typeof raw.max_tokens === 'number' ? raw.max_tokens : 0,
121
- injectedInstructions,
122
- systemPromptHash: this.computeSystemHash(systemPrompt),
123
- messageCount: messages.length,
124
- toolCount: tools.length,
125
- estimatedTokens: 0,
126
- capturedAt: new Date().toISOString(),
127
- };
128
- if (typeof raw.temperature === 'number') {
129
- snapshot.temperature = raw.temperature;
130
- }
131
- snapshot.estimatedTokens = this.estimateTokens(snapshot);
132
- return snapshot;
133
- }
134
- }
135
- //# sourceMappingURL=anthropic.js.map
@@ -1,11 +0,0 @@
1
- import type { ContextSnapshot, Provider, ProviderAdapter } from '../types.js';
2
- export declare abstract class BaseAdapter implements ProviderAdapter {
3
- abstract provider: Provider;
4
- abstract canHandle(host: string, path: string): boolean;
5
- abstract parse(body: string): ContextSnapshot | null;
6
- extractInjectedInstructions(text: string): string[];
7
- computeSystemHash(systemPrompt: string): string;
8
- estimateTokens(snapshot: ContextSnapshot): number;
9
- safeJsonParse(body: string): unknown | null;
10
- }
11
- //# sourceMappingURL=base.d.ts.map
@@ -1,43 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- const SYSTEM_REMINDER_RE = /<system-reminder>([\s\S]*?)<\/system-reminder>/g;
3
- const MAX_BODY_SIZE = 2 * 1024 * 1024; // 2MB safety limit
4
- export class BaseAdapter {
5
- extractInjectedInstructions(text) {
6
- const results = [];
7
- let match;
8
- const re = new RegExp(SYSTEM_REMINDER_RE.source, 'gs');
9
- while ((match = re.exec(text)) !== null) {
10
- const content = match[1].trim();
11
- if (content) {
12
- results.push(content);
13
- }
14
- }
15
- return results;
16
- }
17
- computeSystemHash(systemPrompt) {
18
- return createHash('sha256').update(systemPrompt).digest('hex');
19
- }
20
- estimateTokens(snapshot) {
21
- let chars = snapshot.systemPrompt.length;
22
- for (const msg of snapshot.messages) {
23
- chars += msg.content.length;
24
- }
25
- for (const tool of snapshot.tools) {
26
- chars += tool.name.length + tool.description.length + JSON.stringify(tool.parameters).length;
27
- }
28
- return Math.ceil(chars / 4);
29
- }
30
- safeJsonParse(body) {
31
- if (body.length > MAX_BODY_SIZE) {
32
- console.warn(`[rulemetric] Request body truncated from ${body.length} to ${MAX_BODY_SIZE} bytes — snapshot may be incomplete`);
33
- body = body.slice(0, MAX_BODY_SIZE);
34
- }
35
- try {
36
- return JSON.parse(body);
37
- }
38
- catch {
39
- return null;
40
- }
41
- }
42
- }
43
- //# sourceMappingURL=base.js.map
@@ -1,8 +0,0 @@
1
- import type { ContextSnapshot, Provider } from '../types.js';
2
- import { BaseAdapter } from './base.js';
3
- export declare class BedrockAdapter extends BaseAdapter {
4
- provider: Provider;
5
- canHandle(host: string, path: string): boolean;
6
- parse(body: string): ContextSnapshot | null;
7
- }
8
- //# sourceMappingURL=bedrock.d.ts.map
@@ -1,125 +0,0 @@
1
- import { BaseAdapter } from './base.js';
2
- export class BedrockAdapter extends BaseAdapter {
3
- provider = 'bedrock';
4
- canHandle(host, path) {
5
- return host.includes('bedrock-runtime') && path.includes('/converse');
6
- }
7
- parse(body) {
8
- const parsed = this.safeJsonParse(body);
9
- if (!parsed || typeof parsed !== 'object')
10
- return null;
11
- const raw = parsed;
12
- // Model
13
- const model = typeof raw.modelId === 'string' ? raw.modelId : 'unknown';
14
- // System blocks — always an array of { text: string }
15
- const systemBlocks = [];
16
- if (Array.isArray(raw.system)) {
17
- for (const block of raw.system) {
18
- if (block && typeof block === 'object') {
19
- const b = block;
20
- systemBlocks.push({ text: typeof b.text === 'string' ? b.text : '' });
21
- }
22
- }
23
- }
24
- const systemPrompt = systemBlocks.map((b) => b.text).join('\n');
25
- // Messages
26
- const messages = [];
27
- const rawMessages = Array.isArray(raw.messages) ? raw.messages : [];
28
- for (const msg of rawMessages) {
29
- if (!msg || typeof msg !== 'object')
30
- continue;
31
- const m = msg;
32
- const role = m.role;
33
- if (Array.isArray(m.content)) {
34
- const textParts = [];
35
- let hasToolCalls = false;
36
- let isToolResult = false;
37
- for (const block of m.content) {
38
- if (!block || typeof block !== 'object')
39
- continue;
40
- const bl = block;
41
- if (typeof bl.text === 'string') {
42
- textParts.push(bl.text);
43
- }
44
- else if (bl.toolUse !== undefined) {
45
- hasToolCalls = true;
46
- }
47
- else if (bl.toolResult !== undefined) {
48
- isToolResult = true;
49
- // Extract text from toolResult.content[].text
50
- const tr = bl.toolResult;
51
- if (tr && Array.isArray(tr.content)) {
52
- for (const inner of tr.content) {
53
- if (inner && typeof inner === 'object') {
54
- const ib = inner;
55
- if (typeof ib.text === 'string') {
56
- textParts.push(ib.text);
57
- }
58
- }
59
- }
60
- }
61
- }
62
- }
63
- const normalized = {
64
- role: isToolResult ? 'tool' : role === 'user' ? 'user' : 'assistant',
65
- content: textParts.join('\n'),
66
- };
67
- if (hasToolCalls) {
68
- normalized.hasToolCalls = true;
69
- }
70
- messages.push(normalized);
71
- }
72
- }
73
- // Tools — toolConfig.tools[].toolSpec
74
- const tools = [];
75
- const toolConfig = raw.toolConfig;
76
- if (toolConfig && Array.isArray(toolConfig.tools)) {
77
- for (const tool of toolConfig.tools) {
78
- if (!tool || typeof tool !== 'object')
79
- continue;
80
- const t = tool;
81
- const spec = t.toolSpec;
82
- if (!spec)
83
- continue;
84
- const inputSchema = spec.inputSchema;
85
- const parameters = inputSchema?.json ?? {};
86
- tools.push({
87
- name: typeof spec.name === 'string' ? spec.name : '',
88
- description: typeof spec.description === 'string' ? spec.description : '',
89
- parameters,
90
- });
91
- }
92
- }
93
- // Injected instructions from system prompt and user messages
94
- const injectedInstructions = [];
95
- injectedInstructions.push(...this.extractInjectedInstructions(systemPrompt));
96
- for (const msg of messages) {
97
- if (msg.role === 'user') {
98
- injectedInstructions.push(...this.extractInjectedInstructions(msg.content));
99
- }
100
- }
101
- // Inference config
102
- const inferenceConfig = raw.inferenceConfig;
103
- const snapshot = {
104
- provider: 'bedrock',
105
- model,
106
- systemPrompt,
107
- systemBlocks,
108
- messages,
109
- tools,
110
- maxTokens: typeof inferenceConfig?.maxTokens === 'number' ? inferenceConfig.maxTokens : 0,
111
- injectedInstructions,
112
- systemPromptHash: this.computeSystemHash(systemPrompt),
113
- messageCount: messages.length,
114
- toolCount: tools.length,
115
- estimatedTokens: 0,
116
- capturedAt: new Date().toISOString(),
117
- };
118
- if (typeof inferenceConfig?.temperature === 'number') {
119
- snapshot.temperature = inferenceConfig.temperature;
120
- }
121
- snapshot.estimatedTokens = this.estimateTokens(snapshot);
122
- return snapshot;
123
- }
124
- }
125
- //# sourceMappingURL=bedrock.js.map
@@ -1,9 +0,0 @@
1
- import type { ContextSnapshot, Provider } from '../types.js';
2
- import { BaseAdapter } from './base.js';
3
- export declare class GeminiAdapter extends BaseAdapter {
4
- provider: Provider;
5
- private estimateGeminiTokens;
6
- canHandle(host: string, path: string): boolean;
7
- parse(body: string): ContextSnapshot | null;
8
- }
9
- //# sourceMappingURL=gemini.d.ts.map
@@ -1,140 +0,0 @@
1
- import { BaseAdapter } from './base.js';
2
- export class GeminiAdapter extends BaseAdapter {
3
- provider = 'gemini';
4
- estimateGeminiTokens(snapshot) {
5
- return Math.floor(snapshot.systemPrompt.length / 4);
6
- }
7
- canHandle(host, path) {
8
- return host.includes('googleapis.com') && path.includes(':generateContent');
9
- }
10
- parse(body) {
11
- const parsed = this.safeJsonParse(body);
12
- if (!parsed || typeof parsed !== 'object')
13
- return null;
14
- const raw = parsed;
15
- // Model
16
- const model = typeof raw.model === 'string' ? raw.model : 'unknown';
17
- // System blocks from systemInstruction
18
- const systemBlocks = [];
19
- const sysInst = raw.systemInstruction;
20
- if (sysInst && typeof sysInst === 'object' && Array.isArray(sysInst.parts)) {
21
- for (const part of sysInst.parts) {
22
- if (part && typeof part === 'object') {
23
- const p = part;
24
- if (typeof p.text === 'string') {
25
- systemBlocks.push({ text: p.text });
26
- }
27
- }
28
- }
29
- }
30
- // System prompt = concatenation of all system block texts
31
- const systemPrompt = systemBlocks.map((b) => b.text).join('\n');
32
- // Messages from contents[]
33
- const messages = [];
34
- const rawContents = Array.isArray(raw.contents) ? raw.contents : [];
35
- for (const content of rawContents) {
36
- if (!content || typeof content !== 'object')
37
- continue;
38
- const c = content;
39
- const role = c.role;
40
- const textParts = [];
41
- let hasToolCalls = false;
42
- let hasFunctionResponse = false;
43
- if (Array.isArray(c.parts)) {
44
- for (const part of c.parts) {
45
- if (!part || typeof part !== 'object')
46
- continue;
47
- const p = part;
48
- if (typeof p.text === 'string') {
49
- textParts.push(p.text);
50
- }
51
- else if (p.inlineData !== undefined) {
52
- // skip inline data
53
- }
54
- else if (p.functionCall !== undefined) {
55
- hasToolCalls = true;
56
- }
57
- else if (p.functionResponse !== undefined) {
58
- hasFunctionResponse = true;
59
- }
60
- }
61
- }
62
- let normalizedRole;
63
- if (hasFunctionResponse) {
64
- normalizedRole = 'tool';
65
- }
66
- else if (role === 'model') {
67
- normalizedRole = 'assistant';
68
- }
69
- else {
70
- normalizedRole = 'user';
71
- }
72
- const normalized = {
73
- role: normalizedRole,
74
- content: textParts.join(''),
75
- };
76
- if (hasToolCalls) {
77
- normalized.hasToolCalls = true;
78
- }
79
- messages.push(normalized);
80
- }
81
- // Tools from tools[].functionDeclarations[]
82
- const tools = [];
83
- if (Array.isArray(raw.tools)) {
84
- for (const toolGroup of raw.tools) {
85
- if (!toolGroup || typeof toolGroup !== 'object')
86
- continue;
87
- const tg = toolGroup;
88
- if (Array.isArray(tg.functionDeclarations)) {
89
- for (const decl of tg.functionDeclarations) {
90
- if (!decl || typeof decl !== 'object')
91
- continue;
92
- const d = decl;
93
- tools.push({
94
- name: typeof d.name === 'string' ? d.name : '',
95
- description: typeof d.description === 'string' ? d.description : '',
96
- parameters: d.parameters ?? {},
97
- });
98
- }
99
- }
100
- }
101
- }
102
- // Generation config
103
- const genConfig = raw.generationConfig;
104
- const maxTokens = genConfig && typeof genConfig === 'object' && typeof genConfig.maxOutputTokens === 'number'
105
- ? genConfig.maxOutputTokens
106
- : 0;
107
- // Injected instructions: extract from system prompt and user messages
108
- const injectedInstructions = [];
109
- injectedInstructions.push(...this.extractInjectedInstructions(systemPrompt));
110
- for (const msg of messages) {
111
- if (msg.role === 'user') {
112
- injectedInstructions.push(...this.extractInjectedInstructions(msg.content));
113
- }
114
- }
115
- // Build snapshot
116
- const snapshot = {
117
- provider: 'gemini',
118
- model,
119
- systemPrompt,
120
- systemBlocks,
121
- messages,
122
- tools,
123
- maxTokens,
124
- injectedInstructions,
125
- systemPromptHash: this.computeSystemHash(systemPrompt),
126
- messageCount: messages.length,
127
- toolCount: tools.length,
128
- estimatedTokens: 0,
129
- capturedAt: new Date().toISOString(),
130
- };
131
- if (genConfig &&
132
- typeof genConfig === 'object' &&
133
- typeof genConfig.temperature === 'number') {
134
- snapshot.temperature = genConfig.temperature;
135
- }
136
- snapshot.estimatedTokens = this.estimateGeminiTokens(snapshot);
137
- return snapshot;
138
- }
139
- }
140
- //# sourceMappingURL=gemini.js.map
@@ -1,8 +0,0 @@
1
- import type { Provider, ProviderAdapter } from '../types.js';
2
- export declare function detectProvider(host: string, path: string): Provider | null;
3
- export declare class ProviderRegistry {
4
- private adapters;
5
- register(adapter: ProviderAdapter): void;
6
- detect(host: string, path: string): ProviderAdapter | null;
7
- }
8
- //# sourceMappingURL=index.d.ts.map
@@ -1,116 +0,0 @@
1
- /** Map of known OpenAI-compatible hostnames to provider names. */
2
- const KNOWN_OPENAI_COMPAT_HOSTS = {
3
- 'api.openai.com': 'openai',
4
- 'api.groq.com': 'groq',
5
- 'api.together.xyz': 'together',
6
- 'api.fireworks.ai': 'fireworks',
7
- 'api.deepseek.com': 'deepseek',
8
- 'api.perplexity.ai': 'perplexity',
9
- 'api.cerebras.ai': 'cerebras',
10
- 'api.sambanova.ai': 'sambanova',
11
- 'api.x.ai': 'xai',
12
- 'api.mistral.ai': 'mistral',
13
- 'integrate.api.nvidia.com': 'nvidia_nim',
14
- 'api.ai21.com': 'ai21',
15
- 'api.cohere.com': 'cohere',
16
- 'openrouter.ai': 'openrouter',
17
- 'openai-proxy.replicate.com': 'replicate',
18
- };
19
- /** Derive a short provider name from a hostname (for unknown OpenAI-compat providers). */
20
- function providerFromHost(host) {
21
- const h = host.split(':')[0];
22
- const parts = h.split('.');
23
- if (parts.length >= 2) {
24
- const candidate = parts[parts.length - 2];
25
- if (['co', 'com', 'net', 'org', 'io', 'ai'].includes(candidate)) {
26
- return parts.length >= 3 ? parts[parts.length - 3] : parts[0];
27
- }
28
- return candidate;
29
- }
30
- return parts[0];
31
- }
32
- /**
33
- * Specific rules for non-OpenAI formats. These are checked first,
34
- * before the generic OpenAI-compatible catch-all.
35
- */
36
- const specificRules = [
37
- {
38
- provider: 'anthropic',
39
- match: (host, path) => host === 'api.anthropic.com' && path.startsWith('/v1/messages'),
40
- },
41
- {
42
- provider: 'gemini',
43
- match: (host, path) => host === 'generativelanguage.googleapis.com' && path.includes(':generateContent'),
44
- },
45
- {
46
- provider: 'bedrock',
47
- match: (host, path) => host.includes('bedrock-runtime') && path.includes('/converse'),
48
- },
49
- {
50
- provider: 'cortex',
51
- match: (host, path) => host.endsWith('.snowflakecomputing.com') &&
52
- path.includes('/api/v2/cortex/inference:complete'),
53
- },
54
- {
55
- provider: 'azure_openai',
56
- match: (host, path) => host.endsWith('.openai.azure.com') && path.includes('/chat/completions'),
57
- },
58
- {
59
- provider: 'azure_ai',
60
- match: (host, path) => host.endsWith('.models.ai.azure.com') && path.includes('/chat/completions'),
61
- },
62
- {
63
- provider: 'cloudflare',
64
- match: (host, path) => host === 'api.cloudflare.com' && path.includes('/ai/v1/chat/completions'),
65
- },
66
- {
67
- provider: 'databricks',
68
- match: (host, path) => host.endsWith('.cloud.databricks.com') && path.includes('/invocations'),
69
- },
70
- ];
71
- export function detectProvider(host, path) {
72
- if (!host)
73
- return null;
74
- // 1. Check specific non-OpenAI and special-case rules
75
- for (const rule of specificRules) {
76
- if (rule.match(host, path)) {
77
- return rule.provider;
78
- }
79
- }
80
- // 2. Check known OpenAI-compatible hosts
81
- for (const [knownHost, provider] of Object.entries(KNOWN_OPENAI_COMPAT_HOSTS)) {
82
- if (host === knownHost || host.endsWith('.' + knownHost)) {
83
- if (path.includes('chat/completions') || path.includes('/completions')) {
84
- return provider;
85
- }
86
- }
87
- }
88
- // 3. Ollama native API
89
- if (host.startsWith('localhost') && path.startsWith('/api/chat')) {
90
- return 'ollama';
91
- }
92
- // 4. Ollama OpenAI-compat endpoint
93
- if (host.startsWith('localhost') && path.includes('/v1/chat/completions')) {
94
- return 'ollama';
95
- }
96
- // 5. Generic catch-all: any request to a chat/completions path
97
- if (path.includes('chat/completions')) {
98
- return providerFromHost(host);
99
- }
100
- return null;
101
- }
102
- export class ProviderRegistry {
103
- adapters = [];
104
- register(adapter) {
105
- this.adapters.push(adapter);
106
- }
107
- detect(host, path) {
108
- for (const adapter of this.adapters) {
109
- if (adapter.canHandle(host, path)) {
110
- return adapter;
111
- }
112
- }
113
- return null;
114
- }
115
- }
116
- //# sourceMappingURL=index.js.map
@@ -1,9 +0,0 @@
1
- import type { ContextSnapshot, Provider } from '../types.js';
2
- import { BaseAdapter } from './base.js';
3
- export declare class OpenAIAdapter extends BaseAdapter {
4
- provider: Provider;
5
- canHandle(host: string, path: string): boolean;
6
- parse(body: string): ContextSnapshot | null;
7
- private extractContent;
8
- }
9
- //# sourceMappingURL=openai.d.ts.map
@@ -1,129 +0,0 @@
1
- import { BaseAdapter } from './base.js';
2
- export class OpenAIAdapter extends BaseAdapter {
3
- provider = 'openai';
4
- canHandle(host, path) {
5
- return ((host.includes('openai') || host.includes('azure') || host.includes('ollama') || host.includes('localhost')) &&
6
- path.includes('/v1/chat/completions'));
7
- }
8
- parse(body) {
9
- const parsed = this.safeJsonParse(body);
10
- if (!parsed || typeof parsed !== 'object')
11
- return null;
12
- const raw = parsed;
13
- // Model
14
- const model = typeof raw.model === 'string' ? raw.model : 'unknown';
15
- // Separate system messages, developer messages, and regular messages
16
- const rawMessages = Array.isArray(raw.messages) ? raw.messages : [];
17
- const systemTexts = [];
18
- const developerTexts = [];
19
- const messages = [];
20
- for (const msg of rawMessages) {
21
- if (!msg || typeof msg !== 'object')
22
- continue;
23
- const m = msg;
24
- const role = m.role;
25
- const content = this.extractContent(m);
26
- if (role === 'system') {
27
- systemTexts.push(content);
28
- }
29
- else if (role === 'developer') {
30
- developerTexts.push(content);
31
- }
32
- else if (role === 'user') {
33
- messages.push({ role: 'user', content });
34
- }
35
- else if (role === 'assistant') {
36
- const normalized = { role: 'assistant', content };
37
- if (Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
38
- normalized.hasToolCalls = true;
39
- }
40
- messages.push(normalized);
41
- }
42
- else if (role === 'tool') {
43
- messages.push({ role: 'tool', content });
44
- }
45
- }
46
- // System prompt from system messages
47
- const systemPrompt = systemTexts.join('\n');
48
- // System blocks
49
- const systemBlocks = systemTexts.map((text) => ({ text }));
50
- // Tools
51
- const tools = [];
52
- if (Array.isArray(raw.tools)) {
53
- for (const tool of raw.tools) {
54
- if (!tool || typeof tool !== 'object')
55
- continue;
56
- const t = tool;
57
- if (t.function && typeof t.function === 'object') {
58
- const fn = t.function;
59
- tools.push({
60
- name: typeof fn.name === 'string' ? fn.name : '',
61
- description: typeof fn.description === 'string' ? fn.description : '',
62
- parameters: fn.parameters ?? {},
63
- });
64
- }
65
- }
66
- }
67
- // Injected instructions: extract from system prompt and user messages
68
- const injectedInstructions = [];
69
- injectedInstructions.push(...this.extractInjectedInstructions(systemPrompt));
70
- for (const msg of messages) {
71
- if (msg.role === 'user') {
72
- injectedInstructions.push(...this.extractInjectedInstructions(msg.content));
73
- }
74
- }
75
- // Developer messages → injected instructions
76
- for (const text of developerTexts) {
77
- injectedInstructions.push(text);
78
- }
79
- // max_completion_tokens takes precedence over max_tokens
80
- let maxTokens = 0;
81
- if (typeof raw.max_completion_tokens === 'number') {
82
- maxTokens = raw.max_completion_tokens;
83
- }
84
- else if (typeof raw.max_tokens === 'number') {
85
- maxTokens = raw.max_tokens;
86
- }
87
- // Build snapshot
88
- const snapshot = {
89
- provider: 'openai',
90
- model,
91
- systemPrompt,
92
- systemBlocks,
93
- messages,
94
- tools,
95
- maxTokens,
96
- injectedInstructions,
97
- systemPromptHash: this.computeSystemHash(systemPrompt),
98
- messageCount: messages.length,
99
- toolCount: tools.length,
100
- estimatedTokens: 0,
101
- capturedAt: new Date().toISOString(),
102
- };
103
- if (typeof raw.temperature === 'number') {
104
- snapshot.temperature = raw.temperature;
105
- }
106
- snapshot.estimatedTokens = this.estimateTokens(snapshot);
107
- return snapshot;
108
- }
109
- extractContent(msg) {
110
- if (typeof msg.content === 'string') {
111
- return msg.content;
112
- }
113
- if (Array.isArray(msg.content)) {
114
- const textParts = [];
115
- for (const part of msg.content) {
116
- if (!part || typeof part !== 'object')
117
- continue;
118
- const p = part;
119
- if (p.type === 'text' && typeof p.text === 'string') {
120
- textParts.push(p.text);
121
- }
122
- // Skip image_url and other non-text parts
123
- }
124
- return textParts.join('\n');
125
- }
126
- return '';
127
- }
128
- }
129
- //# sourceMappingURL=openai.js.map
@@ -1,6 +0,0 @@
1
- export interface ActiveSession {
2
- sessionId: string;
3
- rwSessionId: string;
4
- }
5
- export declare function findActiveSession(sessionDir: string): ActiveSession | null;
6
- //# sourceMappingURL=session-linker.d.ts.map
@@ -1,68 +0,0 @@
1
- import { readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
- const MAX_SESSION_AGE_MS = 4 * 60 * 60 * 1000; // 4 hours
5
- export function findActiveSession(sessionDir) {
6
- let entries;
7
- try {
8
- entries = readdirSync(sessionDir);
9
- }
10
- catch (err) {
11
- const code = err.code;
12
- if (code === 'ENOENT') {
13
- // Directory doesn't exist — session tracking likely not installed
14
- return null;
15
- }
16
- // Permission error or other OS error — worth logging
17
- console.warn(`[rulemetric] Cannot read session dir ${sessionDir}: ${err}`);
18
- return null;
19
- }
20
- const now = Date.now();
21
- // Sort by mtime descending (most recent first)
22
- const files = entries
23
- .map((name) => {
24
- const fullPath = join(sessionDir, name);
25
- try {
26
- const stat = statSync(fullPath);
27
- if (!stat.isFile())
28
- return null;
29
- // Skip stale files
30
- if (now - stat.mtimeMs > MAX_SESSION_AGE_MS)
31
- return null;
32
- return { path: fullPath, name, mtime: stat.mtimeMs };
33
- }
34
- catch {
35
- return null;
36
- }
37
- })
38
- .filter((f) => f !== null)
39
- .sort((a, b) => b.mtime - a.mtime);
40
- for (const file of files) {
41
- try {
42
- const content = readFileSync(file.path, 'utf-8').trim();
43
- if (!content)
44
- continue;
45
- const lines = content.split('\n');
46
- const sessionId = lines[0].trim();
47
- const rwSessionId = lines.length > 1 ? lines[1].trim() : '';
48
- // Validate UUID format
49
- if (!UUID_RE.test(sessionId)) {
50
- console.warn(`[rulemetric] Invalid session ID format in ${file.name}: '${sessionId.slice(0, 50)}'`);
51
- continue;
52
- }
53
- if (rwSessionId && !UUID_RE.test(rwSessionId)) {
54
- console.warn(`[rulemetric] Invalid RW session ID in ${file.name}: '${rwSessionId.slice(0, 50)}'`);
55
- return { sessionId, rwSessionId: '' };
56
- }
57
- if (sessionId) {
58
- return { sessionId, rwSessionId };
59
- }
60
- }
61
- catch (err) {
62
- console.warn(`[rulemetric] Cannot read session file ${file.name}: ${err}`);
63
- continue;
64
- }
65
- }
66
- return null;
67
- }
68
- //# sourceMappingURL=session-linker.js.map
package/dist/types.d.ts DELETED
@@ -1,41 +0,0 @@
1
- export type KnownProvider = 'anthropic' | 'openai' | 'azure_openai' | 'azure_ai' | 'gemini' | 'bedrock' | 'ollama' | 'cortex' | 'groq' | 'together' | 'fireworks' | 'deepseek' | 'perplexity' | 'cerebras' | 'sambanova' | 'xai' | 'mistral' | 'nvidia_nim' | 'ai21' | 'cohere' | 'openrouter' | 'replicate' | 'cloudflare' | 'databricks';
2
- /** Known providers or any string for generic OpenAI-compatible providers. */
3
- export type Provider = KnownProvider | (string & {});
4
- export interface SystemBlock {
5
- text: string;
6
- cacheControl?: unknown;
7
- }
8
- export interface NormalizedMessage {
9
- role: 'user' | 'assistant' | 'tool';
10
- content: string;
11
- hasToolCalls?: boolean;
12
- }
13
- export interface NormalizedTool {
14
- name: string;
15
- description: string;
16
- parameters: Record<string, unknown>;
17
- }
18
- export interface ContextSnapshot {
19
- provider: Provider;
20
- model: string;
21
- systemPrompt: string;
22
- systemBlocks: SystemBlock[];
23
- messages: NormalizedMessage[];
24
- tools: NormalizedTool[];
25
- maxTokens: number;
26
- temperature?: number;
27
- injectedInstructions: string[];
28
- systemPromptHash: string;
29
- messageCount: number;
30
- toolCount: number;
31
- estimatedTokens: number;
32
- sessionId?: string;
33
- projectPath?: string;
34
- capturedAt: string;
35
- }
36
- export interface ProviderAdapter {
37
- provider: Provider;
38
- canHandle(host: string, path: string): boolean;
39
- parse(body: string): ContextSnapshot | null;
40
- }
41
- //# sourceMappingURL=types.d.ts.map
package/dist/types.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map