Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/image.tsx
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { RequestType } from '@vscode/copilot-api';
7
import * as l10n from '@vscode/l10n';
8
import { Image as BaseImage, BasePromptElementProps, ChatResponseReferencePartStatusKind, PromptElement, PromptReference, PromptSizing, UserMessage } from '@vscode/prompt-tsx';
9
import { IAuthenticationService } from '../../../../platform/authentication/common/authentication';
10
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
11
import { modelCanUseImageURL } from '../../../../platform/endpoint/common/chatModelCapabilities';
12
import { IImageService } from '../../../../platform/image/common/imageService';
13
import { ILogService } from '../../../../platform/log/common/logService';
14
import { IExperimentationService } from '../../../../platform/telemetry/common/nullExperimentationService';
15
import { getMimeType } from '../../../../util/common/imageUtils';
16
import { Uri } from '../../../../vscodeTypes';
17
import { IPromptEndpoint } from '../base/promptRenderer';
18
19
export interface ImageProps extends BasePromptElementProps {
20
variableName: string;
21
variableValue: Uint8Array | Promise<Uint8Array>;
22
omitReferences?: boolean;
23
reference?: Uri;
24
}
25
26
/**
27
* Props for rendering an image that was previously rendered and stored in conversation history.
28
* These images are already processed (base64 or URL) and don't need re-uploading.
29
*/
30
export interface HistoricalImageProps extends BasePromptElementProps {
31
/** The image source - either a base64 string or URL */
32
src: string;
33
/** The detail level for the image */
34
detail?: 'auto' | 'low' | 'high';
35
/** The MIME type of the image */
36
mimeType?: string;
37
}
38
39
/**
40
* Renders an image from conversation history.
41
* Checks if the current model supports vision and omits the image if not.
42
*/
43
export class HistoricalImage extends PromptElement<HistoricalImageProps, unknown> {
44
constructor(
45
props: HistoricalImageProps,
46
@IPromptEndpoint private readonly promptEndpoint: IPromptEndpoint,
47
@IAuthenticationService private readonly authService: IAuthenticationService,
48
) {
49
super(props);
50
}
51
52
override async render(_state: unknown, sizing: PromptSizing) {
53
// If the model doesn't support vision, omit historical images
54
if (!this.promptEndpoint.supportsVision || !this.authService.copilotToken?.isEditorPreviewFeaturesEnabled()) {
55
return undefined;
56
}
57
58
return <BaseImage src={this.props.src} detail={this.props.detail} mimeType={this.props.mimeType} />;
59
}
60
}
61
62
export class Image extends PromptElement<ImageProps, unknown> {
63
constructor(
64
props: ImageProps,
65
@IPromptEndpoint private readonly promptEndpoint: IPromptEndpoint,
66
@IAuthenticationService private readonly authService: IAuthenticationService,
67
@ILogService private readonly logService: ILogService,
68
@IImageService private readonly imageService: IImageService,
69
@IConfigurationService private readonly configurationService: IConfigurationService,
70
@IExperimentationService private readonly experimentationService: IExperimentationService
71
) {
72
super(props);
73
}
74
75
override async render(_state: unknown, sizing: PromptSizing) {
76
const options = { status: { description: l10n.t("{0} does not support images.", this.promptEndpoint.model), kind: ChatResponseReferencePartStatusKind.Omitted } };
77
78
const fillerUri: Uri = this.props.reference ?? Uri.parse('Attached Image');
79
80
try {
81
if (!this.promptEndpoint.supportsVision || !this.authService.copilotToken?.isEditorPreviewFeaturesEnabled()) {
82
if (this.props.omitReferences) {
83
return;
84
}
85
86
return (
87
<>
88
<references value={[new PromptReference(this.props.variableName ? { variableName: this.props.variableName, value: fillerUri } : fillerUri, undefined, options)]} />
89
</>
90
);
91
}
92
const variable = await this.props.variableValue;
93
let imageSource = Buffer.from(variable).toString('base64');
94
let imageMimeType: string | undefined = undefined;
95
96
const isChatRequest = typeof this.promptEndpoint.urlOrRequestMetadata !== 'string' && (this.promptEndpoint.urlOrRequestMetadata.type === RequestType.ChatCompletions || this.promptEndpoint.urlOrRequestMetadata.type === RequestType.ChatResponses || this.promptEndpoint.urlOrRequestMetadata.type === RequestType.ChatMessages);
97
const enabled = this.configurationService.getExperimentBasedConfig(ConfigKey.EnableChatImageUpload, this.experimentationService);
98
if (isChatRequest && enabled && modelCanUseImageURL(this.promptEndpoint)) {
99
try {
100
const githubToken = (await this.authService.getGitHubSession('any', { silent: true }))?.accessToken;
101
const mimeType = getMimeType(imageSource) ?? imageMimeType;
102
const uri = await this.imageService.uploadChatImageAttachment(variable, this.props.variableName, mimeType, githubToken);
103
if (uri) {
104
imageSource = uri.toString();
105
imageMimeType = mimeType;
106
}
107
} catch (error) {
108
this.logService.warn(`Image upload failed, using base64 fallback: ${error}`);
109
}
110
}
111
112
return (
113
<UserMessage priority={0}>
114
<BaseImage src={imageSource} detail='high' mimeType={imageMimeType} />
115
{this.props.reference && (
116
<references value={[new PromptReference(this.props.variableName ? { variableName: this.props.variableName, value: fillerUri } : fillerUri, undefined)]} />
117
)}
118
</UserMessage>
119
);
120
} catch (err) {
121
if (this.props.omitReferences) {
122
return;
123
}
124
125
return (
126
<>
127
<references value={[new PromptReference(this.props.variableName ? { variableName: this.props.variableName, value: fillerUri } : fillerUri, undefined, options)]} />
128
</>);
129
}
130
}
131
}
132
133