overarch-linux-arm64 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/dist/bin/overarch.d.ts +2 -0
  4. package/dist/bin/overarch.js +178 -0
  5. package/dist/src/agent/context.d.ts +14 -0
  6. package/dist/src/agent/context.js +200 -0
  7. package/dist/src/agent/context_compressor.d.ts +21 -0
  8. package/dist/src/agent/context_compressor.js +90 -0
  9. package/dist/src/agent/manifest.d.ts +22 -0
  10. package/dist/src/agent/manifest.js +131 -0
  11. package/dist/src/agent/planner.d.ts +38 -0
  12. package/dist/src/agent/planner.js +118 -0
  13. package/dist/src/agent/runtime.d.ts +59 -0
  14. package/dist/src/agent/runtime.js +400 -0
  15. package/dist/src/cli/approval.d.ts +13 -0
  16. package/dist/src/cli/approval.js +71 -0
  17. package/dist/src/cli/args.d.ts +15 -0
  18. package/dist/src/cli/args.js +47 -0
  19. package/dist/src/cli/commands/audit.d.ts +10 -0
  20. package/dist/src/cli/commands/audit.js +79 -0
  21. package/dist/src/cli/commands/companion.d.ts +1 -0
  22. package/dist/src/cli/commands/companion.js +143 -0
  23. package/dist/src/cli/commands/config.d.ts +1 -0
  24. package/dist/src/cli/commands/config.js +135 -0
  25. package/dist/src/cli/commands/doctor.d.ts +1 -0
  26. package/dist/src/cli/commands/doctor.js +217 -0
  27. package/dist/src/cli/commands/init.d.ts +1 -0
  28. package/dist/src/cli/commands/init.js +134 -0
  29. package/dist/src/cli/commands/memory.d.ts +2 -0
  30. package/dist/src/cli/commands/memory.js +98 -0
  31. package/dist/src/cli/commands/onboarding.d.ts +3 -0
  32. package/dist/src/cli/commands/onboarding.js +223 -0
  33. package/dist/src/cli/commands/sessions.d.ts +1 -0
  34. package/dist/src/cli/commands/sessions.js +22 -0
  35. package/dist/src/cli/commands/status.d.ts +1 -0
  36. package/dist/src/cli/commands/status.js +37 -0
  37. package/dist/src/cli/commands/update.d.ts +9 -0
  38. package/dist/src/cli/commands/update.js +105 -0
  39. package/dist/src/cli/repl.d.ts +3 -0
  40. package/dist/src/cli/repl.js +821 -0
  41. package/dist/src/cli/ui.d.ts +53 -0
  42. package/dist/src/cli/ui.js +215 -0
  43. package/dist/src/companion/petdex_adapter.d.ts +31 -0
  44. package/dist/src/companion/petdex_adapter.js +171 -0
  45. package/dist/src/companion/petdex_bridge.d.ts +65 -0
  46. package/dist/src/companion/petdex_bridge.js +356 -0
  47. package/dist/src/companion/types.d.ts +57 -0
  48. package/dist/src/companion/types.js +11 -0
  49. package/dist/src/config/index.d.ts +24 -0
  50. package/dist/src/config/index.js +237 -0
  51. package/dist/src/config/types.d.ts +58 -0
  52. package/dist/src/config/types.js +7 -0
  53. package/dist/src/core/diagnostics.d.ts +48 -0
  54. package/dist/src/core/diagnostics.js +115 -0
  55. package/dist/src/core/index.d.ts +23 -0
  56. package/dist/src/core/index.js +23 -0
  57. package/dist/src/core/mission.d.ts +55 -0
  58. package/dist/src/core/mission.js +173 -0
  59. package/dist/src/index.d.ts +1 -0
  60. package/dist/src/index.js +1 -0
  61. package/dist/src/learning/memory_manager.d.ts +26 -0
  62. package/dist/src/learning/memory_manager.js +211 -0
  63. package/dist/src/learning/types.d.ts +54 -0
  64. package/dist/src/learning/types.js +1 -0
  65. package/dist/src/mcp/registry.d.ts +17 -0
  66. package/dist/src/mcp/registry.js +97 -0
  67. package/dist/src/mcp/types.d.ts +13 -0
  68. package/dist/src/mcp/types.js +1 -0
  69. package/dist/src/providers/anthropic.d.ts +10 -0
  70. package/dist/src/providers/anthropic.js +95 -0
  71. package/dist/src/providers/base.d.ts +15 -0
  72. package/dist/src/providers/base.js +108 -0
  73. package/dist/src/providers/factory.d.ts +28 -0
  74. package/dist/src/providers/factory.js +142 -0
  75. package/dist/src/providers/gemini.d.ts +10 -0
  76. package/dist/src/providers/gemini.js +97 -0
  77. package/dist/src/providers/openai.d.ts +9 -0
  78. package/dist/src/providers/openai.js +85 -0
  79. package/dist/src/providers/openrouter.d.ts +5 -0
  80. package/dist/src/providers/openrouter.js +14 -0
  81. package/dist/src/providers/registry.d.ts +19 -0
  82. package/dist/src/providers/registry.js +209 -0
  83. package/dist/src/providers/types.d.ts +59 -0
  84. package/dist/src/providers/types.js +1 -0
  85. package/dist/src/providers/validator.d.ts +24 -0
  86. package/dist/src/providers/validator.js +161 -0
  87. package/dist/src/security/audit.d.ts +56 -0
  88. package/dist/src/security/audit.js +154 -0
  89. package/dist/src/security/guardrails.d.ts +9 -0
  90. package/dist/src/security/guardrails.js +32 -0
  91. package/dist/src/security/permissions.d.ts +12 -0
  92. package/dist/src/security/permissions.js +68 -0
  93. package/dist/src/security/policy.d.ts +220 -0
  94. package/dist/src/security/policy.js +720 -0
  95. package/dist/src/session/manager.d.ts +22 -0
  96. package/dist/src/session/manager.js +172 -0
  97. package/dist/src/session/types.d.ts +36 -0
  98. package/dist/src/session/types.js +1 -0
  99. package/dist/src/tools/checkpoint.d.ts +26 -0
  100. package/dist/src/tools/checkpoint.js +160 -0
  101. package/dist/src/tools/diagnostics.d.ts +2 -0
  102. package/dist/src/tools/diagnostics.js +125 -0
  103. package/dist/src/tools/diff.d.ts +14 -0
  104. package/dist/src/tools/diff.js +124 -0
  105. package/dist/src/tools/filesystem.d.ts +2 -0
  106. package/dist/src/tools/filesystem.js +224 -0
  107. package/dist/src/tools/git.d.ts +2 -0
  108. package/dist/src/tools/git.js +138 -0
  109. package/dist/src/tools/markdown.d.ts +33 -0
  110. package/dist/src/tools/markdown.js +190 -0
  111. package/dist/src/tools/project.d.ts +2 -0
  112. package/dist/src/tools/project.js +63 -0
  113. package/dist/src/tools/registry.d.ts +14 -0
  114. package/dist/src/tools/registry.js +230 -0
  115. package/dist/src/tools/search.d.ts +2 -0
  116. package/dist/src/tools/search.js +136 -0
  117. package/dist/src/tools/shell.d.ts +2 -0
  118. package/dist/src/tools/shell.js +74 -0
  119. package/dist/src/tools/symbols.d.ts +30 -0
  120. package/dist/src/tools/symbols.js +155 -0
  121. package/dist/src/tools/types.d.ts +28 -0
  122. package/dist/src/tools/types.js +1 -0
  123. package/dist/src/tools/web_search.d.ts +14 -0
  124. package/dist/src/tools/web_search.js +154 -0
  125. package/install.sh +29 -0
  126. package/overarch +15 -0
  127. package/package.json +28 -0
