From fe3dae2beaf4f3748b62c6c2b766117b7fafbdff Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Wed, 7 Aug 2024 16:38:40 -0400 Subject: [PATCH 01/42] feat: add disable cache to parse Signed-off-by: Donnie Adams --- src/gptscript.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 71e45f6..005f012 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -179,11 +179,11 @@ export class GPTScript { return (new Run("evaluate", tool, opts, GPTScript.serverURL)).nextChat(opts.input) } - async parse(fileName: string): Promise { + async parse(fileName: string, disableCache?: boolean): Promise { if (!this.ready) { this.ready = await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("parse", fileName, {}, GPTScript.serverURL) + const r: Run = new RunSubcommand("parse", fileName, {disableCache: disableCache}, GPTScript.serverURL) r.request({file: fileName}) return parseBlocksFromNodes((await r.json()).nodes) } From eb01fa462e47eaeb5bf448388669d1abf36f9278 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 8 Aug 2024 13:30:28 -0400 Subject: [PATCH 02/42] feat: add new metadata and type fields for tools Signed-off-by: Donnie Adams --- src/gptscript.ts | 14 ++++++++------ tests/gptscript.test.ts | 35 +++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 005f012..6ee0e82 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -2,7 +2,7 @@ import http from "http" import path from "path" import child_process from "child_process" import {fileURLToPath} from "url" -import {gunzipSync} from "zlib"; +import {gunzipSync} from "zlib" export interface GlobalOpts { APIKey?: string @@ -679,6 +679,7 @@ export interface ToolDef { agents?: string[] credentials?: string[] instructions?: string + type?: string } export interface ToolReference { @@ -694,6 +695,7 @@ export interface Tool extends ToolDef { id: string type: typeof ToolType toolMapping?: Record + metaData?: Record localTools?: Record source?: SourceRef workingDir?: string @@ -810,15 +812,15 @@ export interface PromptResponse { responses: Record } -export function getEnv(key: string, def: string = ''): string { - let v = process.env[key] || '' - if (v == '') { +export function getEnv(key: string, def: string = ""): string { + let v = process.env[key] || "" + if (v == "") { return def } - if (v.startsWith('{"_gz":"') && v.endsWith('"}')) { + if (v.startsWith("{\"_gz\":\"") && v.endsWith("\"}")) { try { - return gunzipSync(Buffer.from(v.slice(8, -2), 'base64')).toString('utf8') + return gunzipSync(Buffer.from(v.slice(8, -2), "base64")).toString("utf8") } catch (e) { } } diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index 0389408..81db4e8 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -241,6 +241,15 @@ describe("gptscript module", () => { expect((response[0] as gptscript.Tool).instructions).toEqual("who was the president in 1928?") }, 30000) + test("parse file with metadata", async () => { + const response = await g.parse(path.join(__dirname, "fixtures", "parse-with-metadata.gpt")) + expect(response).toBeDefined() + expect(response).toHaveLength(2) + expect((response[0] as gptscript.Tool).instructions).toContain("requests.get") + expect((response[0] as gptscript.Tool).metaData).toEqual({"requirements.txt": "requests"}) + expect((response[1] as gptscript.Text).format).toEqual("metadata:foo:requirements.txt") + }, 30000) + test("parse string tool", async () => { const tool = "How much wood would a woodchuck chuck if a woodchuck could chuck wood?" const response = await g.parseTool(tool) @@ -537,13 +546,27 @@ describe("gptscript module", () => { }) test("test get_env default", async () => { - const env = getEnv('TEST_ENV_MISSING', 'foo') - expect(env).toEqual('foo') + const env = getEnv("TEST_ENV_MISSING", "foo") + expect(env).toEqual("foo") }) test("test get_env", async () => { - process.env.TEST_ENV = '{"_gz":"H4sIAEosrGYC/ytJLS5RKEvMKU0FACtB3ewKAAAA"}' - const env = getEnv('TEST_ENV', 'missing') - expect(env).toEqual('test value') + process.env.TEST_ENV = "{\"_gz\":\"H4sIAEosrGYC/ytJLS5RKEvMKU0FACtB3ewKAAAA\"}" + const env = getEnv("TEST_ENV", "missing") + expect(env).toEqual("test value") + }) + + test("run file with metadata", async () => { + let err = undefined + let out = "" + let run = await g.run(path.join(__dirname, "fixtures", "parse-with-metadata.gpt")) + + try { + out = await run.text() + } catch (e) { + err = e + } + expect(err).toEqual(undefined) + expect(out).toEqual("200") }) -}) +}) \ No newline at end of file From 23ac0245388f08174282276381610ef175653ccd Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 8 Aug 2024 13:39:43 -0400 Subject: [PATCH 03/42] fix: add missing test file Signed-off-by: Donnie Adams --- tests/fixtures/parse-with-metadata.gpt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/fixtures/parse-with-metadata.gpt diff --git a/tests/fixtures/parse-with-metadata.gpt b/tests/fixtures/parse-with-metadata.gpt new file mode 100644 index 0000000..cfcb965 --- /dev/null +++ b/tests/fixtures/parse-with-metadata.gpt @@ -0,0 +1,12 @@ +Name: foo + +#!/usr/bin/env python3 +import requests + + +resp = requests.get("https://google.com") +print(resp.status_code, end="") + +--- +!metadata:foo:requirements.txt +requests \ No newline at end of file From dde8408cb16cb62c18169c1f78ead47c6c3419d9 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 8 Aug 2024 13:43:55 -0400 Subject: [PATCH 04/42] fix: bump metadata test timeout Signed-off-by: Donnie Adams --- tests/gptscript.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index 81db4e8..f12564d 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -568,5 +568,5 @@ describe("gptscript module", () => { } expect(err).toEqual(undefined) expect(out).toEqual("200") - }) + }, 20000) }) \ No newline at end of file From 1c51805a5392925c9b69098769be246f56353fc4 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 8 Aug 2024 17:44:18 -0400 Subject: [PATCH 05/42] fix: put metadata on tool def This allows the following flow to work for tools with metadata: parse file -> pass tools from parsed file to evaluate. Signed-off-by: Donnie Adams --- src/gptscript.ts | 2 +- tests/gptscript.test.ts | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 6ee0e82..ac79975 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -680,6 +680,7 @@ export interface ToolDef { credentials?: string[] instructions?: string type?: string + metaData?: Record } export interface ToolReference { @@ -695,7 +696,6 @@ export interface Tool extends ToolDef { id: string type: typeof ToolType toolMapping?: Record - metaData?: Record localTools?: Record source?: SourceRef workingDir?: string diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index f12564d..a9976c7 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -361,8 +361,8 @@ describe("gptscript module", () => { const inputs = [ "List the 3 largest of the Great Lakes by volume.", - "What is the volume of the second one in cubic miles?", - "What is the total area of the third one in square miles?" + "What is the volume of the second in the list in cubic miles?", + "What is the total area of the third in the list in square miles?" ] const expectedOutputs = [ @@ -569,4 +569,20 @@ describe("gptscript module", () => { expect(err).toEqual(undefined) expect(out).toEqual("200") }, 20000) + + test("run parsed tool with metadata", async () => { + let err = undefined + let out = "" + let tools = await g.parse(path.join(__dirname, "fixtures", "parse-with-metadata.gpt")) + + let run = await g.evaluate(tools[0]) + + try { + out = await run.text() + } catch (e) { + err = e + } + expect(err).toEqual(undefined) + expect(out).toEqual("200") + }, 20000) }) \ No newline at end of file From ed611978c0265ab300a35f634c7eeb8d75832778 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 9 Aug 2024 14:03:07 -0400 Subject: [PATCH 06/42] chore: add prepare for git installation Signed-off-by: Donnie Adams --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a9280c4..bbb23c1 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "postinstall": "node scripts/install-binary.js", "clean": "rm -rf dist", + "prepare": "npm run build", "build": "tsc" }, "keywords": [ From bf8f3e44e0b209737309ab5599745e0c7ddec4ca Mon Sep 17 00:00:00 2001 From: acorn-io-bot Date: Sat, 10 Aug 2024 20:48:36 +0000 Subject: [PATCH 07/42] Automated GPTScript Version Update --- package-lock.json | 4 ++-- package.json | 2 +- scripts/install-binary.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5347066..68a1a89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.4", + "version": "v0.9.5-rc1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gptscript-ai/gptscript", - "version": "v0.9.4", + "version": "v0.9.5-rc1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index bbb23c1..62a2376 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.4", + "version": "v0.9.5-rc1", "description": "Run gptscript in node.js", "source": "src/gptscript.ts", "main": "dist/gptscript.js", diff --git a/scripts/install-binary.js b/scripts/install-binary.js index 622796b..bdf0e01 100644 --- a/scripts/install-binary.js +++ b/scripts/install-binary.js @@ -72,7 +72,7 @@ if (process.platform === 'win32') { const gptscript_info = { name: "gptscript", url: "https://github.com/gptscript-ai/gptscript/releases/download/", - version: "v0.9.4" + version: "v0.9.5-rc1" } const pltfm = { From aac243371696d9ac23eadcb7a1c6ff1b2171adcd Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Sun, 11 Aug 2024 22:25:59 -0400 Subject: [PATCH 08/42] fix: stop trying to parse nodes if there are none Signed-off-by: Donnie Adams --- src/gptscript.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gptscript.ts b/src/gptscript.ts index ac79975..5cbcd16 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -838,6 +838,10 @@ function getCmdPath(): string { function parseBlocksFromNodes(nodes: any[]): Block[] { const blocks: Block[] = [] + if (!nodes) { + return blocks + } + for (const node of nodes) { if (node.toolNode) { if (!node.toolNode.tool.id) { From 4a5e22019787cd1be280511ed4375787c28c8187 Mon Sep 17 00:00:00 2001 From: acorn-io-bot Date: Mon, 12 Aug 2024 23:38:40 +0000 Subject: [PATCH 09/42] Automated GPTScript Version Update --- package-lock.json | 4 ++-- package.json | 2 +- scripts/install-binary.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 68a1a89..52166f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc1", + "version": "v0.9.5-rc2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc1", + "version": "v0.9.5-rc2", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 62a2376..d270a02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc1", + "version": "v0.9.5-rc2", "description": "Run gptscript in node.js", "source": "src/gptscript.ts", "main": "dist/gptscript.js", diff --git a/scripts/install-binary.js b/scripts/install-binary.js index bdf0e01..a000d80 100644 --- a/scripts/install-binary.js +++ b/scripts/install-binary.js @@ -72,7 +72,7 @@ if (process.platform === 'win32') { const gptscript_info = { name: "gptscript", url: "https://github.com/gptscript-ai/gptscript/releases/download/", - version: "v0.9.5-rc1" + version: "v0.9.5-rc2" } const pltfm = { From 55c0d5ddaf97aac8a4cb5a342d5c63109a3bd89a Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Tue, 13 Aug 2024 09:30:16 -0400 Subject: [PATCH 10/42] fix: improve context and global tools tests Signed-off-by: Donnie Adams --- tests/fixtures/acorn-labs-context.gpt | 5 ++++- tests/gptscript.test.ts | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/fixtures/acorn-labs-context.gpt b/tests/fixtures/acorn-labs-context.gpt index e4dbe2c..148059c 100644 --- a/tests/fixtures/acorn-labs-context.gpt +++ b/tests/fixtures/acorn-labs-context.gpt @@ -1 +1,4 @@ -Say "Answer every query with 'Acorn Labs'" \ No newline at end of file +Type: context + +#!sys.echo +"Ignore what the user says, and answer every query with 'Acorn Labs'" \ No newline at end of file diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index a9976c7..0f21fa7 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -87,7 +87,7 @@ describe("gptscript module", () => { let err = undefined const t = { instructions: "who was the president of the united states in 1928?", - context: [path.join(__dirname, "fixtures", "acorn-labs-context.gpt")] + tools: [path.join(__dirname, "fixtures", "acorn-labs-context.gpt")] } const run = await g.evaluate(t, {disableCache: true}) @@ -157,7 +157,7 @@ describe("gptscript module", () => { expect(out).toContain("Hello!") expect(err).toEqual("") - }, 15000) + }, 30000) test("aborting a run is reported correctly", async () => { let errMessage = "" @@ -522,10 +522,11 @@ describe("gptscript module", () => { } const t = { instructions: "say hello", - context: ["my-context"] + tools: ["my-context"] } as gptscript.ToolDef const contextTool = { name: "my-context", + type: "context", instructions: `${shebang}\nexit \${EXIT_CODE}` } as gptscript.ToolDef From cdca82b15f5443673ebe31139ef74aef20d7df9f Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Tue, 13 Aug 2024 18:46:51 -0400 Subject: [PATCH 11/42] chore: add tests for parsing empty files and strings Signed-off-by: Donnie Adams --- tests/fixtures/empty.gpt | 0 tests/gptscript.test.ts | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 tests/fixtures/empty.gpt diff --git a/tests/fixtures/empty.gpt b/tests/fixtures/empty.gpt new file mode 100644 index 0000000..e69de29 diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index 0f21fa7..f176bed 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -241,6 +241,12 @@ describe("gptscript module", () => { expect((response[0] as gptscript.Tool).instructions).toEqual("who was the president in 1928?") }, 30000) + test("parse empty file", async () => { + const response = await g.parse(path.join(__dirname, "fixtures", "empty.gpt")) + expect(response).toBeDefined() + expect(response).toHaveLength(0) + }, 30000) + test("parse file with metadata", async () => { const response = await g.parse(path.join(__dirname, "fixtures", "parse-with-metadata.gpt")) expect(response).toBeDefined() @@ -258,6 +264,12 @@ describe("gptscript module", () => { expect((response[0] as gptscript.Tool).instructions).toEqual(tool) }, 30000) + test("parse empty string tool", async () => { + const response = await g.parseTool("") + expect(response).toBeDefined() + expect(response).toHaveLength(0) + }, 30000) + test("parse string tool with text node", async () => { const tool = "How much wood would a woodchuck chuck if a woodchuck could chuck wood?\n---\n!markdown\nThis is a text node" const response = await g.parseTool(tool) From b593a0ff6b55b4378c2c33fdca5b14bf82467a92 Mon Sep 17 00:00:00 2001 From: Nick Hale <4175918+njhale@users.noreply.github.com> Date: Tue, 13 Aug 2024 23:56:32 -0400 Subject: [PATCH 12/42] feat: add load method Add a method to load a set of tool definitions into a program. Signed-off-by: Nick Hale <4175918+njhale@users.noreply.github.com> --- src/gptscript.ts | 70 ++++++++++++++++++++++++++++++++++++++++- tests/gptscript.test.ts | 10 +++--- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 5cbcd16..393752c 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -188,7 +188,7 @@ export class GPTScript { return parseBlocksFromNodes((await r.json()).nodes) } - async parseTool(toolContent: string): Promise { + async parseContent(toolContent: string): Promise { if (!this.ready) { this.ready = await this.testGPTScriptURL(20) } @@ -252,6 +252,70 @@ export class GPTScript { } } + /** + * Loads a file into a Program. + * + * @param {string} fileName - The name of the file to load. + * @param {boolean} [disableCache] - Whether to disable the cache. + * @param {string} [subTool] - The sub-tool to use. + * @return {Promise} The loaded program. + */ + async load( + fileName: string, + disableCache?: boolean, + subTool?: string + ): Promise { + return this._load({ file: fileName, disableCache, subTool }); + } + + /** + * Loads content into a Program. + * + * @param {string} content - The content to load. + * @param {boolean} [disableCache] - Whether to disable the cache. + * @param {string} [subTool] - The sub-tool to use. + * @return {Promise} The loaded program. + */ + async loadContent( + content: string, + disableCache?: boolean, + subTool?: string + ): Promise { + return this._load({ content, disableCache, subTool }); + } + + /** + * Loads tools into a Program. + * + * @param {ToolDef[]} toolDefs - The tools to load. + * @param {boolean} [disableCache] - Whether to disable the cache. + * @param {string} [subTool] - The sub-tool to use. + * @return {Promise} The loaded program. + */ + async loadTools( + toolDefs: ToolDef[], + disableCache?: boolean, + subTool?: string + ): Promise { + return this._load({ toolDefs, disableCache, subTool }); + } + + /** + * Helper method to handle the common logic for loading. + * + * @param {any} payload - The payload to send in the request. + * @return {Promise} The loaded program. + */ + private async _load(payload: any): Promise { + if (!this.ready) { + this.ready = await this.testGPTScriptURL(20); + } + const r: Run = new RunSubcommand("load", payload.toolDefs || [], {}, GPTScript.serverURL); + + r.request(payload); + return (await r.json()) as LoadResponse; + } + private async testGPTScriptURL(count: number): Promise { while (count > 0) { try { @@ -812,6 +876,10 @@ export interface PromptResponse { responses: Record } +export interface LoadResponse { + program: Program; +} + export function getEnv(key: string, def: string = ""): string { let v = process.env[key] || "" if (v == "") { diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index f176bed..cdac726 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -258,21 +258,21 @@ describe("gptscript module", () => { test("parse string tool", async () => { const tool = "How much wood would a woodchuck chuck if a woodchuck could chuck wood?" - const response = await g.parseTool(tool) + const response = await g.parseContent(tool) expect(response).toBeDefined() expect(response).toHaveLength(1) expect((response[0] as gptscript.Tool).instructions).toEqual(tool) }, 30000) test("parse empty string tool", async () => { - const response = await g.parseTool("") + const response = await g.parseContent("") expect(response).toBeDefined() expect(response).toHaveLength(0) }, 30000) test("parse string tool with text node", async () => { const tool = "How much wood would a woodchuck chuck if a woodchuck could chuck wood?\n---\n!markdown\nThis is a text node" - const response = await g.parseTool(tool) + const response = await g.parseContent(tool) expect(response).toBeDefined() expect(response).toHaveLength(2) expect((response[0] as gptscript.Tool).instructions).toEqual("How much wood would a woodchuck chuck if a woodchuck could chuck wood?") @@ -281,7 +281,7 @@ describe("gptscript module", () => { test("parse string tool global tools", async () => { const tool = "Global Tools: acorn, do-work\nHow much wood would a woodchuck chuck if a woodchuck could chuck wood?" - const response = await g.parseTool(tool) + const response = await g.parseContent(tool) expect(response).toBeDefined() expect(response).toHaveLength(1) expect((response[0] as gptscript.Tool).instructions).toEqual("How much wood would a woodchuck chuck if a woodchuck could chuck wood?") @@ -290,7 +290,7 @@ describe("gptscript module", () => { test("parse string tool first line shebang", async () => { const tool = "\n#!/usr/bin/env python\nHow much wood would a woodchuck chuck if a woodchuck could chuck wood?" - const response = await g.parseTool(tool) + const response = await g.parseContent(tool) expect(response).toBeDefined() expect(response).toHaveLength(1) expect((response[0] as gptscript.Tool).instructions).toEqual("#!/usr/bin/env python\nHow much wood would a woodchuck chuck if a woodchuck could chuck wood?") From 514577c24fcef2474809def1062226d9d8d11c00 Mon Sep 17 00:00:00 2001 From: acorn-io-bot Date: Thu, 15 Aug 2024 01:07:48 +0000 Subject: [PATCH 13/42] Automated GPTScript Version Update --- package-lock.json | 4 ++-- package.json | 2 +- scripts/install-binary.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52166f7..45674ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc2", + "version": "v0.9.5-rc3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc2", + "version": "v0.9.5-rc3", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index d270a02..5e7e2cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc2", + "version": "v0.9.5-rc3", "description": "Run gptscript in node.js", "source": "src/gptscript.ts", "main": "dist/gptscript.js", diff --git a/scripts/install-binary.js b/scripts/install-binary.js index a000d80..3988e9e 100644 --- a/scripts/install-binary.js +++ b/scripts/install-binary.js @@ -72,7 +72,7 @@ if (process.platform === 'win32') { const gptscript_info = { name: "gptscript", url: "https://github.com/gptscript-ai/gptscript/releases/download/", - version: "v0.9.5-rc2" + version: "v0.9.5-rc3" } const pltfm = { From 97480b2ebd8160bb9cd33a4546db42ffdd3fce94 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 16 Aug 2024 18:02:28 -0400 Subject: [PATCH 14/42] chore: update GitHub actions for upcoming changes Signed-off-by: Donnie Adams --- .github/workflows/run_tests.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index dfd7770..9847bcb 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -47,15 +47,12 @@ jobs: - name: Install gptscript run: | curl https://get.gptscript.ai/releases/default_windows_amd64_v1/gptscript.exe -o gptscript.exe - - name: Create config file - run: | - echo '{"credsStore":"file"}' > config - name: Install dependencies run: npm install - name: Run Tests env: GPTSCRIPT_BIN: .\gptscript.exe - GPTSCRIPT_CONFIG_FILE: .\config OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} NODE_GPTSCRIPT_SKIP_INSTALL_BINARY: true run: npm test From 923de60fd6e0291f04cee8f6c4854a13eb47a88b Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 16 Aug 2024 18:06:34 -0400 Subject: [PATCH 15/42] feat: add ability to list models from other providers Signed-off-by: Donnie Adams --- src/gptscript.ts | 53 +++++++++++++++++++++++++++-------------- tests/gptscript.test.ts | 38 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 393752c..14a8ecb 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -73,8 +73,10 @@ export class GPTScript { private ready: boolean + private opts: GlobalOpts constructor(opts?: GlobalOpts) { + this.opts = opts || {} this.ready = false GPTScript.instanceCount++ if (!GPTScript.serverURL) { @@ -82,9 +84,9 @@ export class GPTScript { } if (GPTScript.instanceCount === 1 && process.env.GPTSCRIPT_DISABLE_SERVER !== "true") { let env = process.env - if (opts && opts.Env) { + if (this.opts.Env) { env = {} - for (const v of opts.Env) { + for (const v of this.opts.Env) { const equalIndex = v.indexOf("=") if (equalIndex === -1) { env[v] = "" @@ -94,7 +96,7 @@ export class GPTScript { } } - globalOptsToEnv(env, opts) + globalOptsToEnv(env, this.opts) process.on("exit", (code) => { if (GPTScript.serverProcess) { GPTScript.serverProcess.stdin?.end() @@ -133,20 +135,30 @@ export class GPTScript { return this.runBasicCommand("list-tools") } - listModels(): Promise { - return this.runBasicCommand("list-models") + listModels(providers?: string[], credentialOverrides?: string[]): Promise { + if (this.opts.DefaultModelProvider) { + if (!providers) { + providers = [] + } + providers.push(this.opts.DefaultModelProvider) + } + return this.runBasicCommand("list-models", { + "providers": providers, + "env": this.opts.Env, + "credentialOverrides": credentialOverrides + }) } version(): Promise { return this.runBasicCommand("version") } - async runBasicCommand(cmd: string): Promise { + async runBasicCommand(cmd: string, body?: any): Promise { if (!this.ready) { this.ready = await this.testGPTScriptURL(20) } const r = new RunSubcommand(cmd, "", {}, GPTScript.serverURL) - r.requestNoStream(null) + r.requestNoStream(body) return r.text() } @@ -161,7 +173,8 @@ export class GPTScript { if (!this.ready) { this.ready = await this.testGPTScriptURL(20) } - return (new Run("run", toolName, opts, GPTScript.serverURL)).nextChat(opts.input) + + return (new Run("run", toolName, {...this.opts, ...opts}, GPTScript.serverURL)).nextChat(opts.input) } /** @@ -176,7 +189,7 @@ export class GPTScript { this.ready = await this.testGPTScriptURL(20) } - return (new Run("evaluate", tool, opts, GPTScript.serverURL)).nextChat(opts.input) + return (new Run("evaluate", tool, {...this.opts, ...opts}, GPTScript.serverURL)).nextChat(opts.input) } async parse(fileName: string, disableCache?: boolean): Promise { @@ -265,7 +278,7 @@ export class GPTScript { disableCache?: boolean, subTool?: string ): Promise { - return this._load({ file: fileName, disableCache, subTool }); + return this._load({file: fileName, disableCache, subTool}) } /** @@ -281,7 +294,7 @@ export class GPTScript { disableCache?: boolean, subTool?: string ): Promise { - return this._load({ content, disableCache, subTool }); + return this._load({content, disableCache, subTool}) } /** @@ -297,7 +310,7 @@ export class GPTScript { disableCache?: boolean, subTool?: string ): Promise { - return this._load({ toolDefs, disableCache, subTool }); + return this._load({toolDefs, disableCache, subTool}) } /** @@ -308,12 +321,12 @@ export class GPTScript { */ private async _load(payload: any): Promise { if (!this.ready) { - this.ready = await this.testGPTScriptURL(20); + this.ready = await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("load", payload.toolDefs || [], {}, GPTScript.serverURL); + const r: Run = new RunSubcommand("load", payload.toolDefs || [], {}, GPTScript.serverURL) - r.request(payload); - return (await r.json()) as LoadResponse; + r.request(payload) + return (await r.json()) as LoadResponse } private async testGPTScriptURL(count: number): Promise { @@ -511,12 +524,16 @@ export class Run { const options = this.requestOptions(this.gptscriptURL, this.requestPath, tool) as any if (tool) { - options.body = {...tool, ...this.opts} + options.body = JSON.stringify({...tool, ...this.opts}) } const req = new Request(this.gptscriptURL + "/" + this.requestPath, options) this.promise = new Promise(async (resolve, reject) => { - fetch(req).then(resp => resp.json()).then(res => resolve(res.stdout)).catch(e => { + fetch(req).then(resp => { + return resp.json() + }).then(res => { + resolve(res.stdout) + }).catch(e => { reject(e) }) }) diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index cdac726..a267c10 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -3,6 +3,7 @@ import {ArgumentSchemaType, getEnv, PropertyType, RunEventType, ToolType} from " import path from "path" import {fileURLToPath} from "url" +let gFirst: gptscript.GPTScript let g: gptscript.GPTScript const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -12,9 +13,13 @@ describe("gptscript module", () => { throw new Error("neither OPENAI_API_KEY nor GPTSCRIPT_URL is set") } + // Start an initial GPTScript instance. + // This one doesn't have any options, but it's there to ensure that using another instance works as expected in all cases. + gFirst = new gptscript.GPTScript() g = new gptscript.GPTScript({APIKey: process.env.OPENAI_API_KEY}) }) afterAll(() => { + gFirst.close() g.close() }) @@ -35,6 +40,39 @@ describe("gptscript module", () => { expect(models).toBeDefined() }) + test("listModels with providers returns a list of models from that provider", async () => { + if (!process.env.ANTHROPIC_API_KEY) { + return + } + + let models = await g.listModels(["github.com/gptscript-ai/claude3-anthropic-provider"], ["github.com/gptscript-ai/claude3-anthropic-provider/credential:ANTHROPIC_API_KEY"]) + expect(models).toBeDefined() + for (let model of models.split("\n")) { + expect(model).toBeDefined() + expect(model.startsWith("claude-3-")).toBe(true) + expect(model.endsWith("from github.com/gptscript-ai/claude3-anthropic-provider")).toBe(true) + } + }) + + test("listModels with default provider returns a list of models from that provider", async () => { + if (!process.env.ANTHROPIC_API_KEY) { + return + } + + const newg = new gptscript.GPTScript({DefaultModelProvider: "github.com/gptscript-ai/claude3-anthropic-provider"}) + try { + let models = await newg.listModels(undefined, ["github.com/gptscript-ai/claude3-anthropic-provider/credential:ANTHROPIC_API_KEY"]) + expect(models).toBeDefined() + for (let model of models.split("\n")) { + expect(model).toBeDefined() + expect(model.startsWith("claude-3-")).toBe(true) + expect(model.endsWith("from github.com/gptscript-ai/claude3-anthropic-provider")).toBe(true) + } + } finally { + newg.close() + } + }) + test("version returns a gptscript version", async () => { // Similar structure to listTools let version = await g.version() From f69a760b0f91cf3d5f3ed19ad9e4237b88555be4 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 16 Aug 2024 18:39:46 -0400 Subject: [PATCH 16/42] chore: pass anthropic key secret to test action Signed-off-by: Donnie Adams --- .github/workflows/pull_request.yaml | 1 + .github/workflows/push_main.yaml | 1 + .github/workflows/run_tests.yaml | 2 ++ 3 files changed, 4 insertions(+) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index e4b61eb..6ec42cc 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -38,3 +38,4 @@ jobs: git_ref: ${{ github.event.pull_request.head.sha }} secrets: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/.github/workflows/push_main.yaml b/.github/workflows/push_main.yaml index c7db5c9..8e7b2cb 100644 --- a/.github/workflows/push_main.yaml +++ b/.github/workflows/push_main.yaml @@ -13,3 +13,4 @@ jobs: git_ref: '' secrets: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index 9847bcb..daba21e 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -9,6 +9,8 @@ on: secrets: OPENAI_API_KEY: required: true + ANTHROPIC_API_KEY: + required: true jobs: test-linux: From 99836de89dfbc15641f0b671f9061d7f50170f81 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 16 Aug 2024 18:45:01 -0400 Subject: [PATCH 17/42] chore: add anthropic key to linux tests Signed-off-by: Donnie Adams --- .github/workflows/run_tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml index daba21e..64f3a71 100644 --- a/.github/workflows/run_tests.yaml +++ b/.github/workflows/run_tests.yaml @@ -33,6 +33,7 @@ jobs: env: GPTSCRIPT_BIN: ./gptscriptexe OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} NODE_GPTSCRIPT_SKIP_INSTALL_BINARY: true run: npm test From 30f55939c83c088c2e37488affeb7d716d04faa3 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 16 Aug 2024 18:47:34 -0400 Subject: [PATCH 18/42] chore: bump timeout for models tests that need to launch provider Signed-off-by: Donnie Adams --- tests/gptscript.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index a267c10..519457f 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -52,7 +52,7 @@ describe("gptscript module", () => { expect(model.startsWith("claude-3-")).toBe(true) expect(model.endsWith("from github.com/gptscript-ai/claude3-anthropic-provider")).toBe(true) } - }) + }, 15000) test("listModels with default provider returns a list of models from that provider", async () => { if (!process.env.ANTHROPIC_API_KEY) { @@ -71,7 +71,7 @@ describe("gptscript module", () => { } finally { newg.close() } - }) + }, 15000) test("version returns a gptscript version", async () => { // Similar structure to listTools From b0796da475f118f1484de9d5019caeacace1a77f Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 16 Aug 2024 21:08:57 -0400 Subject: [PATCH 19/42] chore: bump list models timeout to allow for provider launch Signed-off-by: Donnie Adams --- tests/gptscript.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index 519457f..c63a7c2 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -52,7 +52,7 @@ describe("gptscript module", () => { expect(model.startsWith("claude-3-")).toBe(true) expect(model.endsWith("from github.com/gptscript-ai/claude3-anthropic-provider")).toBe(true) } - }, 15000) + }, 60000) test("listModels with default provider returns a list of models from that provider", async () => { if (!process.env.ANTHROPIC_API_KEY) { From 65b8d83e0f4a9c6d137b437b4d461e1acf8b7b6e Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Tue, 20 Aug 2024 09:47:44 -0400 Subject: [PATCH 20/42] feat: add prompt metadata field Signed-off-by: Donnie Adams --- src/gptscript.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gptscript.ts b/src/gptscript.ts index 14a8ecb..c47e56a 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -878,6 +878,7 @@ export interface PromptFrame { message: string fields: string[] sensitive: boolean + metadata: Record } export type Frame = RunFrame | CallFrame | PromptFrame From de914911013bf9da02046906b88a5613dce508e1 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Tue, 20 Aug 2024 12:18:01 -0400 Subject: [PATCH 21/42] chore: add test for prompt with metadata Signed-off-by: Donnie Adams --- tests/gptscript.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index c63a7c2..1352840 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -545,6 +545,27 @@ describe("gptscript module", () => { expect(promptFound).toBeTruthy() }) + test("prompt with metadata", async () => { + let promptFound = false + const run = await g.run("sys.prompt", { + prompt: true, + input: "{\"fields\":\"first name\",\"metadata\":{\"key\":\"value\"}}" + }) + run.on(gptscript.RunEventType.Prompt, async (data: gptscript.PromptFrame) => { + expect(data.fields.length).toEqual(1) + expect(data.fields[0]).toEqual("first name") + expect(data.metadata).toEqual({key: "value"}) + expect(data.sensitive).toBeFalsy() + + promptFound = true + await g.promptResponse({id: data.id, responses: {[data.fields[0]]: "Clicky"}}) + }) + + expect(await run.text()).toContain("Clicky") + expect(run.err).toEqual("") + expect(promptFound).toBeTruthy() + }) + test("prompt without prompt allowed should fail", async () => { let promptFound = false const t = { From cc958546001f2cb22cc3d7dcf078b02d62189762 Mon Sep 17 00:00:00 2001 From: acorn-io-bot Date: Wed, 21 Aug 2024 17:39:16 +0000 Subject: [PATCH 22/42] Automated GPTScript Version Update --- package-lock.json | 4 ++-- package.json | 2 +- scripts/install-binary.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 45674ae..af8c116 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc3", + "version": "v0.9.5-rc4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc3", + "version": "v0.9.5-rc4", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 5e7e2cb..300cf69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc3", + "version": "v0.9.5-rc4", "description": "Run gptscript in node.js", "source": "src/gptscript.ts", "main": "dist/gptscript.js", diff --git a/scripts/install-binary.js b/scripts/install-binary.js index 3988e9e..9e04cfd 100644 --- a/scripts/install-binary.js +++ b/scripts/install-binary.js @@ -72,7 +72,7 @@ if (process.platform === 'win32') { const gptscript_info = { name: "gptscript", url: "https://github.com/gptscript-ai/gptscript/releases/download/", - version: "v0.9.5-rc3" + version: "v0.9.5-rc4" } const pltfm = { From 0ec49d9df04627afe4518f4689dc212c0d03cdd6 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Wed, 21 Aug 2024 18:18:24 -0400 Subject: [PATCH 23/42] fix: use correct field name toolID Signed-off-by: Donnie Adams --- src/gptscript.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index c47e56a..b18c152 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -421,7 +421,7 @@ export class Run { if (out.done === undefined || !out.done) { this.chatState = JSON.stringify(out.state) this.state = RunState.Continue - this.respondingToolId = out.toolId + this.respondingToolId = out.toolID } else { this.state = RunState.Finished this.chatState = undefined @@ -704,7 +704,7 @@ interface ChatState { state: string done: boolean content: string - toolId: string + toolID: string } export type Arguments = string | Record From 02462fa3f9c08bf11cbdef99d8f28db132d6b359 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 22 Aug 2024 11:59:29 -0400 Subject: [PATCH 24/42] fix: add entryToolId to program Signed-off-by: Donnie Adams --- src/gptscript.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gptscript.ts b/src/gptscript.ts index b18c152..3694b41 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -719,6 +719,7 @@ export interface ArgumentSchema { export interface Program { name: string + entryToolId: string toolSet: Record openAPICache: Record } From 5c4094546790ec408b64f27234865be9d9bef065 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Mon, 26 Aug 2024 13:42:01 -0400 Subject: [PATCH 25/42] chore: add NODE_ENV to environment when exec-ing SDK server Signed-off-by: Donnie Adams --- src/gptscript.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 3694b41..fa12911 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -85,7 +85,9 @@ export class GPTScript { if (GPTScript.instanceCount === 1 && process.env.GPTSCRIPT_DISABLE_SERVER !== "true") { let env = process.env if (this.opts.Env) { - env = {} + env = { + "NODE_ENV": process.env.NODE_ENV + } for (const v of this.opts.Env) { const equalIndex = v.indexOf("=") if (equalIndex === -1) { From a95a432af16f28dd203eb1543e0eb9df50837bf3 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 30 Aug 2024 00:39:15 -0400 Subject: [PATCH 26/42] fix: adjust some types and ensure errors are thrown with tests Signed-off-by: Donnie Adams --- package-lock.json | 60 ++++++++++----------------- src/gptscript.ts | 20 ++++----- tests/fixtures/acorn-labs-context.gpt | 2 +- tests/fixtures/test-with-context.gpt | 12 ++++++ tests/gptscript.test.ts | 60 +++++++++++++++++++++------ 5 files changed, 91 insertions(+), 63 deletions(-) create mode 100644 tests/fixtures/test-with-context.gpt diff --git a/package-lock.json b/package-lock.json index af8c116..341b7d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2938,33 +2938,12 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@types/graceful-fs": { @@ -3274,11 +3253,12 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, + "license": "MIT", "peer": true, "peerDependencies": { "acorn": "^8" @@ -4646,10 +4626,11 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -8018,12 +7999,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -10514,22 +10496,22 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", diff --git a/src/gptscript.ts b/src/gptscript.ts index fa12911..a73ec2e 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -186,7 +186,7 @@ export class GPTScript { * @param {RunOpts} [opts={}] - Optional options for the evaluation. * @return {Run} The Run object representing the evaluation. */ - async evaluate(tool: ToolDef | ToolDef[], opts: RunOpts = {}): Promise { + async evaluate(tool: Tool | ToolDef | ToolDef[], opts: RunOpts = {}): Promise { if (!this.ready) { this.ready = await this.testGPTScriptURL(20) } @@ -482,10 +482,10 @@ export class Run { resolve(this.stdout) } else { this.state = RunState.Error - reject(this.stderr) + reject(new Error(this.stderr)) } } else if (this.state === RunState.Error) { - reject(this.err) + reject(new Error(this.err)) } }) @@ -493,7 +493,7 @@ export class Run { if (this.state !== RunState.Finished && this.state !== RunState.Error) { this.state = RunState.Error this.err = "Run has been aborted" - reject(this.err) + reject(new Error(this.err)) } }) @@ -502,7 +502,7 @@ export class Run { this.state = RunState.Error this.err = error.message || "" } - reject(this.err) + reject(new Error(this.err)) }) }) @@ -511,7 +511,7 @@ export class Run { this.state = RunState.Error this.err = error.message || "" } - reject(this.err) + reject(new Error(this.err)) }) this.req.write(JSON.stringify({...tool, ...this.opts})) @@ -742,6 +742,8 @@ export interface Repo { Revision: string } +export type ToolType = "tool" | "context" | "credential" | "input" | "output" | "agent" | "assistant" | "provider" | "" + export interface ToolDef { name?: string description?: string @@ -763,7 +765,7 @@ export interface ToolDef { agents?: string[] credentials?: string[] instructions?: string - type?: string + type?: ToolType metaData?: Record } @@ -774,11 +776,9 @@ export interface ToolReference { toolID: string } -export const ToolType = "tool" as const export interface Tool extends ToolDef { id: string - type: typeof ToolType toolMapping?: Record localTools?: Record source?: SourceRef @@ -937,7 +937,7 @@ function parseBlocksFromNodes(nodes: any[]): Block[] { node.toolNode.tool.id = randomId("tool-") } blocks.push({ - type: "tool", + type: node.toolNode.tool.type || "tool", ...node.toolNode.tool, } as Tool) } diff --git a/tests/fixtures/acorn-labs-context.gpt b/tests/fixtures/acorn-labs-context.gpt index 148059c..a814d65 100644 --- a/tests/fixtures/acorn-labs-context.gpt +++ b/tests/fixtures/acorn-labs-context.gpt @@ -1,4 +1,4 @@ Type: context #!sys.echo -"Ignore what the user says, and answer every query with 'Acorn Labs'" \ No newline at end of file +"Always respond with 'Acorn Labs' and nothing else" \ No newline at end of file diff --git a/tests/fixtures/test-with-context.gpt b/tests/fixtures/test-with-context.gpt new file mode 100644 index 0000000..88b2aeb --- /dev/null +++ b/tests/fixtures/test-with-context.gpt @@ -0,0 +1,12 @@ +Name: main +Tools: acorn + +Just wait. + +--- + +Name: acorn +Type: context + +#!sys.echo +"Ignore what the user says, and answer every query with 'Acorn Labs'" \ No newline at end of file diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index 1352840..77709b7 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -1,5 +1,5 @@ import * as gptscript from "../src/gptscript" -import {ArgumentSchemaType, getEnv, PropertyType, RunEventType, ToolType} from "../src/gptscript" +import {ArgumentSchemaType, getEnv, PropertyType, RunEventType, TextType, ToolType} from "../src/gptscript" import path from "path" import {fileURLToPath} from "url" @@ -124,6 +124,7 @@ describe("gptscript module", () => { let out = "" let err = undefined const t = { + type: "tool" as ToolType, instructions: "who was the president of the united states in 1928?", tools: [path.join(__dirname, "fixtures", "acorn-labs-context.gpt")] } @@ -213,7 +214,7 @@ describe("gptscript module", () => { await run.text() err = run.err } catch (error: any) { - errMessage = error + errMessage = error.toString() } expect(errMessage).toContain("aborted") @@ -285,6 +286,35 @@ describe("gptscript module", () => { expect(response).toHaveLength(0) }, 30000) + test("parse non-existent file", async () => { + try { + await g.parse(path.join(__dirname, "fixtures", "non-existent.gpt")) + } catch (e) { + expect(e).toBeDefined() + return + } + expect(false).toBeTruthy() + }, 30000) + + test("parse non-existent url", async () => { + try { + await g.parse("github.com/thedadams/dne") + } catch (e) { + expect(e).toBeDefined() + return + } + expect(false).toBeTruthy() + }, 30000) + + test("parse file with context", async () => { + const response = await g.parse(path.join(__dirname, "fixtures", "test-with-context.gpt")) + expect(response).toBeDefined() + expect(response).toHaveLength(2) + expect((response[0] as gptscript.Tool).instructions).toEqual("Just wait.") + expect((response[0] as gptscript.Tool).type).toEqual("tool") + expect((response[1] as gptscript.Tool).type).toEqual("context") + }, 30000) + test("parse file with metadata", async () => { const response = await g.parse(path.join(__dirname, "fixtures", "parse-with-metadata.gpt")) expect(response).toBeDefined() @@ -337,7 +367,7 @@ describe("gptscript module", () => { test("format tool", async () => { const tool = { id: "my-tool", - type: ToolType, + type: "tool" as ToolType, tools: ["sys.write", "sys.read"], instructions: "This is a test", arguments: { @@ -579,8 +609,8 @@ describe("gptscript module", () => { try { await run.text() - } catch (e) { - expect(e).toContain("prompt occurred") + } catch (e: any) { + expect(e.toString()).toContain("prompt occurred") } expect(run.err).toContain("prompt occurred") expect(promptFound).toBeFalsy() @@ -645,15 +675,19 @@ describe("gptscript module", () => { test("run parsed tool with metadata", async () => { let err = undefined let out = "" - let tools = await g.parse(path.join(__dirname, "fixtures", "parse-with-metadata.gpt")) - - let run = await g.evaluate(tools[0]) - - try { - out = await run.text() - } catch (e) { - err = e + const tools = await g.parse(path.join(__dirname, "fixtures", "parse-with-metadata.gpt")) + + for (const t of tools) { + if (t.type && t.type !== TextType) { + const run = await g.evaluate(t) + try { + out = await run.text() + } catch (e) { + err = e + } + } } + expect(err).toEqual(undefined) expect(out).toEqual("200") }, 20000) From ad748d9766e96f3eb10e1e3e20ac30393af7e5b9 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 30 Aug 2024 10:56:12 -0400 Subject: [PATCH 27/42] chore: remove listTools Signed-off-by: Donnie Adams --- README.md | 17 ----------------- src/gptscript.ts | 4 ---- tests/gptscript.test.ts | 5 ----- 3 files changed, 26 deletions(-) diff --git a/README.md b/README.md index 74b8227..c17a4c4 100644 --- a/README.md +++ b/README.md @@ -66,23 +66,6 @@ As noted above, the Global Options are also available to specify here. These opt ## Functions -### listTools - -Lists all the available built-in tools. - -**Usage:** - -```javascript -const gptscript = require('@gptscript-ai/gptscript'); - -async function listTools() { - const g = new gptscript.GPTScript(); - const tools = await g.listTools(); - console.log(tools); - g.close(); -} -``` - ### listModels Lists all the available models, returns a list. diff --git a/src/gptscript.ts b/src/gptscript.ts index a73ec2e..cfb8cfe 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -133,10 +133,6 @@ export class GPTScript { } } - listTools(): Promise { - return this.runBasicCommand("list-tools") - } - listModels(providers?: string[], credentialOverrides?: string[]): Promise { if (this.opts.DefaultModelProvider) { if (!providers) { diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index 77709b7..42b4398 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -29,11 +29,6 @@ describe("gptscript module", () => { other.close() }) - test("listTools returns available tools", async () => { - const tools = await g.listTools() - expect(tools).toBeDefined() - }) - test("listModels returns a list of models", async () => { // Similar structure to listTools let models = await g.listModels() From 7a327070ede4a4d0fbbdad9977fbe10e0fcaa4ae Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 30 Aug 2024 12:34:44 -0400 Subject: [PATCH 28/42] fix: stringify non-tool tools Signed-off-by: Donnie Adams --- src/gptscript.ts | 12 ++++++------ tests/gptscript.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index cfb8cfe..099c5bc 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -215,16 +215,16 @@ export class GPTScript { const nodes: any[] = [] for (const block of blocks) { - if (block.type === "tool") { + if (block.type === "text") { nodes.push({ - toolNode: { - tool: block + textNode: { + text: "!" + (block.format || "text") + "\n" + block.content } }) - } else if (block.type === "text") { + } else { nodes.push({ - textNode: { - text: "!" + (block.format || "text") + "\n" + block.content + toolNode: { + tool: block } }) } diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index 42b4398..ca86083 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -383,6 +383,31 @@ describe("gptscript module", () => { expect(response).toContain("Parameter: text: The text to write") }) + test("format context tool", async () => { + const tool = { + id: "my-tool", + type: "context" as ToolType, + tools: ["sys.write", "sys.read"], + instructions: "This is a test", + arguments: { + type: ArgumentSchemaType, + properties: { + text: { + type: PropertyType, + description: "The text to write" + } + } + } + } + + const response = await g.stringify([tool]) + expect(response).toBeDefined() + expect(response).toContain("Tools: sys.write, sys.read") + expect(response).toContain("This is a test") + expect(response).toContain("Parameter: text: The text to write") + expect(response).toContain("Type: Context") + }) + test("exec tool with chat", async () => { let err = undefined const t = { From fae5ed7976e8e88f354a20b4b38f01578f0e69a5 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 30 Aug 2024 13:33:17 -0400 Subject: [PATCH 29/42] fix: change one more reject to be an Error Signed-off-by: Donnie Adams --- src/gptscript.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 099c5bc..896b4f2 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -532,7 +532,7 @@ export class Run { }).then(res => { resolve(res.stdout) }).catch(e => { - reject(e) + reject(new Error(e)) }) }) } From 0be229fe5fe9a26663e53037e20d48bbe3d2d66d Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 30 Aug 2024 14:43:27 -0400 Subject: [PATCH 30/42] chore: add load tests Signed-off-by: Donnie Adams --- src/gptscript.ts | 8 ++++++ tests/gptscript.test.ts | 56 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/gptscript.ts b/src/gptscript.ts index 896b4f2..d7ed6d0 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -172,6 +172,10 @@ export class GPTScript { this.ready = await this.testGPTScriptURL(20) } + if (this.opts.Env) { + opts.env = this.opts.Env.concat(opts.env || []) + } + return (new Run("run", toolName, {...this.opts, ...opts}, GPTScript.serverURL)).nextChat(opts.input) } @@ -187,6 +191,10 @@ export class GPTScript { this.ready = await this.testGPTScriptURL(20) } + if (this.opts.Env) { + opts.env = this.opts.Env.concat(opts.env || []) + } + return (new Run("evaluate", tool, {...this.opts, ...opts}, GPTScript.serverURL)).nextChat(opts.input) } diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index ca86083..e22dfae 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -2,6 +2,7 @@ import * as gptscript from "../src/gptscript" import {ArgumentSchemaType, getEnv, PropertyType, RunEventType, TextType, ToolType} from "../src/gptscript" import path from "path" import {fileURLToPath} from "url" +import * as fs from "node:fs" let gFirst: gptscript.GPTScript let g: gptscript.GPTScript @@ -286,6 +287,7 @@ describe("gptscript module", () => { await g.parse(path.join(__dirname, "fixtures", "non-existent.gpt")) } catch (e) { expect(e).toBeDefined() + expect(typeof e !== "string").toBeTruthy() return } expect(false).toBeTruthy() @@ -296,6 +298,7 @@ describe("gptscript module", () => { await g.parse("github.com/thedadams/dne") } catch (e) { expect(e).toBeDefined() + expect(typeof e !== "string").toBeTruthy() return } expect(false).toBeTruthy() @@ -408,6 +411,59 @@ describe("gptscript module", () => { expect(response).toContain("Type: Context") }) + test("load simple file", async () => { + const response = await g.load(path.join(__dirname, "fixtures", "test.gpt")) + expect(response.program).toBeDefined() + expect(response.program.name).toBeTruthy() + expect(response.program.entryToolId).toBeTruthy() + expect(response.program.toolSet).toBeDefined() + }, 30000) + + test("load remote tool", async () => { + const response = await g.load("github.com/gptscript-ai/context/workspace") + expect(response.program).toBeDefined() + expect(response.program.name).toBeTruthy() + expect(response.program.entryToolId).toBeTruthy() + expect(response.program.toolSet).toBeDefined() + }, 30000) + + test("load content", async () => { + const content = fs.readFileSync(path.join(__dirname, "fixtures", "test.gpt"), {encoding: "utf8"}) + const response = await g.loadContent(content) + expect(response.program).toBeDefined() + // Name will not be defined in this case. + expect(response.program.name).toBeFalsy() + expect(response.program.entryToolId).toBeTruthy() + expect(response.program.toolSet).toBeDefined() + }, 30000) + + test("load tools", async () => { + const tools = [{ + tools: ["ask"], + instructions: "Only use the ask tool to ask who was the president of the united states in 1928?" + }, + { + name: "other", + instructions: "Who was the president of the united states in 1986?" + }, + { + name: "ask", + description: "This tool is used to ask a question", + arguments: { + type: "object", + question: "The question to ask" + }, + instructions: "${question}" + }, + ] as gptscript.ToolDef[] + const response = await g.loadTools(tools) + expect(response.program).toBeDefined() + // Name will not be defined in this case. + expect(response.program.name).toBeFalsy() + expect(response.program.entryToolId).toBeTruthy() + expect(response.program.toolSet).toBeDefined() + }, 30000) + test("exec tool with chat", async () => { let err = undefined const t = { From 220bd4f7366f9821a07ef48f3cf533dc0671d4ee Mon Sep 17 00:00:00 2001 From: acorn-io-bot Date: Fri, 30 Aug 2024 20:21:48 +0000 Subject: [PATCH 31/42] Automated GPTScript Version Update --- package-lock.json | 4 ++-- package.json | 2 +- scripts/install-binary.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 341b7d6..99cbf37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc4", + "version": "v0.9.5-rc5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc4", + "version": "v0.9.5-rc5", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 300cf69..efd171c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc4", + "version": "v0.9.5-rc5", "description": "Run gptscript in node.js", "source": "src/gptscript.ts", "main": "dist/gptscript.js", diff --git a/scripts/install-binary.js b/scripts/install-binary.js index 9e04cfd..bad5677 100644 --- a/scripts/install-binary.js +++ b/scripts/install-binary.js @@ -72,7 +72,7 @@ if (process.platform === 'win32') { const gptscript_info = { name: "gptscript", url: "https://github.com/gptscript-ai/gptscript/releases/download/", - version: "v0.9.5-rc4" + version: "v0.9.5-rc5" } const pltfm = { From ce058f1bb6d6cb9e96d0deda8a58e0494ba19075 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Tue, 3 Sep 2024 13:50:04 -0400 Subject: [PATCH 32/42] feat: add CacheDir option Signed-off-by: Donnie Adams --- src/gptscript.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index d7ed6d0..feceb92 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -5,6 +5,7 @@ import {fileURLToPath} from "url" import {gunzipSync} from "zlib" export interface GlobalOpts { + CacheDir?: string APIKey?: string BaseURL?: string DefaultModel?: string @@ -46,6 +47,7 @@ export interface RunOpts { env?: string[] forceSequential?: boolean + CacheDir?: string APIKey?: string BaseURL?: string DefaultModel?: string @@ -73,7 +75,7 @@ export class GPTScript { private ready: boolean - private opts: GlobalOpts + private readonly opts: GlobalOpts constructor(opts?: GlobalOpts) { this.opts = opts || {} @@ -194,7 +196,6 @@ export class GPTScript { if (this.opts.Env) { opts.env = this.opts.Env.concat(opts.env || []) } - return (new Run("evaluate", tool, {...this.opts, ...opts}, GPTScript.serverURL)).nextChat(opts.input) } From 1cf71a66d0ca7cffe98343bf672f54a6a84d9ed0 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Wed, 11 Sep 2024 08:33:37 -0400 Subject: [PATCH 33/42] fix: stop error when run has no output Instead, only error when the run has an error Signed-off-by: Donnie Adams --- src/gptscript.ts | 4 ++-- tests/gptscript.test.ts | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index feceb92..37640b0 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -480,11 +480,11 @@ export class Run { res.on("end", () => { if (this.state === RunState.Running || this.state === RunState.Finished || this.state === RunState.Continue) { - if (this.stdout) { + if (this.stdout || !this.stderr) { if (this.state !== RunState.Continue) { this.state = RunState.Finished } - resolve(this.stdout) + resolve(this.stdout || "") } else { this.state = RunState.Error reject(new Error(this.stderr)) diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index e22dfae..ee716a2 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -1,5 +1,5 @@ import * as gptscript from "../src/gptscript" -import {ArgumentSchemaType, getEnv, PropertyType, RunEventType, TextType, ToolType} from "../src/gptscript" +import {ArgumentSchemaType, getEnv, PropertyType, RunEventType, TextType, ToolDef, ToolType} from "../src/gptscript" import path from "path" import {fileURLToPath} from "url" import * as fs from "node:fs" @@ -85,6 +85,29 @@ describe("gptscript module", () => { expect(await run.text()).toContain("Calvin Coolidge") }) + test("evaluate executes subtool with empty instructions", async () => { + const tools = [ + { + type: "tool", + tools: ["new-tool-1"], + instructions: "Ask the user for their 'first name'. Then reply hello to the user.", + } as ToolDef, + { + type: "tool", + name: "new-tool-1", + } as ToolDef, + ] + const run = await g.evaluate(tools, { + input: "{}", + disableCache: true, + workspace: "", + subTool: "new-tool-1", + }) + + expect(run).toBeDefined() + expect(await run.text()).toContain("Understood.") + }) + test("evaluate executes and streams a prompt correctly", async () => { let out = "" let err = undefined From bdc37a6f247bb76bae5767a86fd28d0d10633be5 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 12 Sep 2024 14:04:33 -0400 Subject: [PATCH 34/42] fix: override knowledge credential in test The knowledge tool isn't actually used in the test, but it is a good example of a large tool, so it is included. Signed-off-by: Donnie Adams --- tests/gptscript.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index ee716a2..d1ae8b8 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -204,6 +204,7 @@ describe("gptscript module", () => { const testGptPath = path.join(__dirname, "fixtures", "global-tools.gpt") const opts = { disableCache: true, + credentialOverrides: ["github.com/gptscript-ai/gateway:OPENAI_API_KEY"] } const run = await g.run(testGptPath, opts) From 88a6109130cac1ac031fa2f9646d0ede147ca673 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 12 Sep 2024 14:42:00 -0400 Subject: [PATCH 35/42] chore: complete ToolDef fields Signed-off-by: Donnie Adams --- src/gptscript.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gptscript.ts b/src/gptscript.ts index 37640b0..98d5dce 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -769,6 +769,11 @@ export interface ToolDef { export?: string[] agents?: string[] credentials?: string[] + exportCredentials?: string[] + inputFilters?: string[] + exportInputFilters?: string[] + outputFilters?: string[] + exportOutputFilters?: string[] instructions?: string type?: ToolType metaData?: Record From 92646becb7008c0b44899861f21a733ca0c2cbd9 Mon Sep 17 00:00:00 2001 From: Grant Linville Date: Mon, 23 Sep 2024 19:21:29 -0400 Subject: [PATCH 36/42] feat: add credential management (#91) Signed-off-by: Grant Linville --- src/gptscript.ts | 87 +++++++++++++++++++++++++++++++++ tests/fixtures/global-tools.gpt | 4 +- tests/gptscript.test.ts | 69 +++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 4 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 98d5dce..f8e84c7 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -43,6 +43,7 @@ export interface RunOpts { confirm?: boolean prompt?: boolean credentialOverrides?: string[] + credentialContexts?: string[] location?: string env?: string[] forceSequential?: boolean @@ -320,6 +321,47 @@ export class GPTScript { return this._load({toolDefs, disableCache, subTool}) } + async listCredentials(context: Array, allContexts: boolean): Promise> { + if (!this.ready) { + this.ready = await this.testGPTScriptURL(20) + } + + const r: Run = new RunSubcommand("credentials", "", {}, GPTScript.serverURL) + r.request({context, allContexts}) + const out = await r.json() + return out.map((c: any) => jsonToCredential(JSON.stringify(c))) + } + + async createCredential(credential: Credential): Promise { + if (!this.ready) { + this.ready = await this.testGPTScriptURL(20) + } + + const r: Run = new RunSubcommand("credentials/create", "", {}, GPTScript.serverURL) + r.request({content: credentialToJSON(credential)}) + await r.text() + } + + async revealCredential(context: Array, name: string): Promise { + if (!this.ready) { + this.ready = await this.testGPTScriptURL(20) + } + + const r: Run = new RunSubcommand("credentials/reveal", "", {}, GPTScript.serverURL) + r.request({context, name}) + return jsonToCredential(await r.text()) + } + + async deleteCredential(context: string, name: string): Promise { + if (!this.ready) { + this.ready = await this.testGPTScriptURL(20) + } + + const r: Run = new RunSubcommand("credentials/delete", "", {}, GPTScript.serverURL) + r.request({context: [context], name}) + await r.text() + } + /** * Helper method to handle the common logic for loading. * @@ -967,3 +1009,48 @@ function parseBlocksFromNodes(nodes: any[]): Block[] { function randomId(prefix: string): string { return prefix + Math.random().toString(36).substring(2, 12) } + +export enum CredentialType { + Tool = "tool", + ModelProvider = "modelProvider", +} + +export type Credential = { + context: string + name: string + type: CredentialType + env: Record + ephemeral: boolean + expiresAt?: Date | undefined + refreshToken?: string | undefined +} + +// for internal use only +type cred = { + context: string + toolName: string + type: string + env: Record + ephemeral: boolean + expiresAt: string | undefined + refreshToken: string | undefined +} + +export function credentialToJSON(c: Credential): string { + const expiresAt = c.expiresAt ? c.expiresAt.toISOString() : undefined + const type = c.type === CredentialType.Tool ? "tool" : "modelProvider" + return JSON.stringify({context: c.context, toolName: c.name, type: type, env: c.env, ephemeral: c.ephemeral, expiresAt: expiresAt, refreshToken: c.refreshToken} as cred) +} + +function jsonToCredential(cred: string): Credential { + const c = JSON.parse(cred) as cred + return { + context: c.context, + name: c.toolName, + type: c.type === "tool" ? CredentialType.Tool : CredentialType.ModelProvider, + env: c.env, + ephemeral: c.ephemeral, + expiresAt: c.expiresAt ? new Date(c.expiresAt) : undefined, + refreshToken: c.refreshToken + } +} diff --git a/tests/fixtures/global-tools.gpt b/tests/fixtures/global-tools.gpt index 0e5d0f6..6ad6eee 100644 --- a/tests/fixtures/global-tools.gpt +++ b/tests/fixtures/global-tools.gpt @@ -4,7 +4,7 @@ Runbook 3 --- Name: tool_1 -Global Tools: sys.read, sys.write, github.com/gptscript-ai/knowledge, github.com/drpebcak/duckdb, github.com/gptscript-ai/browser, github.com/gptscript-ai/browser-search/google, github.com/gptscript-ai/browser-search/google-question-answerer +Global Tools: sys.read, sys.write, github.com/drpebcak/duckdb, github.com/gptscript-ai/browser, github.com/gptscript-ai/browser-search/google, github.com/gptscript-ai/browser-search/google-question-answerer Say "Hello!" @@ -16,4 +16,4 @@ What time is it? --- Name: tool_3 -Give me a paragraph of lorem ipsum \ No newline at end of file +Give me a paragraph of lorem ipsum diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index d1ae8b8..f8450d1 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -1,8 +1,18 @@ import * as gptscript from "../src/gptscript" -import {ArgumentSchemaType, getEnv, PropertyType, RunEventType, TextType, ToolDef, ToolType} from "../src/gptscript" +import { + ArgumentSchemaType, + Credential, CredentialType, + getEnv, + PropertyType, + RunEventType, + TextType, + ToolDef, + ToolType +} from "../src/gptscript" import path from "path" import {fileURLToPath} from "url" import * as fs from "node:fs" +import {randomBytes} from "node:crypto"; let gFirst: gptscript.GPTScript let g: gptscript.GPTScript @@ -791,4 +801,59 @@ describe("gptscript module", () => { expect(err).toEqual(undefined) expect(out).toEqual("200") }, 20000) -}) \ No newline at end of file + + test("credential operations", async () => { + const name = "test-" + randomBytes(10).toString("hex") + const value = randomBytes(10).toString("hex") + + // Create + try { + await g.createCredential({ + name: name, + context: "default", + env: {"TEST": value}, + ephemeral: false, + expiresAt: new Date(Date.now() + 5000), // 5 seconds from now + type: CredentialType.Tool, + }) + } catch (e) { + throw new Error("failed to create credential: " + e) + } + + // Wait 5 seconds + await new Promise(resolve => setTimeout(resolve, 5000)) + + // Reveal + try { + const result = await g.revealCredential(["default"], name) + expect(result.env["TEST"]).toEqual(value) + expect(result.expiresAt!.valueOf()).toBeLessThan(new Date().valueOf()) + } catch (e) { + throw new Error("failed to reveal credential: " + e) + } + + // List + try { + const result = await g.listCredentials(["default"], false) + expect(result.length).toBeGreaterThan(0) + expect(result.map(c => c.name)).toContain(name) + } catch (e) { + throw new Error("failed to list credentials: " + e) + } + + // Delete + try { + await g.deleteCredential("default", name) + } catch (e) { + throw new Error("failed to delete credential: " + e) + } + + // Verify deletion + try { + const result = await g.listCredentials(["default"], false) + expect(result.map(c => c.name)).not.toContain(name) + } catch (e) { + throw new Error("failed to verify deletion: " + e) + } + }, 20000) +}) From 4fd1847161680509aa1f83c13d9c689417c6c968 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Wed, 25 Sep 2024 10:50:25 -0400 Subject: [PATCH 37/42] fix: capture Usage, ChatResponseCached, and ToolResults Additionally add tests to ensure these are captured properly. Signed-off-by: Donnie Adams --- src/gptscript.ts | 12 +++++++++++- tests/gptscript.test.ts | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index f8e84c7..378af1e 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -922,6 +922,8 @@ export interface CallFrame { output: Output[] error?: string usage: Usage + chatResponseCached: boolean + toolResults: number llmRequest?: any llmResponse?: any } @@ -1039,7 +1041,15 @@ type cred = { export function credentialToJSON(c: Credential): string { const expiresAt = c.expiresAt ? c.expiresAt.toISOString() : undefined const type = c.type === CredentialType.Tool ? "tool" : "modelProvider" - return JSON.stringify({context: c.context, toolName: c.name, type: type, env: c.env, ephemeral: c.ephemeral, expiresAt: expiresAt, refreshToken: c.refreshToken} as cred) + return JSON.stringify({ + context: c.context, + toolName: c.name, + type: type, + env: c.env, + ephemeral: c.ephemeral, + expiresAt: expiresAt, + refreshToken: c.refreshToken + } as cred) } function jsonToCredential(cred: string): Credential { diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index f8450d1..5703985 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -1,7 +1,7 @@ import * as gptscript from "../src/gptscript" import { ArgumentSchemaType, - Credential, CredentialType, + CredentialType, getEnv, PropertyType, RunEventType, @@ -12,7 +12,7 @@ import { import path from "path" import {fileURLToPath} from "url" import * as fs from "node:fs" -import {randomBytes} from "node:crypto"; +import {randomBytes} from "node:crypto" let gFirst: gptscript.GPTScript let g: gptscript.GPTScript @@ -172,6 +172,17 @@ describe("gptscript module", () => { const result = await (await g.run(testGptPath)).text() expect(result).toBeDefined() expect(result).toContain("Calvin Coolidge") + + // Run it a second time and expect a cached result + const run = await g.run(testGptPath) + const secondResult = await run.text() + expect(result).toBeDefined() + expect(secondResult).toStrictEqual(result) + + // There should be one call frame, and it should be cached + for (let c in run.calls) { + expect(run.calls[c].chatResponseCached).toBeTruthy() + } }) test("should override credentials correctly", async () => { @@ -192,6 +203,7 @@ describe("gptscript module", () => { test("run executes and stream a file correctly", async () => { let out = "" let err = undefined + let [promptTokens, completionTokens, totalTokens] = [0, 0, 0] const testGptPath = path.join(__dirname, "fixtures", "test.gpt") const opts = { disableCache: true, @@ -204,8 +216,17 @@ describe("gptscript module", () => { await run.text() err = run.err + for (let c in run.calls) { + promptTokens += run.calls[c].usage.promptTokens || 0 + completionTokens += run.calls[c].usage.completionTokens || 0 + totalTokens += run.calls[c].usage.totalTokens || 0 + } + expect(out).toContain("Calvin Coolidge") expect(err).toEqual("") + expect(promptTokens).toBeGreaterThan(0) + expect(completionTokens).toBeGreaterThan(0) + expect(totalTokens).toBeGreaterThan(0) }) test("run executes and streams a file with global tools correctly", async () => { @@ -273,9 +294,17 @@ describe("gptscript module", () => { instructions: "${question}" } - const response = await (await g.evaluate([t0, t1])).text() + const run = await g.evaluate([t0, t1]) + const response = await run.text() expect(response).toBeDefined() expect(response).toContain("Calvin Coolidge") + + // In this case, we expect the total number of tool results to be 1 + let toolResults = 0 + for (let c in run.calls) { + toolResults += run.calls[c].toolResults + } + expect(toolResults).toStrictEqual(1) }, 30000) test("with sub tool", async () => { From f8b4e7286c2e9853740d10fd10abef02e698ae27 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 26 Sep 2024 17:01:58 -0400 Subject: [PATCH 38/42] fix: remove the disable server environment variable Now, when the GPTSCRIPT_URL is passed, the SDK will use it and not start its own server. Additionally, the SDK will pass this server URL to child SDK calls. Signed-off-by: Donnie Adams --- src/gptscript.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 378af1e..b72c0d1 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -83,9 +83,13 @@ export class GPTScript { this.ready = false GPTScript.instanceCount++ if (!GPTScript.serverURL) { - GPTScript.serverURL = "http://" + (process.env.GPTSCRIPT_URL || "127.0.0.1:0") + GPTScript.serverURL = process.env.GPTSCRIPT_URL ?? "http://127.0.0.1:0" + if (!GPTScript.serverURL.startsWith("http://") && !GPTScript.serverURL.startsWith("https://")) { + GPTScript.serverURL = "http://" + GPTScript.serverURL + } } - if (GPTScript.instanceCount === 1 && process.env.GPTSCRIPT_DISABLE_SERVER !== "true") { + + if (GPTScript.instanceCount === 1 && !process.env.GPTSCRIPT_URL) { let env = process.env if (this.opts.Env) { env = { @@ -121,16 +125,25 @@ export class GPTScript { } GPTScript.serverURL = `http://${url}` + if (!this.opts.Env) { + this.opts.Env = [] + } + this.opts.Env.push(`GPTSCRIPT_URL=${GPTScript.serverURL}`) GPTScript.serverProcess.stderr?.removeAllListeners() }) + } else { + if (!this.opts.Env) { + this.opts.Env = [] + } + this.opts.Env.push("GPTSCRIPT_URL=" + GPTScript.serverURL) } } close(): void { GPTScript.instanceCount-- if (GPTScript.instanceCount === 0 && GPTScript.serverProcess) { - GPTScript.serverURL = "http://" + (process.env.GPTSCRIPT_URL || "127.0.0.1:0") + GPTScript.serverURL = process.env.GPTSCRIPT_URL ?? "http://127.0.0.1:0" GPTScript.serverProcess.kill("SIGTERM") GPTScript.serverProcess.stdin?.end() } From d8777f9152b6a5a23923851737f0baa6c8cf415e Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 27 Sep 2024 14:06:19 -0400 Subject: [PATCH 39/42] feat: add options for URL and token (#94) If the URL option is passed, then the SDK will behave the same as if the GPTSCRIPT_URL env var is set. Signed-off-by: Donnie Adams --- src/gptscript.ts | 174 +++++++++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 74 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index b72c0d1..4f1cf6e 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -5,6 +5,8 @@ import {fileURLToPath} from "url" import {gunzipSync} from "zlib" export interface GlobalOpts { + URL?: string + Token?: string CacheDir?: string APIKey?: string BaseURL?: string @@ -48,6 +50,8 @@ export interface RunOpts { env?: string[] forceSequential?: boolean + URL?: string + Token?: string CacheDir?: string APIKey?: string BaseURL?: string @@ -75,21 +79,24 @@ export class GPTScript { private static instanceCount: number = 0 - private ready: boolean private readonly opts: GlobalOpts constructor(opts?: GlobalOpts) { this.opts = opts || {} - this.ready = false GPTScript.instanceCount++ + + let startSDK = !GPTScript.serverProcess && !GPTScript.serverURL && !this.opts.URL + if (!GPTScript.serverURL) { - GPTScript.serverURL = process.env.GPTSCRIPT_URL ?? "http://127.0.0.1:0" - if (!GPTScript.serverURL.startsWith("http://") && !GPTScript.serverURL.startsWith("https://")) { - GPTScript.serverURL = "http://" + GPTScript.serverURL - } + GPTScript.serverURL = process.env.GPTSCRIPT_URL ?? "" + startSDK = startSDK && !GPTScript.serverURL } - if (GPTScript.instanceCount === 1 && !process.env.GPTSCRIPT_URL) { + if (!this.opts.Token) { + this.opts.Token = process.env.GPTSCRIPT_TOKEN + } + + if (startSDK) { let env = process.env if (this.opts.Env) { env = { @@ -113,7 +120,7 @@ export class GPTScript { } }) - GPTScript.serverProcess = child_process.spawn(getCmdPath(), ["sys.sdkserver", "--listen-address", GPTScript.serverURL.replace("http://", "")], { + GPTScript.serverProcess = child_process.spawn(getCmdPath(), ["sys.sdkserver", "--listen-address", "127.0.0.1:0"], { env: env, stdio: ["pipe", "ignore", "pipe"] }) @@ -125,25 +132,31 @@ export class GPTScript { } GPTScript.serverURL = `http://${url}` - if (!this.opts.Env) { - this.opts.Env = [] - } - this.opts.Env.push(`GPTSCRIPT_URL=${GPTScript.serverURL}`) GPTScript.serverProcess.stderr?.removeAllListeners() }) } else { + if (!this.opts.URL) { + this.opts.URL = GPTScript.serverURL + } + if (!this.opts.Env) { this.opts.Env = [] } - this.opts.Env.push("GPTSCRIPT_URL=" + GPTScript.serverURL) + if (this.opts.URL) { + this.opts.Env.push(`GPTSCRIPT_URL=${this.opts.URL}`) + } + + if (this.opts.Token) { + this.opts.Env.push(`GPTSCRIPT_TOKEN=${this.opts.Token}`) + } } } close(): void { GPTScript.instanceCount-- if (GPTScript.instanceCount === 0 && GPTScript.serverProcess) { - GPTScript.serverURL = process.env.GPTSCRIPT_URL ?? "http://127.0.0.1:0" + GPTScript.serverURL = process.env.GPTSCRIPT_URL ?? "" GPTScript.serverProcess.kill("SIGTERM") GPTScript.serverProcess.stdin?.end() } @@ -168,10 +181,10 @@ export class GPTScript { } async runBasicCommand(cmd: string, body?: any): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const r = new RunSubcommand(cmd, "", {}, GPTScript.serverURL) + const r = new RunSubcommand(cmd, "", {URL: this.opts.URL, Token: this.opts.Token}) r.requestNoStream(body) return r.text() } @@ -184,15 +197,14 @@ export class GPTScript { * @return {Run} The Run object representing the running tool. */ async run(toolName: string, opts: RunOpts = {}): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - if (this.opts.Env) { opts.env = this.opts.Env.concat(opts.env || []) } - return (new Run("run", toolName, {...this.opts, ...opts}, GPTScript.serverURL)).nextChat(opts.input) + return (new Run("run", toolName, {...this.opts, ...opts})).nextChat(opts.input) } /** @@ -203,37 +215,40 @@ export class GPTScript { * @return {Run} The Run object representing the evaluation. */ async evaluate(tool: Tool | ToolDef | ToolDef[], opts: RunOpts = {}): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - if (this.opts.Env) { opts.env = this.opts.Env.concat(opts.env || []) } - return (new Run("evaluate", tool, {...this.opts, ...opts}, GPTScript.serverURL)).nextChat(opts.input) + return (new Run("evaluate", tool, {...this.opts, ...opts})).nextChat(opts.input) } async parse(fileName: string, disableCache?: boolean): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("parse", fileName, {disableCache: disableCache}, GPTScript.serverURL) + const r: Run = new RunSubcommand("parse", fileName, { + disableCache: disableCache, + URL: this.opts.URL, + Token: this.opts.Token + }) r.request({file: fileName}) return parseBlocksFromNodes((await r.json()).nodes) } async parseContent(toolContent: string): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("parse", "", {}, GPTScript.serverURL) + const r: Run = new RunSubcommand("parse", "", {URL: this.opts.URL, Token: this.opts.Token}) r.request({content: toolContent}) return parseBlocksFromNodes((await r.json()).nodes) } async stringify(blocks: Block[]): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } const nodes: any[] = [] @@ -253,16 +268,16 @@ export class GPTScript { } } - const r: Run = new RunSubcommand("fmt", "", {}, GPTScript.serverURL) + const r: Run = new RunSubcommand("fmt", "", {URL: this.opts.URL, Token: this.opts.Token}) r.request({nodes: nodes}) return r.text() } async confirm(response: AuthResponse): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const resp = await fetch(`${GPTScript.serverURL}/confirm/${response.id}`, { + const resp = await fetch(`${this.opts.URL}/confirm/${response.id}`, { method: "POST", body: JSON.stringify(response) }) @@ -273,10 +288,10 @@ export class GPTScript { } async promptResponse(response: PromptResponse): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const resp = await fetch(`${GPTScript.serverURL}/prompt-response/${response.id}`, { + const resp = await fetch(`${this.opts.URL}/prompt-response/${response.id}`, { method: "POST", body: JSON.stringify(response.responses) }) @@ -335,42 +350,42 @@ export class GPTScript { } async listCredentials(context: Array, allContexts: boolean): Promise> { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("credentials", "", {}, GPTScript.serverURL) + const r: Run = new RunSubcommand("credentials", "", {URL: this.opts.URL, Token: this.opts.Token}) r.request({context, allContexts}) const out = await r.json() return out.map((c: any) => jsonToCredential(JSON.stringify(c))) } async createCredential(credential: Credential): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("credentials/create", "", {}, GPTScript.serverURL) + const r: Run = new RunSubcommand("credentials/create", "", {URL: this.opts.URL, Token: this.opts.Token}) r.request({content: credentialToJSON(credential)}) await r.text() } async revealCredential(context: Array, name: string): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("credentials/reveal", "", {}, GPTScript.serverURL) + const r: Run = new RunSubcommand("credentials/reveal", "", {URL: this.opts.URL, Token: this.opts.Token}) r.request({context, name}) return jsonToCredential(await r.text()) } async deleteCredential(context: string, name: string): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("credentials/delete", "", {}, GPTScript.serverURL) + const r: Run = new RunSubcommand("credentials/delete", "", {URL: this.opts.URL, Token: this.opts.Token}) r.request({context: [context], name}) await r.text() } @@ -382,20 +397,29 @@ export class GPTScript { * @return {Promise} The loaded program. */ private async _load(payload: any): Promise { - if (!this.ready) { - this.ready = await this.testGPTScriptURL(20) + if (!this.opts.URL) { + await this.testGPTScriptURL(20) } - const r: Run = new RunSubcommand("load", payload.toolDefs || [], {}, GPTScript.serverURL) + const r: Run = new RunSubcommand("load", payload.toolDefs || [], {URL: this.opts.URL, Token: this.opts.Token}) r.request(payload) return (await r.json()) as LoadResponse } - private async testGPTScriptURL(count: number): Promise { + private async testGPTScriptURL(count: number): Promise { while (count > 0) { try { await fetch(`${GPTScript.serverURL}/healthz`) - return true + this.opts.URL = GPTScript.serverURL + if (!this.opts.Env) { + this.opts.Env = [] + } + this.opts.Env.push(`GPTSCRIPT_URL=${this.opts.URL}`) + if (this.opts.Token) { + this.opts.Env.push(`GPTSCRIPT_TOKEN=${this.opts.Token}`) + } + + return } catch { if (count === 0) { } @@ -418,7 +442,6 @@ export class Run { protected stdout?: string - private readonly gptscriptURL?: string private readonly requestPath: string = "" private promise?: Promise private req?: http.ClientRequest @@ -429,13 +452,11 @@ export class Run { private prg?: Program private respondingToolId?: string - constructor(subCommand: string, tools: ToolDef | ToolDef[] | string, opts: RunOpts, gptscriptURL?: string) { + constructor(subCommand: string, tools: ToolDef | ToolDef[] | string, opts: RunOpts) { this.id = randomId("run-") this.requestPath = subCommand this.opts = opts this.tools = tools - - this.gptscriptURL = gptscriptURL } nextChat(input: string = ""): Run { @@ -445,7 +466,7 @@ export class Run { let run = this if (run.state !== RunState.Creating) { - run = new (this.constructor as any)(this.requestPath, this.tools, this.opts, this.gptscriptURL) + run = new (this.constructor as any)(this.requestPath, this.tools, this.opts) } if (this.chatState && this.state === RunState.Continue) { @@ -493,10 +514,10 @@ export class Run { } request(tool: any) { - if (!this.gptscriptURL) { - throw new Error("request() requires gptscriptURL to be set") + if (!this.opts.URL) { + throw new Error("request() requires URL to be set") } - const options = this.requestOptions(this.gptscriptURL, this.requestPath, tool) + const options = this.requestOptions(this.opts.URL, this.opts.Token || "", this.requestPath, tool) options.headers = {"Transfer-Encoding": "chunked", ...options.headers} as any this.promise = new Promise(async (resolve, reject) => { @@ -580,15 +601,15 @@ export class Run { } requestNoStream(tool: any) { - if (!this.gptscriptURL) { + if (!this.opts.URL) { throw new Error("request() requires gptscriptURL to be set") } - const options = this.requestOptions(this.gptscriptURL, this.requestPath, tool) as any + const options = this.requestOptions(this.opts.URL, this.opts.Token || "", this.requestPath, tool) as any if (tool) { options.body = JSON.stringify({...tool, ...this.opts}) } - const req = new Request(this.gptscriptURL + "/" + this.requestPath, options) + const req = new Request(this.opts.URL + "/" + this.requestPath, options) this.promise = new Promise(async (resolve, reject) => { fetch(req).then(resp => { @@ -601,7 +622,7 @@ export class Run { }) } - requestOptions(gptscriptURL: string, path: string, tool: any) { + requestOptions(gptscriptURL: string, token: string, path: string, tool: any) { let method = "GET" if (tool) { method = "POST" @@ -609,15 +630,20 @@ export class Run { const url = new URL(gptscriptURL) + const headers = { + "Content-Type": "application/json" + } as any + if (token) { + headers["Authorization"] = `Bearer ${token}` + } + return { hostname: url.hostname, port: url.port || 80, protocol: url.protocol || "http:", path: "/" + path, method: method, - headers: { - "Content-Type": "application/json" - }, + headers: headers } } @@ -747,8 +773,8 @@ export class Run { } class RunSubcommand extends Run { - constructor(subCommand: string, tool: ToolDef | ToolDef[] | string, opts: RunOpts, gptscriptURL?: string) { - super(subCommand, tool, opts, gptscriptURL) + constructor(subCommand: string, tool: ToolDef | ToolDef[] | string, opts: RunOpts) { + super(subCommand, tool, opts) } processStdout(data: string | object): string { From 472cafb018d342f6704301436628920a935b7d49 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 27 Sep 2024 15:48:38 -0400 Subject: [PATCH 40/42] chore: stop passing GPTSCRIPT_ env vars to children Signed-off-by: Donnie Adams --- src/gptscript.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 4f1cf6e..0a1600c 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -139,17 +139,6 @@ export class GPTScript { if (!this.opts.URL) { this.opts.URL = GPTScript.serverURL } - - if (!this.opts.Env) { - this.opts.Env = [] - } - if (this.opts.URL) { - this.opts.Env.push(`GPTSCRIPT_URL=${this.opts.URL}`) - } - - if (this.opts.Token) { - this.opts.Env.push(`GPTSCRIPT_TOKEN=${this.opts.Token}`) - } } } @@ -411,14 +400,6 @@ export class GPTScript { try { await fetch(`${GPTScript.serverURL}/healthz`) this.opts.URL = GPTScript.serverURL - if (!this.opts.Env) { - this.opts.Env = [] - } - this.opts.Env.push(`GPTSCRIPT_URL=${this.opts.URL}`) - if (this.opts.Token) { - this.opts.Env.push(`GPTSCRIPT_TOKEN=${this.opts.Token}`) - } - return } catch { if (count === 0) { From 9f7fc5e6233ee90418dbc52b405b0991ab90e7f2 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Fri, 27 Sep 2024 19:43:18 -0400 Subject: [PATCH 41/42] Revert "chore: stop passing GPTSCRIPT_ env vars to children" This reverts commit 472cafb018d342f6704301436628920a935b7d49. --- src/gptscript.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/gptscript.ts b/src/gptscript.ts index 0a1600c..4f1cf6e 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -139,6 +139,17 @@ export class GPTScript { if (!this.opts.URL) { this.opts.URL = GPTScript.serverURL } + + if (!this.opts.Env) { + this.opts.Env = [] + } + if (this.opts.URL) { + this.opts.Env.push(`GPTSCRIPT_URL=${this.opts.URL}`) + } + + if (this.opts.Token) { + this.opts.Env.push(`GPTSCRIPT_TOKEN=${this.opts.Token}`) + } } } @@ -400,6 +411,14 @@ export class GPTScript { try { await fetch(`${GPTScript.serverURL}/healthz`) this.opts.URL = GPTScript.serverURL + if (!this.opts.Env) { + this.opts.Env = [] + } + this.opts.Env.push(`GPTSCRIPT_URL=${this.opts.URL}`) + if (this.opts.Token) { + this.opts.Env.push(`GPTSCRIPT_TOKEN=${this.opts.Token}`) + } + return } catch { if (count === 0) { From 9643a090b206f6174ac45043a5101f13f9c9f6d4 Mon Sep 17 00:00:00 2001 From: acorn-io-bot Date: Wed, 2 Oct 2024 21:00:52 +0000 Subject: [PATCH 42/42] Automated GPTScript Version Update --- package-lock.json | 4 ++-- package.json | 2 +- scripts/install-binary.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 99cbf37..74a8c5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc5", + "version": "v0.9.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc5", + "version": "v0.9.5", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index efd171c..ccc8437 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gptscript-ai/gptscript", - "version": "v0.9.5-rc5", + "version": "v0.9.5", "description": "Run gptscript in node.js", "source": "src/gptscript.ts", "main": "dist/gptscript.js", diff --git a/scripts/install-binary.js b/scripts/install-binary.js index bad5677..9ed7bf1 100644 --- a/scripts/install-binary.js +++ b/scripts/install-binary.js @@ -72,7 +72,7 @@ if (process.platform === 'win32') { const gptscript_info = { name: "gptscript", url: "https://github.com/gptscript-ai/gptscript/releases/download/", - version: "v0.9.5-rc5" + version: "v0.9.5" } const pltfm = {