Skip to content

Commit

Permalink
Merge pull request #79 from leftmove/google-provider
Browse files Browse the repository at this point in the history
feat(google): added gemini api as provider
  • Loading branch information
arshad-yaseen authored Nov 20, 2024
2 parents 4b77894 + 0b9f6c8 commit c1ba5d7
Show file tree
Hide file tree
Showing 23 changed files with 501 additions and 305 deletions.
5 changes: 1 addition & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@


## [0.14.2](https://github.com/arshad-yaseen/monacopilot/compare/v0.14.1...v0.14.2) (2024-11-17)


### 🔧 Maintenance

* **completion:** handle backward overlap ([437ede1](https://github.com/arshad-yaseen/monacopilot/commit/437ede107ddc8e52e3327afd1fc86f50fad8484c))
- **completion:** handle backward overlap ([437ede1](https://github.com/arshad-yaseen/monacopilot/commit/437ede107ddc8e52e3327afd1fc86f50fad8484c))

## [0.14.1](https://github.com/arshad-yaseen/monacopilot/compare/v0.14.0...v0.14.1) (2024-11-13)

Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,11 +419,12 @@ The default provider is `anthropic`, and the default model is `claude-3-5-haiku`
There are other providers and models available. Here is a list:

| Provider | Models |
| --------- | --------------------------------------------------------- |
| Groq | `llama-3-70b` |
| OpenAI | `gpt-4o`, `gpt-4o-mini`, `o1-preview`, `o1-mini` |
| Anthropic | `claude-3-5-sonnet`, `claude-3-haiku`, `claude-3-5-haiku` |
| Provider | Models |
| --------- | ----------------------------------------------------------- |
| Groq | `llama-3-70b` |
| OpenAI | `gpt-4o`, `gpt-4o-mini`, `o1-preview`, `o1-mini` |
| Anthropic | `claude-3-5-sonnet`, `claude-3-haiku`, `claude-3-5-haiku` |
| Google | `gemini-1.5-pro`, `gemini-1.5-flash`, `gemini-1.5-flash-8b` |

### Custom Model

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@anthropic-ai/sdk": "^0.27.3",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@google/generative-ai": "^0.21.0",
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
"@release-it/conventional-changelog": "^8.0.2",
"@typescript-eslint/eslint-plugin": "^7.3.1",
Expand Down Expand Up @@ -60,5 +61,6 @@
}
],
"license": "MIT",
"author": "Arshad Yaseen <[email protected]> (https://arshadyaseen.com)"
"author": "Arshad Yaseen <[email protected]> (https://arshadyaseen.com)",
"dependencies": {}
}
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions src/classes/completion-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {Queue} from './queue';
*/
export class CompletionCache {
private static readonly MAX_CACHE_SIZE = 10;
private static readonly LOOK_AROUND = 3; // Number of characters to look around cache range
private cache: Queue<CompletionCacheItem>;

constructor() {
Expand Down Expand Up @@ -71,18 +72,35 @@ export class CompletionCache {
currentRangeValue: string,
): boolean {
const {range, completion} = cacheItem;
const {startLineNumber, startColumn, endColumn} = range;
const {startLineNumber, startColumn, endLineNumber, endColumn} = range;
const {lineNumber, column} = pos;

// Check if cursor is at the start of the completion range
const isAtStartOfRange =
lineNumber === startLineNumber && column === startColumn;

const isWithinCompletionRange =
// For single line completions
if (startLineNumber === endLineNumber) {
return (
isAtStartOfRange ||
(completion.startsWith(currentRangeValue) &&
lineNumber === startLineNumber &&
column >= startColumn - CompletionCache.LOOK_AROUND &&
column <= endColumn + CompletionCache.LOOK_AROUND)
);
}

// Check if cursor is within the valid range for multi-line completion
const isWithinMultiLineRange =
completion.startsWith(currentRangeValue) &&
lineNumber === startLineNumber &&
column >= startColumn - currentRangeValue.length &&
column <= endColumn + currentRangeValue.length;
lineNumber >= startLineNumber &&
lineNumber <= endLineNumber &&
((lineNumber === startLineNumber &&
column >= startColumn - CompletionCache.LOOK_AROUND) ||
(lineNumber === endLineNumber &&
column <= endColumn + CompletionCache.LOOK_AROUND) ||
(lineNumber > startLineNumber && lineNumber < endLineNumber));

return isAtStartOfRange || isWithinCompletionRange;
return isAtStartOfRange || isWithinMultiLineRange;
}
}
111 changes: 111 additions & 0 deletions src/classes/completion-range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {CursorPosition, EditorModel, EditorRange, Monaco} from '../types';

export class CompletionRange {
private monaco: Monaco;

constructor(monaco: Monaco) {
this.monaco = monaco;
}

public computeInsertionRange(
pos: CursorPosition,
completion: string,
mdl: EditorModel,
): EditorRange {
// Handle empty completion
if (!completion) {
return new this.monaco.Range(
pos.lineNumber,
pos.column,
pos.lineNumber,
pos.column,
);
}

const startOffset = mdl.getOffsetAt(pos);
const textBeforeCursor = mdl.getValue().substring(0, startOffset);
const textAfterCursor = mdl.getValue().substring(startOffset);

let prefixOverlapLength = 0;
let suffixOverlapLength = 0;
let maxOverlapLength = 0;
let startOverlapLength = 0;

const completionLength = completion.length;
const beforeLength = textBeforeCursor.length;
const afterLength = textAfterCursor.length;

// Handle cursor at the end of the document
if (startOffset >= mdl.getValue().length) {
return new this.monaco.Range(
pos.lineNumber,
pos.column,
pos.lineNumber,
pos.column,
);
}

// Handle empty remaining text
if (afterLength === 0) {
return new this.monaco.Range(
pos.lineNumber,
pos.column,
pos.lineNumber,
pos.column,
);
}

// Find overlap with text before cursor
const maxBeforeOverlap = Math.min(completionLength, beforeLength);
for (let i = 1; i <= maxBeforeOverlap; i++) {
const completionStart = completion.substring(0, i);
const textEnd = textBeforeCursor.slice(-i);
if (completionStart === textEnd) {
startOverlapLength = i;
}
}

// Find overlap with text after cursor
const maxAfterOverlap = Math.min(completionLength, afterLength);

// Find the longest prefix overlap with text after cursor
for (let i = 0; i < maxAfterOverlap; i++) {
if (completion[i] !== textAfterCursor[i]) break;
prefixOverlapLength++;
}

// Find the longest suffix overlap with text after cursor
for (let i = 1; i <= maxAfterOverlap; i++) {
if (completion.slice(-i) === textAfterCursor.slice(0, i)) {
suffixOverlapLength = i;
}
}

maxOverlapLength = Math.max(prefixOverlapLength, suffixOverlapLength);

// Check for internal overlaps if no prefix or suffix overlap
if (maxOverlapLength === 0) {
for (let i = 1; i < completionLength; i++) {
if (textAfterCursor.startsWith(completion.substring(i))) {
maxOverlapLength = completionLength - i;
break;
}
}
}

// Calculate start and end positions
const startPosition =
startOverlapLength > 0
? mdl.getPositionAt(startOffset - startOverlapLength)
: pos;
const endOffset = startOffset + maxOverlapLength;
const endPosition = mdl.getPositionAt(endOffset);

return new this.monaco.Range(
startPosition.lineNumber,
startPosition.column,
endPosition.lineNumber,
endPosition.column,
);
}
}
8 changes: 6 additions & 2 deletions src/classes/copilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
DEFAULT_COPILOT_PROVIDER,
} from '../constants';
import {
createProviderEndpoint,
createProviderHeaders,
createRequestBody,
getCopilotProviderEndpoint,
parseProviderChatCompletion,
} from '../helpers/provider';
import {deprecated, report} from '../logger';
Expand Down Expand Up @@ -106,7 +106,11 @@ export class Copilot {
requestBody: ChatCompletionCreateParams;
headers: Record<string, string>;
} {
let endpoint = getCopilotProviderEndpoint(this.provider);
let endpoint = createProviderEndpoint(
this.model as CopilotModel,
this.apiKey,
this.provider,
);
let requestBody: ChatCompletionCreateParams;
let headers = createProviderHeaders(this.apiKey, this.provider);

Expand Down
12 changes: 11 additions & 1 deletion src/constants/copilot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {CopilotModel, CopilotProvider} from '../types';

export const COPILOT_PROVIDERS = ['groq', 'openai', 'anthropic'] as const;
export const COPILOT_PROVIDERS = [
'groq',
'openai',
'anthropic',
'google',
] as const;

export const COPILOT_MODEL_IDS: Record<CopilotModel, string> = {
'llama-3-70b': 'llama3-70b-8192',
Expand All @@ -11,6 +16,9 @@ export const COPILOT_MODEL_IDS: Record<CopilotModel, string> = {
'claude-3-5-haiku': 'claude-3-5-haiku-20241022',
'o1-preview': 'o1-preview',
'o1-mini': 'o1-mini',
'gemini-1.5-flash-8b': 'gemini-1.5-flash-8b',
'gemini-1.5-flash': 'gemini-1.5-flash',
'gemini-1.5-pro': 'gemini-1.5-pro',
} as const;

export const COPILOT_PROVIDER_MODEL_MAP: Record<
Expand All @@ -20,6 +28,7 @@ export const COPILOT_PROVIDER_MODEL_MAP: Record<
groq: ['llama-3-70b'],
openai: ['gpt-4o', 'gpt-4o-mini', 'o1-preview', 'o1-mini'],
anthropic: ['claude-3-5-sonnet', 'claude-3-haiku', 'claude-3-5-haiku'],
google: ['gemini-1.5-flash-8b', 'gemini-1.5-pro', 'gemini-1.5-flash'],
} as const;

export const DEFAULT_COPILOT_PROVIDER: CopilotProvider = 'anthropic' as const;
Expand All @@ -29,6 +38,7 @@ export const COPILOT_PROVIDER_ENDPOINT_MAP: Record<CopilotProvider, string> = {
groq: 'https://api.groq.com/openai/v1/chat/completions',
openai: 'https://api.openai.com/v1/chat/completions',
anthropic: 'https://api.anthropic.com/v1/messages',
google: 'https://generativelanguage.googleapis.com/v1beta/models',
} as const;

export const DEFAULT_COPILOT_TEMPERATURE = 0.1 as const;
24 changes: 15 additions & 9 deletions src/core/completion/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {CompletionValidator} from '../../classes';
import {CompletionCache} from '../../classes/completion-cache';
import {CompletionRange} from '../../classes/completion-range';
import {constructCompletionMetadata, fetchCompletionItem} from '../../helpers';
import {report} from '../../logger';
import {
Expand All @@ -9,9 +10,12 @@ import {
InlineCompletionHandlerParams,
TriggerType,
} from '../../types';
import {asyncDebounce, getTextBeforeCursor} from '../../utils';
import {
computeCompletionInsertionRange,
debouncedAsync,
getTextAfterCursor,
getTextBeforeCursor,
} from '../../utils';
import {
createInlineCompletionResult,
formatCompletion,
} from '../../utils/completion';
Expand All @@ -31,18 +35,18 @@ const getDebouncedFunctionPerTrigger = (
fn: FetchCompletionItemHandler,
): Record<
TriggerType,
ReturnType<typeof asyncDebounce<FetchCompletionItemHandler>>
ReturnType<typeof debouncedAsync<FetchCompletionItemHandler>>
> => {
return {
[TriggerType.OnTyping]: asyncDebounce(
[TriggerType.OnTyping]: debouncedAsync(
fn,
DEBOUNCE_DELAYS[TriggerType.OnTyping],
),
[TriggerType.OnIdle]: asyncDebounce(
[TriggerType.OnIdle]: debouncedAsync(
fn,
DEBOUNCE_DELAYS[TriggerType.OnIdle],
),
[TriggerType.OnDemand]: asyncDebounce(
[TriggerType.OnDemand]: debouncedAsync(
fn,
DEBOUNCE_DELAYS[TriggerType.OnDemand],
),
Expand Down Expand Up @@ -123,18 +127,20 @@ const handleInlineCompletions = async ({

if (completion) {
const formattedCompletion = formatCompletion(completion);
const completionInsertionRange = computeCompletionInsertionRange(
monaco,
const completionRange = new CompletionRange(monaco);
const completionInsertionRange = completionRange.computeInsertionRange(
pos,
mdl,
formattedCompletion,
mdl,
);

if (enableCaching) {
completionCache.add({
completion: formattedCompletion,
range: completionInsertionRange,
textBeforeCursor: getTextBeforeCursor(pos, mdl),
textAfterCursor: getTextAfterCursor(pos, mdl),
cachePos: pos,
});
}

Expand Down
Loading

0 comments on commit c1ba5d7

Please sign in to comment.