@@ -0,0 +1,142 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ import { AnthropicProvider } from './anthropic.js';
3
+ import { GeminiProvider } from './gemini.js';
4
+ import { OpenRouterProvider } from './openrouter.js';
5
+ import { CANONICAL_MODELS, SUPPORTED_PROVIDERS, normalizeModel } from './registry.js';
6
+ export { CANONICAL_MODELS, SUPPORTED_PROVIDERS, normalizeModel };
7
+ import { getApiKey } from '../config/index.js';
8
+ export const MODEL_REGISTRY = CANONICAL_MODELS;
9
+ export class OfflineProvider {
10
+ name = 'offline';
11
+ async complete(request) {
12
+ return {
13
+ content: 'Offline mode is currently active. Connect an AI provider with `/setup` or `/config provider` to enable AI completions.',
14
+ toolCalls: [],
15
+ finishReason: 'stop'
16
+ };
17
+ }
18
+ async *stream(request) {
19
+ yield {
20
+ type: 'text',
21
+ content: 'Offline mode is currently active. Connect an AI provider with `/setup` or `/config provider` to enable AI completions.'
22
+ };
23
+ yield { type: 'done' };
24
+ }
25
+ }
26
+ export function createProvider(providerName, config = {}) {
27
+ const normProvider = providerName.toLowerCase().trim();
28
+ if (normProvider === 'none' || normProvider === 'offline') {
29
+ return new OfflineProvider();
30
+ }
31
+ const apiKey = config.apiKey || getApiKey(normProvider);
32
+ if (!apiKey) {
33
+ throw new Error(`API key not found for provider: ${providerName}`);
34
+ }
35
+ switch (normProvider) {
36
+ case 'openai':
37
+ return new OpenAIProvider(apiKey, config.baseUrl);
38
+ case 'anthropic':
39
+ return new AnthropicProvider(apiKey, config.baseUrl);
40
+ case 'gemini':
41
+ case 'google':
42
+ return new GeminiProvider(apiKey, config.baseUrl);
43
+ case 'openrouter':
44
+ return new OpenRouterProvider(apiKey, config.baseUrl);
45
+ default:
46
+ // Generic OpenAI-compatible fallback (e.g. Ollama, Groq, DeepSeek, vLLM)
47
+ return new OpenAIProvider(apiKey, config.baseUrl, normProvider);
48
+ }
49
+ }
50
+ /**
51
+ * Resolves a raw model string, alias, or provider/model into a canonical structure.
52
+ */
53
+ export function resolveModel(modelId, preferredProvider) {
54
+ return normalizeModel(modelId, preferredProvider);
55
+ }
56
+ export function listModels(providerFilter) {
57
+ if (!providerFilter) {
58
+ return CANONICAL_MODELS.filter(m => m.provider !== 'none');
59
+ }
60
+ return CANONICAL_MODELS.filter(m => m.provider.toLowerCase() === providerFilter.toLowerCase());
61
+ }
62
+ export function listProviders() {
63
+ return SUPPORTED_PROVIDERS;
64
+ }
65
+ /**
66
+ * Validates whether an API key is structurally and functionally plausible.
67
+ */
68
+ export async function validateProviderKey(providerName, apiKey) {
69
+ const cleanKey = apiKey.trim();
70
+ if (!cleanKey) {
71
+ return { valid: false, error: 'Key cannot be empty' };
72
+ }
73
+ const norm = providerName.toLowerCase();
74
+ // Basic syntax/prefix heuristics
75
+ if (norm === 'anthropic' && !cleanKey.startsWith('sk-ant-') && cleanKey.length < 20) {
76
+ return { valid: false, error: 'Anthropic keys typically begin with sk-ant-' };
77
+ }
78
+ if (norm === 'openai' && !cleanKey.startsWith('sk-') && cleanKey.length < 20) {
79
+ return { valid: false, error: 'OpenAI keys typically begin with sk-' };
80
+ }
81
+ // Network verification probe
82
+ try {
83
+ const controller = new AbortController();
84
+ const timeout = setTimeout(() => controller.abort(), 6000);
85
+ if (norm === 'anthropic') {
86
+ const res = await fetch('https://api.anthropic.com/v1/models', {
87
+ headers: {
88
+ 'x-api-key': cleanKey,
89
+ 'anthropic-version': '2023-06-01'
90
+ },
91
+ signal: controller.signal
92
+ });
93
+ clearTimeout(timeout);
94
+ if (res.status === 401 || res.status === 403) {
95
+ return { valid: false, error: 'Authentication failed: Invalid Anthropic API key' };
96
+ }
97
+ return { valid: true };
98
+ }
99
+ if (norm === 'openai') {
100
+ const res = await fetch('https://api.openai.com/v1/models', {
101
+ headers: { 'Authorization': `Bearer ${cleanKey}` },
102
+ signal: controller.signal
103
+ });
104
+ clearTimeout(timeout);
105
+ if (res.status === 401 || res.status === 403) {
106
+ return { valid: false, error: 'Authentication failed: Invalid OpenAI API key' };
107
+ }
108
+ return { valid: true };
109
+ }
110
+ if (norm === 'gemini' || norm === 'google') {
111
+ const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${cleanKey}`, {
112
+ signal: controller.signal
113
+ });
114
+ clearTimeout(timeout);
115
+ if (res.status === 400 || res.status === 401 || res.status === 403) {
116
+ return { valid: false, error: 'Authentication failed: Invalid Gemini API key' };
117
+ }
118
+ return { valid: true };
119
+ }
120
+ if (norm === 'openrouter') {
121
+ const res = await fetch('https://openrouter.ai/api/v1/auth/key', {
122
+ headers: { 'Authorization': `Bearer ${cleanKey}` },
123
+ signal: controller.signal
124
+ });
125
+ clearTimeout(timeout);
126
+ if (res.status === 401 || res.status === 403) {
127
+ return { valid: false, error: 'Authentication failed: Invalid OpenRouter API key' };
128
+ }
129
+ return { valid: true };
130
+ }
131
+ clearTimeout(timeout);
132
+ return { valid: true };
133
+ }
134
+ catch (err) {
135
+ if (err.name === 'AbortError') {
136
+ // If network times out, don't block user if key looks well-formed
137
+ return { valid: true };
138
+ }
139
+ // Network unreachable or offline during probe
140
+ return { valid: true };
141
+ }
142
+ }
@@ -0,0 +1,10 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { CompletionRequest, CompletionResponse, StreamChunk } from './types.js';
3
+ export declare class GeminiProvider extends BaseProvider {
4
+ constructor(apiKey: string, baseUrl?: string);
5
+ protected headers(): Record<string, string>;
6
+ protected getEndpoint(request: CompletionRequest): string;
7
+ protected buildRequestBody(request: CompletionRequest): Record<string, unknown>;
8
+ protected parseResponse(body: any): CompletionResponse;
9
+ protected parseStreamChunk(dataStr: string, buffer: Record<string, unknown>): StreamChunk | null;
10
+ }
@@ -0,0 +1,97 @@
1
+ import { BaseProvider } from './base.js';
2
+ export class GeminiProvider extends BaseProvider {
3
+ constructor(apiKey, baseUrl = 'https://generativelanguage.googleapis.com/v1beta') {
4
+ super('gemini', apiKey, baseUrl);
5
+ }
6
+ headers() {
7
+ return {
8
+ 'Content-Type': 'application/json'
9
+ };
10
+ }
11
+ getEndpoint(request) {
12
+ const base = this.baseUrl.replace(/\/$/, '');
13
+ if (request.stream) {
14
+ return `${base}/models/${request.model}:streamGenerateContent?alt=sse&key=${this.apiKey}`;
15
+ }
16
+ return `${base}/models/${request.model}:generateContent?key=${this.apiKey}`;
17
+ }
18
+ buildRequestBody(request) {
19
+ const contents = request.messages.map(m => {
20
+ // All roles should be user-facing except assistant which maps to model.
21
+ // Gemini API does not support a 'function' role; map tool to user.
22
+ let role = 'user';
23
+ if (m.role === 'assistant')
24
+ role = 'model';
25
+ // NOTE: do NOT map tool role to 'function'.
26
+ return {
27
+ role,
28
+ parts: [{ text: m.content || '' }]
29
+ };
30
+ });
31
+ const tools = request.tools ? [{
32
+ functionDeclarations: request.tools.map(t => ({
33
+ name: t.function.name,
34
+ description: t.function.description,
35
+ parameters: t.function.parameters
36
+ }))
37
+ }] : undefined;
38
+ return {
39
+ contents,
40
+ ...(tools ? { tools } : {}),
41
+ generationConfig: {
42
+ ...(request.temperature !== undefined ? { temperature: request.temperature } : {}),
43
+ ...(request.maxTokens !== undefined ? { maxOutputTokens: request.maxTokens } : {})
44
+ }
45
+ };
46
+ }
47
+ parseResponse(body) {
48
+ const candidate = body.candidates?.[0];
49
+ const parts = candidate?.content?.parts || [];
50
+ let content = '';
51
+ const toolCalls = [];
52
+ for (const part of parts) {
53
+ if (part.text)
54
+ content += part.text;
55
+ if (part.functionCall) {
56
+ toolCalls.push({
57
+ id: `call_${Math.random().toString(36).substring(7)}`,
58
+ name: part.functionCall.name,
59
+ arguments: part.functionCall.args
60
+ });
61
+ }
62
+ }
63
+ return {
64
+ content,
65
+ toolCalls,
66
+ finishReason: candidate?.finishReason === 'STOP' ? 'stop' : (toolCalls.length > 0 ? 'tool_calls' : 'stop')
67
+ };
68
+ }
69
+ parseStreamChunk(dataStr, buffer) {
70
+ try {
71
+ const data = JSON.parse(dataStr);
72
+ const candidate = data.candidates?.[0];
73
+ if (!candidate)
74
+ return null;
75
+ const parts = candidate.content?.parts || [];
76
+ for (const part of parts) {
77
+ if (part.text) {
78
+ return { type: 'text', content: part.text };
79
+ }
80
+ if (part.functionCall) {
81
+ return {
82
+ type: 'tool_call_start',
83
+ toolCall: {
84
+ id: `call_${Math.random().toString(36).substring(7)}`,
85
+ name: part.functionCall.name,
86
+ arguments: JSON.stringify(part.functionCall.args)
87
+ }
88
+ };
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ catch (e) {
94
+ return null;
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,9 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { CompletionRequest, CompletionResponse, StreamChunk } from './types.js';
3
+ export declare class OpenAIProvider extends BaseProvider {
4
+ constructor(apiKey: string, baseUrl?: string, name?: string);
5
+ protected getEndpoint(request: CompletionRequest): string;
6
+ protected buildRequestBody(request: CompletionRequest): Record<string, unknown>;
7
+ protected parseResponse(body: any): CompletionResponse;
8
+ protected parseStreamChunk(dataStr: string, buffer: Record<string, unknown>): StreamChunk | null;
9
+ }
@@ -0,0 +1,85 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { formatForOpenAI } from './validator.js';
3
+ export class OpenAIProvider extends BaseProvider {
4
+ constructor(apiKey, baseUrl = 'https://api.openai.com/v1', name = 'openai') {
5
+ super(name, apiKey, baseUrl);
6
+ }
7
+ getEndpoint(request) {
8
+ return `${this.baseUrl.replace(/\/$/, '')}/chat/completions`;
9
+ }
10
+ buildRequestBody(request) {
11
+ return {
12
+ model: request.model,
13
+ messages: formatForOpenAI(request.messages),
14
+ ...(request.tools && request.tools.length > 0 ? { tools: request.tools } : {}),
15
+ ...(request.temperature !== undefined ? { temperature: request.temperature } : {}),
16
+ ...(request.maxTokens !== undefined ? { max_tokens: request.maxTokens } : {}),
17
+ stream: request.stream
18
+ };
19
+ }
20
+ parseResponse(body) {
21
+ const choice = body.choices?.[0];
22
+ const message = choice?.message || {};
23
+ return {
24
+ content: message.content || '',
25
+ toolCalls: (message.tool_calls || []).map((tc) => {
26
+ let parsedArgs = tc.function?.arguments;
27
+ if (typeof parsedArgs === 'string') {
28
+ try {
29
+ parsedArgs = JSON.parse(parsedArgs);
30
+ }
31
+ catch {
32
+ parsedArgs = {};
33
+ }
34
+ }
35
+ return {
36
+ id: tc.id,
37
+ name: tc.function?.name,
38
+ arguments: parsedArgs || {}
39
+ };
40
+ }),
41
+ usage: body.usage ? {
42
+ promptTokens: body.usage.prompt_tokens,
43
+ completionTokens: body.usage.completion_tokens,
44
+ totalTokens: body.usage.total_tokens
45
+ } : undefined,
46
+ finishReason: choice?.finish_reason || 'stop'
47
+ };
48
+ }
49
+ parseStreamChunk(dataStr, buffer) {
50
+ try {
51
+ const data = JSON.parse(dataStr);
52
+ const choice = data.choices?.[0];
53
+ const delta = choice?.delta || {};
54
+ if (delta.content) {
55
+ return { type: 'text', content: delta.content };
56
+ }
57
+ if (delta.tool_calls) {
58
+ const tcDelta = delta.tool_calls[0];
59
+ if (tcDelta.id) {
60
+ buffer.toolCallArgs = '';
61
+ return {
62
+ type: 'tool_call_start',
63
+ toolCall: {
64
+ id: tcDelta.id,
65
+ name: tcDelta.function?.name
66
+ }
67
+ };
68
+ }
69
+ else if (tcDelta.function?.arguments) {
70
+ buffer.toolCallArgs = (buffer.toolCallArgs || '') + tcDelta.function.arguments;
71
+ return {
72
+ type: 'tool_call_args',
73
+ toolCall: {
74
+ arguments: tcDelta.function.arguments
75
+ }
76
+ };
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+ catch (e) {
82
+ return null;
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,5 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ export declare class OpenRouterProvider extends OpenAIProvider {
3
+ constructor(apiKey: string, baseUrl?: string);
4
+ protected headers(): Record<string, string>;
5
+ }
@@ -0,0 +1,14 @@
1
+ import { OpenAIProvider } from './openai.js';
2
+ export class OpenRouterProvider extends OpenAIProvider {
3
+ constructor(apiKey, baseUrl = 'https://openrouter.ai/api/v1') {
4
+ super(apiKey, baseUrl, 'openrouter');
5
+ }
6
+ headers() {
7
+ return {
8
+ 'Content-Type': 'application/json',
9
+ 'Authorization': `Bearer ${this.apiKey}`,
10
+ 'HTTP-Referer': 'https://github.com/overarch/cli',
11
+ 'X-Title': 'Overarch CLI'
12
+ };
13
+ }
14
+ }
@@ -0,0 +1,19 @@
1
+ import { ModelInfo } from './types.js';
2
+ export interface ProviderDefinition {
3
+ id: string;
4
+ name: string;
5
+ envVar: string;
6
+ defaultBaseUrl: string;
7
+ recommendedModels: string[];
8
+ }
9
+ export declare const SUPPORTED_PROVIDERS: ProviderDefinition[];
10
+ export declare const CANONICAL_MODELS: ModelInfo[];
11
+ export declare const MODEL_ALIASES: Record<string, string>;
12
+ /**
13
+ * Normalizes any model ID or alias into a canonical { provider, model, displayName } structure.
14
+ */
15
+ export declare function normalizeModel(input: string, preferredProvider?: string): {
16
+ provider: string;
17
+ model: string;
18
+ displayName: string;
19
+ } | undefined;
@@ -0,0 +1,209 @@
1
+ export const SUPPORTED_PROVIDERS = [
2
+ {
3
+ id: 'anthropic',
4
+ name: 'Anthropic Claude',
5
+ envVar: 'ANTHROPIC_API_KEY',
6
+ defaultBaseUrl: 'https://api.anthropic.com/v1',
7
+ recommendedModels: [
8
+ 'claude-3-5-sonnet-20241022',
9
+ 'claude-opus-4-6',
10
+ 'claude-sonnet-4-20250514',
11
+ 'claude-3-5-haiku-20241022'
12
+ ]
13
+ },
14
+ {
15
+ id: 'openai',
16
+ name: 'OpenAI',
17
+ envVar: 'OPENAI_API_KEY',
18
+ defaultBaseUrl: 'https://api.openai.com/v1',
19
+ recommendedModels: [
20
+ 'gpt-4o',
21
+ 'gpt-4o-mini',
22
+ 'o3-mini',
23
+ 'o1'
24
+ ]
25
+ },
26
+ {
27
+ id: 'gemini',
28
+ name: 'Google Gemini',
29
+ envVar: 'GEMINI_API_KEY',
30
+ defaultBaseUrl: 'https://generativelanguage.googleapis.com/v1beta',
31
+ recommendedModels: [
32
+ 'gemini-2.5-flash',
33
+ 'gemini-2.5-pro',
34
+ 'gemini-2.0-flash-exp',
35
+ 'gemini-1.5-pro'
36
+ ]
37
+ },
38
+ {
39
+ id: 'openrouter',
40
+ name: 'OpenRouter (Multi-Provider)',
41
+ envVar: 'OPENROUTER_API_KEY',
42
+ defaultBaseUrl: 'https://openrouter.ai/api/v1',
43
+ recommendedModels: [
44
+ 'openrouter/auto',
45
+ 'anthropic/claude-3.5-sonnet',
46
+ 'openai/gpt-4o',
47
+ 'deepseek/deepseek-chat',
48
+ 'google/gemini-2.0-flash-001',
49
+ 'meta-llama/llama-3.3-70b-instruct'
50
+ ]
51
+ }
52
+ ];
53
+ export const CANONICAL_MODELS = [
54
+ // Anthropic
55
+ { id: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', provider: 'anthropic', contextWindow: 200000, supportsTools: true },
56
+ { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', provider: 'anthropic', contextWindow: 200000, supportsTools: true },
57
+ { id: 'claude-sonnet-4-20250514', name: 'Claude 4 Sonnet', provider: 'anthropic', contextWindow: 200000, supportsTools: true },
58
+ { id: 'claude-3-5-haiku-20241022', name: 'Claude 3.5 Haiku', provider: 'anthropic', contextWindow: 200000, supportsTools: true },
59
+ // OpenAI
60
+ { id: 'gpt-4o', name: 'GPT-4o', provider: 'openai', contextWindow: 128000, supportsTools: true },
61
+ { id: 'gpt-4o-mini', name: 'GPT-4o Mini', provider: 'openai', contextWindow: 128000, supportsTools: true },
62
+ { id: 'o3-mini', name: 'o3-mini', provider: 'openai', contextWindow: 200000, supportsTools: true },
63
+ { id: 'o1', name: 'o1', provider: 'openai', contextWindow: 200000, supportsTools: false },
64
+ // Gemini
65
+ { id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash', provider: 'gemini', contextWindow: 1048576, supportsTools: true },
66
+ { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', provider: 'gemini', contextWindow: 2097152, supportsTools: true },
67
+ { id: 'gemini-2.0-flash-exp', name: 'Gemini 2.0 Flash', provider: 'gemini', contextWindow: 1048576, supportsTools: true },
68
+ { id: 'gemini-1.5-pro', name: 'Gemini 1.5 Pro', provider: 'gemini', contextWindow: 2097152, supportsTools: true },
69
+ // OpenRouter
70
+ { id: 'openrouter/auto', name: 'OpenRouter Auto (Best)', provider: 'openrouter', contextWindow: 128000, supportsTools: true },
71
+ { id: 'anthropic/claude-3.5-sonnet', name: 'Claude 3.5 Sonnet (OpenRouter)', provider: 'openrouter', contextWindow: 200000, supportsTools: true },
72
+ { id: 'openai/gpt-4o', name: 'GPT-4o (OpenRouter)', provider: 'openrouter', contextWindow: 128000, supportsTools: true },
73
+ { id: 'deepseek/deepseek-chat', name: 'DeepSeek V3 (OpenRouter)', provider: 'openrouter', contextWindow: 128000, supportsTools: true },
74
+ { id: 'google/gemini-2.0-flash-001', name: 'Gemini 2.0 Flash (OpenRouter)', provider: 'openrouter', contextWindow: 1048576, supportsTools: true },
75
+ { id: 'meta-llama/llama-3.3-70b-instruct', name: 'Llama 3.3 70B (OpenRouter)', provider: 'openrouter', contextWindow: 128000, supportsTools: true },
76
+ // Offline
77
+ { id: 'offline', name: 'Offline (No AI)', provider: 'none', contextWindow: 0, supportsTools: true }
78
+ ];
79
+ export const MODEL_ALIASES = {
80
+ // Common Anthropic aliases
81
+ 'claude-3-5-sonnet': 'claude-3-5-sonnet-20241022',
82
+ 'claude-sonnet': 'claude-3-5-sonnet-20241022',
83
+ 'sonnet': 'claude-3-5-sonnet-20241022',
84
+ 'claude-haiku': 'claude-3-5-haiku-20241022',
85
+ 'haiku': 'claude-3-5-haiku-20241022',
86
+ 'claude-opus': 'claude-opus-4-6',
87
+ 'opus': 'claude-opus-4-6',
88
+ // Common OpenAI aliases
89
+ '4o': 'gpt-4o',
90
+ '4o-mini': 'gpt-4o-mini',
91
+ // Common Gemini aliases
92
+ 'gemini-flash': 'gemini-2.5-flash',
93
+ 'gemini-pro': 'gemini-2.5-pro',
94
+ // Common OpenRouter aliases
95
+ 'openrouter': 'openrouter/auto',
96
+ 'auto': 'openrouter/auto',
97
+ // Offline
98
+ 'none': 'offline'
99
+ };
100
+ /**
101
+ * Normalizes any model ID or alias into a canonical { provider, model, displayName } structure.
102
+ */
103
+ export function normalizeModel(input, preferredProvider) {
104
+ const trimmed = input.trim();
105
+ if (!trimmed)
106
+ return undefined;
107
+ // 1. Direct offline check
108
+ if (trimmed.toLowerCase() === 'none' || trimmed.toLowerCase() === 'offline') {
109
+ return { provider: 'none', model: 'offline', displayName: 'Offline Mode' };
110
+ }
111
+ // 2. Check alias map first
112
+ const aliasTarget = MODEL_ALIASES[trimmed.toLowerCase()];
113
+ const lookupId = aliasTarget || trimmed;
114
+ // 3. Match in canonical models
115
+ const canonical = CANONICAL_MODELS.find(m => {
116
+ if (m.id.toLowerCase() !== lookupId.toLowerCase())
117
+ return false;
118
+ if (m.provider === 'openrouter') {
119
+ return preferredProvider === 'openrouter' || lookupId.toLowerCase().startsWith('openrouter/');
120
+ }
121
+ return true;
122
+ });
123
+ if (canonical) {
124
+ return {
125
+ provider: canonical.provider,
126
+ model: canonical.id,
127
+ displayName: canonical.name
128
+ };
129
+ }
130
+ // 4. Handle openrouter/ prefix explicitly
131
+ if (lookupId.toLowerCase().startsWith('openrouter/')) {
132
+ const rawModel = lookupId.slice('openrouter/'.length);
133
+ return {
134
+ provider: 'openrouter',
135
+ model: lookupId,
136
+ displayName: `OpenRouter (${rawModel || 'auto'})`
137
+ };
138
+ }
139
+ // If preferredProvider is explicitly openrouter, preserve model string as-is for OpenRouter
140
+ if (preferredProvider === 'openrouter') {
141
+ return {
142
+ provider: 'openrouter',
143
+ model: lookupId,
144
+ displayName: `OpenRouter (${lookupId})`
145
+ };
146
+ }
147
+ // 5. Handle provider/model syntax
148
+ if (lookupId.includes('/')) {
149
+ const slashIdx = lookupId.indexOf('/');
150
+ const firstPart = lookupId.slice(0, slashIdx).toLowerCase();
151
+ const rest = lookupId.slice(slashIdx + 1);
152
+ const vendorEnvMap = {
153
+ anthropic: 'ANTHROPIC_API_KEY',
154
+ openai: 'OPENAI_API_KEY',
155
+ gemini: 'GEMINI_API_KEY',
156
+ google: 'GEMINI_API_KEY'
157
+ };
158
+ // If OpenRouter key is available and direct provider key is not, route to openrouter
159
+ const directEnv = vendorEnvMap[firstPart];
160
+ if (process.env.OPENROUTER_API_KEY && directEnv && !process.env[directEnv]) {
161
+ return {
162
+ provider: 'openrouter',
163
+ model: lookupId,
164
+ displayName: `OpenRouter (${lookupId})`
165
+ };
166
+ }
167
+ const knownProviders = ['anthropic', 'openai', 'gemini', 'google'];
168
+ if (knownProviders.includes(firstPart)) {
169
+ const canonicalInner = CANONICAL_MODELS.find(m => m.id.toLowerCase() === rest.toLowerCase() && m.provider === firstPart);
170
+ return {
171
+ provider: firstPart === 'google' ? 'gemini' : firstPart,
172
+ model: rest,
173
+ displayName: canonicalInner ? canonicalInner.name : `${firstPart.toUpperCase()} ${rest}`
174
+ };
175
+ }
176
+ // Any other vendor/model (e.g. meta-llama/..., mistralai/..., nex-agi/...)
177
+ if (process.env.OPENROUTER_API_KEY) {
178
+ return {
179
+ provider: 'openrouter',
180
+ model: lookupId,
181
+ displayName: `OpenRouter (${lookupId})`
182
+ };
183
+ }
184
+ return {
185
+ provider: firstPart,
186
+ model: rest,
187
+ displayName: `${firstPart.toUpperCase()} ${rest}`
188
+ };
189
+ }
190
+ // 6. Intelligent prefix inference
191
+ if (lookupId.startsWith('claude-')) {
192
+ return { provider: 'anthropic', model: lookupId, displayName: `Claude (${lookupId})` };
193
+ }
194
+ if (lookupId.startsWith('gpt-') || lookupId.startsWith('o1') || lookupId.startsWith('o3')) {
195
+ return { provider: 'openai', model: lookupId, displayName: `OpenAI (${lookupId})` };
196
+ }
197
+ if (lookupId.startsWith('gemini-')) {
198
+ return { provider: 'gemini', model: lookupId, displayName: `Gemini (${lookupId})` };
199
+ }
200
+ // If preferredProvider is set, associate with that provider
201
+ if (preferredProvider) {
202
+ return {
203
+ provider: preferredProvider,
204
+ model: lookupId,
205
+ displayName: `${preferredProvider.toUpperCase()} (${lookupId})`
206
+ };
207
+ }
208
+ return undefined;
209
+ }
@@ -0,0 +1,59 @@
1
+ export interface Message {
2
+ role: 'system' | 'user' | 'assistant' | 'tool';
3
+ content: string;
4
+ toolCallId?: string;
5
+ toolCalls?: ToolCall[];
6
+ }
7
+ export interface ToolCall {
8
+ id: string;
9
+ name: string;
10
+ arguments: Record<string, unknown>;
11
+ }
12
+ export interface ToolSchema {
13
+ type: 'function';
14
+ function: {
15
+ name: string;
16
+ description: string;
17
+ parameters: {
18
+ type: 'object';
19
+ properties: Record<string, unknown>;
20
+ required: string[];
21
+ };
22
+ };
23
+ }
24
+ export interface CompletionRequest {
25
+ messages: Message[];
26
+ model: string;
27
+ tools?: ToolSchema[];
28
+ temperature?: number;
29
+ maxTokens?: number;
30
+ stream?: boolean;
31
+ }
32
+ export interface CompletionResponse {
33
+ content: string;
34
+ toolCalls: ToolCall[];
35
+ usage?: {
36
+ promptTokens: number;
37
+ completionTokens: number;
38
+ totalTokens: number;
39
+ };
40
+ finishReason: 'stop' | 'tool_calls' | 'length' | 'error';
41
+ }
42
+ export interface StreamChunk {
43
+ type: 'text' | 'tool_call_start' | 'tool_call_args' | 'done' | 'error';
44
+ content?: string;
45
+ toolCall?: Partial<ToolCall>;
46
+ usage?: CompletionResponse['usage'];
47
+ }
48
+ export interface LLMProvider {
49
+ name: string;
50
+ complete(request: CompletionRequest): Promise<CompletionResponse>;
51
+ stream(request: CompletionRequest): AsyncIterable<StreamChunk>;
52
+ }
53
+ export interface ModelInfo {
54
+ id: string;
55
+ name: string;
56
+ provider: string;
57
+ contextWindow: number;
58
+ supportsTools: boolean;
59
+ }
@@ -0,0 +1 @@
1
+ export {};