Skip to content

Commit

Permalink
Add prompts migration API (#5954)
Browse files Browse the repository at this point in the history
Part of SRCH-1162
Closes CODY-3052

This PR adds commands to prompt migration. We store commands locally,
and this migration transfers commands to the prompts library (since the
prompts library has got near feature parity with commands when it comes
to chat submission and inline change execution)

- You can run this migration only once per repository 
- After you start running this migration welcome area and prompts tab
should have the same state in the migration banner (which can be the
initial state, scanning for commands, migration, or error success)
- After the migration is complete, you should see no banner unless you
open VSCode in another repository, and this repository contains some
custom commands.
- You can dismiss the migration banner on the welcome area only 


| Welcome screen | Prompts Tab |
| --------- | -------- | 
| <img width="1092" alt="Screenshot 2024-10-21 at 21 34 28"
src="https://github.com/user-attachments/assets/31597726-ca48-461a-b237-5b07177a30e1">
| <img width="1092" alt="Screenshot 2024-10-21 at 21 34 18"
src="https://github.com/user-attachments/assets/291aaf79-cc53-4524-9b87-f2393f692dc0">
|
 
## Test plan
- Check if you can see and use the migration tool to transfer your
custom commands to the prompts library
  • Loading branch information
vovakulikov authored Oct 22, 2024
1 parent 63d1221 commit 8cff1fc
Show file tree
Hide file tree
Showing 27 changed files with 877 additions and 33 deletions.
8 changes: 7 additions & 1 deletion lib/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export {
isNodeResponse,
INCLUDE_EVERYTHING_CONTEXT_FILTERS,
EXCLUDE_EVERYTHING_CONTEXT_FILTERS,
PromptMode,
type BrowserOrNodeResponse,
type LogEventMode,
type ContextFilters,
Expand Down Expand Up @@ -380,5 +381,10 @@ export {
cachedUserProductSubscription,
userProductSubscription,
} from './sourcegraph-api/userProductSubscription'
export { siteVersion, currentSiteVersion } from './sourcegraph-api/siteVersion'
export {
siteVersion,
currentSiteVersion,
isValidVersion,
checkVersion,
} from './sourcegraph-api/siteVersion'
export { configOverwrites } from './models/configOverwrites'
2 changes: 1 addition & 1 deletion lib/shared/src/misc/observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export function promiseFactoryToObservable<T>(
}
}
}
run()
void run()

