Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,094 changes: 770 additions & 324 deletions extensions/positron-assistant/package-lock.json

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions extensions/positron-assistant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1012,21 +1012,21 @@
"postinstall": "ts-node scripts/post-install.ts"
},
"devDependencies": {
"@ai-sdk/amazon-bedrock": "2.2.12",
"@ai-sdk/anthropic": "^1.0.5",
"@ai-sdk/azure": "^1.1.9",
"@ai-sdk/google": "^1.1.17",
"@ai-sdk/google-vertex": "^2.1.8",
"@ai-sdk/mistral": "^1.1.6",
"@ai-sdk/openai": "^1.0.8",
"@ai-sdk/amazon-bedrock": "^3.0.65",
"@ai-sdk/anthropic": "^2.0.53",
"@ai-sdk/azure": "^2.0.78",
"@ai-sdk/google": "^2.0.44",
"@ai-sdk/google-vertex": "^3.0.85",
"@ai-sdk/mistral": "^2.0.25",
"@ai-sdk/openai": "^2.0.76",
"@aws-sdk/credential-providers": "^3.734.0",
"@eslint/js": "^9.13.0",
"@openrouter/ai-sdk-provider": "^0.0.6",
"@openrouter/ai-sdk-provider": "^1.3.0",
"@stylistic/eslint-plugin": "^2.9.0",
"@types/mocha": "^9.1.0",
"@types/node": "^20",
"@types/sinon": "^17.0.3",
"ai": "^4.3.19",
"ai": "^5.0.106",
"eslint": "^9.13.0",
"google-auth-library": "^9.15.1",
"mocha": "^9.2.1",
Expand Down
4 changes: 2 additions & 2 deletions extensions/positron-assistant/src/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class AnthropicLanguageModel implements positron.ai.LanguageModelChatProv

async provideLanguageModelChatInformation(_options: { silent: boolean }, token: vscode.CancellationToken): Promise<vscode.LanguageModelChatInformation[]> {
log.debug(`[${this.providerName}] Preparing language model chat information...`);
const models = await this.resolveModels(token) ?? [];
const models = (await this.resolveModels(token)) ?? [];

log.debug(`[${this.providerName}] Resolved ${models.length} models.`);
return this.filterModels(models);
Expand Down Expand Up @@ -122,7 +122,7 @@ export class AnthropicLanguageModel implements positron.ai.LanguageModelChatProv

const body: Anthropic.MessageStreamParams = {
model: model.id,
max_tokens: options.modelOptions?.maxTokens ?? this.maxOutputTokens,
max_tokens: options.modelOptions?.maxOutputTokens ?? this.maxOutputTokens,
tools,
tool_choice,
system,
Expand Down
24 changes: 12 additions & 12 deletions extensions/positron-assistant/src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class OpenAILegacyCompletion extends CompletionModel {
token: vscode.CancellationToken
): Promise<vscode.InlineCompletionItem[] | vscode.InlineCompletionList> {
// Check if the file should be excluded from AI features
if (!await positron.ai.areCompletionsEnabled(document.uri)) {
if (!(await positron.ai.areCompletionsEnabled(document.uri))) {
return [];
}

Expand Down Expand Up @@ -376,7 +376,7 @@ class VertexLegacyCompletion extends MistralCompletion {
// (Anthropic, OpenAI, Bedrock, OpenRouter, Gemini, Azure)

abstract class FimPromptCompletion extends CompletionModel {
protected abstract model: ai.LanguageModelV1;
protected abstract model: ai.LanguageModel;

async provideInlineCompletionItems(
document: vscode.TextDocument,
Expand All @@ -385,7 +385,7 @@ abstract class FimPromptCompletion extends CompletionModel {
token: vscode.CancellationToken
): Promise<vscode.InlineCompletionItem[] | vscode.InlineCompletionList> {
// Check if the file should be excluded from AI features
if (!await positron.ai.areCompletionsEnabled(document.uri)) {
if (!(await positron.ai.areCompletionsEnabled(document.uri))) {
return [];
}

Expand All @@ -412,7 +412,7 @@ abstract class FimPromptCompletion extends CompletionModel {
messages: [
{ role: 'user', content: `${relatedText}\n<|file_separator|>${document.fileName}\n<|fim_prefix|>${prefix}<|fim_suffix|>${suffix}\n<|fim_middle|>` }
],
maxTokens: 128,
maxOutputTokens: 128,
temperature: 0.2,
stopSequences: ['\n\n', '<|fim_prefix|>', '<|fim_suffix|>', '<|file_separator|>'],
abortSignal: signal,
Expand Down Expand Up @@ -500,7 +500,7 @@ class OpenAICompatibleCompletion extends OpenAICompletion {
}

class OpenRouterCompletion extends FimPromptCompletion {
protected model: ai.LanguageModelV1;
protected model: ai.LanguageModel;

static source: positron.ai.LanguageModelSource = {
type: positron.PositronLanguageModelType.Completion,
Expand All @@ -526,7 +526,7 @@ class OpenRouterCompletion extends FimPromptCompletion {
}

class AWSCompletion extends FimPromptCompletion {
protected model: ai.LanguageModelV1;
protected model: ai.LanguageModel;

static source: positron.ai.LanguageModelSource = {
type: positron.PositronLanguageModelType.Completion,
Expand All @@ -544,15 +544,15 @@ class AWSCompletion extends FimPromptCompletion {
constructor(_config: ModelConfig) {
super(_config);

// Cast to ai.LanguageModelV1 to satisfy base class type
// Cast to ai.LanguageModel to satisfy base class type
this.model = createAmazonBedrock({
credentialProvider: fromNodeProviderChain(),
})(this._config.model) as unknown as ai.LanguageModelV1;
})(this._config.model) as unknown as ai.LanguageModel;
}
}

class VertexCompletion extends FimPromptCompletion {
protected model: ai.LanguageModelV1;
protected model: ai.LanguageModel;

static source: positron.ai.LanguageModelSource = {
type: positron.PositronLanguageModelType.Completion,
Expand All @@ -579,7 +579,7 @@ class VertexCompletion extends FimPromptCompletion {
}

class GoogleCompletion extends FimPromptCompletion {
protected model: ai.LanguageModelV1;
protected model: ai.LanguageModel;

static source: positron.ai.LanguageModelSource = {
type: positron.PositronLanguageModelType.Completion,
Expand All @@ -606,7 +606,7 @@ class GoogleCompletion extends FimPromptCompletion {
}

class AzureCompletion extends FimPromptCompletion {
protected model: ai.LanguageModelV1;
protected model: ai.LanguageModel;

static source: positron.ai.LanguageModelSource = {
type: positron.PositronLanguageModelType.Completion,
Expand Down Expand Up @@ -663,7 +663,7 @@ export class CopilotCompletion implements vscode.InlineCompletionItemProvider {
token: vscode.CancellationToken
): Promise<vscode.InlineCompletionItem[] | vscode.InlineCompletionList | undefined> {
// Check if the file should be excluded from AI features
if (!await positron.ai.areCompletionsEnabled(document.uri)) {
if (!(await positron.ai.areCompletionsEnabled(document.uri))) {
return [];
}
return await this._copilotService.inlineCompletion(document, position, context, token);
Expand Down
58 changes: 29 additions & 29 deletions extensions/positron-assistant/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class EchoLanguageModel implements positron.ai.LanguageModelChatProvider {

async provideLanguageModelChatInformation(options: { silent: boolean }, token: vscode.CancellationToken): Promise<any[]> {
log.debug(`[${this.providerName}] Preparing language model chat information...`);
const models = this.modelListing ?? await this.resolveModels(token) ?? [];
const models = this.modelListing ?? (await this.resolveModels(token)) ?? [];

log.debug(`[${this.providerName}] Resolved ${models.length} models.`);
return this.filterModels(models);
Expand Down Expand Up @@ -251,7 +251,7 @@ class EchoLanguageModel implements positron.ai.LanguageModelChatProvider {
return applyModelFilters(models, this.provider, this.providerName);
}

private getUserPrompt(messages: ai.CoreMessage[]): ai.CoreMessage | undefined {
private getUserPrompt(messages: ai.ModelMessage[]): ai.ModelMessage | undefined {
if (messages.length === 0) {
return undefined;
}
Expand Down Expand Up @@ -292,7 +292,8 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
public readonly name;
public readonly provider;
public readonly id;
protected abstract aiProvider: (id: string, options?: Record<string, any>) => ai.LanguageModelV1;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected abstract aiProvider: (id: string, options?: Record<string, any>) => any;
protected aiOptions: Record<string, any> = {};

protected modelListing?: vscode.LanguageModelChatInformation[];
Expand Down Expand Up @@ -367,7 +368,7 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
} catch (error) {
const messagePrefix = `[${this.providerName}] '${model}'`;
log.warn(`${messagePrefix} Error sending test message: ${JSON.stringify(error, null, 2)}`);
const errorMsg = await this.parseProviderError(error) ||
const errorMsg = (await this.parseProviderError(error)) ||
(ai.AISDKError.isInstance(error) ? error.message : JSON.stringify(error, null, 2));
errors.push(errorMsg);
}
Expand All @@ -381,7 +382,7 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider

async provideLanguageModelChatInformation(options: { silent: boolean }, token: vscode.CancellationToken): Promise<vscode.LanguageModelChatInformation[]> {
log.debug(`[${this.providerName}] Preparing language model chat information...`);
const models = this.modelListing ?? await this.resolveModels(token) ?? [];
const models = this.modelListing ?? (await this.resolveModels(token)) ?? [];
return this.filterModels(models);
}

Expand All @@ -406,15 +407,15 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
// Only Anthropic currently supports experimental_content in tool
// results.
const toolResultExperimentalContent = this.provider === 'anthropic-api' ||
aiModel.modelId.includes('anthropic');
model.id.includes('anthropic');

// Only select Bedrock models support cache breakpoints; specifically,
// the Claude 3.5 Sonnet models don't support them.
//
// Consider: it'd be more verbose but we should consider including this information
// in the hardcoded model metadata in the model config.
const bedrockCacheBreakpoint = this.provider === 'amazon-bedrock' &&
!aiModel.modelId.includes('anthropic.claude-3-5');
!model.id.includes('anthropic.claude-3-5');

// Add system prompt from `modelOptions.system`, if provided.
// TODO: Once extensions such as databot no longer use `modelOptions.system`,
Expand All @@ -427,7 +428,7 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
}

// Convert all messages to the Vercel AI format.
const aiMessages: ai.CoreMessage[] = toAIMessage(
const aiMessages: ai.ModelMessage[] = toAIMessage(
processedMessages,
toolResultExperimentalContent,
bedrockCacheBreakpoint
Expand All @@ -450,7 +451,7 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
}
acc[tool.name] = ai.tool({
description: tool.description,
parameters: ai.jsonSchema(input_schema),
inputSchema: ai.jsonSchema(input_schema),
});
return acc;
}, {});
Expand All @@ -459,7 +460,7 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
const modelTools = this._config.toolCalls ? tools : undefined;
const requestId = (options.modelOptions as any)?.requestId;

log.info(`[${this.providerName}] [vercel] Start request ${requestId} to ${model.name} [${aiModel.modelId}]: ${aiMessages.length} messages`);
log.info(`[${this.providerName}] [vercel] Start request ${requestId} to ${model.name} [${model.id}]: ${aiMessages.length} messages`);
log.debug(`[${this.providerName}] [${model.name}] SEND ${aiMessages.length} messages, ${modelTools ? Object.keys(modelTools).length : 0} tools`);
if (modelTools) {
log.trace(`[${this.providerName}] tools: ${modelTools ? Object.keys(modelTools).join(', ') : '(none)'}`);
Expand All @@ -475,10 +476,10 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
const result = ai.streamText({
model: aiModel,
messages: aiMessages,
maxSteps: modelOptions.maxSteps ?? 50,
stopWhen: ai.stepCountIs(modelOptions.maxSteps ?? 50),
tools: modelTools,
abortSignal: signal,
maxTokens: getMaxTokens(aiModel.modelId, 'output', this._config.provider, this._config.maxOutputTokens, this.providerName),
maxOutputTokens: getMaxTokens(model.id, 'output', this._config.provider, this._config.maxOutputTokens, this.providerName),
});

let accumulatedTextDeltas: string[] = [];
Expand All @@ -496,28 +497,28 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
break;
}

if (part.type === 'reasoning') {
if (part.type === 'reasoning-delta') {
flushAccumulatedTextDeltas();
log.trace(`[${this.providerName}] [${this._config.name}] RECV reasoning: ${part.textDelta}`);
progress.report(new vscode.LanguageModelTextPart(part.textDelta));
log.trace(`[${this.providerName}] [${this._config.name}] RECV reasoning: ${part.text}`);
progress.report(new vscode.LanguageModelTextPart(part.text));
}

if (part.type === 'text-delta') {
accumulatedTextDeltas.push(part.textDelta);
progress.report(new vscode.LanguageModelTextPart(part.textDelta));
accumulatedTextDeltas.push(part.text);
progress.report(new vscode.LanguageModelTextPart(part.text));
}

if (part.type === 'tool-call') {
flushAccumulatedTextDeltas();
log.trace(`[${this.providerName}] [${this._config.name}] RECV tool-call: ${part.toolCallId} (${part.toolName}) with args: ${JSON.stringify(part.args)}`);
progress.report(new vscode.LanguageModelToolCallPart(part.toolCallId, part.toolName, part.args));
log.trace(`[${this.providerName}] [${this._config.name}] RECV tool-call: ${part.toolCallId} (${part.toolName}) with args: ${JSON.stringify(part.input)}`);
progress.report(new vscode.LanguageModelToolCallPart(part.toolCallId, part.toolName, part.input));
}

if (part.type === 'error') {
flushAccumulatedTextDeltas();
const messagePrefix = `[${this.providerName}] [${model.name}]'`;
log.warn(`${messagePrefix} RECV error: ${JSON.stringify(part.error, null, 2)}`);
const errorMsg = await this.parseProviderError(part.error) ||
const errorMsg = (await this.parseProviderError(part.error)) ||
(typeof part.error === 'string' ? part.error : JSON.stringify(part.error, null, 2));
throw new Error(`${messagePrefix} Error in chat response: ${errorMsg}`);
}
Expand All @@ -530,16 +531,16 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
const warnings = await result.warnings;
if (warnings) {
for (const warning of warnings) {
log.warn(`[${this.providerName}] [${aiModel.modelId}] warn: ${warning}`);
log.warn(`[${this.providerName}] [${model.id}] warn: ${warning}`);
}
}

// ai-sdk provides token usage in the result but it's not clear how it is calculated
const usage = await result.usage;
const metadata = await result.providerMetadata;
const tokens: TokenUsage = {
inputTokens: usage.promptTokens,
outputTokens: usage.completionTokens,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens,
cachedTokens: 0,
providerMetadata: metadata,
};
Expand Down Expand Up @@ -654,7 +655,7 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider
id: model.identifier,
name: model.name,
family: this.provider,
version: this.aiProvider(model.identifier).specificationVersion,
version: 'v2',
provider: this.provider,
providerName: this.providerName,
capabilities: this.capabilities,
Expand All @@ -668,12 +669,11 @@ abstract class AILanguageModel implements positron.ai.LanguageModelChatProvider

protected createDefaultModel(): vscode.LanguageModelChatInformation[] {
log.info(`[${this.providerName}] No models available; returning default model information.`);
const aiModel = this.aiProvider(this._config.model, this.aiOptions);
const modelInfo = createModelInfo({
id: aiModel.modelId,
id: this._config.model,
name: this.name,
family: aiModel.provider,
version: aiModel.specificationVersion,
family: this._config.provider,
version: 'v2',
provider: this._config.provider,
providerName: this.providerName,
capabilities: this.capabilities,
Expand Down Expand Up @@ -774,7 +774,7 @@ export class OpenAILanguageModel extends AILanguageModel implements positron.ai.

async provideLanguageModelChatInformation(options: { silent: boolean }, token: vscode.CancellationToken): Promise<vscode.LanguageModelChatInformation[]> {
log.debug(`[${this.providerName}] Preparing language model chat information...`);
const models = await this.resolveModels(token) ?? [];
const models = (await this.resolveModels(token)) ?? [];

log.debug(`[${this.providerName}] Resolved ${models.length} models.`);
return this.filterModels(models);
Expand Down
10 changes: 0 additions & 10 deletions extensions/positron-assistant/src/openai-fetch-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,6 @@ function transformRequestBody(init: RequestInit, providerName: string): RequestI
bodyModified = true;
}

// Remove temperature parameter, as the default 0 value is not supported by some models.
// `temperature` is no longer set to 0 by default in AI SDK v5.
// Example error message without this fix:
// [OpenAI] [gpt-5]' Error in chat response: {"error":{"message":"Unsupported value: 'temperature' does not support 0 with this model. Only the default (1) value is supported.","type":"invalid_request_error","param":"temperature","code":"unsupported_value"}}
if (requestBody.temperature !== undefined) {
log.debug(`[${providerName}] [DEBUG] Removing temperature parameter to avoid unsupported value error`);
delete requestBody.temperature;
bodyModified = true;
}

if (bodyModified) {
log.debug(`[${providerName}] [DEBUG] Final request body:`, JSON.stringify(requestBody, null, 2));
return {
Expand Down
4 changes: 2 additions & 2 deletions extensions/positron-assistant/src/posit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ export class PositLanguageModel implements positron.ai.LanguageModelChatProvider

const body: Anthropic.MessageStreamParams = {
model: model.id,
max_tokens: options.modelOptions?.maxTokens ?? this.maxOutputTokens,
max_tokens: options.modelOptions?.maxOutputTokens ?? this.maxOutputTokens,
tools,
tool_choice,
system,
Expand Down Expand Up @@ -428,7 +428,7 @@ export class PositLanguageModel implements positron.ai.LanguageModelChatProvider

async provideLanguageModelChatInformation(_options: { silent: boolean }, token: vscode.CancellationToken): Promise<vscode.LanguageModelChatInformation[]> {
log.debug(`[${this.providerName}] Preparing language model chat information...`);
const models = await this.resolveModels(token) ?? [];
const models = (await this.resolveModels(token)) ?? [];

log.debug(`[${this.providerName}] Resolved ${models.length} models.`);
return this.filterModels(models);
Expand Down
Loading
Loading