diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index 3084d12997..295457c0f8 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -33,6 +33,7 @@ import { ChatStart, ChatChunk, ChatCancel, + LanguageModelConfigurationResponse, } from "../../core/src/server/messages" import { envInfo } from "./info" import { LanguageModel } from "../../core/src/chat" @@ -189,6 +190,19 @@ export async function startServer(options: { port: string }) { process.exit(0) break } + case "model.configuration": { + const { model, token } = data + console.log(`model: lookup configuration ${model}`) + const info = await host.getLanguageModelConfiguration( + model, + { token } + ) + response = { + ok: true, + info, + } + break + } case "tests.run": { console.log( `tests: run ${data.scripts?.join(", ") || "*"}` diff --git a/packages/core/src/connection.ts b/packages/core/src/connection.ts index 13f6c6f63d..3c4475713d 100644 --- a/packages/core/src/connection.ts +++ b/packages/core/src/connection.ts @@ -107,8 +107,8 @@ export async function parseTokenFromEnv( ? "GITHUB_MODELS_TOKEN" : "GITHUB_TOKEN" const token = env[tokenVar] - // TODO: handle missing token - // if (!token) throw new Error("GITHUB_TOKEN must be set") + if (!token) + throw new Error("GITHUB_MODELS_TOKEN or GITHUB_TOKEN must be set") const type = "openai" const base = GITHUB_MODELS_BASE return { diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 4de1b3e21c..afd0d6b412 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -37,12 +37,8 @@ export const CLIENT_RECONNECT_DELAY = 3000 export const CLIENT_RECONNECT_MAX_ATTEMPTS = 20 export const RETRIEVAL_PERSIST_DIR = "retrieval" export const HIGHLIGHT_LENGTH = 4000 -export const DEFAULT_MODEL = "openai:gpt-4" -export const DEFAULT_MODEL_CANDIDATES = [ - "openai:gpt-4o", - "azure:gpt-4o", - "github:gpt-4o", -] +export const DEFAULT_MODEL = "openai:gpt-4o" +export const DEFAULT_MODEL_CANDIDATES = ["azure:gpt-4o", "github:gpt-4o"] export const DEFAULT_EMBEDDINGS_MODEL = "openai:text-embedding-ada-002" export const DEFAULT_TEMPERATURE = 0.8 export const BUILTIN_PREFIX = "_builtin/" diff --git a/packages/core/src/github.ts b/packages/core/src/github.ts index 0056b1934e..232630f127 100644 --- a/packages/core/src/github.ts +++ b/packages/core/src/github.ts @@ -5,7 +5,7 @@ import { GITHUB_TOKEN, } from "./constants" import { createFetch } from "./fetch" -import { host } from "./host" +import { runtimeHost } from "./host" import { link, prettifyMarkdown } from "./markdown" import { logError, logVerbose, normalizeInt } from "./util" @@ -70,7 +70,7 @@ export async function githubUpdatePullRequestDescription( assert(commentTag) if (!issue) return { updated: false, statusText: "missing issue number" } - const token = await host.readSecret(GITHUB_TOKEN) + const token = await runtimeHost.readSecret(GITHUB_TOKEN) if (!token) return { updated: false, statusText: "missing github token" } text = prettifyMarkdown(text) @@ -169,7 +169,7 @@ export async function githubCreateIssueComment( const { apiUrl, repository, issue } = info if (!issue) return { created: false, statusText: "missing issue number" } - const token = await host.readSecret(GITHUB_TOKEN) + const token = await runtimeHost.readSecret(GITHUB_TOKEN) if (!token) return { created: false, statusText: "missing github token" } const fetch = await createFetch({ retryOn: [] }) @@ -313,7 +313,7 @@ export async function githubCreatePullRequestReviews( logError("missing commit sha") return false } - const token = await host.readSecret(GITHUB_TOKEN) + const token = await runtimeHost.readSecret(GITHUB_TOKEN) if (!token) { logError("missing github token") return false diff --git a/packages/core/src/host.ts b/packages/core/src/host.ts index 2784b15834..e56534d9fc 100644 --- a/packages/core/src/host.ts +++ b/packages/core/src/host.ts @@ -97,7 +97,6 @@ export interface Host { resolvePath(...segments: string[]): string // read a secret from the environment or a .env file - readSecret(name: string): Promise defaultModelOptions: Required> defaultEmbeddingsModelOptions: Required< Pick @@ -130,6 +129,7 @@ export interface RuntimeHost extends Host { models: ModelService workspace: Omit + readSecret(name: string): Promise // executes a process exec( containerId: string, diff --git a/packages/core/src/server/client.ts b/packages/core/src/server/client.ts index df74c5e9da..25f3b3bb84 100644 --- a/packages/core/src/server/client.ts +++ b/packages/core/src/server/client.ts @@ -3,7 +3,7 @@ import { CLIENT_RECONNECT_DELAY, OPEN, RECONNECT } from "../constants" import { randomHex } from "../crypto" import { errorMessage } from "../error" import { GenerationResult } from "../generation" -import { ResponseStatus, host } from "../host" +import { LanguageModelConfiguration, ResponseStatus, host } from "../host" import { MarkdownTrace } from "../trace" import { assert, logError } from "../util" import { @@ -23,6 +23,7 @@ import { ChatEvents, ChatChunk, ChatStart, + LanguageModelConfigurationRequest, } from "./messages" export type LanguageModelChatRequest = ( @@ -231,6 +232,18 @@ export class WebSocketClient extends EventTarget { cancellers.forEach((a) => a.reject(reason || "cancelled")) } + async getLanguageModelConfiguration( + modelId: string, + options?: { token?: boolean } + ): Promise { + const res = await this.queue({ + type: "model.configuration", + model: modelId, + token: options?.token, + }) + return res.response?.ok ? res.response.info : undefined + } + async version(): Promise { const res = await this.queue({ type: "server.version" }) return res.version diff --git a/packages/core/src/server/messages.ts b/packages/core/src/server/messages.ts index 2ce174613c..0acf9c138e 100644 --- a/packages/core/src/server/messages.ts +++ b/packages/core/src/server/messages.ts @@ -1,6 +1,6 @@ import { ChatCompletionAssistantMessageParam } from "../chattypes" import { GenerationResult } from "../generation" -import { ResponseStatus } from "../host" +import { LanguageModelConfiguration, ResponseStatus } from "../host" export interface RequestMessage { type: string @@ -128,6 +128,17 @@ export interface ShellExec extends RequestMessage { response?: ShellExecResponse } +export interface LanguageModelConfigurationRequest extends RequestMessage { + type: "model.configuration" + model: string + token?: boolean + response?: LanguageModelConfigurationResponse +} + +export interface LanguageModelConfigurationResponse extends ResponseStatus { + info?: LanguageModelConfiguration +} + export interface ChatStart { type: "chat.start" chatId: string @@ -163,6 +174,7 @@ export type RequestMessages = | PromptScriptStart | PromptScriptAbort | ChatChunk + | LanguageModelConfigurationRequest export type PromptScriptResponseEvents = | PromptScriptProgressResponseEvent diff --git a/packages/core/src/websearch.ts b/packages/core/src/websearch.ts index c9db0f698c..22cccca376 100644 --- a/packages/core/src/websearch.ts +++ b/packages/core/src/websearch.ts @@ -1,6 +1,6 @@ import { BING_SEARCH_ENDPOINT } from "./constants" import { createFetch } from "./fetch" -import { host } from "./host" +import { runtimeHost } from "./host" import { MarkdownTrace } from "./trace" function toURLSearchParams(o: any) { @@ -47,7 +47,7 @@ export async function bingSearch( } = options || {} if (!q) return {} - const apiKey = await host.readSecret("BING_SEARCH_API_KEY") + const apiKey = await runtimeHost.readSecret("BING_SEARCH_API_KEY") if (!apiKey) throw new Error( "BING_SEARCH_API_KEY secret is required to use bing search. See https://microsoft.github.io/genaiscript/reference/scripts/web-search/#bing-web-search-configuration." diff --git a/packages/vscode/src/vshost.ts b/packages/vscode/src/vshost.ts index fae9523e51..396225061e 100644 --- a/packages/vscode/src/vshost.ts +++ b/packages/vscode/src/vshost.ts @@ -171,7 +171,10 @@ export class VSCodeHost extends EventTarget implements Host { } let files = Array.from(uris.values()) - if (applyGitIgnore && (await checkFileExists(this.projectUri, ".gitignore"))) { + if ( + applyGitIgnore && + (await checkFileExists(this.projectUri, ".gitignore")) + ) { const gitignore = await readFileText(this.projectUri, ".gitignore") files = await filterGitIgnore(gitignore, files) } @@ -186,26 +189,16 @@ export class VSCodeHost extends EventTarget implements Host { await vscode.workspace.fs.delete(uri, { recursive: true }) } - async readSecret(name: string): Promise { - try { - const dotenv = await readFileText(this.projectUri, DOT_ENV_FILENAME) - const env = dotEnvTryParse(dotenv) - return env?.[name] - } catch (e) { - return undefined - } - } - clientLanguageModel?: LanguageModel async getLanguageModelConfiguration( modelId: string, options?: { token?: boolean } & AbortSignalOptions & TraceOptions ): Promise { - const { signal, token: askToken } = options || {} - const dotenv = await readFileText(this.projectUri, DOT_ENV_FILENAME) - const env = dotEnvTryParse(dotenv) ?? {} - await parseDefaultsFromEnv(env) - const tok = await parseTokenFromEnv(env, modelId) + const tok = await this.server.client.getLanguageModelConfiguration( + modelId, + options + ) + const { token: askToken } = options || {} if ( askToken && tok &&