return () => {
unsubscribed = true
Expand Down
53 changes: 53 additions & 0 deletions lib/shared/src/misc/rpc/webviewAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export interface WebviewToExtensionAPI {
*/
prompts(query: string): Observable<PromptsResult>

/** The commands to prompts library migration information. */
promptsMigrationStatus(): Observable<PromptsMigrationStatus>

startPromptsMigration(): Observable<void>

/**
* The models data, including all available models, site defaults, and user preferences.
*/
Expand Down Expand Up @@ -96,6 +101,7 @@ export function createExtensionAPI(
staticInitialContext?: ContextItem[]
): WebviewToExtensionAPI {
const hydratePromptMessage = proxyExtensionAPI(messageAPI, 'hydratePromptMessage')

return {
mentionMenuData: proxyExtensionAPI(messageAPI, 'mentionMenuData'),
evaluatedFeatureFlag: proxyExtensionAPI(messageAPI, 'evaluatedFeatureFlag'),
Expand All @@ -109,6 +115,8 @@ export function createExtensionAPI(
? () => Observable.of(staticInitialContext)
: proxyExtensionAPI(messageAPI, 'initialContext'),
detectIntent: proxyExtensionAPI(messageAPI, 'detectIntent'),
promptsMigrationStatus: proxyExtensionAPI(messageAPI, 'promptsMigrationStatus'),
startPromptsMigration: proxyExtensionAPI(messageAPI, 'startPromptsMigration'),
resolvedConfig: proxyExtensionAPI(messageAPI, 'resolvedConfig'),
authStatus: proxyExtensionAPI(messageAPI, 'authStatus'),
transcript: proxyExtensionAPI(messageAPI, 'transcript'),
Expand Down Expand Up @@ -147,3 +155,48 @@ export interface PromptsResult {
/** The original query used to fetch this result. */
query: string
}

export type PromptsMigrationStatus =
| InitialPromptsMigrationStatus
| InProgressPromptsMigrationStatus
| SuccessfulPromptsMigrationStatus
| FailedPromptsMigrationStatus
| PromptsMigrationSkipStatus
| NoPromptsMigrationNeeded

interface InitialPromptsMigrationStatus {
type: 'initial_migration'
}

interface InProgressPromptsMigrationStatus {
type: 'migrating'

/**
* Current number of commands that we've migrated during the current session
* (current migration run).
*/
commandsMigrated: number

/**
* undefined value means that we're still scanning existing prompts to calculate
* total commands to migrate (scan first to avoid duplications after migration).
*/
allCommandsToMigrate: number | undefined
}

interface SuccessfulPromptsMigrationStatus {
type: 'migration_success'
}

interface FailedPromptsMigrationStatus {
type: 'migration_failed'
errorMessage: string
}

interface PromptsMigrationSkipStatus {
type: 'migration_skip'
}

interface NoPromptsMigrationNeeded {
type: 'no_migration_needed'
}
70 changes: 69 additions & 1 deletion lib/shared/src/sourcegraph-api/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import { addCodyClientIdentificationHeaders } from '../client-name-version'
import { DOTCOM_URL, isDotCom } from '../environments'
import { isAbortError } from '../errors'
import {
CHANGE_PROMPT_VISIBILITY,
CHAT_INTENT_QUERY,
CONTEXT_FILTERS_QUERY,
CONTEXT_SEARCH_QUERY,
CONTEXT_SEARCH_QUERY_WITH_RANGES,
CREATE_PROMPT_MUTATION,
CURRENT_SITE_CODY_CONFIG_FEATURES,
CURRENT_SITE_CODY_LLM_CONFIGURATION,
CURRENT_SITE_CODY_LLM_CONFIGURATION_SMART_CONTEXT,
Expand All @@ -33,6 +35,7 @@ import {
CURRENT_USER_CODY_SUBSCRIPTION_QUERY,
CURRENT_USER_ID_QUERY,
CURRENT_USER_INFO_QUERY,
CURRENT_USER_ROLE_QUERY,
DELETE_ACCESS_TOKEN_MUTATION,
EVALUATE_FEATURE_FLAG_QUERY,
FILE_CONTENTS_QUERY,
Expand Down Expand Up @@ -182,12 +185,17 @@ interface CurrentUserIdResponse {
currentUser: { id: string } | null
}

interface CurrentUserRoleResponse {
currentUser: { id: string; siteAdmin: boolean } | null
}

interface CurrentUserInfoResponse {
currentUser: {
id: string
hasVerifiedEmail: boolean
displayName?: string
username: string
siteAdmin: boolean
avatarURL: string
codyProEnabled: boolean
primaryEmail?: { email: string } | null
Expand Down Expand Up @@ -428,7 +436,22 @@ export interface Prompt {
}
}

export type PromptMode = 'CHAT' | 'EDIT' | 'INSERT'
export interface PromptInput {
owner: string
name: string
description: string
definitionText: string
draft: boolean
autoSubmit: boolean
mode: PromptMode
visibility?: 'PUBLIC' | 'SECRET'
}

export enum PromptMode {
CHAT = 'CHAT',
EDIT = 'EDIT',
INSERT = 'INSERT',
}

interface ContextFiltersResponse {
site: {
Expand Down Expand Up @@ -495,6 +518,7 @@ export interface CurrentUserInfo {
hasVerifiedEmail: boolean
username: string
displayName?: string
siteAdmin: boolean
avatarURL: string
primaryEmail?: { email: string } | null
organizations: {
Expand Down Expand Up @@ -750,6 +774,17 @@ export class SourcegraphGraphQLAPIClient {
)
}

public async isCurrentUserSideAdmin(): Promise<
CurrentUserRoleResponse['currentUser'] | null | Error
> {
return this.fetchSourcegraphAPI<APIResponse<CurrentUserRoleResponse>>(
CURRENT_USER_ROLE_QUERY,
{}
).then(response =>
extractDataOrError(response, data => (data.currentUser ? data.currentUser : null))
)
}

public async getCurrentUserCodyProEnabled(): Promise<{ codyProEnabled: boolean } | null | Error> {
return this.fetchSourcegraphAPI<APIResponse<CurrentUserCodyProEnabledResponse>>(
CURRENT_USER_CODY_PRO_ENABLED_QUERY,
Expand Down Expand Up @@ -1130,6 +1165,39 @@ export class SourcegraphGraphQLAPIClient {
return result
}

public async createPrompt(input: PromptInput): Promise<{ id: string }> {
const response = await this.fetchSourcegraphAPI<APIResponse<{ createPrompt: { id: string } }>>(
CREATE_PROMPT_MUTATION,
{ input }
)

const result = extractDataOrError(response, data => data.createPrompt)

if (result instanceof Error) {
throw result
}

return result
}

public async transferPromptOwnership(input: {
id: string
visibility: 'PUBLIC' | 'SECRET'
}): Promise<void> {
const response = await this.fetchSourcegraphAPI<APIResponse<unknown>>(CHANGE_PROMPT_VISIBILITY, {
id: input.id,
newVisibility: input.visibility,
})

const result = extractDataOrError(response, data => data)

if (result instanceof Error) {
throw result
}

return
}

/**
* recordTelemetryEvents uses the new Telemetry API to record events that
* gets exported: https://sourcegraph.com/docs/dev/background-information/telemetry
Expand Down
24 changes: 24 additions & 0 deletions lib/shared/src/sourcegraph-api/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ query CurrentUser {
}
}`

export const CURRENT_USER_ROLE_QUERY = `
query CurrentUserRole {
currentUser {
id
siteAdmin
}
}`

export const CURRENT_USER_CODY_PRO_ENABLED_QUERY = `
query CurrentUserCodyProEnabled {
currentUser {
Expand Down Expand Up @@ -436,6 +444,22 @@ mutation RecordTelemetryEvents($events: [TelemetryEventInput!]!) {
}
`

export const CREATE_PROMPT_MUTATION = `
mutation CreatePrompt($input: PromptInput!) {
createPrompt(input: $input) {
id
}
}
`

export const CHANGE_PROMPT_VISIBILITY = `
mutation ChangePromptVisibility($id: ID!, $newVisibility: PromptVisibility!) {
changePromptVisibility(id: $id, newVisibility: $newVisibility) {
id
}
}
`

export const CURRENT_SITE_IDENTIFICATION = `
query SiteIdentification {
site {
Expand Down
34 changes: 34 additions & 0 deletions lib/shared/src/sourcegraph-api/siteVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,40 @@ export function currentSiteVersion(): Promise<SiteAndCodyAPIVersions | null> {
return firstResultFromOperation(siteVersionStorage.observable)
}

interface CheckVersionInput {
currentVersion: string
minimumVersion: string
insider?: boolean
}

export async function isValidVersion({ minimumVersion }: { minimumVersion: string }): Promise<boolean> {
const currentVersion = await currentSiteVersion()

if (currentVersion === null) {
return false
}

return checkVersion({ minimumVersion, currentVersion: currentVersion.siteVersion })
}

/**
* Checks if the current site version is valid based on the given criteria.
*
* @param options - The options for version validation.
* @param options.minimumVersion - The minimum version required.
* @param options.insider - Whether to consider insider builds as valid. Defaults to true.
* @returns A promise that resolves to a boolean indicating if the version is valid.
*/
export function checkVersion({
minimumVersion,
currentVersion,
insider = true,
}: CheckVersionInput): boolean {
const isInsiderBuild = currentVersion.length > 12 || currentVersion.includes('dev')

return (insider && isInsiderBuild) || semver.gte(currentVersion, minimumVersion)
}

type CodyApiVersion = 0 | 1 | 2

/** @internal Exported for testing only. */
Expand Down
24 changes: 24 additions & 0 deletions pnpm-lock.yaml

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

1 change: 1 addition & 0 deletions vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,7 @@
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-form": "^0.1.0",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.0.7",
Expand Down
2 changes: 1 addition & 1 deletion vscode/src/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ export async function validateCredentials(
}
}

logDebug('auth', `Authentication succeeed to endpoint ${config.auth.serverEndpoint}`)
logDebug('auth', `Authentication succeed to endpoint ${config.auth.serverEndpoint}`)
return newAuthStatus({
...userInfo,
endpoint: config.auth.serverEndpoint,
Expand Down
Loading

0 comments on commit 8cff1fc

Please sign in to comment.