diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml new file mode 100644 index 00000000..22f4a678 --- /dev/null +++ b/.github/workflows/integration.yaml @@ -0,0 +1,40 @@ +name: integration + +on: + push: + branches: + - main + paths-ignore: + - docs/** + - tools/** + - README.md + pull_request: + branches: + - main + paths-ignore: + - docs/** + - tools/** + - README.md + +jobs: + integration: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, windows-latest] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: actions/setup-go@v5 + with: + cache: false + go-version: "1.23" + - name: Build + if: matrix.os == 'ubuntu-22.04' + run: make build + - name: build-windows + if: matrix.os == 'windows-latest' + run: make build-exe + - name: Run Integration Tests + run: make integration \ No newline at end of file diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 6b469c39..7a2114ea 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -26,16 +26,16 @@ jobs: uses: actions/setup-go@v5 with: cache: false - go-version: "1.22" + go-version: "1.23" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser - version: v1.23.0 + version: "~> v2" args: release --clean --snapshot env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_PROJECT_TOKEN: ${{ secrets.GH_PROJECT_TOKEN }} + TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} - name: Upload to S3 uses: jakejarvis/s3-sync-action@v0.5.1 env: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 18871150..8b5b0eae 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,13 +12,6 @@ jobs: release-tag: runs-on: ubuntu-22.04 steps: - - name: trigger ui repo tag workflow - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.DISPATCH_PAT }} - repository: gptscript-ai/ui - event-type: release - client-payload: '{"tag": "${{ github.ref_name }}"}' - name: Checkout uses: actions/checkout@v4 with: @@ -27,16 +20,16 @@ jobs: uses: actions/setup-go@v5 with: cache: false - go-version: "1.22" + go-version: "1.23" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser - version: v1.23.0 + version: "~> v2" args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_PROJECT_TOKEN: ${{ secrets.GH_PROJECT_TOKEN }} + TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} GORELEASER_CURRENT_TAG: ${{ github.ref_name }} winget-release: needs: release-tag diff --git a/.github/workflows/smoke.yaml b/.github/workflows/smoke.yaml index 5c26ef8d..1a392a12 100644 --- a/.github/workflows/smoke.yaml +++ b/.github/workflows/smoke.yaml @@ -59,7 +59,7 @@ jobs: echo "run_smoke_tests=false" >> $GITHUB_OUTPUT - gpt-4o-2024-05-13: + gpt-4o-2024-08-06: needs: check-label if: ${{ needs.check-label.outputs.run_smoke_tests == 'true' }} runs-on: ubuntu-22.04 @@ -81,14 +81,14 @@ jobs: go-version: "1.21" - env: OPENAI_API_KEY: ${{ secrets.SMOKE_OPENAI_API_KEY }} - GPTSCRIPT_DEFAULT_MODEL: gpt-4o-2024-05-13 - name: Run smoke test for gpt-4o-2024-05-13 + GPTSCRIPT_DEFAULT_MODEL: gpt-4o-2024-08-06 + name: Run smoke test for gpt-4o-2024-08-06 run: | - echo "Running smoke test for model gpt-4o-2024-05-13" + echo "Running smoke test for model gpt-4o-2024-08-06" export PATH="$(pwd)/bin:${PATH}" make smoke - gpt-4-turbo-2024-04-09: + gpt-4o-mini-2024-07-18: needs: check-label if: ${{ needs.check-label.outputs.run_smoke_tests == 'true' }} runs-on: ubuntu-22.04 @@ -110,14 +110,14 @@ jobs: go-version: "1.21" - env: OPENAI_API_KEY: ${{ secrets.SMOKE_OPENAI_API_KEY }} - GPTSCRIPT_DEFAULT_MODEL: gpt-4-turbo-2024-04-09 - name: Run smoke test for gpt-4-turbo-2024-04-09 + GPTSCRIPT_DEFAULT_MODEL: gpt-4o-mini-2024-07-18 + name: Run smoke test for gpt-4o-mini-2024-07-18 run: | - echo "Running smoke test for model gpt-4-turbo-2024-04-09" + echo "Running smoke test for model gpt-4o-mini-2024-07-18" export PATH="$(pwd)/bin:${PATH}" make smoke - claude-3-opus-20240229: + claude-3-7-sonnet-20250219: needs: check-label if: ${{ needs.check-label.outputs.run_smoke_tests == 'true' }} runs-on: ubuntu-22.04 @@ -139,42 +139,11 @@ jobs: go-version: "1.21" - env: OPENAI_API_KEY: ${{ secrets.SMOKE_OPENAI_API_KEY }} - GPTSCRIPT_DEFAULT_MODEL: claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider + GPTSCRIPT_DEFAULT_MODEL: claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider ANTHROPIC_API_KEY: ${{ secrets.SMOKE_ANTHROPIC_API_KEY }} GPTSCRIPT_CREDENTIAL_OVERRIDE: "github.com/gptscript-ai/claude3-anthropic-provider/credential:ANTHROPIC_API_KEY" - name: Run smoke test for claude-3-opus-20240229 + name: Run smoke test for claude-3-7-sonnet-20250219 run: | - echo "Running smoke test for model claude-3-opus-20240229" + echo "Running smoke test for model claude-3-7-sonnet-20250219" export PATH="$(pwd)/bin:${PATH}" make smoke - - mistral-large-2402: - needs: check-label - if: ${{ needs.check-label.outputs.run_smoke_tests == 'true' }} - runs-on: ubuntu-22.04 - steps: - - name: Checkout base repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - name: Checkout PR code if running for a PR - if: ${{ github.event_name == 'pull_request_target' }} - uses: actions/checkout@v4 - with: - fetch-depth: 1 - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - - uses: actions/setup-go@v5 - with: - cache: false - go-version: "1.21" - - env: - OPENAI_API_KEY: ${{ secrets.SMOKE_OPENAI_API_KEY }} - GPTSCRIPT_DEFAULT_MODEL: mistral-large-2402 from https://api.mistral.ai/v1 - GPTSCRIPT_PROVIDER_API_MISTRAL_AI_API_KEY: ${{ secrets.SMOKE_GPTSCRIPT_PROVIDER_API_MISTRAL_AI_API_KEY }} - name: Run smoke test for mistral-large-2402 - run: | - echo "Running smoke test for model mistral-large-2402" - export PATH="$(pwd)/bin:${PATH}" - make smoke - diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f3829c35..f4da7e62 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -29,7 +29,7 @@ jobs: - uses: actions/setup-go@v5 with: cache: false - go-version: "1.22" + go-version: "1.23" - name: Validate if: matrix.os == 'ubuntu-22.04' run: make validate diff --git a/.github/workflows/validate-docs.yaml b/.github/workflows/validate-docs.yaml index 18368355..f7e3a016 100644 --- a/.github/workflows/validate-docs.yaml +++ b/.github/workflows/validate-docs.yaml @@ -17,6 +17,6 @@ jobs: - uses: actions/setup-go@v5 with: cache: false - go-version: "1.22" + go-version: "1.23" - run: make init-docs - run: make validate-docs diff --git a/.gitignore b/.gitignore index 759e3286..32148f05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /bin +/pkg/tests/bin /.idea /static/ui **/node_modules/ diff --git a/.golangci.yml b/.golangci.yml index e91a9ccc..0f992df5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,24 +1,41 @@ +version: "2" run: timeout: 5m - output: formats: - - format: colored-line-number - + text: + path: stdout linters: - disable-all: true + default: none enable: - - errcheck - - gofmt - - gosimple - - govet - - ineffassign - - staticcheck - - typecheck - - thelper - - unused - - goimports - - whitespace - - revive - fast: false + - errcheck + - govet + - ineffassign + - revive + - staticcheck + - thelper + - unused + - whitespace + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +issues: max-same-issues: 50 +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.goreleaser.yml b/.goreleaser.yml index b04bb8ea..522e3b22 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,4 @@ +version: 2 dist: releases snapshot: name_template: '{{ trimprefix .Summary "v" }}' @@ -52,14 +53,15 @@ release: prerelease: auto brews: - - description: "GPTScript CLI" + - name: gptscript + description: "GPTScript CLI" install: | bin.install "gptscript" - generate_completions_from_executable(bin/"gptscript", "completion", shells: [:bash, :zsh, :fish]) + generate_completions_from_executable(bin/"gptscript", "completion") homepage: "https://github.com/gptscript-ai/gptscript" skip_upload: false - folder: "Formula" + directory: "Formula" repository: owner: gptscript-ai name: homebrew-tap - token: "{{ .Env.GH_PROJECT_TOKEN }}" + token: "{{ .Env.TAP_GITHUB_TOKEN }}" diff --git a/.vscode/launch.json b/.vscode/launch.json index cc84991c..669016b3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,12 +15,12 @@ ] }, { - "name": "Launch Server", + "name": "Clicky Serves", "type": "go", "request": "launch", "mode": "debug", "program": "main.go", - "args": ["--server"] + "args": ["--debug", "--listen-address", "127.0.0.1:63774", "sys.sdkserver"] } ] } diff --git a/Makefile b/Makefile index 0fed6344..80ed0356 100644 --- a/Makefile +++ b/Makefile @@ -12,13 +12,17 @@ tidy: go mod tidy test: - go test -v ./... + go test -v ./pkg/... + +.PHONY: integration +integration: + go test -v ./integration/... smoke: build smoke: go test -v -tags='smoke' ./pkg/tests/smoke/... -GOLANGCI_LINT_VERSION ?= v1.59.0 +GOLANGCI_LINT_VERSION ?= v2.6.1 lint: if ! command -v golangci-lint &> /dev/null; then \ echo "Could not find golangci-lint, installing version $(GOLANGCI_LINT_VERSION)."; \ @@ -45,15 +49,17 @@ serve-docs: # This will initialize the node_modules needed to run the docs dev server. Run this before running serve-docs init-docs: - docker run --rm --workdir=/docs -v $${PWD}/docs:/docs node:18-buster yarn install + docker run --rm --workdir=/docs -v $${PWD}/docs:/docs node:18-buster npm install # Ensure docs build without errors. Makes sure generated docs are in-sync with CLI. -validate-docs: - docker run --rm --workdir=/docs -v $${PWD}/docs:/docs node:18-buster yarn build - go run tools/gendocs/main.go +validate-docs: gen-docs + docker run --rm --workdir=/docs -v $${PWD}/docs:/docs node:18-buster npm run build if [ -n "$$(git status --porcelain --untracked-files=no)" ]; then \ git status --porcelain --untracked-files=no; \ echo "Encountered dirty repo!"; \ git diff; \ exit 1 \ ;fi + +gen-docs: + go run tools/gendocs/main.go diff --git a/README.md b/README.md index 65cc37e4..1da91c90 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Here are some sample use cases of GPTScript: ### Getting started MacOS and Linux (Homebrew): ``` -brew install gptscript-ai/tap/gptscript +brew install gptscript gptscript github.com/gptscript-ai/llm-basics-demo ``` diff --git a/docs/docs/01-overview.md b/docs/docs/01-overview.md index a3e71857..590dcf62 100644 --- a/docs/docs/01-overview.md +++ b/docs/docs/01-overview.md @@ -21,8 +21,19 @@ Here are some sample use cases of GPTScript: + ## Homebrew Tap +___ ```shell - brew install gptscript-ai/tap/gptscript + brew install gptscript + gptscript github.com/gptscript-ai/llm-basics-demo + ``` + ## Homebrew +___ +:::warning +The [formula in homebrew-core](https://github.com/Homebrew/homebrew-core/blob/master/Formula/g/gptscript.rb) might be slightly outdated. Use our homebrew tap to always get the latest updates. +::: + ``` + brew install gptscript gptscript github.com/gptscript-ai/llm-basics-demo ``` diff --git a/docs/docs/02-examples/01-cli.md b/docs/docs/02-examples/01-cli.md index 7a59f592..4c8f3bab 100644 --- a/docs/docs/02-examples/01-cli.md +++ b/docs/docs/02-examples/01-cli.md @@ -1,110 +1,59 @@ # Chat with a Local CLI -GPTScript makes it easy to write AI integrations with CLIs and other executable available on your local workstation. This is powerful because it allows you to work AI to solve complex problems using your available CLIs. You can describe complex requests in plain English and GPTScript will figure out the best CLI commands to make that happen. This guide will show you how to build a GPTScript that integrates with two CLIs: - -- [gh](https://cli.github.com/) - the GitHub CLI -- [kubectl](https://kubernetes.io/docs/reference/kubectl/) - the Kubernetes CLI +GPTScript makes it easy to write AI integrations with CLIs and other executables available on your local workstation. +You can describe complex requests in plain English and GPTScript will figure out the best CLI commands to make that happen. +This guide will show you how to build a GPTScript that integrates with the `gh` CLI for GitHub. :::warning -This script **does not install** or configure gh or kubectl. We assume you've done that already. - -- For gh, you must be logged in via `gh auth login`. [See here for more details](https://docs.github.com/en/github-cli/github-cli/quickstart) -- For kubectl, you must have a proper `kubeconfig`. [See here for more details](https://kubernetes.io/docs/tasks/tools/) - +This script **does not install** or configure `gh`. We assume you've done that already. +You must be logged in via `gh auth login`. [See here for more details](https://docs.github.com/en/github-cli/github-cli/quickstart) ::: -## Too Long; Didn't Read - -Want to start using this script now? Just run: - -``` -gptscript github.com/gptscript-ai/cli-demo -``` - -Or if you want to skip ahead and just grab the full script so that you can start hacking on it, jump to the [Putting it all together section](cli#putting-it-all-together). +You should have basic familiarity with [tools](../03-tools/01-using.md) before starting this guide. ## Getting Started -The rest of this guide will walk you through building a script that can serve as an assistant for GitHub and Kubernetes tasks. We'll be explaining the how, what, and why along the way. - -First, open up a new gptscript file in your favorite editor. We'll call the file cli-demo.gpt +First, open up a new file in your favorite editor. We'll call the file `cli-demo.gpt`. ``` vim cli-demo.gpt ``` -All edits below are assumed to be in this file. At the end, we'll share the entire script as one cohesive file, but along the way we'll just be adding tools one-by-one. - -## Create the Kubernetes Agent - -Let's start by adding the Kubernetes agent. In our script, add the following: - -``` ---- -Name: k8s-agent -Description: An agent that can help you with your Kubernetes cluster by executing kubectl commands -Context: shared-context -Tools: sys.exec -Parameter: task: The kubectl related task to accomplish -Chat: true - -You have the kubectl cli available to you. Use it to accomplish the tasks that the user asks of you. - -``` - -Now, let's walk through this tool line-by-line. - -**---** is a block separator. It's how we delineate tools in a script. - -**Name and Description** help the LLM understand the purpose of this tool. You should always have meaningful names and descriptions. - -**Tools: sys.exec** makes the built-in `sys.exec` tool available to this agent. This gives the agent the ability to execute arbitrary commands. Based on our prompt, it will be used for kubectl commands. GPTScript's authorization system will prompt for approval whenever it's going to run a `sys.exec` command. - -**Parameter: task:** defines a parameter named "task" for this tool. This will be important later on when other tools need to hand-off to this tool - they'll pass the task to it as this parameter. As with the name and description fields, it's important to provide a good description so that the LLM knows how to use this parameter. - -**Chat: true** turns this tool into a "chat-able" tool, which we also call an "agent". This is important for open-ended tasks that might take some iteration. - -Finally, we have the **tool body**, which in this case is a prompt: - -``` -You have the kubectl cli available to you. Use it to accomplish the tasks that the user asks of you. -``` - -This is what the tool will actually do. Tool bodies can be prompts or raw code like python, javascript, or the [world's best programming language](https://x.com/ibuildthecloud/status/1796227491943637125) - bash. For chat-able tools, your tool body should always be a prompt. - -That's all there is to the Kubernetes agent. You can try it out now. One nice thing about GPTScript is that tools are composable. So, you can get this tool working well and then move onto the next tool without affecting this one. To launch this tool, run: - -``` -gptscript --sub-tool k8s-agent cli-demo.gpt -``` - -Once you're chatting, try asking it do something like list all the pods in your cluster or even to launch an new deployment in the cluster. +All edits below are assumed to be in this file. -## Create the GitHub Agent +## Create the entrypoint tool -Now let's add the GitHub Agent. Drop the following into the file below the tool we just added. +Let's start by adding the main tool to the file: ``` ---- -Name: github-agent -Description: An agent to help you with GitHub related tasks using the gh cli Context: learn-gh -Tools: sys.exec -Parameter: task: The GitHub task to accomplish +Context: github.com/gptscript-ai/context/cli Chat: true You have the gh cli available to you. Use it to accomplish the tasks that the user asks of you. ``` -This tool is very similar to the Kubernetes agent. There are just a few key differences: +Let's walk through this tool line by line. + +Each `Context` line references a context tool that will be run before the tool itself runs. +Context tools provide helpful output for the LLM to understand its capabilities and what it is supposed to do. +The first, `learn-gh`, we will define later in this file. +The second, `github.com/gptscript-ai/context/cli`, provides information to the LLM about the operating system that GPTScript is running on, +and gives it access to the `sys.exec` built-in tool, which is used to run commands. -1. Names and descriptions have been changed to reference GitHub and gh as appropriate. -2. We've introduced the `learn-gh` context. We'll explore this next. +`Chat: true` turns this tool into a "chat-able" tool, which we also call an "agent". +This causes the tool to run as an interactive chatbot, asking for user input and providing output. +If `Chat` is set to `false` (or not specified at all), the tool will run once without user interaction and exit. +This is useful for automated tasks, but right now we are working on an agent, so we set it to `true`. + +Lastly, there is the **tool body**, which in this case is a simple prompt, letting the LLM know that it should use the `gh` command and follow the user's instructions. +The tool body specifies what the tool should actually do. It can be a prompt or raw code like Python, JavaScript, or bash. +For chat-able tools, the tool body must be a prompt. ### The learn-gh context tool -Add this for the learn-gh context tool: +Next, add this to the file for the `learn-gh` context tool: ``` --- @@ -112,7 +61,7 @@ Name: learn-gh #!/usr/bin/env bash -echo "The following is the help text for the gh cli and some of its sub-commands. Use these when figuring out how to construct new commands. Note that the --search flag is used for filtering and sorting as well; there is no dedicate --sort flag." +echo "The following is the help text for the gh cli and some of its sub-commands. Use these when figuring out how to construct new commands. Note that the --search flag is used for filtering and sorting as well; there is no dedicated --sort flag." gh --help gh repo --help gh issue --help @@ -128,159 +77,34 @@ gh release --help gh release create --help ``` -As we saw, this tool is used as the context for the github-agent. Why did we add this and what does it do? +The `---` at the top of this tool is a block separator. It's how we delineate tools within a script file. -To answer that, let's first understand what the Context stanza does. Any tools referenced in the Context stanza will be called and their output will be added to the chat context. As the name suggests, this gives the LLM additional context for subsequent messages. Sometimes, an LLM needs extra instructions or context in order to achieve the desired results. There's no hard or fast rule here for when you should include context; it's best discovered through trial-and-error. +This tool has a `Name` field. We named this tool `learn-gh` so that it matches the `Context: learn-gh` line from the entrypoint tool. - We didn't need extra context for the Kubernetes tool because we found our default LLM knows kubectl (and Kubernetes) quite well. However, our same testing showed that our default LLM doesn't know the gh cli as well. Specifically, the LLM would sometimes hallucinate invalid combinations of flags and parameters. Without this context, the LLM often takes several tries to get the gh command correct. +The body of this tool is a bash script, rather than a prompt. +This context tool will be run by GPTScript automatically at the start of execution, and its output will be provided to the LLM. +We're running a bunch of `--help` commands in the `gh` CLI so that the LLM can understand how to use it. +GPTScript knows that this tool body is a script rather than a prompt because it begins with `#!`. -:::tip -Did you catch that "takes several tries to get the command correct" part? One useful feature of GPTScript is that it will feed error messages back to the LLM, which allows the LLM to learn from its mistake and try again. -::: +## Running the tool -And that's the GitHub Agent. You can try it out now: +Now try running the tool: ``` -gptscript --sub-tool github-agent cli-demo.gpt +gptscript cli-demo.gpt ``` -Once you're chatting, try asking it do something like "Open an issue in gptscript-ai/gptscript with a title and body that says Hi from me and states how wonderful gptscript is but jazz it up and make it unique" - -## Your CLI Assistant - -Right now if you were to launch this script, you'd be dropped right into the Kubernetes agent. Let's create a new entrypoint whose job it is to handle your initial conversation and route to the appropriate agent. Add this to the **TOP** of your file: - -``` -Name: Your CLI Assistant -Description: An assistant to help you with local cli-based tasks for GitHub and Kubernetes -Agents: k8s-agent, github-agent -Context: shared-context -Chat: true - -Help the user acomplish their tasks using the tools you have. When the user starts this chat, just say hello and ask what you can help with. You donlt need to start off by guiding them. -``` - -By being at the top of the file, this tool will serve as the script's entrypoint. Here are the parts of this tool that are worth additional explanation: - -**Agents: k8s-agent, github-agent** puts these two agents into a group that can hand-off to each other. So, you can ask a GitHub question, then a Kubernetes question, and then a GitHub question again and the chat conversation will get transferred to the proper agent each time. - -Next is **Context: shared-context**. You're already familiar with contexts, but in the next section we'll explain what's unique about this one. - -### The shared-context tool - -Drop the shared-context tool in at the very bottom of the page: - -``` ---- -Name: shared-context -Share Context: github.com/gptscript-ai/context/history - -#!sys.echo -Always delegate to the best tool for the users request. -Ask the user for information needed to complete a task. -Provide the user with the exact action you will be taking and get the users confirmation when creating or updating resources. -ALWAYS ask the user to confirm deletions, provide as much detail about the action as possible. -``` - -and do one more thing: add it as a context tool to both the k8s-agent and github-agent. For k8s-agent, that means adding this line: `Context: shared-context` and for github-agent, it means modifying the existing Context line to: `Context: learn-gh, shared-context`. - -**Share Context: github.com/gptscript-ai/context/history** - In this line, "Share Context" means that the specified tool(s) will be part of the context for any tools that references this tool in their Context stanza. It's a way to compose and aggregate contexts. - - The specific tool referenced here - github.com/gptscript-ai/context/history - makes it so that when you transition from one agent to the next, your chat history is carried across. Using this file as an example, this would allow you to have a history of all the Kubernetes information you gathered available when talking to the GitHub tool. - -The **#!sys.echo** body is a simple way to directly output whatever text follows it. This is useful if you just have a static set of instructions you need to inject into the context. The actual text should make sense if you read it. We're telling the agents how we want them to behave and interact. - -## Putting it all together - -Let's take a look at this script as one cohesive file: - -``` -Name: Your CLI Assistant -Description: An assistant to help you with local cli-based dev tasks -Context: shared-context -Agents: k8s-agent, github-agent -Chat: true - -Help the user acomplish their tasks using the tools you have. When the user starts this chat, just say hello and ask what you can help with. You donlt need to start off by guiding them. - ---- -Name: k8s-agent -Description: An agent that can help you with your Kubernetes cluster by executing kubectl commands -Context: shared-context -Tools: sys.exec -Parameter: task: The kubectl releated task to accomplish -Chat: true - -You have the kubectl cli available to you. Use it to accomplish the tasks that the user asks of you. - ---- -Name: github-agent -Description: An agent to help you with GitHub related tasks using the gh cli -Context: learn-gh, shared-context -Tools: sys.exec -Parameter: task: The GitHub task to accomplish -Chat: true - -You have the gh cli available to you. Use it to accomplish the tasks that the user asks of you. - ---- -Name: learn-gh - -#!/usr/bin/env bash - -echo "The following is the help text for the gh cli and some of its sub-commands. Use these when figuring out how to construct new commands. Note that the --search flag is used for filtering and sorting as well; there is no dedicate --sort flag." -gh --help -gh repo --help -gh issue --help -gh issue list --help -gh issue create --help -gh issue comment --help -gh issue delete --help -gh issue edit --help -gh pr --help -gh pr create --help -gh pr checkout --help -gh release --help -gh release create --help - - ---- -Name: shared-context -Share Context: github.com/gptscript-ai/context/history - -#!sys.echo -Always delegate to the best tool for the users request. -Ask the user for information needed to complete a task. -Provide the user with the exact action you will be taking and get the users confirmation when creating or updating resources. -ALWAYS ask the user to confirm deletions, provide as much detail about the action as possible. -``` - -There isn't anything new to cover in this file, we just wanted you to get a holistic view of it. This script is now fully functional. You can launch it via: - -``` -gpscript cli-demo.gpt -``` - -### Adding your own CLI - -By now you should notice a simple pattern emerging that you can follow to add your own CLI-powered agents to a script. Here are the basics of what you need: - -``` -Name: {your cli}-agent -Description: An agent to help you with {your taks} related tasks using the gh cli -Context: {here's your biggest decsion to make}, shared-context -Tools: sys.exec -Parameter: task: The {your task}The GitHub task to accomplish -Chat: true - -You have the {your cli} cli available to you. Use it to accomplish the tasks that the user asks of you. -``` +Once you're chatting, try asking it do something like "Open an issue in gptscript-ai/gptscript with a title and body that says Hi from me and states how wonderful gptscript is but jazz it up and make it unique". +GPTScript will ask for confirmation before it runs each command, so you can make sure that it only runs the commands you want it to. -You can drop in your task and CLI and have a fairly functional CLI-based chat agent. The biggest decision you'll need to make is what and how much context to give your agent. For well-known for CLIs/technologies like kubectl and Kubernetes, you probably won't need a custom context. For custom CLIs, you'll definitely need to help the LLM out. The best approach is to experiment and see what works best. +## A note on context tools -## Next steps +Context tools are a powerful way to provide additional information to the LLM, but they are not always necessary. +If you are working with a system that the LLM already understands well, you will probably not need to provide additional context. +When writing your own tools, it may take some trial and error to determine whether a context tool is needed. +If the LLM frequently hallucinates subcommands or arguments, it is probably worth adding a context tool to provide more information about the CLI. -Hopefully you've found this guide helpful. From here, you have several options: +## Next Steps -- You can checkout out some of our other guides available in this section of the docs -- You can dive deeper into the options available when [writing script](/tools/gpt-file-reference) +- You can check out some of our other guides available in this section of the docs +- You can dive deeper into the options available when [writing scripts](/tools/gpt-file-reference) diff --git a/docs/docs/02-examples/02-api.md b/docs/docs/02-examples/02-api.md index d84ae653..6c312c23 100644 --- a/docs/docs/02-examples/02-api.md +++ b/docs/docs/02-examples/02-api.md @@ -1,40 +1,27 @@ # Chat with an API -Interacting with cloud providers through dashboards, APIs, and CLIs is second nature to devops engineers. Using AI chat, the engineer can express a goal, and the AI can generate and execute the calls needed to achieve it. This saves the engineer time from having to look up the API calls needed themselves. GPTScript makes building a chat integration with an existing OpenAPI schema quick and easy. +GPTScript makes it easy to create a chatbot interface to interact with an API. -This guide will walk through the process of using the OpenAPI spec from Digital Ocean to build a chatbot capable of launching droplets and databases. The reader will be able to continue adding Digital Ocean capabilities or build their own chatbot with another OpenAPI schema. +This guide will demonstrate how to build a chatbot that interacts with the DigitalOcean API. -## Too Long; Didn't Read +## Getting Started -If you just want to try out the Digital Ocean chatbot first: - -Follow the [API credential](#api-access) settings here. - -Then you can run the following commands to get started: - -```bash -gptscript github.com/gptscript-ai/digital-ocean-agent -``` - -## Getting started - -First we will need to download a copy of the openapi.yaml. This spec technically can be accessed by URL, but initially, it is easier to download a copy and save it as openapi.yaml. - -### The Digital Ocean openapi.yaml spec - -Getting the openapi.yaml file from Digital Ocean can be done by running the following command in a terminal. +First, you will need to download a copy of DigitalOcean's OpenAPI definition. +While you can reference it by its URL, it is a bit easier to work with it locally. +You can download the file by running the following command: ```bash curl -o openapi.yaml -L https://api-engineering.nyc3.cdn.digitaloceanspaces.com/spec-ci/DigitalOcean-public.v2.yaml ``` -This will download a copy of the openapi yaml file to the local directory. +This will download a copy of the OpenAPI definition to the current directory. -Lets take a look at the spec file a little bit. The integration in GPTScript creates a tool named after each operationId in the OpenAPI spec. You can see what these tools would be by running the following. +Let's examine this OpenAPI file. GPTScript will create a tool named after each operationId in the file. +You can see the operationIds by running the following command: ```bash grep operationId openapi.yaml -# … +# ... # operationId: domains_delete_record # operationId: droplets_list # operationId: droplets_create @@ -50,189 +37,72 @@ grep operationId openapi.yaml # operationId: droplets_list_kernels # operationId: droplets_list_firewalls # operationId: droplets_list_neighbors -# … -``` - -If we look at the operationIds, you’ll notice they are structured around an object like droplet, database, or project. Each object has a collection of verb like list, get, delete, create, etc. Each tool in GPTScript has it’s own set of tools. So we can create agents, tools with chat enabled, that are experts in a specific set of objects and have access to all of the object_verb tools available to them. This allows us to fan out tools from a main entrypoint to multiple experts that can solve the users tasks. - -Lets explore this design pattern. - -## Creating Main Entrypoint - -Lets start by creating our main entrypoint to the Digital Ocean chatbot. The main tool in a GPTScript chat program is usually named agent.gpt. Let’s first setup the agents by giving it a name, the ability to chat, basic instructions, and the main greeting prompt. Create an agent.gpt file with the following contents. - -agent.gpt - -``` -Name: Digital Ocean Bot -Chat: true - -You are a helpful DevOps assistant that is an expert in Digital Ocean. -Using only the tools available, do not answer without using a tool, respond to the user task. -Greet the User with: "Hello! How can I help you with Digital Ocean?" -``` - -This file when run will show the following. - -![screenshot](/img/chat-api.png) - -In the current form, the chatbot will not be able to do anything since it doesn’t have access to any APIs. Let’s address that now, open our tool.gpt file and add the following. - -agent.gpt - +# ... ``` -Name: Digital Ocean Bot -Chat: true -Agents: droplets.gpt -You are a helpful DevOps assistant that is an expert in Digital Ocean -Using only the tools available, do not answer without using a tool, respond to the user task. -Greet the User with: "Hello! How can I help you with Digital Ocean?" -``` +The operationIds generally follow a pattern of `object_verb`. +This will be helpful for us, because we can use wildcard matching to refer to a subset of the operations. -Now lets create a droplets.gpt file to bring in the droplet tools. +## Creating the Script -droplets.gpt +Create a `tool.gpt` file with the following contents: ``` -Name: Droplet Agent +Name: DigitalOcean Bot Chat: true -Tools: droplets* from ./openapi.yaml -Description: Use this tool to work with droplets -Args: request: the task requested by the user +Tools: droplets* from openapi.yaml +Tools: databases* from openapi.yaml +Tools: images* from openapi.yaml +Tools: regions_list from openapi.yaml +Tools: tags* from openapi.yaml +Tools: sizes_list from openapi.yaml +Tools: sshKeys_list from openapi.yaml +Tools: sys.time.now -Help the user complete their Droplet operation requests using the tools available. -When creating droplets, always ask if the user would like to access via password or via SSHkey. +You are a helpful assistant with access to the DigitalOcean API to manage droplets and databases. +Before creating, updating, or deleting anything, tell the user about the exact action you are going to take, and get their confirmation. +Start the conversation by asking the user how you can help. ``` -Here we have defined the Droplet Agent, and enabled chat. We have also brought in an subset of the openapi.yaml tools that relate to droplets. By using droplets* we are making available everything droplet related into the available tools for this agent. We also provided the description to the main agent, and any other agent that has access to it, when to utilize this tool. We also have an argument called “request”, this is used when the LLM decides to call the agent it can smoothly pass off the user request without the Droplet Agent having to ask again. - -## Chat with Digital Ocean +This chatbot has access to several tools that correspond to various operations in the DigitalOcean OpenAPI file. +We give it access to all tools related to droplets and databases, since those are the main things we want it to work with. +In order to support this, we also need to give it access to images, regions, tags, etc. so that it can get the information it needs to create new droplets and databases. +Lastly, the `sys.time.now` tool is a tool that is built-in to GPTScript that provides the current date and time. -### API Access +:::note +We cannot give the entire `openapi.yaml` file to the tool because it contains too many API operations. +Most LLM providers, such as OpenAI, have a limit on the number of tools that you can provide to the model at one time. +The OpenAPI file contains over 300 operations, which is too many for most LLMs to handle at once. +::: -Now that we have brought in our first tool using the OpenAPI spec, we will need to setup authentication. Defined in the openapi.yaml is how the Digital Ocean API expects authenticated requests to work. If you look in the spec file path of components.securitySchemes you will see that Digital Ocean expects bearer_auth. So you will need to create an API key in the Digital Ocean dashboard with the access you want the LLM to be able to interact with Digital Ocean. For instance, you can do a read only key that will allow you to just query information, or you can provide it full access and the operator can work with the LLM to do anything in the project. It is up to you. For this example, we will be using a full access token, but you can adjust for your needs. You can create your API key by going to this link [Apps & API](https://cloud.digitalocean.com/account/api/tokens) section in your account. +## Creating an API Token -Once you have an API key, you will need to set an environment variable with that value stored. - -```bash -export GPTSCRIPT_API_DIGITALOCEAN_COM_BEARER_AUTH=****** -``` +Before you run this script, you need to have a DigitalOcean API token. -Where the *** is the API key created in the dashboard. +Go to [Applications & API](https://cloud.digitalocean.com/account/api/tokens) in the DigitalOcean dashboard and create a new token. +You can select whichever scopes you want, but you should at least give it the ability to read droplets and databases. -### Chatting with Digital Ocean APIs +## Running the Script -Now you can run gptscript to start your conversation with Digital Ocean. +Let's run the script and start chatting with it: ```bash -gptscript agent.gpt -``` - -You should now be able to ask how many droplets are running? - -And get an output from the chatbot. This is great, but not quite ready to use just yet. Lets keep adding some functionality. - -## Adding Database Support - -Now that we can do droplets, we can add support for databases just as easy. Lets create a databases.gpt file with the following contents. - -Ddtabases.gpt - -``` -Name: Database Agent -Chat: true -Tools: databases* from ./openapi.yaml -Description: Call this tool to manage databases on digital ocean -Args: request: the task requested by the user - -Help the user complete database operation requests with the tools available. -``` - -Here again, we are essentially scoping our agent to handle database calls with the Digital Ocean API. Now in order for this to be used, we need to add it to our agent list in the main agent.gpt file. - -Agent.gpt - -``` -Name: Digital Ocean Bot -Chat: true -Agents: droplets.gpt, databases.gpt - -You are a helpful DevOps assistant that is an expert in Digital Ocean -Using only the tools available, do not answer without using a tool, respond to the user task. -Greet the User with: "Hello! How can I help you with Digital Ocean?" -``` - -Now when we test it out we can ask how many databases are running? And it should give back the appropriate response. - -Now, when it comes to creating a database or droplet, we are missing some APIs to gather the correct information. We don’t have access to size information, regions, SSH Keys, etc. Since these are common tools, it would be a bit of a hassle to add lines to both the databases.gpt and droplets.gpt files. To avoid this, we can make use of the GPTScript Context to provide a common set of tools and instructions. - -## Context - -Context is a powerful concept in GPTScript that provides information to the system prompt, and provide a mechanism to compose a common set of tools reducing duplication in your GPTScript application. Lets add a context.gpt file to our chatbot here with the following contents. - -context.gpt - -``` -Share Tools: sys.time.now - -Share Tools: images* from ./openapi.yaml -Share Tools: regions_list from ./openapi.yaml -Share Tools: tags* from openapi.yaml -Share Tools: sizes_list from ./openapi.yaml -Share Tools: sshKeys_list from ./openapi.yaml - - -#!sys.echo -Always delegate to the best tool for the users request. -Ask the user for information needed to complete a task. -Provide the user with the exact action you will be taking and get the users confirmation when creating or updating resources. -ALWAYS ask the user to confirm deletions, provide as much detail about the action as possible. -``` - -There is quite a bit going on here, so lets break it down. Anywhere you see Share Tools it is making that tool available to anything uses the context. In this case, it is providing access to the time now tool so you can ask what was created yesterday and the LLM can get a frame of reference. Additionally, it provides a common set of Digital Ocean APIs that are needed for placement, organization(tags), sizes, and images, etc. Since multiple components in Digital Ocean use these values, it is useful to only need to define it once. Last we are providing a set of common instructions for how we want the chatbot to behave overall. This way, we do not need to provide this information in each agent. Also, since this is in the system prompt, it is given a higher weight to the instructions in the individual agents. - -Now lets add this to our agents. You will need to add the line: - -``` -Context: context.gpt -``` - -To each of our agents, so the droplets.gpt, agent.gpt, and databases.gpt will have this line. - -## Wrapping up - -Provided you have given API access through your token, you should now be able to run the chatbot and create a database or a droplet and be walked through the process. You should also be able to ask quesitons like What VMs were created this week? - -You now know how to add additional capabilities through agents to the chatbots. You can follow the same patterns outlined above to add more capabilities or you can checkout the chat bot repository to see additional functionality. - -### Use your own OpenAPI schema - -If you have your own OpenAPI schema, you can follow the same pattern to build a chatbot for your own APIs. The simplest way to get started is to create a gptscript file with the following contents. - -``` -Name: {Your API Name} Bot -Chat: true -Tools: openapi.yaml - -You are a helpful assistant. Say "Hello, how can I help you with {Your API Name} system today?" +gptscript tool.gpt ``` -You can then run that and the LLM will be able to interact with your API. +Try asking it to list your current databases or droplets, or to help you create a new one. -#### Note on OpenAI tool limits +The first time the LLM tries to make an API call, it will ask for your API token. +Paste it into the prompt. It will be used for all future API calls as well. +The LLM will never see or store your API token. It is only used client-side, on your computer. -As we mentioned before, GPTScript creates a tool for each operationId in the OpenAPI spec. If you have a large OpenAPI spec, you may run into a limit on the number of tools that can be created. OpenAI, the provider of the GPT-4o model only allows a total of 200 tools to be passed in at a single time. If you exceed this limit, you will see an error message from OpenAI. If you run into this issue, you can follow the same pattern we did above to create our Digital Ocean bot. +## Next Steps -A quick check to see how many tools total would be created, you can run the following: +Feel free to modify the script to add other parts of the DigitalOcean API. +You could also try creating a chatbot for a different API with an OpenAPI definition. -```bash -grep operationId openapi.yaml|wc -l - 306 -``` +For a more advanced DigitalOcean chatbot, see our [DigitalOcean Agent](https://github.com/gptscript-ai/digital-ocean-agent) tool. -In our case, there are 306 tools that would be created in the case of our Digital Ocean spec. This would not fit into a single agent, so breaking it up into multiple agents is the best way to handle this. - -## Next Steps +To read more about OpenAPI tools in GPTScript, see the [OpenAPI Tools](../03-tools/03-openapi.md) article. -Now that you have seen how to create a chatbot with an OpenAPI schema, checkout our other guides to see how to build other ChatBots and agents. +To read more about credential storage in GPTScript, see the [Credentials](../06-credentials.md) article. diff --git a/docs/docs/02-examples/04-local-files.md b/docs/docs/02-examples/04-local-files.md index 522deb01..252ddf96 100644 --- a/docs/docs/02-examples/04-local-files.md +++ b/docs/docs/02-examples/04-local-files.md @@ -1,6 +1,9 @@ # Chat with Local Files -With GPTScript interacting with local files is simple and powerful. This can help you streamline repetitive or data-intensive tasks. In this guide, we'll build a script that can query Excel files, CSVs, and PDFs. We'll then use the script to read, transform, and utilize the data in these files. +With GPTScript, interacting with local files is simple and powerful. +This can help you streamline repetitive or data-intensive tasks. +In this guide, we'll build a script that can query Excel files, CSVs, and PDFs. +We'll then use the script to read, transform, and utilize the data in these files. ## Too Long; Didn't Read @@ -14,60 +17,79 @@ gptscript --workspace=~/Documents github.com/gptscript-ai/local-files-demo ``` ## Getting Started -The rest of this guide will walk you through building and using a data processing assistant. We'll be explaining the how, what, and why along the way. + +The rest of this guide will walk you through building and using a data processing assistant. First, let's get some sample data to work with. You can clone our repo with our sample data: + ``` git clone https://github.com/gptscript-ai/local-files-demo.git cd local-files-demo ``` -Next, open up a new gptscript file in your favorite editor. We'll call the file data-assistant.gpt. +Next, open up a new gptscript file in your favorite editor. We'll call the file `data-assistant.gpt`. + ``` vim data-assistant.gpt ``` + All edits below are assumed to be in this file. ### Create the Assistant -Put this in the gpt file: + +Add this to the file: + ``` -Name: Your Data Processing Assitant -Description: An asistant to help you with processing data found in files on your workstation. Helpful for querying spreadsheets, CSVs, JSON files, and pdfs. +Name: Data Processing Assitant +Description: An assistant to help you with processing data found in files on your workstation. Helpful for querying spreadsheets, CSVs, JSON files, and PDFs. Tools: github.com/gptscript-ai/structured-data-querier, github.com/gptscript-ai/pdf-reader Context: github.com/gptscript-ai/context/workspace Chat: true -You are a helpful data processing assistant. Your goal is to help the user with data processing. Help the user accomplish their tasks using the tools you have. When the user starts this chat, just say hi, introduce yourself, and ask what you can help with. +You are a helpful data processing assistant. Your goal is to help the user with data processing. +Help the user accomplish their tasks using the tools you have. +When the user starts this chat, say hi, introduce yourself, and ask what you can help with. ``` -This is actually the entirety of the script. We're packing a lot of power into just a handful of lines here. Let's talk through them. -**Name and Description** help the LLM understand the purpose of this tool. You should always have meaningful names and descriptions. +This is the entire script. Here's what each part does: + +`Name and Description` help the LLM understand the purpose of this tool. You should always have meaningful names and descriptions. -The **Tools: ...** stanza pulls two useful tools into this assistant. +The `Tools: ...` line provides two useful tools to this assistant. -The [structured-data-querier](https://github.com/gptscript-ai/structured-data-querier) makes it possible to query csv, xlsx, and json files as though they SQL databases (using an application called [DuckDB](https://duckdb.org/)). This is extremely powerful when combined with the power of LLMs because it let's you ask natural language questions that the LLM can then translate to SQL. +The [structured-data-querier](https://github.com/gptscript-ai/structured-data-querier) makes it possible to query CSV, XLSX, and JSON files as though they were SQL databases (using an application called [DuckDB](https://duckdb.org/)). +This is extremely powerful when combined with the power of LLMs because it allows you to ask natural language questions that the LLM can then translate to SQL. -The [pdf-reader](https://github.com/gptscript-ai/pdf-reader) isn't quite as exciting, but still useful. It parses and reads PDFs and returns the contents to the LLM. This will put the entire contents in your chat context, so it's not appropriate for extremely large PDFs, but it's handy for smaller ones. +The [pdf-reader](https://github.com/gptscript-ai/pdf-reader) parses and reads PDFs and returns the contents to the LLM. +This will put the entire contents in your chat context, so it's not appropriate for extremely large PDFs, but it's handy for smaller ones. -**Context: github.com/gptscript-ai/context/workspace** introduces a context tool makes this assistant "workspace" aware. It's description reads: +`Context: github.com/gptscript-ai/context/workspace` introduces a [context tool](../03-tools/05-context.md) makes this assistant "workspace" aware. Its description reads: > Adds the workspace and tools needed to access the workspace to the current context -That translates to telling the LLM what the workspace directory is and instructing it to use that directory for reading and writing files. As we saw above, you can specify a workspace like this: +Basically, this context tool tells the LLM what the workspace directory is and instructs it to use that directory for reading and writing files. +As we saw above, you can specify a workspace like this: + ``` gptscript --workspace=/Your/path/here ... ``` + If you don't specify one, a temporary directory will be created and used for the workspace. -This context also shares the `sys.read`, `sys.write`, and `sys.ls` built-in tools with the assistant so that it automatically has access to them. +This context tool also shares the `sys.read`, `sys.write`, and `sys.ls` built-in tools with the assistant. -Next we have **Chat: true**, which you've seen if you looked at any of our other guides. This makes the current tool "chat-able". We refer to chatable tools as agents or assistants. +Next we have `Chat: true`. This makes the current tool "chat-able". We refer to chat-able tools as agents or assistants. Finally, we have the prompt: -> You are a helpful data processing assistant. Your goal is to help the user with data processing tasks. Help the user accomplish their tasks using the tools you have. When the user starts this chat, just say hi, introduce yourself, and ask what you can help with. +> You are a helpful data processing assistant. Your goal is to help the user with data processing. +> Help the user accomplish their tasks using the tools you have. +> When the user starts this chat, say hi, introduce yourself, and ask what you can help with. ## Using the Assistant -Once again, that's all there is to this assistant. You can start using it by specifying your own workspace or using our sample-data directory as the workspace. Assuming you're using our sample data and have followed these instructions, here's how you launch it: + +When you run the assistant, you can specify your own workspace folder or our sample data directory +Assuming you're using our sample data and have followed these instructions, here's how you run it: + ``` gptscript --workspace=./sample-data data-assistant.gpt ``` @@ -75,6 +97,7 @@ gptscript --workspace=./sample-data data-assistant.gpt Here's a few sample interactions with these files. ### Cleaning up data + ``` > whats in the key contacts file? @@ -114,6 +137,7 @@ Here's a few sample interactions with these files. ``` ### Identifying and fixing data gaps + ``` > is there any missing data in that csv? ... @@ -126,6 +150,7 @@ Here's a few sample interactions with these files. ``` ### Cross-referencing + ``` > what were sales like for Kevin's location? @@ -149,7 +174,9 @@ Here's a few sample interactions with these files. Is there anything else you would like to know or do with this data? ``` + ### Pulling all the info together + ``` > Let's help Kevin raise sales. What promotions do we have going on? ... @@ -234,11 +261,12 @@ Here's a few sample interactions with these files. Feel free to customize this email further to better suit your needs. Let me know if there's anything else I can assist you with! ``` + Try it out yourself and see what you can come up with. ## Next steps Hopefully you've found this guide helpful. From here, you have several options: -- You can checkout out some of our other guides available in this section of the docs +- You can check out some of our other guides available in this section of the docs - You can dive deeper into the options available when [writing script](/tools/gpt-file-reference) diff --git a/docs/docs/02-examples/05-workflow.md b/docs/docs/02-examples/05-workflow.md index 0aca6fc3..7f903536 100644 --- a/docs/docs/02-examples/05-workflow.md +++ b/docs/docs/02-examples/05-workflow.md @@ -1,15 +1,14 @@ # Run an Automated Workflow -Automating a sequence of tasks that integrate with one or more systems is a ubiquitous engineering problem that typically requires some degree of domain-specific knowledge up-front. However, workflows written with GPTScript all but eliminate this prerequisite, enabling developers to build their workflows by describing the high-level steps it should perform. +Automating a sequence of tasks that integrate with one or more systems is a ubiquitous engineering problem that typically requires some degree of domain-specific knowledge up-front. +However, workflows written with GPTScript all but eliminate this prerequisite, enabling developers to build their workflows by describing the high-level steps it should perform. This guide will show you how to build a GPTScript that encapsulates a workflow consisting of the following steps: -1. Get a selection of twitter posts +1. Get a selection of X (Twitter) posts 2. Summarize their content 3. Summarize the content of any links they directly reference 4. Write the results to a Markdown document -We'll be explaining the how, what, and why along the way. - ## Too long; didn't read Want to start using this script now? Just run: @@ -53,7 +52,7 @@ This tool: - imports two other tools - `sys.write` is a built-in tool which enables the entrypoint tool to write files to your system. - `summarize-tweet` is a custom tool that encapsulates how each tweet gets summarized. We'll define this tool in the next step. -- ensures tweets are never summarized in parallel to ensure they are summarized in the correct order +- ensures tweets are never summarized in parallel so that they are summarized in the correct order - defines the tweet URLs to summarize and the file to write them to At a high-level, it's getting the summaries for two tweets and storing them in the `tweets.md` file. @@ -87,7 +86,7 @@ This tool - imports three other tools to solve summarization sub-problems - `github.com/gptscript-ai/browser` is an external tool that is used to open the tweet URL in the browser and extract the page content - `get-hyperlinks` and `summarize-hyperlinks` are custom helper tools we'll define momentarily that extract hyperlinks from tweet text and summarize them -- describes the markdown document this tool should produce, leaving it up to the LLM to decide which of the available tools to call to make this happen +- describes the Markdown document this tool should produce, leaving it up to the LLM to decide which of the available tools to call to make this happen ## Hyperlink Summarization Tools diff --git a/docs/docs/03-tools/01-using.md b/docs/docs/03-tools/01-using.md index a2c8326b..27a59037 100644 --- a/docs/docs/03-tools/01-using.md +++ b/docs/docs/03-tools/01-using.md @@ -1,5 +1,9 @@ # Using Tools -In GPTScript, tools are used to extend the capabilities of a script. The idea behind them is that AI performs better when it has very specific instructions for a given task. Tools are a way to break-up the problem into smaller and more focused pieces where each tool is responsible for a specific task. A typical flow like this is to have a main script that imports a set of tools it can use to accomplish its goal. + +In GPTScript, tools are used to extend the capabilities of a script. +The idea behind them is that AI performs better when it has very specific instructions for a given task. +Tools are a way to break up the problem into smaller and more focused pieces where each tool is responsible for a specific task. +A typical pattern is to have a main script that imports a set of tools it can use to accomplish its goal. GPTScripts can utilize tools in one of three ways: 1. Built-in system tools @@ -7,6 +11,7 @@ GPTScripts can utilize tools in one of three ways: 3. External tools ### System Tools + All GPTScripts have access to system tools, like `sys.read` and `sys.write`, that can be used without any additional configuration. ```yaml @@ -16,11 +21,14 @@ Read all of the files in my current directory, do not recurse over any subdirect ``` System tools are a set of core tools that come packaged with GPTScript by default. +To see a list of the system tools, run `gptscript --list-tools`. ### In-Script Tools -Things get more interesting when you start to use custom tools. -The most basic example of this is an in-script tool that is defined in the same file as the main script. This is useful for breaking up a large script into smaller, more manageable pieces. +Things get more interesting when you start to write your own tools. + +The most basic example of this is an in-script tool that is defined in the same file as the main script. +This is useful for breaking up a large script into smaller, more manageable pieces. ```yaml tools: random-number @@ -35,7 +43,9 @@ Select a number at random between 1 and 100 and return only the number. ``` ### External Tools -You can refer to GPTScript tool files that are served on the web or stored locally. This is useful for sharing tools across multiple scripts or for using tools that are not part of the core GPTScript distribution. + +You can refer to GPTScript tool files that are served on the web or stored locally. +This is useful for sharing tools across multiple scripts or for using tools that are not part of the core GPTScript distribution. ```yaml tools: https://get.gptscript.ai/echo.gpt @@ -51,9 +61,11 @@ tools: echo.gpt Echo the phrase "Hello, World!". ``` -You can also refer to OpenAPI definition files as though they were GPTScript tool files. GPTScript will treat each operation in the file as a separate tool. For more details, see [OpenAPI Tools](03-openapi.md). +You can also refer to OpenAPI definition files as though they were GPTScript tool files. +GPTScript will treat each operation in the file as a separate tool. For more details, see [OpenAPI Tools](03-openapi.md). ### Packaged Tools on GitHub + GPTScript tools can be packaged and shared on GitHub, and referred to by their GitHub URL. For example: ```yaml @@ -64,5 +76,9 @@ Generate an image of a city skyline at night and write the resulting image to a Take this image and write a description of it in the style of pirate. ``` +:::important +The GitHub URL must not be prefixed with `http://` or `https://`. +::: + When this script is run, GPTScript will locally clone the referenced GitHub repos and run the tools referenced inside them. For more info on how this works, see [Authoring Tools](02-authoring.md). diff --git a/docs/docs/03-tools/02-authoring.md b/docs/docs/03-tools/02-authoring.md index 3e81613a..423147ab 100644 --- a/docs/docs/03-tools/02-authoring.md +++ b/docs/docs/03-tools/02-authoring.md @@ -2,11 +2,12 @@ You can author your own tools for your use or to share with others. The process for authoring a tool is as simple as creating a `tool.gpt` file in the root directory of your project. -This file is itself a GPTScript that defines the tool's name, description, and what it should do. +This file is a GPTScript that defines the tool's name, description, and what it should do. ## Quickstart -This is a guide for writing portable tools for GPTScript. The supported languages currently are Python, NodeJS, and Go. This guide uses Python but you can see documentation for the other language below. +This is a guide for writing portable tools for GPTScript. The supported languages currently are Python, Node.js, and Go. +This guide uses Python, but you can see documentation for the other languages below. ### 1. Write the code @@ -65,7 +66,11 @@ gptscript github.com// '{"url": "https://github.com"}' ## Sharing Tools -GPTScript is designed to easily export and import tools. Doing this is currently based entirely around the use of GitHub repositories. You can export a tool by creating a GitHub repository and ensuring you have the `tool.gpt` file in the root of the repository. You can then import the tool into a GPTScript by specifying the URL of the repository in the `tools` section of the script. For example, we can leverage the `image-generation` tool by adding the following line to a GPTScript: +GPTScript is designed to easily export and import tools. +Doing this is currently based entirely around the use of GitHub repositories. +You can export a tool by creating a GitHub repository and ensuring you have the `tool.gpt` file in the root of the repository. +You can then import the tool into a GPTScript by specifying the URL of the repository in the `tools` section of the script. +For example, we can leverage the `image-generation` tool by adding the following line to a GPTScript: ```yaml tools: github.com/gptscript-ai/dalle-image-generation @@ -73,21 +78,15 @@ tools: github.com/gptscript-ai/dalle-image-generation Generate an image of a city skyline at night. ``` -### Supported Languages +## Supported Languages -GPTScript can execute any binary that you ask it to. However, it can also manage the installation of a language runtime and dependencies for you. Currently this is only supported for a few languages. Here are the supported languages and examples of tools written in those languages: +GPTScript can execute any binary that you ask it to. +However, it can also manage the installation of a language runtime and dependencies for you. +Currently, this is only supported for a few languages. +Here are the supported languages and examples of tools written in those languages: | Language | Example | |-----------|----------------------------------------------------------------------------------------------------------------| | `Python` | [Image Generation](https://github.com/gptscript-ai/dalle-image-generation) - Generate images based on a prompt | | `Node.js` | [Vision](https://github.com/gptscript-ai/gpt4-v-vision) - Analyze and interpret images | | `Golang` | [Search](https://github.com/gptscript-ai/search) - Use various providers to search the internet | - - -### Automatic Documentation - -Each GPTScript tool is self-documented using the `tool.gpt` file. You can automatically generate documentation for your tools by visiting `tools.gptscript.ai/`. This documentation site allows others to easily search and explore the tools that have been created. - -You can add more information about how to use your tool by adding an `examples` directory to your repository and adding a collection of `.gpt` files that demonstrate how to use your tool. These examples will be automatically included in the documentation. - -For more information and to explore existing tools, visit [tools.gptscript.ai](https://tools.gptscript.ai). diff --git a/docs/docs/03-tools/03-openapi.md b/docs/docs/03-tools/03-openapi.md index 2069b331..e99172eb 100644 --- a/docs/docs/03-tools/03-openapi.md +++ b/docs/docs/03-tools/03-openapi.md @@ -1,6 +1,6 @@ # OpenAPI Tools -GPTScript can treat OpenAPI v3 definition files as though they were tool files. +GPTScript can treat OpenAPI v2 and v3 definition files as though they were tool files. Each operation (a path and HTTP method) in the file will become a simple tool that makes an HTTP request. GPTScript will automatically and internally generate the necessary code to make the request and parse the response. @@ -41,11 +41,6 @@ Will be resolved as `https://api.example.com/v1`. ## Authentication -:::warning -All authentication options will be completely ignored if the server uses HTTP and not HTTPS. -This is to protect users from accidentally sending credentials in plain text. -::: - ### 1. Security Schemes GPTScript will read the defined [security schemes](https://swagger.io/docs/specification/authentication/) in the OpenAPI definition. The currently supported types are `apiKey` and `http`. diff --git a/docs/docs/03-tools/04-credential-tools.md b/docs/docs/03-tools/04-credential-tools.md index 9aaa7601..46a0e69e 100644 --- a/docs/docs/03-tools/04-credential-tools.md +++ b/docs/docs/03-tools/04-credential-tools.md @@ -13,7 +13,7 @@ Here is a simple example of a credential provider tool that uses the builtin `sy ```yaml # my-credential-tool.gpt -name: my-credential-tool +Name: my-credential-tool #!/usr/bin/env bash @@ -27,13 +27,17 @@ echo "{\"env\":{\"MY_ENV_VAR\":\"$credential\"}}" Continuing with the above example, this is how you can use it in a script: ```yaml -credentials: my-credential-tool.gpt +Credentials: my-credential-tool.gpt as myCred #!/usr/bin/env bash echo "The value of MY_ENV_VAR is $MY_ENV_VAR" ``` +:::note +GPTScript accepts `Cred:`, `Creds:`, `Credential:`, and `Credentials:` as valid directives. +::: + When you run the script, GPTScript will call the credential provider tool first, set the environment variables from its output, and then run the script body. The credential provider tool is called by GPTScript itself. GPTScript does not ask the LLM about it or even tell the LLM about the tool. @@ -41,11 +45,13 @@ LLM about it or even tell the LLM about the tool. If GPTScript has called the credential provider tool in the same context (more on that later), then it will use the stored credential instead of fetching it again. +To delete the credential that just got stored, run `gptscript credential delete myCred`. + You can also specify multiple credential tools for the same script, but they must be on separate lines: ```yaml -credentials: credential-tool-1.gpt -credentials: credential-tool-2.gpt +Credentials: credential-tool-1.gpt +Credentials: credential-tool-2.gpt (tool stuff here) ``` @@ -56,7 +62,7 @@ GPTScript also provides a generic credential tool (`github.com/gptscript-ai/cred where you only need to set one environment variable. Here is an example of how to use it: ```yaml -credentials: github.com/gptscript-ai/credential as myCredentialName with MY_ENV_VAR as env and "this message will be displayed to the user" as message and key as field +Credentials: github.com/gptscript-ai/credential as myCredentialName with MY_ENV_VAR as env and "this message will be displayed to the user" as message and key as field (tool stuff here) ``` @@ -66,24 +72,24 @@ the environment variable `MY_ENV_VAR` and stored in a credential called `myCrede See [the repo](https://github.com/gptscript-ai/credential) for more information. -## Credential Tool Arguments +## Credential Tool Parameters -A credential tool may define arguments. Here is an example: +A credential tool may define parameters. Here is an example: ```yaml -name: my-credential-tool -args: env: the environment variable to set -args: val: the value to set it to +Name: my-credential-tool +Parameter: env: the environment variable to set +Parameter: val: the value to set it to #!/usr/bin/env bash echo "{\"env\":{\"$ENV\":\"$VAL\"}}" ``` -When you reference this credential tool in another file, you can use syntax like this to set both arguments: +When you reference this credential tool in another file, you can use syntax like this to set both parameters: ```yaml -credential: my-credential-tool.gpt with MY_ENV_VAR as env and "my value" as val +Credential: my-credential-tool.gpt with MY_ENV_VAR as env and "my value" as val (tool stuff here) ``` @@ -92,7 +98,7 @@ In this example, the tool's output would be `{"env":{"MY_ENV_VAR":"my value"}}` ## Storing Credentials -By default, credentials are automatically stored in the credential store. Read the [main credentials page](../02-credentials.md) +By default, credentials are automatically stored in the credential store. Read the [main credentials page](../06-credentials.md) for more information about the credential store. :::note @@ -105,7 +111,7 @@ will not be stored in the credentials store. When you reference a credential tool in your script, you can give it an alias using the `as` keyword like this: ```yaml -credentials: my-credential-tool.gpt as myAlias +Credentials: my-credential-tool.gpt as myAlias (tool stuff here) ``` @@ -121,8 +127,7 @@ A credential context is basically a namespace for credentials. If you have multi you can switch between them by defining them in different credential contexts. The default context is called `default`, and this is used if none is specified. -You can set the credential context to use with the `--credential-context` flag when running GPTScript. For -example: +You can set the credential context to use with the `--credential-context` flag when running GPTScript. For example: ```bash gptscript --credential-context my-azure-workspace my-azure-script.gpt @@ -181,3 +186,91 @@ In this example, `toolA` provides the variables `ENV_VAR_1` and `ENV_VAR_2`, This will read the values of `ENV_VAR_1` through `ENV_VAR_4` from the current environment and set them for the credential. This is a direct mapping of environment variable names. **This is not recommended when overriding credentials for multiple tools that use the same environment variable names.** + +## Credential Refresh (Advanced) + +Some use cases (such as OAuth) may involve the need to refresh expired credentials. +To support this, your credential tool can return other fields besides `env` in its JSON output. +This is the full list of supported fields in the credential tool output: + +- `env` (type: object) - The environment variables to set. +- `expiresAt` (type: string, timestamp in RFC3339 format) - The time when the credential expires. +- `refreshToken` (type: string) - The refresh token to use to refresh the credential. + +When GPTScript tries to use a credential that has a defined `expiresAt` time, it will check if the credential has expired. +If the credential has expired, it will run the credential tool again, and the current value of the credential will be +set to the environment variable `GPTSCRIPT_EXISTING_CREDENTIAL` as a JSON string. This way, the credential tool can check for +that environment variable, and if it is set, get the refresh token from the existing credential and use it to refresh and return a new credential, +typically without user interaction. + +For an example of a tool that uses the refresh feature, see the [Gateway OAuth2 tool](https://github.com/gptscript-ai/gateway-oauth2). + +### GPTSCRIPT_CREDENTIAL_EXPIRATION environment variable + +When a tool references a credential tool, GPTScript will add the environment variables from the credential to the tool's +environment before executing the tool. If at least one of the credentials has an `expiresAt` field, GPTScript will also +set the environment variable `GPTSCRIPT_CREDENTIAL_EXPIRATION`, which contains the nearest expiration time out of all +credentials referenced by the tool, in RFC 3339 format. That way, it can be referenced in the tool body if needed. +Here is an example: + +``` +Credential: my-credential-tool.gpt as myCred + +#!python3 + +import os + +print("myCred expires at " + os.getenv("GPTSCRIPT_CREDENTIAL_EXPIRATION", "")) +``` + +## Stacked Credential Contexts (Advanced) + +When setting the `--credential-context` argument in GPTScript, you can specify multiple contexts separated by commas. +We refer to this as "stacked credential contexts", or just stacked contexts for short. This allows you to specify an order +of priority for credential contexts. This is best explained by example. + +### Example: stacked contexts when running a script that uses a credential + +Let's say you have two contexts, `one` and `two`, and you specify them like this: + +```bash +gptscript --credential-context one,two my-script.gpt +``` + +``` +Credential: my-credential-tool.gpt as myCred + + +``` + +When GPTScript runs, it will first look for a credential called `myCred` in the `one` context. +If it doesn't find it there, it will look for it in the `two` context. If it also doesn't find it there, +it will run the `my-credential-tool.gpt` tool to get the credential. It will then store the new credential into the `one` +context, since that has the highest priority. + +### Example: stacked contexts when listing credentials + +```bash +gptscript --credential-context one,two credentials +``` + +When you list credentials like this, GPTScript will print out the information for all credentials in contexts one and two, +with one exception. If there is a credential name that exists in both contexts, GPTScript will only print the information +for the credential in the context with the highest priority, which in this case is `one`. + +(To see all credentials in all contexts, you can still use the `--all-contexts` flag, and it will show all credentials, +regardless of whether the same name appears in another context.) + +### Example: stacked contexts when showing credentials + +```bash +gptscript --credential-context one,two credential show myCred +``` + +When you show a credential like this, GPTScript will first look for `myCred` in the `one` context. If it doesn't find it +there, it will look for it in the `two` context. If it doesn't find it in either context, it will print an error message. + +:::note +You cannot specify stacked contexts when doing `gptscript credential delete`. GPTScript will return an error if +more than one context is specified for this command. +::: diff --git a/docs/docs/03-tools/05-context.md b/docs/docs/03-tools/05-context.md index 3a4e8c15..15e600e4 100644 --- a/docs/docs/03-tools/05-context.md +++ b/docs/docs/03-tools/05-context.md @@ -1,97 +1,94 @@ # Context -GPTScript provides a mechanism to share prompt information across many tools using the tool parameter `context`. It is used to provide additional information to the calling tool on when to use a specific tool by prepending the `context` to the instruction of the calling tool. +GPTScript provides a mechanism to share prompt information across many tools using the tool directive `Context`. +It is used to provide additional information to the calling tool on when to use a specific tool by prepending the context to the instruction of the calling tool. - Context can point to a static text or a GPTScript. -- Context tools are just regular GPTScript tools, and any valid gptscript field can be used. -- Exported tools from a context tool are made available to the calling tool. +- Context tools are just regular GPTScript tools, and any valid GPTScript fields can be used in them. +- Shared tools from a context tool are made available to the calling tool. - When context points to a GPTScript tool, output from the context tool gets prepended to the instruction of the calling tool. ## Writing a Context Provider Tool as static text ```yaml -# my-search-context.txt +# my-context.txt -You are an expert web researcher with access to the Search tool.If the search tool fails to return any information stop execution of the script with message "Sorry! Search did not return any results". Feel free to get the contents of the returned URLs in order to get more information. Provide as much detail as you can. Also return the source of the search results. +You have access to run commands on the user's system. Please ask for confirmation from the user before running a command. ``` -## Using a Context Provider Tool +## Using a Context Tool -Continuing with the above example, this is how you can use the same context in tools that uses different search providers: +Continuing with the above example, this is how you can use the same context in different tools: ```yaml -# my-search-duduckgo.gpt -context: ./my-search-context.txt -tools: github.com/gptscript-ai/search/duckduckgo,sys.http.html2text - -What are some of the most popular tourist destinations in Scotland, and how many people visit them each year? +Context: ./my-context.txt +Tools: sys.exec, sys.write +Which processes on my system are using the most memory? Write their PIDs to a file called pids.txt. ``` ```yaml -# my-search-brave.gpt -context: ./my-search-context.txt -tools: github.com/gptscript-ai/search/brave,sys.http.html2text - -List out some of the main actors in the Christopher Nolan movie Inception, as well as the names of the other Christopher Nolan movies they have appeared in. +Context: ./my-context.txt +Tools: sys.exec +Which file in my current directory is the largest? ``` - ## Context Provider Tool with exported tools Here is a simple example of a context provider tool that provides additional context to search tool: ```yaml -# my-search-context-tool.gpt -export: sys.http.html2text? +# my-context-tool.gpt +Share Tools: sys.exec -#!/bin/bash -echo You are an expert web researcher with access to the Search tool.If the search tool fails to return any information stop execution of the script with message "Sorry! Search did not return any results". Feel free to get the contents of the returned URLs in order to get more information. Provide as much detail as you can. Also return the source of the search results. +#!sys.echo +You have access to run commands on the user's system. Please ask for confirmation from the user before running a command. ``` +The `#!sys.echo` at the start of the tool body tells GPTScript to return everything after it as the output of the tool. + Continuing with the above example, this is how you can use it in a script: ```yaml -context: ./my-search-context-tool.gpt -tools: github.com/gptscript-ai/search/duckduckgo - -What are some of the most popular tourist destinations in Scotland, and how many people visit them each year? +Context: ./my-context-tool.gpt +Tools: sys.write +Which processes on my system are using the most memory? Write their PIDs to a file called pids.txt. ``` When you run this script, GPTScript will use the output from the context tool and add it to the user message along with the existing prompt in this tool to provide additional context to LLM. -## Context Provider Tool with args +## Context Provider Tool with Parameters -Here is an example of a context provider tool that uses args to decide which search tool to use when answering the user provided queries: +Here is an example of a context provider tool that takes a parameter: ```yaml -# context_with_arg.gpt -export: github.com/gptscript-ai/search/duckduckgo, github.com/gptscript-ai/search/brave, sys.http.html2text? -args: search_tool: tool to search with +# context_with_param.gpt +Param: tone: the tone to use when responding to the user's request #!/bin/bash -echo You are an expert web researcher with access to the ${search_tool} Search tool.If the search tool fails to return any information stop execution of the script with message "Sorry! Search did not return any results". Feel free to get the contents of the returned URLs in order to get more information. Provide as much detail as you can. Also return the source of the search results. +echo "Respond to the user's request in a ${tone} tone." ``` Continuing with the above example, this is how you can use it in a script: ```yaml -# my_context_with_arg.gpt -context: ./context_with_arg.gpt with ${search} as search_tool -Args: search: Search tool to use +# tool.gpt +Context: ./context_with_param.gpt with ${tone} as tone +Param: tone: the tone to use when responding to the user's request +Tools: sys.http.html2text -What are some of the most popular tourist destinations in Scotland, and how many people visit them each year? +What are the top stories on Hacker News right now? ``` -This script can be used to search with `brave` or `duckduckdb` tools depending on the search parameter passed to the tool. -Example usage for using brave search tool: +Here's how you can run the script and define the tone parameter: + ```yaml -gptscript --disable-cache my_context_with_arg.gpt '{"search": "brave"}' +gptscript tool.gpt '{"tone": "obnoxious"}' ``` diff --git a/docs/docs/03-tools/06-how-it-works.md b/docs/docs/03-tools/06-how-it-works.md index c6538395..31dd17ce 100644 --- a/docs/docs/03-tools/06-how-it-works.md +++ b/docs/docs/03-tools/06-how-it-works.md @@ -1,33 +1,33 @@ # How it works -**_GPTScript is composed of tools._** Each tool performs a series of actions similar to a function. Tools have available -to them other tools that can be invoked similar to a function call. While similar to a function, the tools are -primarily implemented with a natural language prompt. **_The interaction of the tools is determined by the AI model_**, -the model determines if the tool needs to be invoked and what arguments to pass. Tools are intended to be implemented -with a natural language prompt but can also be implemented with a command or HTTP call. +**_GPTScript is fundamentally composed of tools._** Each tool is either a natural language prompt for the LLM, or is +programmatic (i.e. a command, script, or program to be run). Tools that use a natural language prompt can also invoke +other tools, similar to function calls. The LLM decides when a tool needs to be invoked and sets the parameters to pass to it. ## Example -Below are two tool definitions, separated by `---`. The first tool does not require a name or description, but -every tool after name and description are required. The first tool, has the parameter `tools: bob` meaning that the tool named `bob` is available to be called if needed. +Below are two tool definitions, separated by `---`. +The first tool in the file (often referred to as the "entrypoint tool") does not need a name and description, +but a name is required for all other tools in the file, and a description is recommended. +The entrypoint tool also has the line `Tools: bob` meaning that the tool named `bob` is available to be called if needed. ```yaml -tools: bob +Tools: bob Ask Bob how he is doing and let me know exactly what he said. --- -name: bob -description: I'm Bob, a friendly guy. -args: question: The question to ask Bob. +Name: bob +Description: I'm Bob, a friendly guy. +Param: question: The question to ask Bob. When asked how I am doing, respond with "Thanks for asking "${question}", I'm doing great fellow friendly AI tool!" ``` Put the above content in a file named `bob.gpt` and run the following command: -```shell -$ gptscript bob.gpt +```bash +gptscript bob.gpt ``` ``` @@ -36,8 +36,8 @@ OUTPUT: Bob said, "Thanks for asking 'How are you doing?', I'm doing great fellow friendly AI tool!" ``` -Tools can be implemented by invoking a program instead of a natural language prompt. The below -example is the same as the previous example but implements Bob using python. +Tools can be implemented by invoking a program instead of a natural language prompt. +The below example is the same as the previous example but implements Bob using Python. ```yaml Tools: bob @@ -47,7 +47,7 @@ Ask Bob how he is doing and let me know exactly what he said. --- Name: bob Description: I'm Bob, a friendly guy. -Args: question: The question to ask Bob. +Param: question: The question to ask Bob. #!python3 @@ -56,6 +56,4 @@ import os print(f"Thanks for asking {os.environ['question']}, I'm doing great fellow friendly AI tool!") ``` -With these basic building blocks you can create complex scripts with AI interacting with AI, your local system, data, -or external services. - +With these basic building blocks you can create complex scripts with AI interacting with AI, your local system, data, or external services. diff --git a/docs/docs/03-tools/07-gpt-file-reference.md b/docs/docs/03-tools/07-gpt-file-reference.md index c6207ad2..fdc3b363 100644 --- a/docs/docs/03-tools/07-gpt-file-reference.md +++ b/docs/docs/03-tools/07-gpt-file-reference.md @@ -23,10 +23,10 @@ Do more sample tool stuff. ## Tool Definition -A tool starts with a preamble that defines the tool's name, description, args, available tools and additional parameters. +A tool starts with a preamble that defines the tool's name, description, parameters, available tools, and additional directives. The preamble is followed by the tool's body, which contains the instructions for the tool. Comments in the preamble are lines starting with `#` and are ignored by the parser. Comments are not really encouraged -as the text is typically more useful in the description, argument descriptions or instructions. +as the text is typically more useful in the description, parameter descriptions, or body. ```yaml Name: tool-name @@ -34,51 +34,53 @@ Name: tool-name Description: Tool description # This tool can invoke tool1 or tool2 if needed Tools: tool1, tool2 -Args: arg1: The description of arg1 +Param: param1: The description of param1 Tool instructions go here. ``` -## Tool Parameters - -Tool parameters are key-value pairs defined at the beginning of a tool block, before any instructional text. They are specified in the format `key: value`. The parser recognizes the following keys (case-insensitive and spaces are ignored): - -| Key | Description | -|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| -| `Name` | The name of the tool. | -| `Model Name` | The LLM model to use, by default it uses "gpt-4-turbo". | -| `Global Model Name`| The LLM model to use for all the tools. | -| `Description` | The description of the tool. It is important that this properly describes the tool's purpose as the description is used by the LLM. | -| `Internal Prompt` | Setting this to `false` will disable the built-in system prompt for this tool. | -| `Tools` | A comma-separated list of tools that are available to be called by this tool. | -| `Global Tools` | A comma-separated list of tools that are available to be called by all tools. | -| `Credentials` | A comma-separated list of credential tools to run before the main tool. | -| `Args` | Arguments for the tool. Each argument is defined in the format `arg-name: description`. | -| `Max Tokens` | Set to a number if you wish to limit the maximum number of tokens that can be generated by the LLM. | -| `JSON Response` | Setting to `true` will cause the LLM to respond in a JSON format. If you set true you must also include instructions in the tool. | -| `Temperature` | A floating-point number representing the temperature parameter. By default, the temperature is 0. Set to a higher number for more creativity. | -| `Chat` | Setting it to `true` will enable an interactive chat session for the tool. | - - +## Tool Directives + +Tool directives are key-value pairs defined at the beginning of a tool block, before the tool body. +They are specified in the format `Key: value`. The parser recognizes the following keys (case-insensitive and spaces are ignored): + +| Key | Description | +|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| `Name` | The name of the tool. | +| `Model Name` | The LLM model to use, by default it uses "gpt-4-turbo". | +| `Global Model Name` | The LLM model to use for all the tools. | +| `Description` | The description of the tool. It is important that this properly describes the tool's purpose as the description is used by the LLM. | +| `Internal Prompt` | Setting this to `false` will disable the built-in system prompt for this tool. | +| `Tools` | A comma-separated list of tools that are available to be called by this tool. | +| `Global Tools` | A comma-separated list of tools that are available to be called by all tools. | +| `Parameter` / `Args` | Parameters for the tool. Each parameter is defined in the format `param-name: description`. | +| `Max Tokens` | Set to a number if you wish to limit the maximum number of tokens that can be generated by the LLM. | +| `JSON Response` | Setting to `true` will cause the LLM to respond in a JSON format. If you set true you must also include instructions in the tool. | +| `Temperature` | A floating-point number representing the temperature parameter. By default, the temperature is 0. Set to a higher number for more creativity. | +| `Chat` | Setting it to `true` will enable an interactive chat session for the tool. | +| `Credential` | Credential tool to call to set credentials as environment variables before doing anything else. One per line. | +| `Agents` | A comma-separated list of agents that are available to the tool. | +| `Share Tools` | A comma-separated list of tools that are shared by the tool. | +| `Context` | A comma-separated list of context tools available to the tool. | +| `Share Context` | A comma-separated list of context tools shared by this tool with any tool including this tool in its context. | ## Tool Body -The tool body contains the instructions for the tool which can be a natural language prompt or -a command to execute. Commands must start with `#!` followed by the interpreter (e.g. `#!/bin/bash`, `#!python3`) -a text that will be placed in a file and passed to the interpreter. Arguments can be references in the instructions -using the format `${arg1}`. +The tool body contains the instructions for the tool. It can be a natural language prompt or +a command to execute. Commands must start with `#!` followed by the interpreter (e.g. `#!/bin/bash`, `#!python3`). +Parameters can be referenced in the body using the format `${param1}`. ```yaml -name: echo-ai -description: A tool that echos the input -args: input: The input +Name: echo-ai +Description: A tool that echos the input +Parameter: input: The input Just return only "${input}" --- -name: echo-command -description: A tool that echos the input -args: input: The input +Name: echo-command +Description: A tool that echos the input +Parameter: input: The input #!/bin/bash diff --git a/docs/docs/03-tools/08-workspace.md b/docs/docs/03-tools/08-workspace.md new file mode 100644 index 00000000..8756cb2a --- /dev/null +++ b/docs/docs/03-tools/08-workspace.md @@ -0,0 +1,37 @@ +# Workspace + +One concept in GPTScript is the workspace directory. +This is a directory meant to be used by tools that need to interact with the local file system. +By default, the workspace directory is a one-off temporary directory. +The workspace directory can be set with the `--workspace` argument when running GPTScript, like this: + +```bash +gptscript --workspace . my-script.gpt +``` + +In the above example, the user’s current directory (denoted by `.`) will be set as the workspace. +The workspace directory is no longer temporary if it is explicitly set, and everything in it will persist after the script has finished running. +Both absolute and relative paths are supported. + +Regardless of whether it is set implicitly or explicitly, the workspace is then made available to the script execution as the `GPTSCRIPT_WORKSPACE_DIR` environment variable. + +:::info +GPTScript does not force scripts or tools to write to, read from, or otherwise use the workspace. +The tools must decide to make use of the workspace environment variable. +::: + +## The Workspace Context Tool + +To make a non-code tool that uses the LLM aware of the workspace, you can reference the workspace context tool: + +``` +Context: github.com/gptscript-ai/context/workspace +``` + +This tells the LLM (by way of a [system message](https://platform.openai.com/docs/guides/text-generation/chat-completions-api)) what the workspace directory is, +what its initial contents are, and that if it decides to create a file or directory, it should do so in the workspace directory. +This will not, however, have any impact on code-based tools (i.e. Python, Bash, or Go tools). +Such tools will have the `GPTSCRIPT_WORKSPACE_DIR` environment variable available to them, but they must be written in such a way that they make use of it. + +This context tool also automatically shares the `sys.ls`, `sys.read`, and `sys.write` tools with the tool that is using it as a context. +This is because if a tool intends to interact with the workspace, it minimally needs these tools. diff --git a/docs/docs/03-tools/09-code-tool-guidelines.md b/docs/docs/03-tools/09-code-tool-guidelines.md new file mode 100644 index 00000000..0f13ac7f --- /dev/null +++ b/docs/docs/03-tools/09-code-tool-guidelines.md @@ -0,0 +1,135 @@ +# Code Tool Guidelines + +GPTScript can handle the packaging and distribution of code-based tools via GitHub repos. +For more information on how this works, see the [authoring guide](02-authoring.md#sharing-tools). + +This guide provides guidelines for setting up GitHub repos for proper tool distribution. + +## Common Guidelines + +### `tool.gpt` or `agent.gpt` file + +Every repo should have a `tool.gpt` or `agent.gpt` file. This is the main logic of the tool. +If both files exist, GPTScript will use the `agent.gpt` file and ignore the `tool.gpt` file. +Your repo can have other `.gpt` files that are referenced by the main file, but there must be a `tool.gpt` or `agent.gpt` file present. + +Under most circumstances, this file should live in the root of the repo. +If you are using a single repo for the distribution of multiple tools (see [gptscript-ai/context](https://github.com/gptscript-ai/context) for an example), +then you can have the `tool.gpt`/`agent.gpt` file in a subdirectory, and the tool will now be able to be referenced as `github.com///`. + +### Name and Description directives + +We recommend including a `Name` and `Description` directive for your tool. +This is useful for both people and LLMs to understand what the tool will do and when to use it. + +### Parameters + +Any parameters specified in the tool will be available as environment variables in your code. +We recommend handling parameters that way, rather than using command-line arguments. + +## Python Guidelines + +### Calling Python in the tool body + +The body of the `tool.gpt`/`agent.gpt` file needs to call Python. This can be done as an inline script like this: + +``` +Name: my-python-tool + +#!python3 + +print('hello world') +``` + +An inline script like this is only recommended for simple use cases that don't need external dependencies. + +If your use case is more complex or requires external dependencies, you can reference a Python script in your repo, like this: + +``` +Name: my-python-tool + +#!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/tool.py +``` + +(This example assumes that your entrypoint to your Python program is in a file called `tool.py`. You can call it what you want.) + +### `requirements.txt` file + +If your Python program needs any external dependencies, you can create a `requirements.txt` file at the same level as +your `tool.gpt`/`agent.gpt` file. GPTScript will handle downloading the dependencies before it runs the tool. + +The file structure should look something like this: + +``` +. +├── requirements.txt +├── tool.py +└── tool.gpt +``` + +## JavaScript (Node.js) Guidelines + +### Calling Node.js in the tool body + +The body of the `tool.gpt`/`agent.gpt` file needs to call Node. This can be done as an inline script like this: + +``` +Name: my-node-tool + +#!node + +console.log('hello world') +``` + +An inline script like this is only recommended for simple use cases that don't need external dependencies. + +If your use case is more complex or requires external dependencies, you can reference a Node script in your repo, like this: + +``` +Name: my-node-tool + +#!/usr/bin/env node ${GPTSCRIPT_TOOL_DIR}/tool.js +``` + +(This example assumes that your entrypoint to your Node program is in a file called `tool.js`. You can call it what you want.) + +### `package.json` file + +If your Node program needs any external dependencies, you can create a `package.json` file at the same level as +your `tool.gpt`/`agent.gpt` file. GPTScript will handle downloading the dependencies before it runs the tool. + +The file structure should look something like this: + +``` +. +├── package.json +├── tool.js +└── tool.gpt +``` + +## Go Guidelines + +GPTScript does not support inline code for Go, so you must call to an external program from the tool body like this: + +``` +Name: my-go-tool + +#!${GPTSCRIPT_TOOL_DIR}/bin/gptscript-go-tool +``` + +:::important +Unlike the Python and Node cases above where you can name the file anything you want, Go tools must be `#!${GPTSCRIPT_TOOL_DIR}/bin/gptscript-go-tool`. +::: + +GPTScript will build the Go program located at `./main.go` to a file called `./bin/gptscript-go-tool` before running the tool. +All of your dependencies need to be properly specified in a `go.mod` file. + +The file structure should look something like this: + +``` +. +├── go.mod +├── go.sum +├── main.go +└── tool.gpt +``` diff --git a/docs/docs/03-tools/10-daemon.md b/docs/docs/03-tools/10-daemon.md new file mode 100644 index 00000000..128c161a --- /dev/null +++ b/docs/docs/03-tools/10-daemon.md @@ -0,0 +1,108 @@ +# Daemon Tools (Advanced) + +One advanced use case that GPTScript supports is daemon tools. +A daemon tool is a tool that starts a long-running HTTP server in the background, that will continue running until GPTScript is done executing. +Other tools can easily send HTTP POST requests to the daemon tool. + +## Example + +Here is an example of a daemon tool with a simple echo server written in an inline Node.js script: + +``` +Tools: my-daemon +Param: first: the first parameter +Param: second: the second parameter + +#!http://my-daemon.daemon.gptscript.local/myPath + +--- +Name: my-daemon + +#!sys.daemon node + +const http = require('http'); + +const server = http.createServer((req, res) => { + if (req.method === 'GET' || req.method === 'POST') { + // Extract the path from the request URL + const path = req.url; + + let body = ''; + + req.on('data', chunk => { + body += chunk.toString(); + }) + + // Respond with the path and body + req.on('end', () => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write(`Body: ${body}\n`); + res.end(`Path: ${path}`); + }) + } else { + res.writeHead(405, { 'Content-Type': 'text/plain' }); + res.end('Method Not Allowed'); + } +}); + +const PORT = process.env.PORT || 3000; +server.listen(PORT, () => { + console.log(`Server is listening on port ${PORT}`); +}); +``` + +Let's talk about the daemon tool, called `my-daemon`, first. + +### The Daemon Tool + +The body of this tool begins with `#!sys.daemon`. This tells GPTScript to take the rest of the body as a command to be +run in the background that will listen for HTTP requests. GPTScript will run this command (in this case, a Node script). +GPTScript will assign a port number for the server and set the `PORT` environment variable to that number, so the +server needs to check that variable and listen on the proper port. + +After GPTScript runs the daemon, it will send it an HTTP GET request to make sure that it is running properly. +The daemon needs to respond with a 200 OK to this request. +By default, the request goes to `/`, but this can be configured with the following syntax: + +``` +#!sys.daemon (path=/api/ready) node + +// (node script here) +``` + +### The Entrypoint Tool + +The entrypoint tool at the top of this script sends an HTTP request to the daemon tool. +There are a few important things to note here: + +- The `Tools: my-daemon` directive is needed to show that this tool requires the `my-daemon` tool to already be running. + - When the entrypoint tool runs, GPTScript will check if `my-daemon` is already running. If it is not, GPTScript will start it. +- The `#!http://my-daemon.daemon.gptscript.local/myPath` in the body tells GPTScript to send an HTTP request to the daemon tool. + - The request will be a POST request, with the body of the request being a JSON string of the parameters passed to the entrypoint tool. + - For example, if the script is run like `gptscript script.gpt '{"first":"hello","second":"world"}'`, then the body of the request will be `{"first":"hello","second":"world"}`. + - The path of the request will be `/myPath`. + - The hostname is `my-daemon.daemon.gptscript.local`. When sending a request to a daemon tool, the hostname must always start with the daemon tool's name, followed by `.daemon.gptscript.local`. + - GPTScript recognizes this hostname and determines the correct port number to send the request to, on localhost. + +### Running the Example + +Now let's try running it: + +```bash +gptscript script.gpt '{"first":"hello","second":"world"}' +``` + +``` +OUTPUT: + +Body: {"first":"hello","second":"world"} +Path: /myPath +``` + +This is exactly what we expected. This is a silly, small example just to demonstrate how this feature works. +A real-world situation would involve several different tools sending different HTTP requests to the daemon tool, +likely with an LLM determining when to call which tool. + +## Real-World Example + +To see a real-world example of a daemon tool, check out the [GPTScript Browser tool](https://github.com/gptscript-ai/browser). diff --git a/docs/docs/03-tools/11-input-output-filters.md b/docs/docs/03-tools/11-input-output-filters.md new file mode 100644 index 00000000..1db8f937 --- /dev/null +++ b/docs/docs/03-tools/11-input-output-filters.md @@ -0,0 +1,137 @@ +# Input and Output Filters (Advanced) + +GPTScript supports input and output filters, which are tools that can modify the input to a tool or the output from a tool. +These are best explained with examples. + +## Input Filter Example + +In this example, the entrypoint tool uses an input filter to modify the `message` parameter, before calling the subtool. +Then, the subtool uses another input filter to modify the message, then writes it to a file. + +``` +# File name: script.gpt +Param: message: the message from the user +Tools: subtool +Input Filter: appleToOrange + +Take the message and give it to the subtool. Then say "Done". + +--- +Name: subtool +Param: message: the message from the user +Input Filter: orangeToBanana + +#!python3 + +import os + +message = os.getenv("message", "") +with open("gptscript_output.txt", "w") as f: + f.write(message) + +--- +Name: appleToOrange + +#!python3 + +import os + +def output(input: str): + return input.replace("apple", "orange") + +print(output(os.getenv("INPUT", ""))) + +--- +Name: orangeToBanana + +#!python3 + +import os + +def output(input: str): + return input.replace("orange", "banana") + +print(output(os.getenv("INPUT", ""))) +``` + +Try running this tool with the following command: + +```bash +gptscript script.gpt '{"message":"apple is great"}' + +# Then view the output: +cat gptscript_output.txt +``` + +The output should say "banana is great". +This matches what we expect, because the input filter `appleToOrange` changes "apple" to "orange", +and the input filter `orangeToBanana` changes "orange" to "banana". +If we run the tool again with a different message, like "hello world", the final message will be unmodified, +since it did not include the words "apple" or "orange". + +The input filter tools both read the input from the environment variable `INPUT`. +They write their modified input to stdout. +This variable is set by GPTScript before running the input filter tool. + +### Input Filter Real-World Example + +For a real-world example of an input filter tool, check out the [gptscript-ai/context/at-syntax](https://github.com/gptscript-ai/context/tree/main/at-syntax) tool. + +## Output Filter Example + +In this example, the tool is asked to write a poem about apples. +The output filter then replaces all references to apples with oranges. + +``` +Output Filter: applesToOranges + +Write a poem about apples. + +--- +Name: applesToOranges + +#!python3 + +import os + +replacements = { + "Apples": "Oranges", + "apples": "oranges", + "apple": "orange", + "Apple": "Orange", +} + +def applesToOranges(input: str) -> str: + for key, value in replacements.items(): + if input.startswith(key): + # This approach doesn't maintain whitespace, but it's good enough for this example + input = input.replace(key, value) + return input + +output: str = os.getenv("OUTPUT", "") +new_output: str = "" +for i in output.split(): + new_output += applesToOranges(i) + " " +print(new_output.strip()) +``` + +``` +OUTPUT: + +In orchards where the sunlight gleams, Among the leaves, in golden beams, The oranges hang on branches high, A feast for both the heart and eye. +Their skins, a palette rich and bright, In hues of red and green delight, With every bite, a crisp surprise, A taste of autumn, pure and wise. +From pies to cider, sweet and bold, Their stories through the seasons told, In every crunch, a memory, Of nature's gift, so wild and free. +Oh, oranges, treasures of the earth, In every form, you bring us mirth, A simple fruit, yet so profound, In you, a world of joy is found. +``` + +The output tool reads the output from the environment variable `OUTPUT`. +It can then modify the output as needed, and print the new output to stdout. + +Output filter tools can also access the following environment variables if needed: + +- `CHAT` (boolean): indicates whether the current script is being run in chat mode or not +- `CONTINUATION` (boolean): if `CHAT` is true, indicates whether the current chat will continue executing, or if this is the final message + +### Output Filter Real-World Example + +For a real-world example of an output filter tool, check out the [gptscript-ai/context/chat-summary](https://github.com/gptscript-ai/context/tree/main/chat-summary) tool. diff --git a/docs/docs/04-command-line-reference/gptscript.md b/docs/docs/04-command-line-reference/gptscript.md index 0c485603..4ca35228 100644 --- a/docs/docs/04-command-line-reference/gptscript.md +++ b/docs/docs/04-command-line-reference/gptscript.md @@ -12,37 +12,40 @@ gptscript [flags] PROGRAM_FILE [INPUT...] ### Options ``` - --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) - --chat-state string The chat state to continue, or null to start a new chat and return the state ($GPTSCRIPT_CHAT_STATE) - -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) - --color Use color in output (default true) ($GPTSCRIPT_COLOR) - --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) - --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) - --credential-context string Context name in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) (default "default") - --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) - --debug Enable debug logging ($GPTSCRIPT_DEBUG) - --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) - --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") - --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) - --disable-tui Don't use chat TUI but instead verbose output ($GPTSCRIPT_DISABLE_TUI) - --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) - --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) - --force-chat Force an interactive chat session if even the top level tool is not a chat tool ($GPTSCRIPT_FORCE_CHAT) - --force-sequential Force parallel calls to run sequentially ($GPTSCRIPT_FORCE_SEQUENTIAL) - -h, --help help for gptscript - -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) - --list-models List the models available and exit ($GPTSCRIPT_LIST_MODELS) - --list-tools List built-in tools and exit ($GPTSCRIPT_LIST_TOOLS) - --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) - --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) - --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) - --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) - -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) - -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) - --save-chat-state-file string A file to save the chat state to so that a conversation can be resumed with --chat-state ($GPTSCRIPT_SAVE_CHAT_STATE_FILE) - --sub-tool string Use tool of this name, not the first tool in file ($GPTSCRIPT_SUB_TOOL) - --ui Launch the UI ($GPTSCRIPT_UI) - --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) + --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) + --chat-state string The chat state to continue, or null to start a new chat and return the state ($GPTSCRIPT_CHAT_STATE) + -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) + --color Use color in output (default true) ($GPTSCRIPT_COLOR) + --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) + --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) + --credential-context strings Context name(s) in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) + --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) + --debug Enable debug logging ($GPTSCRIPT_DEBUG) + --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) + --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") + --default-model-provider string Default LLM model provider to use, this will override OpenAI settings ($GPTSCRIPT_DEFAULT_MODEL_PROVIDER) + --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) + --disable-tui Don't use chat TUI but instead verbose output ($GPTSCRIPT_DISABLE_TUI) + --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) + --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) + --force-chat Force an interactive chat session if even the top level tool is not a chat tool ($GPTSCRIPT_FORCE_CHAT) + --force-sequential Force parallel calls to run sequentially ($GPTSCRIPT_FORCE_SEQUENTIAL) + --github-enterprise-hostname string The host name for a Github Enterprise instance to enable for remote loading ($GPTSCRIPT_GITHUB_ENTERPRISE_HOSTNAME) + -h, --help help for gptscript + -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) + --list-models List the models available and exit ($GPTSCRIPT_LIST_MODELS) + --list-tools List built-in tools and exit ($GPTSCRIPT_LIST_TOOLS) + --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) + --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) + --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) + --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) + -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) + -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) + --save-chat-state-file string A file to save the chat state to so that a conversation can be resumed with --chat-state ($GPTSCRIPT_SAVE_CHAT_STATE_FILE) + --sub-tool string Use tool of this name, not the first tool in file ($GPTSCRIPT_SUB_TOOL) + --system-tools-dir string Directory that contains system managed tool for which GPTScript will not manage the runtime ($GPTSCRIPT_SYSTEM_TOOLS_DIR) + --ui Launch the UI ($GPTSCRIPT_UI) + --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) ``` ### SEE ALSO @@ -50,5 +53,6 @@ gptscript [flags] PROGRAM_FILE [INPUT...] * [gptscript credential](gptscript_credential.md) - List stored credentials * [gptscript eval](gptscript_eval.md) - * [gptscript fmt](gptscript_fmt.md) - +* [gptscript getenv](gptscript_getenv.md) - Looks up an environment variable for use in GPTScript tools * [gptscript parse](gptscript_parse.md) - diff --git a/docs/docs/04-command-line-reference/gptscript_credential.md b/docs/docs/04-command-line-reference/gptscript_credential.md index 435ba6e5..eb5781f4 100644 --- a/docs/docs/04-command-line-reference/gptscript_credential.md +++ b/docs/docs/04-command-line-reference/gptscript_credential.md @@ -20,7 +20,7 @@ gptscript credential [flags] ### Options inherited from parent commands ``` - --credential-context string Context name in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) (default "default") + --credential-context strings Context name(s) in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) ``` ### SEE ALSO diff --git a/docs/docs/04-command-line-reference/gptscript_credential_delete.md b/docs/docs/04-command-line-reference/gptscript_credential_delete.md index c2f78e88..c9cffdd3 100644 --- a/docs/docs/04-command-line-reference/gptscript_credential_delete.md +++ b/docs/docs/04-command-line-reference/gptscript_credential_delete.md @@ -18,7 +18,7 @@ gptscript credential delete [flags] ### Options inherited from parent commands ``` - --credential-context string Context name in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) (default "default") + --credential-context strings Context name(s) in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) ``` ### SEE ALSO diff --git a/docs/docs/04-command-line-reference/gptscript_credential_show.md b/docs/docs/04-command-line-reference/gptscript_credential_show.md index f5fb11af..f89df87a 100644 --- a/docs/docs/04-command-line-reference/gptscript_credential_show.md +++ b/docs/docs/04-command-line-reference/gptscript_credential_show.md @@ -18,7 +18,7 @@ gptscript credential show [flags] ### Options inherited from parent commands ``` - --credential-context string Context name in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) (default "default") + --credential-context strings Context name(s) in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) ``` ### SEE ALSO diff --git a/docs/docs/04-command-line-reference/gptscript_eval.md b/docs/docs/04-command-line-reference/gptscript_eval.md index 0fdd0249..ddbecc9f 100644 --- a/docs/docs/04-command-line-reference/gptscript_eval.md +++ b/docs/docs/04-command-line-reference/gptscript_eval.md @@ -25,27 +25,29 @@ gptscript eval [flags] ### Options inherited from parent commands ``` - --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) - -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) - --color Use color in output (default true) ($GPTSCRIPT_COLOR) - --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) - --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) - --credential-context string Context name in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) (default "default") - --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) - --debug Enable debug logging ($GPTSCRIPT_DEBUG) - --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) - --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") - --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) - --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) - --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) - -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) - --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) - --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) - --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) - --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) - -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) - -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) - --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) + --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) + -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) + --color Use color in output (default true) ($GPTSCRIPT_COLOR) + --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) + --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) + --credential-context strings Context name(s) in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) + --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) + --debug Enable debug logging ($GPTSCRIPT_DEBUG) + --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) + --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") + --default-model-provider string Default LLM model provider to use, this will override OpenAI settings ($GPTSCRIPT_DEFAULT_MODEL_PROVIDER) + --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) + --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) + --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) + -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) + --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) + --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) + --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) + --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) + -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) + -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) + --system-tools-dir string Directory that contains system managed tool for which GPTScript will not manage the runtime ($GPTSCRIPT_SYSTEM_TOOLS_DIR) + --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) ``` ### SEE ALSO diff --git a/docs/docs/04-command-line-reference/gptscript_fmt.md b/docs/docs/04-command-line-reference/gptscript_fmt.md index 5780c838..2b042623 100644 --- a/docs/docs/04-command-line-reference/gptscript_fmt.md +++ b/docs/docs/04-command-line-reference/gptscript_fmt.md @@ -19,27 +19,29 @@ gptscript fmt [flags] ### Options inherited from parent commands ``` - --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) - -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) - --color Use color in output (default true) ($GPTSCRIPT_COLOR) - --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) - --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) - --credential-context string Context name in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) (default "default") - --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) - --debug Enable debug logging ($GPTSCRIPT_DEBUG) - --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) - --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") - --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) - --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) - --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) - -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) - --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) - --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) - --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) - --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) - -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) - -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) - --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) + --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) + -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) + --color Use color in output (default true) ($GPTSCRIPT_COLOR) + --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) + --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) + --credential-context strings Context name(s) in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) + --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) + --debug Enable debug logging ($GPTSCRIPT_DEBUG) + --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) + --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") + --default-model-provider string Default LLM model provider to use, this will override OpenAI settings ($GPTSCRIPT_DEFAULT_MODEL_PROVIDER) + --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) + --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) + --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) + -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) + --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) + --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) + --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) + --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) + -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) + -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) + --system-tools-dir string Directory that contains system managed tool for which GPTScript will not manage the runtime ($GPTSCRIPT_SYSTEM_TOOLS_DIR) + --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) ``` ### SEE ALSO diff --git a/docs/docs/04-command-line-reference/gptscript_getenv.md b/docs/docs/04-command-line-reference/gptscript_getenv.md new file mode 100644 index 00000000..7e677c5c --- /dev/null +++ b/docs/docs/04-command-line-reference/gptscript_getenv.md @@ -0,0 +1,49 @@ +--- +title: "gptscript getenv" +--- +## gptscript getenv + +Looks up an environment variable for use in GPTScript tools + +``` +gptscript getenv [flags] KEY [DEFAULT] +``` + +### Options + +``` + -h, --help help for getenv +``` + +### Options inherited from parent commands + +``` + --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) + -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) + --color Use color in output (default true) ($GPTSCRIPT_COLOR) + --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) + --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) + --credential-context strings Context name(s) in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) + --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) + --debug Enable debug logging ($GPTSCRIPT_DEBUG) + --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) + --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") + --default-model-provider string Default LLM model provider to use, this will override OpenAI settings ($GPTSCRIPT_DEFAULT_MODEL_PROVIDER) + --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) + --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) + --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) + -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) + --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) + --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) + --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) + --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) + -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) + -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) + --system-tools-dir string Directory that contains system managed tool for which GPTScript will not manage the runtime ($GPTSCRIPT_SYSTEM_TOOLS_DIR) + --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) +``` + +### SEE ALSO + +* [gptscript](gptscript.md) - + diff --git a/docs/docs/04-command-line-reference/gptscript_parse.md b/docs/docs/04-command-line-reference/gptscript_parse.md index 680aebf6..567b0c05 100644 --- a/docs/docs/04-command-line-reference/gptscript_parse.md +++ b/docs/docs/04-command-line-reference/gptscript_parse.md @@ -19,27 +19,29 @@ gptscript parse [flags] ### Options inherited from parent commands ``` - --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) - -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) - --color Use color in output (default true) ($GPTSCRIPT_COLOR) - --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) - --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) - --credential-context string Context name in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) (default "default") - --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) - --debug Enable debug logging ($GPTSCRIPT_DEBUG) - --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) - --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") - --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) - --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) - --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) - -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) - --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) - --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) - --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) - --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) - -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) - -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) - --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) + --cache-dir string Directory to store cache (default: $XDG_CACHE_HOME/gptscript) ($GPTSCRIPT_CACHE_DIR) + -C, --chdir string Change current working directory ($GPTSCRIPT_CHDIR) + --color Use color in output (default true) ($GPTSCRIPT_COLOR) + --config string Path to GPTScript config file ($GPTSCRIPT_CONFIG) + --confirm Prompt before running potentially dangerous commands ($GPTSCRIPT_CONFIRM) + --credential-context strings Context name(s) in which to store credentials ($GPTSCRIPT_CREDENTIAL_CONTEXT) + --credential-override strings Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234) ($GPTSCRIPT_CREDENTIAL_OVERRIDE) + --debug Enable debug logging ($GPTSCRIPT_DEBUG) + --debug-messages Enable logging of chat completion calls ($GPTSCRIPT_DEBUG_MESSAGES) + --default-model string Default LLM model to use ($GPTSCRIPT_DEFAULT_MODEL) (default "gpt-4o") + --default-model-provider string Default LLM model provider to use, this will override OpenAI settings ($GPTSCRIPT_DEFAULT_MODEL_PROVIDER) + --disable-cache Disable caching of LLM API responses ($GPTSCRIPT_DISABLE_CACHE) + --dump-state string Dump the internal execution state to a file ($GPTSCRIPT_DUMP_STATE) + --events-stream-to string Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\.\pipe\my-pipe) ($GPTSCRIPT_EVENTS_STREAM_TO) + -f, --input string Read input from a file ("-" for stdin) ($GPTSCRIPT_INPUT_FILE) + --no-trunc Do not truncate long log messages ($GPTSCRIPT_NO_TRUNC) + --openai-api-key string OpenAI API KEY ($OPENAI_API_KEY) + --openai-base-url string OpenAI base URL ($OPENAI_BASE_URL) + --openai-org-id string OpenAI organization ID ($OPENAI_ORG_ID) + -o, --output string Save output to a file, or - for stdout ($GPTSCRIPT_OUTPUT) + -q, --quiet No output logging (set --quiet=false to force on even when there is no TTY) ($GPTSCRIPT_QUIET) + --system-tools-dir string Directory that contains system managed tool for which GPTScript will not manage the runtime ($GPTSCRIPT_SYSTEM_TOOLS_DIR) + --workspace string Directory to use for the workspace, if specified it will not be deleted on exit ($GPTSCRIPT_WORKSPACE) ``` ### SEE ALSO diff --git a/docs/docs/05-alternative-model-providers.md b/docs/docs/05-alternative-model-providers.md index aa637136..8e32ca21 100644 --- a/docs/docs/05-alternative-model-providers.md +++ b/docs/docs/05-alternative-model-providers.md @@ -1,41 +1,31 @@ # Supported Models and Platforms -## Usage +GPTScript can be used against alternative models that expose an OpenAI-compatible API or have a provider available. +Here is an example using Claude: -GPTScript can be used against alternative models that expose an OpenAI compatible API or have a provider shim available. - -### Using a model with an OpenAI compatible API - -```gptscript -model: mistral-large-latest from https://api.mistral.ai/v1 - -Say hello world -``` - -#### Note -Mistral's La Plateforme has an OpenAI compatible API, but the model does not behave identically to gpt-4. For that reason, we also have a provider for it that might get better results in some cases. - - -### Using a model that requires a provider ```gptscript model: claude-3-haiku-20240307 from github.com/gptscript-ai/claude3-anthropic-provider Say hello world ``` -### Authentication +A note on model compatibility: -For OpenAI compatible providers, GPTScript will look for an API key to be configured with the -prefix `GPTSCRIPT_PROVIDER_`, the base domain converted to environment variable format, and a suffix of `_API_KEY`. -As an example if you are using `mistral-large-latest from https://api.mistral.ai/v1`, the environment variable would -be `GPTSCRIPT_PROVIDER_API_MISTRAL_AI_API_KEY` +:::important +While the providers allow GPTScript to work with other models, the effectiveness of using a +different model will depend on a combination of prompt engineering and the quality of the model. You may need to change +wording or add more description if you are not getting the results you want. In some cases, the model might not be +capable of intelligently handling the complex function calls. +::: -Each provider shim has different requirements for authentication. Please check the readme for the provider you are +## Authentication + +Each provider has different requirements for authentication. Please check the readme for the provider you are trying to use. ## Available Model Providers -The following shims are currently available: +The following providers are currently available: * [github.com/gptscript-ai/azure-openai-provider](https://github.com/gptscript-ai/azure-openai-provider) * [github.com/gptscript-ai/azure-other-provider](https://github.com/gptscript-ai/azure-other-provider) @@ -50,16 +40,40 @@ The following shims are currently available: For any provider that supports listing models, you can use this command: ```bash -# With a shim gptscript --list-models github.com/gptscript-ai/claude3-anthropic-provider +``` -# To OpenAI compatible endpoint -gptscript --list-models https://api.mistral.ai/v1 +## OpenAI-Compatible APIs (Advanced) + +:::warning +Even if a non-OpenAI service has an API that claims to be OpenAI-compatible, there are usually subtle differences that cause things to break. +The approach described in this section often does not work. +::: + +You can use a model from an OpenAI-compatible API like this: + +```gptscript +model: mistral-large-latest from https://api.mistral.ai/v1 + +Say hello world ``` -## Compatibility +:::note +Mistral's La Plateforme has an OpenAI-compatible API, but the model does not behave identically to gpt-4. +For that reason, we also have a provider for it that might get better results in some cases. +::: -While the shims provide support for using GPTScript with other models, the effectiveness of using a -different model will depend on a combination of prompt engineering and the quality of the model. You may need to change -wording or add more description if you are not getting the results you want. In some cases, the model might not be -capable of intelligently handling the complex function calls. +### Authentication + +For OpenAI-compatible providers, GPTScript will look for an API key to be configured with the +prefix `GPTSCRIPT_PROVIDER_`, the base domain converted to environment variable format, and a suffix of `_API_KEY`. +For example, if you are using `mistral-large-latest from https://api.mistral.ai/v1`, the environment variable would +be `GPTSCRIPT_PROVIDER_API_MISTRAL_AI_API_KEY`. + +### Listing available models + +You can list models from an OpenAI-compatible API like this: + +```bash +gptscript --list-models https://api.mistral.ai/v1 +``` diff --git a/docs/docs/02-credentials.md b/docs/docs/06-credentials.md similarity index 96% rename from docs/docs/02-credentials.md rename to docs/docs/06-credentials.md index 5ba349b9..1d3431de 100644 --- a/docs/docs/02-credentials.md +++ b/docs/docs/06-credentials.md @@ -18,7 +18,9 @@ The configuration file is located in the following location based on your operat - macOS: `$HOME/Library/Application Support/gptscript/config.json` - Linux: `$XDG_CONFIG_HOME/gptscript/config.json` -(Note: if you set the `XDG_CONFIG_HOME` environment variable on macOS, then the same path as Linux will be used.) +:::note +If you set the `XDG_CONFIG_HOME` environment variable on macOS, then the same path as Linux will be used. +::: The configured credential store will be automatically downloaded and compiled from the [gptscript-ai/gptscript-credential-helpers](https://github.com/gptscript-ai/gptscript-credential-helpers) repository, other than the `file` store, which is built-in to GPTScript itself. diff --git a/docs/docs/09-faqs.md b/docs/docs/09-faqs.md index b849e1d0..20196011 100644 --- a/docs/docs/09-faqs.md +++ b/docs/docs/09-faqs.md @@ -2,14 +2,15 @@ ### I don't have Homebrew, how can I install GPTScript? -On MacOS and Linux, you can alternatively install via: `curl https://get.gptscript.ai/install.sh | sh` +On macOS and Linux, you can alternatively install via: `curl https://get.gptscript.ai/install.sh | sh` On all supported systems, you download and install the archive for your platform and architecture from the [releases page](https://github.com/gptscript-ai/gptscript/releases). - ### Does GPTScript have an SDK or API I can program against? -Currently, there are three SDKs being maintained: [Python](https://github.com/gptscript-ai/py-gptscript), [Node](https://github.com/gptscript-ai/node-gptscript), and [Go](https://github.com/gptscript-ai/go-gptscript). They are currently under development and are being iterated on relatively rapidly. The READMEs in each repository contain the most up-to-date documentation for the functionality of each. +Currently, there are three SDKs being maintained: [Python](https://github.com/gptscript-ai/py-gptscript), [Node](https://github.com/gptscript-ai/node-gptscript), and [Go](https://github.com/gptscript-ai/go-gptscript). +They are under development and are being iterated on relatively rapidly. +The READMEs in each repository contain the most up-to-date documentation for the functionality of each. ### I see there's a --disable-cache flag. How does caching working in GPTScript? @@ -17,39 +18,65 @@ GPTScript leverages caching to speed up execution and reduce LLM costs. There ar - Git commit hash lookups for tools - LLM responses -Caching is enabled for both of these by default. It can be disabled via the `--disable-cache` flag. Below is an explanation of how these areas behave when caching is enabled and disabled. +Caching is enabled for both of these by default. It can be disabled via the `--disable-cache` flag. +Below is an explanation of how these areas behave when caching is enabled and disabled. #### Git commit hash lookups for tools -When a remote tool or context is included in your script (like so: `Tools: github.com/gptscript-ai/browser`) and then invoked during script execution, GPTScript will pull the Git repo for that tool and build it. The tool’s repo and build will be stored in your system’s cache directory (at [$XDG_CACHE_HOME](https://pkg.go.dev/os#UserCacheDir)/gptscript/repos). Subsequent invocations of the tool leverage that cache. When the cache is enabled, GPTScript will only check for a newer version of the tool once an hour; if an hour hasn’t passed since the last check, it will just use the one it has. If this is the first invocation and the tool doesn’t yet exist in the cache, it will be pulled and built as normal. +When a remote tool or context is included in your script (like so: `Tools: github.com/gptscript-ai/browser`) and then invoked during script execution, +GPTScript will pull the Git repo for that tool and build it. +The tool's repo and build will be stored in your system's cache directory (at [$XDG_CACHE_HOME](https://pkg.go.dev/os#UserCacheDir)/gptscript/repos). +Subsequent invocations of the tool leverage that cache. +When the cache is enabled, GPTScript will only check for a newer version of the tool once an hour; +if an hour hasn't passed since the last check, it will just use the one it has. +If this is the first invocation and the tool doesn't yet exist in the cache, it will be pulled and built as normal. -When the cache is disabled, GPTScript will check that it has the latest version of the tool (meaning the latest git commit for the repo) on every single invocation of the tool. If GPTScript determines it already has the latest version, that build will be used as-is. In other words, disabling the cache DOES NOT force GPTScript to rebuild the tool, it only forces GPTScript to always check if it has the latest version. +When the cache is disabled, GPTScript will check that it has the latest version of the tool (meaning the latest git commit for the repo) on every single invocation of the tool. +If GPTScript determines it already has the latest version, that build will be used as-is. +In other words, disabling the cache DOES NOT force GPTScript to rebuild the tool, it only forces GPTScript to always check if it has the latest version. #### LLM responses -With regards to LLM responses, when the cache is enabled GPTScript will cache the LLM’s response to a chat completion request. Each response is stored as a gob-encoded file in $XDG_CACHE_HOME/gptscript, where the file name is a hash of the chat completion request. +In regard to LLM responses, when the cache is enabled, GPTScript will cache the LLM's response to a chat completion request. +Each response is stored as a gob-encoded file in $XDG_CACHE_HOME/gptscript, where the file name is a hash of the chat completion request. -It is important to note that all [messages in chat completion request](https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages) are used to generate the hash that is used as the file name. This means that every message between user and LLM affects the cache lookup. So, when using GPTScript in chat mode, it is very unlikely you’ll receive a cached LLM response. Conversely, non-chat GPTScript automations are much more likely to be consistent and thus make use of cached LLM responses. +It is important to note that all [messages in chat completion request](https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages) are used to generate the hash that is used as the file name. +This means that every message between user and LLM affects the cache lookup. +So, when using GPTScript in chat mode, it is very unlikely you'll receive a cached LLM response. +Conversely, non-chat GPTScript automations are much more likely to be consistent and thus make use of cached LLM responses. ### I see there's a --workspace flag. How do I make use of that? -Every invocation of GPTScript has a workspace directory available to it. By default, this directory is a one-off temp directory, but you can override this and explicitly set a workspace using the `--workspace` flag, like so: +Every invocation of GPTScript has a workspace directory available to it. +By default, this directory is a one-off temp directory, but you can override this and explicitly set a workspace using the `--workspace` flag, like so: + ``` gptscript --workspace . my-script.gpt ``` -In the above example, the user’s current directory (denoted by `.`) will be set as the workspace. Both absolute and relative paths are supported. -Regardless of whether it is set implicitly or explicitly, the workspace is then made available to the script execution as the `GPTSCRIPT_WORKSPACE_DIR` environment variable. +For more info, see the [Workspace](03-tools/08-workspace.md) page. -:::info -GPTScript does not force scripts or tools to write to, read from, or otherwise use the workspace. The tools must decide to make use of the workspace environment variable. -::: +### I'm hitting GitHub's rate limit for unauthenticated requests when using GPTScript. -To make prompt-based tools workspace aware, you can add our workspace context, like so: +By default, GPTScript makes unauthenticated requests to GitHub when pulling tools. +Since GitHub's rate limits for unauthenticated requests are fairly low, running into them when developing with GPTScript is a common issue. +To avoid this, you can get GPTScript to make authenticated requests -- which have higher rate limits -- by setting the `GITHUB_AUTH_TOKEN` environment variable to your github account's PAT (Personal Access Token). +If you're already authenticated with the `gh` CLI, you can use its token by running: + +```bash +export GITHUB_AUTH_TOKEN="$(gh auth token)" ``` -Context: github.com/gptscript-ai/context/workspace + +### Can I save my chat and resume it later? + +Yes! When you run GPTScript, be sure to specify the `--save-chat-state-file` argument like this: + +```bash +gptscript --save-chat-state-file chat-state.json my-script.gpt ``` -This tells the LLM (by way of a [system message](https://platform.openai.com/docs/guides/text-generation/chat-completions-api)) what the workspace directory is, what its initial contents are, and that if it decides to create a file or directory, it should do so in the workspace directory. This will not, however, have any impact on code-based tools (ie python, bash, or go tools). Such tools will have the `GPTSCRIPT_WORKSPACE_DIR` environment variable available to them, but they must be written in such a way that they make use of it. -This context also automatically shares the `sys.ls`, `sys.read`, and `sys.write` tools with the tool that is using it as a context. This is because if a tool intends to interact with the workspace, it minimally needs these tools. +Then, when you want to resume your chat, you can use the `--chat-state` argument to specify the file you saved: +```bash +gptscript --chat-state chat-state.json my-script.gpt +``` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index c48b8ac9..f3344bca 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -66,8 +66,8 @@ const config = { position: "right", }, { - href: "https://tools.gptscript.ai/", - label: "Tool Search", + href: "mailto:info@acorn.io?subject=Reaching out from GPTScript Docs", + label: "Contact Us", position: "right", }, ], diff --git a/docs/package-lock.json b/docs/package-lock.json index 9bbb14b2..43b226ef 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -198,81 +198,19 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", @@ -602,17 +540,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -639,99 +579,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", - "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.27.3" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2040,36 +1907,35 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.9.tgz", - "integrity": "sha512-oeOFTrYWdWXCvXGB5orvMTJ6gCZ9I6FBjR+M38iKNXCsPxr4xT0RTdg5uz1H7QP8pp74IzPtwritEr+JscqHXQ==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.6.tgz", + "integrity": "sha512-vDVrlmRAY8z9Ul/HxT+8ceAru95LQgkSKiXkSYZvqtbkPSfhZJgpRp45Cldbh1GJ1kxzQkI70AqyrTI58KpaWQ==", + "license": "MIT", "dependencies": { - "core-js-pure": "^3.30.2", - "regenerator-runtime": "^0.14.0" + "core-js-pure": "^3.30.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2096,13 +1962,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2834,9 +2700,10 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -3359,9 +3226,10 @@ } }, "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==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.4", @@ -3648,145 +3516,162 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" }, "node_modules/accepts": { "version": "1.3.8", @@ -3820,9 +3705,10 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3830,14 +3716,6 @@ "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==", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -4198,20 +4076,21 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", + "qs": "6.13.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -4224,6 +4103,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4232,6 +4112,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -4239,7 +4120,8 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/bonjour-service": { "version": "1.2.1", @@ -4277,29 +4159,31 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "funding": [ { "type": "opencollective", @@ -4314,11 +4198,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -4394,6 +4279,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4434,9 +4348,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001589", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", - "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==", + "version": "1.0.30001722", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", + "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", "funding": [ { "type": "opencollective", @@ -4450,7 +4364,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/ccount": { "version": "2.0.1", @@ -4736,7 +4651,8 @@ "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" }, "node_modules/combine-promises": { "version": "1.2.0", @@ -4879,6 +4795,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4889,9 +4806,10 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5028,9 +4946,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5484,6 +5403,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5500,6 +5420,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -5676,6 +5597,20 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -5689,12 +5624,14 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.679", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.679.tgz", - "integrity": "sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==" + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", + "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -5724,17 +5661,19 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5763,12 +5702,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5786,10 +5723,23 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5923,15 +5873,12 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.0.1.tgz", - "integrity": "sha512-b2tdzTurEIbwRh+mKrEcaWfu1wgb8J1hVsgREg7FFiecWwK/PhO8X0kyc+0bIcKNtD4sqxIdNoRy6/p/TvECEA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz", + "integrity": "sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==", + "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "is-plain-obj": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" + "@types/estree": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/remcohaszing" @@ -5981,6 +5928,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6033,36 +5981,37 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6071,6 +6020,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/content-disposition": { @@ -6098,9 +6051,10 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", @@ -6151,14 +6105,6 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "dependencies": { - "punycode": "^1.3.2" - } - }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -6274,9 +6220,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6285,12 +6232,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -6305,6 +6253,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6312,7 +6261,8 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/find-cache-dir": { "version": "4.0.0", @@ -6353,15 +6303,16 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -6531,6 +6482,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6588,15 +6540,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6610,6 +6568,19 @@ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -6659,7 +6630,8 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" }, "node_modules/global-dirs": { "version": "3.0.1", @@ -6746,11 +6718,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6868,21 +6841,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6902,9 +6865,10 @@ } }, "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -7310,6 +7274,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -7340,9 +7305,10 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -7397,6 +7363,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -7424,9 +7391,10 @@ } }, "node_modules/image-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", - "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", "dependencies": { "queue": "6.0.2" }, @@ -7698,6 +7666,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -8140,6 +8109,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdast-util-directive": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", @@ -8527,6 +8505,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8543,9 +8522,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -10240,11 +10223,12 @@ ] }, "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==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -10255,6 +10239,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -10369,15 +10354,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -10430,9 +10416,10 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -10497,9 +10484,13 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10538,6 +10529,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -10827,9 +10819,10 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "license": "MIT", "dependencies": { "isarray": "0.0.1" } @@ -10853,9 +10846,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -11570,9 +11564,10 @@ } }, "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -11638,11 +11633,6 @@ "node": ">= 0.10" } }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, "node_modules/pupa": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", @@ -11658,11 +11648,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -11726,9 +11717,10 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -11743,6 +11735,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -12095,11 +12088,6 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", @@ -12569,7 +12557,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/sax": { "version": "1.3.0", @@ -12585,9 +12574,10 @@ } }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -12595,7 +12585,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -12682,9 +12672,10 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -12708,6 +12699,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -12715,17 +12707,29 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/send/node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -12739,24 +12743,25 @@ } }, "node_modules/serve-handler": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", - "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "license": "MIT", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", "mime-types": "2.1.18", "minimatch": "3.1.2", "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", + "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" }, "node_modules/serve-index": { "version": "1.9.1", @@ -12829,14 +12834,15 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -12861,7 +12867,8 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", @@ -12923,14 +12930,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", - "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -13118,6 +13180,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -13390,12 +13453,13 @@ } }, "node_modules/terser": { - "version": "5.27.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz", - "integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.42.0.tgz", + "integrity": "sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==", + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -13407,15 +13471,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -13439,29 +13504,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -13475,28 +13517,6 @@ "node": ">= 10.13.0" } }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -13536,18 +13556,11 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -13559,6 +13572,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -13609,6 +13623,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -13621,6 +13636,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13629,6 +13645,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -13838,14 +13855,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -13860,9 +13878,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -14135,9 +14154,10 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -14164,33 +14184,34 @@ } }, "node_modules/webpack": { - "version": "5.90.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", - "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.21.10", + "version": "5.99.9", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", + "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -14244,9 +14265,10 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -14269,6 +14291,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -14277,6 +14300,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -14288,14 +14312,16 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -14325,7 +14351,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -14351,9 +14377,10 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -14391,34 +14418,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, "node_modules/webpack/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -14438,23 +14437,6 @@ "node": ">= 0.6" } }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/webpackbar": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", @@ -14595,9 +14577,10 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { "node": ">=8.3.0" }, diff --git a/examples/gcp-assistant.gpt b/examples/gcp-assistant.gpt new file mode 100644 index 00000000..29b682d0 --- /dev/null +++ b/examples/gcp-assistant.gpt @@ -0,0 +1,28 @@ +Name: GCP Assistant +Description: Agent to help you interact with Google Cloud +Context: learn-gcp, learn-kubectl +Tools: sys.exec, sys.http.html2text?, sys.find, sys.read, sys.write +Chat:true +You are an assistant for Google Cloud Platform (GCP). +Rules +1. Use gcloud CLI to interact with GCP. +2. Assume the user is using Google cloud. + +--- +Name: learn-gcp +Description: A tool to help you learn gcp cli +#!/bin/bash +echo "Current gcloud config:" +gcloud config list || true +--- +Name: learn-kubectl +Description: A tool to help you learn k8s and related commands +#!/bin/bash + +CMDS="kubectl helm" +echo 'The additional CLI commands are available locally, use the `exec` tool to invoke them:' +for i in $CMDS; do + if [ -e "$(command -v $i)" ]; then + echo ' ' $i + fi +done diff --git a/examples/gptreview-ghaction/README.md b/examples/gptreview-ghaction/README.md new file mode 100644 index 00000000..fe793a92 --- /dev/null +++ b/examples/gptreview-ghaction/README.md @@ -0,0 +1,21 @@ +# GPTReview + +This folder contains an example of building and implementing your own code reviewer as part of GitHub Actions. + +Below are the files present here: + +- `codereview.gpt`: Contains the GPTScript code and prompts. +- `workflow.yaml`: The workflow file for the GitHub action. + +## Pre-requisites + +- GitHub Account +- OpenAI API Key + +## How To Run This Example + +- Create a new repository in your GitHub account and create a `codereview.gpt` file in the root of that repo based on the contents provided in this file. +- Congfigure a GitHub Action for that repository. To do so, navigate to the "Actions" tab and then click on "setup a workflow yourself" link. This will create a new `main.yaml` inside `.github/workflows` path. Copy the contents from `workflow.yaml` to your `main.yaml`. +- Configure your `OPENAI_API_KEY` and `GH_TOKEN` as environment variables in your GitHub repo. Refer to [these steps](https://docs.github.com/en/actions/learn-github-actions/variables#creating-configuration-variables-for-a-repository) to create environment variables for your repository. +- Create a new branch, and add some code file to the repository and open a new pull request. +- The GitHub Action will trigger and our GPTReview will review your code and provide review comments. diff --git a/examples/gptreview-ghaction/codereview.gpt b/examples/gptreview-ghaction/codereview.gpt new file mode 100644 index 00000000..f2502b50 --- /dev/null +++ b/examples/gptreview-ghaction/codereview.gpt @@ -0,0 +1,26 @@ +Name: Code Reviewer +Description: A tool to help you perform code review of open PRs +Context: learn-gh +Tools: sys.exec, sys.http.html2text?, sys.find, sys.read, sys.write +Args: PR_URL: The GitHub PR_URL + +You have the gh cli available to you. Use it to perform code review for a pr from the $(repo) provided. + +Perform the following steps in order: +1. Identify the files changed in the pull request ($PR_URL) using the pr number and perform a diff. + 1. Analyze the complete code of each identified file and perform a detailed line by line code review. + 2. Repeat the process for each changed file in the pr. +2. Share your review comments separately for each file. +3. In a new line write "Code: Approved" or "Code: Require Changes" based on the review comments. +--- +Name: learn-gh +Description: A tool to help you learn gh cli + +#!/usr/bin/env bash + +echo "The following is the help text for the gh cli and some of its sub-commands. Use these when figuring out how to construct new commands. Note that the --search flag is used for filtering and sorting as well; there is no dedicate --sort flag." +gh --help +gh repo --help +gh pr --help +gh pr checkout --help +gh pr diff --help diff --git a/examples/gptreview-ghaction/workflow.yaml b/examples/gptreview-ghaction/workflow.yaml new file mode 100644 index 00000000..21ade39b --- /dev/null +++ b/examples/gptreview-ghaction/workflow.yaml @@ -0,0 +1,57 @@ +name: PR Review with GPTScript + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + pr_review: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Get PR Details + id: pr_details + run: | + PR_URL=$(jq -r '.pull_request.html_url' $GITHUB_EVENT_PATH) + PR_NUMBER=$(jq -r '.pull_request.number' $GITHUB_EVENT_PATH) + PR_FILES=$(jq -r '.pull_request.changed_files' $GITHUB_EVENT_PATH) + echo "PR_URL=${PR_URL}" >> $GITHUB_ENV + echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_ENV + echo "PR_FILES=${PR_FILES}" >> $GITHUB_ENV + + - name: Install GPTScript + run: curl https://get.gptscript.ai/install.sh | sh + + - name: Run GPTScript for Code Review + id: run_gptscript + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + { + echo 'REVIEW<> "$GITHUB_ENV" + + + - name: Post Review Comment + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + gh pr comment ${{ github.event.pull_request.number }} --body "$REVIEW" + + - name: Set PR Status Fail + if: contains(env.REVIEW, 'Require Changes') + run: | + echo "Code Requires Changes" + exit 1 + + - name: Set PR Status Pass + if: contains(env.REVIEW, 'Approved') + run: | + echo "Code Approved" + diff --git a/examples/gptreview-jenkins/Jenkinsfile b/examples/gptreview-jenkins/Jenkinsfile new file mode 100644 index 00000000..b65780d7 --- /dev/null +++ b/examples/gptreview-jenkins/Jenkinsfile @@ -0,0 +1,56 @@ +pipeline { + agent any + + stages { + stage('Clean Workspace') { + steps { + deleteDir() + } + } + + stage('GPT Review') { + steps { + script { + checkout([ + $class: 'GitSCM', + branches: [[name: '*/main']], // Specify branch + userRemoteConfigs: [[ + url: '' // Provide the URL for your repo that has the codereview.gpt file. + ]] + ]) + + withCredentials([string(credentialsId: 'OPENAI_API_KEY', variable: 'OPENAI_API_KEY')]){ + withCredentials([string(credentialsId: 'GH_TOKEN', variable: 'GH_TOKEN')]) { + // GPTSCript reviews the code + REVIEW = sh(script: "gptscript codereview.gpt --PR_URL=${PR_URL}", returnStdout: true).trim() + + // Construct the JSON payload using Groovy's JSON library + def jsonPayload = groovy.json.JsonOutput.toJson([body: REVIEW]) + + // Post the review comment to the GitHub PR + sh "curl -H \"Authorization: token ${GH_TOKEN}\" -H \"Content-Type: application/json\" -X POST -d '${jsonPayload}' '${PR_COMMENTS_URL}'" + } + } + } + } + } + + stage('Check PR Status') { + steps { + script { + // Check if REVIEW contains 'Require Changes' + if (REVIEW.contains('Require Changes')) { + echo 'Code Requires Changes' + currentBuild.result = 'FAILURE' // Mark the build as failed + error 'Code Requires Changes' // Terminate the build with an error + } + + // Check if REVIEW contains 'Approved' + if (REVIEW.contains('Approved')) { + echo 'Code Approved' + } + } + } + } + } +} \ No newline at end of file diff --git a/examples/gptreview-jenkins/README.md b/examples/gptreview-jenkins/README.md new file mode 100644 index 00000000..60fdb663 --- /dev/null +++ b/examples/gptreview-jenkins/README.md @@ -0,0 +1,31 @@ +# GPTReview With Jenkins + +This folder contains an example of building and implementing your own code reviewer as part of Jenkins Pipeline. + +Below are the files present here: + +- `codereview.gpt`: Contains the GPTScript code and prompts. +- `Jenkinsfile`: Jenkins pipeline file. + +## Pre-requisites + +- An OpenAI API Key. +- GitHub repository. +- Jenkins. +- [GPTScript](https://github.com/gptscript-ai/gptscript) and [GH](https://github.com/cli/cli) CLI installed on the system running Jenkins. + +## How To Run This Example + +- Create a new repository in your GitHub account and create a `codereview.gpt` file in the root of that repo based on the contents provided in this file. +- Configure Jenkins: + - Install required plugins - [GitHub](https://plugins.jenkins.io/github/), [Generic Webhook Trigger Plugin](https://plugins.jenkins.io/generic-webhook-trigger/) & [HTTP Request Plugin](https://plugins.jenkins.io/http_request/). + - Create a Pipeline + - Configure the “Open_AI_API” and “GH_TOKEN” environment variables + +- Congfigure GitHub: + - Setup up Webhook by providing your Jenkins pipeline URL: `http:///generic-webhook-trigger/invoke?token=` + - Add `Jenkinsfile` in the root of the repo. *Note: Replace the repository URL with your repo URL in the Jenkinsfile provided.* + +- Executing the Script: + - Create a new branch, and add some code file to the repository and open a new pull request. + - The Jenkins pipeline will trigger and our GPTReview will review your code and provide review comments. diff --git a/examples/gptreview-jenkins/codereview.gpt b/examples/gptreview-jenkins/codereview.gpt new file mode 100644 index 00000000..f2502b50 --- /dev/null +++ b/examples/gptreview-jenkins/codereview.gpt @@ -0,0 +1,26 @@ +Name: Code Reviewer +Description: A tool to help you perform code review of open PRs +Context: learn-gh +Tools: sys.exec, sys.http.html2text?, sys.find, sys.read, sys.write +Args: PR_URL: The GitHub PR_URL + +You have the gh cli available to you. Use it to perform code review for a pr from the $(repo) provided. + +Perform the following steps in order: +1. Identify the files changed in the pull request ($PR_URL) using the pr number and perform a diff. + 1. Analyze the complete code of each identified file and perform a detailed line by line code review. + 2. Repeat the process for each changed file in the pr. +2. Share your review comments separately for each file. +3. In a new line write "Code: Approved" or "Code: Require Changes" based on the review comments. +--- +Name: learn-gh +Description: A tool to help you learn gh cli + +#!/usr/bin/env bash + +echo "The following is the help text for the gh cli and some of its sub-commands. Use these when figuring out how to construct new commands. Note that the --search flag is used for filtering and sorting as well; there is no dedicate --sort flag." +gh --help +gh repo --help +gh pr --help +gh pr checkout --help +gh pr diff --help diff --git a/examples/gptreview.gpt b/examples/gptreview.gpt new file mode 100644 index 00000000..2176c89a --- /dev/null +++ b/examples/gptreview.gpt @@ -0,0 +1,28 @@ + Name: Code Reviewer + Description: A tool to help you perform code review of open PRs + Context: learn-gh + Tools: sys.exec, sys.http.html2text?, sys.find, sys.read, sys.write + Args: PR_URL: The GitHub PR_URL + chat:true + + You have the gh cli available to you. Use it to perform code review for a pr. + + Perform the following steps in order: + 1. Ask the user for the ($PR_URL) and save it. + 2. Identify the files changed in the pull request ($PR_URL) using the pr number and perform a diff. + 1. Analyze the complete code of each identified file and perform a detailed line by line code review. + 2. Repeat the process for each changed file in the pr. + 3. Share your review comments separately for each file. + 4. In a new line write "Code: Approved" or "Code: Require Changes" based on the review. + --- + Name: learn-gh + Description: A tool to help you learn gh cli + + #!/usr/bin/env bash + + echo "The following is the help text for the gh cli and some of its sub-commands. Use these when figuring out how to construct new commands. Note that the --search flag is used for filtering and sorting as well; there is no dedicated --sort flag." + gh --help + gh repo --help + gh pr --help + gh pr checkout --help + gh pr diff --help \ No newline at end of file diff --git a/examples/testkube.gpt b/examples/testkube.gpt new file mode 100644 index 00000000..758d9b34 --- /dev/null +++ b/examples/testkube.gpt @@ -0,0 +1,38 @@ +Name: Testkube +Description: A tool to help you perform testing of your application on your Kubernetes clusters using Testkube. +Context: learn-testkube, learn-kubectl +Tools: sys.exec, sys.http.html2text?, sys.find, sys.read, sys.write, github.com/gptscript-ai/browse-web-page +chat:true + +You are an assistant for Testkube and help the user create, manage and execute test workflows. You can also perform kubernetes related tasks. + +Rules +1. Access the testkube workflow docs at https://docs.testkube.io/articles/test-workflows and remember the latest specification to create testworkflows. +2. Use testkube CLI to interact with Testkube. +3. Use kubectl CLI to interact with the Kubernetes cluster. +4. Based on the user's request, perform actions on the Kubernetes cluster and create, manage, delete test workflows. + + +--- + +Name: learn-testkube +Description: A tool to help you learn testkube cli +#!/bin/bash +testkube --help +testkube create --help +testkube create testworkflow --help +testkube run --help + +--- + +Name: learn-kubectl +Description: A tool to help you learn k8s and related commands +#!/bin/bash + +CMDS="kubectl helm" +echo 'The additional CLI commands are available locally, use the `exec` tool to invoke them:' +for i in $CMDS; do + if [ -e "$(command -v $i)" ]; then + echo ' ' $i + fi +done diff --git a/go.mod b/go.mod index 868dc060..a0e0ec0a 100644 --- a/go.mod +++ b/go.mod @@ -1,36 +1,43 @@ module github.com/gptscript-ai/gptscript -go 1.22.3 +go 1.25.3 require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 - github.com/adrg/xdg v0.4.0 + github.com/adrg/xdg v0.5.3 github.com/chzyer/readline v1.5.1 github.com/docker/cli v26.0.0+incompatible github.com/docker/docker-credential-helpers v0.8.1 - github.com/fatih/color v1.17.0 - github.com/getkin/kin-openapi v0.124.0 + github.com/fatih/color v1.18.0 + github.com/getkin/kin-openapi v0.132.0 + github.com/go-git/go-git/v5 v5.16.3 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 github.com/gptscript-ai/broadcaster v0.0.0-20240625175512-c43682019b86 - github.com/gptscript-ai/chat-completion-client v0.0.0-20240531200700-af8e7ecf0379 - github.com/gptscript-ai/cmd v0.0.0-20240625175447-4250b42feb7d - github.com/gptscript-ai/tui v0.0.0-20240627044440-d416df63c10d - github.com/hexops/autogold/v2 v2.2.1 - github.com/hexops/valast v1.4.4 + github.com/gptscript-ai/chat-completion-client v0.0.0-20250224164718-139cb4507b1d + github.com/gptscript-ai/cmd v0.0.0-20250530150401-bc71fddf8070 + github.com/gptscript-ai/go-gptscript v0.9.6-0.20250714170123-17ad44ae8c54 + github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9 + github.com/hexops/autogold/v2 v2.3.0 + github.com/hexops/valast v1.5.0 github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 - github.com/mholt/archiver/v4 v4.0.0-alpha.8 + github.com/mholt/archives v0.1.5 + github.com/modelcontextprotocol/go-sdk v0.2.0 + github.com/nanobot-ai/nanobot v0.0.6-0.20250825141756-f61b8b0f41f8 + github.com/pkoukk/tiktoken-go v0.1.7 + github.com/pkoukk/tiktoken-go-loader v0.0.2-0.20240522064338-c17e8bc0f699 github.com/rs/cors v1.11.0 github.com/samber/lo v1.38.1 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.8.0 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.4 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 + github.com/stretchr/testify v1.10.0 github.com/tidwall/gjson v1.17.1 - golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc - golang.org/x/sync v0.7.0 - golang.org/x/term v0.20.0 + github.com/xeipuuv/gojsonschema v1.2.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/sync v0.17.0 + golang.org/x/term v0.34.0 gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.5.1 sigs.k8s.io/yaml v1.4.0 @@ -40,74 +47,101 @@ require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/STARRY-S/zip v0.2.3 // indirect github.com/alecthomas/chroma/v2 v2.8.0 // indirect - github.com/andybalholm/brotli v1.0.4 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/bodgit/plumbing v1.2.0 // indirect - github.com/bodgit/sevenzip v1.3.0 // indirect - github.com/bodgit/windows v1.0.0 // indirect + github.com/bodgit/plumbing v1.3.0 // indirect + github.com/bodgit/sevenzip v1.6.1 // indirect + github.com/bodgit/windows v1.0.1 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/glamour v0.7.0 // indirect - github.com/charmbracelet/lipgloss v0.11.0 // indirect - github.com/charmbracelet/x/ansi v0.1.1 // indirect - github.com/connesc/cipherio v0.2.1 // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/containerd/console v1.0.4 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/creack/pty v1.1.24 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/dsnet/compress v0.0.1 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/swag v0.22.8 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/dop251/goja v0.0.0-20250531102226-cb187b08699c // indirect + github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/gookit/color v1.5.4 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/gptscript-ai/go-gptscript v0.0.0-20240625134437-4b83849794cc // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hexops/autogold v1.3.1 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/invopop/yaml v0.2.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.16.5 // indirect - github.com/klauspost/pgzip v1.2.5 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/mikelolasagasti/xz v1.0.1 // indirect + github.com/minio/minlz v1.0.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/nightlyone/lockfile v1.0.0 // indirect - github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect + github.com/nwaples/rardecode/v2 v2.2.0 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pierrec/lz4/v4 v4.1.15 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pterm/pterm v0.12.79 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/sourcegraph/go-diff-patch v0.0.0-20240223163233-798fd1e94a8e // indirect + github.com/spf13/afero v1.15.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect - github.com/therootcompany/xz v1.0.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect - github.com/ulikunitz/xz v0.5.10 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.5.4 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect - go4.org v0.0.0-20200411211856-f5505b9728dd // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.20.0 // indirect - mvdan.cc/gofumpt v0.6.0 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + mvdan.cc/gofumpt v0.8.0 // indirect ) diff --git a/go.sum b/go.sum index 8a6d9dc8..6b6dc8e1 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= @@ -38,36 +40,55 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= +github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264= github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= -github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= -github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= -github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM= -github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA= -github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= +github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= +github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4= +github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8= +github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= +github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= -github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g= -github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8= -github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk= -github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -78,64 +99,81 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= -github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUyzJVPxD30I= github.com/docker/cli v26.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= -github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dop251/goja v0.0.0-20250531102226-cb187b08699c h1:In87uFQZsuGfjDDNfWnzMVY6JVTwc8XYMl6W2DAmNjk= +github.com/dop251/goja v0.0.0-20250531102226-cb187b08699c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= -github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= +github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= +github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= -github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= -github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -143,15 +181,18 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -167,49 +208,49 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gptscript-ai/broadcaster v0.0.0-20240625175512-c43682019b86 h1:m9yLtIEd0z1ia8qFjq3u0Ozb6QKwidyL856JLJp6nbA= github.com/gptscript-ai/broadcaster v0.0.0-20240625175512-c43682019b86/go.mod h1:lK3K5EZx4dyT24UG3yCt0wmspkYqrj4D/8kxdN3relk= -github.com/gptscript-ai/chat-completion-client v0.0.0-20240531200700-af8e7ecf0379 h1:vYnXoIyCXzaCEw0sYifQ4bDpsv3/fO/dZ2suEsTwCIo= -github.com/gptscript-ai/chat-completion-client v0.0.0-20240531200700-af8e7ecf0379/go.mod h1:7P/o6/IWa1KqsntVf68hSnLKuu3+xuqm6lYhch1w4jo= -github.com/gptscript-ai/cmd v0.0.0-20240625175447-4250b42feb7d h1:sKf7T7twhGXs6AVbvD9pKDVewykkwSAPwEpmIEQIR/4= -github.com/gptscript-ai/cmd v0.0.0-20240625175447-4250b42feb7d/go.mod h1:DJAo1xTht1LDkNYFNydVjTHd576TC7MlpsVRl3oloVw= -github.com/gptscript-ai/go-gptscript v0.0.0-20240625134437-4b83849794cc h1:ABV7VAK65YBkqL7VlNp5ryVXnRqkKQ+U/NZfUO3ypqA= -github.com/gptscript-ai/go-gptscript v0.0.0-20240625134437-4b83849794cc/go.mod h1:Dh6vYRAiVcyC3ElZIGzTvNF1FxtYwA07BHfSiFKQY7s= -github.com/gptscript-ai/tui v0.0.0-20240627044440-d416df63c10d h1:hbJ5rkwMDDntqbvHMbsEoP8Nsa5nqTOzF+ktkw3uDQQ= -github.com/gptscript-ai/tui v0.0.0-20240627044440-d416df63c10d/go.mod h1:NwFdBDmGQvjLFFDnSRBRakkhw0MIO1sSdRnWNk4cCQ0= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/gptscript-ai/chat-completion-client v0.0.0-20250224164718-139cb4507b1d h1:p5uqZufDIMQzAALblZFkr8fwbnZbFXbBCR1ZMAFylXk= +github.com/gptscript-ai/chat-completion-client v0.0.0-20250224164718-139cb4507b1d/go.mod h1:7P/o6/IWa1KqsntVf68hSnLKuu3+xuqm6lYhch1w4jo= +github.com/gptscript-ai/cmd v0.0.0-20250530150401-bc71fddf8070 h1:xm5ZZFraWFwxyE7TBEncCXArubCDZTwG6s5bpMzqhSY= +github.com/gptscript-ai/cmd v0.0.0-20250530150401-bc71fddf8070/go.mod h1:DJAo1xTht1LDkNYFNydVjTHd576TC7MlpsVRl3oloVw= +github.com/gptscript-ai/go-gptscript v0.9.6-0.20250714170123-17ad44ae8c54 h1:9OAiDBdOQUHVL89wmb38+/XOuewboMhgnk6NqoJiC00= +github.com/gptscript-ai/go-gptscript v0.9.6-0.20250714170123-17ad44ae8c54/go.mod h1:HLPvKBhDtsEkyyUWefJVhPpl98R3tZG6ps7+mQ+EKVI= +github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9 h1:wQC8sKyeGA50WnCEG+Jo5FNRIkuX3HX8d3ubyWCCoI8= +github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9/go.mod h1:iwHxuueg2paOak7zIg0ESBWx7A0wIHGopAratbgaPNY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hexops/autogold v0.8.1/go.mod h1:97HLDXyG23akzAoRYJh/2OBs3kd80eHyKPvZw0S5ZBY= github.com/hexops/autogold v1.3.1 h1:YgxF9OHWbEIUjhDbpnLhgVsjUDsiHDTyDfy2lrfdlzo= github.com/hexops/autogold v1.3.1/go.mod h1:sQO+mQUCVfxOKPht+ipDSkJ2SCJ7BNJVHZexsXqWMx4= -github.com/hexops/autogold/v2 v2.2.1 h1:JPUXuZQGkcQMv7eeDXuNMovjfoRYaa0yVcm+F3voaGY= -github.com/hexops/autogold/v2 v2.2.1/go.mod h1:IJwxtUfj1BGLm0YsR/k+dIxYi6xbeLjqGke2bzcOTMI= +github.com/hexops/autogold/v2 v2.3.0 h1:tObVFzC7WDIF2tT80Bo9p42mXlkqcyLKmIMghcjoTWE= +github.com/hexops/autogold/v2 v2.3.0/go.mod h1:e77HQw5vjubldctJpHjjDHr7KHUmrFc5KrWKFFieO7Q= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hexops/valast v1.4.3/go.mod h1:Iqx2kLj3Jn47wuXpj3wX40xn6F93QNFBHuiKBerkTGA= -github.com/hexops/valast v1.4.4 h1:rETyycw+/L2ZVJHHNxEBgh8KUn+87WugH9MxcEv9PGs= -github.com/hexops/valast v1.4.4/go.mod h1:Jcy1pNH7LNraVaAZDLyv21hHg2WBv9Nf9FL6fGxU7o4= +github.com/hexops/valast v1.5.0 h1:FBTuvVi0wjTngtXJRZXMbkN/Dn6DgsUsBwch2DUJU8Y= +github.com/hexops/valast v1.5.0/go.mod h1:Jcy1pNH7LNraVaAZDLyv21hHg2WBv9Nf9FL6fGxU7o4= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= -github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -217,8 +258,8 @@ github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -235,46 +276,65 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= -github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= +github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ= +github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= +github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= +github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= +github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= +github.com/modelcontextprotocol/go-sdk v0.2.0 h1:PESNYOmyM1c369tRkzXLY5hHrazj8x9CY1Xu0fLCryM= +github.com/modelcontextprotocol/go-sdk v0.2.0/go.mod h1:0sL9zUKKs2FTTkeCCVnKqbLJTw5TScefPAzojjU459E= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/nanobot-ai/nanobot v0.0.6-0.20250825141756-f61b8b0f41f8 h1:SZsity7OCSBRVnqfPMpmaSnaIFlMUm3z8sGED5C31XU= +github.com/nanobot-ai/nanobot v0.0.6-0.20250825141756-f61b8b0f41f8/go.mod h1:vKoxU5Fro4DuvHq2AsxjhNYF3/KRlAuHLFT+NZ9ns5w= github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= -github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk= -github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= +github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A= +github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77 h1:3bMMZ1f+GPXFQ1uNaYbO/uECWvSfqEA+ZEXn1rFAT88= github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77/go.mod h1:8Hf+pH6thup1sPZPD+NLg7d6vbpsdilu9CPIeikvgMQ= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkoukk/tiktoken-go v0.1.7 h1:qOBHXX4PHtvIvmOtyg1EeKlwFRiMKAcoMp4Q+bLQDmw= +github.com/pkoukk/tiktoken-go v0.1.7/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= +github.com/pkoukk/tiktoken-go-loader v0.0.2-0.20240522064338-c17e8bc0f699 h1:Sp8yiuxsitkmCfEvUnmNf8wzuZwlGNkRjI2yF0C3QUQ= +github.com/pkoukk/tiktoken-go-loader v0.0.2-0.20240522064338-c17e8bc0f699/go.mod h1:4mIkYyZooFlnenDlormIo6cd5wrlUKNr97wp9nGgEKo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -294,9 +354,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -304,27 +363,41 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= +github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= github.com/sourcegraph/go-diff-patch v0.0.0-20240223163233-798fd1e94a8e h1:H+jDTUeF+SVd4ApwnSFoew8ZwGNRfgb9EsZc7LcocAg= github.com/sourcegraph/go-diff-patch v0.0.0-20240223163233-798fd1e94a8e/go.mod h1:VsUklG6OQo7Ctunu0gS3AtEOCEc2kMB6r5rKzxAes58= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= -github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -333,12 +406,22 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= @@ -349,14 +432,16 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= -go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -365,8 +450,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -387,10 +472,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -404,21 +487,21 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -428,9 +511,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -441,6 +523,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -449,10 +532,10 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -464,35 +547,29 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -522,10 +599,8 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -568,11 +643,14 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= @@ -583,9 +661,8 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= -mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= -mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= -mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= +mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k= +mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/integration/cred_test.go b/integration/cred_test.go new file mode 100644 index 00000000..a32e50e9 --- /dev/null +++ b/integration/cred_test.go @@ -0,0 +1,85 @@ +package integration + +import ( + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestGPTScriptCredential(t *testing.T) { + out, err := GPTScriptExec("cred") + require.NoError(t, err) + require.Contains(t, out, "CREDENTIAL") +} + +// TestCredentialExpirationEnv tests a GPTScript with two credentials that expire at different times. +// One expires after two hours, and the other expires after one hour. +// This test makes sure that the GPTSCRIPT_CREDENTIAL_EXPIRATION environment variable is set to the nearer expiration time (1h). +func TestCredentialExpirationEnv(t *testing.T) { + out, err := RunScript("scripts/cred_expiration.gpt") + require.NoError(t, err) + + for _, line := range strings.Split(out, "\n") { + if timestamp, found := strings.CutPrefix(line, "Expires: "); found { + expiresTime, err := time.Parse(time.RFC3339, timestamp) + require.NoError(t, err) + require.True(t, time.Until(expiresTime) < time.Hour) + } + } +} + +// TestStackedCredentialContexts tests creating, using, listing, showing, and deleting credentials when there are multiple contexts. +func TestStackedCredentialContexts(t *testing.T) { + // First, test credential creation. We will create a credential called testcred in two different contexts called one and two. + _, err := RunScript("scripts/cred_stacked.gpt", "--sub-tool", "testcred_one", "--credential-context", "one,two") + require.NoError(t, err) + + _, err = RunScript("scripts/cred_stacked.gpt", "--sub-tool", "testcred_two", "--credential-context", "two") + require.NoError(t, err) + + // Next, we try running the testcred_one tool. It should print the value of "testcred" in whichever context it finds the cred first. + out, err := RunScript("scripts/cred_stacked.gpt", "--sub-tool", "testcred_one", "--credential-context", "one,two") + require.NoError(t, err) + require.Contains(t, out, "one") + require.NotContains(t, out, "two") + + out, err = RunScript("scripts/cred_stacked.gpt", "--sub-tool", "testcred_one", "--credential-context", "two,one") + require.NoError(t, err) + require.Contains(t, out, "two") + require.NotContains(t, out, "one") + + // Next, list credentials and specify both contexts. We should get the credential from the first specified context. + out, err = GPTScriptExec("--credential-context", "one,two", "cred") + require.NoError(t, err) + require.Contains(t, out, "one") + require.NotContains(t, out, "two") + + out, err = GPTScriptExec("--credential-context", "two,one", "cred") + require.NoError(t, err) + require.Contains(t, out, "two") + require.NotContains(t, out, "one") + + // Next, try showing the credentials. + out, err = GPTScriptExec("--credential-context", "one,two", "cred", "show", "testcred") + require.NoError(t, err) + require.Contains(t, out, "one") + require.NotContains(t, out, "two") + + out, err = GPTScriptExec("--credential-context", "two,one", "cred", "show", "testcred") + require.NoError(t, err) + require.Contains(t, out, "two") + require.NotContains(t, out, "one") + + // Make sure we get an error if we try to delete a credential with multiple contexts specified. + _, err = GPTScriptExec("--credential-context", "one,two", "cred", "delete", "testcred") + require.Error(t, err) + + // Now actually delete the credentials. + _, err = GPTScriptExec("--credential-context", "one", "cred", "delete", "testcred") + require.NoError(t, err) + + _, err = GPTScriptExec("--credential-context", "two", "cred", "delete", "testcred") + require.NoError(t, err) +} diff --git a/integration/helpers.go b/integration/helpers.go new file mode 100644 index 00000000..33304676 --- /dev/null +++ b/integration/helpers.go @@ -0,0 +1,20 @@ +package integration + +import ( + "os/exec" + "runtime" +) + +func GPTScriptExec(args ...string) (string, error) { + cmd := exec.Command("../bin/gptscript", args...) + if runtime.GOOS == "windows" { + cmd = exec.Command("..\\bin\\gptscript.exe", args...) + } + + out, err := cmd.CombinedOutput() + return string(out), err +} + +func RunScript(script string, options ...string) (string, error) { + return GPTScriptExec(append(options, "--quiet", script)...) +} diff --git a/integration/scripts/cred_expiration.gpt b/integration/scripts/cred_expiration.gpt new file mode 100644 index 00000000..da535df0 --- /dev/null +++ b/integration/scripts/cred_expiration.gpt @@ -0,0 +1,46 @@ +cred: credentialTool with 2 as hours +cred: credentialTool with 1 as hours + +#!python3 + +import os + +print("Expires: " + os.getenv("GPTSCRIPT_CREDENTIAL_EXPIRATION", ""), end="") + +--- +name: credentialTool +args: hours: the number of hours from now to expire + +#!python3 + +import os +import json +from datetime import datetime, timedelta, timezone + +class Output: + def __init__(self, env, expires_at): + self.env = env + self.expiresAt = expires_at + + def to_dict(self): + return { + "env": self.env, + "expiresAt": self.expiresAt.isoformat() + } + +hours_str = os.getenv("HOURS") +if hours_str is None: + print("HOURS environment variable is not set") + os._exit(1) + +try: + hours = int(hours_str) +except ValueError: + print("failed to parse HOURS") + os._exit(1) + +expires_at = datetime.now(timezone.utc) + timedelta(hours=hours) +out = Output(env={"yeet": "yote"}, expires_at=expires_at) +out_json = json.dumps(out.to_dict()) + +print(out_json) diff --git a/integration/scripts/cred_stacked.gpt b/integration/scripts/cred_stacked.gpt new file mode 100644 index 00000000..1072ca7b --- /dev/null +++ b/integration/scripts/cred_stacked.gpt @@ -0,0 +1,36 @@ +name: testcred_one +credential: cred_one as testcred + +#!python3 + +import os + +print(os.environ.get("VALUE")) + +--- +name: testcred_two +credential: cred_two as testcred + +#!python3 + +import os + +print(os.environ.get("VALUE")) + +--- +name: cred_one + +#!python3 + +import json + +print(json.dumps({"env": {"VALUE": "one"}})) + +--- +name: cred_two + +#!python3 + +import json + +print(json.dumps({"env": {"VALUE": "two"}})) diff --git a/main.go b/main.go index 33ab4278..02923925 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,6 @@ package main import ( "github.com/gptscript-ai/gptscript/pkg/cli" - // Load all VCS - _ "github.com/gptscript-ai/gptscript/pkg/loader/vcs" ) func main() { diff --git a/pkg/assemble/assemble.go b/pkg/assemble/assemble.go deleted file mode 100644 index ad44d1dd..00000000 --- a/pkg/assemble/assemble.go +++ /dev/null @@ -1,17 +0,0 @@ -package assemble - -import ( - "encoding/json" - "io" - - "github.com/gptscript-ai/gptscript/pkg/types" -) - -var Header = []byte("GPTSCRIPT!") - -func Assemble(prg types.Program, output io.Writer) error { - if _, err := output.Write(Header); err != nil { - return err - } - return json.NewEncoder(output).Encode(prg) -} diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 5c818902..0a664b4d 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -23,7 +23,7 @@ func Authorize(ctx engine.Context, input string) (runner.AuthorizerResponse, err var result bool err := survey.AskOne(&survey.Confirm{ - Help: fmt.Sprintf("The full source of the tools is as follows:\n\n%s", ctx.Tool.String()), + Help: fmt.Sprintf("The full source of the tools is as follows:\n\n%s", ctx.Tool.Print()), Default: true, Message: ConfirmMessage(ctx, input), }, &result) diff --git a/pkg/builtin/builtin.go b/pkg/builtin/builtin.go index f6811549..b339b52a 100644 --- a/pkg/builtin/builtin.go +++ b/pkg/builtin/builtin.go @@ -26,14 +26,15 @@ import ( ) var SafeTools = map[string]struct{}{ - "sys.abort": {}, - "sys.chat.finish": {}, - "sys.chat.history": {}, - "sys.chat.current": {}, - "sys.echo": {}, - "sys.prompt": {}, - "sys.time.now": {}, - "sys.context": {}, + "sys.abort": {}, + "sys.chat.finish": {}, + "sys.chat.history": {}, + "sys.chat.current": {}, + "sys.echo": {}, + "sys.prompt": {}, + "sys.time.now": {}, + "sys.context": {}, + "sys.model.provider.credential": {}, } var tools = map[string]types.Tool{ @@ -58,7 +59,7 @@ var tools = map[string]types.Tool{ "sys.read": { ToolDef: types.ToolDef{ Parameters: types.Parameters{ - Description: "Reads the contents of a file", + Description: "Reads the contents of a file. Can only read plain text files, not binary files", Arguments: types.ObjectSchema( "filename", "The name of the file to read"), }, @@ -216,6 +217,7 @@ var tools = map[string]types.Tool{ "message", "The message to display to the user", "fields", "A comma-separated list of fields to prompt for", "sensitive", "(true or false) Whether the input should be hidden", + "metadata", "(optional) A JSON object of metadata to attach to the prompt", ), }, BuiltinFunc: prompt.SysPrompt, @@ -248,6 +250,15 @@ var tools = map[string]types.Tool{ BuiltinFunc: SysContext, }, }, + "sys.model.provider.credential": { + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Description: "A credential tool to set the OPENAI_API_KEY and OPENAI_BASE_URL to give access to the default model provider", + Arguments: types.ObjectSchema(), + }, + BuiltinFunc: SysModelProviderCredential, + }, + }, } func ListTools() (result []types.Tool) { @@ -258,18 +269,19 @@ func ListTools() (result []types.Tool) { sort.Strings(keys) for _, key := range keys { - t, _ := Builtin(key) + t, _ := DefaultModel(key, "") result = append(result, t) } return } -func Builtin(name string) (types.Tool, bool) { +func DefaultModel(name, defaultModel string) (types.Tool, bool) { // Legacy syntax not used anymore name = strings.TrimSuffix(name, "?") t, ok := tools[name] - t.Parameters.Name = name + t.Name = name + t.ModelName = defaultModel t.ID = name t.Instructions = "#!" + name return SetDefaults(t), ok @@ -316,7 +328,7 @@ func SysFind(_ context.Context, _ []string, input string, _ chan<- string) (stri return strings.Join(result, "\n"), nil } -func SysExec(_ context.Context, env []string, input string, progress chan<- string) (string, error) { +func SysExec(ctx context.Context, env []string, input string, progress chan<- string) (string, error) { var params struct { Command string `json:"command,omitempty"` Directory string `json:"directory,omitempty"` @@ -329,14 +341,20 @@ func SysExec(_ context.Context, env []string, input string, progress chan<- stri params.Directory = "." } + commandCtx, _ := engine.FromContext(ctx) + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + commandCtx.OnUserCancel(ctx, cancel) + log.Debugf("Running %s in %s", params.Command, params.Directory) var cmd *exec.Cmd - if runtime.GOOS == "windows" { - cmd = exec.Command("cmd.exe", "/c", params.Command) + cmd = exec.CommandContext(ctx, "cmd.exe", "/c", params.Command) } else { - cmd = exec.Command("/bin/sh", "-c", params.Command) + cmd = exec.CommandContext(ctx, "/bin/sh", "-c", params.Command) } var ( @@ -355,7 +373,8 @@ func SysExec(_ context.Context, env []string, input string, progress chan<- stri cmd.Dir = params.Directory cmd.Stdout = combined cmd.Stderr = combined - if err := cmd.Run(); err != nil { + if err := cmd.Run(); err != nil && (ctx.Err() == nil || commandCtx.Ctx.Err() != nil) { + // If the command failed and the context hasn't been canceled, then return the error. return fmt.Sprintf("ERROR: %s\nOUTPUT:\n%s", err, &out), nil } return out.String(), nil @@ -404,7 +423,6 @@ func getWorkspaceEnvFileContents(envs []string) ([]string, error) { } return envContents, nil - } func getWorkspaceDir(envs []string) (string, error) { @@ -478,6 +496,12 @@ func SysRead(_ context.Context, _ []string, input string, _ chan<- string) (stri if len(data) == 0 { return fmt.Sprintf("The file %s has no contents", params.Filename), nil } + + // Assume the file is not text if it contains a null byte + if bytes.IndexByte(data, 0) != -1 { + return fmt.Sprintf("The file %s cannot be read because it is not a plaintext file", params.Filename), nil + } + return string(data), nil } @@ -643,6 +667,7 @@ func DiscardProgress() (progress chan<- string, closeFunc func()) { ch := make(chan string) go func() { for range ch { + continue } }() return ch, func() { @@ -678,6 +703,22 @@ func invalidArgument(input string, err error) string { return fmt.Sprintf("Failed to parse arguments %s: %v", input, err) } +func SysModelProviderCredential(ctx context.Context, env []string, _ string, _ chan<- string) (string, error) { + engineContext, _ := engine.FromContext(ctx) + auth, url, err := engineContext.Engine.Model.ProxyInfo(env) + if err != nil { + return "", err + } + data, err := json.Marshal(map[string]any{ + "env": map[string]string{ + "OPENAI_API_KEY": auth, + "OPENAI_BASE_URL": url, + }, + "ephemeral": true, + }) + return string(data), err +} + func SysContext(ctx context.Context, _ []string, _ string, _ chan<- string) (string, error) { engineContext, _ := engine.FromContext(ctx) @@ -751,7 +792,7 @@ func SysChatFinish(_ context.Context, _ []string, input string, _ chan<- string) var params struct { Message string `json:"return,omitempty"` } - if err := json.Unmarshal([]byte(input), ¶ms); err != nil { + if err := json.Unmarshal([]byte(input), ¶ms); err != nil || params.Message == "" { return "", &engine.ErrChatFinish{ Message: input, } diff --git a/pkg/builtin/defaults.go b/pkg/builtin/defaults.go index ac264ae6..85bdf485 100644 --- a/pkg/builtin/defaults.go +++ b/pkg/builtin/defaults.go @@ -18,8 +18,8 @@ func SetDefaultModel(model string) { } func SetDefaults(tool types.Tool) types.Tool { - if tool.Parameters.ModelName == "" { - tool.Parameters.ModelName = GetDefaultModel() + if tool.ModelName == "" { + tool.ModelName = GetDefaultModel() } return tool } diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index bc499aef..e5b4494e 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -9,7 +9,9 @@ import ( "errors" "io/fs" "os" + "os/user" "path/filepath" + "strings" "github.com/adrg/xdg" "github.com/getkin/kin-openapi/openapi3" @@ -40,10 +42,28 @@ func Complete(opts ...Options) (result Options) { } if result.CacheDir == "" { result.CacheDir = filepath.Join(xdg.CacheHome, version.ProgramName) + } else if !filepath.IsAbs(result.CacheDir) { + var err error + result.CacheDir, err = makeAbsolute(result.CacheDir) + if err != nil { + result.CacheDir = filepath.Join(xdg.CacheHome, version.ProgramName) + } } return } +func makeAbsolute(path string) (string, error) { + if strings.HasPrefix(path, "~"+string(filepath.Separator)) { + usr, err := user.Current() + if err != nil { + return "", err + } + + return filepath.Join(usr.HomeDir, path[2:]), nil + } + return filepath.Abs(path) +} + type noCacheKey struct{} func IsNoCache(ctx context.Context) bool { @@ -72,6 +92,7 @@ func (c *Client) CacheDir() string { func (c *Client) cacheKey(key any) (string, error) { hash := sha256.New() + hash.Write([]byte("v2")) if err := json.NewEncoder(hash).Encode(key); err != nil { return "", err } @@ -84,6 +105,13 @@ func (c *Client) Store(ctx context.Context, key, value any) error { return nil } + select { + // If the context has been canceled, then don't try to save. + case <-ctx.Done(): + return nil + default: + } + if c.noop || IsNoCache(ctx) { keyValue, err := c.cacheKey(key) if err == nil { diff --git a/pkg/chat/chat.go b/pkg/chat/chat.go index a3fdb97a..5adc0676 100644 --- a/pkg/chat/chat.go +++ b/pkg/chat/chat.go @@ -17,7 +17,7 @@ type Prompter interface { } type Chatter interface { - Chat(ctx context.Context, prevState runner.ChatState, prg types.Program, env []string, input string) (resp runner.ChatResponse, err error) + Chat(ctx context.Context, prevState runner.ChatState, prg types.Program, env []string, input string, opts runner.RunOptions) (resp runner.ChatResponse, err error) } type GetProgram func() (types.Program, error) @@ -51,25 +51,30 @@ func Start(ctx context.Context, prevState runner.ChatState, chatter Chatter, prg resp runner.ChatResponse ) - prg, err := prg() + prog, err := prg() if err != nil { return err } - prompter.SetPrompt(getPrompt(prg, prevResp)) + prompter.SetPrompt(getPrompt(prog, prevResp)) if startInput != "" { input = startInput startInput = "" - } else if targetTool := prg.ToolSet[prg.EntryToolID]; !((prevState == nil || prevState == "") && targetTool.Arguments == nil && targetTool.Instructions != "") { + } else if targetTool := prog.ToolSet[prog.EntryToolID]; prevState != nil && prevState != "" || targetTool.Arguments != nil || targetTool.Instructions == "" { // The above logic will skip prompting if this is the first loop and the chat expects no args input, ok, err = prompter.Readline() if !ok || err != nil { return err } + + prog, err = prg() + if err != nil { + return err + } } - resp, err = chatter.Chat(ctx, prevState, prg, env, input) + resp, err = chatter.Chat(ctx, prevState, prog, env, input, runner.RunOptions{}) if err != nil { return err } diff --git a/pkg/cli/credential.go b/pkg/cli/credential.go index b0c4a30a..866ed4e5 100644 --- a/pkg/cli/credential.go +++ b/pkg/cli/credential.go @@ -9,11 +9,8 @@ import ( "time" cmd2 "github.com/gptscript-ai/cmd" - "github.com/gptscript-ai/gptscript/pkg/cache" - "github.com/gptscript-ai/gptscript/pkg/config" "github.com/gptscript-ai/gptscript/pkg/credentials" - "github.com/gptscript-ai/gptscript/pkg/repos/runtimes" - "github.com/gptscript-ai/gptscript/pkg/runner" + "github.com/gptscript-ai/gptscript/pkg/gptscript" "github.com/spf13/cobra" ) @@ -38,34 +35,24 @@ func (c *Credential) Customize(cmd *cobra.Command) { } func (c *Credential) Run(cmd *cobra.Command, _ []string) error { - cfg, err := config.ReadCLIConfig(c.root.ConfigFile) - if err != nil { - return fmt.Errorf("failed to read CLI config: %w", err) - } - - ctx := c.root.CredentialContext - if c.AllContexts { - ctx = "*" - } - opts, err := c.root.NewGPTScriptOpts() if err != nil { return err } - opts.Cache = cache.Complete(opts.Cache) - opts.Runner = runner.Complete(opts.Runner) - if opts.Runner.RuntimeManager == nil { - opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir) + gptScript, err := gptscript.New(cmd.Context(), opts) + if err != nil { + return err } + defer gptScript.Close(true) - if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil { - return err + credCtxs := gptScript.DefaultCredentialContexts + if c.AllContexts { + credCtxs = []string{credentials.AllCredentialContexts} } - // Initialize the credential store and get all the credentials. - store, err := credentials.NewStore(cfg, opts.Runner.RuntimeManager, ctx, opts.Cache.CacheDir) + store, err := gptScript.CredentialStoreFactory.NewStore(credCtxs) if err != nil { - return fmt.Errorf("failed to get credentials store: %w", err) + return err } creds, err := store.List(cmd.Context()) @@ -77,7 +64,7 @@ func (c *Credential) Run(cmd *cobra.Command, _ []string) error { defer w.Flush() // Sort credentials and print column names, depending on the options. - if c.AllContexts { + if c.AllContexts || len(c.root.CredentialContext) > 1 { // Sort credentials by context sort.Slice(creds, func(i, j int) bool { if creds[i].Context == creds[j].Context { @@ -114,7 +101,7 @@ func (c *Credential) Run(cmd *cobra.Command, _ []string) error { } var fields []any - if c.AllContexts { + if c.AllContexts || len(c.root.CredentialContext) > 1 { fields = []any{cred.Context, cred.ToolName, expires} } else { fields = []any{cred.ToolName, expires} diff --git a/pkg/cli/credential_delete.go b/pkg/cli/credential_delete.go index 9c986c54..6c43a41b 100644 --- a/pkg/cli/credential_delete.go +++ b/pkg/cli/credential_delete.go @@ -3,11 +3,7 @@ package cli import ( "fmt" - "github.com/gptscript-ai/gptscript/pkg/cache" - "github.com/gptscript-ai/gptscript/pkg/config" - "github.com/gptscript-ai/gptscript/pkg/credentials" - "github.com/gptscript-ai/gptscript/pkg/repos/runtimes" - "github.com/gptscript-ai/gptscript/pkg/runner" + "github.com/gptscript-ai/gptscript/pkg/gptscript" "github.com/spf13/cobra" ) @@ -29,24 +25,15 @@ func (c *Delete) Run(cmd *cobra.Command, args []string) error { return err } - cfg, err := config.ReadCLIConfig(c.root.ConfigFile) + gptScript, err := gptscript.New(cmd.Context(), opts) if err != nil { - return fmt.Errorf("failed to read CLI config: %w", err) - } - - opts.Cache = cache.Complete(opts.Cache) - opts.Runner = runner.Complete(opts.Runner) - if opts.Runner.RuntimeManager == nil { - opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir) - } - - if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil { return err } + defer gptScript.Close(true) - store, err := credentials.NewStore(cfg, opts.Runner.RuntimeManager, c.root.CredentialContext, opts.Cache.CacheDir) + store, err := gptScript.CredentialStoreFactory.NewStore(gptScript.DefaultCredentialContexts) if err != nil { - return fmt.Errorf("failed to get credentials store: %w", err) + return err } if err = store.Remove(cmd.Context(), args[0]); err != nil { diff --git a/pkg/cli/credential_show.go b/pkg/cli/credential_show.go index ccfe3675..95cb4f11 100644 --- a/pkg/cli/credential_show.go +++ b/pkg/cli/credential_show.go @@ -5,11 +5,7 @@ import ( "os" "text/tabwriter" - "github.com/gptscript-ai/gptscript/pkg/cache" - "github.com/gptscript-ai/gptscript/pkg/config" - "github.com/gptscript-ai/gptscript/pkg/credentials" - "github.com/gptscript-ai/gptscript/pkg/repos/runtimes" - "github.com/gptscript-ai/gptscript/pkg/runner" + "github.com/gptscript-ai/gptscript/pkg/gptscript" "github.com/spf13/cobra" ) @@ -31,24 +27,15 @@ func (c *Show) Run(cmd *cobra.Command, args []string) error { return err } - cfg, err := config.ReadCLIConfig(c.root.ConfigFile) + gptScript, err := gptscript.New(cmd.Context(), opts) if err != nil { - return fmt.Errorf("failed to read CLI config: %w", err) - } - - opts.Cache = cache.Complete(opts.Cache) - opts.Runner = runner.Complete(opts.Runner) - if opts.Runner.RuntimeManager == nil { - opts.Runner.RuntimeManager = runtimes.Default(opts.Cache.CacheDir) - } - - if err = opts.Runner.RuntimeManager.SetUpCredentialHelpers(cmd.Context(), cfg, opts.Env); err != nil { return err } + defer gptScript.Close(true) - store, err := credentials.NewStore(cfg, opts.Runner.RuntimeManager, c.root.CredentialContext, opts.Cache.CacheDir) + store, err := gptScript.CredentialStoreFactory.NewStore(gptScript.DefaultCredentialContexts) if err != nil { - return fmt.Errorf("failed to get credentials store: %w", err) + return err } cred, exists, err := store.Get(cmd.Context(), args[0]) diff --git a/pkg/cli/eval.go b/pkg/cli/eval.go index 2cd4b1b5..4afdf112 100644 --- a/pkg/cli/eval.go +++ b/pkg/cli/eval.go @@ -10,6 +10,7 @@ import ( "github.com/gptscript-ai/gptscript/pkg/gptscript" "github.com/gptscript-ai/gptscript/pkg/input" "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/runner" "github.com/gptscript-ai/gptscript/pkg/types" "github.com/spf13/cobra" ) @@ -56,13 +57,13 @@ func (e *Eval) Run(cmd *cobra.Command, args []string) error { return err } - runner, err := gptscript.New(cmd.Context(), opts) + g, err := gptscript.New(cmd.Context(), opts) if err != nil { return err } prg, err := loader.ProgramFromSource(cmd.Context(), tool.String(), "", loader.Options{ - Cache: runner.Cache, + Cache: g.Cache, }) if err != nil { return err @@ -74,12 +75,14 @@ func (e *Eval) Run(cmd *cobra.Command, args []string) error { } if e.Chat { - return chat.Start(cmd.Context(), nil, runner, func() (types.Program, error) { - return prg, nil + return chat.Start(cmd.Context(), nil, g, func() (types.Program, error) { + return loader.ProgramFromSource(cmd.Context(), tool.String(), "", loader.Options{ + Cache: g.Cache, + }) }, os.Environ(), toolInput, "") } - toolOutput, err := runner.Run(cmd.Context(), prg, opts.Env, toolInput) + toolOutput, err := g.Run(cmd.Context(), prg, opts.Env, toolInput, runner.RunOptions{}) if err != nil { return err } diff --git a/pkg/cli/fmt.go b/pkg/cli/fmt.go index 72696756..8e669349 100644 --- a/pkg/cli/fmt.go +++ b/pkg/cli/fmt.go @@ -43,9 +43,9 @@ func (e *Fmt) Run(_ *cobra.Command, args []string) error { } if e.Write && loc != "" { - return os.WriteFile(loc, []byte(doc.String()), 0644) + return os.WriteFile(loc, []byte(doc.Print()), 0644) } - fmt.Print(doc.String()) + fmt.Print(doc.Print()) return nil } diff --git a/pkg/cli/getenv.go b/pkg/cli/getenv.go new file mode 100644 index 00000000..6d81944d --- /dev/null +++ b/pkg/cli/getenv.go @@ -0,0 +1,60 @@ +package cli + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "io" + "os" + "strings" + + "github.com/spf13/cobra" +) + +type Getenv struct { +} + +func (e *Getenv) Customize(cmd *cobra.Command) { + cmd.Use = "getenv [flags] KEY [DEFAULT]" + cmd.Short = "Looks up an environment variable for use in GPTScript tools" + cmd.Args = cobra.RangeArgs(1, 2) +} + +func (e *Getenv) Run(_ *cobra.Command, args []string) error { + var ( + key = args[0] + def string + ) + if len(args) > 1 { + def = args[1] + } + value := getEnv(key, def) + fmt.Print(value) + return nil +} + +func getEnv(key, def string) string { + v := os.Getenv(key) + if v == "" { + return def + } + + if strings.HasPrefix(v, `{"_gz":"`) && strings.HasSuffix(v, `"}`) { + data, err := base64.StdEncoding.DecodeString(v[8 : len(v)-2]) + if err != nil { + return v + } + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return v + } + strBytes, err := io.ReadAll(gz) + if err != nil { + return v + } + return string(strBytes) + } + + return v +} diff --git a/pkg/cli/getenv_test.go b/pkg/cli/getenv_test.go new file mode 100644 index 00000000..8cc9e05f --- /dev/null +++ b/pkg/cli/getenv_test.go @@ -0,0 +1,57 @@ +package cli + +import ( + "os" + "testing" +) + +func TestGetEnv(t *testing.T) { + // Cleaning up + defer func(currentEnvValue string) { + os.Setenv("testKey", currentEnvValue) + }(os.Getenv("testKey")) + + // Tests + testCases := []struct { + name string + key string + def string + envValue string + expectedResult string + }{ + { + name: "NoValueUseDefault", + key: "testKey", + def: "defaultValue", + envValue: "", + expectedResult: "defaultValue", + }, + { + name: "ValueExistsNoCompress", + key: "testKey", + def: "defaultValue", + envValue: "testValue", + expectedResult: "testValue", + }, + { + name: "ValueExistsCompressed", + key: "testKey", + def: "defaultValue", + envValue: `{"_gz":"H4sIAEosrGYC/ytJLS5RKEvMKU0FACtB3ewKAAAA"}`, + + expectedResult: "test value", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + os.Setenv(test.key, test.envValue) + + result := getEnv(test.key, test.def) + + if result != test.expectedResult { + t.Errorf("expected: %s, got: %s", test.expectedResult, result) + } + }) + } +} diff --git a/pkg/cli/gptscript.go b/pkg/cli/gptscript.go index 19a6b2f2..b5a823b2 100644 --- a/pkg/cli/gptscript.go +++ b/pkg/cli/gptscript.go @@ -13,7 +13,7 @@ import ( "github.com/fatih/color" "github.com/gptscript-ai/cmd" - "github.com/gptscript-ai/gptscript/pkg/assemble" + gptscript2 "github.com/gptscript-ai/go-gptscript" "github.com/gptscript-ai/gptscript/pkg/auth" "github.com/gptscript-ai/gptscript/pkg/builtin" "github.com/gptscript-ai/gptscript/pkg/cache" @@ -22,6 +22,7 @@ import ( "github.com/gptscript-ai/gptscript/pkg/gptscript" "github.com/gptscript-ai/gptscript/pkg/input" "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/loader/github" "github.com/gptscript-ai/gptscript/pkg/monitor" "github.com/gptscript-ai/gptscript/pkg/mvl" "github.com/gptscript-ai/gptscript/pkg/openai" @@ -45,6 +46,7 @@ type GPTScript struct { CacheOptions OpenAIOptions DisplayOptions + SystemToolsDir string `usage:"Directory that contains system managed tool for which GPTScript will not manage the runtime"` Color *bool `usage:"Use color in output (default true)" default:"true"` Confirm bool `usage:"Prompt before running potentially dangerous commands"` Debug bool `usage:"Enable debug logging"` @@ -53,24 +55,25 @@ type GPTScript struct { Output string `usage:"Save output to a file, or - for stdout" short:"o"` EventsStreamTo string `usage:"Stream events to this location, could be a file descriptor/handle (e.g. fd://2), filename, or named pipe (e.g. \\\\.\\pipe\\my-pipe)" name:"events-stream-to"` // Input should not be using GPTSCRIPT_INPUT env var because that is the same value that is set in tool executions - Input string `usage:"Read input from a file (\"-\" for stdin)" short:"f" env:"GPTSCRIPT_INPUT_FILE"` - SubTool string `usage:"Use tool of this name, not the first tool in file" local:"true"` - Assemble bool `usage:"Assemble tool to a single artifact, saved to --output" hidden:"true" local:"true"` - ListModels bool `usage:"List the models available and exit" local:"true"` - ListTools bool `usage:"List built-in tools and exit" local:"true"` - ListenAddress string `usage:"Server listen address" default:"127.0.0.1:0" hidden:"true"` - Chdir string `usage:"Change current working directory" short:"C"` - Daemon bool `usage:"Run tool as a daemon" local:"true" hidden:"true"` - Ports string `usage:"The port range to use for ephemeral daemon ports (ex: 11000-12000)" hidden:"true"` - CredentialContext string `usage:"Context name in which to store credentials" default:"default"` - CredentialOverride []string `usage:"Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234)"` - ChatState string `usage:"The chat state to continue, or null to start a new chat and return the state" local:"true"` - ForceChat bool `usage:"Force an interactive chat session if even the top level tool is not a chat tool" local:"true"` - ForceSequential bool `usage:"Force parallel calls to run sequentially" local:"true"` - Workspace string `usage:"Directory to use for the workspace, if specified it will not be deleted on exit"` - UI bool `usage:"Launch the UI" local:"true" name:"ui"` - DisableTUI bool `usage:"Don't use chat TUI but instead verbose output" local:"true" name:"disable-tui"` - SaveChatStateFile string `usage:"A file to save the chat state to so that a conversation can be resumed with --chat-state" local:"true"` + Input string `usage:"Read input from a file (\"-\" for stdin)" short:"f" env:"GPTSCRIPT_INPUT_FILE"` + SubTool string `usage:"Use tool of this name, not the first tool in file" local:"true"` + ListModels bool `usage:"List the models available and exit" local:"true"` + ListTools bool `usage:"List built-in tools and exit" local:"true"` + ListenAddress string `usage:"Server listen address" default:"127.0.0.1:0" hidden:"true"` + Chdir string `usage:"Change current working directory" short:"C"` + Daemon bool `usage:"Run tool as a daemon" local:"true" hidden:"true"` + Ports string `usage:"The port range to use for ephemeral daemon ports (ex: 11000-12000)" hidden:"true"` + CredentialContext []string `usage:"Context name(s) in which to store credentials"` + CredentialOverride []string `usage:"Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234)"` + ChatState string `usage:"The chat state to continue, or null to start a new chat and return the state" local:"true"` + ForceChat bool `usage:"Force an interactive chat session if even the top level tool is not a chat tool" local:"true"` + ForceSequential bool `usage:"Force parallel calls to run sequentially" local:"true"` + Workspace string `usage:"Directory to use for the workspace, if specified it will not be deleted on exit"` + UI bool `usage:"Launch the UI" local:"true" name:"ui"` + DisableTUI bool `usage:"Don't use chat TUI but instead verbose output" local:"true" name:"disable-tui"` + SaveChatStateFile string `usage:"A file to save the chat state to so that a conversation can be resumed with --chat-state" local:"true"` + DefaultModelProvider string `usage:"Default LLM model provider to use, this will override OpenAI settings"` + GithubEnterpriseHostname string `usage:"The host name for a Github Enterprise instance to enable for remote loading" local:"true"` readData []byte } @@ -81,8 +84,9 @@ func New() *cobra.Command { root, &Eval{gptscript: root}, &Credential{root: root}, - &Parse{}, + &Parse{gptscript: root}, &Fmt{}, + &Getenv{}, &SDKServer{ GPTScript: root, }, @@ -109,6 +113,7 @@ func New() *cobra.Command { newFlag := pflag.Flag{ Name: f.Name, Usage: f.Usage, + Value: f.Value, } if f.Name != "credential-context" { @@ -134,11 +139,13 @@ func (r *GPTScript) NewGPTScriptOpts() (gptscript.Options, error) { CredentialOverrides: r.CredentialOverride, Sequential: r.ForceSequential, }, - Quiet: r.Quiet, - Env: os.Environ(), - CredentialContext: r.CredentialContext, - Workspace: r.Workspace, - DisablePromptServer: r.UI, + Quiet: r.Quiet, + Env: os.Environ(), + CredentialContexts: r.CredentialContext, + Workspace: r.Workspace, + DisablePromptServer: r.UI, + DefaultModelProvider: r.DefaultModelProvider, + SystemToolsDir: r.SystemToolsDir, } if r.Confirm { @@ -208,7 +215,7 @@ func (r *GPTScript) listTools(ctx context.Context, gptScript *gptscript.GPTScrip // Don't print instructions tool.Instructions = "" - lines = append(lines, tool.String()) + lines = append(lines, tool.Print()) } fmt.Println(strings.Join(lines, "\n---\n")) return nil @@ -267,7 +274,10 @@ func (r *GPTScript) listModels(ctx context.Context, gptScript *gptscript.GPTScri if err != nil { return err } - fmt.Println(strings.Join(models, "\n")) + + for _, model := range models { + fmt.Println(model.ID) + } return nil } @@ -329,21 +339,34 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { return err } + if r.GithubEnterpriseHostname != "" { + loader.AddVSC(github.LoaderForPrefix(r.GithubEnterpriseHostname)) + } + // If the user is trying to launch the chat-builder UI, then set up the tool and options here. if r.UI { - args = append([]string{uiTool()}, args...) + if os.Getenv(system.BinEnvVar) == "" { + gptOpt.Env = append(gptOpt.Env, system.BinEnvVar+"="+system.Bin()) + } + + // Pass the corrected environment variables for SDK server options + if r.DefaultModel != "" { + gptOpt.Env = append(gptOpt.Env, "GPTSCRIPT_SDKSERVER_DEFAULT_MODEL="+r.DefaultModel) + } + if len(r.CredentialOverride) > 0 { + gptOpt.Env = append(gptOpt.Env, "GPTSCRIPT_SDKSERVER_CREDENTIAL_OVERRIDE="+strings.Join(r.CredentialOverride, ",")) + } // If args has more than one element, then the user has provided a file. - if len(args) > 1 { - if args[1] == "-" { + if len(args) > 0 { + file := args[0] + if file == "-" { return fmt.Errorf("chat UI only supports files, cannot read from stdin") } - file := args[1] - // If the file is external, then set the SCRIPTS_PATH to the current working directory. Otherwise, // set it to the directory of the script and set the file to the base. - if !(strings.HasPrefix(file, "http://") || strings.HasPrefix(file, "https://") || strings.HasPrefix(file, "github.com")) { + if !strings.HasPrefix(file, "http://") && !strings.HasPrefix(file, "https://") && !strings.HasPrefix(file, "github.com") { absPathToScript, err := filepath.Abs(file) if err != nil { return fmt.Errorf("cannot determine absolute path to script %s: %v", file, err) @@ -358,20 +381,9 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { gptOpt.Env = append(gptOpt.Env, "SCRIPTS_PATH="+cwd) } - if os.Getenv(system.BinEnvVar) == "" { - gptOpt.Env = append(gptOpt.Env, system.BinEnvVar+"="+system.Bin()) - } - - // If the DefaultModel is set, then pass the correct environment variable. - if r.DefaultModel != "" { - gptOpt.Env = append(gptOpt.Env, "GPTSCRIPT_SDKSERVER_DEFAULT_MODEL="+r.DefaultModel) - } - - args = append([]string{args[0]}, "--file="+file) - - if len(args) > 2 { - args = append(args, args[2:]...) - } + gptOpt.Env = append(gptOpt.Env, "UI_RUN_FILE="+file) + // Remove the file from args because the above line will pass it to the UI tool. + args = args[1:] } else { cwd, err := os.Getwd() if err != nil { @@ -382,6 +394,10 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { // The UI must run in daemon mode. r.Daemon = true + // Use the UI tool as the first argument. + args = append([]string{ + env.VarOrDefault("GPTSCRIPT_CHAT_UI_TOOL", "github.com/gptscript-ai/ui@v0.9.4"), + }, args...) } ctx := cmd.Context() @@ -393,6 +409,9 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { defer gptScript.Close(true) if r.ListModels { + if r.DefaultModelProvider != "" { + args = append(args, r.DefaultModelProvider) + } return r.listModels(ctx, gptScript, args) } @@ -418,20 +437,6 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { return cmd.Help() } - if r.Assemble { - var out io.Writer = os.Stdout - if r.Output != "" && r.Output != "-" { - f, err := os.Create(r.Output) - if err != nil { - return fmt.Errorf("opening %s: %w", r.Output, err) - } - defer f.Close() - out = f - } - - return assemble.Assemble(prg, out) - } - toolInput, err := input.FromCLI(r.Input, args) if err != nil { return err @@ -448,7 +453,7 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { // This chat in a stateless mode if r.SaveChatStateFile == "-" || r.SaveChatStateFile == "stdout" { - resp, err := gptScript.Chat(cmd.Context(), chatState, prg, gptOpt.Env, toolInput) + resp, err := gptScript.Chat(cmd.Context(), chatState, prg, gptOpt.Env, toolInput, runner.RunOptions{}) if err != nil { return err } @@ -463,13 +468,16 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { if !r.DisableTUI && !r.Debug && !r.DebugMessages && !r.NoTrunc { // Don't use cmd.Context() because then sigint will cancel everything return tui.Run(context.Background(), args[0], tui.RunOptions{ - OpenAIAPIKey: r.OpenAIOptions.APIKey, - OpenAIBaseURL: r.OpenAIOptions.BaseURL, - DefaultModel: r.DefaultModel, + ClientOpts: &gptscript2.GlobalOptions{ + OpenAIAPIKey: r.APIKey, + OpenAIBaseURL: r.BaseURL, + DefaultModel: r.DefaultModel, + DefaultModelProvider: r.DefaultModelProvider, + }, TrustedRepoPrefixes: []string{"github.com/gptscript-ai"}, DisableCache: r.DisableCache, + CredentialOverrides: r.CredentialOverride, Input: toolInput, - CacheDir: r.CacheDir, SubTool: r.SubTool, Workspace: r.Workspace, SaveChatStateFile: r.SaveChatStateFile, @@ -487,22 +495,10 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { gptScript.ExtraEnv = nil } - s, err := gptScript.Run(cmd.Context(), prg, gptOpt.Env, toolInput) + s, err := gptScript.Run(cmd.Context(), prg, gptOpt.Env, toolInput, runner.RunOptions{}) if err != nil { return err } return r.PrintOutput(toolInput, s) } - -// uiTool returns the versioned UI tool reference for the current GPTScript version. -// For release versions, a reference with a matching release tag is returned. -// For all other versions, a reference to main is returned. -func uiTool() string { - ref := "github.com/gptscript-ai/ui" - if tag := version.Tag; !strings.Contains(tag, "v0.0.0-dev") { - ref = fmt.Sprintf("%s@%s", ref, tag) - } - - return env.VarOrDefault("GPTSCRIPT_CHAT_UI_TOOL", ref) -} diff --git a/pkg/cli/main.go b/pkg/cli/main.go index d06f614f..33048e0e 100644 --- a/pkg/cli/main.go +++ b/pkg/cli/main.go @@ -1,22 +1,38 @@ package cli import ( + "context" + "fmt" "os" + "os/signal" "github.com/gptscript-ai/cmd" "github.com/gptscript-ai/gptscript/pkg/daemon" "github.com/gptscript-ai/gptscript/pkg/mvl" + "github.com/nanobot-ai/nanobot/pkg/supervise" ) func Main() { - if len(os.Args) > 2 && os.Args[1] == "sys.daemon" { - if os.Getenv("GPTSCRIPT_DEBUG") == "true" { - mvl.SetDebug() + if len(os.Args) > 2 { + if os.Args[1] == "sys.daemon" { + if os.Getenv("GPTSCRIPT_DEBUG") == "true" { + mvl.SetDebug() + } + if err := daemon.SysDaemon(); err != nil { + log.Debugf("failed running daemon: %v", err) + } + os.Exit(0) } - if err := daemon.SysDaemon(); err != nil { - log.Debugf("failed running daemon: %v", err) + if os.Args[1] == "_exec" { + if err := supervise.Daemon(); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "failed running _exec: %v\n", err) + os.Exit(1) + } + os.Exit(0) } - os.Exit(0) } - cmd.Main(New()) + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + cmd.MainCtx(ctx, New()) } diff --git a/pkg/cli/parse.go b/pkg/cli/parse.go index 8599bd97..081116c1 100644 --- a/pkg/cli/parse.go +++ b/pkg/cli/parse.go @@ -12,6 +12,7 @@ import ( type Parse struct { PrettyPrint bool `usage:"Indent the json output" short:"p"` + gptscript *GPTScript } func (e *Parse) Customize(cmd *cobra.Command) { @@ -26,7 +27,7 @@ func locationName(l string) string { } func (e *Parse) Run(_ *cobra.Command, args []string) error { - content, err := input.FromLocation(args[0]) + content, err := input.FromLocation(args[0], e.gptscript.DisableCache) if err != nil { return err } diff --git a/pkg/cli/sdk_server.go b/pkg/cli/sdk_server.go index a2ac8488..42f0f949 100644 --- a/pkg/cli/sdk_server.go +++ b/pkg/cli/sdk_server.go @@ -11,6 +11,8 @@ import ( type SDKServer struct { *GPTScript + DatasetTool string `usage:"Tool to use for datasets"` + WorkspaceTool string `usage:"Tool to use for workspace"` } func (c *SDKServer) Customize(cmd *cobra.Command) { @@ -29,13 +31,15 @@ func (c *SDKServer) Run(cmd *cobra.Command, _ []string) error { // Don't use cmd.Context() as we don't want to die on ctrl+c ctx := context.Background() if term.IsTerminal(int(os.Stdin.Fd())) { - // Only support CTRL+C if stdin is the terminal. When ran as a SDK it will be a pipe + // Only support CTRL+C if stdin is the terminal. When ran as an SDK it will be a pipe ctx = cmd.Context() } - return sdkserver.Start(ctx, sdkserver.Options{ + return sdkserver.Run(ctx, sdkserver.Options{ Options: opts, ListenAddress: c.ListenAddress, Debug: c.Debug, + DatasetTool: c.DatasetTool, + WorkspaceTool: c.WorkspaceTool, }) } diff --git a/pkg/config/cliconfig.go b/pkg/config/cliconfig.go index e4aa49ab..d7944d8e 100644 --- a/pkg/config/cliconfig.go +++ b/pkg/config/cliconfig.go @@ -3,25 +3,30 @@ package config import ( "encoding/base64" "encoding/json" - "errors" "fmt" "os" "runtime" - "slices" "strings" "sync" "github.com/adrg/xdg" "github.com/docker/cli/cli/config/types" + "github.com/gptscript-ai/gptscript/pkg/mvl" ) -var ( - darwinHelpers = []string{"osxkeychain", "file"} - windowsHelpers = []string{"wincred", "file"} - linuxHelpers = []string{"secretservice", "pass", "file"} +const ( + WincredCredHelper = "wincred" + OsxkeychainCredHelper = "osxkeychain" + SecretserviceCredHelper = "secretservice" + PassCredHelper = "pass" + FileCredHelper = "file" ) -const GPTScriptHelperPrefix = "gptscript-credential-" +var ( + // Helpers is a list of all supported credential helpers from github.com/gptscript-ai/gptscript-credential-helpers + Helpers = []string{WincredCredHelper, OsxkeychainCredHelper, SecretserviceCredHelper, PassCredHelper} + log = mvl.Package() +) type AuthConfig types.AuthConfig @@ -52,12 +57,13 @@ func (a *AuthConfig) UnmarshalJSON(data []byte) error { } type CLIConfig struct { - Auths map[string]AuthConfig `json:"auths,omitempty"` - CredentialsStore string `json:"credsStore,omitempty"` - GPTScriptConfigFile string `json:"gptscriptConfig,omitempty"` + Auths map[string]AuthConfig `json:"auths,omitempty"` + CredentialsStore string `json:"credsStore,omitempty"` + raw []byte auths map[string]types.AuthConfig authsLock *sync.Mutex + location string } func (c *CLIConfig) Sanitize() *CLIConfig { @@ -81,17 +87,29 @@ func (c *CLIConfig) Save() error { } if c.auths != nil { - c.Auths = map[string]AuthConfig{} + c.Auths = make(map[string]AuthConfig, len(c.auths)) for k, v := range c.auths { - c.Auths[k] = (AuthConfig)(v) + c.Auths[k] = AuthConfig(v) } c.auths = nil } - data, err := json.Marshal(c) + + // This is to not overwrite additional fields that might be the config file + out := map[string]any{} + if len(c.raw) > 0 { + err := json.Unmarshal(c.raw, &out) + if err != nil { + return err + } + } + out["auths"] = c.Auths + out["credsStore"] = c.CredentialsStore + + data, err := json.Marshal(out) if err != nil { return err } - return os.WriteFile(c.GPTScriptConfigFile, data, 0655) + return os.WriteFile(c.location, data, 0655) } func (c *CLIConfig) GetAuthConfigs() map[string]types.AuthConfig { @@ -100,18 +118,26 @@ func (c *CLIConfig) GetAuthConfigs() map[string]types.AuthConfig { defer c.authsLock.Unlock() } + if err := c.readFileIntoConfig(c.location); err != nil { + // This is implementing an interface, so we can't return this error. + log.Warnf("Failed to read config file: %v", err) + } + if c.auths == nil { - c.auths = map[string]types.AuthConfig{} - for k, v := range c.Auths { - authConfig := (types.AuthConfig)(v) - c.auths[k] = authConfig - } + c.auths = make(map[string]types.AuthConfig, len(c.Auths)) + } + + // Assume that whatever was pulled from the file is more recent. + // The docker creds framework will save the file after creating or updating a credential. + for k, v := range c.Auths { + c.auths[k] = types.AuthConfig(v) } + return c.auths } func (c *CLIConfig) GetFilename() string { - return c.GPTScriptConfigFile + return c.location } func ReadCLIConfig(gptscriptConfigFile string) (*CLIConfig, error) { @@ -126,76 +152,52 @@ func ReadCLIConfig(gptscriptConfigFile string) (*CLIConfig, error) { } } - data, err := readFile(gptscriptConfigFile) - if err != nil { - return nil, err - } result := &CLIConfig{ - authsLock: &sync.Mutex{}, - GPTScriptConfigFile: gptscriptConfigFile, + authsLock: &sync.Mutex{}, + location: gptscriptConfigFile, } - if err := json.Unmarshal(data, result); err != nil { + + if err := result.readFileIntoConfig(gptscriptConfigFile); err != nil { return nil, err } + if store := os.Getenv("GPTSCRIPT_CREDENTIAL_STORE"); store != "" { + result.CredentialsStore = store + } + if result.CredentialsStore == "" { if err := result.setDefaultCredentialsStore(); err != nil { return nil, err } } - if !isValidCredentialHelper(result.CredentialsStore) { - errMsg := fmt.Sprintf("invalid credential store '%s'", result.CredentialsStore) - switch runtime.GOOS { - case "darwin": - errMsg += " (use 'osxkeychain' or 'file')" - case "windows": - errMsg += " (use 'wincred' or 'file')" - case "linux": - errMsg += " (use 'secretservice', 'pass', or 'file')" - default: - errMsg += " (use 'file')" - } - errMsg += fmt.Sprintf("\nPlease edit your config file at %s to fix this.", result.GPTScriptConfigFile) - - return nil, errors.New(errMsg) - } - return result, nil } func (c *CLIConfig) setDefaultCredentialsStore() error { switch runtime.GOOS { case "darwin": - c.CredentialsStore = "osxkeychain" + c.CredentialsStore = OsxkeychainCredHelper case "windows": - c.CredentialsStore = "wincred" + c.CredentialsStore = WincredCredHelper default: - c.CredentialsStore = "file" + c.CredentialsStore = FileCredHelper } return c.Save() } -func isValidCredentialHelper(helper string) bool { - switch runtime.GOOS { - case "darwin": - return slices.Contains(darwinHelpers, helper) - case "windows": - return slices.Contains(windowsHelpers, helper) - case "linux": - return slices.Contains(linuxHelpers, helper) - default: - return helper == "file" - } -} - -func readFile(path string) ([]byte, error) { +func (c *CLIConfig) readFileIntoConfig(path string) error { data, err := os.ReadFile(path) if os.IsNotExist(err) { - return []byte("{}"), nil + return nil } else if err != nil { - return nil, fmt.Errorf("failed to read user config %s: %w", path, err) + return fmt.Errorf("failed to read user config %s: %w", path, err) + } + + c.raw = data + if err := json.Unmarshal(data, c); err != nil { + return fmt.Errorf("failed to unmarshal %s: %v", path, err) } - return data, nil + return nil } diff --git a/pkg/context/context.go b/pkg/context/context.go index 0169d0e0..31474f6c 100644 --- a/pkg/context/context.go +++ b/pkg/context/context.go @@ -46,14 +46,3 @@ func GetLogger(ctx context.Context) mvl.Logger { return l } - -type envKey struct{} - -func WithEnv(ctx context.Context, env []string) context.Context { - return context.WithValue(ctx, envKey{}, env) -} - -func GetEnv(ctx context.Context) []string { - l, _ := ctx.Value(envKey{}).([]string) - return l -} diff --git a/pkg/credentials/credential.go b/pkg/credentials/credential.go index c81adf2b..9d314a70 100644 --- a/pkg/credentials/credential.go +++ b/pkg/credentials/credential.go @@ -16,15 +16,20 @@ const ( CredentialTypeTool CredentialType = "tool" CredentialTypeModelProvider CredentialType = "modelProvider" ExistingCredential = "GPTSCRIPT_EXISTING_CREDENTIAL" + CredentialExpiration = "GPTSCRIPT_CREDENTIAL_EXPIRATION" ) type Credential struct { - Context string `json:"context"` - ToolName string `json:"toolName"` - Type CredentialType `json:"type"` - Env map[string]string `json:"env"` - ExpiresAt *time.Time `json:"expiresAt"` - RefreshToken string `json:"refreshToken"` + Context string `json:"context"` + ToolName string `json:"toolName"` + Type CredentialType `json:"type"` + Env map[string]string `json:"env"` + // If the CheckParam that is stored is different from the param on the tool, + // then the credential will be re-authed as if it does not exist. + CheckParam string `json:"checkParam"` + Ephemeral bool `json:"ephemeral,omitempty"` + ExpiresAt *time.Time `json:"expiresAt"` + RefreshToken string `json:"refreshToken"` } func (c Credential) IsExpired() bool { @@ -35,6 +40,9 @@ func (c Credential) IsExpired() bool { } func (c Credential) toDockerAuthConfig() (types.AuthConfig, error) { + for k, v := range c.Env { + c.Env[k] = strings.TrimSpace(v) + } cred, err := json.Marshal(c) if err != nil { return types.AuthConfig{}, err @@ -50,13 +58,8 @@ func (c Credential) toDockerAuthConfig() (types.AuthConfig, error) { func credentialFromDockerAuthConfig(authCfg types.AuthConfig) (Credential, error) { var cred Credential if authCfg.Password != "" { - if err := json.Unmarshal([]byte(authCfg.Password), &cred); err != nil || len(cred.Env) == 0 { - // Legacy: try unmarshalling into just an env map - var env map[string]string - if err := json.Unmarshal([]byte(authCfg.Password), &env); err != nil { - return Credential{}, err - } - cred.Env = env + if err := json.Unmarshal([]byte(authCfg.Password), &cred); err != nil { + return cred, fmt.Errorf("failed to unmarshal credential: %w", err) } } @@ -82,6 +85,7 @@ func credentialFromDockerAuthConfig(authCfg types.AuthConfig) (Credential, error Context: ctx, ToolName: tool, Type: CredentialType(credType), + CheckParam: cred.CheckParam, Env: cred.Env, ExpiresAt: cred.ExpiresAt, RefreshToken: cred.RefreshToken, diff --git a/pkg/credentials/error.go b/pkg/credentials/error.go new file mode 100644 index 00000000..f3990f7c --- /dev/null +++ b/pkg/credentials/error.go @@ -0,0 +1,12 @@ +package credentials + +import ( + "strings" +) + +func IsCredentialsNotFoundError(err error) bool { + if err == nil { + return false + } + return strings.Contains(err.Error(), "credentials not found in native keychain") +} diff --git a/pkg/credentials/factory.go b/pkg/credentials/factory.go new file mode 100644 index 00000000..60f1c838 --- /dev/null +++ b/pkg/credentials/factory.go @@ -0,0 +1,109 @@ +package credentials + +import ( + "context" + "strings" + + "github.com/docker/docker-credential-helpers/client" + "github.com/gptscript-ai/gptscript/pkg/config" + "github.com/gptscript-ai/gptscript/pkg/types" +) + +type ProgramLoaderRunner interface { + Load(ctx context.Context, toolName string) (prg types.Program, err error) + Run(ctx context.Context, prg types.Program, input string) (output string, err error) +} + +func NewFactory(ctx context.Context, cfg *config.CLIConfig, overrides []string, plr ProgramLoaderRunner) (StoreFactory, error) { + creds, err := ParseCredentialOverrides(overrides) + if err != nil { + return StoreFactory{}, err + } + + overrideMap := make(map[string]map[string]map[string]string) + for k, v := range creds { + contextName, toolName, ok := strings.Cut(k, ctxSeparator) + if !ok { + continue + } + toolMap, ok := overrideMap[contextName] + if !ok { + toolMap = make(map[string]map[string]string) + } + toolMap[toolName] = v + overrideMap[contextName] = toolMap + } + + toolName := translateToolName(cfg.CredentialsStore) + if toolName == config.FileCredHelper { + return StoreFactory{ + file: true, + cfg: cfg, + overrides: overrideMap, + }, nil + } + + prg, err := plr.Load(ctx, toolName) + if err != nil { + return StoreFactory{}, err + } + + return StoreFactory{ + ctx: ctx, + prg: prg, + runner: plr, + cfg: cfg, + overrides: overrideMap, + }, nil +} + +type StoreFactory struct { + ctx context.Context + prg types.Program + file bool + runner ProgramLoaderRunner + cfg *config.CLIConfig + // That's a lot of maps: context -> toolName -> key -> value + overrides map[string]map[string]map[string]string +} + +func (s *StoreFactory) NewStore(credCtxs []string) (CredentialStore, error) { + if err := validateCredentialCtx(credCtxs); err != nil { + return nil, err + } + if s.file { + return &withOverride{ + target: &Store{ + credCtxs: credCtxs, + cfg: s.cfg, + }, + overrides: s.overrides, + credContext: credCtxs, + }, nil + } + return &withOverride{ + target: &Store{ + credCtxs: credCtxs, + cfg: s.cfg, + program: s.program, + }, + overrides: s.overrides, + credContext: credCtxs, + }, nil +} + +func (s *StoreFactory) program(args ...string) client.Program { + return &runnerProgram{ + factory: s, + action: args[0], + } +} + +func translateToolName(toolName string) string { + for _, helper := range config.Helpers { + if helper == toolName { + return "github.com/gptscript-ai/gptscript-credential-helpers/" + toolName + "/cmd" + } + } + return toolName +} diff --git a/pkg/credentials/helper.go b/pkg/credentials/helper.go deleted file mode 100644 index e5cd34f6..00000000 --- a/pkg/credentials/helper.go +++ /dev/null @@ -1,112 +0,0 @@ -package credentials - -import ( - "errors" - "net/url" - "regexp" - "strings" - - "github.com/docker/cli/cli/config/credentials" - "github.com/docker/cli/cli/config/types" - "github.com/docker/docker-credential-helpers/client" - credentials2 "github.com/docker/docker-credential-helpers/credentials" - "github.com/gptscript-ai/gptscript/pkg/config" -) - -func NewHelper(c *config.CLIConfig, helper string) (credentials.Store, error) { - return &HelperStore{ - file: credentials.NewFileStore(c), - program: client.NewShellProgramFunc(helper), - }, nil -} - -type HelperStore struct { - file credentials.Store - program client.ProgramFunc -} - -func (h *HelperStore) Erase(serverAddress string) error { - var errs []error - if err := client.Erase(h.program, serverAddress); err != nil { - errs = append(errs, err) - } - if err := h.file.Erase(serverAddress); err != nil { - errs = append(errs, err) - } - return errors.Join(errs...) -} - -func (h *HelperStore) Get(serverAddress string) (types.AuthConfig, error) { - creds, err := client.Get(h.program, serverAddress) - if credentials2.IsErrCredentialsNotFound(err) { - return h.file.Get(serverAddress) - } else if err != nil { - return types.AuthConfig{}, err - } - return types.AuthConfig{ - Username: creds.Username, - Password: creds.Secret, - ServerAddress: serverAddress, - }, nil -} - -func (h *HelperStore) GetAll() (map[string]types.AuthConfig, error) { - result := map[string]types.AuthConfig{} - - serverAddresses, err := client.List(h.program) - if err != nil { - return nil, err - } - - newCredAddresses := make(map[string]string, len(serverAddresses)) - for serverAddress, val := range serverAddresses { - // If the serverAddress contains a port, we need to put it back in the right spot. - // For some reason, even when a credential is stored properly as http://hostname:8080///credctx, - // the list function will return http://hostname///credctx:8080. This is something wrong - // with macOS's built-in libraries. So we need to fix it here. - toolName, ctx, err := toolNameAndCtxFromAddress(serverAddress) - if err != nil { - return nil, err - } - - contextPieces := strings.Split(ctx, ":") - if len(contextPieces) > 1 { - possiblePortNumber := contextPieces[len(contextPieces)-1] - if regexp.MustCompile(`^\d+$`).MatchString(possiblePortNumber) { - // port number confirmed - toolURL, err := url.Parse(toolName) - if err != nil { - return nil, err - } - - // Save the path so we can put it back after removing it. - path := toolURL.Path - toolURL.Path = "" - - toolName = toolURL.String() + ":" + possiblePortNumber + path - ctx = strings.TrimSuffix(ctx, ":"+possiblePortNumber) - } - } - - newCredAddresses[toolNameWithCtx(toolName, ctx)] = val - delete(serverAddresses, serverAddress) - } - - for serverAddress := range newCredAddresses { - ac, err := h.Get(serverAddress) - if err != nil { - return nil, err - } - result[serverAddress] = ac - } - - return result, nil -} - -func (h *HelperStore) Store(authConfig types.AuthConfig) error { - return client.Store(h.program, &credentials2.Credentials{ - ServerURL: authConfig.ServerAddress, - Username: authConfig.Username, - Secret: authConfig.Password, - }) -} diff --git a/pkg/credentials/noop.go b/pkg/credentials/noop.go index 5f3cc5ad..540d80aa 100644 --- a/pkg/credentials/noop.go +++ b/pkg/credentials/noop.go @@ -1,6 +1,8 @@ package credentials -import "context" +import ( + "context" +) type NoopStore struct{} @@ -12,6 +14,10 @@ func (s NoopStore) Add(context.Context, Credential) error { return nil } +func (s NoopStore) Refresh(context.Context, Credential) error { + return nil +} + func (s NoopStore) Remove(context.Context, string) error { return nil } @@ -19,3 +25,7 @@ func (s NoopStore) Remove(context.Context, string) error { func (s NoopStore) List(context.Context) ([]Credential, error) { return nil, nil } + +func (s NoopStore) RecreateAll(context.Context) error { + return nil +} diff --git a/pkg/credentials/overrides.go b/pkg/credentials/overrides.go new file mode 100644 index 00000000..747909d7 --- /dev/null +++ b/pkg/credentials/overrides.go @@ -0,0 +1,153 @@ +package credentials + +import ( + "context" + "fmt" + "maps" + "os" + "strings" +) + +// ParseCredentialOverrides parses a string of credential overrides that the user provided as a command line arg. +// The format of credential overrides can be one of two things: +// cred1:ENV1,ENV2 (direct mapping of environment variables) +// cred1:ENV1=VALUE1,ENV2=VALUE2 (key-value pairs) +// +// This function turns it into a map[string]map[string]string like this: +// +// { +// "cred1": { +// "ENV1": "VALUE1", +// "ENV2": "VALUE2", +// } +// } +func ParseCredentialOverrides(overrides []string) (map[string]map[string]string, error) { + credentialOverrides := make(map[string]map[string]string) + + for _, o := range overrides { + credName, envs, found := strings.Cut(o, ":") + if !found { + return nil, fmt.Errorf("invalid credential override: %s", o) + } + envMap, ok := credentialOverrides[credName] + if !ok { + envMap = make(map[string]string) + } + for _, env := range strings.Split(envs, ",") { + for _, env := range strings.Split(env, "|") { + key, value, found := strings.Cut(env, "=") + if !found { + // User just passed an env var name as the key, so look up the value. + value = os.Getenv(key) + } + envMap[key] = value + } + } + credentialOverrides[credName] = envMap + } + + return credentialOverrides, nil +} + +type withOverride struct { + target CredentialStore + credContext []string + overrides map[string]map[string]map[string]string +} + +func (w withOverride) Get(ctx context.Context, toolName string) (*Credential, bool, error) { + for _, credCtx := range w.credContext { + overrides, ok := w.overrides[credCtx] + if !ok { + continue + } + override, ok := overrides[toolName] + if !ok { + continue + } + + return &Credential{ + Context: credCtx, + ToolName: toolName, + Type: CredentialTypeTool, + Env: maps.Clone(override), + }, true, nil + } + + return w.target.Get(ctx, toolName) +} + +func (w withOverride) Add(ctx context.Context, cred Credential) error { + for _, credCtx := range w.credContext { + if override, ok := w.overrides[credCtx]; ok { + if _, ok := override[cred.ToolName]; ok { + return fmt.Errorf("cannot add credential with context %q and tool %q because it is statically configure", cred.Context, cred.ToolName) + } + } + } + return w.target.Add(ctx, cred) +} + +func (w withOverride) Refresh(ctx context.Context, cred Credential) error { + if override, ok := w.overrides[cred.Context]; ok { + if _, ok := override[cred.ToolName]; ok { + return nil + } + } + return w.target.Refresh(ctx, cred) +} + +func (w withOverride) Remove(ctx context.Context, toolName string) error { + for _, credCtx := range w.credContext { + if override, ok := w.overrides[credCtx]; ok { + if _, ok := override[toolName]; ok { + return fmt.Errorf("cannot remove credential with context %q and tool %q because it is statically configure", credCtx, toolName) + } + } + } + return w.target.Remove(ctx, toolName) +} + +func (w withOverride) List(ctx context.Context) ([]Credential, error) { + creds, err := w.target.List(ctx) + if err != nil { + return nil, err + } + + added := make(map[string]map[string]bool) + for i, cred := range creds { + if override, ok := w.overrides[cred.Context]; ok { + if _, ok := override[cred.ToolName]; ok { + creds[i].Type = CredentialTypeTool + creds[i].Env = maps.Clone(override[cred.ToolName]) + } + } + tools, ok := added[cred.Context] + if !ok { + tools = make(map[string]bool) + } + tools[cred.ToolName] = true + added[cred.Context] = tools + } + + for _, credCtx := range w.credContext { + tools := w.overrides[credCtx] + for toolName := range tools { + if _, ok := added[credCtx][toolName]; ok { + continue + } + creds = append(creds, Credential{ + Context: credCtx, + ToolName: toolName, + Type: CredentialTypeTool, + Env: maps.Clone(tools[toolName]), + }) + } + } + + return creds, nil +} + +func (w withOverride) RecreateAll(ctx context.Context) error { + return w.target.RecreateAll(ctx) +} diff --git a/pkg/credentials/runnerprogram.go b/pkg/credentials/runnerprogram.go new file mode 100644 index 00000000..4ae123a0 --- /dev/null +++ b/pkg/credentials/runnerprogram.go @@ -0,0 +1,29 @@ +package credentials + +import ( + "io" +) + +type runnerProgram struct { + factory *StoreFactory + action string + output string + err error +} + +func (r *runnerProgram) Output() ([]byte, error) { + return []byte(r.output), r.err +} + +func (r *runnerProgram) Input(in io.Reader) { + input, err := io.ReadAll(in) + if err != nil { + r.err = err + return + } + + prg := r.factory.prg + prg.EntryToolID = prg.ToolSet[prg.EntryToolID].LocalTools[r.action] + + r.output, r.err = r.factory.runner.Run(r.factory.ctx, prg, string(input)) +} diff --git a/pkg/credentials/store.go b/pkg/credentials/store.go index 3940184b..6e5d24ca 100644 --- a/pkg/credentials/store.go +++ b/pkg/credentials/store.go @@ -3,70 +3,100 @@ package credentials import ( "context" "fmt" - "path/filepath" "regexp" - "strings" + "slices" + "sync" "github.com/docker/cli/cli/config/credentials" + "github.com/docker/cli/cli/config/types" + "github.com/docker/docker-credential-helpers/client" "github.com/gptscript-ai/gptscript/pkg/config" + "golang.org/x/exp/maps" ) -type CredentialBuilder interface { - EnsureCredentialHelpers(ctx context.Context) error -} +const ( + DefaultCredentialContext = "default" + AllCredentialContexts = "*" +) type CredentialStore interface { Get(ctx context.Context, toolName string) (*Credential, bool, error) Add(ctx context.Context, cred Credential) error + Refresh(ctx context.Context, cred Credential) error Remove(ctx context.Context, toolName string) error List(ctx context.Context) ([]Credential, error) + RecreateAll(ctx context.Context) error } type Store struct { - credCtx string - credBuilder CredentialBuilder - credHelperDirs CredentialHelperDirs - cfg *config.CLIConfig + credCtxs []string + cfg *config.CLIConfig + program client.ProgramFunc + recreateAllLock sync.RWMutex } -func NewStore(cfg *config.CLIConfig, credentialBuilder CredentialBuilder, credCtx, cacheDir string) (CredentialStore, error) { - if err := validateCredentialCtx(credCtx); err != nil { - return nil, err +func (s *Store) Get(_ context.Context, toolName string) (*Credential, bool, error) { + s.recreateAllLock.RLock() + defer s.recreateAllLock.RUnlock() + + if len(s.credCtxs) > 0 && s.credCtxs[0] == AllCredentialContexts { + return nil, false, fmt.Errorf("cannot get a credential with context %q", AllCredentialContexts) } - return Store{ - credCtx: credCtx, - credBuilder: credentialBuilder, - credHelperDirs: GetCredentialHelperDirs(cacheDir), - cfg: cfg, - }, nil -} -func (s Store) Get(ctx context.Context, toolName string) (*Credential, bool, error) { - store, err := s.getStore(ctx) + store, err := s.getStore() if err != nil { return nil, false, err } - auth, err := store.Get(toolNameWithCtx(toolName, s.credCtx)) - if err != nil { - return nil, false, err - } else if auth.Password == "" { + + var ( + authCfg types.AuthConfig + credCtx string + ) + for _, c := range s.credCtxs { + auth, err := store.Get(toolNameWithCtx(toolName, c)) + if err != nil { + if IsCredentialsNotFoundError(err) { + continue + } + return nil, false, err + } else if auth.Password == "" { + continue + } + + authCfg = auth + credCtx = c + break + } + + if credCtx == "" { + // Didn't find the credential return nil, false, nil } - if auth.ServerAddress == "" { - auth.ServerAddress = toolNameWithCtx(toolName, s.credCtx) // Not sure why we have to do this, but we do. + if authCfg.ServerAddress == "" { + authCfg.ServerAddress = toolNameWithCtx(toolName, credCtx) // Not sure why we have to do this, but we do. } - cred, err := credentialFromDockerAuthConfig(auth) + cred, err := credentialFromDockerAuthConfig(authCfg) if err != nil { return nil, false, err } return &cred, true, nil } -func (s Store) Add(ctx context.Context, cred Credential) error { - cred.Context = s.credCtx - store, err := s.getStore(ctx) +// Add adds a new credential to the credential store. +// Any context set on the credential object will be overwritten with the first context of the credential store. +func (s *Store) Add(_ context.Context, cred Credential) error { + s.recreateAllLock.RLock() + defer s.recreateAllLock.RUnlock() + + first := first(s.credCtxs) + if first == AllCredentialContexts { + return fmt.Errorf("cannot add a credential with context %q", AllCredentialContexts) + } + cred.Context = first + + store, err := s.getStore() if err != nil { return err } @@ -77,16 +107,48 @@ func (s Store) Add(ctx context.Context, cred Credential) error { return store.Store(auth) } -func (s Store) Remove(ctx context.Context, toolName string) error { - store, err := s.getStore(ctx) +// Refresh updates an existing credential in the credential store. +func (s *Store) Refresh(_ context.Context, cred Credential) error { + s.recreateAllLock.RLock() + defer s.recreateAllLock.RUnlock() + + if !slices.Contains(s.credCtxs, cred.Context) { + return fmt.Errorf("context %q not in list of valid contexts for this credential store", cred.Context) + } + + store, err := s.getStore() if err != nil { return err } - return store.Erase(toolNameWithCtx(toolName, s.credCtx)) + auth, err := cred.toDockerAuthConfig() + if err != nil { + return err + } + return store.Store(auth) +} + +func (s *Store) Remove(_ context.Context, toolName string) error { + s.recreateAllLock.RLock() + defer s.recreateAllLock.RUnlock() + + first := first(s.credCtxs) + if len(s.credCtxs) > 1 || first == AllCredentialContexts { + return fmt.Errorf("error: credential deletion is not supported when multiple credential contexts are provided") + } + + store, err := s.getStore() + if err != nil { + return err + } + + return store.Erase(toolNameWithCtx(toolName, first)) } -func (s Store) List(ctx context.Context) ([]Credential, error) { - store, err := s.getStore(ctx) +func (s *Store) List(_ context.Context) ([]Credential, error) { + s.recreateAllLock.RLock() + defer s.recreateAllLock.RUnlock() + + store, err := s.getStore() if err != nil { return nil, err } @@ -95,58 +157,149 @@ func (s Store) List(ctx context.Context) ([]Credential, error) { return nil, err } - var creds []Credential - for serverAddress, authCfg := range list { - if authCfg.ServerAddress == "" { - authCfg.ServerAddress = serverAddress // Not sure why we have to do this, but we do. + if len(s.credCtxs) > 0 && s.credCtxs[0] == AllCredentialContexts { + allCreds := make([]Credential, 0, len(list)) + for serverAddress := range list { + ac, err := store.Get(serverAddress) + if err != nil { + return nil, err + } + ac.ServerAddress = serverAddress + + cred, err := credentialFromDockerAuthConfig(ac) + if err != nil { + return nil, err + } + allCreds = append(allCreds, cred) } - c, err := credentialFromDockerAuthConfig(authCfg) + return allCreds, nil + } + + serverAddressesByContext := make(map[string][]string) + for serverAddress := range list { + _, ctx, err := toolNameAndCtxFromAddress(serverAddress) if err != nil { return nil, err } - if s.credCtx == "*" || c.Context == s.credCtx { - creds = append(creds, c) + + if serverAddressesByContext[ctx] == nil { + serverAddressesByContext[ctx] = []string{serverAddress} + } else { + serverAddressesByContext[ctx] = append(serverAddressesByContext[ctx], serverAddress) } } - return creds, nil -} + // Go through the contexts in reverse order so that higher priority contexts override lower ones. + credsByName := make(map[string]Credential) + for i := len(s.credCtxs) - 1; i >= 0; i-- { + for _, serverAddress := range serverAddressesByContext[s.credCtxs[i]] { + ac, err := store.Get(serverAddress) + if err != nil { + return nil, err + } + ac.ServerAddress = serverAddress + + cred, err := credentialFromDockerAuthConfig(ac) + if err != nil { + return nil, err + } + + toolName, _, err := toolNameAndCtxFromAddress(serverAddress) + if err != nil { + return nil, err + } + + credsByName[toolName] = cred + } + } -func (s *Store) getStore(ctx context.Context) (credentials.Store, error) { - return s.getStoreByHelper(ctx, config.GPTScriptHelperPrefix+s.cfg.CredentialsStore) + return maps.Values(credsByName), nil } -func (s *Store) getStoreByHelper(ctx context.Context, helper string) (credentials.Store, error) { - if helper == "" || helper == config.GPTScriptHelperPrefix+"file" { - return credentials.NewFileStore(s.cfg), nil +func (s *Store) RecreateAll(_ context.Context) error { + store, err := s.getStore() + if err != nil { + return err } - // If the helper is referencing one of the credential helper programs, then reference the full path. - if strings.HasPrefix(helper, "gptscript-credential-") { - if err := s.credBuilder.EnsureCredentialHelpers(ctx); err != nil { - return nil, err + // New credentials might be created after our GetAll, but they will be created with the current encryption configuration, + // so it's okay that they are skipped by this function. + s.recreateAllLock.Lock() + all, err := store.GetAll() + s.recreateAllLock.Unlock() + if err != nil { + return err + } + + // Loop through and recreate each individual credential. + for serverAddress := range all { + if err := s.recreateCredential(store, serverAddress); err != nil { + return err } + } + + return nil +} + +func (s *Store) recreateCredential(store credentials.Store, serverAddress string) error { + s.recreateAllLock.Lock() + defer s.recreateAllLock.Unlock() - helper = filepath.Join(s.credHelperDirs.BinDir, helper) + authConfig, err := store.Get(serverAddress) + if err != nil { + if IsCredentialsNotFoundError(err) { + // This can happen if the credential was deleted between the GetAll and the Get by another thread. + return nil + } + return err + } + + if err := store.Erase(serverAddress); err != nil { + return err + } + + if err := store.Store(authConfig); err != nil { + return err } - return NewHelper(s.cfg, helper) + return nil } -func validateCredentialCtx(ctx string) error { - if ctx == "" { - return fmt.Errorf("credential context cannot be empty") +func (s *Store) getStore() (credentials.Store, error) { + if s.program != nil { + return &toolCredentialStore{ + file: credentials.NewFileStore(s.cfg), + program: s.program, + contexts: s.credCtxs, + }, nil } + return credentials.NewFileStore(s.cfg), nil +} - if ctx == "*" { // this represents "all contexts" and is allowed +func validateCredentialCtx(ctxs []string) error { + if len(ctxs) == 0 { + return fmt.Errorf("credential contexts must be provided") + } + + if len(ctxs) == 1 && ctxs[0] == AllCredentialContexts { return nil } // check alphanumeric - r := regexp.MustCompile("^[a-zA-Z0-9]+$") - if !r.MatchString(ctx) { - return fmt.Errorf("credential context must be alphanumeric") + r := regexp.MustCompile("^[-a-zA-Z0-9.]+$") + for _, c := range ctxs { + if !r.MatchString(c) { + return fmt.Errorf("credential contexts must be alphanumeric") + } } + return nil } + +func first(s []string) string { + if len(s) == 0 { + return "" + } + return s[0] +} diff --git a/pkg/credentials/toolstore.go b/pkg/credentials/toolstore.go new file mode 100644 index 00000000..66d4d38d --- /dev/null +++ b/pkg/credentials/toolstore.go @@ -0,0 +1,154 @@ +package credentials + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/url" + "regexp" + "strings" + + "github.com/docker/cli/cli/config/credentials" + "github.com/docker/cli/cli/config/types" + "github.com/docker/docker-credential-helpers/client" + credentials2 "github.com/docker/docker-credential-helpers/credentials" +) + +type toolCredentialStore struct { + file credentials.Store + program client.ProgramFunc + contexts []string +} + +func (h *toolCredentialStore) Erase(serverAddress string) error { + var errs []error + if err := client.Erase(h.program, serverAddress); err != nil { + errs = append(errs, err) + } + if err := h.file.Erase(serverAddress); err != nil { + errs = append(errs, err) + } + return errors.Join(errs...) +} + +func (h *toolCredentialStore) Get(serverAddress string) (types.AuthConfig, error) { + creds, err := client.Get(h.program, serverAddress) + if IsCredentialsNotFoundError(err) { + return h.file.Get(serverAddress) + } else if err != nil { + return types.AuthConfig{}, err + } + return types.AuthConfig{ + Username: creds.Username, + Password: creds.Secret, + ServerAddress: serverAddress, + }, nil +} + +// GetAll will list all credentials in the credential store. +// It MAY (but is not required to) filter the credentials based on the contexts provided. +// This is only supported by some credential stores, while others will ignore it and return all credentials. +// The caller of this function is still required to filter the output to only include the contexts requested. +func (h *toolCredentialStore) GetAll() (map[string]types.AuthConfig, error) { + var ( + serverAddresses map[string]string + err error + ) + if len(h.contexts) == 0 { + serverAddresses, err = client.List(h.program) + } else { + serverAddresses, err = listWithContexts(h.program, h.contexts) + } + + if err != nil { + return nil, err + } + + result := make(map[string]types.AuthConfig, len(serverAddresses)) + for serverAddress, val := range serverAddresses { + // If the serverAddress contains a port, we need to put it back in the right spot. + // For some reason, even when a credential is stored properly as http://hostname:8080///credctx, + // the list function will return http://hostname///credctx:8080. This is something wrong + // with macOS's built-in libraries. So we need to fix it here. + toolName, ctx, err := toolNameAndCtxFromAddress(serverAddress) + if err != nil { + return nil, err + } + + contextPieces := strings.Split(ctx, ":") + if len(contextPieces) > 1 { + possiblePortNumber := contextPieces[len(contextPieces)-1] + if regexp.MustCompile(`^\d+$`).MatchString(possiblePortNumber) { + // port number confirmed + toolURL, err := url.Parse(toolName) + if err != nil { + return nil, err + } + + // Save the path so we can put it back after removing it. + path := toolURL.Path + toolURL.Path = "" + + toolName = toolURL.String() + ":" + possiblePortNumber + path + ctx = strings.TrimSuffix(ctx, ":"+possiblePortNumber) + } + } + + result[toolNameWithCtx(toolName, ctx)] = types.AuthConfig{ + Username: val, + ServerAddress: serverAddress, + } + } + + return result, nil +} + +func (h *toolCredentialStore) Store(authConfig types.AuthConfig) error { + return client.Store(h.program, &credentials2.Credentials{ + ServerURL: authConfig.ServerAddress, + Username: authConfig.Username, + Secret: authConfig.Password, + }) +} + +// listWithContexts is almost an exact copy of the List function in Docker's libraries, +// the only difference being that we pass the context through as input to the program. +// This will allow some credential stores, like Postgres, to do an optimized list. +func listWithContexts(program client.ProgramFunc, contexts []string) (map[string]string, error) { + cmd := program(credentials2.ActionList) + + contextsJSON, err := json.Marshal(contexts) + if err != nil { + return nil, err + } + + cmd.Input(bytes.NewReader(contextsJSON)) + out, err := cmd.Output() + if err != nil { + t := strings.TrimSpace(string(out)) + + if isValidErr := isValidCredsMessage(t); isValidErr != nil { + err = isValidErr + } + + return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t) + } + + var resp map[string]string + if err = json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil { + return nil, err + } + + return resp, nil +} + +func isValidCredsMessage(msg string) error { + if credentials2.IsCredentialsMissingServerURLMessage(msg) { + return credentials2.NewErrCredentialsMissingServerURL() + } + if credentials2.IsCredentialsMissingUsernameMessage(msg) { + return credentials2.NewErrCredentialsMissingUsername() + } + return nil +} diff --git a/pkg/credentials/toolstore_test.go b/pkg/credentials/toolstore_test.go new file mode 100644 index 00000000..fdbae25c --- /dev/null +++ b/pkg/credentials/toolstore_test.go @@ -0,0 +1,130 @@ +package credentials + +import ( + "encoding/json" + "fmt" + "io" + "testing" + + "github.com/docker/cli/cli/config/types" + "github.com/docker/docker-credential-helpers/client" + "github.com/docker/docker-credential-helpers/credentials" +) + +type mockProgram struct { + // mode is either "db" or "normal" + // db mode will honor contexts, normal mode will not + mode string + action string + contexts []string +} + +func (m *mockProgram) Input(in io.Reader) { + switch m.action { + case credentials.ActionList: + var contexts []string + if err := json.NewDecoder(in).Decode(&contexts); err == nil && len(contexts) > 0 { + m.contexts = contexts + } + } + // TODO: add other cases here as needed +} + +func (m *mockProgram) Output() ([]byte, error) { + switch m.action { + case credentials.ActionList: + switch m.mode { + case "db": + // Return only credentials that are in the list of contexts. + creds := make(map[string]string) + for _, context := range m.contexts { + creds[fmt.Sprintf("https://example///%s", context)] = "username" + } + return json.Marshal(creds) + case "normal": + // Return credentials in the list of contexts, plus some made up extras. + creds := make(map[string]string) + for _, context := range m.contexts { + creds[fmt.Sprintf("https://example///%s", context)] = "username" + } + creds[fmt.Sprintf("https://example///%s", "otherContext1")] = "username" + creds[fmt.Sprintf("https://example///%s", "otherContext2")] = "username" + return json.Marshal(creds) + } + } + return nil, nil +} + +func newMockProgram(t *testing.T, mode string) client.ProgramFunc { + t.Helper() + return func(args ...string) client.Program { + p := &mockProgram{ + mode: mode, + } + if len(args) > 0 { + p.action = args[0] + } + return p + } +} + +func TestGetAll(t *testing.T) { + dbProgram := newMockProgram(t, "db") + normalProgram := newMockProgram(t, "normal") + + tests := []struct { + name string + program client.ProgramFunc + wantErr bool + contexts []string + expected map[string]types.AuthConfig + }{ + {name: "db", program: dbProgram, wantErr: false, contexts: []string{"credctx"}, expected: map[string]types.AuthConfig{ + "https://example///credctx": { + Username: "username", + ServerAddress: "https://example///credctx", + }, + }}, + {name: "normal", program: normalProgram, wantErr: false, contexts: []string{"credctx"}, expected: map[string]types.AuthConfig{ + "https://example///credctx": { + Username: "username", + ServerAddress: "https://example///credctx", + }, + "https://example///otherContext1": { + Username: "username", + ServerAddress: "https://example///otherContext1", + }, + "https://example///otherContext2": { + Username: "username", + ServerAddress: "https://example///otherContext2", + }, + }}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + store := &toolCredentialStore{ + program: test.program, + contexts: test.contexts, + } + got, err := store.GetAll() + if (err != nil) != test.wantErr { + t.Errorf("GetAll() error = %v, wantErr %v", err, test.wantErr) + } + if len(got) != len(test.expected) { + t.Errorf("GetAll() got %d credentials, want %d", len(got), len(test.expected)) + } + for name, cred := range got { + if _, ok := test.expected[name]; !ok { + t.Errorf("GetAll() got unexpected credential: %s", name) + } + if got[name].Username != test.expected[name].Username { + t.Errorf("GetAll() got unexpected username for %s", cred.ServerAddress) + } + if got[name].Username != test.expected[name].Username { + t.Errorf("GetAll() got unexpected username for %s", name) + } + } + }) + } +} diff --git a/pkg/credentials/util.go b/pkg/credentials/util.go deleted file mode 100644 index f6165308..00000000 --- a/pkg/credentials/util.go +++ /dev/null @@ -1,18 +0,0 @@ -package credentials - -import ( - "path/filepath" -) - -type CredentialHelperDirs struct { - RevisionFile, LastCheckedFile, BinDir, RepoDir string -} - -func GetCredentialHelperDirs(cacheDir string) CredentialHelperDirs { - return CredentialHelperDirs{ - RevisionFile: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "revision"), - LastCheckedFile: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "last-checked"), - BinDir: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "bin"), - RepoDir: filepath.Join(cacheDir, "repos", "gptscript-credential-helpers", "repo"), - } -} diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 58afbf98..bce6ec43 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -5,6 +5,7 @@ import ( "io" "os" "os/exec" + "runtime" ) func SysDaemon() error { @@ -19,5 +20,11 @@ func SysDaemon() error { cmd := exec.CommandContext(ctx, os.Args[2], os.Args[3:]...) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout + cmd.Cancel = func() error { + if runtime.GOOS == "windows" { + return cmd.Process.Kill() + } + return cmd.Process.Signal(os.Interrupt) + } return cmd.Run() } diff --git a/pkg/engine/call.go b/pkg/engine/call.go new file mode 100644 index 00000000..4a3b70b5 --- /dev/null +++ b/pkg/engine/call.go @@ -0,0 +1,95 @@ +package engine + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/gptscript-ai/gptscript/pkg/types" +) + +func (e *Engine) runCall(ctx Context, tool types.Tool, input string) (*Return, error) { + interpreter, body, _ := strings.Cut(tool.Instructions, "\n") + + fields := strings.Fields(interpreter) + if len(fields) < 2 { + return nil, fmt.Errorf("invalid tool call, no target tool found in %s", tool.Instructions) + } + toolRef := strings.Join(fields[1:], " ") + + toolName, args := types.SplitArg(toolRef) + + toolNameParts := strings.Fields(toolName) + + toolName = toolNameParts[0] + toolNameArgs := toolNameParts[1:] + + targetTools, ok := tool.ToolMapping[toolName] + if !ok || len(targetTools) == 0 { + return nil, fmt.Errorf("target tool %s not found, must reference in `tools:` fields", toolName) + } + + ref := types.ToolReference{ + Reference: toolName, + Arg: args, + ToolID: targetTools[0].ToolID, + } + + newInput, err := types.GetToolRefInput(ctx.Program, ref, input) + if err != nil { + return nil, err + } + + newInput, err = mergeInputs(input, newInput) + if err != nil { + return nil, fmt.Errorf("failed to merge inputs: %w", err) + } + + newInput, err = mergeInputs(newInput, toString(map[string]string{ + "TOOL_CALL_ARGS": strings.Join(toolNameArgs, " "), + "TOOL_CALL_BODY": body, + })) + if err != nil { + return nil, fmt.Errorf("failed to merge inputs for tool calls: %w", err) + } + + newCtx := ctx + newCtx.Tool = ctx.Program.ToolSet[ref.ToolID] + + return e.Start(newCtx, newInput) +} + +func toString(data map[string]string) string { + out, err := json.Marshal(data) + if err != nil { + // this will never happen + panic(err) + } + return string(out) +} + +func mergeInputs(base, overlay string) (string, error) { + baseMap := map[string]interface{}{} + overlayMap := map[string]interface{}{} + + if overlay == "" || overlay == "{}" { + return base, nil + } + + if base != "" { + if err := json.Unmarshal([]byte(base), &baseMap); err != nil { + return "", fmt.Errorf("failed to unmarshal base input: %w", err) + } + } + + if err := json.Unmarshal([]byte(overlay), &overlayMap); err != nil { + return "", fmt.Errorf("failed to unmarshal overlay input: %w", err) + } + + for k, v := range overlayMap { + baseMap[k] = v + } + + out, err := json.Marshal(baseMap) + return string(out), err +} diff --git a/pkg/engine/cmd.go b/pkg/engine/cmd.go index e0ea5e3a..e7671436 100644 --- a/pkg/engine/cmd.go +++ b/pkg/engine/cmd.go @@ -2,13 +2,15 @@ package engine import ( "bytes" + "compress/gzip" "context" + "encoding/base64" "encoding/json" "fmt" "io" "os" "os/exec" - "path/filepath" + "path" "runtime" "sort" "strings" @@ -44,15 +46,36 @@ func (o *outputWriter) Write(p []byte) (n int, err error) { return len(p), nil } -func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCategory ToolCategory) (cmdOut string, cmdErr error) { +func compressEnv(envs []string) (result []string) { + for _, env := range envs { + k, v, ok := strings.Cut(env, "=") + if !ok || len(v) < 40_000 { + result = append(result, env) + continue + } + + out := bytes.NewBuffer(nil) + b64 := base64.NewEncoder(base64.StdEncoding, out) + gz := gzip.NewWriter(b64) + _, _ = gz.Write([]byte(v)) + _ = gz.Close() + _ = b64.Close() + result = append(result, k+`={"_gz":"`+out.String()+`"}`) + } + return +} + +func (e *Engine) runCommand(ctx Context, tool types.Tool, input string) (cmdOut string, cmdErr error) { id := counter.Next() + var combinedOutput string defer func() { e.Progress <- types.CompletionStatus{ CompletionID: id, Response: map[string]any{ - "output": cmdOut, - "err": cmdErr, + "output": cmdOut, + "fullOutput": combinedOutput, + "err": cmdErr, }, } }() @@ -88,20 +111,27 @@ func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCate } }() - return tool.BuiltinFunc(ctx.WrappedContext(), e.Env, input, progress) + return tool.BuiltinFunc(ctx.WrappedContext(e), e.Env, input, progress) } var instructions []string for _, inputContext := range ctx.InputContext { instructions = append(instructions, inputContext.Content) } - var extraEnv = []string{ - strings.TrimSpace(fmt.Sprintf("GPTSCRIPT_CONTEXT=%s", strings.Join(instructions, "\n"))), + + extraEnv := []string{ + strings.TrimSpace("GPTSCRIPT_CONTEXT=" + strings.Join(instructions, "\n")), } - cmd, stop, err := e.newCommand(ctx.Ctx, extraEnv, tool, input) + commandCtx, cancel := context.WithCancel(ctx.Ctx) + defer cancel() + + cmd, stop, err := e.newCommand(commandCtx, extraEnv, tool, input, true) if err != nil { - return "", err + if ctx.ToolCategory == NoCategory && ctx.Parent != nil { + return fmt.Sprintf("ERROR: got (%v) while parsing command", err), nil + } + return "", fmt.Errorf("got (%v) while parsing command", err) } defer stop() @@ -113,24 +143,39 @@ func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCate }, } - output := &bytes.Buffer{} - all := &bytes.Buffer{} - cmd.Stderr = io.MultiWriter(all, os.Stderr) - cmd.Stdout = io.MultiWriter(all, output, &outputWriter{ - id: id, - progress: e.Progress, - }) - - if err := cmd.Run(); err != nil { - if toolCategory == NoCategory { - return fmt.Sprintf("ERROR: got (%v) while running tool, OUTPUT: %s", err, all), nil + var ( + stdout = &bytes.Buffer{} + stdoutAndErr = &bytes.Buffer{} + progressOut = &outputWriter{ + id: id, + progress: e.Progress, + } + result *bytes.Buffer + ) + + if tool.Stdin { + cmd.Stdin = strings.NewReader(input) + } + cmd.Stdout = io.MultiWriter(stdout, stdoutAndErr, progressOut) + cmd.Stderr = io.MultiWriter(stdoutAndErr, progressOut, os.Stderr) + result = stdout + defer func() { + combinedOutput = stdoutAndErr.String() + }() + + ctx.OnUserCancel(commandCtx, cancel) + + if err := cmd.Run(); err != nil && (commandCtx.Err() == nil || ctx.Ctx.Err() != nil) { + // If the command failed and the context hasn't been canceled, then return the error. + if ctx.ToolCategory == NoCategory && ctx.Parent != nil { + // If this is a sub-call, then don't return the error; return the error as a message so that the LLM can retry. + return fmt.Sprintf("ERROR: got (%v) while running tool, OUTPUT: %s", err, stdoutAndErr), nil } - _, _ = os.Stderr.Write(output.Bytes()) - log.Errorf("failed to run tool [%s] cmd %v: %v", tool.Parameters.Name, cmd.Args, err) - return "", fmt.Errorf("ERROR: %s: %w", all, err) + log.Errorf("failed to run tool [%s] cmd %v: %v", tool.Name, cmd.Args, err) + return "", fmt.Errorf("ERROR: %s: %w", stdoutAndErr, err) } - return output.String(), IsChatFinishMessage(output.String()) + return result.String(), IsChatFinishMessage(result.String()) } func (e *Engine) getRuntimeEnv(ctx context.Context, tool types.Tool, cmd, env []string) ([]string, error) { @@ -172,10 +217,8 @@ var ignoreENV = map[string]struct{}{ } func appendEnv(envs []string, k, v string) []string { - for _, k := range []string{k, env.ToEnvLike(k)} { - if _, ignore := ignoreENV[k]; !ignore { - envs = append(envs, k+"="+v) - } + if _, ignore := ignoreENV[k]; !ignore { + envs = append(envs, strings.ToUpper(env.ToEnvLike(k))+"="+v) } return envs } @@ -185,34 +228,42 @@ func appendInputAsEnv(env []string, input string) []string { dec := json.NewDecoder(bytes.NewReader([]byte(input))) dec.UseNumber() - env = appendEnv(env, "GPTSCRIPT_INPUT", input) + // If we don't create a new slice here, then parallel tool calls can end up getting messed up. + newEnv := make([]string, len(env), cap(env)+1+len(data)) + copy(newEnv, env) + + newEnv = appendEnv(newEnv, "GPTSCRIPT_INPUT", input) - if err := json.Unmarshal([]byte(input), &data); err != nil { + if err := dec.Decode(&data); err != nil { // ignore invalid JSON - return env + return newEnv } for k, v := range data { switch val := v.(type) { case string: - env = appendEnv(env, k, val) + newEnv = appendEnv(newEnv, k, val) case json.Number: - env = appendEnv(env, k, string(val)) + newEnv = appendEnv(newEnv, k, string(val)) case bool: - env = appendEnv(env, k, fmt.Sprint(val)) + newEnv = appendEnv(newEnv, k, fmt.Sprint(val)) default: data, err := json.Marshal(val) if err == nil { - env = appendEnv(env, k, string(data)) + newEnv = appendEnv(newEnv, k, string(data)) } } } - return env + return newEnv } -func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.Tool, input string) (*exec.Cmd, func(), error) { - envvars := append(e.Env[:], extraEnv...) +func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.Tool, input string, useShell bool) (*exec.Cmd, func(), error) { + if runtime.GOOS == "windows" { + useShell = false + } + + envvars := append(e.Env, extraEnv...) envvars = appendInputAsEnv(envvars, input) if log.IsDebug() { envvars = append(envvars, "GPTSCRIPT_DEBUG=true") @@ -221,9 +272,17 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T interpreter, rest, _ := strings.Cut(tool.Instructions, "\n") interpreter = strings.TrimSpace(interpreter)[2:] - args, err := shlex.Split(interpreter) - if err != nil { - return nil, nil, err + var ( + args []string + err error + ) + if useShell { + args = strings.Fields(interpreter) + } else { + args, err = shlex.Split(interpreter) + if err != nil { + return nil, nil, err + } } envvars, err = e.getRuntimeEnv(ctx, tool, args, envvars) @@ -232,28 +291,26 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T } envvars, envMap := envAsMapAndDeDup(envvars) - for i, arg := range args { - args[i] = os.Expand(arg, func(s string) string { - return envMap[s] - }) + + if runtime.GOOS == "windows" && (args[0] == "/bin/bash" || args[0] == "/bin/sh") { + args[0] = path.Base(args[0]) } if runtime.GOOS == "windows" && (args[0] == "/usr/bin/env" || args[0] == "/bin/env") { args = args[1:] } - var ( - cmdArgs = args[1:] - stop = func() {} - ) + ctx, cancel := context.WithCancel(ctx) + stop := cancel if strings.TrimSpace(rest) != "" { - f, err := os.CreateTemp("", version.ProgramName+requiredFileExtensions[args[0]]) + f, err := os.CreateTemp(env.Getenv("GPTSCRIPT_TMPDIR", envvars), version.ProgramName+requiredFileExtensions[args[0]]) if err != nil { return nil, nil, err } stop = func() { _ = os.Remove(f.Name()) + cancel() } _, err = f.Write([]byte(rest)) @@ -262,21 +319,33 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T stop() return nil, nil, err } - cmdArgs = append(cmdArgs, f.Name()) + args = append(args, f.Name()) + } + + // Expand and/or normalize env references + for i, arg := range args { + args[i] = os.Expand(arg, func(s string) string { + if strings.HasPrefix(s, "!") { + return envMap[s[1:]] + } + if !useShell { + return envMap[s] + } + return "${" + s + "}" + }) } - // This is a workaround for Windows, where the command interpreter is constructed with unix style paths - // It converts unix style paths to windows style paths if runtime.GOOS == "windows" { - parts := strings.Split(args[0], "/") - if parts[len(parts)-1] == "gptscript-go-tool" { - parts[len(parts)-1] = "gptscript-go-tool.exe" - } + args[0] = strings.ReplaceAll(args[0], "/", "\\") + } - args[0] = filepath.Join(parts...) + if useShell { + args = append([]string{"/bin/sh", "-c"}, "exec "+strings.Join(args, " ")) + } else { + args[0] = env.Lookup(envvars, args[0]) } - cmd := exec.CommandContext(ctx, env.Lookup(envvars, args[0]), cmdArgs...) - cmd.Env = envvars + cmd := exec.CommandContext(ctx, args[0], args[1:]...) + cmd.Env = compressEnv(envvars) return cmd, stop, nil } diff --git a/pkg/engine/daemon.go b/pkg/engine/daemon.go index 4cdab995..3a0ecba6 100644 --- a/pkg/engine/daemon.go +++ b/pkg/engine/daemon.go @@ -11,6 +11,8 @@ import ( "sync" "time" + cryptorand "crypto/rand" + "github.com/gptscript-ai/gptscript/pkg/system" "github.com/gptscript-ai/gptscript/pkg/types" ) @@ -18,8 +20,10 @@ import ( var ports Ports type Ports struct { - daemonPorts map[string]int64 - daemonLock sync.Mutex + daemonPorts map[string]int64 + daemonTokens map[string]string + daemonsRunning map[string]func() + daemonLock sync.Mutex startPort, endPort int64 usedPorts map[int64]struct{} @@ -28,6 +32,13 @@ type Ports struct { daemonWG sync.WaitGroup } +func IsDaemonRunning(url string) bool { + ports.daemonLock.Lock() + defer ports.daemonLock.Unlock() + _, ok := ports.daemonsRunning[url] + return ok +} + func SetPorts(start, end int64) { ports.daemonLock.Lock() defer ports.daemonLock.Unlock() @@ -49,6 +60,17 @@ func CloseDaemons() { ports.daemonWG.Wait() } +func StopDaemon(url string) { + ports.daemonLock.Lock() + defer ports.daemonLock.Unlock() + + if stop := ports.daemonsRunning[url]; stop != nil { + stop() + } + + delete(ports.daemonsRunning, url) +} + func nextPort() int64 { if ports.startPort == 0 { ports.startPort = 10240 @@ -100,7 +122,30 @@ func getPath(instructions string) (string, string) { return strings.TrimSpace(rest), strings.TrimSpace(value) } -func (e *Engine) startDaemon(tool types.Tool) (string, error) { +func getDaemonToken(toolID string) (string, error) { + token, ok := ports.daemonTokens[toolID] + if !ok { + // Generate a new token. + tokenBytes := make([]byte, 50) + count, err := cryptorand.Read(tokenBytes) + if err != nil { + return "", fmt.Errorf("failed to generate daemon token: %w", err) + } else if count != len(tokenBytes) { + return "", fmt.Errorf("failed to generate daemon token") + } + + token = fmt.Sprintf("%x", tokenBytes) + + if ports.daemonTokens == nil { + ports.daemonTokens = map[string]string{} + } + ports.daemonTokens[toolID] = token + } + + return token, nil +} + +func (e *Engine) startDaemon(tool types.Tool) (string, string, error) { ports.daemonLock.Lock() defer ports.daemonLock.Unlock() @@ -108,10 +153,15 @@ func (e *Engine) startDaemon(tool types.Tool) (string, error) { instructions, path := getPath(instructions) tool.Instructions = types.CommandPrefix + instructions + token, err := getDaemonToken(tool.ID) + if err != nil { + return "", "", err + } + port, ok := ports.daemonPorts[tool.ID] url := fmt.Sprintf("http://127.0.0.1:%d%s", port, path) - if ok { - return url, nil + if ok && ports.daemonsRunning[url] != nil { + return url, token, nil } if ports.daemonCtx == nil { @@ -130,17 +180,19 @@ func (e *Engine) startDaemon(tool types.Tool) (string, error) { cmd, stop, err := e.newCommand(ctx, []string{ fmt.Sprintf("PORT=%d", port), fmt.Sprintf("GPTSCRIPT_PORT=%d", port), + fmt.Sprintf("GPTSCRIPT_DAEMON_TOKEN=%s", token), }, tool, "{}", + false, ) if err != nil { - return url, err + return url, "", err } r, w, err := os.Pipe() if err != nil { - return "", err + return "", "", err } // Loop back to gptscript to help with process supervision @@ -155,35 +207,38 @@ func (e *Engine) startDaemon(tool types.Tool) (string, error) { return w.Close() } - log.Infof("launched [%s][%s] port [%d] %v", tool.Parameters.Name, tool.ID, port, cmd.Args) + log.Infof("launched [%s][%s] port [%d] %v", tool.Name, tool.ID, port, cmd.Args) if err := cmd.Start(); err != nil { stop() - return url, err + return url, "", err } if ports.daemonPorts == nil { ports.daemonPorts = map[string]int64{} + ports.daemonsRunning = map[string]func(){} } ports.daemonPorts[tool.ID] = port + ports.daemonsRunning[url] = stop - killedCtx, cancel := context.WithCancelCause(ctx) - defer cancel(nil) + killedCtx, killedCancel := context.WithCancelCause(ctx) + defer killedCancel(nil) ports.daemonWG.Add(1) go func() { err := cmd.Wait() if err != nil { - log.Debugf("daemon exited tool [%s] %v: %v", tool.Parameters.Name, cmd.Args, err) + log.Debugf("daemon exited tool [%s] %v: %v", tool.Name, cmd.Args, err) } _ = r.Close() _ = w.Close() - cancel(err) + killedCancel(err) stop() ports.daemonLock.Lock() defer ports.daemonLock.Unlock() delete(ports.daemonPorts, tool.ID) + delete(ports.daemonsRunning, url) ports.daemonWG.Done() }() @@ -194,20 +249,20 @@ func (e *Engine) startDaemon(tool types.Tool) (string, error) { _, _ = io.ReadAll(resp.Body) _ = resp.Body.Close() }() - return url, nil + return url, token, nil } select { case <-killedCtx.Done(): - return url, fmt.Errorf("daemon failed to start: %w", context.Cause(killedCtx)) + return url, "", fmt.Errorf("daemon failed to start: %w", context.Cause(killedCtx)) case <-time.After(time.Second): } } - return url, fmt.Errorf("timeout waiting for 200 response from GET %s", url) + return url, "", fmt.Errorf("timeout waiting for 200 response from GET %s", url) } -func (e *Engine) runDaemon(ctx context.Context, prg *types.Program, tool types.Tool, input string) (cmdRet *Return, cmdErr error) { - url, err := e.startDaemon(tool) +func (e *Engine) runDaemon(ctx Context, tool types.Tool, input string) (cmdRet *Return, cmdErr error) { + url, _, err := e.startDaemon(tool) if err != nil { return nil, err } @@ -215,5 +270,5 @@ func (e *Engine) runDaemon(ctx context.Context, prg *types.Program, tool types.T tool.Instructions = strings.Join(append([]string{ types.CommandPrefix + url, }, strings.Split(tool.Instructions, "\n")[1:]...), "\n") - return e.runHTTP(ctx, prg, tool, input) + return e.runHTTP(ctx, tool, input) } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index b0a6e4eb..c509af9c 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -4,24 +4,36 @@ import ( "context" "encoding/json" "fmt" + "os" + "slices" + "strconv" "strings" "sync" - "github.com/gptscript-ai/gptscript/pkg/config" - gcontext "github.com/gptscript-ai/gptscript/pkg/context" "github.com/gptscript-ai/gptscript/pkg/counter" "github.com/gptscript-ai/gptscript/pkg/types" "github.com/gptscript-ai/gptscript/pkg/version" ) +var maxConsecutiveToolCalls = 50 + +const AbortedSuffix = "\n\nABORTED BY USER" + +func init() { + if val := os.Getenv("GPTSCRIPT_MAX_CONSECUTIVE_TOOL_CALLS"); val != "" { + if i, err := strconv.Atoi(val); err == nil && i > 0 { + maxConsecutiveToolCalls = i + } + } +} + type Model interface { - Call(ctx context.Context, messageRequest types.CompletionRequest, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) + Call(ctx context.Context, messageRequest types.CompletionRequest, env []string, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) + ProxyInfo([]string) (string, string, error) } type RuntimeManager interface { GetContext(ctx context.Context, tool types.Tool, cmd, env []string) (string, []string, error) - EnsureCredentialHelpers(ctx context.Context) error - SetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error } type Engine struct { @@ -29,6 +41,11 @@ type Engine struct { RuntimeManager RuntimeManager Env []string Progress chan<- types.CompletionStatus + MCPRunner MCPRunner +} + +type MCPRunner interface { + Run(ctx Context, progress chan<- types.CompletionStatus, tool types.Tool, input string) (string, error) } type State struct { @@ -45,8 +62,9 @@ type Return struct { } type Call struct { - ToolID string `json:"toolID,omitempty"` - Input string `json:"input,omitempty"` + Missing bool `json:"missing,omitempty"` + ToolID string `json:"toolID,omitempty"` + Input string `json:"input,omitempty"` } type CallResult struct { @@ -78,9 +96,11 @@ type Context struct { Parent *Context LastReturn *Return CurrentReturn *Return + Engine *Engine Program *types.Program // Input is saved only so that we can render display text, don't use otherwise - Input string + Input string + userCancel <-chan struct{} } type ChatHistory struct { @@ -176,6 +196,18 @@ func (c *Context) MarshalJSON() ([]byte, error) { return json.Marshal(c.GetCallContext()) } +func (c *Context) OnUserCancel(ctx context.Context, cancel func()) { + go func() { + select { + case <-ctx.Done(): + // If the context is canceled, then nothing to do. + case <-c.userCancel: + // If the user is requesting a cancel, then cancel the context. + cancel() + } + }() +} + type toolCategoryKey struct{} func WithToolCategory(ctx context.Context, toolCategory ToolCategory) context.Context { @@ -187,7 +219,7 @@ func ToolCategoryFromContext(ctx context.Context) ToolCategory { return category } -func NewContext(ctx context.Context, prg *types.Program, input string) (Context, error) { +func NewContext(ctx context.Context, prg *types.Program, input string, userCancel <-chan struct{}) (Context, error) { category := ToolCategoryFromContext(ctx) callCtx := Context{ @@ -196,31 +228,34 @@ func NewContext(ctx context.Context, prg *types.Program, input string) (Context, Tool: prg.ToolSet[prg.EntryToolID], ToolCategory: category, }, - Ctx: ctx, - Program: prg, - Input: input, + Ctx: ctx, + Program: prg, + Input: input, + userCancel: userCancel, } - agentGroup, err := callCtx.Tool.GetAgents(*prg) + agentGroup, err := callCtx.Tool.GetToolsByType(prg, types.ToolTypeAgent) if err != nil { return callCtx, err } callCtx.AgentGroup = agentGroup + + if callCtx.Tool.IsAgentsOnly() && len(callCtx.AgentGroup) > 0 { + callCtx.Tool = callCtx.Program.ToolSet[callCtx.AgentGroup[0].ToolID] + } + return callCtx, nil } func (c *Context) SubCallContext(ctx context.Context, input, toolID, callID string, toolCategory ToolCategory) (Context, error) { - tool, ok := c.Program.ToolSet[toolID] - if !ok { - return Context{}, fmt.Errorf("failed to file tool for id [%s]", toolID) - } + tool := c.Program.ToolSet[toolID] if callID == "" { callID = counter.Next() } - agentGroup, err := c.Tool.GetNextAgentGroup(*c.Program, c.AgentGroup, toolID) + agentGroup, err := c.Tool.GetNextAgentGroup(c.Program, c.AgentGroup, toolID) if err != nil { return Context{}, err } @@ -237,6 +272,7 @@ func (c *Context) SubCallContext(ctx context.Context, input, toolID, callID stri Program: c.Program, CurrentReturn: c.CurrentReturn, Input: input, + userCancel: c.userCancel, }, nil } @@ -247,64 +283,101 @@ func FromContext(ctx context.Context) (*Context, bool) { return c, ok } -func (c *Context) WrappedContext() context.Context { - return context.WithValue(c.Ctx, engineContext{}, c) +func (c *Context) WrappedContext(e *Engine) context.Context { + cp := *c + cp.Engine = e + return context.WithValue(c.Ctx, engineContext{}, &cp) +} + +func populateMessageParams(ctx Context, completion *types.CompletionRequest, tool types.Tool) error { + completion.Model = tool.ModelName + completion.MaxTokens = tool.MaxTokens + completion.JSONResponse = tool.JSONResponse + completion.Cache = tool.Cache + completion.Chat = tool.Chat + completion.Temperature = tool.Temperature + completion.InternalSystemPrompt = tool.InternalPrompt + + if tool.Chat && completion.InternalSystemPrompt == nil { + completion.InternalSystemPrompt = new(bool) + } + + var err error + completion.Tools, err = tool.GetChatCompletionTools(*ctx.Program, ctx.AgentGroup...) + if err != nil { + return err + } + + completion.Messages = addUpdateSystem(ctx, tool, completion.Messages) + return nil +} + +func (e *Engine) runMCPInvoke(ctx Context, tool types.Tool, input string) (*Return, error) { + output, err := e.MCPRunner.Run(ctx, e.Progress, tool, input) + if err != nil { + return nil, fmt.Errorf("failed to run MCP invoke: %w", err) + } + + return &Return{ + Result: &output, + }, nil +} + +func (e *Engine) runCommandTools(ctx Context, tool types.Tool, input string) (*Return, error) { + if tool.IsHTTP() { + return e.runHTTP(ctx, tool, input) + } else if tool.IsDaemon() { + return e.runDaemon(ctx, tool, input) + } else if tool.IsOpenAPI() { + return e.runOpenAPI(ctx, tool, input) + } else if tool.IsEcho() { + return e.runEcho(tool) + } else if tool.IsCall() { + return e.runCall(ctx, tool, input) + } + s, err := e.runCommand(ctx, tool, input) + return &Return{ + Result: &s, + }, err } -func (e *Engine) Start(ctx Context, input string) (ret *Return, _ error) { +func (e *Engine) Start(ctx Context, input string) (ret *Return, err error) { tool := ctx.Tool defer func() { if ret != nil && ret.State != nil { ret.State.Input = input } + select { + case <-ctx.userCancel: + if ret == nil { + ret = new(Return) + } + if ret.Result == nil { + ret.Result = new(string) + } + *ret.Result += AbortedSuffix + default: + } }() + if tool.IsMCPInvoke() { + return e.runMCPInvoke(ctx, tool, input) + } + if tool.IsCommand() { - if tool.IsHTTP() { - return e.runHTTP(ctx.Ctx, ctx.Program, tool, input) - } else if tool.IsDaemon() { - return e.runDaemon(ctx.Ctx, ctx.Program, tool, input) - } else if tool.IsOpenAPI() { - return e.runOpenAPI(tool, input) - } else if tool.IsEcho() { - return e.runEcho(tool) - } - s, err := e.runCommand(ctx, tool, input, ctx.ToolCategory) - if err != nil { - return nil, err - } - return &Return{ - Result: &s, - }, nil + return e.runCommandTools(ctx, tool, input) } if ctx.ToolCategory == CredentialToolCategory { return nil, fmt.Errorf("credential tools cannot make calls to the LLM") } - completion := types.CompletionRequest{ - Model: tool.Parameters.ModelName, - MaxTokens: tool.Parameters.MaxTokens, - JSONResponse: tool.Parameters.JSONResponse, - Cache: tool.Parameters.Cache, - Chat: tool.Parameters.Chat, - Temperature: tool.Parameters.Temperature, - InternalSystemPrompt: tool.Parameters.InternalPrompt, - } - - if tool.Chat && completion.InternalSystemPrompt == nil { - completion.InternalSystemPrompt = new(bool) - } - - var err error - completion.Tools, err = tool.GetCompletionTools(*ctx.Program, ctx.AgentGroup...) - if err != nil { + var completion types.CompletionRequest + if err := populateMessageParams(ctx, &completion, tool); err != nil { return nil, err } - completion.Messages = addUpdateSystem(ctx, tool, completion.Messages) - if tool.Chat && input == "{}" { input = "" } @@ -316,7 +389,7 @@ func (e *Engine) Start(ctx Context, input string) (ret *Return, _ error) { }) } - return e.complete(ctx.Ctx, &State{ + return e.complete(ctx, &State{ Completion: completion, }) } @@ -328,6 +401,7 @@ func addUpdateSystem(ctx Context, tool types.Tool, msgs []types.CompletionMessag instructions = append(instructions, context.Content) } + tool.Instructions = strings.TrimPrefix(tool.Instructions, types.PromptPrefix) if tool.Instructions != "" { instructions = append(instructions, tool.Instructions) } @@ -348,7 +422,7 @@ func addUpdateSystem(ctx Context, tool types.Tool, msgs []types.CompletionMessag return append([]types.CompletionMessage{msg}, msgs...) } -func (e *Engine) complete(ctx context.Context, state *State) (*Return, error) { +func (e *Engine) complete(ctx Context, state *State) (*Return, error) { var ( progress = make(chan types.CompletionStatus) ret = Return{ @@ -372,9 +446,38 @@ func (e *Engine) complete(ctx context.Context, state *State) (*Return, error) { } }() - resp, err := e.Model.Call(gcontext.WithEnv(ctx, e.Env), state.Completion, progress) + // Limit the number of consecutive tool calls. + // We don't want the LLM to call tools unrestricted or get stuck in an error loop. + var messagesSinceLastUserMessage int + for _, msg := range slices.Backward(state.Completion.Messages) { + if msg.Role == types.CompletionMessageRoleTypeUser { + break + } else if msg.Role == types.CompletionMessageRoleTypeAssistant { + for _, content := range msg.Content { + // If this message is requesting that a tool call be made, then count it towards the limit. + if content.ToolCall != nil { + messagesSinceLastUserMessage++ + break + } + } + } + } + + if messagesSinceLastUserMessage > maxConsecutiveToolCalls { + msg := fmt.Sprintf("We cannot continue because the number of consecutive tool calls is limited to %d.", maxConsecutiveToolCalls) + ret.State.Completion.Messages = append(state.Completion.Messages, types.CompletionMessage{ + Role: types.CompletionMessageRoleTypeAssistant, + Content: []types.ContentPart{{Text: msg}}, + }) + // Setting this ensures that chat continues as expected when we hit this problem. + state.Pending = map[string]types.CompletionToolCall{} + ret.Result = &msg + return &ret, nil + } + + resp, err := e.Model.Call(ctx.WrappedContext(e), state.Completion, e.Env, progress) if err != nil { - return nil, err + return nil, fmt.Errorf("failed calling model for completion: %w", err) } state.Completion.Messages = append(state.Completion.Messages, *resp) @@ -382,19 +485,25 @@ func (e *Engine) complete(ctx context.Context, state *State) (*Return, error) { state.Pending = map[string]types.CompletionToolCall{} for _, content := range resp.Content { if content.ToolCall != nil { - var toolID string + var ( + toolID string + missing bool + ) for _, tool := range state.Completion.Tools { - if tool.Function.Name == content.ToolCall.Function.Name { + if strings.EqualFold(tool.Function.Name, content.ToolCall.Function.Name) { toolID = tool.Function.ToolID } } if toolID == "" { - return nil, fmt.Errorf("failed to find tool id for tool %s in tool_call result", content.ToolCall.Function.Name) + log.Debugf("failed to find tool id for tool %s in tool_call result", content.ToolCall.Function.Name) + toolID = types.ToolNormalizer(content.ToolCall.Function.Name) + missing = true } state.Pending[content.ToolCall.ID] = *content.ToolCall ret.Calls[content.ToolCall.ID] = Call{ - ToolID: toolID, - Input: content.ToolCall.Function.Arguments, + ToolID: toolID, + Missing: missing, + Input: content.ToolCall.Function.Arguments, } } else { cp := content.Text @@ -411,7 +520,25 @@ func (e *Engine) complete(ctx context.Context, state *State) (*Return, error) { return &ret, nil } -func (e *Engine) Continue(ctx Context, state *State, results ...CallResult) (*Return, error) { +func (e *Engine) Continue(ctx Context, state *State, results ...CallResult) (ret *Return, _ error) { + defer func() { + select { + case <-ctx.userCancel: + if ret.Result == nil { + ret.Result = new(string) + } + *ret.Result += AbortedSuffix + default: + } + }() + if ctx.Tool.IsCommand() { + var input string + if len(results) == 1 { + input = results[0].User + } + return e.runCommandTools(ctx, ctx.Tool, input) + } + if state == nil { return nil, fmt.Errorf("invalid continue call, missing state") } @@ -437,7 +564,7 @@ func (e *Engine) Continue(ctx Context, state *State, results ...CallResult) (*Re } } - ret := Return{ + ret = &Return{ State: state, Calls: map[string]Call{}, } @@ -453,7 +580,7 @@ func (e *Engine) Continue(ctx Context, state *State, results ...CallResult) (*Re if len(ret.Calls) > 0 { // Outstanding tool calls still pending - return &ret, nil + return ret, nil } for _, content := range state.Completion.Messages[len(state.Completion.Messages)-1].Content { @@ -484,6 +611,9 @@ func (e *Engine) Continue(ctx Context, state *State, results ...CallResult) (*Re return nil, fmt.Errorf("invalid continue call, no completion needed") } - state.Completion.Messages = addUpdateSystem(ctx, ctx.Tool, state.Completion.Messages) - return e.complete(ctx.Ctx, state) + if err := populateMessageParams(ctx, &state.Completion, ctx.Tool); err != nil { + return nil, err + } + + return e.complete(ctx, state) } diff --git a/pkg/engine/http.go b/pkg/engine/http.go index a81f1bb3..a9a635e8 100644 --- a/pkg/engine/http.go +++ b/pkg/engine/http.go @@ -1,13 +1,14 @@ package engine import ( - "context" "encoding/json" "fmt" "io" + "maps" "net/http" "net/url" "os" + "slices" "strings" "github.com/gptscript-ai/gptscript/pkg/types" @@ -15,9 +16,14 @@ import ( const DaemonURLSuffix = ".daemon.gptscript.local" -func (e *Engine) runHTTP(ctx context.Context, prg *types.Program, tool types.Tool, input string) (cmdRet *Return, cmdErr error) { +func (e *Engine) runHTTP(ctx Context, tool types.Tool, input string) (cmdRet *Return, cmdErr error) { envMap := map[string]string{} + for _, env := range appendInputAsEnv(nil, input) { + k, v, _ := strings.Cut(env, "=") + envMap[k] = v + } + for _, env := range e.Env { k, v, _ := strings.Cut(env, "=") envMap[k] = v @@ -25,7 +31,7 @@ func (e *Engine) runHTTP(ctx context.Context, prg *types.Program, tool types.Too toolURL := strings.Split(tool.Instructions, "\n")[0][2:] toolURL = os.Expand(toolURL, func(s string) string { - return envMap[s] + return url.PathEscape(envMap[s]) }) parsed, err := url.Parse(toolURL) @@ -33,17 +39,21 @@ func (e *Engine) runHTTP(ctx context.Context, prg *types.Program, tool types.Too return nil, err } + var ( + requestedEnvVars map[string]struct{} + daemonToken string + ) if strings.HasSuffix(parsed.Hostname(), DaemonURLSuffix) { referencedToolName := strings.TrimSuffix(parsed.Hostname(), DaemonURLSuffix) referencedToolRefs, ok := tool.ToolMapping[referencedToolName] if !ok || len(referencedToolRefs) != 1 { return nil, fmt.Errorf("invalid reference [%s] to tool [%s] from [%s], missing \"tools: %s\" parameter", toolURL, referencedToolName, tool.Source, referencedToolName) } - referencedTool, ok := prg.ToolSet[referencedToolRefs[0].ToolID] + referencedTool, ok := ctx.Program.ToolSet[referencedToolRefs[0].ToolID] if !ok { return nil, fmt.Errorf("failed to find tool [%s] for [%s]", referencedToolName, parsed.Hostname()) } - toolURL, err = e.startDaemon(referencedTool) + toolURL, daemonToken, err = e.startDaemon(referencedTool) if err != nil { return nil, err } @@ -53,6 +63,14 @@ func (e *Engine) runHTTP(ctx context.Context, prg *types.Program, tool types.Too } parsed.Host = toolURLParsed.Host toolURL = parsed.String() + + metadataEnvVars := strings.Split(referencedTool.MetaData["requestedEnvVars"], ",") + requestedEnvVars = make(map[string]struct{}, len(metadataEnvVars)) + for _, e := range metadataEnvVars { + if e != "" { + requestedEnvVars[e] = struct{}{} + } + } } if tool.Blocking { @@ -61,12 +79,47 @@ func (e *Engine) runHTTP(ctx context.Context, prg *types.Program, tool types.Too }, nil } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, toolURL, strings.NewReader(input)) + if body, ok := envMap["BODY"]; ok { + input = body + } + + req, err := http.NewRequestWithContext(ctx.Ctx, http.MethodPost, toolURL, strings.NewReader(input)) if err != nil { return nil, err } - req.Header.Set("X-GPTScript-Tool-Name", tool.Parameters.Name) + if daemonToken != "" { + req.Header.Add("X-GPTScript-Daemon-Token", daemonToken) + } + + for _, k := range slices.Sorted(maps.Keys(envMap)) { + if _, ok := requestedEnvVars[k]; ok || strings.HasPrefix(k, "GPTSCRIPT_WORKSPACE_") { + req.Header.Add("X-GPTScript-Env", k+"="+envMap[k]) + } + } + + for _, prefix := range strings.Split(envMap["GPTSCRIPT_HTTP_ENV_PREFIX"], ",") { + if prefix == "" { + continue + } + for _, k := range slices.Sorted(maps.Keys(envMap)) { + if strings.HasPrefix(k, prefix) { + req.Header.Add("X-GPTScript-Env", k+"="+envMap[k]) + } + } + } + + for _, k := range strings.Split(envMap["GPTSCRIPT_HTTP_ENV"], ",") { + if k == "" { + continue + } + v := envMap[k] + if v != "" { + req.Header.Add("X-GPTScript-Env", k+"="+v) + } + } + + req.Header.Set("X-GPTScript-Tool-Name", tool.Name) if err := json.Unmarshal([]byte(input), &map[string]any{}); err == nil { req.Header.Set("Content-Type", "application/json") @@ -74,6 +127,13 @@ func (e *Engine) runHTTP(ctx context.Context, prg *types.Program, tool types.Too req.Header.Set("Content-Type", "text/plain") } + // If the user canceled the run, then don't make the request. + select { + case <-ctx.userCancel: + return &Return{}, nil + default: + } + resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err @@ -81,8 +141,8 @@ func (e *Engine) runHTTP(ctx context.Context, prg *types.Program, tool types.Too defer resp.Body.Close() if resp.StatusCode > 299 { - _, _ = io.ReadAll(resp.Body) - return nil, fmt.Errorf("error in request to [%s] [%d]: %s", toolURL, resp.StatusCode, resp.Status) + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("error in request to [%s] [%d]: %s: %s", toolURL, resp.StatusCode, resp.Status, body) } content, err := io.ReadAll(resp.Body) diff --git a/pkg/engine/openapi.go b/pkg/engine/openapi.go index 2e338ca4..2e79bc38 100644 --- a/pkg/engine/openapi.go +++ b/pkg/engine/openapi.go @@ -8,90 +8,159 @@ import ( "mime/multipart" "net/http" "net/url" + "os" "strings" "github.com/gptscript-ai/gptscript/pkg/env" + "github.com/gptscript-ai/gptscript/pkg/openapi" "github.com/gptscript-ai/gptscript/pkg/types" "github.com/tidwall/gjson" - "golang.org/x/exp/maps" ) -var ( - SupportedMIMETypes = []string{"application/json", "text/plain", "multipart/form-data"} - SupportedSecurityTypes = []string{"apiKey", "http"} -) +func (e *Engine) runOpenAPIRevamp(tool types.Tool, input string) (*Return, error) { + envMap := make(map[string]string, len(e.Env)) + for _, env := range e.Env { + k, v, _ := strings.Cut(env, "=") + envMap[k] = v + } -type Parameter struct { - Name string `json:"name"` - Style string `json:"style"` - Explode *bool `json:"explode"` -} + _, inst, _ := strings.Cut(tool.Instructions, types.OpenAPIPrefix+" ") + args := strings.Fields(inst) -// A SecurityInfo represents a security scheme in OpenAPI. -type SecurityInfo struct { - Name string `json:"name"` // name as defined in the security schemes - Type string `json:"type"` // http or apiKey - Scheme string `json:"scheme"` // bearer or basic, for type==http - APIKeyName string `json:"apiKeyName"` // name of the API key, for type==apiKey - In string `json:"in"` // header, query, or cookie, for type==apiKey -} + if len(args) != 3 { + return nil, fmt.Errorf("expected 3 arguments to %s", types.OpenAPIPrefix) + } -func (i SecurityInfo) GetCredentialToolStrings(hostname string) []string { - vars := i.getCredentialNamesAndEnvVars(hostname) - var tools []string - - for cred, v := range vars { - field := "value" - switch i.Type { - case "apiKey": - field = i.APIKeyName - case "http": - if i.Scheme == "bearer" { - field = "bearer token" - } else { - if strings.Contains(v, "PASSWORD") { - field = "password" - } else { - field = "username" - } + command := args[0] + source := args[1] + filter := args[2] + + var res *Return + switch command { + case openapi.ListTool: + t, err := openapi.Load(source) + if err != nil { + return nil, fmt.Errorf("failed to load OpenAPI file %s: %w", source, err) + } + + opList, err := openapi.List(t, filter) + if err != nil { + return nil, fmt.Errorf("failed to list operations: %w", err) + } + + opListJSON, err := json.MarshalIndent(opList, "", " ") + if err != nil { + return nil, fmt.Errorf("failed to marshal operation list: %w", err) + } + + res = &Return{ + Result: ptr(string(opListJSON)), + } + case openapi.GetSchemaTool: + operation := gjson.Get(input, "operation").String() + + if filter != "" && filter != openapi.NoFilter { + match, err := openapi.MatchFilters(strings.Split(filter, "|"), operation) + if err != nil { + return nil, err + } else if !match { + // Report to the LLM that the operation was not found + return &Return{ + Result: ptr(fmt.Sprintf("ERROR: operation %s not found", operation)), + }, nil } } - tools = append(tools, fmt.Sprintf("github.com/gptscript-ai/credential as %s with %s as env and %q as message and %q as field", - cred, v, "Please provide a value for the "+v+" environment variable", field)) - } - return tools -} + t, err := openapi.Load(source) + if err != nil { + return nil, fmt.Errorf("failed to load OpenAPI file %s: %w", source, err) + } -func (i SecurityInfo) getCredentialNamesAndEnvVars(hostname string) map[string]string { - if i.Type == "http" && i.Scheme == "basic" { - return map[string]string{ - hostname + i.Name + "Username": "GPTSCRIPT_" + env.ToEnvLike(hostname) + "_" + env.ToEnvLike(i.Name) + "_USERNAME", - hostname + i.Name + "Password": "GPTSCRIPT_" + env.ToEnvLike(hostname) + "_" + env.ToEnvLike(i.Name) + "_PASSWORD", + var defaultHost string + if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { + u, err := url.Parse(source) + if err != nil { + return nil, fmt.Errorf("failed to parse server URL %s: %w", source, err) + } + defaultHost = u.Scheme + "://" + u.Hostname() + } + + schema, _, found, err := openapi.GetSchema(operation, defaultHost, t) + if err != nil { + return nil, fmt.Errorf("failed to get schema: %w", err) + } + if !found { + // Report to the LLM that the operation was not found + return &Return{ + Result: ptr(fmt.Sprintf("ERROR: operation %s not found", operation)), + }, nil + } + + schemaJSON, err := json.MarshalIndent(schema, "", " ") + if err != nil { + return nil, fmt.Errorf("failed to marshal schema: %w", err) + } + + res = &Return{ + Result: ptr(string(schemaJSON)), + } + case openapi.RunTool: + operation := gjson.Get(input, "operation").String() + args := gjson.Get(input, "args").String() + + if filter != "" && filter != openapi.NoFilter { + match, err := openapi.MatchFilters(strings.Split(filter, "|"), operation) + if err != nil { + return nil, err + } else if !match { + // Report to the LLM that the operation was not found + return &Return{ + Result: ptr(fmt.Sprintf("ERROR: operation %s not found", operation)), + }, nil + } + } + + t, err := openapi.Load(source) + if err != nil { + return nil, fmt.Errorf("failed to load OpenAPI file %s: %w", source, err) + } + + var defaultHost string + if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { + u, err := url.Parse(source) + if err != nil { + return nil, fmt.Errorf("failed to parse server URL %s: %w", source, err) + } + defaultHost = u.Scheme + "://" + u.Hostname() + } + + result, found, err := openapi.Run(operation, defaultHost, args, t, e.Env) + if err != nil { + return nil, fmt.Errorf("failed to run operation %s: %w", operation, err) + } else if !found { + // Report to the LLM that the operation was not found + return &Return{ + Result: ptr(fmt.Sprintf("ERROR: operation %s not found", operation)), + }, nil + } + + res = &Return{ + Result: &result, } } - return map[string]string{ - hostname + i.Name: "GPTSCRIPT_" + env.ToEnvLike(hostname) + "_" + env.ToEnvLike(i.Name), - } -} -type OpenAPIInstructions struct { - Server string `json:"server"` - Path string `json:"path"` - Method string `json:"method"` - BodyContentMIME string `json:"bodyContentMIME"` - SecurityInfos [][]SecurityInfo `json:"apiKeyInfos"` - QueryParameters []Parameter `json:"queryParameters"` - PathParameters []Parameter `json:"pathParameters"` - HeaderParameters []Parameter `json:"headerParameters"` - CookieParameters []Parameter `json:"cookieParameters"` + return res, nil } // runOpenAPI runs a tool that was generated from an OpenAPI definition. // The tool itself will have instructions regarding the HTTP request that needs to be made. // The tools Instructions field will be in the format "#!sys.openapi '{Instructions JSON}'", // where {Instructions JSON} is a JSON string of type OpenAPIInstructions. -func (e *Engine) runOpenAPI(tool types.Tool, input string) (*Return, error) { +func (e *Engine) runOpenAPI(ctx Context, tool types.Tool, input string) (*Return, error) { + if os.Getenv("GPTSCRIPT_OPENAPI_REVAMP") == "true" { + return e.runOpenAPIRevamp(tool, input) + } + envMap := map[string]string{} for _, env := range e.Env { @@ -100,7 +169,7 @@ func (e *Engine) runOpenAPI(tool types.Tool, input string) (*Return, error) { } // Extract the instructions from the tool to determine server, path, method, etc. - var instructions OpenAPIInstructions + var instructions openapi.OperationInfo _, inst, _ := strings.Cut(tool.Instructions, types.OpenAPIPrefix+" ") inst = strings.TrimPrefix(inst, "'") inst = strings.TrimSuffix(inst, "'") @@ -109,7 +178,7 @@ func (e *Engine) runOpenAPI(tool types.Tool, input string) (*Return, error) { } // Handle path parameters - instructions.Path = handlePathParameters(instructions.Path, instructions.PathParameters, input) + instructions.Path = openapi.HandlePathParameters(instructions.Path, instructions.PathParams, input) // Parse the URL path, err := url.JoinPath(instructions.Server, instructions.Path) @@ -128,28 +197,26 @@ func (e *Engine) runOpenAPI(tool types.Tool, input string) (*Return, error) { return nil, fmt.Errorf("failed to create request: %w", err) } - // Check for authentication (only if using HTTPS or localhost) - if u.Scheme == "https" || u.Hostname() == "localhost" || u.Hostname() == "127.0.0.1" { - if len(instructions.SecurityInfos) > 0 { - if err := handleAuths(req, envMap, instructions.SecurityInfos); err != nil { - return nil, fmt.Errorf("error setting up authentication: %w", err) - } + // Check for authentication + if len(instructions.SecurityInfos) > 0 { + if err := openapi.HandleAuths(req, envMap, instructions.SecurityInfos); err != nil { + return nil, fmt.Errorf("error setting up authentication: %w", err) } + } - // If there is a bearer token set for the whole server, and no Authorization header has been defined, use it. - if token, ok := envMap["GPTSCRIPT_"+env.ToEnvLike(u.Hostname())+"_BEARER_TOKEN"]; ok { - if req.Header.Get("Authorization") == "" { - req.Header.Set("Authorization", "Bearer "+token) - } + // If there is a bearer token set for the whole server, and no Authorization header has been defined, use it. + if token, ok := envMap["GPTSCRIPT_"+env.ToEnvLike(u.Hostname())+"_BEARER_TOKEN"]; ok { + if req.Header.Get("Authorization") == "" { + req.Header.Set("Authorization", "Bearer "+token) } } // Handle query parameters - req.URL.RawQuery = handleQueryParameters(req.URL.Query(), instructions.QueryParameters, input).Encode() + req.URL.RawQuery = openapi.HandleQueryParameters(req.URL.Query(), instructions.QueryParams, input).Encode() // Handle header and cookie parameters - handleHeaderParameters(req, instructions.HeaderParameters, input) - handleCookieParameters(req, instructions.CookieParameters, input) + openapi.HandleHeaderParameters(req, instructions.HeaderParams, input) + openapi.HandleCookieParameters(req, instructions.CookieParams, input) // Handle request body if instructions.BodyContentMIME != "" { @@ -199,6 +266,13 @@ func (e *Engine) runOpenAPI(tool types.Tool, input string) (*Return, error) { req.Body = io.NopCloser(&body) } + // If the user canceled the run, then don't make the request. + select { + case <-ctx.userCancel: + return &Return{}, nil + default: + } + // Make the request resp, err := http.DefaultClient.Do(req) if err != nil { @@ -217,299 +291,6 @@ func (e *Engine) runOpenAPI(tool types.Tool, input string) (*Return, error) { }, nil } -// handleAuths will set up the request with the necessary authentication information. -// A set of sets of SecurityInfo is passed in, where each represents a possible set of security options. -func handleAuths(req *http.Request, envMap map[string]string, infoSets [][]SecurityInfo) error { - var missingVariables [][]string - - // We need to find a set of infos where we have all the needed environment variables. - for _, infoSet := range infoSets { - var missing []string // Keep track of any missing environment variables - for _, info := range infoSet { - vars := info.getCredentialNamesAndEnvVars(req.URL.Hostname()) - - for _, envName := range vars { - if _, ok := envMap[envName]; !ok { - missing = append(missing, envName) - } - } - } - if len(missing) > 0 { - missingVariables = append(missingVariables, missing) - continue - } - - // We're using this info set, because no environment variables were missing. - // Set up the request as needed. - for _, info := range infoSet { - envNames := maps.Values(info.getCredentialNamesAndEnvVars(req.URL.Hostname())) - switch info.Type { - case "apiKey": - switch info.In { - case "header": - req.Header.Set(info.APIKeyName, envMap[envNames[0]]) - case "query": - v := url.Values{} - v.Add(info.APIKeyName, envMap[envNames[0]]) - req.URL.RawQuery = v.Encode() - case "cookie": - req.AddCookie(&http.Cookie{ - Name: info.APIKeyName, - Value: envMap[envNames[0]], - }) - } - case "http": - switch info.Scheme { - case "bearer": - req.Header.Set("Authorization", "Bearer "+envMap[envNames[0]]) - case "basic": - req.SetBasicAuth(envMap[envNames[0]], envMap[envNames[1]]) - } - } - } - return nil - } - - return fmt.Errorf("did not find the needed environment variables for any of the security options. "+ - "At least one of these sets of environment variables must be provided: %v", missingVariables) -} - -// handleQueryParameters extracts each query parameter from the input JSON and adds it to the URL query. -func handleQueryParameters(q url.Values, params []Parameter, input string) url.Values { - for _, param := range params { - res := gjson.Get(input, param.Name) - if res.Exists() { - // If it's an array or object, handle the serialization style - if res.IsArray() { - switch param.Style { - case "form", "": // form is the default style for query parameters - if param.Explode == nil || *param.Explode { // default is to explode - for _, item := range res.Array() { - q.Add(param.Name, item.String()) - } - } else { - var strs []string - for _, item := range res.Array() { - strs = append(strs, item.String()) - } - q.Add(param.Name, strings.Join(strs, ",")) - } - case "spaceDelimited": - if param.Explode == nil || *param.Explode { - for _, item := range res.Array() { - q.Add(param.Name, item.String()) - } - } else { - var strs []string - for _, item := range res.Array() { - strs = append(strs, item.String()) - } - q.Add(param.Name, strings.Join(strs, " ")) - } - case "pipeDelimited": - if param.Explode == nil || *param.Explode { - for _, item := range res.Array() { - q.Add(param.Name, item.String()) - } - } else { - var strs []string - for _, item := range res.Array() { - strs = append(strs, item.String()) - } - q.Add(param.Name, strings.Join(strs, "|")) - } - } - } else if res.IsObject() { - switch param.Style { - case "form", "": // form is the default style for query parameters - if param.Explode == nil || *param.Explode { // default is to explode - for k, v := range res.Map() { - q.Add(k, v.String()) - } - } else { - var strs []string - for k, v := range res.Map() { - strs = append(strs, k, v.String()) - } - q.Add(param.Name, strings.Join(strs, ",")) - } - case "deepObject": - for k, v := range res.Map() { - q.Add(param.Name+"["+k+"]", v.String()) - } - } - } else { - q.Add(param.Name, res.String()) - } - } - } - return q -} - -// handlePathParameters extracts each path parameter from the input JSON and replaces its placeholder in the URL path. -func handlePathParameters(path string, params []Parameter, input string) string { - for _, param := range params { - res := gjson.Get(input, param.Name) - if res.Exists() { - // If it's an array or object, handle the serialization style - if res.IsArray() { - switch param.Style { - case "simple", "": // simple is the default style for path parameters - // simple looks the same regardless of whether explode is true - strs := make([]string, len(res.Array())) - for i, item := range res.Array() { - strs[i] = item.String() - } - path = strings.Replace(path, "{"+param.Name+"}", strings.Join(strs, ","), 1) - case "label": - strs := make([]string, len(res.Array())) - for i, item := range res.Array() { - strs[i] = item.String() - } - - if param.Explode == nil || !*param.Explode { // default is to not explode - path = strings.Replace(path, "{"+param.Name+"}", "."+strings.Join(strs, ","), 1) - } else { - path = strings.Replace(path, "{"+param.Name+"}", "."+strings.Join(strs, "."), 1) - } - case "matrix": - strs := make([]string, len(res.Array())) - for i, item := range res.Array() { - strs[i] = item.String() - } - - if param.Explode == nil || !*param.Explode { // default is to not explode - path = strings.Replace(path, "{"+param.Name+"}", ";"+param.Name+"="+strings.Join(strs, ","), 1) - } else { - s := "" - for _, str := range strs { - s += ";" + param.Name + "=" + str - } - path = strings.Replace(path, "{"+param.Name+"}", s, 1) - } - } - } else if res.IsObject() { - switch param.Style { - case "simple", "": - if param.Explode == nil || !*param.Explode { // default is to not explode - var strs []string - for k, v := range res.Map() { - strs = append(strs, k, v.String()) - } - path = strings.Replace(path, "{"+param.Name+"}", strings.Join(strs, ","), 1) - } else { - var strs []string - for k, v := range res.Map() { - strs = append(strs, k+"="+v.String()) - } - path = strings.Replace(path, "{"+param.Name+"}", strings.Join(strs, ","), 1) - } - case "label": - if param.Explode == nil || !*param.Explode { // default is to not explode - var strs []string - for k, v := range res.Map() { - strs = append(strs, k, v.String()) - } - path = strings.Replace(path, "{"+param.Name+"}", "."+strings.Join(strs, ","), 1) - } else { - s := "" - for k, v := range res.Map() { - s += "." + k + "=" + v.String() - } - path = strings.Replace(path, "{"+param.Name+"}", s, 1) - } - case "matrix": - if param.Explode == nil || !*param.Explode { // default is to not explode - var strs []string - for k, v := range res.Map() { - strs = append(strs, k, v.String()) - } - path = strings.Replace(path, "{"+param.Name+"}", ";"+param.Name+"="+strings.Join(strs, ","), 1) - } else { - s := "" - for k, v := range res.Map() { - s += ";" + k + "=" + v.String() - } - path = strings.Replace(path, "{"+param.Name+"}", s, 1) - } - } - } else { - // Serialization is handled slightly differently even for basic types. - // Explode doesn't do anything though. - switch param.Style { - case "simple", "": - path = strings.Replace(path, "{"+param.Name+"}", res.String(), 1) - case "label": - path = strings.Replace(path, "{"+param.Name+"}", "."+res.String(), 1) - case "matrix": - path = strings.Replace(path, "{"+param.Name+"}", ";"+param.Name+"="+res.String(), 1) - } - } - } - } - return path -} - -// handleHeaderParameters extracts each header parameter from the input JSON and adds it to the request headers. -func handleHeaderParameters(req *http.Request, params []Parameter, input string) { - for _, param := range params { - res := gjson.Get(input, param.Name) - if res.Exists() { - if res.IsArray() { - strs := make([]string, len(res.Array())) - for i, item := range res.Array() { - strs[i] = item.String() - } - req.Header.Add(param.Name, strings.Join(strs, ",")) - } else if res.IsObject() { - // Handle explosion - var strs []string - if param.Explode == nil || !*param.Explode { // default is to not explode - for k, v := range res.Map() { - strs = append(strs, k, v.String()) - } - } else { - for k, v := range res.Map() { - strs = append(strs, k+"="+v.String()) - } - } - req.Header.Add(param.Name, strings.Join(strs, ",")) - } else { // basic type - req.Header.Add(param.Name, res.String()) - } - } - } -} - -// handleCookieParameters extracts each cookie parameter from the input JSON and adds it to the request cookies. -func handleCookieParameters(req *http.Request, params []Parameter, input string) { - for _, param := range params { - res := gjson.Get(input, param.Name) - if res.Exists() { - if res.IsArray() { - strs := make([]string, len(res.Array())) - for i, item := range res.Array() { - strs[i] = item.String() - } - req.AddCookie(&http.Cookie{ - Name: param.Name, - Value: strings.Join(strs, ","), - }) - } else if res.IsObject() { - var strs []string - for k, v := range res.Map() { - strs = append(strs, k, v.String()) - } - req.AddCookie(&http.Cookie{ - Name: param.Name, - Value: strings.Join(strs, ","), - }) - } else { // basic type - req.AddCookie(&http.Cookie{ - Name: param.Name, - Value: res.String(), - }) - } - } - } +func ptr[T any](t T) *T { + return &t } diff --git a/pkg/engine/openapi_test.go b/pkg/engine/openapi_test.go index df1e00fc..9fd5d34e 100644 --- a/pkg/engine/openapi_test.go +++ b/pkg/engine/openapi_test.go @@ -5,6 +5,7 @@ import ( "net/url" "testing" + "github.com/gptscript-ai/gptscript/pkg/openapi" "github.com/stretchr/testify/require" ) @@ -89,7 +90,7 @@ func TestPathParameterSerialization(t *testing.T) { t.Run(test.name, func(t *testing.T) { path := path params := getParameters(test.style, test.explode) - path = handlePathParameters(path, params, string(inputStr)) + path = openapi.HandlePathParameters(path, params, string(inputStr)) require.Contains(t, test.expectedPaths, path) }) } @@ -111,13 +112,13 @@ func TestQueryParameterSerialization(t *testing.T) { tests := []struct { name string input string - param Parameter + param openapi.Parameter expectedQueries []string // We use multiple expected queries due to randomness in map iteration }{ { name: "value", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "v", }, expectedQueries: []string{"v=42"}, @@ -125,7 +126,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "array form + explode", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "a", Style: "form", Explode: boolPointer(true), @@ -135,7 +136,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "array form + no explode", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "a", Style: "form", Explode: boolPointer(false), @@ -145,7 +146,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "array spaceDelimited + explode", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "a", Style: "spaceDelimited", Explode: boolPointer(true), @@ -155,7 +156,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "array spaceDelimited + no explode", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "a", Style: "spaceDelimited", Explode: boolPointer(false), @@ -165,7 +166,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "array pipeDelimited + explode", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "a", Style: "pipeDelimited", Explode: boolPointer(true), @@ -175,7 +176,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "array pipeDelimited + no explode", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "a", Style: "pipeDelimited", Explode: boolPointer(false), @@ -185,7 +186,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "object form + explode", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "o", Style: "form", Explode: boolPointer(true), @@ -198,7 +199,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "object form + no explode", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "o", Style: "form", Explode: boolPointer(false), @@ -211,7 +212,7 @@ func TestQueryParameterSerialization(t *testing.T) { { name: "object deepObject", input: string(inputStr), - param: Parameter{ + param: openapi.Parameter{ Name: "o", Style: "deepObject", }, @@ -224,14 +225,14 @@ func TestQueryParameterSerialization(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - q := handleQueryParameters(url.Values{}, []Parameter{test.param}, test.input) + q := openapi.HandleQueryParameters(url.Values{}, []openapi.Parameter{test.param}, test.input) require.Contains(t, test.expectedQueries, q.Encode()) }) } } -func getParameters(style string, explode bool) []Parameter { - return []Parameter{ +func getParameters(style string, explode bool) []openapi.Parameter { + return []openapi.Parameter{ { Name: "v", Style: style, diff --git a/pkg/env/env.go b/pkg/env/env.go index 7c8bd7c7..2994825b 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -26,6 +26,15 @@ func ToEnvLike(v string) string { return strings.ToUpper(v) } +func Getenv(key string, envs []string) string { + for i := len(envs) - 1; i >= 0; i-- { + if k, v, ok := strings.Cut(envs[i], "="); ok && k == key { + return v + } + } + return "" +} + func Matches(cmd []string, bin string) bool { switch len(cmd) { case 0: @@ -60,6 +69,10 @@ func AppendPath(env []string, binPath string) []string { // Lookup will try to find bin in the PATH in env. It will refer to PATHEXT for Windows support. // If bin can not be resolved to anything the original bin string is returned. func Lookup(env []string, bin string) string { + if strings.Contains(bin, string(filepath.Separator)) { + return bin + } + for _, env := range env { for _, prefix := range []string{"PATH=", "Path="} { suffix, ok := strings.CutPrefix(env, prefix) diff --git a/pkg/gptscript/gptscript.go b/pkg/gptscript/gptscript.go index d25915b1..b7facbd1 100644 --- a/pkg/gptscript/gptscript.go +++ b/pkg/gptscript/gptscript.go @@ -2,6 +2,7 @@ package gptscript import ( "context" + "errors" "fmt" "os" "os/user" @@ -9,14 +10,16 @@ import ( "slices" "strings" + openai2 "github.com/gptscript-ai/chat-completion-client" "github.com/gptscript-ai/gptscript/pkg/builtin" "github.com/gptscript-ai/gptscript/pkg/cache" "github.com/gptscript-ai/gptscript/pkg/config" context2 "github.com/gptscript-ai/gptscript/pkg/context" "github.com/gptscript-ai/gptscript/pkg/credentials" "github.com/gptscript-ai/gptscript/pkg/engine" - "github.com/gptscript-ai/gptscript/pkg/hash" "github.com/gptscript-ai/gptscript/pkg/llm" + "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/mcp" "github.com/gptscript-ai/gptscript/pkg/monitor" "github.com/gptscript-ai/gptscript/pkg/mvl" "github.com/gptscript-ai/gptscript/pkg/openai" @@ -25,33 +28,42 @@ import ( "github.com/gptscript-ai/gptscript/pkg/repos/runtimes" "github.com/gptscript-ai/gptscript/pkg/runner" "github.com/gptscript-ai/gptscript/pkg/types" + + // Load all VCS + _ "github.com/gptscript-ai/gptscript/pkg/loader/vcs" ) var log = mvl.Package() type GPTScript struct { - Registry *llm.Registry - Runner *runner.Runner - Cache *cache.Client - WorkspacePath string - DeleteWorkspaceOnClose bool - ExtraEnv []string - close func() + Registry *llm.Registry + Runner *runner.Runner + Cache *cache.Client + CredentialStoreFactory credentials.StoreFactory + DefaultCredentialContexts []string + WorkspacePath string + DeleteWorkspaceOnClose bool + ExtraEnv []string + close func() } type Options struct { - Cache cache.Options - OpenAI openai.Options - Monitor monitor.Options - Runner runner.Options - CredentialContext string - Quiet *bool - Workspace string - DisablePromptServer bool - Env []string -} - -func complete(opts ...Options) Options { + Cache cache.Options + OpenAI openai.Options + Monitor monitor.Options + Runner runner.Options + DefaultModelProvider string + CredentialContexts []string + Quiet *bool + Workspace string + DisablePromptServer bool + SystemToolsDir string + Env []string + CredentialStore string + CredentialToolsEnv []string +} + +func Complete(opts ...Options) Options { var result Options for _, opt := range opts { result.Cache = cache.Complete(result.Cache, opt.Cache) @@ -59,11 +71,15 @@ func complete(opts ...Options) Options { result.Runner = runner.Complete(result.Runner, opt.Runner) result.OpenAI = openai.Complete(result.OpenAI, opt.OpenAI) - result.CredentialContext = types.FirstSet(opt.CredentialContext, result.CredentialContext) + result.SystemToolsDir = types.FirstSet(opt.SystemToolsDir, result.SystemToolsDir) + result.CredentialContexts = opt.CredentialContexts result.Quiet = types.FirstSet(opt.Quiet, result.Quiet) result.Workspace = types.FirstSet(opt.Workspace, result.Workspace) result.Env = append(result.Env, opt.Env...) + result.CredentialToolsEnv = append(result.CredentialToolsEnv, opt.CredentialToolsEnv...) result.DisablePromptServer = types.FirstSet(opt.DisablePromptServer, result.DisablePromptServer) + result.DefaultModelProvider = types.FirstSet(opt.DefaultModelProvider, result.DefaultModelProvider) + result.CredentialStore = types.FirstSet(opt.CredentialStore, result.CredentialStore) } if result.Quiet == nil { @@ -72,15 +88,18 @@ func complete(opts ...Options) Options { if len(result.Env) == 0 { result.Env = os.Environ() } - if result.CredentialContext == "" { - result.CredentialContext = "default" + if len(result.CredentialToolsEnv) == 0 { + result.CredentialToolsEnv = result.Env + } + if len(result.CredentialContexts) == 0 { + result.CredentialContexts = []string{credentials.DefaultCredentialContext} } return result } func New(ctx context.Context, o ...Options) (*GPTScript, error) { - opts := complete(o...) + opts := Complete(o...) registry := llm.NewRegistry() cacheClient, err := cache.New(opts.Cache) @@ -93,29 +112,41 @@ func New(ctx context.Context, o ...Options) (*GPTScript, error) { return nil, err } + if opts.CredentialStore != "" { + cliCfg.CredentialsStore = opts.CredentialStore + } + if opts.Runner.RuntimeManager == nil { - opts.Runner.RuntimeManager = runtimes.Default(cacheClient.CacheDir()) + opts.Runner.RuntimeManager = runtimes.Default(cacheClient.CacheDir(), opts.SystemToolsDir) } - if err := opts.Runner.RuntimeManager.SetUpCredentialHelpers(context.Background(), cliCfg, opts.Env); err != nil { + simplerRunner, err := newSimpleRunner(cacheClient, opts.Runner.RuntimeManager, opts.CredentialToolsEnv) + if err != nil { return nil, err } - credStore, err := credentials.NewStore(cliCfg, opts.Runner.RuntimeManager, opts.CredentialContext, cacheClient.CacheDir()) + storeFactory, err := credentials.NewFactory(ctx, cliCfg, opts.Runner.CredentialOverrides, simplerRunner) if err != nil { return nil, err } - oaiClient, err := openai.NewClient(ctx, credStore, opts.OpenAI, openai.Options{ - Cache: cacheClient, - SetSeed: true, - }) + credStore, err := storeFactory.NewStore(opts.CredentialContexts) if err != nil { return nil, err } - if err := registry.AddClient(oaiClient); err != nil { - return nil, err + if opts.DefaultModelProvider == "" { + oaiClient, err := openai.NewClient(ctx, credStore, opts.OpenAI, openai.Options{ + Cache: cacheClient, + SetSeed: true, + }) + if err != nil { + return nil, err + } + + if err := registry.AddClient(oaiClient); err != nil { + return nil, err + } } if opts.Runner.MonitorFactory == nil { @@ -143,24 +174,40 @@ func New(ctx context.Context, o ...Options) (*GPTScript, error) { fullEnv := append(opts.Env, extraEnv...) - remoteClient := remote.New(runner, fullEnv, cacheClient, credStore) + remoteClient := remote.New(runner, fullEnv, cacheClient, credStore, opts.DefaultModelProvider) if err := registry.AddClient(remoteClient); err != nil { closeServer() return nil, err } return &GPTScript{ - Registry: registry, - Runner: runner, - Cache: cacheClient, - WorkspacePath: opts.Workspace, - DeleteWorkspaceOnClose: opts.Workspace == "", - ExtraEnv: extraEnv, - close: closeServer, + Registry: registry, + Runner: runner, + Cache: cacheClient, + CredentialStoreFactory: storeFactory, + DefaultCredentialContexts: opts.CredentialContexts, + WorkspacePath: opts.Workspace, + DeleteWorkspaceOnClose: opts.Workspace == "", + ExtraEnv: extraEnv, + close: closeServer, }, nil } func (g *GPTScript) getEnv(env []string) ([]string, error) { + var ( + id string + ) + + scheme, rest, isScheme := strings.Cut(g.WorkspacePath, "://") + if isScheme && scheme == "directory" { + id = g.WorkspacePath + g.WorkspacePath = rest + } else if isScheme { + id = g.WorkspacePath + g.WorkspacePath = "" + g.DeleteWorkspaceOnClose = true + } + if g.WorkspacePath == "" { var err error g.WorkspacePath, err = os.MkdirTemp("", "gptscript-workspace-*") @@ -177,9 +224,12 @@ func (g *GPTScript) getEnv(env []string) ([]string, error) { if err := os.MkdirAll(g.WorkspacePath, 0700); err != nil { return nil, err } + if id == "" { + id = "directory://" + g.WorkspacePath + } return slices.Concat(g.ExtraEnv, env, []string{ fmt.Sprintf("GPTSCRIPT_WORKSPACE_DIR=%s", g.WorkspacePath), - fmt.Sprintf("GPTSCRIPT_WORKSPACE_ID=%s", hash.ID(g.WorkspacePath)), + fmt.Sprintf("GPTSCRIPT_WORKSPACE_ID=%s", id), }), nil } @@ -195,25 +245,25 @@ func makeAbsolute(path string) (string, error) { return filepath.Abs(path) } -func (g *GPTScript) Chat(ctx context.Context, prevState runner.ChatState, prg types.Program, envs []string, input string) (runner.ChatResponse, error) { +func (g *GPTScript) Chat(ctx context.Context, prevState runner.ChatState, prg types.Program, envs []string, input string, opts runner.RunOptions) (runner.ChatResponse, error) { envs, err := g.getEnv(envs) if err != nil { return runner.ChatResponse{}, err } - return g.Runner.Chat(ctx, prevState, prg, envs, input) + return g.Runner.Chat(ctx, prevState, prg, envs, input, opts) } -func (g *GPTScript) Run(ctx context.Context, prg types.Program, envs []string, input string) (string, error) { +func (g *GPTScript) Run(ctx context.Context, prg types.Program, envs []string, input string, opts runner.RunOptions) (string, error) { envs, err := g.getEnv(envs) if err != nil { return "", err } - return g.Runner.Run(ctx, prg, envs, input) + return g.Runner.Run(ctx, prg, envs, input, opts) } -func (g *GPTScript) Close(closeDaemons bool) { +func (g *GPTScript) Close(closeDaemonsAndMCP bool) { if g.DeleteWorkspaceOnClose && g.WorkspacePath != "" { if err := os.RemoveAll(g.WorkspacePath); err != nil { log.Errorf("failed to delete workspace %s: %s", g.WorkspacePath, err) @@ -222,8 +272,11 @@ func (g *GPTScript) Close(closeDaemons bool) { g.close() - if closeDaemons { + if closeDaemonsAndMCP { engine.CloseDaemons() + if err := mcp.DefaultLoader.Close(); err != nil { + log.Errorf("failed to close MCP loader: %s", err) + } } } @@ -238,6 +291,78 @@ func (g *GPTScript) ListTools(_ context.Context, prg types.Program) []types.Tool return prg.TopLevelTools() } -func (g *GPTScript) ListModels(ctx context.Context, providers ...string) ([]string, error) { +func (g *GPTScript) ListModels(ctx context.Context, providers ...string) ([]openai2.Model, error) { return g.Registry.ListModels(ctx, providers...) } + +type simpleRunner struct { + cache *cache.Client + runner *runner.Runner + env []string +} + +func newSimpleRunner(cache *cache.Client, rm engine.RuntimeManager, env []string) (*simpleRunner, error) { + runner, err := runner.New(noopModel{}, credentials.NoopStore{}, runner.Options{ + RuntimeManager: rm, + MonitorFactory: simpleMonitorFactory{}, + }) + if err != nil { + return nil, err + } + return &simpleRunner{ + cache: cache, + runner: runner, + env: env, + }, nil +} + +func (s *simpleRunner) Load(ctx context.Context, toolName string) (prg types.Program, err error) { + return loader.Program(ctx, toolName, "", loader.Options{ + Cache: s.cache, + }) +} + +func (s *simpleRunner) Run(ctx context.Context, prg types.Program, input string) (output string, err error) { + return s.runner.Run(ctx, prg, s.env, input, runner.RunOptions{}) +} + +type noopModel struct { +} + +func (n noopModel) Call(_ context.Context, _ types.CompletionRequest, _ []string, _ chan<- types.CompletionStatus) (*types.CompletionMessage, error) { + return nil, errors.New("unsupported") +} + +func (n noopModel) ProxyInfo([]string) (string, string, error) { + return "", "", errors.New("unsupported") +} + +type simpleMonitorFactory struct { +} + +func (s simpleMonitorFactory) Start(_ context.Context, _ *types.Program, _ []string, _ string) (runner.Monitor, error) { + return simpleMonitor{}, nil +} + +func (s simpleMonitorFactory) Pause() func() { + //TODO implement me + panic("implement me") +} + +type simpleMonitor struct { +} + +func (s simpleMonitor) Stop(_ context.Context, _ string, _ error) { +} + +func (s simpleMonitor) Event(event runner.Event) { + if event.Type == runner.EventTypeCallProgress { + if !strings.HasPrefix(event.Content, "{") { + fmt.Println(event.Content) + } + } +} + +func (s simpleMonitor) Pause() func() { + return func() {} +} diff --git a/pkg/hash/sha256.go b/pkg/hash/sha256.go index 05209e44..93aee56a 100644 --- a/pkg/hash/sha256.go +++ b/pkg/hash/sha256.go @@ -2,8 +2,8 @@ package hash import ( "crypto/sha256" - "encoding/gob" "encoding/hex" + "encoding/json" ) func ID(parts ...string) string { @@ -26,7 +26,7 @@ func Digest(obj any) string { case string: hash.Write([]byte(v)) default: - if err := gob.NewEncoder(hash).Encode(obj); err != nil { + if err := json.NewEncoder(hash).Encode(obj); err != nil { panic(err) } } diff --git a/pkg/input/input.go b/pkg/input/input.go index 0037fa5e..f930753f 100644 --- a/pkg/input/input.go +++ b/pkg/input/input.go @@ -3,10 +3,12 @@ package input import ( "fmt" "io" + "io/fs" "os" "path/filepath" "strings" + "github.com/gptscript-ai/gptscript/internal" "github.com/gptscript-ai/gptscript/pkg/loader" "github.com/gptscript-ai/gptscript/pkg/types" ) @@ -33,7 +35,7 @@ func FromFile(file string) (string, error) { } return string(data), nil } else if file != "" { - if s, err := os.Stat(file); err == nil && s.IsDir() { + if s, err := fs.Stat(internal.FS, file); err == nil && s.IsDir() { for _, ext := range types.DefaultFiles { if _, err := os.Stat(filepath.Join(file, ext)); err == nil { file = filepath.Join(file, ext) @@ -42,7 +44,7 @@ func FromFile(file string) (string, error) { } } log.Debugf("reading file %s", file) - data, err := os.ReadFile(file) + data, err := fs.ReadFile(internal.FS, file) if err != nil { return "", fmt.Errorf("reading %s: %w", file, err) } @@ -53,13 +55,13 @@ func FromFile(file string) (string, error) { } // FromLocation takes a string that can be a file path or a URL to a file and returns the content of that file. -func FromLocation(s string) (string, error) { +func FromLocation(s string, disableCache bool) (string, error) { // Attempt to read the file first, if that fails, try to load the URL. Finally, // return an error if both fail. content, err := FromFile(s) if err != nil { log.Debugf("failed to read file %s (due to %v) attempting to load the URL...", s, err) - content, err = loader.ContentFromURL(s) + content, err = loader.ContentFromURL(s, disableCache) if err != nil { return "", err } diff --git a/pkg/llm/proxy.go b/pkg/llm/proxy.go new file mode 100644 index 00000000..82b33d02 --- /dev/null +++ b/pkg/llm/proxy.go @@ -0,0 +1,117 @@ +package llm + +import ( + "bytes" + "encoding/json" + "io" + "net" + "net/http" + "net/http/httputil" + "net/url" + "path" + "strings" + + "github.com/gptscript-ai/gptscript/pkg/builtin" + "github.com/gptscript-ai/gptscript/pkg/openai" +) + +func (r *Registry) ProxyInfo(env []string) (string, string, error) { + var proxyURL, proxyToken string + for _, e := range env { + if url, ok := strings.CutPrefix(e, "GPTSCRIPT_MODEL_PROVIDER_PROXY_URL="); ok { + proxyURL = url + } else if token, ok := strings.CutPrefix(e, "GPTSCRIPT_MODEL_PROVIDER_PROXY_TOKEN="); ok { + proxyToken = token + } + } + + if proxyToken != "" && proxyURL != "" { + return proxyToken, proxyURL, nil + } + + r.proxyLock.Lock() + defer r.proxyLock.Unlock() + + if r.proxyURL != "" { + return r.proxyToken, r.proxyURL, nil + } + + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return "", "", err + } + + go func() { + _ = http.Serve(l, r) + r.proxyLock.Lock() + defer r.proxyLock.Unlock() + _ = l.Close() + r.proxyURL = "" + }() + + r.proxyURL = "http://" + l.Addr().String() + return r.proxyToken, r.proxyURL, nil +} + +func (r *Registry) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if r.proxyToken != strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ") { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return + } + + inBytes, err := io.ReadAll(req.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + var ( + model string + data map[string]any + ) + + if json.Unmarshal(inBytes, &data) == nil { + model, _ = data["model"].(string) + } + + if model == "" { + model = builtin.GetDefaultModel() + } + + c, err := r.getClient(req.Context(), model, nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + oai, ok := c.(*openai.Client) + if !ok { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + + auth, targetURL := oai.ProxyInfo(nil) + if targetURL == "" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + + newURL, err := url.Parse(targetURL) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + newURL.Path = path.Join(newURL.Path, req.URL.Path) + + rp := httputil.ReverseProxy{ + Director: func(proxyReq *http.Request) { + proxyReq.Body = io.NopCloser(bytes.NewReader(inBytes)) + proxyReq.URL = newURL + proxyReq.Header.Del("Authorization") + proxyReq.Header.Add("Authorization", "Bearer "+auth) + proxyReq.Host = newURL.Hostname() + }, + } + rp.ServeHTTP(w, req) +} diff --git a/pkg/llm/registry.go b/pkg/llm/registry.go index ba648f58..d53d96b9 100644 --- a/pkg/llm/registry.go +++ b/pkg/llm/registry.go @@ -5,23 +5,33 @@ import ( "errors" "fmt" "sort" + "sync" + "github.com/google/uuid" + openai2 "github.com/gptscript-ai/chat-completion-client" + "github.com/gptscript-ai/gptscript/pkg/env" "github.com/gptscript-ai/gptscript/pkg/openai" + "github.com/gptscript-ai/gptscript/pkg/remote" "github.com/gptscript-ai/gptscript/pkg/types" ) type Client interface { - Call(ctx context.Context, messageRequest types.CompletionRequest, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) - ListModels(ctx context.Context, providers ...string) (result []string, _ error) + Call(ctx context.Context, messageRequest types.CompletionRequest, env []string, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) + ListModels(ctx context.Context, providers ...string) (result []openai2.Model, _ error) Supports(ctx context.Context, modelName string) (bool, error) } type Registry struct { - clients []Client + proxyToken string + proxyURL string + proxyLock sync.Mutex + clients []Client } func NewRegistry() *Registry { - return &Registry{} + return &Registry{ + proxyToken: env.VarOrDefault("GPTSCRIPT_INTERNAL_PROXY_TOKEN", uuid.New().String()), + } } func (r *Registry) AddClient(client Client) error { @@ -29,7 +39,7 @@ func (r *Registry) AddClient(client Client) error { return nil } -func (r *Registry) ListModels(ctx context.Context, providers ...string) (result []string, _ error) { +func (r *Registry) ListModels(ctx context.Context, providers ...string) (result []openai2.Model, _ error) { for _, v := range r.clients { models, err := v.ListModels(ctx, providers...) if err != nil { @@ -37,15 +47,90 @@ func (r *Registry) ListModels(ctx context.Context, providers ...string) (result } result = append(result, models...) } - sort.Strings(result) + sort.Slice(result, func(i, j int) bool { + return result[i].ID < result[j].ID + }) return result, nil } -func (r *Registry) Call(ctx context.Context, messageRequest types.CompletionRequest, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) { +func (r *Registry) fastPath(modelName string) Client { + // This is optimization hack to avoid doing List Models + if len(r.clients) == 1 { + return r.clients[0] + } + + if len(r.clients) != 2 { + return nil + } + + _, modelFromProvider := types.SplitToolRef(modelName) + if modelFromProvider != "" { + return nil + } + + _, ok := r.clients[0].(*openai.Client) + if !ok { + return nil + } + + _, ok = r.clients[1].(*remote.Client) + if !ok { + return nil + } + + return r.clients[0] +} + +func (r *Registry) getClient(ctx context.Context, modelName string, env []string) (Client, error) { + if c := r.fastPath(modelName); c != nil { + return c, nil + } + + var errs []error + var oaiClient *openai.Client + for _, client := range r.clients { + ok, err := client.Supports(ctx, modelName) + if err != nil { + // If we got an OpenAI invalid auth error back, store the OpenAI client for later. + if errors.Is(err, openai.InvalidAuthError{}) { + oaiClient = client.(*openai.Client) + } + + errs = append(errs, err) + } else if ok { + return client, nil + } + } + + if len(errs) > 0 && oaiClient != nil { + // Prompt the user to enter their OpenAI API key and try again. + if err := oaiClient.RetrieveAPIKey(ctx, env); err != nil { + return nil, err + } + ok, err := oaiClient.Supports(ctx, modelName) + if err != nil { + return nil, err + } else if ok { + return oaiClient, nil + } + } + + if len(errs) == 0 { + return nil, fmt.Errorf("failed to find a model provider for model [%s]", modelName) + } + + return nil, errors.Join(errs...) +} + +func (r *Registry) Call(ctx context.Context, messageRequest types.CompletionRequest, env []string, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) { if messageRequest.Model == "" { return nil, fmt.Errorf("model is required") } + if c := r.fastPath(messageRequest.Model); c != nil { + return c.Call(ctx, messageRequest, env, status) + } + var errs []error var oaiClient *openai.Client for _, client := range r.clients { @@ -58,20 +143,20 @@ func (r *Registry) Call(ctx context.Context, messageRequest types.CompletionRequ errs = append(errs, err) } else if ok { - return client.Call(ctx, messageRequest, status) + return client.Call(ctx, messageRequest, env, status) } } if len(errs) > 0 && oaiClient != nil { // Prompt the user to enter their OpenAI API key and try again. - if err := oaiClient.RetrieveAPIKey(ctx); err != nil { + if err := oaiClient.RetrieveAPIKey(ctx, env); err != nil { return nil, err } ok, err := oaiClient.Supports(ctx, messageRequest.Model) if err != nil { return nil, err } else if ok { - return oaiClient.Call(ctx, messageRequest, status) + return oaiClient.Call(ctx, messageRequest, env, status) } } diff --git a/pkg/loader/github/github.go b/pkg/loader/github/github.go index 2fb01c3d..7b6e79ec 100644 --- a/pkg/loader/github/github.go +++ b/pkg/loader/github/github.go @@ -2,6 +2,7 @@ package github import ( "context" + "crypto/tls" "encoding/json" "fmt" "io" @@ -18,52 +19,63 @@ import ( "github.com/gptscript-ai/gptscript/pkg/types" ) -const ( - GithubPrefix = "github.com/" - githubRepoURL = "https://github.com/%s/%s.git" - githubDownloadURL = "https://raw.githubusercontent.com/%s/%s/%s/%s" - githubCommitURL = "https://api.github.com/repos/%s/%s/commits/%s" -) +type Config struct { + Prefix string + RepoURL string + DownloadURL string + CommitURL string + AuthToken string +} var ( - githubAuthToken = os.Getenv("GITHUB_AUTH_TOKEN") - log = mvl.Package() + log = mvl.Package() + defaultGithubConfig = &Config{ + Prefix: "github.com/", + RepoURL: "https://github.com/%s/%s.git", + DownloadURL: "https://raw.githubusercontent.com/%s/%s/%s/%s", + CommitURL: "https://api.github.com/repos/%s/%s/commits/%s", + AuthToken: os.Getenv("GITHUB_AUTH_TOKEN"), + } ) func init() { loader.AddVSC(Load) } -func getCommitLsRemote(ctx context.Context, account, repo, ref string) (string, error) { - url := fmt.Sprintf(githubRepoURL, account, repo) +func getCommitLsRemote(ctx context.Context, account, repo, ref string, config *Config) (string, error) { + url := fmt.Sprintf(config.RepoURL, account, repo) return git.LsRemote(ctx, url, ref) } // regexp to match a git commit id var commitRegexp = regexp.MustCompile("^[a-f0-9]{40}$") -func getCommit(ctx context.Context, account, repo, ref string) (string, error) { +func getCommit(ctx context.Context, account, repo, ref string, config *Config) (string, error) { if commitRegexp.MatchString(ref) { return ref, nil } - url := fmt.Sprintf(githubCommitURL, account, repo, ref) + url := fmt.Sprintf(config.CommitURL, account, repo, ref) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", fmt.Errorf("failed to create request of %s/%s at %s: %w", account, repo, url, err) } - if githubAuthToken != "" { - req.Header.Add("Authorization", "Bearer "+githubAuthToken) + if config.AuthToken != "" { + req.Header.Add("Authorization", "Bearer "+config.AuthToken) } - resp, err := http.DefaultClient.Do(req) + client := http.DefaultClient + if req.Host == config.Prefix && strings.ToLower(os.Getenv("GH_ENTERPRISE_SKIP_VERIFY")) == "true" { + client = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} + } + resp, err := client.Do(req) if err != nil { return "", err } else if resp.StatusCode != http.StatusOK { c, _ := io.ReadAll(resp.Body) resp.Body.Close() - commit, fallBackErr := getCommitLsRemote(ctx, account, repo, ref) + commit, fallBackErr := getCommitLsRemote(ctx, account, repo, ref, config) if fallBackErr == nil { return commit, nil } @@ -88,8 +100,28 @@ func getCommit(ctx context.Context, account, repo, ref string) (string, error) { return commit.SHA, nil } -func Load(ctx context.Context, _ *cache.Client, urlName string) (string, string, *types.Repo, bool, error) { - if !strings.HasPrefix(urlName, GithubPrefix) { +func LoaderForPrefix(prefix string) func(context.Context, *cache.Client, string) (string, string, *types.Repo, bool, error) { + return func(ctx context.Context, c *cache.Client, urlName string) (string, string, *types.Repo, bool, error) { + return LoadWithConfig(ctx, c, urlName, NewGithubEnterpriseConfig(prefix)) + } +} + +func Load(ctx context.Context, c *cache.Client, urlName string) (string, string, *types.Repo, bool, error) { + return LoadWithConfig(ctx, c, urlName, defaultGithubConfig) +} + +func NewGithubEnterpriseConfig(prefix string) *Config { + return &Config{ + Prefix: prefix, + RepoURL: fmt.Sprintf("https://%s/%%s/%%s.git", prefix), + DownloadURL: fmt.Sprintf("https://raw.%s/%%s/%%s/%%s/%%s", prefix), + CommitURL: fmt.Sprintf("https://%s/api/v3/repos/%%s/%%s/commits/%%s", prefix), + AuthToken: os.Getenv("GH_ENTERPRISE_TOKEN"), + } +} + +func LoadWithConfig(ctx context.Context, _ *cache.Client, urlName string, config *Config) (string, string, *types.Repo, bool, error) { + if !strings.HasPrefix(urlName, config.Prefix) { return "", "", nil, false, nil } @@ -107,12 +139,12 @@ func Load(ctx context.Context, _ *cache.Client, urlName string) (string, string, account, repo := parts[1], parts[2] path := strings.Join(parts[3:], "/") - ref, err := getCommit(ctx, account, repo, ref) + ref, err := getCommit(ctx, account, repo, ref, config) if err != nil { return "", "", nil, false, err } - downloadURL := fmt.Sprintf(githubDownloadURL, account, repo, ref, path) + downloadURL := fmt.Sprintf(config.DownloadURL, account, repo, ref, path) if path == "" || path == "/" || !strings.Contains(parts[len(parts)-1], ".") { var ( testPath string @@ -124,13 +156,20 @@ func Load(ctx context.Context, _ *cache.Client, urlName string) (string, string, } else { testPath = path + "/" + ext } - testURL = fmt.Sprintf(githubDownloadURL, account, repo, ref, testPath) + testURL = fmt.Sprintf(config.DownloadURL, account, repo, ref, testPath) if i == len(types.DefaultFiles)-1 { // no reason to test the last one, we are just going to use it. Being that the default list is only // two elements this loop could have been one check, but hey over-engineered code ftw. break } - if resp, err := http.Head(testURL); err == nil { + headReq, err := http.NewRequest("HEAD", testURL, nil) + if err != nil { + break + } + if config.AuthToken != "" { + headReq.Header.Add("Authorization", "Bearer "+config.AuthToken) + } + if resp, err := http.DefaultClient.Do(headReq); err == nil { _ = resp.Body.Close() if resp.StatusCode == 200 { break @@ -141,9 +180,9 @@ func Load(ctx context.Context, _ *cache.Client, urlName string) (string, string, path = testPath } - return downloadURL, githubAuthToken, &types.Repo{ + return downloadURL, config.AuthToken, &types.Repo{ VCS: "git", - Root: fmt.Sprintf(githubRepoURL, account, repo), + Root: fmt.Sprintf(config.RepoURL, account, repo), Path: gpath.Dir(path), Name: gpath.Base(path), Revision: ref, diff --git a/pkg/loader/github/github_test.go b/pkg/loader/github/github_test.go index d627ee5e..483722bc 100644 --- a/pkg/loader/github/github_test.go +++ b/pkg/loader/github/github_test.go @@ -2,6 +2,10 @@ package github import ( "context" + "fmt" + "net/http" + "net/http/httptest" + "os" "testing" "github.com/gptscript-ai/gptscript/pkg/types" @@ -44,3 +48,56 @@ func TestLoad(t *testing.T) { Revision: "172dfb00b48c6adbbaa7e99270933f95887d1b91", }).Equal(t, repo) } + +func TestLoad_GithubEnterprise(t *testing.T) { + gheToken := "mytoken" + os.Setenv("GH_ENTERPRISE_SKIP_VERIFY", "true") + os.Setenv("GH_ENTERPRISE_TOKEN", gheToken) + s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v3/repos/gptscript-ai/gptscript/commits/172dfb0": + _, _ = w.Write([]byte(`{"sha": "172dfb00b48c6adbbaa7e99270933f95887d1b91"}`)) + default: + w.WriteHeader(404) + } + })) + defer s.Close() + + serverAddr := s.Listener.Addr().String() + + url, token, repo, ok, err := LoadWithConfig(context.Background(), nil, fmt.Sprintf("%s/gptscript-ai/gptscript/pkg/loader/testdata/tool@172dfb0", serverAddr), NewGithubEnterpriseConfig(serverAddr)) + require.NoError(t, err) + assert.True(t, ok) + autogold.Expect(fmt.Sprintf("https://raw.%s/gptscript-ai/gptscript/172dfb00b48c6adbbaa7e99270933f95887d1b91/pkg/loader/testdata/tool/tool.gpt", serverAddr)).Equal(t, url) + autogold.Expect(&types.Repo{ + VCS: "git", Root: fmt.Sprintf("https://%s/gptscript-ai/gptscript.git", serverAddr), + Path: "pkg/loader/testdata/tool", + Name: "tool.gpt", + Revision: "172dfb00b48c6adbbaa7e99270933f95887d1b91", + }).Equal(t, repo) + autogold.Expect(gheToken).Equal(t, token) + + url, token, repo, ok, err = Load(context.Background(), nil, "github.com/gptscript-ai/gptscript/pkg/loader/testdata/agent@172dfb0") + require.NoError(t, err) + assert.True(t, ok) + autogold.Expect("https://raw.githubusercontent.com/gptscript-ai/gptscript/172dfb00b48c6adbbaa7e99270933f95887d1b91/pkg/loader/testdata/agent/agent.gpt").Equal(t, url) + autogold.Expect(&types.Repo{ + VCS: "git", Root: "https://github.com/gptscript-ai/gptscript.git", + Path: "pkg/loader/testdata/agent", + Name: "agent.gpt", + Revision: "172dfb00b48c6adbbaa7e99270933f95887d1b91", + }).Equal(t, repo) + autogold.Expect("").Equal(t, token) + + url, token, repo, ok, err = Load(context.Background(), nil, "github.com/gptscript-ai/gptscript/pkg/loader/testdata/bothtoolagent@172dfb0") + require.NoError(t, err) + assert.True(t, ok) + autogold.Expect("https://raw.githubusercontent.com/gptscript-ai/gptscript/172dfb00b48c6adbbaa7e99270933f95887d1b91/pkg/loader/testdata/bothtoolagent/agent.gpt").Equal(t, url) + autogold.Expect(&types.Repo{ + VCS: "git", Root: "https://github.com/gptscript-ai/gptscript.git", + Path: "pkg/loader/testdata/bothtoolagent", + Name: "agent.gpt", + Revision: "172dfb00b48c6adbbaa7e99270933f95887d1b91", + }).Equal(t, repo) + autogold.Expect("").Equal(t, token) +} diff --git a/pkg/loader/loader.go b/pkg/loader/loader.go index c12f976f..626cc87f 100644 --- a/pkg/loader/loader.go +++ b/pkg/loader/loader.go @@ -8,30 +8,39 @@ import ( "fmt" "io" "io/fs" + "os" "path" "path/filepath" - "strconv" "strings" "time" "unicode/utf8" - "github.com/getkin/kin-openapi/openapi2" - "github.com/getkin/kin-openapi/openapi2conv" "github.com/getkin/kin-openapi/openapi3" "github.com/gptscript-ai/gptscript/internal" - "github.com/gptscript-ai/gptscript/pkg/assemble" "github.com/gptscript-ai/gptscript/pkg/builtin" "github.com/gptscript-ai/gptscript/pkg/cache" "github.com/gptscript-ai/gptscript/pkg/hash" + "github.com/gptscript-ai/gptscript/pkg/mcp" + "github.com/gptscript-ai/gptscript/pkg/openapi" "github.com/gptscript-ai/gptscript/pkg/parser" "github.com/gptscript-ai/gptscript/pkg/system" "github.com/gptscript-ai/gptscript/pkg/types" - "gopkg.in/yaml.v3" - kyaml "sigs.k8s.io/yaml" ) const CacheTimeout = time.Hour +var Remap = map[string]string{} + +func init() { + remap := os.Getenv("GPTSCRIPT_TOOL_REMAP") + for _, pair := range strings.Split(remap, ",") { + k, v, ok := strings.Cut(pair, "=") + if ok { + Remap[k] = v + } + } +} + type source struct { // Content The content of the source Content []byte @@ -71,8 +80,23 @@ func openFile(path string) (io.ReadCloser, bool, error) { } func loadLocal(base *source, name string) (*source, bool, error) { - // We want to keep all strings in / format, and only convert to platform specific when reading - filePath := path.Join(base.Path, name) + var remapped bool + if !strings.HasPrefix(name, ".") { + for k, v := range Remap { + if strings.HasPrefix(name, k) { + name = v + name[len(k):] + remapped = true + break + } + } + } + + filePath := name + if !remapped && !filepath.IsAbs(name) { + // We want to keep all strings in / format, and only convert to platform specific when reading + // This is why we use path instead of filepath. + filePath = path.Join(base.Path, name) + } if s, err := fs.Stat(internal.FS, filepath.Clean(filePath)); err == nil && s.IsDir() { for _, def := range types.DefaultFiles { @@ -108,36 +132,6 @@ func loadLocal(base *source, name string) (*source, bool, error) { }, true, nil } -func loadProgram(data []byte, into *types.Program, targetToolName string) (types.Tool, error) { - var ext types.Program - - if err := json.Unmarshal(data[len(assemble.Header):], &ext); err != nil { - return types.Tool{}, err - } - - into.ToolSet = make(map[string]types.Tool, len(ext.ToolSet)) - for k, v := range ext.ToolSet { - if builtinTool, ok := builtin.Builtin(k); ok { - v = builtinTool - } - into.ToolSet[k] = v - } - - tool := into.ToolSet[ext.EntryToolID] - if targetToolName == "" { - return tool, nil - } - - tool, ok := into.ToolSet[tool.LocalTools[strings.ToLower(targetToolName)]] - if !ok { - return tool, &types.ErrToolNotFound{ - ToolName: targetToolName, - } - } - - return tool, nil -} - func loadOpenAPI(prg *types.Program, data []byte) *openapi3.T { var ( openAPICacheKey = hash.Digest(data) @@ -153,33 +147,8 @@ func loadOpenAPI(prg *types.Program, data []byte) *openapi3.T { prg.OpenAPICache = map[string]any{} } - switch isOpenAPI(data) { - case 2: - // Convert OpenAPI v2 to v3 - jsondata := data - if !json.Valid(data) { - jsondata, err = kyaml.YAMLToJSON(data) - if err != nil { - return nil - } - } - - doc := &openapi2.T{} - if err := doc.UnmarshalJSON(jsondata); err != nil { - return nil - } - - openAPIDocument, err = openapi2conv.ToV3(doc) - if err != nil { - return nil - } - case 3: - // Use OpenAPI v3 as is - openAPIDocument, err = openapi3.NewLoader().LoadFromData(data) - if err != nil { - return nil - } - default: + openAPIDocument, err = openapi.LoadFromBytes(data) + if err != nil { return nil } @@ -187,25 +156,37 @@ func loadOpenAPI(prg *types.Program, data []byte) *openapi3.T { return openAPIDocument } -func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, targetToolName string) ([]types.Tool, error) { - data := base.Content - - if bytes.HasPrefix(data, assemble.Header) { - tool, err := loadProgram(data, prg, targetToolName) - if err != nil { - return nil, err +func processMCP(ctx context.Context, tool []types.Tool, mcpLoader MCPLoader) (result []types.Tool, _ error) { + for _, t := range tool { + if t.IsMCP() { + mcpTools, err := mcpLoader.Load(ctx, t) + if err != nil { + return nil, fmt.Errorf("error loading MCP tools: %w", err) + } + result = append(result, mcpTools...) + } else { + result = append(result, t) } - return []types.Tool{tool}, nil } - var tools []types.Tool + return result, nil +} + +func readTool(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, targetToolName, defaultModel string) ([]types.Tool, error) { + data := base.Content + + var ( + tools []types.Tool + isOpenAPI bool + ) if openAPIDocument := loadOpenAPI(prg, data); openAPIDocument != nil { + isOpenAPI = true var err error if base.Remote { - tools, err = getOpenAPITools(openAPIDocument, base.Location) + tools, err = getOpenAPITools(openAPIDocument, base.Location, base.Location, targetToolName) } else { - tools, err = getOpenAPITools(openAPIDocument, "") + tools, err = getOpenAPITools(openAPIDocument, "", base.Name, targetToolName) } if err != nil { return nil, fmt.Errorf("error parsing OpenAPI definition: %w", err) @@ -228,11 +209,19 @@ func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base // If we didn't get any tools from trying to parse it as OpenAPI, try to parse it as a GPTScript if len(tools) == 0 { var err error - tools, err = parser.ParseTools(bytes.NewReader(data), parser.Options{ - AssignGlobals: true, - }) - if err != nil { - return nil, err + _, marshaled, ok := strings.Cut(string(data), "#!GPTSCRIPT") + if ok { + err = json.Unmarshal([]byte(marshaled), &tools) + if err != nil { + return nil, fmt.Errorf("error parsing marshalled script: %w", err) + } + } else { + tools, err = parser.ParseTools(bytes.NewReader(data), parser.Options{ + AssignGlobals: true, + }) + if err != nil { + return nil, err + } } } @@ -240,6 +229,11 @@ func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base return nil, fmt.Errorf("no tools found in %s", base) } + tools, err := processMCP(ctx, tools, mcp) + if err != nil { + return nil, err + } + var ( localTools = types.ToolSet{} targetTools []types.Tool @@ -253,56 +247,71 @@ func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base // Probably a better way to come up with an ID tool.ID = tool.Source.Location + ":" + tool.Name - if i == 0 && targetToolName == "" { - targetTools = append(targetTools, tool) - } - - if i != 0 && tool.Parameters.Name == "" { + if i != 0 && tool.Name == "" { return nil, parser.NewErrLine(tool.Source.Location, tool.Source.LineNo, fmt.Errorf("only the first tool in a file can have no name")) } - if i != 0 && tool.Parameters.GlobalModelName != "" { + if i != 0 && tool.GlobalModelName != "" { return nil, parser.NewErrLine(tool.Source.Location, tool.Source.LineNo, fmt.Errorf("only the first tool in a file can have global model name")) } - if i != 0 && len(tool.Parameters.GlobalTools) > 0 { + if i != 0 && len(tool.GlobalTools) > 0 { return nil, parser.NewErrLine(tool.Source.Location, tool.Source.LineNo, fmt.Errorf("only the first tool in a file can have global tools")) } - if targetToolName != "" && tool.Parameters.Name != "" { - if strings.EqualFold(tool.Parameters.Name, targetToolName) { + // Determine targetTools + if isOpenAPI && os.Getenv("GPTSCRIPT_OPENAPI_REVAMP") == "true" { + targetTools = append(targetTools, tool) + } else { + if i == 0 && targetToolName == "" { targetTools = append(targetTools, tool) - } else if strings.Contains(targetToolName, "*") { - match, err := filepath.Match(strings.ToLower(targetToolName), strings.ToLower(tool.Parameters.Name)) - if err != nil { - return nil, parser.NewErrLine(tool.Source.Location, tool.Source.LineNo, err) - } - if match { + } + + if targetToolName != "" && tool.Name != "" { + if strings.EqualFold(tool.Name, targetToolName) { targetTools = append(targetTools, tool) + } else if strings.Contains(targetToolName, "*") { + var patterns []string + if strings.Contains(targetToolName, "|") { + patterns = strings.Split(targetToolName, "|") + } else { + patterns = []string{targetToolName} + } + + for _, pattern := range patterns { + match, err := filepath.Match(strings.ToLower(pattern), strings.ToLower(tool.Name)) + if err != nil { + return nil, parser.NewErrLine(tool.Source.Location, tool.Source.LineNo, err) + } + if match { + targetTools = append(targetTools, tool) + break + } + } } } } - if existing, ok := localTools[strings.ToLower(tool.Parameters.Name)]; ok { + if existing, ok := localTools[strings.ToLower(tool.Name)]; ok { return nil, parser.NewErrLine(tool.Source.Location, tool.Source.LineNo, - fmt.Errorf("duplicate tool name [%s] in %s found at lines %d and %d", tool.Parameters.Name, tool.Source.Location, + fmt.Errorf("duplicate tool name [%s] in %s found at lines %d and %d", tool.Name, tool.Source.Location, tool.Source.LineNo, existing.Source.LineNo)) } - localTools[strings.ToLower(tool.Parameters.Name)] = tool + localTools[strings.ToLower(tool.Name)] = tool } - return linkAll(ctx, cache, prg, base, targetTools, localTools) + return linkAll(ctx, cache, mcp, prg, base, targetTools, localTools, defaultModel) } -func linkAll(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, tools []types.Tool, localTools types.ToolSet) (result []types.Tool, _ error) { +func linkAll(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, tools []types.Tool, localTools types.ToolSet, defaultModel string) (result []types.Tool, _ error) { localToolsMapping := make(map[string]string, len(tools)) for _, localTool := range localTools { - localToolsMapping[strings.ToLower(localTool.Parameters.Name)] = localTool.ID + localToolsMapping[strings.ToLower(localTool.Name)] = localTool.ID } for _, tool := range tools { - tool, err := link(ctx, cache, prg, base, tool, localTools, localToolsMapping) + tool, err := link(ctx, cache, mcp, prg, base, tool, localTools, localToolsMapping, defaultModel) if err != nil { return nil, err } @@ -311,7 +320,7 @@ func linkAll(ctx context.Context, cache *cache.Client, prg *types.Program, base return } -func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, tool types.Tool, localTools types.ToolSet, localToolsMapping map[string]string) (types.Tool, error) { +func link(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, tool types.Tool, localTools types.ToolSet, localToolsMapping map[string]string, defaultModel string) (types.Tool, error) { if existing, ok := prg.ToolSet[tool.ID]; ok { return existing, nil } @@ -327,7 +336,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so // The below is done in two loops so that local names stay as the tool names // and don't get mangled by external references - for _, targetToolName := range tool.Parameters.ToolRefNames() { + for _, targetToolName := range tool.ToolRefNames() { noArgs, _ := types.SplitArg(targetToolName) localTool, ok := localTools[strings.ToLower(noArgs)] if ok { @@ -336,7 +345,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so linkedTool = existing } else { var err error - linkedTool, err = link(ctx, cache, prg, base, localTool, localTools, localToolsMapping) + linkedTool, err = link(ctx, cache, mcp, prg, base, localTool, localTools, localToolsMapping, defaultModel) if err != nil { return types.Tool{}, fmt.Errorf("failed linking %s at %s: %w", targetToolName, base, err) } @@ -346,7 +355,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so toolNames[targetToolName] = struct{}{} } else { toolName, subTool := types.SplitToolRef(targetToolName) - resolvedTools, err := resolve(ctx, cache, prg, base, toolName, subTool) + resolvedTools, err := resolve(ctx, cache, mcp, prg, base, toolName, subTool, defaultModel) if err != nil { return types.Tool{}, fmt.Errorf("failed resolving %s from %s: %w", targetToolName, base, err) } @@ -358,6 +367,10 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so tool.LocalTools = localToolsMapping + if tool.ModelName == "" { + tool.ModelName = defaultModel + } + tool = builtin.SetDefaults(tool) prg.ToolSet[tool.ID] = tool @@ -373,13 +386,21 @@ func ProgramFromSource(ctx context.Context, content, subToolName string, opts .. } opt := complete(opts...) + var locationPath, locationName string + if opt.Location != "" { + locationPath = path.Dir(opt.Location) + locationName = path.Base(opt.Location) + } + prg := types.Program{ ToolSet: types.ToolSet{}, } - tools, err := readTool(ctx, opt.Cache, &prg, &source{ + tools, err := readTool(ctx, opt.Cache, opt.MCPLoader, &prg, &source{ Content: []byte(content), - Location: "inline", - }, subToolName) + Path: locationPath, + Name: locationName, + Location: opt.Location, + }, subToolName, opt.DefaultModel) if err != nil { return types.Program{}, err } @@ -388,12 +409,35 @@ func ProgramFromSource(ctx context.Context, content, subToolName string, opts .. } type Options struct { - Cache *cache.Client + Cache *cache.Client + Location string + DefaultModel string + MCPLoader MCPLoader +} + +type MCPLoader interface { + Load(ctx context.Context, tool types.Tool) ([]types.Tool, error) + Close() error } func complete(opts ...Options) (result Options) { for _, opt := range opts { result.Cache = types.FirstSet(opt.Cache, result.Cache) + result.Location = types.FirstSet(opt.Location, result.Location) + result.DefaultModel = types.FirstSet(opt.DefaultModel, result.DefaultModel) + result.MCPLoader = types.FirstSet(opt.MCPLoader, result.MCPLoader) + } + + if result.Location == "" { + result.Location = "inline" + } + + if result.DefaultModel == "" { + result.DefaultModel = builtin.GetDefaultModel() + } + + if result.MCPLoader == nil { + result.MCPLoader = mcp.DefaultLoader } return @@ -419,7 +463,7 @@ func Program(ctx context.Context, name, subToolName string, opts ...Options) (ty Name: name, ToolSet: types.ToolSet{}, } - tools, err := resolve(ctx, opt.Cache, &prg, &source{}, name, subToolName) + tools, err := resolve(ctx, opt.Cache, opt.MCPLoader, &prg, &source{}, name, subToolName, opt.DefaultModel) if err != nil { return types.Program{}, err } @@ -427,9 +471,9 @@ func Program(ctx context.Context, name, subToolName string, opts ...Options) (ty return prg, nil } -func resolve(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, name, subTool string) ([]types.Tool, error) { +func resolve(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, name, subTool, defaultModel string) ([]types.Tool, error) { if subTool == "" { - t, ok := builtin.Builtin(name) + t, ok := builtin.DefaultModel(name, defaultModel) if ok { prg.ToolSet[t.ID] = t return []types.Tool{t}, nil @@ -441,7 +485,7 @@ func resolve(ctx context.Context, cache *cache.Client, prg *types.Program, base return nil, err } - result, err := readTool(ctx, cache, prg, s, subTool) + result, err := readTool(ctx, cache, mcp, prg, s, subTool, defaultModel) if err != nil { return nil, err } @@ -473,42 +517,3 @@ func input(ctx context.Context, cache *cache.Client, base *source, name string) return nil, fmt.Errorf("can not load tools path=%s name=%s", base.Path, name) } - -// isOpenAPI checks if the data is an OpenAPI definition and returns the version if it is. -func isOpenAPI(data []byte) int { - var fragment struct { - Paths map[string]any `json:"paths,omitempty"` - Swagger string `json:"swagger,omitempty"` - OpenAPI string `json:"openapi,omitempty"` - } - - if err := json.Unmarshal(data, &fragment); err != nil { - if err := yaml.Unmarshal(data, &fragment); err != nil { - return 0 - } - } - if len(fragment.Paths) == 0 { - return 0 - } - - if v, _, _ := strings.Cut(fragment.OpenAPI, "."); v != "" { - ver, err := strconv.Atoi(v) - if err != nil { - log.Debugf("invalid OpenAPI version: openapi=%q", fragment.OpenAPI) - return 0 - } - return ver - } - - if v, _, _ := strings.Cut(fragment.Swagger, "."); v != "" { - ver, err := strconv.Atoi(v) - if err != nil { - log.Debugf("invalid Swagger version: swagger=%q", fragment.Swagger) - return 0 - } - return ver - } - - log.Debugf("no OpenAPI version found in input data: openapi=%q, swagger=%q", fragment.OpenAPI, fragment.Swagger) - return 0 -} diff --git a/pkg/loader/loader_test.go b/pkg/loader/loader_test.go index a5c328b5..a39c2df9 100644 --- a/pkg/loader/loader_test.go +++ b/pkg/loader/loader_test.go @@ -10,7 +10,7 @@ import ( "path/filepath" "testing" - "github.com/gptscript-ai/gptscript/pkg/types" + "github.com/gptscript-ai/gptscript/pkg/openapi" "github.com/hexops/autogold/v2" "github.com/stretchr/testify/require" ) @@ -54,61 +54,20 @@ Stuff func TestIsOpenAPI(t *testing.T) { datav2, err := os.ReadFile("testdata/openapi_v2.yaml") require.NoError(t, err) - v := isOpenAPI(datav2) + v := openapi.IsOpenAPI(datav2) require.Equal(t, 2, v, "(yaml) expected openapi v2") datav2, err = os.ReadFile("testdata/openapi_v2.json") require.NoError(t, err) - v = isOpenAPI(datav2) + v = openapi.IsOpenAPI(datav2) require.Equal(t, 2, v, "(json) expected openapi v2") datav3, err := os.ReadFile("testdata/openapi_v3.yaml") require.NoError(t, err) - v = isOpenAPI(datav3) + v = openapi.IsOpenAPI(datav3) require.Equal(t, 3, v, "(json) expected openapi v3") } -func TestLoadOpenAPI(t *testing.T) { - numOpenAPITools := func(set types.ToolSet) int { - num := 0 - for _, v := range set { - if v.IsOpenAPI() { - num++ - } - } - return num - } - - prgv3 := types.Program{ - ToolSet: types.ToolSet{}, - } - datav3, err := os.ReadFile("testdata/openapi_v3.yaml") - require.NoError(t, err) - _, err = readTool(context.Background(), nil, &prgv3, &source{Content: datav3}, "") - require.NoError(t, err, "failed to read openapi v3") - require.Equal(t, 3, numOpenAPITools(prgv3.ToolSet), "expected 3 openapi tools") - - prgv2json := types.Program{ - ToolSet: types.ToolSet{}, - } - datav2, err := os.ReadFile("testdata/openapi_v2.json") - require.NoError(t, err) - _, err = readTool(context.Background(), nil, &prgv2json, &source{Content: datav2}, "") - require.NoError(t, err, "failed to read openapi v2") - require.Equal(t, 3, numOpenAPITools(prgv2json.ToolSet), "expected 3 openapi tools") - - prgv2yaml := types.Program{ - ToolSet: types.ToolSet{}, - } - datav2, err = os.ReadFile("testdata/openapi_v2.yaml") - require.NoError(t, err) - _, err = readTool(context.Background(), nil, &prgv2yaml, &source{Content: datav2}, "") - require.NoError(t, err, "failed to read openapi v2 (yaml)") - require.Equal(t, 3, numOpenAPITools(prgv2yaml.ToolSet), "expected 3 openapi tools") - - require.EqualValuesf(t, prgv2json.ToolSet, prgv2yaml.ToolSet, "expected same toolset for openapi v2 json and yaml") -} - func TestHelloWorld(t *testing.T) { prg, err := Program(context.Background(), "https://raw.githubusercontent.com/ibuildthecloud/test/bafe5a62174e8a0ea162277dcfe3a2ddb7eea928/example/sub/tool.gpt", @@ -172,13 +131,13 @@ func TestHelloWorld(t *testing.T) { "modelName": "gpt-4o", "internalPrompt": null, "arguments": { + "type": "object", "properties": { "input": { - "description": "Any string", - "type": "string" + "type": "string", + "description": "Any string" } - }, - "type": "object" + } }, "instructions": "echo \"${input}\"", "id": "https://get.gptscript.ai/echo.gpt:", diff --git a/pkg/loader/openapi.go b/pkg/loader/openapi.go index 8790fbb7..ce8c5dc6 100644 --- a/pkg/loader/openapi.go +++ b/pkg/loader/openapi.go @@ -4,21 +4,30 @@ import ( "encoding/json" "fmt" "net/url" + "os" + "regexp" "slices" "sort" "strings" "time" "github.com/getkin/kin-openapi/openapi3" - "github.com/gptscript-ai/gptscript/pkg/engine" + "github.com/gptscript-ai/gptscript/pkg/openapi" "github.com/gptscript-ai/gptscript/pkg/types" + "github.com/modelcontextprotocol/go-sdk/jsonschema" ) +var toolNameRegex = regexp.MustCompile(`[^a-zA-Z0-9_-]+`) + // getOpenAPITools parses an OpenAPI definition and generates a set of tools from it. // Each operation will become a tool definition. // The tool's Instructions will be in the format "#!sys.openapi '{JSON Instructions}'", -// where the JSON Instructions are a JSON-serialized engine.OpenAPIInstructions struct. -func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { +// where the JSON Instructions are a JSON-serialized openapi.OperationInfo struct. +func getOpenAPITools(t *openapi3.T, defaultHost, source, targetToolName string) ([]types.Tool, error) { + if os.Getenv("GPTSCRIPT_OPENAPI_REVAMP") == "true" { + return getOpenAPIToolsRevamp(t, source, targetToolName) + } + if log.IsDebug() { start := time.Now() defer func() { @@ -48,7 +57,7 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { for _, item := range t.Security { current := map[string]struct{}{} for name := range item { - if scheme, ok := t.Components.SecuritySchemes[name]; ok && slices.Contains(engine.SupportedSecurityTypes, scheme.Value.Type) { + if scheme, ok := t.Components.SecuritySchemes[name]; ok && slices.Contains(openapi.GetSupportedSecurityTypes(), scheme.Value.Type) { current[name] = struct{}{} } } @@ -78,7 +87,7 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { pathObj := pathMap[pathString] // Handle path-level server override, if one exists pathServer := defaultServer - if pathObj.Servers != nil && len(pathObj.Servers) > 0 { + if len(pathObj.Servers) > 0 { pathServer, err = parseServer(pathObj.Servers[0]) if err != nil { return nil, err @@ -115,6 +124,13 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { toolDesc = toolDesc[:1024] } + toolName := operation.OperationID + if toolName == "" { + // When there is no operation ID, we use the method + path as the tool name and remove all characters + // except letters, numbers, underscores, and hyphens. + toolName = toolNameRegex.ReplaceAllString(strings.ToLower(method)+strings.ReplaceAll(pathString, "/", "_"), "") + } + var ( // auths are represented as a list of maps, where each map contains the names of the required security schemes. // Items within the same map are a logical AND. The maps themselves are a logical OR. For example: @@ -124,21 +140,20 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { // - C // D auths []map[string]struct{} - queryParameters []engine.Parameter - pathParameters []engine.Parameter - headerParameters []engine.Parameter - cookieParameters []engine.Parameter + queryParameters []openapi.Parameter + pathParameters []openapi.Parameter + headerParameters []openapi.Parameter + cookieParameters []openapi.Parameter bodyMIME string ) tool := types.Tool{ ToolDef: types.ToolDef{ Parameters: types.Parameters{ - Name: operation.OperationID, + Name: toolName, Description: toolDesc, - Arguments: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{}, - Required: []string{}, + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: make(map[string]*jsonschema.Schema), }, }, }, @@ -159,15 +174,15 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { } // Add the new arg to the tool's arguments - tool.Parameters.Arguments.Properties[param.Value.Name] = &openapi3.SchemaRef{Value: arg} + tool.Arguments.Properties[param.Value.Name] = openAPI3SchemaToJSONSchema(arg) // Check whether it is required if param.Value.Required { - tool.Parameters.Arguments.Required = append(tool.Parameters.Arguments.Required, param.Value.Name) + tool.Arguments.Required = append(tool.Arguments.Required, param.Value.Name) } // Add the parameter to the appropriate list for the tool's instructions - p := engine.Parameter{ + p := openapi.Parameter{ Name: param.Value.Name, Style: param.Value.Style, Explode: param.Value.Explode, @@ -189,11 +204,16 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { for mime, content := range operation.RequestBody.Value.Content { // Each MIME type needs to be handled individually, so we // keep a list of the ones we support. - if !slices.Contains(engine.SupportedMIMETypes, mime) { + if !slices.Contains(openapi.GetSupportedMIMETypes(), mime) { continue } bodyMIME = mime + // requestBody content mime without schema + if content == nil || content.Schema == nil { + continue + } + arg := content.Schema.Value if arg.Description == "" { arg.Description = content.Schema.Value.Description @@ -207,7 +227,7 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { } // Unfortunately, the request body doesn't contain any good descriptor for it, // so we just use "requestBodyContent" as the name of the arg. - tool.Parameters.Arguments.Properties["requestBodyContent"] = &openapi3.SchemaRef{Value: arg} + tool.Arguments.Properties["requestBodyContent"] = openAPI3SchemaToJSONSchema(arg) break } @@ -240,18 +260,18 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { } // For each set of auths, turn them into SecurityInfos, and drop ones that contain unsupported types. - var infos [][]engine.SecurityInfo + var infos [][]openapi.SecurityInfo outer: for _, auth := range auths { - var current []engine.SecurityInfo + var current []openapi.SecurityInfo for name := range auth { if scheme, ok := t.Components.SecuritySchemes[name]; ok { - if !slices.Contains(engine.SupportedSecurityTypes, scheme.Value.Type) { + if !slices.Contains(openapi.GetSupportedSecurityTypes(), scheme.Value.Type) { // There is an unsupported type in this auth, so move on to the next one. continue outer } - current = append(current, engine.SecurityInfo{ + current = append(current, openapi.SecurityInfo{ Type: scheme.Value.Type, Name: name, In: scheme.Value.In, @@ -285,12 +305,12 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { if err != nil { return nil, fmt.Errorf("failed to parse operation server URL: %w", err) } - tool.Credentials = info.GetCredentialToolStrings(operationServerURL.Hostname()) + tool.Credentials = append(tool.Credentials, info.GetCredentialToolStrings(operationServerURL.Hostname())...) } } // Register - toolNames = append(toolNames, tool.Parameters.Name) + toolNames = append(toolNames, tool.Name) tools = append(tools, tool) operationNum++ } @@ -314,17 +334,17 @@ func getOpenAPITools(t *openapi3.T, defaultHost string) ([]types.Tool, error) { return tools, nil } -func instructionString(server, method, path, bodyMIME string, queryParameters, pathParameters, headerParameters, cookieParameters []engine.Parameter, infos [][]engine.SecurityInfo) (string, error) { - inst := engine.OpenAPIInstructions{ - Server: server, - Path: path, - Method: method, - BodyContentMIME: bodyMIME, - SecurityInfos: infos, - QueryParameters: queryParameters, - PathParameters: pathParameters, - HeaderParameters: headerParameters, - CookieParameters: cookieParameters, +func instructionString(server, method, path, bodyMIME string, queryParameters, pathParameters, headerParameters, cookieParameters []openapi.Parameter, infos [][]openapi.SecurityInfo) (string, error) { + inst := openapi.OperationInfo{ + Server: server, + Path: path, + Method: method, + BodyContentMIME: bodyMIME, + SecurityInfos: infos, + QueryParams: queryParameters, + PathParams: pathParameters, + HeaderParams: headerParameters, + CookieParams: cookieParameters, } instBytes, err := json.Marshal(inst) if err != nil { @@ -352,3 +372,235 @@ func parseServer(server *openapi3.Server) (string, error) { } return s, nil } + +// openAPI3SchemaToJSONSchema converts an openapi3.Schema to a jsonschema.Schema +func openAPI3SchemaToJSONSchema(schema *openapi3.Schema) *jsonschema.Schema { + if schema == nil { + return nil + } + + result := &jsonschema.Schema{ + Title: schema.Title, + Description: schema.Description, + Format: schema.Format, + } + + // Convert type + if schema.Type != nil && len(*schema.Type) > 0 { + result.Types = *schema.Type + } + + // In OpenAPI v3.0, there is a nullable field. + // In OpenAPI v3.1, nullable is specified by providing a separate type. + if schema.Nullable && !slices.Contains(result.Types, "null") { + result.Types = append(result.Types, "null") + } + + // Convert enum + if schema.Enum != nil { + result.Enum = schema.Enum + } + + // Convert min/max + if schema.Min != nil { + minVal := *schema.Min + result.Minimum = &minVal + + // In OpenAPI 3, ExclusiveMin is a boolean flag that applies to Min + // In OpenAPI 3.1, ExclusiveMinimum is a separate value + if schema.ExclusiveMin { + result.ExclusiveMinimum = &minVal + } + } + if schema.Max != nil { + maxVal := *schema.Max + result.Maximum = &maxVal + + // In OpenAPI 3, ExclusiveMax is a boolean flag that applies to Max + // In OpenAPI 3.1, ExclusiveMaximum is a separate value + if schema.ExclusiveMax { + result.ExclusiveMaximum = &maxVal + } + } + + // Convert minLength/maxLength + if schema.MinLength != 0 { + minLength := int(schema.MinLength) + result.MinLength = &minLength + } + if schema.MaxLength != nil { + maxLength := int(*schema.MaxLength) + result.MaxLength = &maxLength + } + + // Convert pattern + if schema.Pattern != "" { + result.Pattern = schema.Pattern + } + + // Convert minItems/maxItems + if schema.MinItems != 0 { + minItems := int(schema.MinItems) + result.MinItems = &minItems + } + if schema.MaxItems != nil { + maxItems := int(*schema.MaxItems) + result.MaxItems = &maxItems + } + + // Convert uniqueItems + result.UniqueItems = schema.UniqueItems + + // Convert minProperties/maxProperties + if schema.MinProps != 0 { + minProps := int(schema.MinProps) + result.MinProperties = &minProps + } + if schema.MaxProps != nil { + maxProps := int(*schema.MaxProps) + result.MaxProperties = &maxProps + } + + // Convert required + if schema.Required != nil { + result.Required = schema.Required + } + + // Convert properties + if schema.Properties != nil { + result.Properties = make(map[string]*jsonschema.Schema, len(schema.Properties)) + for name, propRef := range schema.Properties { + if propRef != nil && propRef.Value != nil { + result.Properties[name] = openAPI3SchemaToJSONSchema(propRef.Value) + } + } + } + + // Convert items + if schema.Items != nil && schema.Items.Value != nil { + result.Items = openAPI3SchemaToJSONSchema(schema.Items.Value) + } + + // Convert oneOf + if schema.OneOf != nil { + result.OneOf = make([]*jsonschema.Schema, len(schema.OneOf)) + for i, oneOfRef := range schema.OneOf { + if oneOfRef != nil && oneOfRef.Value != nil { + result.OneOf[i] = openAPI3SchemaToJSONSchema(oneOfRef.Value) + } + } + } + + // Convert anyOf + if schema.AnyOf != nil { + result.AnyOf = make([]*jsonschema.Schema, len(schema.AnyOf)) + for i, anyOfRef := range schema.AnyOf { + if anyOfRef != nil && anyOfRef.Value != nil { + result.AnyOf[i] = openAPI3SchemaToJSONSchema(anyOfRef.Value) + } + } + } + + // Convert allOf + if schema.AllOf != nil { + result.AllOf = make([]*jsonschema.Schema, len(schema.AllOf)) + for i, allOfRef := range schema.AllOf { + if allOfRef != nil && allOfRef.Value != nil { + result.AllOf[i] = openAPI3SchemaToJSONSchema(allOfRef.Value) + } + } + } + + // Convert not + if schema.Not != nil && schema.Not.Value != nil { + result.Not = openAPI3SchemaToJSONSchema(schema.Not.Value) + } + + return result +} + +func getOpenAPIToolsRevamp(t *openapi3.T, source, targetToolName string) ([]types.Tool, error) { + if t == nil { + return nil, fmt.Errorf("OpenAPI spec is nil") + } else if t.Info == nil { + return nil, fmt.Errorf("OpenAPI spec is missing info field") + } + + if targetToolName == "" { + targetToolName = openapi.NoFilter + } + + list := types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: types.ToolNormalizer("list-operations-" + t.Info.Title), + Description: fmt.Sprintf("List available operations for %s. Each of these operations is an OpenAPI operation. Run this tool before you do anything else.", t.Info.Title), + }, + Instructions: fmt.Sprintf("%s %s %s %s", types.OpenAPIPrefix, openapi.ListTool, source, targetToolName), + }, + Source: types.ToolSource{ + LineNo: 0, + }, + } + + getSchema := types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: types.ToolNormalizer("get-schema-" + t.Info.Title), + Description: fmt.Sprintf("Get the JSONSchema for the arguments for an operation for %s. You must do this before you run the operation.", t.Info.Title), + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "operation": { + Type: "string", + Title: "operation", + Description: "the name of the operation to get the schema for", + Required: []string{"operation"}, + }, + }, + }, + }, + Instructions: fmt.Sprintf("%s %s %s %s", types.OpenAPIPrefix, openapi.GetSchemaTool, source, targetToolName), + }, + Source: types.ToolSource{ + LineNo: 1, + }, + } + + run := types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: types.ToolNormalizer("run-operation-" + t.Info.Title), + Description: fmt.Sprintf("Run an operation for %s. You MUST call %s for the operation before you use this tool.", t.Info.Title, openapi.GetSchemaTool), + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "operation": { + Type: "string", + Title: "operation", + Description: "the name of the operation to run", + Required: []string{"operation"}, + }, + "args": { + Type: "string", + Title: "args", + Description: "the JSON string containing arguments; must match the JSONSchema for the operation", + Required: []string{"args"}, + }, + }, + }, + }, + Instructions: fmt.Sprintf("%s %s %s %s", types.OpenAPIPrefix, openapi.RunTool, source, targetToolName), + }, + } + + exportTool := types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Export: []string{list.Name, getSchema.Name, run.Name}, + }, + }, + } + + return []types.Tool{exportTool, list, getSchema, run}, nil +} diff --git a/pkg/loader/openapi_test.go b/pkg/loader/openapi_test.go new file mode 100644 index 00000000..594d8cf7 --- /dev/null +++ b/pkg/loader/openapi_test.go @@ -0,0 +1,137 @@ +package loader + +import ( + "context" + "os" + "testing" + + "github.com/gptscript-ai/gptscript/pkg/types" + "github.com/hexops/autogold/v2" + "github.com/stretchr/testify/require" +) + +func TestLoadOpenAPI(t *testing.T) { + numOpenAPITools := func(set types.ToolSet) int { + num := 0 + for _, v := range set { + if v.IsOpenAPI() { + num++ + } + } + return num + } + + prgv3 := types.Program{ + ToolSet: types.ToolSet{}, + } + datav3, err := os.ReadFile("testdata/openapi_v3.yaml") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv3, &source{Content: datav3}, "", "") + require.NoError(t, err, "failed to read openapi v3") + require.Equal(t, 3, numOpenAPITools(prgv3.ToolSet), "expected 3 openapi tools") + + prgv2json := types.Program{ + ToolSet: types.ToolSet{}, + } + datav2, err := os.ReadFile("testdata/openapi_v2.json") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv2json, &source{Content: datav2}, "", "") + require.NoError(t, err, "failed to read openapi v2") + require.Equal(t, 3, numOpenAPITools(prgv2json.ToolSet), "expected 3 openapi tools") + + prgv2yaml := types.Program{ + ToolSet: types.ToolSet{}, + } + datav2, err = os.ReadFile("testdata/openapi_v2.yaml") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv2yaml, &source{Content: datav2}, "", "") + require.NoError(t, err, "failed to read openapi v2 (yaml)") + require.Equal(t, 3, numOpenAPITools(prgv2yaml.ToolSet), "expected 3 openapi tools") + + require.EqualValuesf(t, prgv2json.ToolSet, prgv2yaml.ToolSet, "expected same toolset for openapi v2 json and yaml") +} + +func TestOpenAPIv3(t *testing.T) { + prgv3 := types.Program{ + ToolSet: types.ToolSet{}, + } + datav3, err := os.ReadFile("testdata/openapi_v3.yaml") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv3, &source{Content: datav3}, "", "") + require.NoError(t, err) + + autogold.ExpectFile(t, prgv3.ToolSet, autogold.Dir("testdata/openapi")) +} + +func TestOpenAPIv3NoOperationIDs(t *testing.T) { + prgv3 := types.Program{ + ToolSet: types.ToolSet{}, + } + datav3, err := os.ReadFile("testdata/openapi_v3_no_operation_ids.yaml") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv3, &source{Content: datav3}, "", "") + require.NoError(t, err) + + autogold.ExpectFile(t, prgv3.ToolSet, autogold.Dir("testdata/openapi")) +} + +func TestOpenAPIv2(t *testing.T) { + prgv2 := types.Program{ + ToolSet: types.ToolSet{}, + } + datav2, err := os.ReadFile("testdata/openapi_v2.yaml") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv2, &source{Content: datav2}, "", "") + require.NoError(t, err) + + autogold.ExpectFile(t, prgv2.ToolSet, autogold.Dir("testdata/openapi")) +} + +func TestOpenAPIv3Revamp(t *testing.T) { + os.Setenv("GPTSCRIPT_OPENAPI_REVAMP", "true") + prgv3 := types.Program{ + ToolSet: types.ToolSet{}, + } + datav3, err := os.ReadFile("testdata/openapi_v3.yaml") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv3, &source{Content: datav3}, "", "") + require.NoError(t, err) + + autogold.ExpectFile(t, prgv3.ToolSet, autogold.Dir("testdata/openapi")) +} + +func TestOpenAPIv3NoOperationIDsRevamp(t *testing.T) { + os.Setenv("GPTSCRIPT_OPENAPI_REVAMP", "true") + prgv3 := types.Program{ + ToolSet: types.ToolSet{}, + } + datav3, err := os.ReadFile("testdata/openapi_v3_no_operation_ids.yaml") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv3, &source{Content: datav3}, "", "") + require.NoError(t, err) + + autogold.ExpectFile(t, prgv3.ToolSet, autogold.Dir("testdata/openapi")) +} + +func TestOpenAPIv2Revamp(t *testing.T) { + os.Setenv("GPTSCRIPT_OPENAPI_REVAMP", "true") + prgv2 := types.Program{ + ToolSet: types.ToolSet{}, + } + datav2, err := os.ReadFile("testdata/openapi_v2.yaml") + require.NoError(t, err) + _, err = readTool(context.Background(), nil, fakeMCPLoader{}, &prgv2, &source{Content: datav2}, "", "") + require.NoError(t, err) + + autogold.ExpectFile(t, prgv2.ToolSet, autogold.Dir("testdata/openapi")) +} + +type fakeMCPLoader struct{} + +func (fakeMCPLoader) Load(context.Context, types.Tool) ([]types.Tool, error) { + return nil, nil +} + +func (fakeMCPLoader) Close() error { + return nil +} diff --git a/pkg/loader/testdata/openapi/TestOpenAPIv2.golden b/pkg/loader/testdata/openapi/TestOpenAPIv2.golden new file mode 100644 index 00000000..ebf29cb4 --- /dev/null +++ b/pkg/loader/testdata/openapi/TestOpenAPIv2.golden @@ -0,0 +1,115 @@ +types.ToolSet{ + ":": types.Tool{ + ToolDef: types.ToolDef{Parameters: types.Parameters{ + Description: "This is a tool set for the Swagger Petstore OpenAPI spec", + ModelName: "gpt-4o", + Export: []string{ + "listPets", + "createPets", + "showPetById", + }, + }}, + ID: ":", + ToolMapping: map[string][]types.ToolReference{ + "createPets": {{ + Reference: "createPets", + ToolID: ":createPets", + }}, + "listPets": {{ + Reference: "listPets", + ToolID: ":listPets", + }}, + "showPetById": {{ + Reference: "showPetById", + ToolID: ":showPetById", + }}, + }, + LocalTools: map[string]string{ + "": ":", + "createpets": ":createPets", + "listpets": ":listPets", + "showpetbyid": ":showPetById", + }, + }, + ":createPets": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "createPets", + Description: "Create a pet", + ModelName: "gpt-4o", + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets","method":"POST","bodyContentMIME":"","securityInfos":null,"queryParameters":null,"pathParameters":null,"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":createPets", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "createpets": ":createPets", + "listpets": ":listPets", + "showpetbyid": ":showPetById", + }, + Source: types.ToolSource{LineNo: 2}, + }, + ":listPets": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "listPets", + Description: "List all pets", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "limit": { + Description: "How many items to return at one time (max 100)", + Types: []string{ + "integer", + }, + Properties: map[string]*jsonschema.Schema{}, + AllOf: []*jsonschema.Schema{}, + Format: "int32", + }, + }, + }, + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets","method":"GET","bodyContentMIME":"","securityInfos":null,"queryParameters":[{"name":"limit","style":"","explode":null}],"pathParameters":null,"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":listPets", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "createpets": ":createPets", + "listpets": ":listPets", + "showpetbyid": ":showPetById", + }, + Source: types.ToolSource{LineNo: 1}, + }, + ":showPetById": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "showPetById", + Description: "Info for a specific pet", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Required: []string{"petId"}, + Properties: map[string]*jsonschema.Schema{"petId": { + Description: "The id of the pet to retrieve", + Types: []string{"string"}, + Properties: map[string]*jsonschema.Schema{}, + AllOf: []*jsonschema.Schema{}, + }}, + }, + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets/{petId}","method":"GET","bodyContentMIME":"","securityInfos":null,"queryParameters":null,"pathParameters":[{"name":"petId","style":"","explode":null}],"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":showPetById", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "createpets": ":createPets", + "listpets": ":listPets", + "showpetbyid": ":showPetById", + }, + Source: types.ToolSource{LineNo: 3}, + }, +} diff --git a/pkg/loader/testdata/openapi/TestOpenAPIv2Revamp.golden b/pkg/loader/testdata/openapi/TestOpenAPIv2Revamp.golden new file mode 100644 index 00000000..ac32cc58 --- /dev/null +++ b/pkg/loader/testdata/openapi/TestOpenAPIv2Revamp.golden @@ -0,0 +1,118 @@ +types.ToolSet{ + ":": types.Tool{ + ToolDef: types.ToolDef{Parameters: types.Parameters{ + ModelName: "gpt-4o", + Export: []string{ + "listOperationsSwaggerPetstore", + "getSchemaSwaggerPetstore", + "runOperationSwaggerPetstore", + }, + }}, + ID: ":", + ToolMapping: map[string][]types.ToolReference{ + "getSchemaSwaggerPetstore": {{ + Reference: "getSchemaSwaggerPetstore", + ToolID: ":getSchemaSwaggerPetstore", + }}, + "listOperationsSwaggerPetstore": {{ + Reference: "listOperationsSwaggerPetstore", + ToolID: ":listOperationsSwaggerPetstore", + }}, + "runOperationSwaggerPetstore": {{ + Reference: "runOperationSwaggerPetstore", + ToolID: ":runOperationSwaggerPetstore", + }}, + }, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, + ":getSchemaSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "getSchemaSwaggerPetstore", + Description: "Get the JSONSchema for the arguments for an operation for Swagger Petstore. You must do this before you run the operation.", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "operation": { + Title: "operation", + Description: "the name of the operation to get the schema for", + Type: "string", + Required: []string{ + "operation", + }, + }, + }, + }, + }, + Instructions: "#!sys.openapi get-schema ", + }, + ID: ":getSchemaSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + Source: types.ToolSource{LineNo: 1}, + }, + ":listOperationsSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "listOperationsSwaggerPetstore", + Description: "List available operations for Swagger Petstore. Each of these operations is an OpenAPI operation. Run this tool before you do anything else.", + ModelName: "gpt-4o", + }, + Instructions: "#!sys.openapi list ", + }, + ID: ":listOperationsSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, + ":runOperationSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "runOperationSwaggerPetstore", + Description: "Run an operation for Swagger Petstore. You MUST call get-schema for the operation before you use this tool.", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "args": { + Title: "args", + Description: "the JSON string containing arguments; must match the JSONSchema for the operation", + Type: "string", + Required: []string{"args"}, + }, + "operation": { + Title: "operation", + Description: "the name of the operation to run", + Type: "string", + Required: []string{"operation"}, + }, + }, + }, + }, + Instructions: "#!sys.openapi run ", + }, + ID: ":runOperationSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, +} diff --git a/pkg/loader/testdata/openapi/TestOpenAPIv3.golden b/pkg/loader/testdata/openapi/TestOpenAPIv3.golden new file mode 100644 index 00000000..7e4a7993 --- /dev/null +++ b/pkg/loader/testdata/openapi/TestOpenAPIv3.golden @@ -0,0 +1,132 @@ +types.ToolSet{ + ":": types.Tool{ + ToolDef: types.ToolDef{Parameters: types.Parameters{ + Description: "This is a tool set for the Swagger Petstore OpenAPI spec", + ModelName: "gpt-4o", + Export: []string{ + "listPets", + "createPets", + "showPetById", + }, + }}, + ID: ":", + ToolMapping: map[string][]types.ToolReference{ + "createPets": {{ + Reference: "createPets", + ToolID: ":createPets", + }}, + "listPets": {{ + Reference: "listPets", + ToolID: ":listPets", + }}, + "showPetById": {{ + Reference: "showPetById", + ToolID: ":showPetById", + }}, + }, + LocalTools: map[string]string{ + "": ":", + "createpets": ":createPets", + "listpets": ":listPets", + "showpetbyid": ":showPetById", + }, + }, + ":createPets": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "createPets", + Description: "Create a pet", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "requestBodyContent": { + Types: []string{ + "object", + }, + Required: []string{ + "id", + "name", + }, + Properties: map[string]*jsonschema.Schema{ + "id": { + Types: []string{ + "integer", + }, + Format: "int64", + }, + "name": {Types: []string{"string"}}, + "tag": {Types: []string{"string"}}, + }, + }, + }, + }, + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets","method":"POST","bodyContentMIME":"application/json","securityInfos":null,"queryParameters":null,"pathParameters":null,"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":createPets", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "createpets": ":createPets", + "listpets": ":listPets", + "showpetbyid": ":showPetById", + }, + Source: types.ToolSource{LineNo: 2}, + }, + ":listPets": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "listPets", + Description: "List all pets", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{"limit": { + Description: "How many items to return at one time (max 100)", + Types: []string{"integer"}, + Maximum: valast.Ptr(float64(100)), + Format: "int32", + }}, + }, + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets","method":"GET","bodyContentMIME":"","securityInfos":null,"queryParameters":[{"name":"limit","style":"","explode":null}],"pathParameters":null,"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":listPets", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "createpets": ":createPets", + "listpets": ":listPets", + "showpetbyid": ":showPetById", + }, + Source: types.ToolSource{LineNo: 1}, + }, + ":showPetById": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "showPetById", + Description: "Info for a specific pet", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Required: []string{"petId"}, + Properties: map[string]*jsonschema.Schema{"petId": { + Description: "The id of the pet to retrieve", + Types: []string{"string"}, + }}, + }, + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets/{petId}","method":"GET","bodyContentMIME":"","securityInfos":null,"queryParameters":null,"pathParameters":[{"name":"petId","style":"","explode":null}],"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":showPetById", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "createpets": ":createPets", + "listpets": ":listPets", + "showpetbyid": ":showPetById", + }, + Source: types.ToolSource{LineNo: 3}, + }, +} diff --git a/pkg/loader/testdata/openapi/TestOpenAPIv3NoOperationIDs.golden b/pkg/loader/testdata/openapi/TestOpenAPIv3NoOperationIDs.golden new file mode 100644 index 00000000..5ebd8aa9 --- /dev/null +++ b/pkg/loader/testdata/openapi/TestOpenAPIv3NoOperationIDs.golden @@ -0,0 +1,132 @@ +types.ToolSet{ + ":": types.Tool{ + ToolDef: types.ToolDef{Parameters: types.Parameters{ + Description: "This is a tool set for the Swagger Petstore OpenAPI spec", + ModelName: "gpt-4o", + Export: []string{ + "get_pets", + "post_pets", + "get_pets_petId", + }, + }}, + ID: ":", + ToolMapping: map[string][]types.ToolReference{ + "get_pets": {{ + Reference: "get_pets", + ToolID: ":get_pets", + }}, + "get_pets_petId": {{ + Reference: "get_pets_petId", + ToolID: ":get_pets_petId", + }}, + "post_pets": {{ + Reference: "post_pets", + ToolID: ":post_pets", + }}, + }, + LocalTools: map[string]string{ + "": ":", + "get_pets": ":get_pets", + "get_pets_petid": ":get_pets_petId", + "post_pets": ":post_pets", + }, + }, + ":get_pets": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "get_pets", + Description: "List all pets", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "limit": { + Description: "How many items to return at one time (max 100)", + Types: []string{ + "integer", + }, + Maximum: valast.Ptr(float64(100)), + Format: "int32", + }, + }, + }, + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets","method":"GET","bodyContentMIME":"","securityInfos":null,"queryParameters":[{"name":"limit","style":"","explode":null}],"pathParameters":null,"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":get_pets", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "get_pets": ":get_pets", + "get_pets_petid": ":get_pets_petId", + "post_pets": ":post_pets", + }, + Source: types.ToolSource{LineNo: 1}, + }, + ":get_pets_petId": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "get_pets_petId", + Description: "Info for a specific pet", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Required: []string{"petId"}, + Properties: map[string]*jsonschema.Schema{"petId": { + Description: "The id of the pet to retrieve", + Types: []string{"string"}, + }}, + }, + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets/{petId}","method":"GET","bodyContentMIME":"","securityInfos":null,"queryParameters":null,"pathParameters":[{"name":"petId","style":"","explode":null}],"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":get_pets_petId", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "get_pets": ":get_pets", + "get_pets_petid": ":get_pets_petId", + "post_pets": ":post_pets", + }, + Source: types.ToolSource{LineNo: 3}, + }, + ":post_pets": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "post_pets", + Description: "Create a pet", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{"requestBodyContent": { + Types: []string{"object"}, + Required: []string{ + "id", + "name", + }, + Properties: map[string]*jsonschema.Schema{ + "id": { + Types: []string{ + "integer", + }, + Format: "int64", + }, + "name": {Types: []string{"string"}}, + "tag": {Types: []string{"string"}}, + }, + }}, + }, + }, + Instructions: `#!sys.openapi '{"server":"http://petstore.swagger.io/v1","path":"/pets","method":"POST","bodyContentMIME":"application/json","securityInfos":null,"queryParameters":null,"pathParameters":null,"headerParameters":null,"cookieParameters":null}'`, + }, + ID: ":post_pets", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "get_pets": ":get_pets", + "get_pets_petid": ":get_pets_petId", + "post_pets": ":post_pets", + }, + Source: types.ToolSource{LineNo: 2}, + }, +} diff --git a/pkg/loader/testdata/openapi/TestOpenAPIv3NoOperationIDsRevamp.golden b/pkg/loader/testdata/openapi/TestOpenAPIv3NoOperationIDsRevamp.golden new file mode 100644 index 00000000..ac32cc58 --- /dev/null +++ b/pkg/loader/testdata/openapi/TestOpenAPIv3NoOperationIDsRevamp.golden @@ -0,0 +1,118 @@ +types.ToolSet{ + ":": types.Tool{ + ToolDef: types.ToolDef{Parameters: types.Parameters{ + ModelName: "gpt-4o", + Export: []string{ + "listOperationsSwaggerPetstore", + "getSchemaSwaggerPetstore", + "runOperationSwaggerPetstore", + }, + }}, + ID: ":", + ToolMapping: map[string][]types.ToolReference{ + "getSchemaSwaggerPetstore": {{ + Reference: "getSchemaSwaggerPetstore", + ToolID: ":getSchemaSwaggerPetstore", + }}, + "listOperationsSwaggerPetstore": {{ + Reference: "listOperationsSwaggerPetstore", + ToolID: ":listOperationsSwaggerPetstore", + }}, + "runOperationSwaggerPetstore": {{ + Reference: "runOperationSwaggerPetstore", + ToolID: ":runOperationSwaggerPetstore", + }}, + }, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, + ":getSchemaSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "getSchemaSwaggerPetstore", + Description: "Get the JSONSchema for the arguments for an operation for Swagger Petstore. You must do this before you run the operation.", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "operation": { + Title: "operation", + Description: "the name of the operation to get the schema for", + Type: "string", + Required: []string{ + "operation", + }, + }, + }, + }, + }, + Instructions: "#!sys.openapi get-schema ", + }, + ID: ":getSchemaSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + Source: types.ToolSource{LineNo: 1}, + }, + ":listOperationsSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "listOperationsSwaggerPetstore", + Description: "List available operations for Swagger Petstore. Each of these operations is an OpenAPI operation. Run this tool before you do anything else.", + ModelName: "gpt-4o", + }, + Instructions: "#!sys.openapi list ", + }, + ID: ":listOperationsSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, + ":runOperationSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "runOperationSwaggerPetstore", + Description: "Run an operation for Swagger Petstore. You MUST call get-schema for the operation before you use this tool.", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "args": { + Title: "args", + Description: "the JSON string containing arguments; must match the JSONSchema for the operation", + Type: "string", + Required: []string{"args"}, + }, + "operation": { + Title: "operation", + Description: "the name of the operation to run", + Type: "string", + Required: []string{"operation"}, + }, + }, + }, + }, + Instructions: "#!sys.openapi run ", + }, + ID: ":runOperationSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, +} diff --git a/pkg/loader/testdata/openapi/TestOpenAPIv3Revamp.golden b/pkg/loader/testdata/openapi/TestOpenAPIv3Revamp.golden new file mode 100644 index 00000000..ac32cc58 --- /dev/null +++ b/pkg/loader/testdata/openapi/TestOpenAPIv3Revamp.golden @@ -0,0 +1,118 @@ +types.ToolSet{ + ":": types.Tool{ + ToolDef: types.ToolDef{Parameters: types.Parameters{ + ModelName: "gpt-4o", + Export: []string{ + "listOperationsSwaggerPetstore", + "getSchemaSwaggerPetstore", + "runOperationSwaggerPetstore", + }, + }}, + ID: ":", + ToolMapping: map[string][]types.ToolReference{ + "getSchemaSwaggerPetstore": {{ + Reference: "getSchemaSwaggerPetstore", + ToolID: ":getSchemaSwaggerPetstore", + }}, + "listOperationsSwaggerPetstore": {{ + Reference: "listOperationsSwaggerPetstore", + ToolID: ":listOperationsSwaggerPetstore", + }}, + "runOperationSwaggerPetstore": {{ + Reference: "runOperationSwaggerPetstore", + ToolID: ":runOperationSwaggerPetstore", + }}, + }, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, + ":getSchemaSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "getSchemaSwaggerPetstore", + Description: "Get the JSONSchema for the arguments for an operation for Swagger Petstore. You must do this before you run the operation.", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "operation": { + Title: "operation", + Description: "the name of the operation to get the schema for", + Type: "string", + Required: []string{ + "operation", + }, + }, + }, + }, + }, + Instructions: "#!sys.openapi get-schema ", + }, + ID: ":getSchemaSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + Source: types.ToolSource{LineNo: 1}, + }, + ":listOperationsSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "listOperationsSwaggerPetstore", + Description: "List available operations for Swagger Petstore. Each of these operations is an OpenAPI operation. Run this tool before you do anything else.", + ModelName: "gpt-4o", + }, + Instructions: "#!sys.openapi list ", + }, + ID: ":listOperationsSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, + ":runOperationSwaggerPetstore": types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "runOperationSwaggerPetstore", + Description: "Run an operation for Swagger Petstore. You MUST call get-schema for the operation before you use this tool.", + ModelName: "gpt-4o", + Arguments: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "args": { + Title: "args", + Description: "the JSON string containing arguments; must match the JSONSchema for the operation", + Type: "string", + Required: []string{"args"}, + }, + "operation": { + Title: "operation", + Description: "the name of the operation to run", + Type: "string", + Required: []string{"operation"}, + }, + }, + }, + }, + Instructions: "#!sys.openapi run ", + }, + ID: ":runOperationSwaggerPetstore", + ToolMapping: map[string][]types.ToolReference{}, + LocalTools: map[string]string{ + "": ":", + "getschemaswaggerpetstore": ":getSchemaSwaggerPetstore", + "listoperationsswaggerpetstore": ":listOperationsSwaggerPetstore", + "runoperationswaggerpetstore": ":runOperationSwaggerPetstore", + }, + }, +} diff --git a/pkg/loader/testdata/openapi_v3_no_operation_ids.yaml b/pkg/loader/testdata/openapi_v3_no_operation_ids.yaml new file mode 100644 index 00000000..53e58b88 --- /dev/null +++ b/pkg/loader/testdata/openapi_v3_no_operation_ids.yaml @@ -0,0 +1,116 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/pkg/loader/url.go b/pkg/loader/url.go index bc4d5c9f..d84e08be 100644 --- a/pkg/loader/url.go +++ b/pkg/loader/url.go @@ -7,6 +7,7 @@ import ( "net/http" url2 "net/url" "path" + "regexp" "strings" "time" @@ -33,6 +34,14 @@ type cacheValue struct { Time time.Time } +func (c *cacheKey) isStatic() bool { + return c.Repo != nil && + c.Repo.Revision != "" && + stableRef.MatchString(c.Repo.Revision) +} + +var stableRef = regexp.MustCompile("^([a-f0-9]{7,40}$|v[0-9]|[0-9])") + func loadURL(ctx context.Context, cache *cache.Client, base *source, name string) (*source, bool, error) { var ( repo *types.Repo @@ -47,9 +56,20 @@ func loadURL(ctx context.Context, cache *cache.Client, base *source, name string cachedValue cacheValue ) + if cachedKey.Repo == nil { + if _, rev, ok := strings.Cut(name, "@"); ok && stableRef.MatchString(rev) { + cachedKey.Repo = &types.Repo{ + Revision: rev, + } + } + } + if cachedKey.Path == "" { + cachedKey.Path = "." + } + if ok, err := cache.Get(ctx, cachedKey, &cachedValue); err != nil { return nil, false, err - } else if ok && time.Since(cachedValue.Time) < CacheTimeout { + } else if ok && (cachedKey.isStatic() || time.Since(cachedValue.Time) < CacheTimeout) { return cachedValue.Source, true, nil } @@ -111,11 +131,20 @@ func loadURL(ctx context.Context, cache *cache.Client, base *source, name string req.Header.Set("Authorization", "Bearer "+bearerToken) } - data, err := getWithDefaults(req) + data, defaulted, err := getWithDefaults(req) if err != nil { return nil, false, fmt.Errorf("error loading %s: %v", url, err) } + if defaulted != "" { + pathString = url + name = defaulted + if repo != nil { + repo.Path = path.Join(repo.Path, repo.Name) + repo.Name = defaulted + } + } + log.Debugf("opened %s", url) result := &source{ @@ -137,31 +166,32 @@ func loadURL(ctx context.Context, cache *cache.Client, base *source, name string return result, true, nil } -func getWithDefaults(req *http.Request) ([]byte, error) { +func getWithDefaults(req *http.Request) ([]byte, string, error) { originalPath := req.URL.Path // First, try to get the original path as is. It might be an OpenAPI definition. resp, err := http.DefaultClient.Do(req) if err != nil { - return nil, err + return nil, "", err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { - if toolBytes, err := io.ReadAll(resp.Body); err == nil && isOpenAPI(toolBytes) != 0 { - return toolBytes, nil - } + toolBytes, err := io.ReadAll(resp.Body) + return toolBytes, "", err + } + + base := path.Base(originalPath) + if strings.Contains(base, ".") { + return nil, "", fmt.Errorf("error loading %s: %s", req.URL.String(), resp.Status) } for i, def := range types.DefaultFiles { - base := path.Base(originalPath) - if !strings.Contains(base, ".") { - req.URL.Path = path.Join(originalPath, def) - } + req.URL.Path = path.Join(originalPath, def) resp, err := http.DefaultClient.Do(req) if err != nil { - return nil, err + return nil, "", err } defer resp.Body.Close() @@ -170,16 +200,20 @@ func getWithDefaults(req *http.Request) ([]byte, error) { } if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("error loading %s: %s", req.URL.String(), resp.Status) + return nil, "", fmt.Errorf("error loading %s: %s", req.URL.String(), resp.Status) } - return io.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) + return data, def, err } + panic("unreachable") } -func ContentFromURL(url string) (string, error) { - cache, err := cache.New() +func ContentFromURL(url string, disableCache bool) (string, error) { + cache, err := cache.New(cache.Options{ + DisableCache: disableCache, + }) if err != nil { return "", fmt.Errorf("failed to create cache: %w", err) } diff --git a/pkg/mcp/client.go b/pkg/mcp/client.go new file mode 100644 index 00000000..a6563bd2 --- /dev/null +++ b/pkg/mcp/client.go @@ -0,0 +1,26 @@ +package mcp + +import ( + nmcp "github.com/nanobot-ai/nanobot/pkg/mcp" +) + +func (l *Local) Client(server ServerConfig, clientOpts ...nmcp.ClientOption) (*Client, error) { + session, err := l.loadSession(server, "default", clientOpts...) + if err != nil { + return nil, err + } + + return &Client{ + Client: session.Client, + ID: session.ID, + }, nil +} + +type Client struct { + *nmcp.Client + ID string +} + +func (c *Client) Capabilities() nmcp.ServerCapabilities { + return c.Session.InitializeResult.Capabilities +} diff --git a/pkg/mcp/loader.go b/pkg/mcp/loader.go new file mode 100644 index 00000000..7ec6fb5c --- /dev/null +++ b/pkg/mcp/loader.go @@ -0,0 +1,333 @@ +package mcp + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "maps" + "slices" + "strings" + "sync" + + "github.com/gptscript-ai/gptscript/pkg/hash" + "github.com/gptscript-ai/gptscript/pkg/mvl" + "github.com/gptscript-ai/gptscript/pkg/types" + "github.com/modelcontextprotocol/go-sdk/jsonschema" + nmcp "github.com/nanobot-ai/nanobot/pkg/mcp" +) + +var ( + DefaultLoader = &Local{} + DefaultRunner = DefaultLoader + + logger = mvl.Package() +) + +type Local struct { + lock sync.Mutex + sessions map[string]*Session + sessionCtx context.Context + cancel context.CancelFunc +} + +type Session struct { + ID string + Client *nmcp.Client + Config ServerConfig +} + +type Config struct { + MCPServers map[string]ServerConfig `json:"mcpServers"` +} + +// ServerConfig represents an MCP server configuration for tools calls. +// It is important that this type doesn't have any maps. +type ServerConfig struct { + DisableInstruction bool `json:"disableInstruction"` + Command string `json:"command"` + Args []string `json:"args"` + Env []string `json:"env"` + Server string `json:"server"` + URL string `json:"url"` + BaseURL string `json:"baseURL,omitempty"` + Headers []string `json:"headers"` + Scope string `json:"scope"` + AllowedTools []string `json:"allowedTools"` +} + +func (s *ServerConfig) GetBaseURL() string { + if s.BaseURL != "" { + return s.BaseURL + } + if s.Server != "" { + return s.Server + } + return s.URL +} + +func (l *Local) Load(ctx context.Context, tool types.Tool) (result []types.Tool, _ error) { + if !tool.IsMCP() { + return nil, nil + } + + _, configData, _ := strings.Cut(tool.Instructions, "\n") + + var servers Config + if err := json.Unmarshal([]byte(strings.TrimSpace(configData)), &servers); err != nil { + return nil, fmt.Errorf("failed to parse MCP configuration: %w\n%s", err, configData) + } + + if len(servers.MCPServers) == 0 { + // Try to load just one server + var server ServerConfig + if err := json.Unmarshal([]byte(strings.TrimSpace(configData)), &server); err != nil { + return nil, fmt.Errorf("failed to parse single MCP server configuration: %w\n%s", err, configData) + } + if server.Command == "" && server.URL == "" && server.Server == "" { + return nil, fmt.Errorf("no MCP server configuration found in tool instructions: %s", configData) + } + servers.MCPServers = map[string]ServerConfig{ + "default": server, + } + } + + if len(servers.MCPServers) > 1 { + return nil, fmt.Errorf("only a single MCP server definition is supported") + } + + for server := range maps.Keys(servers.MCPServers) { + tools, err := l.LoadTools(ctx, servers.MCPServers[server], server, tool.Name) + if err != nil { + return nil, fmt.Errorf("failed to load MCP session for server %s: %w", server, err) + } + + return tools, nil + } + + // This should never happen, but just in case + return nil, fmt.Errorf("no MCP server configuration found in tool instructions: %s", configData) +} + +func (l *Local) LoadTools(ctx context.Context, server ServerConfig, serverName, toolName string) ([]types.Tool, error) { + allowedTools := server.AllowedTools + // Reset so we don't start a new MCP server, no reason to if one is already running and the allowed tools change. + server.AllowedTools = nil + + session, err := l.loadSession(server, serverName) + if err != nil { + return nil, err + } + + return l.sessionToTools(ctx, session, toolName, allowedTools) +} + +func (l *Local) ShutdownServer(server ServerConfig) error { + if l == nil { + return nil + } + + id := hash.Digest(server) + + l.lock.Lock() + + if l.sessionCtx == nil { + l.lock.Unlock() + return nil + } + + session := l.sessions[id] + delete(l.sessions, id) + + l.lock.Unlock() + + if session != nil && session.Client != nil { + session.Client.Session.Close(true) + session.Client.Session.Wait() + } + + return nil +} + +func (l *Local) Close() error { + if l == nil { + return nil + } + + l.lock.Lock() + defer l.lock.Unlock() + + if l.sessionCtx == nil { + return nil + } + + defer func() { + l.cancel() + l.sessionCtx = nil + }() + + var errs []error + for id, session := range l.sessions { + logger.Infof("closing MCP session %s", id) + session.Client.Session.Close(false) + session.Client.Session.Wait() + } + + return errors.Join(errs...) +} + +func (l *Local) sessionToTools(ctx context.Context, session *Session, toolName string, allowedTools []string) ([]types.Tool, error) { + allToolsAllowed := allowedTools == nil || slices.Contains(allowedTools, "*") + + tools, err := session.Client.ListTools(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list tools: %w", err) + } + + toolDefs := []types.Tool{{ /* this is a placeholder for main tool */ }} + var toolNames []string + + for _, tool := range tools.Tools { + if !allToolsAllowed && !slices.Contains(allowedTools, tool.Name) { + continue + } + if tool.Name == "" { + // I dunno, bad tool? + continue + } + + var schema jsonschema.Schema + + schemaData, err := json.Marshal(tool.InputSchema) + if err != nil { + panic(err) + } + + if err := json.Unmarshal(schemaData, &schema); err != nil { + return nil, fmt.Errorf("failed to unmarshal tool input schema: %w", err) + } + + annotations, err := json.Marshal(tool.Annotations) + if err != nil { + return nil, fmt.Errorf("failed to marshal tool annotations: %w", err) + } + + toolDef := types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: tool.Name, + Description: tool.Description, + Arguments: &schema, + }, + Instructions: types.MCPInvokePrefix + tool.Name + " " + session.ID, + }, + } + + if string(annotations) != "{}" && string(annotations) != "null" { + toolDef.MetaData = map[string]string{ + "mcp-tool-annotations": string(annotations), + } + } + + if tool.Annotations != nil && tool.Annotations.Title != "" && !slices.Contains(strings.Fields(tool.Annotations.Title), "as") { + toolNames = append(toolNames, tool.Name+" as "+tool.Annotations.Title) + } else { + toolNames = append(toolNames, tool.Name) + } + + toolDefs = append(toolDefs, toolDef) + } + + main := types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: toolName, + Description: session.Client.Session.InitializeResult.ServerInfo.Name, + Export: toolNames, + }, + MetaData: map[string]string{ + "bundle": "true", + }, + }, + } + + if session.Client.Session.InitializeResult.Instructions != "" { + data, _ := json.Marshal(map[string]any{ + "tools": toolNames, + "instructions": session.Client.Session.InitializeResult.Instructions, + }) + toolDefs = append(toolDefs, types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: session.ID, + Type: "context", + }, + Instructions: types.EchoPrefix + "\n" + `# START MCP SERVER INFO: ` + session.Client.Session.InitializeResult.ServerInfo.Name + "\n" + + `You have available the following tools from an MCP Server that has provided the following additional instructions` + "\n" + + string(data) + "\n" + + `# END MCP SERVER INFO` + "\n", + }, + }) + + main.ExportContext = append(main.ExportContext, session.ID) + } + + toolDefs[0] = main + return toolDefs, nil +} + +func (l *Local) loadSession(server ServerConfig, serverName string, clientOpts ...nmcp.ClientOption) (*Session, error) { + id := hash.Digest(server) + l.lock.Lock() + existing, ok := l.sessions[id] + if l.sessionCtx == nil { + l.sessionCtx, l.cancel = context.WithCancel(context.Background()) + } + l.lock.Unlock() + + if ok { + return existing, nil + } + + c, err := nmcp.NewClient(l.sessionCtx, serverName, nmcp.Server{ + Env: splitIntoMap(server.Env), + Command: server.Command, + Args: server.Args, + BaseURL: server.GetBaseURL(), + Headers: splitIntoMap(server.Headers), + }, clientOpts...) + if err != nil { + return nil, fmt.Errorf("failed to create MCP client: %w", err) + } + + result := &Session{ + ID: id, + Client: c, + Config: server, + } + + l.lock.Lock() + defer l.lock.Unlock() + + if existing, ok = l.sessions[id]; ok { + c.Session.Close(true) + return existing, nil + } + + if l.sessions == nil { + l.sessions = make(map[string]*Session, 1) + } + l.sessions[id] = result + return result, nil +} + +func splitIntoMap(list []string) map[string]string { + result := make(map[string]string, len(list)) + for _, s := range list { + k, v, ok := strings.Cut(s, "=") + if ok { + result[k] = v + } + } + return result +} diff --git a/pkg/mcp/runner.go b/pkg/mcp/runner.go new file mode 100644 index 00000000..37032392 --- /dev/null +++ b/pkg/mcp/runner.go @@ -0,0 +1,58 @@ +package mcp + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/gptscript-ai/gptscript/pkg/engine" + "github.com/gptscript-ai/gptscript/pkg/types" +) + +func (l *Local) Run(ctx engine.Context, _ chan<- types.CompletionStatus, tool types.Tool, input string) (string, error) { + fields := strings.Fields(tool.Instructions) + if len(fields) < 2 { + return "", fmt.Errorf("invalid mcp call, invalid number of fields in %s", tool.Instructions) + } + + id := fields[1] + toolName, ok := strings.CutPrefix(fields[0], types.MCPInvokePrefix) + if !ok { + return "", fmt.Errorf("invalid mcp call, invalid tool name in %s", tool.Instructions) + } + + arguments := map[string]any{} + + if input != "" { + if err := json.Unmarshal([]byte(input), &arguments); err != nil { + return "", fmt.Errorf("failed to unmarshal input: %w", err) + } + } + + l.lock.Lock() + session, ok := l.sessions[id] + l.lock.Unlock() + if !ok { + return "", fmt.Errorf("session not found for MCP server %s", id) + } + + result, err := session.Client.Call(ctx.Ctx, toolName, arguments) + if err != nil { + if ctx.ToolCategory == engine.NoCategory && ctx.Parent != nil { + var output []byte + if result != nil { + output, _ = json.Marshal(result) + } + // If this is a sub-call, then don't return the error; return the error as a message so that the LLM can retry. + return fmt.Sprintf("ERROR: got (%v) while running tool, OUTPUT: %s", err, string(output)), nil + } + return "", fmt.Errorf("failed to call tool %s: %w", toolName, err) + } + + str, err := json.Marshal(result) + if err != nil { + return "", fmt.Errorf("failed to marshal result: %w", err) + } + + return string(str), nil +} diff --git a/pkg/monitor/display.go b/pkg/monitor/display.go index 73a15006..6bc6e9f3 100644 --- a/pkg/monitor/display.go +++ b/pkg/monitor/display.go @@ -386,7 +386,7 @@ func (c callName) String() string { for { tool := c.prg.ToolSet[currentCall.ToolID] - name := tool.Parameters.Name + name := tool.Name if name == "" { name = tool.Source.Location } diff --git a/pkg/openai/client.go b/pkg/openai/client.go index 4ade654a..1a2f0968 100644 --- a/pkg/openai/client.go +++ b/pkg/openai/client.go @@ -2,18 +2,20 @@ package openai import ( "context" + "errors" "io" "log/slog" "os" "slices" "sort" "strings" + "time" openai "github.com/gptscript-ai/chat-completion-client" "github.com/gptscript-ai/gptscript/pkg/cache" - gcontext "github.com/gptscript-ai/gptscript/pkg/context" "github.com/gptscript-ai/gptscript/pkg/counter" "github.com/gptscript-ai/gptscript/pkg/credentials" + "github.com/gptscript-ai/gptscript/pkg/engine" "github.com/gptscript-ai/gptscript/pkg/hash" "github.com/gptscript-ai/gptscript/pkg/mvl" "github.com/gptscript-ai/gptscript/pkg/prompt" @@ -24,6 +26,7 @@ import ( const ( DefaultModel = openai.GPT4o BuiltinCredName = "sys.openai" + TooLongMessage = "Error: tool call output is too long" ) var ( @@ -130,6 +133,13 @@ func NewClient(ctx context.Context, credStore credentials.CredentialStore, opts }, nil } +func (c *Client) ProxyInfo([]string) (token, urlBase string) { + if c.invalidAuth { + return "", "" + } + return c.c.GetAPIKeyAndBaseURL() +} + func (c *Client) ValidAuth() error { if c.invalidAuth { return InvalidAuthError{} @@ -148,10 +158,15 @@ func (c *Client) Supports(ctx context.Context, modelName string) (bool, error) { return false, InvalidAuthError{} } - return slices.Contains(models, modelName), nil + for _, model := range models { + if model.ID == modelName { + return true, nil + } + } + return false, nil } -func (c *Client) ListModels(ctx context.Context, providers ...string) (result []string, _ error) { +func (c *Client) ListModels(ctx context.Context, providers ...string) ([]openai.Model, error) { // Only serve if providers is empty or "" is in the list if len(providers) != 0 && !slices.Contains(providers, "") { return nil, nil @@ -170,11 +185,10 @@ func (c *Client) ListModels(ctx context.Context, providers ...string) (result [] if err != nil { return nil, err } - for _, model := range models.Models { - result = append(result, model.ID) - } - sort.Strings(result) - return result, nil + sort.Slice(models.Models, func(i, j int) bool { + return models.Models[i].ID < models.Models[j].ID + }) + return models.Models, nil } func (c *Client) cacheKey(request openai.ChatCompletionRequest) any { @@ -203,15 +217,15 @@ func (c *Client) seed(request openai.ChatCompletionRequest) int { return hash.Seed(newRequest) } -func (c *Client) fromCache(ctx context.Context, messageRequest types.CompletionRequest, request openai.ChatCompletionRequest) (result []openai.ChatCompletionStreamResponse, _ bool, _ error) { +func (c *Client) fromCache(ctx context.Context, messageRequest types.CompletionRequest, request openai.ChatCompletionRequest) (result types.CompletionMessage, _ bool, _ error) { if !messageRequest.GetCache() { - return nil, false, nil + return types.CompletionMessage{}, false, nil } found, err := c.cache.Get(ctx, c.cacheKey(request), &result) if err != nil { - return nil, false, err + return types.CompletionMessage{}, false, err } else if !found { - return nil, false, nil + return types.CompletionMessage{}, false, nil } return result, true, nil } @@ -268,10 +282,7 @@ func toMessages(request types.CompletionRequest, compat bool) (result []openai.C chatMessage.ToolCalls = append(chatMessage.ToolCalls, toToolCall(*content.ToolCall)) } if content.Text != "" { - chatMessage.MultiContent = append(chatMessage.MultiContent, openai.ChatMessagePart{ - Type: openai.ChatMessagePartTypeText, - Text: content.Text, - }) + chatMessage.MultiContent = append(chatMessage.MultiContent, textToMultiContent(content.Text)...) } } @@ -293,9 +304,38 @@ func toMessages(request types.CompletionRequest, compat bool) (result []openai.C return } -func (c *Client) Call(ctx context.Context, messageRequest types.CompletionRequest, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) { +const imagePrefix = "data:image/png;base64," + +func textToMultiContent(text string) []openai.ChatMessagePart { + var chatParts []openai.ChatMessagePart + parts := strings.Split(text, "\n") + for i := len(parts) - 1; i >= 0; i-- { + if strings.HasPrefix(parts[i], imagePrefix) { + chatParts = append(chatParts, openai.ChatMessagePart{ + Type: openai.ChatMessagePartTypeImageURL, + ImageURL: &openai.ChatMessageImageURL{ + URL: parts[i], + }, + }) + parts = parts[:i] + } else { + break + } + } + if len(parts) > 0 { + chatParts = append(chatParts, openai.ChatMessagePart{ + Type: openai.ChatMessagePartTypeText, + Text: strings.Join(parts, "\n"), + }) + } + + slices.Reverse(chatParts) + return chatParts +} + +func (c *Client) Call(ctx context.Context, messageRequest types.CompletionRequest, env []string, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) { if err := c.ValidAuth(); err != nil { - if err := c.RetrieveAPIKey(ctx); err != nil { + if err := c.RetrieveAPIKey(ctx, env); err != nil { return nil, err } } @@ -309,8 +349,29 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques return nil, err } + toolTokenCount, err := countTools(messageRequest.Tools) + if err != nil { + return nil, err + } + if messageRequest.Chat { - msgs = dropMessagesOverCount(messageRequest.MaxTokens, msgs) + // Check the last message. If it is from a tool call, and if it takes up more than 80% of the budget on its own, reject it. + lastMessage := msgs[len(msgs)-1] + lastMessageCount, err := countMessage(lastMessage) + if err != nil { + return nil, err + } + + if lastMessage.Role == string(types.CompletionMessageRoleTypeTool) && lastMessageCount+toolTokenCount > int(float64(getBudget(messageRequest.MaxTokens))*0.8) { + // We need to update it in the msgs slice for right now and in the messageRequest for future calls. + msgs[len(msgs)-1].Content = TooLongMessage + messageRequest.Messages[len(messageRequest.Messages)-1].Content = types.Text(TooLongMessage) + } + + msgs, err = dropMessagesOverCount(messageRequest.MaxTokens, toolTokenCount, msgs) + if err != nil { + return nil, err + } } if len(msgs) == 0 { @@ -339,6 +400,7 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques } } + toolMapping := map[string]string{} for _, tool := range messageRequest.Tools { var params any = tool.Function.Parameters if tool.Function.Parameters == nil || len(tool.Function.Parameters.Properties) == 0 { @@ -348,6 +410,10 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques } } + if tool.Function.ToolID != "" { + toolMapping[tool.Function.Name] = tool.Function.ToolID + } + request.Tools = append(request.Tools, openai.Tool{ Type: openai.ToolTypeFunction, Function: &openai.FunctionDefinition{ @@ -361,7 +427,10 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques id := counter.Next() status <- types.CompletionStatus{ CompletionID: id, - Request: request, + Request: map[string]any{ + "chatCompletion": request, + "toolMapping": toolMapping, + }, } var cacheResponse bool @@ -371,11 +440,20 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques IncludeUsage: true, } } - response, ok, err := c.fromCache(ctx, messageRequest, request) + result, ok, err := c.fromCache(ctx, messageRequest, request) if err != nil { return nil, err } else if !ok { - response, err = c.call(ctx, request, id, status) + result, err = c.call(ctx, request, id, env, status) + + // If we got back a context length exceeded error, keep retrying and shrinking the message history until we pass. + var apiError *openai.APIError + if errors.As(err, &apiError) && apiError.Code == "context_length_exceeded" && messageRequest.Chat { + // Decrease maxTokens by 10% to make garbage collection more aggressive. + // The retry loop will further decrease maxTokens if needed. + maxTokens := decreaseTenPercent(messageRequest.MaxTokens) + result, err = c.contextLimitRetryLoop(ctx, request, id, env, maxTokens, toolTokenCount, status) + } if err != nil { return nil, err } @@ -383,11 +461,6 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques cacheResponse = true } - result := types.CompletionMessage{} - for _, response := range response { - result = appendMessage(result, response) - } - for i, content := range result.Content { if content.ToolCall != nil && content.ToolCall.ID == "" { content.ToolCall.ID = "call_" + hash.ID(content.ToolCall.Function.Name, content.ToolCall.Function.Arguments)[:8] @@ -405,7 +478,6 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques status <- types.CompletionStatus{ CompletionID: id, - Chunks: response, Response: result, Usage: result.Usage, Cached: cacheResponse, @@ -414,6 +486,36 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques return &result, nil } +func (c *Client) contextLimitRetryLoop(ctx context.Context, request openai.ChatCompletionRequest, id string, env []string, maxTokens int, toolTokenCount int, status chan<- types.CompletionStatus) (types.CompletionMessage, error) { + var ( + response types.CompletionMessage + err error + ) + + for range 10 { // maximum 10 tries + // Try to drop older messages again, with a decreased max tokens. + request.Messages, err = dropMessagesOverCount(maxTokens, toolTokenCount, request.Messages) + if err != nil { + return types.CompletionMessage{}, err + } + + response, err = c.call(ctx, request, id, env, status) + if err == nil { + return response, nil + } + + var apiError *openai.APIError + if errors.As(err, &apiError) && apiError.Code == "context_length_exceeded" { + // Decrease maxTokens and try again + maxTokens = decreaseTenPercent(maxTokens) + continue + } + return types.CompletionMessage{}, err + } + + return types.CompletionMessage{}, err +} + func appendMessage(msg types.CompletionMessage, response openai.ChatCompletionStreamResponse) types.CompletionMessage { msg.Usage.CompletionTokens = types.FirstSet(msg.Usage.CompletionTokens, response.Usage.CompletionTokens) msg.Usage.PromptTokens = types.FirstSet(msg.Usage.PromptTokens, response.Usage.PromptTokens) @@ -450,8 +552,9 @@ func appendMessage(msg types.CompletionMessage, response openai.ChatCompletionSt if tc.ToolCall.Function.Name != tool.Function.Name { tc.ToolCall.Function.Name += tool.Function.Name } - // OpenAI like to sometimes add this prefix for no good reason + // OpenAI like to sometimes add these prefix because it's confused tc.ToolCall.Function.Name = strings.TrimPrefix(tc.ToolCall.Function.Name, "namespace.") + tc.ToolCall.Function.Name = strings.TrimPrefix(tc.ToolCall.Function.Name, "@") tc.ToolCall.Function.Arguments += tool.Function.Arguments msg.Content[idx] = tc @@ -486,78 +589,132 @@ func override(left, right string) string { return left } -func (c *Client) call(ctx context.Context, request openai.ChatCompletionRequest, transactionID string, partial chan<- types.CompletionStatus) (responses []openai.ChatCompletionStreamResponse, _ error) { +const WaitingMessage = "Waiting for model response..." + +func (c *Client) call(ctx context.Context, request openai.ChatCompletionRequest, transactionID string, env []string, partial chan<- types.CompletionStatus) (types.CompletionMessage, error) { streamResponse := os.Getenv("GPTSCRIPT_INTERNAL_OPENAI_STREAMING") != "false" partial <- types.CompletionStatus{ CompletionID: transactionID, PartialResponse: &types.CompletionMessage{ Role: types.CompletionMessageRoleTypeAssistant, - Content: types.Text("Waiting for model response..."), + Content: types.Text(WaitingMessage), }, } + var ( + headers map[string]string + modelProviderEnv []string + retryOpts = []openai.RetryOptions{ + { + Retries: 5, + RetryAboveCode: 499, // 5xx errors + RetryCodes: []int{429}, // 429 Too Many Requests (ratelimit) + }, + } + ) + for _, e := range env { + if strings.HasPrefix(e, "GPTSCRIPT_MODEL_PROVIDER_") { + modelProviderEnv = append(modelProviderEnv, e) + } else if strings.HasPrefix(e, "GPTSCRIPT_DISABLE_RETRIES") { + retryOpts = nil + } + } + + if len(modelProviderEnv) > 0 { + headers = map[string]string{ + "X-GPTScript-Env": strings.Join(modelProviderEnv, ","), + } + } + slog.Debug("calling openai", "message", request.Messages) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + engineCtx, ok := engine.FromContext(ctx) + if ok { + engineCtx.OnUserCancel(ctx, cancel) + } + if !streamResponse { request.StreamOptions = nil - resp, err := c.c.CreateChatCompletion(ctx, request) + resp, err := c.c.CreateChatCompletion(ctx, request, headers, retryOpts...) if err != nil { - return nil, err + if errors.Is(err, context.Canceled) { + err = nil + } + return types.CompletionMessage{}, err } - return []openai.ChatCompletionStreamResponse{ - { - ID: resp.ID, - Object: resp.Object, - Created: resp.Created, - Model: resp.Model, - Usage: resp.Usage, - Choices: []openai.ChatCompletionStreamChoice{ - { - Index: resp.Choices[0].Index, - Delta: openai.ChatCompletionStreamChoiceDelta{ - Content: resp.Choices[0].Message.Content, - Role: resp.Choices[0].Message.Role, - FunctionCall: resp.Choices[0].Message.FunctionCall, - ToolCalls: resp.Choices[0].Message.ToolCalls, - }, - FinishReason: resp.Choices[0].FinishReason, + return appendMessage(types.CompletionMessage{}, openai.ChatCompletionStreamResponse{ + ID: resp.ID, + Object: resp.Object, + Created: resp.Created, + Model: resp.Model, + Usage: resp.Usage, + Choices: []openai.ChatCompletionStreamChoice{ + { + Index: resp.Choices[0].Index, + Delta: openai.ChatCompletionStreamChoiceDelta{ + Content: resp.Choices[0].Message.Content, + Role: resp.Choices[0].Message.Role, + FunctionCall: resp.Choices[0].Message.FunctionCall, + ToolCalls: resp.Choices[0].Message.ToolCalls, }, + FinishReason: resp.Choices[0].FinishReason, }, }, - }, nil + }), nil } - - stream, err := c.c.CreateChatCompletionStream(ctx, request) + stream, err := c.c.CreateChatCompletionStream(ctx, request, headers, retryOpts...) if err != nil { - return nil, err + if errors.Is(err, context.Canceled) { + return types.CompletionMessage{ + Content: []types.ContentPart{ + { + Text: "User aborted the chat before model could respond", + }, + }, + Role: types.CompletionMessageRoleTypeAssistant, + }, nil + } + return types.CompletionMessage{}, err } defer stream.Close() - var partialMessage types.CompletionMessage + var ( + partialMessage types.CompletionMessage + start = time.Now() + ) for { response, err := stream.Recv() - if err == io.EOF { - return responses, c.cache.Store(ctx, c.cacheKey(request), responses) + if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) { + if len(partialMessage.Content) > 0 && partialMessage.Content[0].Text == "" && errors.Is(err, context.Canceled) { + // Place a text holder if LLM doesn't respond or user cancel the stream before it can produce any response. + // In anthropic models it will yield an error about non-empty message for assistant message + partialMessage.Content[0].Text = "User aborted the chat or chat finished before LLM can respond" + } + // If the stream is finished, either because we got an EOF or the context was canceled, + // then we're done. The cache won't save the response if the context was canceled. + return partialMessage, c.cache.Store(ctx, c.cacheKey(request), partialMessage) } else if err != nil { - return nil, err - } - if len(response.Choices) > 0 { - slog.Debug("stream", "content", response.Choices[0].Delta.Content) + return types.CompletionMessage{}, err } + partialMessage = appendMessage(partialMessage, response) if partial != nil { - partialMessage = appendMessage(partialMessage, response) - partial <- types.CompletionStatus{ - CompletionID: transactionID, - PartialResponse: &partialMessage, + if time.Since(start) > 100*time.Millisecond { + partial <- types.CompletionStatus{ + CompletionID: transactionID, + PartialResponse: &partialMessage, + } + start = time.Now() } } - responses = append(responses, response) } } -func (c *Client) RetrieveAPIKey(ctx context.Context) error { - k, err := prompt.GetModelProviderCredential(ctx, c.credStore, BuiltinCredName, "OPENAI_API_KEY", "Please provide your OpenAI API key:", gcontext.GetEnv(ctx)) +func (c *Client) RetrieveAPIKey(ctx context.Context, env []string) error { + k, err := prompt.GetModelProviderCredential(ctx, c.credStore, BuiltinCredName, "OPENAI_API_KEY", "Please provide your OpenAI API key:", env) if err != nil { return err } diff --git a/pkg/openai/client_test.go b/pkg/openai/client_test.go index 30f1705b..78f3eac2 100644 --- a/pkg/openai/client_test.go +++ b/pkg/openai/client_test.go @@ -9,6 +9,44 @@ import ( "github.com/hexops/valast" ) +func TestTextToMultiContent(t *testing.T) { + autogold.Expect([]openai.ChatMessagePart{{ + Type: "text", + Text: "hi\ndata:image/png;base64,xxxxx\n", + }}).Equal(t, textToMultiContent("hi\ndata:image/png;base64,xxxxx\n")) + + autogold.Expect([]openai.ChatMessagePart{ + { + Type: "text", + Text: "hi", + }, + { + Type: "image_url", + ImageURL: &openai.ChatMessageImageURL{URL: "data:image/png;base64,xxxxx"}, + }, + }).Equal(t, textToMultiContent("hi\ndata:image/png;base64,xxxxx")) + + autogold.Expect([]openai.ChatMessagePart{{ + Type: "image_url", + ImageURL: &openai.ChatMessageImageURL{URL: "data:image/png;base64,xxxxx"}, + }}).Equal(t, textToMultiContent("data:image/png;base64,xxxxx")) + + autogold.Expect([]openai.ChatMessagePart{ + { + Type: "text", + Text: "\none\ntwo", + }, + { + Type: "image_url", + ImageURL: &openai.ChatMessageImageURL{URL: "data:image/png;base64,xxxxx"}, + }, + { + Type: "image_url", + ImageURL: &openai.ChatMessageImageURL{URL: "data:image/png;base64,yyyyy"}, + }, + }).Equal(t, textToMultiContent("\none\ntwo\ndata:image/png;base64,xxxxx\ndata:image/png;base64,yyyyy")) +} + func Test_appendMessage(t *testing.T) { autogold.Expect(types.CompletionMessage{Content: []types.ContentPart{ {ToolCall: &types.CompletionToolCall{ diff --git a/pkg/openai/count.go b/pkg/openai/count.go index 47c5c9bd..3c3de1a9 100644 --- a/pkg/openai/count.go +++ b/pkg/openai/count.go @@ -1,23 +1,46 @@ package openai -import openai "github.com/gptscript-ai/chat-completion-client" +import ( + "encoding/json" -func dropMessagesOverCount(maxTokens int, msgs []openai.ChatCompletionMessage) (result []openai.ChatCompletionMessage) { + openai "github.com/gptscript-ai/chat-completion-client" + "github.com/gptscript-ai/gptscript/pkg/types" + "github.com/pkoukk/tiktoken-go" + tiktoken_loader "github.com/pkoukk/tiktoken-go-loader" +) + +func init() { + tiktoken.SetBpeLoader(tiktoken_loader.NewOfflineLoader()) +} + +const DefaultMaxTokens = 400_000 // This is the limit for GPT-5 + +func decreaseTenPercent(maxTokens int) int { + maxTokens = getBudget(maxTokens) + return int(float64(maxTokens) * 0.9) +} + +func getBudget(maxTokens int) int { + if maxTokens <= 0 { + return DefaultMaxTokens + } + return maxTokens +} + +func dropMessagesOverCount(maxTokens, toolTokenCount int, msgs []openai.ChatCompletionMessage) (result []openai.ChatCompletionMessage, err error) { var ( lastSystem int withinBudget int - budget = maxTokens + budget = getBudget(maxTokens) - toolTokenCount ) - if maxTokens == 0 { - budget = 300_000 - } else { - budget *= 3 - } - for i, msg := range msgs { if msg.Role == openai.ChatMessageRoleSystem { - budget -= countMessage(msg) + count, err := countMessage(msg) + if err != nil { + return nil, err + } + budget -= count lastSystem = i result = append(result, msg) } else { @@ -27,31 +50,63 @@ func dropMessagesOverCount(maxTokens int, msgs []openai.ChatCompletionMessage) ( for i := len(msgs) - 1; i > lastSystem; i-- { withinBudget = i - budget -= countMessage(msgs[i]) + count, err := countMessage(msgs[i]) + if err != nil { + return nil, err + } + budget -= count if budget <= 0 { break } } + // OpenAI gets upset if there is a tool message without a tool call preceding it. + // Check the oldest message within budget, and if it is a tool message, just drop it. + // We do this in a loop because it is possible for multiple tool messages to be in a row, + // due to parallel tool calls. + for withinBudget < len(msgs) && msgs[withinBudget].Role == openai.ChatMessageRoleTool { + withinBudget++ + } + if withinBudget == len(msgs)-1 { // We are going to drop all non system messages, which seems useless, so just return them // all and let it fail - return msgs + return msgs, nil } - return append(result, msgs[withinBudget:]...) + return append(result, msgs[withinBudget:]...), nil } -func countMessage(msg openai.ChatCompletionMessage) (count int) { - count += len(msg.Role) - count += len(msg.Content) +func countMessage(msg openai.ChatCompletionMessage) (int, error) { + encoding, err := tiktoken.GetEncoding("o200k_base") + if err != nil { + return 0, err + } + + count := len(encoding.Encode(msg.Role, nil, nil)) + count += len(encoding.Encode(msg.Content, nil, nil)) for _, content := range msg.MultiContent { - count += len(content.Text) + count += len(encoding.Encode(content.Text, nil, nil)) } for _, tool := range msg.ToolCalls { - count += len(tool.Function.Name) - count += len(tool.Function.Arguments) + count += len(encoding.Encode(tool.Function.Name, nil, nil)) + count += len(encoding.Encode(tool.Function.Arguments, nil, nil)) } - count += len(msg.ToolCallID) - return count / 3 + count += len(encoding.Encode(msg.ToolCallID, nil, nil)) + + return count, nil +} + +func countTools(tools []types.ChatCompletionTool) (int, error) { + encoding, err := tiktoken.GetEncoding("o200k_base") + if err != nil { + return 0, err + } + + toolJSON, err := json.Marshal(tools) + if err != nil { + return 0, err + } + + return len(encoding.Encode(string(toolJSON), nil, nil)), nil } diff --git a/pkg/openapi/getschema.go b/pkg/openapi/getschema.go new file mode 100644 index 00000000..d2966aac --- /dev/null +++ b/pkg/openapi/getschema.go @@ -0,0 +1,285 @@ +package openapi + +import ( + "encoding/json" + "fmt" + "slices" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +type Parameter struct { + Name string `json:"name"` + Style string `json:"style"` + Explode *bool `json:"explode"` +} + +type OperationInfo struct { + Server string `json:"server"` + Path string `json:"path"` + Method string `json:"method"` + BodyContentMIME string `json:"bodyContentMIME"` + SecurityInfos [][]SecurityInfo `json:"securityInfos"` + QueryParams []Parameter `json:"queryParameters"` + PathParams []Parameter `json:"pathParameters"` + HeaderParams []Parameter `json:"headerParameters"` + CookieParams []Parameter `json:"cookieParameters"` +} + +var ( + supportedMIMETypes = []string{"application/json", "application/x-www-form-urlencoded", "multipart/form-data"} + supportedSecurityTypes = []string{"apiKey", "http"} +) + +const GetSchemaTool = "get-schema" + +func GetSupportedMIMETypes() []string { + return supportedMIMETypes +} + +func GetSupportedSecurityTypes() []string { + return supportedSecurityTypes +} + +// GetSchema returns the JSONSchema and OperationInfo for a particular OpenAPI operation. +// Return values in order: JSONSchema (string), OperationInfo, found (bool), error. +func GetSchema(operationID, defaultHost string, t *openapi3.T) (string, OperationInfo, bool, error) { + arguments := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{}, + Required: []string{}, + } + + info := OperationInfo{} + + // Determine the default server. + var ( + defaultServer = defaultHost + err error + ) + if len(t.Servers) > 0 { + defaultServer, err = parseServer(t.Servers[0]) + if err != nil { + return "", OperationInfo{}, false, err + } + } + + var globalSecurity []map[string]struct{} + if t.Security != nil { + for _, item := range t.Security { + current := map[string]struct{}{} + for name := range item { + if scheme, ok := t.Components.SecuritySchemes[name]; ok && slices.Contains(supportedSecurityTypes, scheme.Value.Type) { + current[name] = struct{}{} + } + } + if len(current) > 0 { + globalSecurity = append(globalSecurity, current) + } + } + } + + for path, pathItem := range t.Paths.Map() { + // Handle path-level server override, if one exists. + pathServer := defaultServer + if len(pathItem.Servers) > 0 { + pathServer, err = parseServer(pathItem.Servers[0]) + if err != nil { + return "", OperationInfo{}, false, err + } + } + + for method, operation := range pathItem.Operations() { + if operation.OperationID == operationID { + // Handle operation-level server override, if one exists. + operationServer := pathServer + if operation.Servers != nil && len(*operation.Servers) > 0 { + operationServer, err = parseServer((*operation.Servers)[0]) + if err != nil { + return "", OperationInfo{}, false, err + } + } + + info.Server = operationServer + info.Path = path + info.Method = method + + // We found our operation. Now we need to process it and build the arguments. + // Handle query, path, header, and cookie parameters first. + for _, param := range append(operation.Parameters, pathItem.Parameters...) { + removeRefs(param.Value.Schema) + arg := param.Value.Schema.Value + + if arg.Description == "" { + arg.Description = param.Value.Description + } + + // Store the arg + arguments.Properties[param.Value.Name] = &openapi3.SchemaRef{Value: arg} + + // Check whether it is required + if param.Value.Required { + arguments.Required = append(arguments.Required, param.Value.Name) + } + + // Save the parameter to the correct set of params. + p := Parameter{ + Name: param.Value.Name, + Style: param.Value.Style, + Explode: param.Value.Explode, + } + switch param.Value.In { + case "query": + info.QueryParams = append(info.QueryParams, p) + case "path": + info.PathParams = append(info.PathParams, p) + case "header": + info.HeaderParams = append(info.HeaderParams, p) + case "cookie": + info.CookieParams = append(info.CookieParams, p) + } + } + + // Next, handle the request body, if one exists. + if operation.RequestBody != nil { + for mime, content := range operation.RequestBody.Value.Content { + // Each MIME type needs to be handled individually, so we keep a list of the ones we support. + if !slices.Contains(supportedMIMETypes, mime) { + continue + } + info.BodyContentMIME = mime + + removeRefs(content.Schema) + + arg := content.Schema.Value + if arg.Description == "" { + arg.Description = content.Schema.Value.Description + } + + // Read Only cannot be sent in the request body, so we remove it + for key, property := range arg.Properties { + if property.Value.ReadOnly { + delete(arg.Properties, key) + } + } + + // Unfortunately, the request body doesn't contain any good descriptor for it, + // so we just use "requestBodyContent" as the name of the arg. + arguments.Properties["requestBodyContent"] = &openapi3.SchemaRef{Value: arg} + arguments.Required = append(arguments.Required, "requestBodyContent") + break + } + + if info.BodyContentMIME == "" { + return "", OperationInfo{}, false, fmt.Errorf("no supported MIME type found for request body in operation %s", operationID) + } + } + + // See if there is any auth defined for this operation + var ( + noAuth bool + auths []map[string]struct{} + ) + if operation.Security != nil { + if len(*operation.Security) == 0 { + noAuth = true + } + for _, req := range *operation.Security { + current := map[string]struct{}{} + for name := range req { + current[name] = struct{}{} + } + if len(current) > 0 { + auths = append(auths, current) + } + } + } + + // Use the global security if it was not overridden for this operation + if !noAuth && len(auths) == 0 { + auths = append(auths, globalSecurity...) + } + + // For each set of auths, turn them into SecurityInfos, and drop ones that contain unsupported types. + outer: + for _, auth := range auths { + var current []SecurityInfo + for name := range auth { + if scheme, ok := t.Components.SecuritySchemes[name]; ok { + if !slices.Contains(supportedSecurityTypes, scheme.Value.Type) { + // There is an unsupported type in this auth, so move on to the next one. + continue outer + } + + current = append(current, SecurityInfo{ + Type: scheme.Value.Type, + Name: name, + In: scheme.Value.In, + Scheme: scheme.Value.Scheme, + APIKeyName: scheme.Value.Name, + }) + } + } + + if len(current) > 0 { + info.SecurityInfos = append(info.SecurityInfos, current) + } + } + + argumentsJSON, err := json.MarshalIndent(arguments, "", " ") + if err != nil { + return "", OperationInfo{}, false, err + } + return string(argumentsJSON), info, true, nil + } + } + } + + return "", OperationInfo{}, false, nil +} + +func parseServer(server *openapi3.Server) (string, error) { + s := server.URL + for name, variable := range server.Variables { + if variable == nil { + continue + } + + if variable.Default != "" { + s = strings.Replace(s, "{"+name+"}", variable.Default, 1) + } else if len(variable.Enum) > 0 { + s = strings.Replace(s, "{"+name+"}", variable.Enum[0], 1) + } + } + + if !strings.HasPrefix(s, "http") { + return "", fmt.Errorf("invalid server URL: %s (must use HTTP or HTTPS; relative URLs not supported)", s) + } + return s, nil +} + +func removeRefs(r *openapi3.SchemaRef) { + if r == nil { + return + } + + r.Ref = "" + r.Value.Discriminator = nil // Discriminators are not very useful and can junk up the schema. + + for i := range r.Value.OneOf { + removeRefs(r.Value.OneOf[i]) + } + for i := range r.Value.AnyOf { + removeRefs(r.Value.AnyOf[i]) + } + for i := range r.Value.AllOf { + removeRefs(r.Value.AllOf[i]) + } + removeRefs(r.Value.Not) + removeRefs(r.Value.Items) + + for i := range r.Value.Properties { + removeRefs(r.Value.Properties[i]) + } +} diff --git a/pkg/openapi/list.go b/pkg/openapi/list.go new file mode 100644 index 00000000..857c7014 --- /dev/null +++ b/pkg/openapi/list.go @@ -0,0 +1,68 @@ +package openapi + +import ( + "path/filepath" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +type OperationList struct { + Operations map[string]Operation `json:"operations"` +} + +type Operation struct { + Description string `json:"description,omitempty"` + Summary string `json:"summary,omitempty"` +} + +const ( + ListTool = "list" + NoFilter = "" +) + +func List(t *openapi3.T, filter string) (OperationList, error) { + operations := make(map[string]Operation) + for _, pathItem := range t.Paths.Map() { + for _, operation := range pathItem.Operations() { + var ( + match bool + err error + ) + if filter != "" && filter != NoFilter { + if strings.Contains(filter, "*") { + match, err = MatchFilters(strings.Split(filter, "|"), operation.OperationID) + if err != nil { + return OperationList{}, err + } + } else { + match = operation.OperationID == filter + } + } else { + match = true + } + + if match { + operations[operation.OperationID] = Operation{ + Description: operation.Description, + Summary: operation.Summary, + } + } + } + } + + return OperationList{Operations: operations}, nil +} + +func MatchFilters(filters []string, operationID string) (bool, error) { + for _, filter := range filters { + match, err := filepath.Match(filter, operationID) + if err != nil { + return false, err + } + if match { + return true, nil + } + } + return false, nil +} diff --git a/pkg/openapi/load.go b/pkg/openapi/load.go new file mode 100644 index 00000000..0ff82fdb --- /dev/null +++ b/pkg/openapi/load.go @@ -0,0 +1,121 @@ +package openapi + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" + + "github.com/getkin/kin-openapi/openapi2" + "github.com/getkin/kin-openapi/openapi2conv" + "github.com/getkin/kin-openapi/openapi3" + "gopkg.in/yaml.v3" + kyaml "sigs.k8s.io/yaml" +) + +func Load(source string) (*openapi3.T, error) { + if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { + return loadFromURL(source) + } + return loadFromFile(source) +} + +func loadFromURL(source string) (*openapi3.T, error) { + resp, err := http.DefaultClient.Get(source) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + contents, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return LoadFromBytes(contents) +} + +func loadFromFile(source string) (*openapi3.T, error) { + contents, err := os.ReadFile(source) + if err != nil { + return nil, err + } + + return LoadFromBytes(contents) +} + +func LoadFromBytes(content []byte) (*openapi3.T, error) { + var ( + openAPIDocument *openapi3.T + err error + ) + + switch IsOpenAPI(content) { + case 2: + // Convert OpenAPI v2 to v3 + if !json.Valid(content) { + content, err = kyaml.YAMLToJSON(content) + if err != nil { + return nil, err + } + } + + doc := &openapi2.T{} + if err := doc.UnmarshalJSON(content); err != nil { + return nil, fmt.Errorf("failed to unmarshal OpenAPI v2 document: %w", err) + } + + openAPIDocument, err = openapi2conv.ToV3(doc) + if err != nil { + return nil, fmt.Errorf("failed to convert OpenAPI v2 to v3: %w", err) + } + case 3: + openAPIDocument, err = openapi3.NewLoader().LoadFromData(content) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unsupported OpenAPI version") + } + + return openAPIDocument, nil +} + +// IsOpenAPI checks if the data is an OpenAPI definition and returns the version if it is. +func IsOpenAPI(data []byte) int { + var fragment struct { + Paths map[string]any `json:"paths,omitempty"` + Swagger string `json:"swagger,omitempty"` + OpenAPI string `json:"openapi,omitempty"` + } + + if err := json.Unmarshal(data, &fragment); err != nil { + if err := yaml.Unmarshal(data, &fragment); err != nil { + return 0 + } + } + if len(fragment.Paths) == 0 { + return 0 + } + + if v, _, _ := strings.Cut(fragment.OpenAPI, "."); v != "" { + ver, err := strconv.Atoi(v) + if err != nil { + return 0 + } + return ver + } + + if v, _, _ := strings.Cut(fragment.Swagger, "."); v != "" { + ver, err := strconv.Atoi(v) + if err != nil { + return 0 + } + return ver + } + + return 0 +} diff --git a/pkg/openapi/run.go b/pkg/openapi/run.go new file mode 100644 index 00000000..237d8b57 --- /dev/null +++ b/pkg/openapi/run.go @@ -0,0 +1,451 @@ +package openapi + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/gptscript-ai/gptscript/pkg/env" + "github.com/tidwall/gjson" + "github.com/xeipuuv/gojsonschema" + "golang.org/x/exp/maps" +) + +const RunTool = "run" + +func Run(operationID, defaultHost, args string, t *openapi3.T, envs []string) (string, bool, error) { + envMap := make(map[string]string, len(envs)) + for _, e := range envs { + k, v, _ := strings.Cut(e, "=") + envMap[k] = v + } + + if args == "" { + args = "{}" + } + schemaJSON, opInfo, found, err := GetSchema(operationID, defaultHost, t) + if err != nil || !found { + return "", false, err + } + + // Validate args against the schema. + validationResult, err := gojsonschema.Validate(gojsonschema.NewStringLoader(schemaJSON), gojsonschema.NewStringLoader(args)) + if err != nil { + // We don't return an error here because we want the LLM to be able to maintain control and try again. + return fmt.Sprintf("ERROR: failed to validate arguments. Make sure your arguments are valid JSON. %v", err), true, nil + } + + if !validationResult.Valid() { + // We don't return an error here because we want the LLM to be able to maintain control and try again. + return fmt.Sprintf("invalid arguments for operation %s: %s", operationID, validationResult.Errors()), true, nil + } + + // Construct and execute the HTTP request. + + // Handle path parameters. + opInfo.Path = HandlePathParameters(opInfo.Path, opInfo.PathParams, args) + + // Parse the URL + path, err := url.JoinPath(opInfo.Server, opInfo.Path) + if err != nil { + return "", false, fmt.Errorf("failed to join server and path: %w", err) + } + + u, err := url.Parse(path) + if err != nil { + return "", false, fmt.Errorf("failed to parse server URL %s: %w", opInfo.Server+opInfo.Path, err) + } + + // Set up the request + req, err := http.NewRequest(opInfo.Method, u.String(), nil) + if err != nil { + return "", false, fmt.Errorf("failed to create request: %w", err) + } + + // Check for authentication + if len(opInfo.SecurityInfos) > 0 { + if err := HandleAuths(req, envMap, opInfo.SecurityInfos); err != nil { + return "", false, fmt.Errorf("error setting up authentication: %w", err) + } + } + + // If there is a bearer token set for the whole server, and no Authorization header has been defined, use it. + if token, ok := envMap["GPTSCRIPT_"+env.ToEnvLike(u.Hostname())+"_BEARER_TOKEN"]; ok { + if req.Header.Get("Authorization") == "" { + req.Header.Set("Authorization", "Bearer "+token) + } + } + + // Handle query parameters + req.URL.RawQuery = HandleQueryParameters(req.URL.Query(), opInfo.QueryParams, args).Encode() + + // Handle header and cookie parameters + HandleHeaderParameters(req, opInfo.HeaderParams, args) + HandleCookieParameters(req, opInfo.CookieParams, args) + + // Handle request body + if opInfo.BodyContentMIME != "" { + res := gjson.Get(args, "requestBodyContent") + var body bytes.Buffer + switch opInfo.BodyContentMIME { + case "application/json": + var reqBody any = struct{}{} + if res.Exists() { + reqBody = res.Value() + } + if err := json.NewEncoder(&body).Encode(reqBody); err != nil { + return "", false, fmt.Errorf("failed to encode JSON: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.ContentLength = int64(body.Len()) + + case "text/plain": + reqBody := "" + if res.Exists() { + reqBody = res.String() + } + body.WriteString(reqBody) + + req.Header.Set("Content-Type", "text/plain") + + case "multipart/form-data": + multiPartWriter := multipart.NewWriter(&body) + req.Header.Set("Content-Type", multiPartWriter.FormDataContentType()) + if res.Exists() && res.IsObject() { + for k, v := range res.Map() { + if err := multiPartWriter.WriteField(k, v.String()); err != nil { + return "", false, fmt.Errorf("failed to write multipart field: %w", err) + } + } + } else { + return "", false, fmt.Errorf("multipart/form-data requires an object as the requestBodyContent") + } + if err := multiPartWriter.Close(); err != nil { + return "", false, fmt.Errorf("failed to close multipart writer: %w", err) + } + + default: + return "", false, fmt.Errorf("unsupported MIME type: %s", opInfo.BodyContentMIME) + } + req.Body = io.NopCloser(&body) + } + + // Make the request + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", false, fmt.Errorf("failed to make request: %w", err) + } + defer resp.Body.Close() + + result, err := io.ReadAll(resp.Body) + if err != nil { + return "", false, fmt.Errorf("failed to read response: %w", err) + } + + return string(result), true, nil +} + +// HandleAuths will set up the request with the necessary authentication information. +// A set of sets of SecurityInfo is passed in, where each represents a possible set of security options. +func HandleAuths(req *http.Request, envMap map[string]string, infoSets [][]SecurityInfo) error { + var missingVariables [][]string + + // We need to find a set of infos where we have all the needed environment variables. + for _, infoSet := range infoSets { + var missing []string // Keep track of any missing environment variables + for _, info := range infoSet { + vars := info.getCredentialNamesAndEnvVars(req.URL.Hostname()) + + for _, envName := range vars { + if _, ok := envMap[envName]; !ok { + missing = append(missing, envName) + } + } + } + if len(missing) > 0 { + missingVariables = append(missingVariables, missing) + continue + } + + // We're using this info set, because no environment variables were missing. + // Set up the request as needed. + v := url.Values{} + for _, info := range infoSet { + envNames := maps.Values(info.getCredentialNamesAndEnvVars(req.URL.Hostname())) + switch info.Type { + case "apiKey": + switch info.In { + case "header": + req.Header.Set(info.APIKeyName, envMap[envNames[0]]) + case "query": + v.Add(info.APIKeyName, envMap[envNames[0]]) + case "cookie": + req.AddCookie(&http.Cookie{ + Name: info.APIKeyName, + Value: envMap[envNames[0]], + }) + } + case "http": + switch info.Scheme { + case "bearer": + req.Header.Set("Authorization", "Bearer "+envMap[envNames[0]]) + case "basic": + req.SetBasicAuth(envMap[envNames[0]], envMap[envNames[1]]) + } + } + } + if len(v) > 0 { + req.URL.RawQuery = v.Encode() + } + return nil + } + + return fmt.Errorf("did not find the needed environment variables for any of the security options. "+ + "At least one of these sets of environment variables must be provided: %v", missingVariables) +} + +// HandlePathParameters extracts each path parameter from the input JSON and replaces its placeholder in the URL path. +func HandlePathParameters(path string, params []Parameter, input string) string { + for _, param := range params { + res := gjson.Get(input, param.Name) + if res.Exists() { + // If it's an array or object, handle the serialization style + if res.IsArray() { + switch param.Style { + case "simple", "": // simple is the default style for path parameters + // simple looks the same regardless of whether explode is true + strs := make([]string, len(res.Array())) + for i, item := range res.Array() { + strs[i] = item.String() + } + path = strings.Replace(path, "{"+param.Name+"}", strings.Join(strs, ","), 1) + case "label": + strs := make([]string, len(res.Array())) + for i, item := range res.Array() { + strs[i] = item.String() + } + + if param.Explode == nil || !*param.Explode { // default is to not explode + path = strings.Replace(path, "{"+param.Name+"}", "."+strings.Join(strs, ","), 1) + } else { + path = strings.Replace(path, "{"+param.Name+"}", "."+strings.Join(strs, "."), 1) + } + case "matrix": + strs := make([]string, len(res.Array())) + for i, item := range res.Array() { + strs[i] = item.String() + } + + if param.Explode == nil || !*param.Explode { // default is to not explode + path = strings.Replace(path, "{"+param.Name+"}", ";"+param.Name+"="+strings.Join(strs, ","), 1) + } else { + s := "" + for _, str := range strs { + s += ";" + param.Name + "=" + str + } + path = strings.Replace(path, "{"+param.Name+"}", s, 1) + } + } + } else if res.IsObject() { + switch param.Style { + case "simple", "": + if param.Explode == nil || !*param.Explode { // default is to not explode + var strs []string + for k, v := range res.Map() { + strs = append(strs, k, v.String()) + } + path = strings.Replace(path, "{"+param.Name+"}", strings.Join(strs, ","), 1) + } else { + var strs []string + for k, v := range res.Map() { + strs = append(strs, k+"="+v.String()) + } + path = strings.Replace(path, "{"+param.Name+"}", strings.Join(strs, ","), 1) + } + case "label": + if param.Explode == nil || !*param.Explode { // default is to not explode + var strs []string + for k, v := range res.Map() { + strs = append(strs, k, v.String()) + } + path = strings.Replace(path, "{"+param.Name+"}", "."+strings.Join(strs, ","), 1) + } else { + s := "" + for k, v := range res.Map() { + s += "." + k + "=" + v.String() + } + path = strings.Replace(path, "{"+param.Name+"}", s, 1) + } + case "matrix": + if param.Explode == nil || !*param.Explode { // default is to not explode + var strs []string + for k, v := range res.Map() { + strs = append(strs, k, v.String()) + } + path = strings.Replace(path, "{"+param.Name+"}", ";"+param.Name+"="+strings.Join(strs, ","), 1) + } else { + s := "" + for k, v := range res.Map() { + s += ";" + k + "=" + v.String() + } + path = strings.Replace(path, "{"+param.Name+"}", s, 1) + } + } + } else { + // Serialization is handled slightly differently even for basic types. + // Explode doesn't do anything though. + switch param.Style { + case "simple", "": + path = strings.Replace(path, "{"+param.Name+"}", res.String(), 1) + case "label": + path = strings.Replace(path, "{"+param.Name+"}", "."+res.String(), 1) + case "matrix": + path = strings.Replace(path, "{"+param.Name+"}", ";"+param.Name+"="+res.String(), 1) + } + } + } + } + return path +} + +// HandleQueryParameters extracts each query parameter from the input JSON and adds it to the URL query. +func HandleQueryParameters(q url.Values, params []Parameter, input string) url.Values { + for _, param := range params { + res := gjson.Get(input, param.Name) + if res.Exists() { + // If it's an array or object, handle the serialization style + if res.IsArray() { + switch param.Style { + case "form", "": // form is the default style for query parameters + if param.Explode == nil || *param.Explode { // default is to explode + for _, item := range res.Array() { + q.Add(param.Name, item.String()) + } + } else { + var strs []string + for _, item := range res.Array() { + strs = append(strs, item.String()) + } + q.Add(param.Name, strings.Join(strs, ",")) + } + case "spaceDelimited": + if param.Explode == nil || *param.Explode { + for _, item := range res.Array() { + q.Add(param.Name, item.String()) + } + } else { + var strs []string + for _, item := range res.Array() { + strs = append(strs, item.String()) + } + q.Add(param.Name, strings.Join(strs, " ")) + } + case "pipeDelimited": + if param.Explode == nil || *param.Explode { + for _, item := range res.Array() { + q.Add(param.Name, item.String()) + } + } else { + var strs []string + for _, item := range res.Array() { + strs = append(strs, item.String()) + } + q.Add(param.Name, strings.Join(strs, "|")) + } + } + } else if res.IsObject() { + switch param.Style { + case "form", "": // form is the default style for query parameters + if param.Explode == nil || *param.Explode { // default is to explode + for k, v := range res.Map() { + q.Add(k, v.String()) + } + } else { + var strs []string + for k, v := range res.Map() { + strs = append(strs, k, v.String()) + } + q.Add(param.Name, strings.Join(strs, ",")) + } + case "deepObject": + for k, v := range res.Map() { + q.Add(param.Name+"["+k+"]", v.String()) + } + } + } else { + q.Add(param.Name, res.String()) + } + } + } + return q +} + +// HandleHeaderParameters extracts each header parameter from the input JSON and adds it to the request headers. +func HandleHeaderParameters(req *http.Request, params []Parameter, input string) { + for _, param := range params { + res := gjson.Get(input, param.Name) + if res.Exists() { + if res.IsArray() { + strs := make([]string, len(res.Array())) + for i, item := range res.Array() { + strs[i] = item.String() + } + req.Header.Add(param.Name, strings.Join(strs, ",")) + } else if res.IsObject() { + // Handle explosion + var strs []string + if param.Explode == nil || !*param.Explode { // default is to not explode + for k, v := range res.Map() { + strs = append(strs, k, v.String()) + } + } else { + for k, v := range res.Map() { + strs = append(strs, k+"="+v.String()) + } + } + req.Header.Add(param.Name, strings.Join(strs, ",")) + } else { // basic type + req.Header.Add(param.Name, res.String()) + } + } + } +} + +// HandleCookieParameters extracts each cookie parameter from the input JSON and adds it to the request cookies. +func HandleCookieParameters(req *http.Request, params []Parameter, input string) { + for _, param := range params { + res := gjson.Get(input, param.Name) + if res.Exists() { + if res.IsArray() { + strs := make([]string, len(res.Array())) + for i, item := range res.Array() { + strs[i] = item.String() + } + req.AddCookie(&http.Cookie{ + Name: param.Name, + Value: strings.Join(strs, ","), + }) + } else if res.IsObject() { + var strs []string + for k, v := range res.Map() { + strs = append(strs, k, v.String()) + } + req.AddCookie(&http.Cookie{ + Name: param.Name, + Value: strings.Join(strs, ","), + }) + } else { // basic type + req.AddCookie(&http.Cookie{ + Name: param.Name, + Value: res.String(), + }) + } + } + } +} diff --git a/pkg/openapi/security.go b/pkg/openapi/security.go new file mode 100644 index 00000000..dd4521fc --- /dev/null +++ b/pkg/openapi/security.go @@ -0,0 +1,56 @@ +package openapi + +import ( + "fmt" + "strings" + + "github.com/gptscript-ai/gptscript/pkg/env" +) + +// A SecurityInfo represents a security scheme in OpenAPI. +type SecurityInfo struct { + Name string `json:"name"` // name as defined in the security schemes + Type string `json:"type"` // http or apiKey + Scheme string `json:"scheme"` // bearer or basic, for type==http + APIKeyName string `json:"apiKeyName"` // name of the API key, for type==apiKey + In string `json:"in"` // header, query, or cookie, for type==apiKey +} + +func (i SecurityInfo) GetCredentialToolStrings(hostname string) []string { + vars := i.getCredentialNamesAndEnvVars(hostname) + var tools []string + + for cred, v := range vars { + field := "value" + switch i.Type { + case "apiKey": + field = i.APIKeyName + case "http": + if i.Scheme == "bearer" { + field = "bearer token" + } else { + if strings.Contains(v, "PASSWORD") { + field = "password" + } else { + field = "username" + } + } + } + + tools = append(tools, fmt.Sprintf("github.com/gptscript-ai/credential as %s with %s as env and %q as message and %q as field", + cred, v, "Please provide a value for the "+v+" environment variable", field)) + } + return tools +} + +func (i SecurityInfo) getCredentialNamesAndEnvVars(hostname string) map[string]string { + if i.Type == "http" && i.Scheme == "basic" { + return map[string]string{ + hostname + i.Name + "Username": "GPTSCRIPT_" + env.ToEnvLike(hostname) + "_" + env.ToEnvLike(i.Name) + "_USERNAME", + hostname + i.Name + "Password": "GPTSCRIPT_" + env.ToEnvLike(hostname) + "_" + env.ToEnvLike(i.Name) + "_PASSWORD", + } + } + return map[string]string{ + hostname + i.Name: "GPTSCRIPT_" + env.ToEnvLike(hostname) + "_" + env.ToEnvLike(i.Name), + } +} diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index b998320b..3d26d9cc 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -1,22 +1,25 @@ package parser import ( - "bufio" "fmt" "io" + "maps" + "path" "regexp" "slices" "strconv" "strings" - "github.com/getkin/kin-openapi/openapi3" "github.com/gptscript-ai/gptscript/pkg/types" + "github.com/modelcontextprotocol/go-sdk/jsonschema" ) var ( sepRegex = regexp.MustCompile(`^\s*---+\s*$`) + endHeaderRegex = regexp.MustCompile(`^\s*===+\s*$`) strictSepRegex = regexp.MustCompile(`^---\n$`) - skipRegex = regexp.MustCompile(`^![-\w]+\s*$`) + skipRegex = regexp.MustCompile(`^![ -.:*\w]+\s*$`) + nameRegex = regexp.MustCompile(`^[a-z]+$`) ) func normalize(key string) string { @@ -50,10 +53,10 @@ func csv(line string) (result []string) { } func addArg(line string, tool *types.Tool) error { - if tool.Parameters.Arguments == nil { - tool.Parameters.Arguments = &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{}, + if tool.Arguments == nil { + tool.Arguments = &jsonschema.Schema{ + Type: "object", + Properties: make(map[string]*jsonschema.Schema, 1), } } @@ -62,17 +65,15 @@ func addArg(line string, tool *types.Tool) error { return fmt.Errorf("invalid arg format: %s", line) } - tool.Parameters.Arguments.Properties[key] = &openapi3.SchemaRef{ - Value: &openapi3.Schema{ - Description: strings.TrimSpace(value), - Type: &openapi3.Types{"string"}, - }, + tool.Arguments.Properties[key] = &jsonschema.Schema{ + Description: strings.TrimSpace(value), + Type: "string", } return nil } -func isParam(line string, tool *types.Tool) (_ bool, err error) { +func isParam(line string, tool *types.Tool, scan *simplescanner) (_ bool, err error) { key, value, ok := strings.Cut(line, ":") if !ok { return false, nil @@ -80,53 +81,65 @@ func isParam(line string, tool *types.Tool) (_ bool, err error) { value = strings.TrimSpace(value) switch normalize(key) { case "name": - tool.Parameters.Name = value + tool.Name = value case "modelprovider": - tool.Parameters.ModelProvider = true + tool.ModelProvider = true case "model", "modelname": - tool.Parameters.ModelName = value + tool.ModelName = value case "globalmodel", "globalmodelname": - tool.Parameters.GlobalModelName = value + tool.GlobalModelName = value case "description": - tool.Parameters.Description = value + tool.Description = scan.AddMultiline(value) case "internalprompt": v, err := toBool(value) if err != nil { return false, err } - tool.Parameters.InternalPrompt = &v + tool.InternalPrompt = &v case "chat": v, err := toBool(value) if err != nil { return false, err } - tool.Parameters.Chat = v - case "export", "exporttool", "exports", "exporttools", "sharetool", "sharetools": - tool.Parameters.Export = append(tool.Parameters.Export, csv(value)...) + tool.Chat = v + case "export", "exporttool", "exports", "exporttools", "sharetool", "sharetools", "sharedtool", "sharedtools": + tool.Export = append(tool.Export, csv(scan.AddMultiline(value))...) case "tool", "tools": - tool.Parameters.Tools = append(tool.Parameters.Tools, csv(value)...) + tool.Tools = append(tool.Tools, csv(scan.AddMultiline(value))...) case "inputfilter", "inputfilters": - tool.Parameters.InputFilters = append(tool.Parameters.InputFilters, csv(value)...) - case "shareinputfilter", "shareinputfilters": - tool.Parameters.ExportInputFilters = append(tool.Parameters.ExportInputFilters, csv(value)...) + tool.InputFilters = append(tool.InputFilters, csv(scan.AddMultiline(value))...) + case "shareinputfilter", "shareinputfilters", "sharedinputfilter", "sharedinputfilters": + tool.ExportInputFilters = append(tool.ExportInputFilters, csv(scan.AddMultiline(value))...) case "outputfilter", "outputfilters": - tool.Parameters.OutputFilters = append(tool.Parameters.OutputFilters, csv(value)...) - case "shareoutputfilter", "shareoutputfilters": - tool.Parameters.ExportOutputFilters = append(tool.Parameters.ExportOutputFilters, csv(value)...) + tool.OutputFilters = append(tool.OutputFilters, csv(scan.AddMultiline(value))...) + case "shareoutputfilter", "shareoutputfilters", "sharedoutputfilter", "sharedoutputfilters": + tool.ExportOutputFilters = append(tool.ExportOutputFilters, csv(scan.AddMultiline(value))...) case "agent", "agents": - tool.Parameters.Agents = append(tool.Parameters.Agents, csv(value)...) + tool.Agents = append(tool.Agents, csv(scan.AddMultiline(value))...) case "globaltool", "globaltools": - tool.Parameters.GlobalTools = append(tool.Parameters.GlobalTools, csv(value)...) - case "exportcontext", "exportcontexts", "sharecontext", "sharecontexts": - tool.Parameters.ExportContext = append(tool.Parameters.ExportContext, csv(value)...) + tool.GlobalTools = append(tool.GlobalTools, csv(scan.AddMultiline(value))...) + case "exportcontext", "exportcontexts", "sharecontext", "sharecontexts", "sharedcontext", "sharedcontexts": + tool.ExportContext = append(tool.ExportContext, csv(scan.AddMultiline(value))...) case "context": - tool.Parameters.Context = append(tool.Parameters.Context, csv(value)...) + tool.Context = append(tool.Context, csv(scan.AddMultiline(value))...) + case "stdin": + b, err := toBool(value) + if err != nil { + return false, err + } + tool.Stdin = b + case "metadata": + mkey, mvalue, _ := strings.Cut(scan.AddMultiline(value), ":") + if tool.MetaData == nil { + tool.MetaData = map[string]string{} + } + tool.MetaData[strings.TrimSpace(mkey)] = strings.TrimSpace(mvalue) case "args", "arg", "param", "params", "parameters", "parameter": - if err := addArg(value, tool); err != nil { + if err := addArg(scan.AddMultiline(value), tool); err != nil { return false, err } case "maxtoken", "maxtokens": - tool.Parameters.MaxTokens, err = strconv.Atoi(value) + tool.MaxTokens, err = strconv.Atoi(value) if err != nil { return false, err } @@ -135,21 +148,25 @@ func isParam(line string, tool *types.Tool) (_ bool, err error) { if err != nil { return false, err } - tool.Parameters.Cache = &b + tool.Cache = &b case "jsonmode", "json", "jsonoutput", "jsonformat", "jsonresponse": - tool.Parameters.JSONResponse, err = toBool(value) + tool.JSONResponse, err = toBool(value) if err != nil { return false, err } case "temperature": - tool.Parameters.Temperature, err = toFloatPtr(value) + tool.Temperature, err = toFloatPtr(value) if err != nil { return false, err } case "credentials", "creds", "credential", "cred": - tool.Parameters.Credentials = append(tool.Parameters.Credentials, value) + tool.Credentials = append(tool.Credentials, csv(scan.AddMultiline(value))...) + case "sharecredentials", "sharecreds", "sharecredential", "sharecred", "sharedcredentials", "sharedcreds", "sharedcredential", "sharedcred": + tool.ExportCredentials = append(tool.ExportCredentials, scan.AddMultiline(value)) + case "type": + tool.Type = types.ToolType(strings.ToLower(value)) default: - return false, nil + return nameRegex.MatchString(key), nil } return true, nil @@ -192,13 +209,15 @@ type context struct { func (c *context) finish(tools *[]Node) { c.tool.Instructions = strings.TrimSpace(strings.Join(c.instructions, "")) if c.tool.Instructions != "" || - c.tool.Parameters.Name != "" || + c.tool.Name != "" || len(c.tool.Export) > 0 || len(c.tool.Tools) > 0 || c.tool.GlobalModelName != "" || len(c.tool.GlobalTools) > 0 || len(c.tool.ExportInputFilters) > 0 || len(c.tool.ExportOutputFilters) > 0 || + len(c.tool.Agents) > 0 || + len(c.tool.ExportCredentials) > 0 || c.tool.Chat { *tools = append(*tools, Node{ ToolNode: &ToolNode{ @@ -242,7 +261,7 @@ func writeSep(buf *strings.Builder, lastText bool) { } } -func (d Document) String() string { +func (d Document) Print() string { buf := strings.Builder{} lastText := false for _, node := range d.Nodes { @@ -253,7 +272,7 @@ func (d Document) String() string { } if node.ToolNode != nil { writeSep(&buf, lastText) - buf.WriteString(node.ToolNode.Tool.String()) + buf.WriteString(node.ToolNode.Tool.Print()) lastText = false } } @@ -303,6 +322,8 @@ func Parse(input io.Reader, opts ...Options) (Document, error) { } } + nodes = assignMetadata(nodes) + if !opt.AssignGlobals { return Document{ Nodes: nodes, @@ -354,6 +375,55 @@ func Parse(input io.Reader, opts ...Options) (Document, error) { }, nil } +func assignMetadata(nodes []Node) (result []Node) { + metadata := map[string]map[string]string{} + result = make([]Node, 0, len(nodes)) + for _, node := range nodes { + if node.TextNode != nil { + body, ok := strings.CutPrefix(node.TextNode.Text, "!metadata:") + if ok { + line, rest, ok := strings.Cut(body, "\n") + if ok { + toolName, metaKey, ok := strings.Cut(strings.TrimSpace(line), ":") + if ok { + d, ok := metadata[toolName] + if !ok { + d = map[string]string{} + metadata[toolName] = d + } + d[metaKey] = strings.TrimSpace(rest) + } + } + } + } + } + if len(metadata) == 0 { + return nodes + } + + for _, node := range nodes { + if node.ToolNode != nil { + if node.ToolNode.Tool.MetaData == nil { + node.ToolNode.Tool.MetaData = map[string]string{} + } + maps.Copy(node.ToolNode.Tool.MetaData, metadata[node.ToolNode.Tool.Name]) + for wildcard := range metadata { + if strings.Contains(wildcard, "*") { + if m, err := path.Match(wildcard, node.ToolNode.Tool.Name); m && err == nil { + if node.ToolNode.Tool.MetaData == nil { + node.ToolNode.Tool.MetaData = map[string]string{} + } + maps.Copy(node.ToolNode.Tool.MetaData, metadata[wildcard]) + } + } + } + } + result = append(result, node) + } + + return +} + func isGPTScriptHashBang(line string) bool { if !strings.HasPrefix(line, "#!") { return false @@ -378,15 +448,71 @@ func isGPTScriptHashBang(line string) bool { return false } -func parse(input io.Reader) ([]Node, error) { - scan := bufio.NewScanner(input) +type simplescanner struct { + lines []string +} +func newSimpleScanner(data []byte) *simplescanner { + if len(data) == 0 { + return &simplescanner{} + } + lines := strings.Split(string(data), "\n") + return &simplescanner{ + lines: append([]string{""}, lines...), + } +} + +func dropCR(s string) string { + if len(s) > 0 && s[len(s)-1] == '\r' { + return s[:len(s)-1] + } + return s +} + +func (s *simplescanner) AddMultiline(current string) string { + result := current + for { + if len(s.lines) < 2 || len(s.lines[1]) == 0 { + return result + } + if strings.HasPrefix(s.lines[1], " ") || strings.HasPrefix(s.lines[1], "\t") { + result += " " + dropCR(s.lines[1]) + s.lines = s.lines[1:] + } else { + return result + } + } +} + +func (s *simplescanner) Text() string { + if len(s.lines) == 0 { + return "" + } + return dropCR(s.lines[0]) +} + +func (s *simplescanner) Scan() bool { + if len(s.lines) == 0 { + return false + } + s.lines = s.lines[1:] + return true +} + +func parse(input io.Reader) ([]Node, error) { var ( tools []Node context context lineNo int ) + data, err := io.ReadAll(input) + if err != nil { + return nil, err + } + + scan := newSimpleScanner(data) + for scan.Scan() { lineNo++ if context.tool.Source.LineNo == 0 { @@ -433,11 +559,15 @@ func parse(input io.Reader) ([]Node, error) { } // Look for params - if isParam, err := isParam(line, &context.tool); err != nil { + if isParam, err := isParam(line, &context.tool, scan); err != nil { return nil, NewErrLine("", lineNo, err) } else if isParam { context.seenParam = true continue + } else if endHeaderRegex.MatchString(line) { + // force the end of the header and don't include the current line in the header + context.inBody = true + continue } } diff --git a/pkg/parser/parser_test.go b/pkg/parser/parser_test.go index 9f682efa..a3263539 100644 --- a/pkg/parser/parser_test.go +++ b/pkg/parser/parser_test.go @@ -1,11 +1,13 @@ package parser import ( + "reflect" "strings" "testing" "github.com/gptscript-ai/gptscript/pkg/types" "github.com/hexops/autogold/v2" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -239,3 +241,136 @@ share output filters: shared }}, }}).Equal(t, out) } + +func TestParseMetaDataSpace(t *testing.T) { + input := ` +name: a space +body +--- +!metadata:a space:other +foo bar +` + tools, err := ParseTools(strings.NewReader(input)) + require.NoError(t, err) + + assert.Len(t, tools, 1) + autogold.Expect(map[string]string{ + "other": "foo bar", + }).Equal(t, tools[0].MetaData) +} + +func TestParseMetaData(t *testing.T) { + input := ` +name: first +metadata: foo: bar + +body +--- +!metadata:first:package.json +foo=base +f + +--- +!metadata:first2:requirements.txt +asdf2 + +--- +!metadata:first:requirements.txt +asdf + +--- +!metadata:f*r*:other + +foo bar +` + tools, err := ParseTools(strings.NewReader(input)) + require.NoError(t, err) + + assert.Len(t, tools, 1) + autogold.Expect(map[string]string{ + "foo": "bar", + "package.json": "foo=base\nf", + "requirements.txt": "asdf", + "other": "foo bar", + }).Equal(t, tools[0].MetaData) + + autogold.Expect(`Name: first +Meta Data: foo: bar +Meta Data: other: foo bar +Meta Data: requirements.txt: asdf + +body +--- +!metadata:first:package.json +foo=base +f +`).Equal(t, tools[0].Print()) +} + +func TestFormatWithBadInstruction(t *testing.T) { + input := types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "foo", + }, + Instructions: "foo: bar", + }, + } + autogold.Expect("Name: foo\n===\nfoo: bar\n").Equal(t, input.Print()) + + tools, err := ParseTools(strings.NewReader(input.Print())) + require.NoError(t, err) + if reflect.DeepEqual(input, tools[0]) { + t.Errorf("expected %v, got %v", input, tools[0]) + } +} + +func TestSingleTool(t *testing.T) { + input := ` +name: foo + +#!sys.echo +hi +` + + tools, err := ParseTools(strings.NewReader(input)) + require.NoError(t, err) + autogold.Expect(types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{Name: "foo"}, + Instructions: "#!sys.echo\nhi", + }, + Source: types.ToolSource{LineNo: 1}, + }).Equal(t, tools[0]) +} + +func TestMultiline(t *testing.T) { + input := ` +name: first +credential: foo + , bar, + baz +model: the model + +body +` + tools, err := ParseTools(strings.NewReader(input)) + require.NoError(t, err) + + assert.Len(t, tools, 1) + autogold.Expect(types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "first", + ModelName: "the model", + Credentials: []string{ + "foo", + "bar", + "baz", + }, + }, + Instructions: "body", + }, + Source: types.ToolSource{LineNo: 1}, + }).Equal(t, tools[0]) +} diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index c1b693be..fa4beeb6 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -51,9 +51,10 @@ func sysPromptHTTP(ctx context.Context, envs []string, url string, prompt types. func SysPrompt(ctx context.Context, envs []string, input string, _ chan<- string) (_ string, err error) { var params struct { - Message string `json:"message,omitempty"` - Fields string `json:"fields,omitempty"` - Sensitive string `json:"sensitive,omitempty"` + Message string `json:"message,omitempty"` + Fields types.Fields `json:"fields,omitempty"` + Sensitive string `json:"sensitive,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` } if err := json.Unmarshal([]byte(input), ¶ms); err != nil { return "", err @@ -63,9 +64,11 @@ func SysPrompt(ctx context.Context, envs []string, input string, _ chan<- string if url, ok := strings.CutPrefix(env, types.PromptURLEnvVar+"="); ok { httpPrompt := types.Prompt{ Message: params.Message, - Fields: strings.Split(params.Fields, ","), + Fields: params.Fields, Sensitive: params.Sensitive == "true", + Metadata: params.Metadata, } + return sysPromptHTTP(ctx, envs, url, httpPrompt) } } @@ -76,7 +79,7 @@ func SysPrompt(ctx context.Context, envs []string, input string, _ chan<- string func sysPrompt(ctx context.Context, req types.Prompt) (_ string, err error) { defer context2.GetPauseFuncFromCtx(ctx)()() - if req.Message != "" && len(req.Fields) == 1 && strings.TrimSpace(req.Fields[0]) == "" { + if req.Message != "" && len(req.Fields) == 0 { var errs []error _, err := fmt.Fprintln(os.Stderr, req.Message) errs = append(errs, err) @@ -94,21 +97,25 @@ func sysPrompt(ctx context.Context, req types.Prompt) (_ string, err error) { results := map[string]string{} for _, f := range req.Fields { var ( - value string - msg = f + value string + msg = f.Name + sensitive = req.Sensitive ) + if f.Sensitive != nil { + sensitive = *f.Sensitive + } if len(req.Fields) == 1 && req.Message != "" { msg = req.Message } - if req.Sensitive { - err = survey.AskOne(&survey.Password{Message: msg}, &value, survey.WithStdio(os.Stdin, os.Stderr, os.Stderr)) + if sensitive { + err = survey.AskOne(&survey.Password{Message: msg, Help: f.Description}, &value, survey.WithStdio(os.Stdin, os.Stderr, os.Stderr)) } else { - err = survey.AskOne(&survey.Input{Message: msg}, &value, survey.WithStdio(os.Stdin, os.Stderr, os.Stderr)) + err = survey.AskOne(&survey.Input{Message: msg, Help: f.Description}, &value, survey.WithStdio(os.Stdin, os.Stderr, os.Stderr)) } if err != nil { return "", err } - results[f] = value + results[f.Name] = value } resultsStr, err := json.Marshal(results) diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index 6cb3644e..441a01dd 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -9,8 +9,8 @@ import ( "strings" "sync" + openai2 "github.com/gptscript-ai/chat-completion-client" "github.com/gptscript-ai/gptscript/pkg/cache" - gcontext "github.com/gptscript-ai/gptscript/pkg/context" "github.com/gptscript-ai/gptscript/pkg/credentials" "github.com/gptscript-ai/gptscript/pkg/engine" env2 "github.com/gptscript-ai/gptscript/pkg/env" @@ -22,39 +22,48 @@ import ( ) type Client struct { - clientsLock sync.Mutex - cache *cache.Client - clients map[string]*openai.Client - models map[string]*openai.Client - runner *runner.Runner - envs []string - credStore credentials.CredentialStore + clientsLock sync.Mutex + cache *cache.Client + clients map[string]clientInfo + runner *runner.Runner + envs []string + credStore credentials.CredentialStore + defaultProvider string } -func New(r *runner.Runner, envs []string, cache *cache.Client, credStore credentials.CredentialStore) *Client { +func New(r *runner.Runner, envs []string, cache *cache.Client, credStore credentials.CredentialStore, defaultProvider string) *Client { return &Client{ - cache: cache, - runner: r, - envs: envs, - credStore: credStore, + cache: cache, + runner: r, + envs: envs, + credStore: credStore, + defaultProvider: defaultProvider, + clients: make(map[string]clientInfo), } } -func (c *Client) Call(ctx context.Context, messageRequest types.CompletionRequest, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) { - c.clientsLock.Lock() - client, ok := c.models[messageRequest.Model] - c.clientsLock.Unlock() - - if !ok { +func (c *Client) Call(ctx context.Context, messageRequest types.CompletionRequest, env []string, status chan<- types.CompletionStatus) (*types.CompletionMessage, error) { + _, provider := c.parseModel(messageRequest.Model) + if provider == "" { return nil, fmt.Errorf("failed to find remote model %s", messageRequest.Model) } - _, modelName := types.SplitToolRef(messageRequest.Model) + client, err := c.load(ctx, provider, env...) + if err != nil { + return nil, err + } + + toolName, modelName := types.SplitToolRef(messageRequest.Model) + if modelName == "" { + // modelName is empty, then the messageRequest.Model is not of the form 'modelName from provider' + // Therefore, the modelName is the toolName + modelName = toolName + } messageRequest.Model = modelName - return client.Call(ctx, messageRequest, status) + return client.Call(ctx, messageRequest, env, status) } -func (c *Client) ListModels(ctx context.Context, providers ...string) (result []string, _ error) { +func (c *Client) ListModels(ctx context.Context, providers ...string) (result []openai2.Model, _ error) { for _, provider := range providers { client, err := c.load(ctx, provider) if err != nil { @@ -64,34 +73,40 @@ func (c *Client) ListModels(ctx context.Context, providers ...string) (result [] if err != nil { return nil, err } - for _, model := range models { - result = append(result, model+" from "+provider) + for i := range models { + models[i].ID = fmt.Sprintf("%s from %s", models[i].ID, provider) } + + result = append(result, models...) } - sort.Strings(result) + sort.Slice(result, func(i, j int) bool { + return result[i].ID < result[j].ID + }) return } -func (c *Client) Supports(ctx context.Context, modelName string) (bool, error) { - toolName, modelNameSuffix := types.SplitToolRef(modelName) - if modelNameSuffix == "" { +func (c *Client) parseModel(modelString string) (modelName, providerName string) { + toolName, subTool := types.SplitToolRef(modelString) + if subTool == "" { + // This is just a plain model string "gpt4o" + return toolName, c.defaultProvider + } + // This is a provider string "modelName from provider" + return subTool, toolName +} + +func (c *Client) Supports(ctx context.Context, modelString string) (bool, error) { + _, providerName := c.parseModel(modelString) + if providerName == "" { return false, nil } - client, err := c.load(ctx, toolName) + _, err := c.load(ctx, providerName) if err != nil { return false, err } - c.clientsLock.Lock() - defer c.clientsLock.Unlock() - - if c.models == nil { - c.models = map[string]*openai.Client{} - } - - c.models[modelName] = client return true, nil } @@ -100,7 +115,7 @@ func isHTTPURL(toolName string) bool { strings.HasPrefix(toolName, "https://") } -func (c *Client) clientFromURL(ctx context.Context, apiURL string) (*openai.Client, error) { +func (c *Client) clientFromURL(ctx context.Context, apiURL string, envs []string) (*openai.Client, error) { parsed, err := url.Parse(apiURL) if err != nil { return nil, err @@ -110,7 +125,7 @@ func (c *Client) clientFromURL(ctx context.Context, apiURL string) (*openai.Clie if key == "" && !isLocalhost(apiURL) { var err error - key, err = c.retrieveAPIKey(ctx, env, apiURL) + key, err = c.retrieveAPIKey(ctx, env, apiURL, envs) if err != nil { return nil, err } @@ -123,25 +138,24 @@ func (c *Client) clientFromURL(ctx context.Context, apiURL string) (*openai.Clie }) } -func (c *Client) load(ctx context.Context, toolName string) (*openai.Client, error) { +func (c *Client) load(ctx context.Context, toolName string, env ...string) (*openai.Client, error) { c.clientsLock.Lock() defer c.clientsLock.Unlock() client, ok := c.clients[toolName] - if ok { - return client, nil - } - - if c.clients == nil { - c.clients = make(map[string]*openai.Client) + if ok && !isHTTPURL(toolName) && engine.IsDaemonRunning(client.url) { + return client.client, nil } if isHTTPURL(toolName) { - remoteClient, err := c.clientFromURL(ctx, toolName) + remoteClient, err := c.clientFromURL(ctx, toolName, env) if err != nil { return nil, err } - c.clients[toolName] = remoteClient + c.clients[toolName] = clientInfo{ + client: remoteClient, + url: toolName, + } return remoteClient, nil } @@ -152,19 +166,13 @@ func (c *Client) load(ctx context.Context, toolName string) (*openai.Client, err return nil, err } - url, err := c.runner.Run(engine.WithToolCategory(ctx, engine.ProviderToolCategory), prg.SetBlocking(), c.envs, "") + url, err := c.runner.Run(engine.WithToolCategory(ctx, engine.ProviderToolCategory), prg.SetBlocking(), c.envs, "", runner.RunOptions{}) if err != nil { return nil, err } - if strings.HasSuffix(url, "/") { - url += "v1" - } else { - url += "/v1" - } - - client, err = openai.NewClient(ctx, c.credStore, openai.Options{ - BaseURL: url, + oClient, err := openai.NewClient(ctx, c.credStore, openai.Options{ + BaseURL: strings.TrimSuffix(url, "/") + "/v1", Cache: c.cache, CacheKey: prg.EntryToolID, }) @@ -172,15 +180,23 @@ func (c *Client) load(ctx context.Context, toolName string) (*openai.Client, err return nil, err } - c.clients[toolName] = client - return client, nil + c.clients[toolName] = clientInfo{ + client: oClient, + url: url, + } + return oClient, nil } -func (c *Client) retrieveAPIKey(ctx context.Context, env, url string) (string, error) { - return prompt.GetModelProviderCredential(ctx, c.credStore, url, env, fmt.Sprintf("Please provide your API key for %s", url), append(gcontext.GetEnv(ctx), c.envs...)) +func (c *Client) retrieveAPIKey(ctx context.Context, env, url string, envs []string) (string, error) { + return prompt.GetModelProviderCredential(ctx, c.credStore, url, env, fmt.Sprintf("Please provide your API key for %s", url), append(envs, c.envs...)) } func isLocalhost(url string) bool { return strings.HasPrefix(url, "http://localhost") || strings.HasPrefix(url, "http://127.0.0.1") || strings.HasPrefix(url, "https://localhost") || strings.HasPrefix(url, "https://127.0.0.1") } + +type clientInfo struct { + client *openai.Client + url string +} diff --git a/pkg/repos/download/extract.go b/pkg/repos/download/extract.go index 95a82d74..9615d372 100644 --- a/pkg/repos/download/extract.go +++ b/pkg/repos/download/extract.go @@ -9,10 +9,12 @@ import ( "net/http" "net/url" "os" + "path" "path/filepath" + "strings" "time" - "github.com/mholt/archiver/v4" + "github.com/mholt/archives" ) func Extract(ctx context.Context, downloadURL, digest, targetDir string) error { @@ -60,17 +62,29 @@ func Extract(ctx context.Context, downloadURL, digest, targetDir string) error { return err } - format, input, err := archiver.Identify(filepath.Base(parsedURL.Path), tmpFile) + bin := path.Base(parsedURL.Path) + if strings.HasSuffix(bin, ".exe") { + dst, err := os.Create(filepath.Join(targetDir, bin)) + if err != nil { + return err + } + defer dst.Close() + + _, err = io.Copy(dst, tmpFile) + return err + } + + format, input, err := archives.Identify(ctx, filepath.Base(parsedURL.Path), tmpFile) if err != nil { return err } - ex, ok := format.(archiver.Extractor) + ex, ok := format.(archives.Extractor) if !ok { return fmt.Errorf("failed to detect proper archive for extraction from %s got: %v", downloadURL, ex) } - err = ex.Extract(ctx, input, nil, func(_ context.Context, f archiver.File) error { + err = ex.Extract(ctx, input, func(_ context.Context, f archives.FileInfo) error { target := filepath.Join(targetDir, f.NameInArchive) if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { return err diff --git a/pkg/repos/get.go b/pkg/repos/get.go index be8d8594..d77fb526 100644 --- a/pkg/repos/get.go +++ b/pkg/repos/get.go @@ -4,30 +4,24 @@ import ( "context" "encoding/json" "errors" - "fmt" "io/fs" "os" - "path" "path/filepath" + "regexp" "strings" - "sync" - "time" "github.com/BurntSushi/locker" - "github.com/gptscript-ai/gptscript/pkg/config" - "github.com/gptscript-ai/gptscript/pkg/credentials" - "github.com/gptscript-ai/gptscript/pkg/loader/github" + "github.com/gptscript-ai/gptscript/pkg/hash" "github.com/gptscript-ai/gptscript/pkg/repos/git" - "github.com/gptscript-ai/gptscript/pkg/repos/runtimes/golang" "github.com/gptscript-ai/gptscript/pkg/types" ) -const credentialHelpersRepo = "github.com/gptscript-ai/gptscript-credential-helpers" - type Runtime interface { ID() string - Supports(cmd []string) bool - Setup(ctx context.Context, dataRoot, toolSource string, env []string) ([]string, error) + Supports(tool types.Tool, cmd []string) bool + Binary(ctx context.Context, tool types.Tool, dataRoot, toolSource string, env []string) (bool, []string, error) + Setup(ctx context.Context, tool types.Tool, dataRoot, toolSource string, env []string) ([]string, error) + GetHash(tool types.Tool) (string, error) } type noopRuntime struct { @@ -37,154 +31,62 @@ func (n noopRuntime) ID() string { return "none" } -func (n noopRuntime) Supports(_ []string) bool { - return false -} - -func (n noopRuntime) Setup(_ context.Context, _, _ string, _ []string) ([]string, error) { - return nil, nil +func (n noopRuntime) GetHash(_ types.Tool) (string, error) { + return "", nil } -type Manager struct { - storageDir string - gitDir string - runtimeDir string - credHelperDirs credentials.CredentialHelperDirs - runtimes []Runtime - credHelperConfig *credHelperConfig +func (n noopRuntime) Supports(_ types.Tool, _ []string) bool { + return false } -type credHelperConfig struct { - lock sync.Mutex - initialized bool - cliCfg *config.CLIConfig - env []string +func (n noopRuntime) Binary(_ context.Context, _ types.Tool, _, _ string, _ []string) (bool, []string, error) { + return false, nil, nil } -func New(cacheDir string, runtimes ...Runtime) *Manager { - root := filepath.Join(cacheDir, "repos") - return &Manager{ - storageDir: root, - gitDir: filepath.Join(root, "git"), - runtimeDir: filepath.Join(root, "runtimes"), - credHelperDirs: credentials.GetCredentialHelperDirs(cacheDir), - runtimes: runtimes, - } -} - -func (m *Manager) EnsureCredentialHelpers(ctx context.Context) error { - if m.credHelperConfig == nil { - return nil - } - m.credHelperConfig.lock.Lock() - defer m.credHelperConfig.lock.Unlock() - - if !m.credHelperConfig.initialized { - if err := m.deferredSetUpCredentialHelpers(ctx, m.credHelperConfig.cliCfg, m.credHelperConfig.env); err != nil { - return err - } - m.credHelperConfig.initialized = true - } - - return nil +func (n noopRuntime) Setup(_ context.Context, _ types.Tool, _, _ string, _ []string) ([]string, error) { + return nil, nil } -func (m *Manager) SetUpCredentialHelpers(_ context.Context, cliCfg *config.CLIConfig, env []string) error { - m.credHelperConfig = &credHelperConfig{ - cliCfg: cliCfg, - env: env, - } - return nil +type Manager struct { + cacheDir string + storageDir string + gitDir string + runtimeDir string + systemDirs []string + runtimes []Runtime } -func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error { +func New(cacheDir, systemDir string, runtimes ...Runtime) *Manager { var ( - helperName = cliCfg.CredentialsStore - suffix string + systemDirs []string + root = filepath.Join(cacheDir, "repos") ) - if helperName == "wincred" { - suffix = ".exe" - } - - // The file helper is built-in and does not need to be compiled. - if helperName == "file" { - return nil - } - - locker.Lock("gptscript-credential-helpers") - defer locker.Unlock("gptscript-credential-helpers") - - // Load the last-checked file to make sure we haven't checked the repo in the last 24 hours. - now := time.Now() - lastChecked, err := os.ReadFile(m.credHelperDirs.LastCheckedFile) - if err == nil { - if t, err := time.Parse(time.RFC3339, strings.TrimSpace(string(lastChecked))); err == nil && now.Sub(t) < 24*time.Hour { - // Make sure the binary still exists, and if it does, return. - if _, err := os.Stat(filepath.Join(m.credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil { - log.Debugf("Credential helper %s up-to-date as of %v, checking for updates after %v", helperName, t, t.Add(24*time.Hour)) - return nil - } - } - } - - // Load the credential helpers repo information. - _, _, repo, _, err := github.Load(ctx, nil, credentialHelpersRepo) - if err != nil { - return err - } - - if err := os.MkdirAll(path.Dir(m.credHelperDirs.LastCheckedFile), 0755); err != nil { - return err - } - - // Update the last-checked file. - if err := os.WriteFile(m.credHelperDirs.LastCheckedFile, []byte(now.Format(time.RFC3339)), 0644); err != nil { - return err - } - - var needsBuild bool - // Check the last revision shasum and see if it is different from the current one. - lastRevision, err := os.ReadFile(m.credHelperDirs.RevisionFile) - if (err == nil && strings.TrimSpace(string(lastRevision)) != repo.Revision) || errors.Is(err, fs.ErrNotExist) { - // Need to pull the latest version. - needsBuild = true - if err := git.Checkout(ctx, m.gitDir, repo.Root, repo.Revision, filepath.Join(m.credHelperDirs.RepoDir, repo.Revision)); err != nil { - return err - } - // Update the revision file to the new revision. - if err := os.WriteFile(m.credHelperDirs.RevisionFile, []byte(repo.Revision), 0644); err != nil { - return err - } - } else if err != nil { - return err + if strings.TrimSpace(systemDir) != "" { + systemDirs = regexp.MustCompile("[;:,]").Split(strings.TrimSpace(systemDir), -1) } - if !needsBuild { - // Check for the existence of the gptscript-credential-osxkeychain binary. - // If it's there, we have no need to build it and can just return. - if _, err := os.Stat(filepath.Join(m.credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix)); err == nil { - return nil - } - } - - // Find the Go runtime and use it to build the credential helper. - for _, runtime := range m.runtimes { - if strings.HasPrefix(runtime.ID(), "go") { - goRuntime := runtime.(*golang.Runtime) - return goRuntime.BuildCredentialHelper(ctx, helperName, m.credHelperDirs, m.runtimeDir, repo.Revision, env) - } + return &Manager{ + cacheDir: cacheDir, + storageDir: root, + gitDir: filepath.Join(root, "git"), + runtimeDir: filepath.Join(root, "runtimes"), + systemDirs: systemDirs, + runtimes: runtimes, } - - return fmt.Errorf("no Go runtime found to build the credential helper") } func (m *Manager) setup(ctx context.Context, runtime Runtime, tool types.Tool, env []string) (string, []string, error) { locker.Lock(tool.ID) defer locker.Unlock(tool.ID) + runtimeHash, err := runtime.GetHash(tool) + if err != nil { + return "", nil, err + } + target := filepath.Join(m.storageDir, tool.Source.Repo.Revision, tool.Source.Repo.Path, tool.Source.Repo.Name, runtime.ID()) - targetFinal := filepath.Join(target, tool.Source.Repo.Path) + targetFinal := filepath.Join(target, tool.Source.Repo.Path+runtimeHash) doneFile := targetFinal + ".done" envData, err := os.ReadFile(doneFile) if err == nil { @@ -201,13 +103,28 @@ func (m *Manager) setup(ctx context.Context, runtime Runtime, tool types.Tool, e _ = os.RemoveAll(doneFile) _ = os.RemoveAll(target) - if err := git.Checkout(ctx, m.gitDir, tool.Source.Repo.Root, tool.Source.Repo.Revision, target); err != nil { - return "", nil, err - } + var ( + newEnv []string + isBinary bool + ) - newEnv, err := runtime.Setup(ctx, m.runtimeDir, targetFinal, env) - if err != nil { + if isBinary, newEnv, err = runtime.Binary(ctx, tool, m.runtimeDir, targetFinal, env); err != nil { return "", nil, err + } else if !isBinary { + if tool.Source.Repo.VCS == "git" { + if err := git.Checkout(ctx, m.gitDir, tool.Source.Repo.Root, tool.Source.Repo.Revision, target); err != nil { + return "", nil, err + } + } else { + if err := os.MkdirAll(target, 0755); err != nil { + return "", nil, err + } + } + + newEnv, err = runtime.Setup(ctx, tool, m.runtimeDir, targetFinal, env) + if err != nil { + return "", nil, err + } } out, err := os.Create(doneFile + ".tmp") @@ -228,20 +145,40 @@ func (m *Manager) setup(ctx context.Context, runtime Runtime, tool types.Tool, e } func (m *Manager) GetContext(ctx context.Context, tool types.Tool, cmd, env []string) (string, []string, error) { - if tool.Source.Repo == nil { - return tool.WorkingDir, env, nil + for _, systemDir := range m.systemDirs { + if strings.HasPrefix(tool.WorkingDir, systemDir) { + return tool.WorkingDir, env, nil + } } - if tool.Source.Repo.VCS != "git" { - return "", nil, fmt.Errorf("only git is supported, found VCS %s for %s", tool.Source.Repo.VCS, tool.ID) + var isLocal bool + if tool.Source.Repo == nil { + isLocal = true + d, _ := json.Marshal(tool) + id := hash.Digest(d)[:12] + tool.Source.Repo = &types.Repo{ + VCS: "", + Root: id, + Path: "/", + Name: id, + Revision: id, + } } for _, runtime := range m.runtimes { - if runtime.Supports(cmd) { + if runtime.Supports(tool, cmd) { log.Debugf("Runtime %s supports %v", runtime.ID(), cmd) - return m.setup(ctx, runtime, tool, env) + wd, env, err := m.setup(ctx, runtime, tool, env) + if isLocal { + wd = tool.WorkingDir + } + return wd, env, err } } + if isLocal { + return tool.WorkingDir, env, nil + } + return m.setup(ctx, &noopRuntime{}, tool, env) } diff --git a/pkg/repos/get_test.go b/pkg/repos/get_test.go index d59e5513..3a656dc0 100644 --- a/pkg/repos/get_test.go +++ b/pkg/repos/get_test.go @@ -19,7 +19,7 @@ var ( ) func TestManager_GetContext(t *testing.T) { - m := New(testCacheHome, &python.Runtime{ + m := New(testCacheHome, "", &python.Runtime{ Version: "3.11", }) cwd, env, err := m.GetContext(context.Background(), types.Tool{ diff --git a/pkg/repos/git/cmd.go b/pkg/repos/git/cmd.go index ad6d7350..3cdcff09 100644 --- a/pkg/repos/git/cmd.go +++ b/pkg/repos/git/cmd.go @@ -17,6 +17,10 @@ func newGitCommand(ctx context.Context, args ...string) *debugcmd.WrappedCmd { } func LsRemote(ctx context.Context, repo, ref string) (string, error) { + if usePureGo() { + return lsRemotePureGo(ctx, repo, ref) + } + cmd := newGitCommand(ctx, "ls-remote", repo, ref) if err := cmd.Run(); err != nil { return "", err diff --git a/pkg/repos/git/git.go b/pkg/repos/git/git.go index 978f3a6d..0c9c22be 100644 --- a/pkg/repos/git/git.go +++ b/pkg/repos/git/git.go @@ -29,7 +29,11 @@ func Checkout(ctx context.Context, base, repo, commit, toDir string) error { return err } - if err := Fetch(ctx, base, repo, commit); err != nil { + if usePureGo() { + return checkoutPureGo(ctx, base, repo, commit, toDir) + } + + if err := fetch(ctx, base, repo, commit); err != nil { return err } @@ -41,7 +45,7 @@ func gitDir(base, repo string) string { return filepath.Join(base, "repos", hash.Digest(repo)) } -func Fetch(ctx context.Context, base, repo, commit string) error { +func fetch(ctx context.Context, base, repo, commit string) error { gitDir := gitDir(base, repo) if found, err := exists(gitDir); err != nil { return err diff --git a/pkg/repos/git/git_go.go b/pkg/repos/git/git_go.go new file mode 100644 index 00000000..8f6517a2 --- /dev/null +++ b/pkg/repos/git/git_go.go @@ -0,0 +1,97 @@ +package git + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "runtime" + "sync" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/storage/memory" +) + +var ( + gitCheck sync.Once + externalGit bool +) + +func usePureGo() bool { + if os.Getenv("GPTSCRIPT_PURE_GO_GIT") == "true" { + return true + } + gitCheck.Do(func() { + if runtime.GOOS == "darwin" { + if exec.Command("xcode-select", "-p").Run() == nil { + externalGit = true + } + } else if _, err := exec.LookPath("git"); err == nil { + externalGit = true + } + }) + return !externalGit +} + +func lsRemotePureGo(_ context.Context, repo, ref string) (string, error) { + // Clone the repository in memory + r := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{ + Name: "origin", + URLs: []string{repo}, + }) + + refs, err := r.List(&git.ListOptions{ + PeelingOption: git.AppendPeeled, + }) + if err != nil { + return "", fmt.Errorf("failed to list remote refs: %w", err) + } + + for _, checkRef := range refs { + if checkRef.Name().Short() == ref { + return checkRef.Hash().String(), nil + } + } + + return "", fmt.Errorf("failed to find remote ref %q", ref) +} + +func checkoutPureGo(ctx context.Context, _, repo, commit, toDir string) error { + log.InfofCtx(ctx, "Checking out %s to %s", commit, toDir) + // Clone the repository + r, err := git.PlainCloneContext(ctx, toDir, false, &git.CloneOptions{ + URL: repo, + NoCheckout: true, + }) + if err != nil { + return fmt.Errorf("failed to clone the repo: %w", err) + } + + // Fetch the specific commit + err = r.Fetch(&git.FetchOptions{ + RefSpecs: []config.RefSpec{ + config.RefSpec(fmt.Sprintf("+%s:%s", commit, commit)), + }, + }) + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { + return fmt.Errorf("failed to fetch the commit: %w", err) + } + + // Checkout the specific commit + w, err := r.Worktree() + if err != nil { + return fmt.Errorf("failed to get worktree: %w", err) + } + + err = w.Checkout(&git.CheckoutOptions{ + Hash: plumbing.NewHash(commit), + }) + if err != nil { + return fmt.Errorf("failed to checkout the commit: %w", err) + } + + return nil +} diff --git a/pkg/repos/git/git_test.go b/pkg/repos/git/git_test.go index 5b66d49f..573bf0bb 100644 --- a/pkg/repos/git/git_test.go +++ b/pkg/repos/git/git_test.go @@ -17,7 +17,7 @@ var ( ) func TestFetch(t *testing.T) { - err := Fetch(context.Background(), testCacheHome, + err := fetch(context.Background(), testCacheHome, "https://github.com/gptscript-ai/dalle-image-generation.git", testCommit) require.NoError(t, err) diff --git a/pkg/repos/runtimes/busybox/SHASUMS256.txt b/pkg/repos/runtimes/busybox/SHASUMS256.txt new file mode 100644 index 00000000..7da1aff6 --- /dev/null +++ b/pkg/repos/runtimes/busybox/SHASUMS256.txt @@ -0,0 +1 @@ +6d2dfd1c1412c3550a89071a1b36a6f6073844320e687343d1dfc72719ecb8d9 FRP-5301-gda71f7c57/busybox-w64-FRP-5301-gda71f7c57.exe \ No newline at end of file diff --git a/pkg/repos/runtimes/busybox/busybox.go b/pkg/repos/runtimes/busybox/busybox.go new file mode 100644 index 00000000..5c77ee2b --- /dev/null +++ b/pkg/repos/runtimes/busybox/busybox.go @@ -0,0 +1,121 @@ +package busybox + +import ( + "bufio" + "bytes" + "context" + _ "embed" + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" + "sync" + + runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env" + "github.com/gptscript-ai/gptscript/pkg/hash" + "github.com/gptscript-ai/gptscript/pkg/repos/download" + "github.com/gptscript-ai/gptscript/pkg/types" +) + +//go:embed SHASUMS256.txt +var releasesData []byte + +const downloadURL = "https://github.com/gptscript-ai/busybox-w32/releases/download/%s" + +type Runtime struct { + runtimeSetupLock sync.Mutex +} + +func (r *Runtime) ID() string { + return "busybox" +} + +func (r *Runtime) GetHash(_ types.Tool) (string, error) { + return "", nil +} + +func (r *Runtime) Supports(_ types.Tool, cmd []string) bool { + if runtime.GOOS != "windows" { + return false + } + for _, bin := range []string{"bash", "sh", "/bin/sh", "/bin/bash"} { + if runtimeEnv.Matches(cmd, bin) { + return true + } + } + return false +} + +func (r *Runtime) Binary(_ context.Context, _ types.Tool, _, _ string, _ []string) (bool, []string, error) { + return false, nil, nil +} + +func (r *Runtime) Setup(ctx context.Context, _ types.Tool, dataRoot, _ string, env []string) ([]string, error) { + binPath, err := r.getRuntime(ctx, dataRoot) + if err != nil { + return nil, err + } + + newEnv := runtimeEnv.AppendPath(env, binPath) + return newEnv, nil +} + +func (r *Runtime) getReleaseAndDigest() (string, string, error) { + scanner := bufio.NewScanner(bytes.NewReader(releasesData)) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + return fmt.Sprintf(downloadURL, fields[1]), fields[0], nil + } + + return "", "", fmt.Errorf("failed to find %s release", r.ID()) +} + +func (r *Runtime) getRuntime(ctx context.Context, cwd string) (string, error) { + r.runtimeSetupLock.Lock() + defer r.runtimeSetupLock.Unlock() + + url, sha, err := r.getReleaseAndDigest() + if err != nil { + return "", err + } + + target := filepath.Join(cwd, "busybox", hash.ID(url, sha)) + if _, err := os.Stat(target); err == nil { + return target, nil + } else if !errors.Is(err, fs.ErrNotExist) { + return "", err + } + + log.Infof("Downloading Busybox") + tmp := target + ".download" + defer os.RemoveAll(tmp) + + if err := os.MkdirAll(tmp, 0755); err != nil { + return "", err + } + + if err := download.Extract(ctx, url, sha, tmp); err != nil { + return "", err + } + + bbExe := filepath.Join(tmp, path.Base(url)) + + cmd := exec.Command(bbExe, "--install", ".") + cmd.Dir = filepath.Dir(bbExe) + + if err := cmd.Run(); err != nil { + return "", err + } + + if err := os.Rename(tmp, target); err != nil { + return "", err + } + + return target, nil +} diff --git a/pkg/repos/runtimes/busybox/busybox_test.go b/pkg/repos/runtimes/busybox/busybox_test.go new file mode 100644 index 00000000..77bfae59 --- /dev/null +++ b/pkg/repos/runtimes/busybox/busybox_test.go @@ -0,0 +1,42 @@ +package busybox + +import ( + "context" + "errors" + "io/fs" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/adrg/xdg" + "github.com/gptscript-ai/gptscript/pkg/types" + "github.com/samber/lo" + "github.com/stretchr/testify/require" +) + +var ( + testCacheHome = lo.Must(xdg.CacheFile("gptscript-test-cache/runtime")) +) + +func firstPath(s []string) string { + _, p, _ := strings.Cut(s[0], "=") + return strings.Split(p, string(os.PathListSeparator))[0] +} + +func TestRuntime(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip() + } + + r := Runtime{} + + s, err := r.Setup(context.Background(), types.Tool{}, testCacheHome, "testdata", os.Environ()) + require.NoError(t, err) + _, err = os.Stat(filepath.Join(firstPath(s), "busybox.exe")) + if errors.Is(err, fs.ErrNotExist) { + _, err = os.Stat(filepath.Join(firstPath(s), "busybox")) + } + require.NoError(t, err) +} diff --git a/pkg/repos/runtimes/busybox/log.go b/pkg/repos/runtimes/busybox/log.go new file mode 100644 index 00000000..b7e486f1 --- /dev/null +++ b/pkg/repos/runtimes/busybox/log.go @@ -0,0 +1,5 @@ +package busybox + +import "github.com/gptscript-ai/gptscript/pkg/mvl" + +var log = mvl.Package() diff --git a/pkg/repos/runtimes/default.go b/pkg/repos/runtimes/default.go index d37cca8f..ea237cc4 100644 --- a/pkg/repos/runtimes/default.go +++ b/pkg/repos/runtimes/default.go @@ -3,12 +3,14 @@ package runtimes import ( "github.com/gptscript-ai/gptscript/pkg/engine" "github.com/gptscript-ai/gptscript/pkg/repos" + "github.com/gptscript-ai/gptscript/pkg/repos/runtimes/busybox" "github.com/gptscript-ai/gptscript/pkg/repos/runtimes/golang" "github.com/gptscript-ai/gptscript/pkg/repos/runtimes/node" "github.com/gptscript-ai/gptscript/pkg/repos/runtimes/python" ) var Runtimes = []repos.Runtime{ + &busybox.Runtime{}, &python.Runtime{ Version: "3.12", Default: true, @@ -20,14 +22,14 @@ var Runtimes = []repos.Runtime{ Version: "3.10", }, &node.Runtime{ - Version: "21", + Version: "20", Default: true, }, &golang.Runtime{ - Version: "1.22.1", + Version: "1.23.0", }, } -func Default(cacheDir string) engine.RuntimeManager { - return repos.New(cacheDir, Runtimes...) +func Default(cacheDir, systemDir string) engine.RuntimeManager { + return repos.New(cacheDir, systemDir, Runtimes...) } diff --git a/pkg/repos/runtimes/golang/digests.txt b/pkg/repos/runtimes/golang/digests.txt index 8a1b82c6..df86facf 100644 --- a/pkg/repos/runtimes/golang/digests.txt +++ b/pkg/repos/runtimes/golang/digests.txt @@ -1,10 +1,10 @@ -3bc971772f4712fec0364f4bc3de06af22a00a12daab10b6f717fdcd13156cc0 go1.22.1.darwin-amd64.tar.gz -943e4f9f038239f9911c44366f52ab9202f6ee13610322a668fe42406fb3deef go1.22.1.darwin-amd64.pkg -f6a9cec6b8a002fcc9c0ee24ec04d67f430a52abc3cfd613836986bcc00d8383 go1.22.1.darwin-arm64.tar.gz -5f10b95e2678618f85ba9d87fbed506b3b87efc9d5a8cafda939055cb97949ba go1.22.1.darwin-arm64.pkg -8484df36d3d40139eaf0fe5e647b006435d826cc12f9ae72973bf7ec265e0ae4 go1.22.1.linux-386.tar.gz -aab8e15785c997ae20f9c88422ee35d962c4562212bb0f879d052a35c8307c7f go1.22.1.linux-amd64.tar.gz -e56685a245b6a0c592fc4a55f0b7803af5b3f827aaa29feab1f40e491acf35b8 go1.22.1.linux-arm64.tar.gz -8cb7a90e48c20daed39a6ac8b8a40760030ba5e93c12274c42191d868687c281 go1.22.1.linux-armv6l.tar.gz -0c5ebb7eb39b7884ec99f92b425d4c03a96a72443562aafbf6e7d15c42a3108a go1.22.1.windows-386.zip -cf9c66a208a106402a527f5b956269ca506cfe535fc388e828d249ea88ed28ba go1.22.1.windows-amd64.zip +ffd070acf59f054e8691b838f274d540572db0bd09654af851e4e76ab88403dc go1.23.0.darwin-amd64.tar.gz +bc91d2573939a01731413fac0884c329606c1c168883692131ce772669caf27b go1.23.0.darwin-amd64.pkg +b770812aef17d7b2ea406588e2b97689e9557aac7e646fe76218b216e2c51406 go1.23.0.darwin-arm64.tar.gz +d73ae741ed449ea842238f76f4b02935277eb867689f84ace0640965b2caf700 go1.23.0.darwin-arm64.pkg +0e8a7340c2632e6fb5088d60f95b52be1f8303143e04cd34e9b2314fafc24edd go1.23.0.linux-386.tar.gz +905a297f19ead44780548933e0ff1a1b86e8327bb459e92f9c0012569f76f5e3 go1.23.0.linux-amd64.tar.gz +62788056693009bcf7020eedc778cdd1781941c6145eab7688bd087bce0f8659 go1.23.0.linux-arm64.tar.gz +0efa1338e644d7f74064fa7f1016b5da7872b2df0070ea3b56e4fef63192e35b go1.23.0.linux-armv6l.tar.gz +09448fedec0cdf98ad12397222e0c8bfc835b1d0894c0015ced653534b8d7427 go1.23.0.windows-386.zip +d4be481ef73079ee0ad46081d278923aa3fd78db1b3cf147172592f73e14c1ac go1.23.0.windows-amd64.zip diff --git a/pkg/repos/runtimes/golang/golang.go b/pkg/repos/runtimes/golang/golang.go index 28300439..5cba4779 100644 --- a/pkg/repos/runtimes/golang/golang.go +++ b/pkg/repos/runtimes/golang/golang.go @@ -4,20 +4,27 @@ import ( "bufio" "bytes" "context" + "crypto/sha256" _ "embed" + "encoding/hex" + "encoding/json" "errors" "fmt" + "io" "io/fs" + "net/http" "os" "path/filepath" "runtime" "strings" + "sync" - "github.com/gptscript-ai/gptscript/pkg/credentials" + "github.com/gptscript-ai/gptscript/pkg/config" "github.com/gptscript-ai/gptscript/pkg/debugcmd" runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env" "github.com/gptscript-ai/gptscript/pkg/hash" "github.com/gptscript-ai/gptscript/pkg/repos/download" + "github.com/gptscript-ai/gptscript/pkg/types" ) //go:embed digests.txt @@ -28,17 +35,234 @@ const downloadURL = "https://go.dev/dl/" type Runtime struct { // version something like "1.22.1" Version string + + runtimeSetupLock sync.Mutex } func (r *Runtime) ID() string { return "go" + r.Version } -func (r *Runtime) Supports(cmd []string) bool { - return len(cmd) > 0 && cmd[0] == "${GPTSCRIPT_TOOL_DIR}/bin/gptscript-go-tool" +func (r *Runtime) GetHash(_ types.Tool) (string, error) { + return "", nil +} + +func (r *Runtime) Supports(tool types.Tool, cmd []string) bool { + return tool.Source.IsGit() && + len(cmd) > 0 && cmd[0] == "${GPTSCRIPT_TOOL_DIR}/bin/gptscript-go-tool" +} + +type release struct { + account, repo, label string +} + +func (r release) checksumTxt() string { + return fmt.Sprintf( + "https://github.com/%s/%s/releases/download/%s/checksums.txt", + r.account, + r.repo, + r.label) +} + +func (r release) binURL() string { + return fmt.Sprintf( + "https://github.com/%s/%s/releases/download/%s/%s", + r.account, + r.repo, + r.label, + r.srcBinName()) +} + +func (r release) targetBinName() string { + suffix := "" + if runtime.GOOS == "windows" { + suffix = ".exe" + } + + return "gptscript-go-tool" + suffix +} + +func (r release) srcBinName() string { + suffix := "" + if runtime.GOOS == "windows" { + suffix = ".exe" + } + + return r.repo + "-" + + runtime.GOOS + "-" + + runtime.GOARCH + suffix +} + +type tag struct { + Name string `json:"name,omitempty"` + Commit struct { + Sha string `json:"sha,omitempty"` + } `json:"commit"` +} + +func getLatestRelease(tool types.Tool) (*release, bool, error) { + if tool.Source.Repo == nil || !strings.HasPrefix(tool.Source.Repo.Root, "https://github.com/") { + return nil, false, nil + } + + parts := strings.Split(strings.TrimPrefix(strings.TrimSuffix(tool.Source.Repo.Root, ".git"), "https://"), "/") + if len(parts) != 3 { + return nil, false, fmt.Errorf("invalid GitHub URL: %s", tool.Source.Repo.Root) + } + + client := http.Client{ + CheckRedirect: func(_ *http.Request, _ []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + account, repo := parts[1], parts[2] + + resp, err := client.Get(fmt.Sprintf("https://api.github.com/repos/%s/%s/tags", account, repo)) + if err != nil { + return nil, false, fmt.Errorf("failed to get tags: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, false, fmt.Errorf("unexpected status when getting tags: %s", resp.Status) + } + + var tags []tag + if err := json.NewDecoder(resp.Body).Decode(&tags); err != nil { + return nil, false, fmt.Errorf("failed to decode GitHub tags: %w", err) + } + for _, tag := range tags { + if tag.Commit.Sha == tool.Source.Repo.Revision { + return &release{ + account: account, + repo: repo, + label: tag.Name, + }, true, nil + } + } + + resp, err = client.Get(fmt.Sprintf("https://github.com/%s/%s/releases/latest", account, repo)) + if err != nil { + return nil, false, fmt.Errorf("failed to get latest release: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusFound { + return nil, false, fmt.Errorf("unexpected status when getting latest release: %s", resp.Status) + } + + target := resp.Header.Get("Location") + if target == "" { + return nil, false, nil + } + + parts = strings.Split(target, "/") + label := parts[len(parts)-1] + + return &release{ + account: account, + repo: repo, + label: label, + }, true, nil +} + +func get(ctx context.Context, url string) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } else if resp.StatusCode != http.StatusOK { + _ = resp.Body.Close() + return nil, fmt.Errorf("bad HTTP status code: %d", resp.StatusCode) + } + + return resp, nil +} + +func downloadBin(ctx context.Context, checksum, src, url, bin string) error { + resp, err := get(ctx, url) + if err != nil { + return err + } + defer resp.Body.Close() + + if err := os.MkdirAll(filepath.Join(src, "bin"), 0755); err != nil { + return err + } + + targetFile, err := os.Create(filepath.Join(src, "bin", bin)) + if err != nil { + return err + } + + digest := sha256.New() + + if _, err := io.Copy(io.MultiWriter(targetFile, digest), resp.Body); err != nil { + return err + } + + if err := targetFile.Close(); err != nil { + return nil + } + + if got := hex.EncodeToString(digest.Sum(nil)); got != checksum { + return fmt.Errorf("checksum mismatch %s != %s", got, checksum) + } + + if err := os.Chmod(targetFile.Name(), 0755); err != nil { + return err + } + + return nil +} + +func getChecksum(ctx context.Context, rel *release, artifactName string) string { + resp, err := get(ctx, rel.checksumTxt()) + if err != nil { + // ignore error + return "" + } + defer resp.Body.Close() + + scan := bufio.NewScanner(resp.Body) + for scan.Scan() { + fields := strings.Fields(scan.Text()) + if len(fields) == 2 && (fields[1] == artifactName || fields[1] == "*"+artifactName) { + return fields[0] + } + } + + return "" } -func (r *Runtime) Setup(ctx context.Context, dataRoot, toolSource string, env []string) ([]string, error) { +func (r *Runtime) Binary(ctx context.Context, tool types.Tool, _, toolSource string, _ []string) (bool, []string, error) { + if !tool.Source.IsGit() { + return false, nil, nil + } + + // ignore the error + rel, ok, _ := getLatestRelease(tool) + if !ok { + return false, nil, nil + } + + checksum := getChecksum(ctx, rel, rel.srcBinName()) + if checksum == "" { + return false, nil, nil + } + + if err := downloadBin(ctx, checksum, toolSource, rel.binURL(), rel.targetBinName()); err != nil { + // ignore error + return false, nil, nil + } + + return true, nil, nil +} + +func (r *Runtime) Setup(ctx context.Context, _ types.Tool, dataRoot, toolSource string, env []string) ([]string, error) { binPath, err := r.getRuntime(ctx, dataRoot) if err != nil { return nil, err @@ -52,30 +276,29 @@ func (r *Runtime) Setup(ctx context.Context, dataRoot, toolSource string, env [] return newEnv, nil } -func (r *Runtime) BuildCredentialHelper(ctx context.Context, helperName string, credHelperDirs credentials.CredentialHelperDirs, dataRoot, revision string, env []string) error { - if helperName == "file" { +func (r *Runtime) DownloadCredentialHelper(ctx context.Context, tool types.Tool, helperName, distInfo, suffix string, binDir string) error { + if helperName == config.FileCredHelper { return nil } - var suffix string - if helperName == "wincred" { - suffix = ".exe" + // ignore the error + rel, ok, _ := getLatestRelease(tool) + if !ok { + return fmt.Errorf("failed to find %s release", r.ID()) + } + binaryName := "gptscript-credential-" + helperName + checksum := getChecksum(ctx, rel, binaryName+distInfo+suffix) + if checksum == "" { + return fmt.Errorf("failed to find %s release checksum for os=%s arch=%s", r.ID(), runtime.GOOS, runtime.GOARCH) } - binPath, err := r.getRuntime(ctx, dataRoot) - if err != nil { - return err + url, _ := strings.CutSuffix(rel.binURL(), rel.srcBinName()) + url += binaryName + distInfo + suffix + if err := downloadBin(ctx, checksum, strings.TrimSuffix(binDir, "bin"), url, binaryName+suffix); err != nil { + return fmt.Errorf("failed to download %s release for os=%s arch=%s: %w", r.ID(), runtime.GOOS, runtime.GOARCH, err) } - newEnv := runtimeEnv.AppendPath(env, binPath) - log.InfofCtx(ctx, "Building credential helper %s", helperName) - cmd := debugcmd.New(ctx, filepath.Join(binPath, "go"), - "build", "-buildvcs=false", "-o", - filepath.Join(credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix), - fmt.Sprintf("./%s/cmd/", helperName)) - cmd.Env = stripGo(append(env, newEnv...)) - cmd.Dir = filepath.Join(credHelperDirs.RepoDir, revision) - return cmd.Run() + return nil } func (r *Runtime) getReleaseAndDigest() (string, string, error) { @@ -122,6 +345,9 @@ func (r *Runtime) binDir(rel string) string { } func (r *Runtime) getRuntime(ctx context.Context, cwd string) (string, error) { + r.runtimeSetupLock.Lock() + defer r.runtimeSetupLock.Unlock() + url, sha, err := r.getReleaseAndDigest() if err != nil { return "", err diff --git a/pkg/repos/runtimes/golang/golang_test.go b/pkg/repos/runtimes/golang/golang_test.go index 5f71fb50..f3d888fd 100644 --- a/pkg/repos/runtimes/golang/golang_test.go +++ b/pkg/repos/runtimes/golang/golang_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/adrg/xdg" + "github.com/gptscript-ai/gptscript/pkg/types" "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,10 +25,10 @@ func TestRuntime(t *testing.T) { os.RemoveAll("testdata/bin") }) r := Runtime{ - Version: "1.22.1", + Version: "1.23.0", } - s, err := r.Setup(context.Background(), testCacheHome, "testdata", os.Environ()) + s, err := r.Setup(context.Background(), types.Tool{}, testCacheHome, "testdata", os.Environ()) require.NoError(t, err) p, v, _ := strings.Cut(s[0], "=") v, _, _ = strings.Cut(v, string(filepath.ListSeparator)) diff --git a/pkg/repos/runtimes/golang/testdata/go.mod b/pkg/repos/runtimes/golang/testdata/go.mod index 93eac7fe..6725cbfd 100644 --- a/pkg/repos/runtimes/golang/testdata/go.mod +++ b/pkg/repos/runtimes/golang/testdata/go.mod @@ -1,3 +1,3 @@ module example.com -go 1.22.1 +go 1.23.0 diff --git a/pkg/repos/runtimes/node/SHASUMS256.txt.asc b/pkg/repos/runtimes/node/SHASUMS256.txt.asc index 093da96b..faaeab97 100644 --- a/pkg/repos/runtimes/node/SHASUMS256.txt.asc +++ b/pkg/repos/runtimes/node/SHASUMS256.txt.asc @@ -1,117 +1,60 @@ -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 -43a881788549e1b3425eb5f2b92608f438f146e08213de09c5bd5ff841cae7ae node-v20.11.1-aix-ppc64.tar.gz -3f8e77b775372c0b27d2b85ce899d80339691f480e64dde43d4eb01504a58679 node-v20.11.1-arm64.msi -e0065c61f340e85106a99c4b54746c5cee09d59b08c5712f67f99e92aa44995d node-v20.11.1-darwin-arm64.tar.gz -fd771bf3881733bfc0622128918ae6baf2ed1178146538a53c30ac2f7006af5b node-v20.11.1-darwin-arm64.tar.xz -c52e7fb0709dbe63a4cbe08ac8af3479188692937a7bd8e776e0eedfa33bb848 node-v20.11.1-darwin-x64.tar.gz -ed69f1f300beb75fb4cad45d96aacd141c3ddca03b6d77c76b42cb258202363d node-v20.11.1-darwin-x64.tar.xz -0aa42c91b441e945ff43bd3a837759c58b436de57dcd033d02e5cbcd2fba1f87 node-v20.11.1-headers.tar.gz -edce238817acf5adce3123366b55304aff2a1f0849231d1b49f42370e454b6f8 node-v20.11.1-headers.tar.xz -e34ab2fc2726b4abd896bcbff0250e9b2da737cbd9d24267518a802ed0606f3b node-v20.11.1-linux-arm64.tar.gz -c957f29eb4e341903520caf362534f0acd1db7be79c502ae8e283994eed07fe1 node-v20.11.1-linux-arm64.tar.xz -e42791f76ece283c7a4b97fbf716da72c5128c54a9779f10f03ae74a4bcfb8f6 node-v20.11.1-linux-armv7l.tar.gz -28e0120d2d150a8f41717899d33167b8b32053778665583d49ff971bfd188d1b node-v20.11.1-linux-armv7l.tar.xz -9823305ac3a66925a9b61d8032f6bbb4c3e33c28e7f957ebb27e49732feffb23 node-v20.11.1-linux-ppc64le.tar.gz -51343cacf5cdf5c4b5e93e919d19dd373d6ef43d5f2c666eae299f26e31d08b5 node-v20.11.1-linux-ppc64le.tar.xz -4c66b2f247fdd8720853321526d7cda483018fcb32014b75c30f3a54ecacaea7 node-v20.11.1-linux-s390x.tar.gz -b32616b705cd0ddbb230b95c693e3d7a37becc2ced9bcadea8dc824cceed6be0 node-v20.11.1-linux-s390x.tar.xz -bf3a779bef19452da90fb88358ec2c57e0d2f882839b20dc6afc297b6aafc0d7 node-v20.11.1-linux-x64.tar.gz -d8dab549b09672b03356aa2257699f3de3b58c96e74eb26a8b495fbdc9cf6fbe node-v20.11.1-linux-x64.tar.xz -f1cd449fcbeb1b948e8498cb8edd9655fa319d109a7f4c5bd96a9b122b91538a node-v20.11.1-win-arm64.7z -e85461ec124956a2853c4ee6e13c4f4889d63c88beb3d530c1ee0c4b51dc10e7 node-v20.11.1-win-arm64.zip -fb9b5348259988a562a48eed7349e7e716c0bec78d98ad0a336b2993a8b3bf34 node-v20.11.1-win-x64.7z -bc032628d77d206ffa7f133518a6225a9c5d6d9210ead30d67e294ff37044bda node-v20.11.1-win-x64.zip -c2b1863d8979546804a39fc63d0a9bc9c6e49cb2f6c9d1e52844a24629b24765 node-v20.11.1-win-x86.7z -b98e95f78416d1359b647cfa09ba2a48b76d41b56a776df822bf36ffe8e76a2d node-v20.11.1-win-x86.zip -c54f5f7e2416e826fd84e878f28e3b53363ae9c3f60a140af4434b2453b5ae89 node-v20.11.1-x64.msi -63e2aed4dabb96eed6903a3974e006d3c29c218472aac60ae3c3c7de00df13b1 node-v20.11.1-x86.msi -c46019a095a1549d000e85da13f17972a448e0be5854a51786ecccde7278a012 node-v20.11.1.pkg -4af1ba6ea848cc05908b8a62b02fb27684dd52b2a7988ee82b0cfa72deb90b94 node-v20.11.1.tar.gz -77813edbf3f7f16d2d35d3353443dee4e61d5ee84d9e3138c7538a3c0ca5209e node-v20.11.1.tar.xz -a5a9d30a8f7d56e00ccb27c1a7d24c8d0bc96a2689ebba8eb7527698793496f1 win-arm64/node.exe -93529170cebe57c0f4830a4cc6a261b6cc9bcf0cd8b3e88ac4995a5015031d79 win-arm64/node.lib -c14c6e927406b8683cbfb8a67ca4c8fd5093ca7812b5b1627e3d6a53d3674565 win-arm64/node_pdb.7z -68034cd09d8dfaa755d1b280da13e20388cc486ac57b037b3e11dfe2d6b74284 win-arm64/node_pdb.zip -bc585910690318aaebe3c57669cb83ca9d1e5791efd63195e238f54686e6c2ec win-x64/node.exe -53a982d490cb9fcc4b231a8b95147de423b36186bc6f4ba5697b20117fdcbd5d win-x64/node.lib -ccac9f2f5219ed858aeddb306d6493478ba9675c7cbf009e83742437d6752c4f win-x64/node_pdb.7z -bec5da4035c84580843978a59ef9bcc1c0eaca881cf9e1c94e63a1862cf14421 win-x64/node_pdb.zip -3829137e062b1e2eb9947ef05e4b717ae578a8fce1c5c60fe4f6ae7ef2ec0240 win-x86/node.exe -c5321bb65dcecb3989f9b8f6ec56369c16627ca4bade0c78afb6b88f7dde50e4 win-x86/node.lib -20ca60ced1fc21f15ea952b4406aec6bde39d20eab11cf042040628841b2249e win-x86/node_pdb.7z -bef05cebedce5949ae35e87e7d4789c16fa73caf478483fcf92e5dbb9ba5d774 win-x86/node_pdb.zip +5eb1b7ea405c86be0a21ec3850997c89df238d6e4659a0b990aa793a8cbfd9cf node-v20.16.0-aix-ppc64.tar.gz +f366fe5903dcb3b6cd495c8add77c87a32772085718a672d52ad17d9d91d2018 node-v20.16.0-arm64.msi +fc7355e778b181575153b7dea4879e8021776eeb376c43c50f65893d2ea70aa3 node-v20.16.0-darwin-arm64.tar.gz +5043e98cdf859963b1a0aff54c1f1813a2a8059e4179403171860d664ca090f2 node-v20.16.0-darwin-arm64.tar.xz +e18942cd706e4d69a4845ddacee2f1c17a72e853a229e3d2623d2edeb7efde72 node-v20.16.0-darwin-x64.tar.gz +9df751ac5edbb2181335200060dff14de25f828eaed70d8b48459d2c203aeedc node-v20.16.0-darwin-x64.tar.xz +6cc5690a67b9b1e1fa8cedaeca41f1bdb5e1af1f7948761c798d33d99f789a5c node-v20.16.0-headers.tar.gz +a1464c304980d3ab41922cda7025ebc2ec0dc2a0b89d9b9183c589560810feaa node-v20.16.0-headers.tar.xz +551588f8f5ca05c04efb53f1b2bb7d9834603327bdc82d60a944d385569866e1 node-v20.16.0-linux-arm64.tar.gz +1d9929e72f692179f884cd676b2dfabd879cb77defa7869dc8cfc802619277fb node-v20.16.0-linux-arm64.tar.xz +1c77c52ab507ddee479012f0b4bf523dd8400df4504447d623632353076e2e27 node-v20.16.0-linux-armv7l.tar.gz +a23a49029e8c7788c701eb3ace553260b7676a5a2ea9965ba92e4817008fbefe node-v20.16.0-linux-armv7l.tar.xz +80b515595e46afb9bae77f61083a4ca7c21bbdb627f69ff53fd5dca3a26773fb node-v20.16.0-linux-ppc64le.tar.gz +86cf6e8c93a9e517bfcfdfb4ad2774105312679ad21e03da75ab516ebc10e2dc node-v20.16.0-linux-ppc64le.tar.xz +ae7a9f6e631a0bede76a501d8b1d806f56b97acfa5a1d6833bab5ce90a404e5e node-v20.16.0-linux-s390x.tar.gz +6c38ac5c516a6a36ee6e0426975e6466795db30b9ced04e59f0f33fe6b3d657e node-v20.16.0-linux-s390x.tar.xz +b3f874ea84e440d69ed02ca92429d0eccd17737fde86db69c1c153d16ec654f2 node-v20.16.0-linux-x64.tar.gz +c30af7dfea46de7d8b9b370fa33b8b15440bc93f0a686af8601bbb48b82f16c0 node-v20.16.0-linux-x64.tar.xz +55852a420ca41db9f128f97e0dd8751199c23d63f5a7978432fd7c9e0c74c323 node-v20.16.0.pkg +8f24bf9abe455a09ab30f9ae8edda1e945ed678a4b1c3b07ee0f901fdc0ff4fd node-v20.16.0.tar.gz +cd6c8fc3ff2606aadbc7155db6f7e77247d2d0065ac18e2f7f049095584b8b46 node-v20.16.0.tar.xz +52e5666a379acd8533d9ccab66c2321a6ffc83766248419bfbd41ba8bc071244 node-v20.16.0-win-arm64.7z +af5a85ea299fcebd34c3c726a47a926e73171f9b657a6eaa796c011597241bf8 node-v20.16.0-win-arm64.zip +1b3961054a484476872715d9ca04bc491d797fde6336db514b6e6fcbb71fae9d node-v20.16.0-win-x64.7z +4e88373ac5ae859ad4d50cc3c5fa86eb3178d089b72e64c4dbe6eeac5d7b5979 node-v20.16.0-win-x64.zip +76f1806fde0b09ed4044f29ea140fb2bea9bce745b9892ec4aeb6537344db6f1 node-v20.16.0-win-x86.7z +1adc1f086595ecbc98da40eccb42fa1691b6c6c0658ff875dda19e4e02b1d5f0 node-v20.16.0-win-x86.zip +813306c94e6f5f061a5789f037d48f57d52240284a679e5ace4a0f73f8f2feeb node-v20.16.0-x64.msi +2bb8c3084384c95c47c4191c38098d5ecf55c0f02c1de5e0968730dec957ea15 node-v20.16.0-x86.msi +7e773fba3a19eac5ccbe85c1f87a05d7b112ecf41440076e6b6de1c7bffa0fdf win-arm64/node.exe +a4f01329c1c211082ac3ed387ff6651530040bbf7250ec419ce8f95b10d7804a win-arm64/node.lib +e1bec70ae9529cc637a21de850c070125f8016070451094d72f96408001200a2 win-arm64/node_pdb.7z +bc5b60eecd3b6c92b35755adef2e4aad01e021a3c434d46c2555a49056c5bcf7 win-arm64/node_pdb.zip +ba221658a3b68bd583e3068903eb675b5206d86a883c084ed95502e8f634b82a win-x64/node.exe +87056190b7cd06f40058f8e059efd328cdcc7600b825afa102c0aa5039865af5 win-x64/node.lib +bf2ad1e1f4e7c3853d5209fe9ef24ad7117edafc71f6401ec0121d8b681b8c3c win-x64/node_pdb.7z +5386f3c3af1af1b325b43b574043c5a7e830b3e9e7df0370ae0797ce4f39b375 win-x64/node_pdb.zip +b7b8d6b5fdd1c073b6f5f6d15bc849f4b5f92c4a66f23e77294f4bdf5f51e9f6 win-x86/node.exe +fa02ae7feca7eb6c4a0f1b929126df400719f5d18a2ec4b7d12c52fbe0b13814 win-x86/node.lib +328b2dcc91255c1c75faa8ce7eb687a8960ae09555d3bca0ae8e0dac4238c873 win-x86/node_pdb.7z +71b1e6b75c61227342ba6f1edb9014445dbee857d6cb14dce3d9b8d94c694d55 win-x86/node_pdb.zip -----BEGIN PGP SIGNATURE----- -iQGzBAEBCAAdFiEEiQwI24V5Fi/uDfnbi+q0389VXvQFAmXM+TcACgkQi+q0389V -XvQl3AwAqqm2uBMDzd+BlR1sG7y/eUtUYPVdwmCh0DeFXPHxuaIbFf0PGMEgcV8u -kn3OBF4pnSCPZNbJYJsLO1S+b/5Vk+Vlkq1WkOxqQHUHmM9GcJUuShadl0YaDNen -WXXMoYKWqMRJ6fQ3tRRh+vbMSXtsLqXT8TMVJq+Qb7a7yj4QRjw/Dd+8uKGGIhBY -U04HWsz33RJLu6AUnhF03eO1N8E1V48JptklDx5ZkY8GYa3F6jQsFld+jhmkZ9tg -4q9NDNijVpj56UsUhLAYD0J9IKS18tvQxNrKmBGUSZjFOByVhbUdLXnSMtW1i1U9 -cYhP6Q5wg/fnjqCfQ90TauoJZOblKIL/PHlf6cQGPrrRa1bz3xGyCAIve5KFhLxf -Vfj1ctk2ktzmuNhjAu5G/1VALQUNpiTm4Yz433JpoMMZ3mTHN+fuALOX4TQbdLRz -HKphTz02436348XC9bNz2cvjm74cy9fqwjQ/y84AmxiTJMFPg0XqICg4tu9rd49d -8FJc4TLZ -=r/CD ------END PGP SIGNATURE----- - ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA256 - -c31d5b60c4a284c6855bd468d4aae4436a16b351362b2971d3c0db2a471d3f24 node-v21.7.0-aix-ppc64.tar.gz -7d7dc37aa30b6dbb52d698d01cfed1d99056c82396eadd41a49fc55a873c423d node-v21.7.0-arm64.msi -f48ad51cf3c2814bbf61c8c26efd810e5e22dcac980786fd7ac5b54365233d2c node-v21.7.0-darwin-arm64.tar.gz -0805239d8a7402dae49e0033b7ad8208fed498dbeee9a3194863e52f6f3c6d7f node-v21.7.0-darwin-arm64.tar.xz -3f81adca80b523b413e26f03f855f4a2ae52d9af20f0cda2e259dd26a0608607 node-v21.7.0-darwin-x64.tar.gz -6a755416292854f2be38e74704ccf09edeba247718e9f047f5e1939b0dba17bd node-v21.7.0-darwin-x64.tar.xz -628d9e4f866e3828b77ce812dc99f33d3e7c673d0c499f13eadff6fa6ccb4383 node-v21.7.0-headers.tar.gz -627d6552d2120660a51f74fff0d40573f4a35d8545462250d30592ce0ba4eec7 node-v21.7.0-headers.tar.xz -520a3e5c83a05a782b1f4959f150c2fdc03e2ea056e855ef6bbb74f6ccf7aa7d node-v21.7.0-linux-arm64.tar.gz -73ce1e4e956532e0916fc7014f5b649573bd2b5870fef5cfc26cc42f58358ae7 node-v21.7.0-linux-arm64.tar.xz -723abb32135ad4baa6e9671447a72f5c9a5bfc681fc540b0e4864e965171b6ed node-v21.7.0-linux-armv7l.tar.gz -8a367a3bf667f0bb3abb9e8121326911d47a31886419ad052d5a52d8c6531d9d node-v21.7.0-linux-armv7l.tar.xz -c2290cb35b11ee2b0f0ae34ad3c8372652688ff2dc3d9a89ada46c2b84ea5dda node-v21.7.0-linux-ppc64le.tar.gz -b85348211a4d195de2f850a17cdec77aedc8fc1c402864b2bc3501608e6c9c47 node-v21.7.0-linux-ppc64le.tar.xz -90b8678ed113950613edeae5eaf298cf795c72005fac6ffd9b7fbb90ddd86738 node-v21.7.0-linux-s390x.tar.gz -99a09f4c790f3210a6d26032bf69713ba199cf2e73af43e04b1b1d9bd1c8db76 node-v21.7.0-linux-s390x.tar.xz -0fce039e2b6af00766492127a49f959ae92ed22fede4c49e9a8c2543aadbd6e2 node-v21.7.0-linux-x64.tar.gz -68510c3851133a21c6a6f9940e58c5bc8fed39f1d91a08e34c5070dd0615fef1 node-v21.7.0-linux-x64.tar.xz -d680d5c3d0b2476a97d11b30cbbdaf1d7f92ffd1cc89e5c640782a6b52480666 node-v21.7.0-win-arm64.7z -11b11b9a3f2db7b5076cf16655e05cd63dc3d8843cd4836ecb12e11315f03441 node-v21.7.0-win-arm64.zip -31c8b4721f37e30ca8e2131a4cb848fc7347f67bf87618e82959b58481f17bc4 node-v21.7.0-win-x64.7z -204de88f4073b08ae3dbe4c412b071eee565fc681e163be205d5cc88065f0322 node-v21.7.0-win-x64.zip -b17ef0c5557e61610774cae5beb0f877699ab419c4672e9c6e3bb3da3d571ed1 node-v21.7.0-win-x86.7z -6aba3fe2258d5c0c40a89e81dfe90113a67489f2a67fd05b7f216b63b4c7bb02 node-v21.7.0-win-x86.zip -512945cf8816e1e906143ea2ee6816f8744a3d114ea38f3540c3ebe685fe3e3a node-v21.7.0-x64.msi -4bedb6069c94a71fd6f0b8fbea280468d5ecdcf209eef6da1a45808e8b15cba6 node-v21.7.0-x86.msi -ccac99782e587c6090b6ad82979210fa0c352322636a6cf290d37eb41152d0b5 node-v21.7.0.pkg -26d6b600e1076f132d4175a90ddc1a709263e75d81967300aa1ffbd86103b991 node-v21.7.0.tar.gz -e41eefe1e59624ee7f312c38f8f7dfc11595641acb2293d21176f03d2763e9d4 node-v21.7.0.tar.xz -25511d1e05d7d0b049945c5ef1cf2a4daa5d6ad16692ccd2c1399142a1c57a65 win-arm64/node.exe -7920932f7be355dbf4568492ab7d104fc059f689ba1a46ac0d6568884c8d201a win-arm64/node.lib -40c423a2b126fc5b6858f8617f0f8537fd8f8d2fa73a5c918607f3ccd386f8c9 win-arm64/node_pdb.7z -dec9eaa91a431ea0f3c243605f0556dbe6459df5c04c10df7935d678a6b3fca4 win-arm64/node_pdb.zip -c486fe72a3663379105538e886ef9d2deacad1deaa64b338e570cb086be592d3 win-x64/node.exe -96d09c2055c2f252122c86b65d2aabd5f90b1a075844f24bf8bcdbab05baf53e win-x64/node.lib -08990dd6bcce80710d59ef76cd74ab98b5bed36b0d2584ca3acbc029f92db4fc win-x64/node_pdb.7z -1a27a25c92f6339b3aa77588063cca537af389aee26bfdf1d0ef505d790e63a3 win-x64/node_pdb.zip -4aaa5b3a95ee4ab932a80b9708c31662a9c4a99d19fea7cb1f7b0ff79d8399ed win-x86/node.exe -6e2502e84c3a0e2da643f6399b59381ade5b525f544a5bcabae923188b8f9998 win-x86/node.lib -d0cd5494364039f558c76d4fc7a1db69739149873e10a5200fb9e2a0ab12fe10 win-x86/node_pdb.7z -354031f3f9576733ebeeccbcafcc691c8326427153a48978ff5cd6f2c8ef5d36 win-x86/node_pdb.zip ------BEGIN PGP SIGNATURE----- - -iQGzBAEBCAAdFiEEiQwI24V5Fi/uDfnbi+q0389VXvQFAmXouAIACgkQi+q0389V -XvRp+wv+IPHjBUmVC6YzAxFhRD4GHVUgjckfSbP2jH/acre1mYgm9LJ//7l2GaJy -oEOO85WaHgaKCHCdv9GBc3dDbbt1n9J2IGmBqcdE8e9cRko5qhBoVUvW7p7Ki7ci -nAq5DS3YkpWAocsY/k+LyR0Ky8mW466ARAucTp9kuZmxB2FW53B0bYK57++1qGuo -tr9kJPoGYQB0cUiTSwTaMbOIdl/4CL+a9J7mIrpaDVW5g3PnNy5y1vgDvtuU7Qcn -uEucciBlOn0Ib4mBnky+NX1ThL9WNwLjaivxdioFgc0E4sMwf0CjF3vMUuEvI8qi -PJ5lYndsHI4fdh1SbcgoFNZzTkMZbTr9xcZIGLzLkMX8r+ztLTiFtiLIUSQq0jgm -fqKQghuDN2SVi7WW4KAa7K1285zmV7L27N9mnNWH4ujTqCW73Wdo2XkG/TwM3yEC -5o+YookAV6RHT1X6RPJ8rQaC0BrBgpm/MQH1kvH4vUyF2HRVZ2ZgEYorvKtOwf9D -f7v3IC9J -=/YNz +iQIzBAEBCAAdFiEEzGj1oxBv9EgyLkjtJ/XjjVsKIV8FAmag7tcACgkQJ/XjjVsK +IV/hlRAAjvdBRTWPfjXzTqxQODXLZps1HREXRZAa8C8bbAoagCJ1jfm6d8yVUegH +Bl5FqDAutGfTlEhXtQqBmnbQPv4Ahj156cVYtp3dFrxPF15bP+o9q+Un5+R1zcfX +kH9W26G7IvfrtFJkDClpBlPKYE5HcDrrJBNfvALf4th17bkVHMpr04oJz5IwGV2M +petLMwqFxcqQ+15tzRW42Z6EhWHvNaMveab6SM4JEqBxvqB8K+m4nsw2ER7ycU5b +Isa0bUsxtRICtSX0yzzdzEYrFXZmb9eXZRVfJ4sBpUhw0xtBmHn3c1MZH0qez+Nm +tbc6pcgGv9cUSXauBeD8rrYMzQHcrhihd51i9a3Cen3RDy/dtuNx9jEXnxfkY+n9 +wkwKb4Lask962L+yTHQCfJ+JQxgouADxqzMxhcup1iiHXCd7pSBSoeAvd5Z1AeGX +qBYrLU9mcyIuLrbtADSfnWmXWs2k1hgnP3UXMBhu/GuobQf9kJ2Gwwx5Gp0aB8z9 +4EA+oUXnkM2kJF0MYVMXL+z8VcQpHgyVPujglhNn/a4WdCVTr1jKptNqqnriH9zl +bHMZuiKbAt8RL9rQ3XuFD1sN9k1z/mj8bCHES2WVta+3kCmY9u+eKXNdXJYt+5Xh +bGxwXP5T+Z8Yzc9FVmgPzZzVddCX74Yug0j8BUyE3vPaDq32H6M= +=cZH5 -----END PGP SIGNATURE----- diff --git a/pkg/repos/runtimes/node/node.go b/pkg/repos/runtimes/node/node.go index 575e3b23..53b77ca2 100644 --- a/pkg/repos/runtimes/node/node.go +++ b/pkg/repos/runtimes/node/node.go @@ -12,30 +12,42 @@ import ( "path/filepath" "runtime" "strings" + "sync" "github.com/gptscript-ai/gptscript/pkg/debugcmd" runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env" "github.com/gptscript-ai/gptscript/pkg/hash" "github.com/gptscript-ai/gptscript/pkg/repos/download" + "github.com/gptscript-ai/gptscript/pkg/types" ) //go:embed SHASUMS256.txt.asc var releasesData []byte -const downloadURL = "https://nodejs.org/dist/%s/" +const ( + downloadURL = "https://nodejs.org/dist/%s/" + packageJSON = "package.json" + nodeModules = "node_modules" +) type Runtime struct { // version something like "3.12" Version string // If true this is the version that will be used for python or python3 Default bool + + runtimeSetupLock sync.Mutex } func (r *Runtime) ID() string { return "node" + r.Version } -func (r *Runtime) Supports(cmd []string) bool { +func (r *Runtime) Binary(_ context.Context, _ types.Tool, _, _ string, _ []string) (bool, []string, error) { + return false, nil, nil +} + +func (r *Runtime) Supports(_ types.Tool, cmd []string) bool { for _, testCmd := range []string{"node", "npx", "npm"} { if r.supports(testCmd, cmd) { return true @@ -54,17 +66,39 @@ func (r *Runtime) supports(testCmd string, cmd []string) bool { return runtimeEnv.Matches(cmd, testCmd) } -func (r *Runtime) Setup(ctx context.Context, dataRoot, toolSource string, env []string) ([]string, error) { +func (r *Runtime) GetHash(tool types.Tool) (string, error) { + if !tool.Source.IsGit() && tool.WorkingDir != "" { + var prefix string + // This hashes if the node_modules directory was deleted + if s, err := os.Stat(filepath.Join(tool.WorkingDir, nodeModules)); err == nil { + prefix = hash.Digest(tool.WorkingDir + s.ModTime().String())[:7] + } else if s, err := os.Stat(tool.WorkingDir); err == nil { + prefix = hash.Digest(tool.WorkingDir + s.ModTime().String())[:7] + } + if s, err := os.Stat(filepath.Join(tool.WorkingDir, packageJSON)); err == nil { + return prefix + hash.Digest(tool.WorkingDir + s.ModTime().String())[:7], nil + } + } + return "", nil +} + +func (r *Runtime) Setup(ctx context.Context, tool types.Tool, dataRoot, toolSource string, env []string) ([]string, error) { binPath, err := r.getRuntime(ctx, dataRoot) if err != nil { return nil, err } newEnv := runtimeEnv.AppendPath(env, binPath) - if err := r.runNPM(ctx, toolSource, binPath, append(env, newEnv...)); err != nil { + if err := r.runNPM(ctx, tool, toolSource, binPath, append(env, newEnv...)); err != nil { return nil, err } + if _, ok := tool.MetaData[packageJSON]; ok { + newEnv = append(newEnv, "GPTSCRIPT_TMPDIR="+toolSource) + } else if !tool.Source.IsGit() && tool.WorkingDir != "" { + newEnv = append(newEnv, "GPTSCRIPT_TMPDIR="+tool.WorkingDir, "GPTSCRIPT_RUNTIME_DEV=true") + } + return newEnv, nil } @@ -100,11 +134,26 @@ func (r *Runtime) getReleaseAndDigest() (string, string, error) { return "", "", fmt.Errorf("failed to find %s release for os=%s arch=%s", r.ID(), osName(), arch()) } -func (r *Runtime) runNPM(ctx context.Context, toolSource, binDir string, env []string) error { +func (r *Runtime) runNPM(ctx context.Context, tool types.Tool, toolSource, binDir string, env []string) error { log.InfofCtx(ctx, "Running npm in %s", toolSource) cmd := debugcmd.New(ctx, filepath.Join(binDir, "npm"), "install") cmd.Env = env cmd.Dir = toolSource + if contents, ok := tool.MetaData[packageJSON]; ok { + if err := os.WriteFile(filepath.Join(toolSource, packageJSON), []byte(contents+"\n"), 0644); err != nil { + return err + } + } else if !tool.Source.IsGit() { + if tool.WorkingDir == "" { + return nil + } + if _, err := os.Stat(filepath.Join(tool.WorkingDir, packageJSON)); errors.Is(err, fs.ErrNotExist) { + return nil + } else if err != nil { + return err + } + cmd.Dir = tool.WorkingDir + } return cmd.Run() } @@ -129,6 +178,9 @@ func (r *Runtime) binDir(rel string) (string, error) { } func (r *Runtime) getRuntime(ctx context.Context, cwd string) (string, error) { + r.runtimeSetupLock.Lock() + defer r.runtimeSetupLock.Unlock() + url, sha, err := r.getReleaseAndDigest() if err != nil { return "", err diff --git a/pkg/repos/runtimes/node/node_test.go b/pkg/repos/runtimes/node/node_test.go index 50ef1e0a..ce2fcde1 100644 --- a/pkg/repos/runtimes/node/node_test.go +++ b/pkg/repos/runtimes/node/node_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/adrg/xdg" + "github.com/gptscript-ai/gptscript/pkg/types" "github.com/samber/lo" "github.com/stretchr/testify/require" ) @@ -28,21 +29,7 @@ func TestRuntime(t *testing.T) { Version: "20", } - s, err := r.Setup(context.Background(), testCacheHome, "testdata", os.Environ()) - require.NoError(t, err) - _, err = os.Stat(filepath.Join(firstPath(s), "node.exe")) - if errors.Is(err, fs.ErrNotExist) { - _, err = os.Stat(filepath.Join(firstPath(s), "node")) - } - require.NoError(t, err) -} - -func TestRuntime21(t *testing.T) { - r := Runtime{ - Version: "21", - } - - s, err := r.Setup(context.Background(), testCacheHome, "testdata", os.Environ()) + s, err := r.Setup(context.Background(), types.Tool{}, testCacheHome, "testdata", os.Environ()) require.NoError(t, err) _, err = os.Stat(filepath.Join(firstPath(s), "node.exe")) if errors.Is(err, fs.ErrNotExist) { diff --git a/pkg/repos/runtimes/python/python.go b/pkg/repos/runtimes/python/python.go index a5268f31..4aebe0cf 100644 --- a/pkg/repos/runtimes/python/python.go +++ b/pkg/repos/runtimes/python/python.go @@ -12,17 +12,23 @@ import ( "os" "path/filepath" "runtime" + "sync" "github.com/gptscript-ai/gptscript/pkg/debugcmd" runtimeEnv "github.com/gptscript-ai/gptscript/pkg/env" "github.com/gptscript-ai/gptscript/pkg/hash" "github.com/gptscript-ai/gptscript/pkg/repos/download" + "github.com/gptscript-ai/gptscript/pkg/types" ) //go:embed python.json var releasesData []byte -const uvVersion = "uv==0.2.3" +const ( + uvVersion = "uv==0.2.33" + requirementsTxt = "requirements.txt" + gptscriptRequirementsTxt = "requirements-gptscript.txt" +) type Release struct { OS string `json:"os,omitempty"` @@ -37,13 +43,15 @@ type Runtime struct { Version string // If true this is the version that will be used for python or python3 Default bool + + runtimeSetupLock sync.Mutex } func (r *Runtime) ID() string { return "python" + r.Version } -func (r *Runtime) Supports(cmd []string) bool { +func (r *Runtime) Supports(_ types.Tool, cmd []string) bool { if runtimeEnv.Matches(cmd, r.ID()) { return true } @@ -112,7 +120,7 @@ func (r *Runtime) copyPythonForWindows(binDir string) error { return nil } -func (r *Runtime) Setup(ctx context.Context, dataRoot, toolSource string, env []string) ([]string, error) { +func (r *Runtime) Setup(ctx context.Context, tool types.Tool, dataRoot, toolSource string, env []string) ([]string, error) { binPath, err := r.getRuntime(ctx, dataRoot) if err != nil { return nil, err @@ -145,7 +153,7 @@ func (r *Runtime) Setup(ctx context.Context, dataRoot, toolSource string, env [] } } - if err := r.runPip(ctx, toolSource, binPath, append(env, newEnv...)); err != nil { + if err := r.runPip(ctx, tool, toolSource, binPath, append(env, newEnv...)); err != nil { return nil, err } @@ -170,10 +178,48 @@ func (r *Runtime) getReleaseAndDigest() (string, string, error) { return "", "", fmt.Errorf("failed to find an python runtime for %s", r.Version) } -func (r *Runtime) runPip(ctx context.Context, toolSource, binDir string, env []string) error { +func (r *Runtime) Binary(_ context.Context, _ types.Tool, _, _ string, _ []string) (bool, []string, error) { + return false, nil, nil +} + +func (r *Runtime) GetHash(tool types.Tool) (string, error) { + if !tool.Source.IsGit() && tool.WorkingDir != "" { + if _, ok := tool.MetaData[requirementsTxt]; ok { + return "", nil + } + for _, req := range []string{gptscriptRequirementsTxt, requirementsTxt} { + reqFile := filepath.Join(tool.WorkingDir, req) + if s, err := os.Stat(reqFile); err == nil && !s.IsDir() { + return hash.Digest(tool.WorkingDir + s.ModTime().String())[:7], nil + } + } + } + + return "", nil +} + +func (r *Runtime) runPip(ctx context.Context, tool types.Tool, toolSource, binDir string, env []string) error { log.InfofCtx(ctx, "Running pip in %s", toolSource) - for _, req := range []string{"requirements-gptscript.txt", "requirements.txt"} { - reqFile := filepath.Join(toolSource, req) + if content, ok := tool.MetaData[requirementsTxt]; ok { + reqFile := filepath.Join(toolSource, requirementsTxt) + if err := os.WriteFile(reqFile, []byte(content+"\n"), 0644); err != nil { + return err + } + cmd := debugcmd.New(ctx, uvBin(binDir), "pip", "install", "-r", reqFile) + cmd.Env = env + return cmd.Run() + } + + reqPath := toolSource + if !tool.Source.IsGit() { + if tool.WorkingDir == "" { + return nil + } + reqPath = tool.WorkingDir + } + + for _, req := range []string{gptscriptRequirementsTxt, requirementsTxt} { + reqFile := filepath.Join(reqPath, req) if s, err := os.Stat(reqFile); err == nil && !s.IsDir() { cmd := debugcmd.New(ctx, uvBin(binDir), "pip", "install", "-r", reqFile) cmd.Env = env @@ -185,11 +231,15 @@ func (r *Runtime) runPip(ctx context.Context, toolSource, binDir string, env []s } func (r *Runtime) setupUV(ctx context.Context, tmp string) error { + log.InfofCtx(ctx, "Install uv %s", uvVersion) cmd := debugcmd.New(ctx, pythonCmd(tmp), "-m", "pip", "install", uvVersion) return cmd.Run() } func (r *Runtime) getRuntime(ctx context.Context, cwd string) (string, error) { + r.runtimeSetupLock.Lock() + defer r.runtimeSetupLock.Unlock() + url, sha, err := r.getReleaseAndDigest() if err != nil { return "", err diff --git a/pkg/repos/runtimes/python/python_test.go b/pkg/repos/runtimes/python/python_test.go index fc2ededc..0e483305 100644 --- a/pkg/repos/runtimes/python/python_test.go +++ b/pkg/repos/runtimes/python/python_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/adrg/xdg" + "github.com/gptscript-ai/gptscript/pkg/types" "github.com/samber/lo" "github.com/stretchr/testify/require" ) @@ -27,7 +28,7 @@ func TestRuntime(t *testing.T) { Version: "3.12", } - s, err := r.Setup(context.Background(), testCacheHome, "testdata", os.Environ()) + s, err := r.Setup(context.Background(), types.Tool{}, testCacheHome, "testdata", os.Environ()) require.NoError(t, err) _, err = os.Stat(filepath.Join(firstPath(s), "python.exe")) if errors.Is(err, os.ErrNotExist) { diff --git a/pkg/runner/credentials.go b/pkg/runner/credentials.go deleted file mode 100644 index d2fbb00e..00000000 --- a/pkg/runner/credentials.go +++ /dev/null @@ -1,43 +0,0 @@ -package runner - -import ( - "fmt" - "os" - "strings" -) - -// parseCredentialOverrides parses a string of credential overrides that the user provided as a command line arg. -// The format of credential overrides can be one of two things: -// cred1:ENV1,ENV2 (direct mapping of environment variables) -// cred1:ENV1=VALUE1,ENV2=VALUE2 (key-value pairs) -// -// This function turns it into a map[string]map[string]string like this: -// -// { -// "cred1": { -// "ENV1": "VALUE1", -// "ENV2": "VALUE2", -// } -// } -func parseCredentialOverrides(overrides []string) (map[string]map[string]string, error) { - credentialOverrides := make(map[string]map[string]string) - - for _, o := range overrides { - credName, envs, found := strings.Cut(o, ":") - if !found { - return nil, fmt.Errorf("invalid credential override: %s", o) - } - envMap := make(map[string]string) - for _, env := range strings.Split(envs, ",") { - key, value, found := strings.Cut(env, "=") - if !found { - // User just passed an env var name as the key, so look up the value. - value = os.Getenv(key) - } - envMap[key] = value - } - credentialOverrides[credName] = envMap - } - - return credentialOverrides, nil -} diff --git a/pkg/runner/credentials_test.go b/pkg/runner/credentials_test.go index c568d6be..74fa9353 100644 --- a/pkg/runner/credentials_test.go +++ b/pkg/runner/credentials_test.go @@ -4,6 +4,7 @@ import ( "os" "testing" + "github.com/gptscript-ai/gptscript/pkg/credentials" "github.com/stretchr/testify/require" ) @@ -119,7 +120,7 @@ func TestParseCredentialOverrides(t *testing.T) { _ = os.Setenv(k, v) } - out, err := parseCredentialOverrides(tc.in) + out, err := credentials.ParseCredentialOverrides(tc.in) if tc.expectErr { require.Error(t, err) return diff --git a/pkg/runner/input.go b/pkg/runner/input.go index 7d77330e..04d17cc3 100644 --- a/pkg/runner/input.go +++ b/pkg/runner/input.go @@ -5,22 +5,31 @@ import ( "fmt" "github.com/gptscript-ai/gptscript/pkg/engine" + "github.com/gptscript-ai/gptscript/pkg/types" ) func (r *Runner) handleInput(callCtx engine.Context, monitor Monitor, env []string, input string) (string, error) { - inputToolRefs, err := callCtx.Tool.GetInputFilterTools(*callCtx.Program) + inputToolRefs, err := callCtx.Tool.GetToolsByType(callCtx.Program, types.ToolTypeInput) if err != nil { return "", err } for _, inputToolRef := range inputToolRefs { - inputData, err := json.Marshal(map[string]any{ - "input": input, - }) + if callCtx.Program.ToolSet[inputToolRef.ToolID].IsNoop() { + continue + } + data := map[string]any{} + _ = json.Unmarshal([]byte(input), &data) + data["input"] = input + + inputArgs, err := argsForFilters(callCtx.Program, inputToolRef, &State{ + StartInput: &input, + }, data) if err != nil { return "", fmt.Errorf("failed to marshal input: %w", err) } - res, err := r.subCall(callCtx.Ctx, callCtx, monitor, env, inputToolRef.ToolID, string(inputData), "", engine.InputToolCategory) + + res, err := r.subCall(callCtx.Ctx, callCtx, monitor, env, inputToolRef.ToolID, inputArgs, "", engine.InputToolCategory) if err != nil { return "", err } diff --git a/pkg/runner/output.go b/pkg/runner/output.go index d4cb4b9b..e48891fd 100644 --- a/pkg/runner/output.go +++ b/pkg/runner/output.go @@ -4,12 +4,49 @@ import ( "encoding/json" "errors" "fmt" + "maps" + "strings" "github.com/gptscript-ai/gptscript/pkg/engine" + "github.com/gptscript-ai/gptscript/pkg/types" ) -func (r *Runner) handleOutput(callCtx engine.Context, monitor Monitor, env []string, state *State, retErr error) (*State, error) { - outputToolRefs, err := callCtx.Tool.GetOutputFilterTools(*callCtx.Program) +func argsForFilters(prg *types.Program, tool types.ToolReference, startState *State, filterDefinedInput map[string]any) (string, error) { + startInput := "" + if startState.ResumeInput != nil { + startInput = *startState.ResumeInput + } else if startState.StartInput != nil { + startInput = *startState.StartInput + } + + parsedArgs, err := types.GetToolRefInput(prg, tool, startInput) + if err != nil { + return "", err + } + + argData := map[string]any{} + if strings.HasPrefix(parsedArgs, "{") { + if err := json.Unmarshal([]byte(parsedArgs), &argData); err != nil { + return "", fmt.Errorf("failed to unmarshal parsedArgs for filter: %w", err) + } + } else if _, hasInput := filterDefinedInput["input"]; parsedArgs != "" && !hasInput { + argData["input"] = parsedArgs + } + + resultData := map[string]any{} + maps.Copy(resultData, filterDefinedInput) + maps.Copy(resultData, argData) + + result, err := json.Marshal(resultData) + if err != nil { + return "", fmt.Errorf("failed to marshal resultData for filter: %w", err) + } + + return string(result), nil +} + +func (r *Runner) handleOutput(callCtx engine.Context, monitor Monitor, env []string, startState, state *State, retErr error) (*State, error) { + outputToolRefs, err := callCtx.Tool.GetToolsByType(callCtx.Program, types.ToolTypeOutput) if err != nil { return nil, err } @@ -39,7 +76,10 @@ func (r *Runner) handleOutput(callCtx engine.Context, monitor Monitor, env []str } for _, outputToolRef := range outputToolRefs { - inputData, err := json.Marshal(map[string]any{ + if callCtx.Program.ToolSet[outputToolRef.ToolID].IsNoop() { + continue + } + inputData, err := argsForFilters(callCtx.Program, outputToolRef, startState, map[string]any{ "output": output, "continuation": continuation, "chat": callCtx.Tool.Chat, @@ -47,7 +87,7 @@ func (r *Runner) handleOutput(callCtx engine.Context, monitor Monitor, env []str if err != nil { return nil, fmt.Errorf("marshaling input for output filter: %w", err) } - res, err := r.subCall(callCtx.Ctx, callCtx, monitor, env, outputToolRef.ToolID, string(inputData), "", engine.OutputToolCategory) + res, err := r.subCall(callCtx.Ctx, callCtx, monitor, env, outputToolRef.ToolID, inputData, "", engine.OutputToolCategory) if err != nil { return nil, err } diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index cc5a3927..200c453b 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -14,6 +14,7 @@ import ( context2 "github.com/gptscript-ai/gptscript/pkg/context" "github.com/gptscript-ai/gptscript/pkg/credentials" "github.com/gptscript-ai/gptscript/pkg/engine" + "github.com/gptscript-ai/gptscript/pkg/mcp" "github.com/gptscript-ai/gptscript/pkg/types" "golang.org/x/exp/maps" ) @@ -37,6 +38,11 @@ type Options struct { CredentialOverrides []string `usage:"-"` Sequential bool `usage:"-"` Authorizer AuthorizerFunc `usage:"-"` + MCPRunner engine.MCPRunner `usage:"-"` +} + +type RunOptions struct { + UserCancel <-chan struct{} } type AuthorizerResponse struct { @@ -65,6 +71,9 @@ func Complete(opts ...Options) (result Options) { if opt.CredentialOverrides != nil { result.CredentialOverrides = append(result.CredentialOverrides, opt.CredentialOverrides...) } + if opt.MCPRunner != nil { + result.MCPRunner = opt.MCPRunner + } } return } @@ -83,6 +92,9 @@ func complete(opts ...Options) Options { if result.Authorizer == nil { result.Authorizer = DefaultAuthorizer } + if result.MCPRunner == nil { + result.MCPRunner = mcp.DefaultRunner + } return result } @@ -95,6 +107,7 @@ type Runner struct { credOverrides []string credStore credentials.CredentialStore sequential bool + mcpRunner engine.MCPRunner } func New(client engine.Model, credStore credentials.CredentialStore, opts ...Options) (*Runner, error) { @@ -109,6 +122,7 @@ func New(client engine.Model, credStore credentials.CredentialStore, opts ...Opt credStore: credStore, sequential: opt.Sequential, auth: opt.Authorizer, + mcpRunner: opt.MCPRunner, } if opt.StartPort != 0 { @@ -130,7 +144,7 @@ type ChatResponse struct { type ChatState interface{} -func (r *Runner) Chat(ctx context.Context, prevState ChatState, prg types.Program, env []string, input string) (resp ChatResponse, err error) { +func (r *Runner) Chat(ctx context.Context, prevState ChatState, prg types.Program, env []string, input string, opts RunOptions) (resp ChatResponse, err error) { var state *State defer func() { @@ -167,30 +181,23 @@ func (r *Runner) Chat(ctx context.Context, prevState ChatState, prg types.Progra monitor.Stop(ctx, resp.Content, err) }() - callCtx, err := engine.NewContext(ctx, &prg, input) + callCtx, err := engine.NewContext(ctx, &prg, input, opts.UserCancel) if err != nil { return resp, err } - if state == nil || state.StartContinuation { - if state != nil { - state = state.WithResumeInput(&input) - input = state.InputContextContinuationInput - } + if state == nil { state, err = r.start(callCtx, state, monitor, env, input) if err != nil { return resp, err } } else { state = state.WithResumeInput(&input) - state.ResumeInput = &input } - if !state.StartContinuation { - state, err = r.resume(callCtx, monitor, env, state) - if err != nil { - return resp, err - } + state, err = r.resume(callCtx, monitor, env, state) + if err != nil { + return resp, err } if state.Result != nil { @@ -217,8 +224,8 @@ func (r *Runner) Chat(ctx context.Context, prevState ChatState, prg types.Progra }, nil } -func (r *Runner) Run(ctx context.Context, prg types.Program, env []string, input string) (output string, err error) { - resp, err := r.Chat(ctx, nil, prg, env, input) +func (r *Runner) Run(ctx context.Context, prg types.Program, env []string, input string, opts RunOptions) (output string, err error) { + resp, err := r.Chat(ctx, nil, prg, env, input, opts) if err != nil { return "", err } @@ -252,103 +259,10 @@ var ( EventTypeRunFinish EventType = "runFinish" ) -func getToolRefInput(prg *types.Program, ref types.ToolReference, input string) (string, error) { - if ref.Arg == "" { - return "", nil - } - - targetArgs := prg.ToolSet[ref.ToolID].Arguments - targetKeys := map[string]string{} - - if targetArgs == nil { - return "", nil - } - - for targetKey := range targetArgs.Properties { - targetKeys[strings.ToLower(targetKey)] = targetKey - } - - inputMap := map[string]interface{}{} - outputMap := map[string]interface{}{} - - _ = json.Unmarshal([]byte(input), &inputMap) - - fields := strings.Fields(ref.Arg) - - for i := 0; i < len(fields); i++ { - field := fields[i] - if field == "and" { - continue - } - if field == "as" { - i++ - continue - } - - var ( - keyName string - val any - ) - - if strings.HasPrefix(field, "$") { - key := strings.TrimPrefix(field, "$") - key = strings.TrimPrefix(key, "{") - key = strings.TrimSuffix(key, "}") - val = inputMap[key] - } else { - val = field - } - - if len(fields) > i+1 && fields[i+1] == "as" { - keyName = strings.ToLower(fields[i+2]) - } - - if len(targetKeys) == 0 { - return "", fmt.Errorf("can not assign arg to context because target tool [%s] has no defined args", ref.ToolID) - } - - if keyName == "" { - if len(targetKeys) != 1 { - return "", fmt.Errorf("can not assign arg to context because target tool [%s] has does not have one args. You must use \"as\" syntax to map the arg to a key %v", ref.ToolID, targetKeys) - } - for k := range targetKeys { - keyName = k - } - } - - if targetKey, ok := targetKeys[strings.ToLower(keyName)]; ok { - outputMap[targetKey] = val - } else { - return "", fmt.Errorf("can not assign arg to context because target tool [%s] has does not args [%s]", ref.ToolID, keyName) - } - } - - if len(outputMap) == 0 { - return "", nil - } - - output, err := json.Marshal(outputMap) - return string(output), err -} - -func (r *Runner) getContext(callCtx engine.Context, state *State, monitor Monitor, env []string, input string) (result []engine.InputContext, _ *State, _ error) { - toolRefs, err := callCtx.Program.GetContextToolRefs(callCtx.Tool.ID) +func (r *Runner) getContext(callCtx engine.Context, state *State, monitor Monitor, env []string, input string) (result []engine.InputContext, _ error) { + toolRefs, err := callCtx.Tool.GetToolsByType(callCtx.Program, types.ToolTypeContext) if err != nil { - return nil, nil, err - } - - var newState *State - if state != nil { - cp := *state - newState = &cp - if newState.InputContextContinuation != nil { - newState.InputContexts = nil - newState.InputContextContinuation = nil - newState.InputContextContinuationInput = "" - newState.ResumeInput = state.InputContextContinuationResumeInput - - input = state.InputContextContinuationInput - } + return nil, err } for i, toolRef := range toolRefs { @@ -357,31 +271,18 @@ func (r *Runner) getContext(callCtx engine.Context, state *State, monitor Monito continue } - contextInput, err := getToolRefInput(callCtx.Program, toolRef, input) + contextInput, err := types.GetToolRefInput(callCtx.Program, toolRef, input) if err != nil { - return nil, nil, err + return nil, err } var content *State - if state != nil && state.InputContextContinuation != nil { - content, err = r.subCallResume(callCtx.Ctx, callCtx, monitor, env, toolRef.ToolID, "", state.InputContextContinuation.WithResumeInput(state.ResumeInput), engine.ContextToolCategory) - } else { - content, err = r.subCall(callCtx.Ctx, callCtx, monitor, env, toolRef.ToolID, contextInput, "", engine.ContextToolCategory) - } + content, err = r.subCall(callCtx.Ctx, callCtx, monitor, env, toolRef.ToolID, contextInput, "", engine.ContextToolCategory) if err != nil { - return nil, nil, err + return nil, err } if content.Continuation != nil { - if newState == nil { - newState = &State{} - } - newState.InputContexts = result - newState.InputContextContinuation = content - newState.InputContextContinuationInput = input - if state != nil { - newState.InputContextContinuationResumeInput = state.ResumeInput - } - return nil, newState, nil + return nil, fmt.Errorf("invalid state: context tool [%s] can not result in a continuation", toolRef.ToolID) } result = append(result, engine.InputContext{ ToolID: toolRef.ToolID, @@ -389,7 +290,7 @@ func (r *Runner) getContext(callCtx engine.Context, state *State, monitor Monito }) } - return result, newState, nil + return result, nil } func (r *Runner) call(callCtx engine.Context, monitor Monitor, env []string, input string) (*State, error) { @@ -397,9 +298,6 @@ func (r *Runner) call(callCtx engine.Context, monitor Monitor, env []string, inp if err != nil { return nil, err } - if result.StartContinuation { - return result, nil - } return r.resume(callCtx, monitor, env, result) } @@ -419,26 +317,26 @@ func (r *Runner) start(callCtx engine.Context, state *State, monitor Monitor, en return nil, err } - if len(callCtx.Tool.Credentials) > 0 { + credTools, err := callCtx.Tool.GetToolsByType(callCtx.Program, types.ToolTypeCredential) + if err != nil { + return nil, err + } + if len(credTools) > 0 { var err error - env, err = r.handleCredentials(callCtx, monitor, env) + env, err = r.handleCredentials(callCtx, monitor, env, credTools) if err != nil { return nil, err } } - var newState *State - callCtx.InputContext, newState, err = r.getContext(callCtx, state, monitor, env, input) + callCtx.InputContext, err = r.getContext(callCtx, state, monitor, env, input) if err != nil { return nil, err } - if newState != nil && newState.InputContextContinuation != nil { - newState.StartContinuation = true - return newState, nil - } e := engine.Engine{ Model: r.c, + MCPRunner: r.mcpRunner, RuntimeManager: runtimeWithLogger(callCtx, monitor, r.runtimeManager), Progress: progress, Env: env, @@ -459,6 +357,7 @@ func (r *Runner) start(callCtx engine.Context, state *State, monitor Monitor, en msg = "Tool call request has been denied" } return &State{ + StartInput: &input, Continuation: &engine.Return{ Result: &msg, }, @@ -472,6 +371,7 @@ func (r *Runner) start(callCtx engine.Context, state *State, monitor Monitor, en } return &State{ + StartInput: &input, Continuation: ret, }, nil } @@ -481,15 +381,13 @@ type State struct { ContinuationToolID string `json:"continuationToolID,omitempty"` Result *string `json:"result,omitempty"` + StartInput *string `json:"startInput,omitempty"` + ResumeInput *string `json:"resumeInput,omitempty"` SubCalls []SubCallResult `json:"subCalls,omitempty"` SubCallID string `json:"subCallID,omitempty"` - InputContexts []engine.InputContext `json:"inputContexts,omitempty"` - InputContextContinuation *State `json:"inputContextContinuation,omitempty"` - InputContextContinuationInput string `json:"inputContextContinuationInput,omitempty"` - InputContextContinuationResumeInput *string `json:"inputContextContinuationResumeInput,omitempty"` - StartContinuation bool `json:"startContinuation,omitempty"` + InputContexts []engine.InputContext `json:"inputContexts,omitempty"` } func (s State) WithResumeInput(input *string) *State { @@ -502,10 +400,6 @@ func (s State) ContinuationContentToolID() (string, error) { return s.ContinuationToolID, nil } - if s.InputContextContinuation != nil { - return s.InputContextContinuation.ContinuationContentToolID() - } - for _, subCall := range s.SubCalls { if s.SubCallID == subCall.CallID { return subCall.State.ContinuationContentToolID() @@ -519,10 +413,6 @@ func (s State) ContinuationContent() (string, error) { return *s.Continuation.Result, nil } - if s.InputContextContinuation != nil { - return s.InputContextContinuation.ContinuationContent() - } - for _, subCall := range s.SubCalls { if s.SubCallID == subCall.CallID { return subCall.State.ContinuationContent() @@ -531,20 +421,15 @@ func (s State) ContinuationContent() (string, error) { return "", fmt.Errorf("illegal state: no result message found in chat response") } -type Needed struct { - Content string `json:"content,omitempty"` - Input string `json:"input,omitempty"` -} - func (r *Runner) resume(callCtx engine.Context, monitor Monitor, env []string, state *State) (retState *State, retErr error) { + handleOutput := true + defer func() { - retState, retErr = r.handleOutput(callCtx, monitor, env, retState, retErr) + if handleOutput { + retState, retErr = r.handleOutput(callCtx, monitor, env, state, retState, retErr) + } }() - if state.StartContinuation { - return nil, fmt.Errorf("invalid state, resume should not have StartContinuation set to true") - } - if state.Continuation == nil { return nil, errors.New("invalid state, resume should have Continuation data") } @@ -552,9 +437,13 @@ func (r *Runner) resume(callCtx engine.Context, monitor Monitor, env []string, s progress, progressClose := streamProgress(&callCtx, monitor) defer progressClose() - if len(callCtx.Tool.Credentials) > 0 { + credTools, err := callCtx.Tool.GetToolsByType(callCtx.Program, types.ToolTypeCredential) + if err != nil { + return nil, err + } + if len(credTools) > 0 { var err error - env, err = r.handleCredentials(callCtx, monitor, env) + env, err = r.handleCredentials(callCtx, monitor, env, credTools) if err != nil { return nil, err } @@ -565,21 +454,33 @@ func (r *Runner) resume(callCtx engine.Context, monitor Monitor, env []string, s if state.Continuation.Result != nil && len(state.Continuation.Calls) == 0 && state.SubCallID == "" && state.ResumeInput == nil { progressClose() - monitor.Event(Event{ - Time: time.Now(), - CallContext: callCtx.GetCallContext(), - Type: EventTypeCallFinish, - Content: getEventContent(*state.Continuation.Result, callCtx), - }) if callCtx.Tool.Chat { - return &State{ + retState = &State{ Continuation: state.Continuation, ContinuationToolID: callCtx.Tool.ID, - }, nil + } + } else { + retState = &State{ + Result: state.Continuation.Result, + } } - return &State{ - Result: state.Continuation.Result, - }, nil + handleOutput = false + retState, retErr = r.handleOutput(callCtx, monitor, env, state, retState, nil) + if retErr == nil { + var content string + if retState.Continuation != nil && retState.Continuation.Result != nil { + content = *retState.Continuation.Result + } else if retState.Result != nil { + content = *retState.Result + } + monitor.Event(Event{ + Time: time.Now(), + CallContext: callCtx.GetCallContext(), + Type: EventTypeCallFinish, + Content: getEventContent(content, callCtx), + }) + } + return retState, retErr } monitor.Event(Event{ @@ -620,15 +521,21 @@ func (r *Runner) resume(callCtx engine.Context, monitor Monitor, env []string, s } } + var content string + if state.ResumeInput != nil { + content = *state.ResumeInput + } monitor.Event(Event{ Time: time.Now(), CallContext: callCtx.GetCallContext(), Type: EventTypeCallContinue, ToolResults: len(callResults), + Content: content, }) e := engine.Engine{ Model: r.c, + MCPRunner: r.mcpRunner, RuntimeManager: runtimeWithLogger(callCtx, monitor, r.runtimeManager), Progress: progress, Env: env, @@ -640,8 +547,12 @@ func (r *Runner) resume(callCtx engine.Context, monitor Monitor, env []string, s contentInput = state.Continuation.State.Input } - callCtx.InputContext, state, err = r.getContext(callCtx, state, monitor, env, contentInput) - if err != nil || state.InputContextContinuation != nil { + if state.ResumeInput != nil { + contentInput = *state.ResumeInput + } + + callCtx.InputContext, err = r.getContext(callCtx, state, monitor, env, contentInput) + if err != nil { return state, err } @@ -719,7 +630,13 @@ func (r *Runner) subCall(ctx context.Context, parentContext engine.Context, moni }, nil } - return r.call(callCtx, monitor, env, input) + state, err := r.call(callCtx, monitor, env, input) + if finishErr := (*engine.ErrChatFinish)(nil); errors.As(err, &finishErr) && callCtx.Tool.Chat { + return &State{ + Result: &finishErr.Message, + }, nil + } + return state, err } func (r *Runner) subCallResume(ctx context.Context, parentContext engine.Context, monitor Monitor, env []string, toolID, callID string, state *State, toolCategory engine.ToolCategory) (*State, error) { @@ -728,7 +645,13 @@ func (r *Runner) subCallResume(ctx context.Context, parentContext engine.Context return nil, err } - return r.resume(callCtx, monitor, env, state) + state, err = r.resume(callCtx, monitor, env, state) + if finishErr := (*engine.ErrChatFinish)(nil); errors.As(err, &finishErr) && callCtx.Tool.Chat { + return &State{ + Result: &finishErr.Message, + }, nil + } + return state, err } type SubCallResult struct { @@ -744,17 +667,27 @@ func (r *Runner) newDispatcher(ctx context.Context) dispatcher { return newParallelDispatcher(ctx) } -func (r *Runner) subCalls(callCtx engine.Context, monitor Monitor, env []string, state *State, toolCategory engine.ToolCategory) (_ *State, callResults []SubCallResult, _ error) { - var resultLock sync.Mutex +func idForToolCall(id string, state *engine.Return) string { + if state == nil || state.State == nil { + return id + } + tc, ok := state.State.Pending[id] + if !ok || tc.Index == nil { + return id + } + return fmt.Sprintf("%03d", *tc.Index) +} + +func (r *Runner) subCalls(callCtx engine.Context, monitor Monitor, env []string, state *State, toolCategory engine.ToolCategory) (*State, []SubCallResult, error) { + var ( + resultLock sync.Mutex + callResults []SubCallResult + ) if state.Continuation != nil { callCtx.LastReturn = state.Continuation } - if state.InputContextContinuation != nil { - return state, nil, nil - } - if state.SubCallID != "" { if state.ResumeInput == nil { return nil, nil, fmt.Errorf("invalid state, input must be set for sub call continuation on callID [%s]", state.SubCallID) @@ -763,8 +696,6 @@ func (r *Runner) subCalls(callCtx engine.Context, monitor Monitor, env []string, for _, subCall := range state.SubCalls { if subCall.CallID == state.SubCallID { found = true - subState := *subCall.State - subState.ResumeInput = state.ResumeInput result, err := r.subCallResume(callCtx.Ctx, callCtx, monitor, env, subCall.ToolID, subCall.CallID, subCall.State.WithResumeInput(state.ResumeInput), toolCategory) if err != nil { return nil, nil, err @@ -790,10 +721,24 @@ func (r *Runner) subCalls(callCtx engine.Context, monitor Monitor, env []string, // Sort the id so if sequential the results are predictable ids := maps.Keys(state.Continuation.Calls) - sort.Strings(ids) + sort.Slice(ids, func(i, j int) bool { + return idForToolCall(ids[i], state.Continuation) < idForToolCall(ids[j], state.Continuation) + }) for _, id := range ids { call := state.Continuation.Calls[id] + if call.Missing { + resultLock.Lock() + callResults = append(callResults, SubCallResult{ + ToolID: call.ToolID, + CallID: id, + State: &State{ + Result: &[]string{fmt.Sprintf("ERROR: can not call unknown tool named [%s]", call.ToolID)}[0], + }, + }) + resultLock.Unlock() + continue + } d.Run(func(ctx context.Context) error { result, err := r.subCall(ctx, callCtx, monitor, env, call.ToolID, call.Input, id, toolCategory) if err != nil { @@ -828,7 +773,7 @@ func getEventContent(content string, callCtx engine.Context) string { return content } -func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env []string) ([]string, error) { +func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env []string, credToolRefs []types.ToolReference) ([]string, error) { // Since credential tools (usually) prompt the user, we want to only run one at a time. r.credMutex.Lock() defer r.credMutex.Unlock() @@ -839,16 +784,22 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env err error ) if r.credOverrides != nil { - credOverrides, err = parseCredentialOverrides(r.credOverrides) + credOverrides, err = credentials.ParseCredentialOverrides(r.credOverrides) if err != nil { return nil, fmt.Errorf("failed to parse credential overrides: %w", err) } } - for _, credToolName := range callCtx.Tool.Credentials { - toolName, credentialAlias, args, err := types.ParseCredentialArgs(credToolName, callCtx.Input) + var nearestExpiration *time.Time + for _, ref := range credToolRefs { + toolName, credentialAlias, checkParam, args, err := types.ParseCredentialArgs(ref.Reference, callCtx.Input) if err != nil { - return nil, fmt.Errorf("failed to parse credential tool %q: %w", credToolName, err) + return nil, fmt.Errorf("failed to parse credential tool %q: %w", ref.Reference, err) + } + + if callCtx.Program.ToolSet[ref.ToolID].IsNoop() { + // ignore empty tools + continue } credName := toolName @@ -865,15 +816,12 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env } var ( - c *credentials.Credential - exists bool + c *credentials.Credential + resultCredential credentials.Credential + exists bool + refresh bool ) - rm := runtimeWithLogger(callCtx, monitor, r.runtimeManager) - if err := rm.EnsureCredentialHelpers(callCtx.Ctx); err != nil { - return nil, fmt.Errorf("failed to setup credential helpers: %w", err) - } - // Only try to look up the cred if the tool is on GitHub or has an alias. // If it is a GitHub tool and has an alias, the alias overrides the tool name, so we use it as the credential name. if isGitHubTool(toolName) && credentialAlias == "" { @@ -884,7 +832,7 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env } else if credentialAlias != "" { c, exists, err = r.credStore.Get(callCtx.Ctx, credentialAlias) if err != nil { - return nil, fmt.Errorf("failed to get credentials for tool %s: %w", credentialAlias, err) + return nil, fmt.Errorf("failed to get credential %s: %w", credentialAlias, err) } } @@ -894,14 +842,11 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env // If the credential doesn't already exist in the store, run the credential tool in order to get the value, // and save it in the store. - if !exists || c.IsExpired() { - credToolRefs, ok := callCtx.Tool.ToolMapping[credToolName] - if !ok || len(credToolRefs) != 1 { - return nil, fmt.Errorf("failed to find ID for tool %s", credToolName) - } - + if !exists || c.IsExpired() || checkParam != c.CheckParam { // If the existing credential is expired, we need to provide it to the cred tool through the environment. - if exists && c.IsExpired() { + // If the check parameter is different, then we don't refresh. We should re-auth below. + if exists && c.IsExpired() && checkParam == c.CheckParam { + refresh = true credJSON, err := json.Marshal(c) if err != nil { return nil, fmt.Errorf("failed to marshal credential: %w", err) @@ -914,51 +859,86 @@ func (r *Runner) handleCredentials(callCtx engine.Context, monitor Monitor, env if args != nil { inputBytes, err := json.Marshal(args) if err != nil { - return nil, fmt.Errorf("failed to marshal args for tool %s: %w", credToolName, err) + return nil, fmt.Errorf("failed to marshal args for tool %s: %w", ref.Reference, err) } input = string(inputBytes) } - res, err := r.subCall(callCtx.Ctx, callCtx, monitor, env, credToolRefs[0].ToolID, input, "", engine.CredentialToolCategory) + res, err := r.subCall(callCtx.Ctx, callCtx, monitor, env, ref.ToolID, input, "", engine.CredentialToolCategory) if err != nil { - return nil, fmt.Errorf("failed to run credential tool %s: %w", credToolName, err) + return nil, err } if res.Result == nil { - return nil, fmt.Errorf("invalid state: credential tool [%s] can not result in a continuation", credToolName) + return nil, fmt.Errorf("invalid state: credential tool [%s] can not result in a continuation", ref.Reference) } - if err := json.Unmarshal([]byte(*res.Result), &c); err != nil { - return nil, fmt.Errorf("failed to unmarshal credential tool %s response: %w", credToolName, err) + if *res.Result == "" { + continue + } + + if strings.HasSuffix(*res.Result, engine.AbortedSuffix) { + continue + } + + if err := json.Unmarshal([]byte(*res.Result), &resultCredential); err != nil { + return nil, fmt.Errorf("failed to unmarshal credential tool %s response: %w", ref.Reference, err) + } + resultCredential.ToolName = credName + resultCredential.Type = credentials.CredentialTypeTool + + if refresh { + // If this is a credential refresh, we need to make sure we use the same context. + resultCredential.Context = c.Context + } else { + // If it is a new credential, let the credential store determine the context. + resultCredential.Context = "" } - c.ToolName = credName - c.Type = credentials.CredentialTypeTool isEmpty := true - for _, v := range c.Env { + for _, v := range resultCredential.Env { if v != "" { isEmpty = false break } } - // Only store the credential if the tool is on GitHub or has an alias, and the credential is non-empty. - if (isGitHubTool(toolName) && callCtx.Program.ToolSet[credToolRefs[0].ToolID].Source.Repo != nil) || credentialAlias != "" { - if isEmpty { - log.Warnf("Not saving empty credential for tool %s", toolName) - } else if err := r.credStore.Add(callCtx.Ctx, *c); err != nil { - return nil, fmt.Errorf("failed to add credential for tool %s: %w", toolName, err) + if !resultCredential.Ephemeral { + // Only store the credential if the tool is on GitHub or has an alias, and the credential is non-empty. + if (isGitHubTool(toolName) && callCtx.Program.ToolSet[ref.ToolID].Source.Repo != nil) || credentialAlias != "" { + if isEmpty { + log.Warnf("Not saving empty credential for tool %s", toolName) + } else { + if refresh { + err = r.credStore.Refresh(callCtx.Ctx, resultCredential) + } else { + err = r.credStore.Add(callCtx.Ctx, resultCredential) + } + if err != nil { + return nil, fmt.Errorf("failed to save credential for tool %s: %w", toolName, err) + } + } + } else { + log.Warnf("Not saving credential for tool %s - credentials will only be saved for tools from GitHub, or tools that use aliases.", toolName) } - } else { - log.Warnf("Not saving credential for tool %s - credentials will only be saved for tools from GitHub, or tools that use aliases.", toolName) } + } else { + resultCredential = *c } - for k, v := range c.Env { + if resultCredential.ExpiresAt != nil && (nearestExpiration == nil || nearestExpiration.After(*resultCredential.ExpiresAt)) { + nearestExpiration = resultCredential.ExpiresAt + } + + for k, v := range resultCredential.Env { env = append(env, fmt.Sprintf("%s=%s", k, v)) } } + if nearestExpiration != nil { + env = append(env, fmt.Sprintf("%s=%s", credentials.CredentialExpiration, nearestExpiration.Format(time.RFC3339))) + } + return env, nil } diff --git a/pkg/runner/runtimemanager.go b/pkg/runner/runtimemanager.go index e1c5a4c6..1c293215 100644 --- a/pkg/runner/runtimemanager.go +++ b/pkg/runner/runtimemanager.go @@ -5,7 +5,6 @@ import ( "fmt" "time" - "github.com/gptscript-ai/gptscript/pkg/config" "github.com/gptscript-ai/gptscript/pkg/engine" "github.com/gptscript-ai/gptscript/pkg/mvl" "github.com/gptscript-ai/gptscript/pkg/types" @@ -40,11 +39,3 @@ func (r runtimeManagerLogger) Infof(msg string, args ...any) { func (r runtimeManagerLogger) GetContext(ctx context.Context, tool types.Tool, cmd, env []string) (string, []string, error) { return r.rm.GetContext(mvl.WithInfo(ctx, r), tool, cmd, env) } - -func (r runtimeManagerLogger) EnsureCredentialHelpers(ctx context.Context) error { - return r.rm.EnsureCredentialHelpers(mvl.WithInfo(ctx, r)) -} - -func (r runtimeManagerLogger) SetUpCredentialHelpers(_ context.Context, _ *config.CLIConfig, _ []string) error { - panic("not implemented") -} diff --git a/pkg/sdkserver/credentials.go b/pkg/sdkserver/credentials.go new file mode 100644 index 00000000..adf86bc7 --- /dev/null +++ b/pkg/sdkserver/credentials.go @@ -0,0 +1,187 @@ +package sdkserver + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "slices" + + gcontext "github.com/gptscript-ai/gptscript/pkg/context" + "github.com/gptscript-ai/gptscript/pkg/credentials" +) + +func (s *server) initializeCredentialStore(_ context.Context, credCtxs []string) (credentials.CredentialStore, error) { + store, err := s.client.CredentialStoreFactory.NewStore(credCtxs) + if err != nil { + return nil, fmt.Errorf("failed to initialize credential store: %w", err) + } + + return store, nil +} + +func (s *server) recreateAllCredentials(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + + store, err := s.initializeCredentialStore(r.Context(), []string{credentials.AllCredentialContexts}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, err) + return + } + + if err := store.RecreateAll(r.Context()); err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to recreate all credentials: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": "All credentials recreated successfully"}) +} + +func (s *server) listCredentials(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + req := new(credentialsRequest) + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + if req.AllContexts { + req.Context = []string{credentials.AllCredentialContexts} + } else if len(req.Context) == 0 { + req.Context = []string{credentials.DefaultCredentialContext} + } + + store, err := s.initializeCredentialStore(r.Context(), req.Context) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, err) + return + } + + creds, err := store.List(r.Context()) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to list credentials: %w", err)) + return + } + + // Remove the environment variable values (which are secrets) and refresh tokens from the response. + for i := range creds { + for k := range creds[i].Env { + creds[i].Env[k] = "" + } + creds[i].RefreshToken = "" + } + + writeResponse(logger, w, map[string]any{"stdout": creds}) +} + +func (s *server) createCredential(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + req := new(credentialsRequest) + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + cred := new(credentials.Credential) + if err := json.Unmarshal([]byte(req.Content), cred); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid credential: %w", err)) + return + } + + if cred.Context == "" { + cred.Context = credentials.DefaultCredentialContext + } + + store, err := s.initializeCredentialStore(r.Context(), []string{cred.Context}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, err) + return + } + + if err := store.Add(r.Context(), *cred); err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to create credential: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": "Credential created successfully"}) +} + +func (s *server) revealCredential(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + req := new(credentialsRequest) + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + if req.Name == "" { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("missing credential name")) + return + } + + if req.AllContexts || slices.Contains(req.Context, credentials.AllCredentialContexts) { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("allContexts is not supported for credential retrieval; please specify the specific context that the credential is in")) + return + } else if len(req.Context) == 0 { + req.Context = []string{credentials.DefaultCredentialContext} + } + + store, err := s.initializeCredentialStore(r.Context(), req.Context) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, err) + return + } + + cred, ok, err := store.Get(r.Context(), req.Name) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to get credential: %w", err)) + return + } else if !ok { + writeError(logger, w, http.StatusNotFound, fmt.Errorf("credential not found")) + return + } + + writeResponse(logger, w, map[string]any{"stdout": cred}) +} + +func (s *server) deleteCredential(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + req := new(credentialsRequest) + if err := json.NewDecoder(r.Body).Decode(req); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + } + + if req.Name == "" { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("missing credential name")) + return + } + + if req.AllContexts || slices.Contains(req.Context, credentials.AllCredentialContexts) { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("allContexts is not supported for credential deletion; please specify the specific context that the credential is in")) + return + } else if len(req.Context) == 0 { + req.Context = []string{credentials.DefaultCredentialContext} + } + + store, err := s.initializeCredentialStore(r.Context(), req.Context) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, err) + return + } + + // Check to see if a cred exists so we can return a 404 if it doesn't. + if _, ok, err := store.Get(r.Context(), req.Name); err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to get credential: %w", err)) + return + } else if !ok { + writeError(logger, w, http.StatusNotFound, fmt.Errorf("credential not found")) + return + } + + if err := store.Remove(r.Context(), req.Name); err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to delete credential: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": "Credential deleted successfully"}) +} diff --git a/pkg/sdkserver/datasets.go b/pkg/sdkserver/datasets.go new file mode 100644 index 00000000..b923490b --- /dev/null +++ b/pkg/sdkserver/datasets.go @@ -0,0 +1,281 @@ +package sdkserver + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + gcontext "github.com/gptscript-ai/gptscript/pkg/context" + "github.com/gptscript-ai/gptscript/pkg/gptscript" + "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/runner" +) + +func (s *server) getDatasetTool(req datasetRequest) string { + if req.DatasetTool != "" { + return req.DatasetTool + } + + return s.datasetTool +} + +type datasetRequest struct { + Input string `json:"input"` + DatasetTool string `json:"datasetTool"` + Env []string `json:"env"` +} + +func (r datasetRequest) validate(requireInput bool) error { + if requireInput && r.Input == "" { + return fmt.Errorf("input is required") + } else if len(r.Env) == 0 { + return fmt.Errorf("env is required") + } + return nil +} + +func (r datasetRequest) opts(o gptscript.Options) gptscript.Options { + opts := gptscript.Options{ + Cache: o.Cache, + Monitor: o.Monitor, + Runner: o.Runner, + } + for _, e := range r.Env { + v, ok := strings.CutPrefix(e, "GPTSCRIPT_WORKSPACE_ID=") + if ok { + opts.Workspace = v + } + } + return opts +} + +func (s *server) listDatasets(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + + var req datasetRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to decode request body: %w", err)) + return + } + + if err := req.validate(false); err != nil { + writeError(logger, w, http.StatusBadRequest, err) + return + } + + g, err := gptscript.New(r.Context(), req.opts(s.gptscriptOpts)) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to initialize gptscript: %w", err)) + return + } + defer g.Close(false) + + prg, err := loader.Program(r.Context(), s.getDatasetTool(req), "List Datasets", loader.Options{ + Cache: g.Cache, + }) + + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + result, err := g.Run(r.Context(), prg, s.getServerToolsEnv(req.Env), req.Input, runner.RunOptions{}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": result}) +} + +type addDatasetElementsArgs struct { + DatasetID string `json:"datasetID"` + Name string `json:"name"` + Description string `json:"description"` + Elements []struct { + Name string `json:"name"` + Description string `json:"description"` + Contents string `json:"contents"` + BinaryContents []byte `json:"binaryContents"` + } `json:"elements"` +} + +func (a addDatasetElementsArgs) validate() error { + if len(a.Elements) == 0 { + return fmt.Errorf("elements is required") + } + return nil +} + +func (s *server) addDatasetElements(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + + var req datasetRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to decode request body: %w", err)) + return + } + + if err := req.validate(true); err != nil { + writeError(logger, w, http.StatusBadRequest, err) + return + } + + g, err := gptscript.New(r.Context(), req.opts(s.gptscriptOpts)) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to initialize gptscript: %w", err)) + return + } + defer g.Close(false) + + var args addDatasetElementsArgs + if err := json.Unmarshal([]byte(req.Input), &args); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to unmarshal input: %w", err)) + return + } + + if err := args.validate(); err != nil { + writeError(logger, w, http.StatusBadRequest, err) + return + } + + prg, err := loader.Program(r.Context(), s.getDatasetTool(req), "Add Elements", loader.Options{ + Cache: g.Cache, + }) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + result, err := g.Run(r.Context(), prg, s.getServerToolsEnv(req.Env), req.Input, runner.RunOptions{}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": result}) +} + +type listDatasetElementsArgs struct { + DatasetID string `json:"datasetID"` +} + +func (a listDatasetElementsArgs) validate() error { + if a.DatasetID == "" { + return fmt.Errorf("datasetID is required") + } + return nil +} + +func (s *server) listDatasetElements(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + + var req datasetRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to decode request body: %w", err)) + return + } + + if err := req.validate(true); err != nil { + writeError(logger, w, http.StatusBadRequest, err) + return + } + + g, err := gptscript.New(r.Context(), req.opts(s.gptscriptOpts)) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to initialize gptscript: %w", err)) + return + } + defer g.Close(false) + + var args listDatasetElementsArgs + if err := json.Unmarshal([]byte(req.Input), &args); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to unmarshal input: %w", err)) + return + } + + if err := args.validate(); err != nil { + writeError(logger, w, http.StatusBadRequest, err) + return + } + + prg, err := loader.Program(r.Context(), s.getDatasetTool(req), "List Elements", loader.Options{ + Cache: g.Cache, + }) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + result, err := g.Run(r.Context(), prg, s.getServerToolsEnv(req.Env), req.Input, runner.RunOptions{}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": result}) +} + +type getDatasetElementArgs struct { + DatasetID string `json:"datasetID"` + Name string `json:"name"` +} + +func (a getDatasetElementArgs) validate() error { + if a.DatasetID == "" { + return fmt.Errorf("datasetID is required") + } else if a.Name == "" { + return fmt.Errorf("name is required") + } + return nil +} + +func (s *server) getDatasetElement(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + + var req datasetRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to decode request body: %w", err)) + return + } + + if err := req.validate(true); err != nil { + writeError(logger, w, http.StatusBadRequest, err) + return + } + + g, err := gptscript.New(r.Context(), req.opts(s.gptscriptOpts)) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to initialize gptscript: %w", err)) + return + } + defer g.Close(false) + + var args getDatasetElementArgs + if err := json.Unmarshal([]byte(req.Input), &args); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to unmarshal input: %w", err)) + return + } + + if err := args.validate(); err != nil { + writeError(logger, w, http.StatusBadRequest, err) + return + } + + prg, err := loader.Program(r.Context(), s.getDatasetTool(req), "Get Element", loader.Options{ + Cache: g.Cache, + }) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + result, err := g.Run(r.Context(), prg, s.getServerToolsEnv(req.Env), req.Input, runner.RunOptions{}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": result}) +} diff --git a/pkg/sdkserver/monitor.go b/pkg/sdkserver/monitor.go index a5b0236b..bdd88c67 100644 --- a/pkg/sdkserver/monitor.go +++ b/pkg/sdkserver/monitor.go @@ -33,6 +33,7 @@ func (s SessionFactory) Start(ctx context.Context, prg *types.Program, env []str Time: time.Now(), Type: runner.EventTypeRunStart, }, + Input: input, RunID: id, Program: prg, }, @@ -43,7 +44,6 @@ func (s SessionFactory) Start(ctx context.Context, prg *types.Program, env []str id: id, prj: prg, env: env, - input: input, events: s.events, }, nil } @@ -56,7 +56,6 @@ type Session struct { id string prj *types.Program env []string - input string events *broadcaster.Broadcaster[event] runLock sync.Mutex } @@ -68,7 +67,6 @@ func (s *Session) Event(e runner.Event) { Event: gserver.Event{ Event: e, RunID: s.id, - Input: s.input, }, } } @@ -87,7 +85,6 @@ func (s *Session) Stop(ctx context.Context, output string, err error) { Type: runner.EventTypeRunFinish, }, RunID: s.id, - Input: s.input, Output: output, }, } diff --git a/pkg/sdkserver/prompt.go b/pkg/sdkserver/prompt.go index 8d34fc53..a519f7b2 100644 --- a/pkg/sdkserver/prompt.go +++ b/pkg/sdkserver/prompt.go @@ -76,11 +76,7 @@ func (s *server) prompt(w http.ResponseWriter, r *http.Request) { }(id) s.events.C <- event{ - Prompt: types.Prompt{ - Message: prompt.Message, - Fields: prompt.Fields, - Sensitive: prompt.Sensitive, - }, + Prompt: prompt, Event: gserver.Event{ RunID: id, Event: runner.Event{ diff --git a/pkg/sdkserver/routes.go b/pkg/sdkserver/routes.go index c16b4429..52a06994 100644 --- a/pkg/sdkserver/routes.go +++ b/pkg/sdkserver/routes.go @@ -1,20 +1,17 @@ package sdkserver import ( - "context" "encoding/json" "fmt" - "io" "net/http" - "os" "sort" "strings" "sync" - "time" "github.com/gptscript-ai/broadcaster" "github.com/gptscript-ai/gptscript/pkg/cache" gcontext "github.com/gptscript-ai/gptscript/pkg/context" + "github.com/gptscript-ai/gptscript/pkg/engine" "github.com/gptscript-ai/gptscript/pkg/gptscript" "github.com/gptscript-ai/gptscript/pkg/input" "github.com/gptscript-ai/gptscript/pkg/loader" @@ -26,17 +23,23 @@ import ( "github.com/gptscript-ai/gptscript/pkg/version" ) -const toolRunTimeout = 15 * time.Minute - type server struct { - gptscriptOpts gptscript.Options - address, token string - client *gptscript.GPTScript - events *broadcaster.Broadcaster[event] + gptscriptOpts gptscript.Options + address, token string + datasetTool, workspaceTool string + serverToolsEnv []string + client *gptscript.GPTScript + mcpLoader loader.MCPLoader + events *broadcaster.Broadcaster[event] + + runtimeManager engine.RuntimeManager lock sync.RWMutex waitingToConfirm map[string]chan runner.AuthorizerResponse waitingToPrompt map[string]chan map[string]string + + runningLock sync.Mutex + running map[string]chan struct{} } func (s *server) addRoutes(mux *http.ServeMux) { @@ -53,6 +56,9 @@ func (s *server) addRoutes(mux *http.ServeMux) { mux.HandleFunc("POST /run", s.execHandler) mux.HandleFunc("POST /evaluate", s.execHandler) + mux.HandleFunc("POST /abort/{run_id}", s.abort) + + mux.HandleFunc("POST /load", s.load) mux.HandleFunc("POST /parse", s.parse) mux.HandleFunc("POST /fmt", s.fmtDocument) @@ -60,6 +66,30 @@ func (s *server) addRoutes(mux *http.ServeMux) { mux.HandleFunc("POST /confirm/{id}", s.confirm) mux.HandleFunc("POST /prompt/{id}", s.prompt) mux.HandleFunc("POST /prompt-response/{id}", s.promptResponse) + + mux.HandleFunc("POST /credentials", s.listCredentials) + mux.HandleFunc("POST /credentials/create", s.createCredential) + mux.HandleFunc("POST /credentials/reveal", s.revealCredential) + mux.HandleFunc("POST /credentials/delete", s.deleteCredential) + mux.HandleFunc("POST /credentials/recreate-all", s.recreateAllCredentials) + + mux.HandleFunc("POST /datasets", s.listDatasets) + mux.HandleFunc("POST /datasets/list-elements", s.listDatasetElements) + mux.HandleFunc("POST /datasets/get-element", s.getDatasetElement) + mux.HandleFunc("POST /datasets/add-elements", s.addDatasetElements) + + mux.HandleFunc("POST /workspaces/create", s.createWorkspace) + mux.HandleFunc("POST /workspaces/delete", s.deleteWorkspace) + mux.HandleFunc("POST /workspaces/list", s.listWorkspaceContents) + mux.HandleFunc("POST /workspaces/remove-all-with-prefix", s.removeAllWithPrefixInWorkspace) + mux.HandleFunc("POST /workspaces/write-file", s.writeFileInWorkspace) + mux.HandleFunc("POST /workspaces/delete-file", s.removeFileInWorkspace) + mux.HandleFunc("POST /workspaces/read-file", s.readFileInWorkspace) + mux.HandleFunc("POST /workspaces/read-file-with-revision", s.readFileWithRevisionInWorkspace) + mux.HandleFunc("POST /workspaces/stat-file", s.statFileInWorkspace) + mux.HandleFunc("POST /workspaces/list-revisions", s.listRevisions) + mux.HandleFunc("POST /workspaces/get-revision", s.getRevisionForFileInWorkspace) + mux.HandleFunc("POST /workspaces/delete-revision", s.deleteRevisionForFileInWorkspace) } // health just provides an endpoint for checking whether the server is running and accessible. @@ -75,43 +105,17 @@ func (s *server) version(w http.ResponseWriter, r *http.Request) { // listTools will return the output of `gptscript --list-tools` func (s *server) listTools(w http.ResponseWriter, r *http.Request) { logger := gcontext.GetLogger(r.Context()) - var prg types.Program - if r.ContentLength != 0 { - reqObject := new(toolOrFileRequest) - err := json.NewDecoder(r.Body).Decode(reqObject) - if err != nil { - writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to decode request body: %w", err)) - return - } - - if reqObject.Content != "" { - prg, err = loader.ProgramFromSource(r.Context(), reqObject.Content, reqObject.SubTool, loader.Options{Cache: s.client.Cache}) - } else if reqObject.File != "" { - prg, err = loader.Program(r.Context(), reqObject.File, reqObject.SubTool, loader.Options{Cache: s.client.Cache}) - } else { - prg, err = loader.ProgramFromSource(r.Context(), reqObject.ToolDefs.String(), reqObject.SubTool, loader.Options{Cache: s.client.Cache}) - } - if err != nil { - writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) - return - } - } - - tools := s.client.ListTools(r.Context(), prg) + tools := s.client.ListTools(r.Context(), types.Program{}) sort.Slice(tools, func(i, j int) bool { return tools[i].Name < tools[j].Name }) lines := make([]string, 0, len(tools)) for _, tool := range tools { - if tool.Name == "" { - tool.Name = prg.Name - } - // Don't print instructions tool.Instructions = "" - lines = append(lines, tool.String()) + lines = append(lines, tool.Print()) } writeResponse(logger, w, map[string]any{"stdout": strings.Join(lines, "\n---\n")}) @@ -120,53 +124,73 @@ func (s *server) listTools(w http.ResponseWriter, r *http.Request) { // listModels will return the output of `gptscript --list-models` func (s *server) listModels(w http.ResponseWriter, r *http.Request) { logger := gcontext.GetLogger(r.Context()) + client := s.client + var providers []string if r.ContentLength != 0 { reqObject := new(modelsRequest) - if err := json.NewDecoder(r.Body).Decode(reqObject); err != nil { + err := json.NewDecoder(r.Body).Decode(reqObject) + if err != nil { writeError(logger, w, http.StatusBadRequest, fmt.Errorf("failed to decode request body: %w", err)) return } providers = reqObject.Providers + + client, err = gptscript.New(r.Context(), s.gptscriptOpts, gptscript.Options{Env: reqObject.Env, Runner: runner.Options{CredentialOverrides: reqObject.CredentialOverrides}}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to create client: %w", err)) + return + } } - out, err := s.client.ListModels(r.Context(), providers...) + if s.gptscriptOpts.DefaultModelProvider != "" { + providers = append(providers, s.gptscriptOpts.DefaultModelProvider) + } + + out, err := client.ListModels(r.Context(), providers...) if err != nil { writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to list models: %w", err)) return } - writeResponse(logger, w, map[string]any{"stdout": strings.Join(out, "\n")}) + writeResponse(logger, w, map[string]any{"stdout": out}) } // execHandler is a general handler for executing tools with gptscript. This is mainly responsible for parsing the request body. // Then the options and tool are passed to the process function. func (s *server) execHandler(w http.ResponseWriter, r *http.Request) { logger := gcontext.GetLogger(r.Context()) - body, err := io.ReadAll(r.Body) - if err != nil { - writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to read request body: %w", err)) - return - } reqObject := new(toolOrFileRequest) - if err := json.Unmarshal(body, reqObject); err != nil { + if err := json.NewDecoder(r.Body).Decode(reqObject); err != nil { writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) return } ctx := gserver.ContextWithNewRunID(r.Context()) runID := gserver.RunIDFromContext(ctx) - ctx, cancel := context.WithTimeout(ctx, toolRunTimeout) - defer cancel() + cancel := make(chan struct{}) + s.runningLock.Lock() + s.running[runID] = cancel + s.runningLock.Unlock() + + defer func() { + s.runningLock.Lock() + // Need to check if the cancel is still in map. In case when user abort, the channel will be deleted from map and closed already, and closing it again will panic + _, ok := s.running[runID] + if ok { + close(cancel) + } + delete(s.running, runID) + s.runningLock.Unlock() + }() // Ensure chat state is not empty. if reqObject.ChatState == "" { reqObject.ChatState = "null" } - reqObject.Env = append(os.Environ(), reqObject.Env...) // Don't overwrite the PromptURLEnvVar if it is already set in the environment. var promptTokenAlreadySet bool for _, env := range reqObject.Env { @@ -183,7 +207,7 @@ func (s *server) execHandler(w http.ResponseWriter, r *http.Request) { logger.Debugf("executing tool: %+v", reqObject) var ( def fmt.Stringer = &reqObject.ToolDefs - programLoader loaderFunc = loader.ProgramFromSource + programLoader = loaderWithLocation(loader.ProgramFromSource, reqObject.Location) ) if reqObject.Content != "" { def = &reqObject.content @@ -193,23 +217,94 @@ func (s *server) execHandler(w http.ResponseWriter, r *http.Request) { } opts := gptscript.Options{ - Cache: cache.Options(reqObject.cacheOptions), - OpenAI: openai.Options(reqObject.openAIOptions), - Env: reqObject.Env, - Workspace: reqObject.Workspace, - CredentialContext: reqObject.CredentialContext, + Cache: cache.Options(reqObject.cacheOptions), + OpenAI: openai.Options(reqObject.openAIOptions), + Env: reqObject.Env, + Workspace: reqObject.Workspace, + CredentialContexts: reqObject.CredentialContexts, Runner: runner.Options{ // Set the monitor factory so that we can get events from the server. MonitorFactory: NewSessionFactory(s.events), CredentialOverrides: reqObject.CredentialOverrides, + Sequential: reqObject.ForceSequential, }, + DefaultModelProvider: reqObject.DefaultModelProvider, } if reqObject.Confirm { opts.Runner.Authorizer = s.authorize } - s.execAndStream(ctx, programLoader, logger, w, opts, reqObject.ChatState, reqObject.Input, reqObject.SubTool, def) + s.execAndStream(ctx, programLoader, logger, w, opts, reqObject.ChatState, reqObject.Input, reqObject.SubTool, def, cancel) +} + +// abort will abort the run in a way such that the chat state will be returned. +func (s *server) abort(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + runID := r.PathValue("run_id") + if runID == "" { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("run_id is required")) + return + } + + s.runningLock.Lock() + cancel := s.running[runID] + delete(s.running, runID) + s.runningLock.Unlock() + + if cancel == nil { + writeResponse(logger, w, "run not found") + return + } + + close(cancel) + writeResponse(logger, w, "run aborted") +} + +// load will load the file and return the corresponding Program. +func (s *server) load(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + reqObject := new(loadRequest) + if err := json.NewDecoder(r.Body).Decode(reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + logger.Debugf("parsing file: file=%s, content=%s", reqObject.File, reqObject.Content) + + var ( + prg types.Program + err error + + ctx = r.Context() + ) + + if reqObject.DisableCache { + ctx = cache.WithNoCache(ctx) + } + + if reqObject.Content != "" { + prg, err = loader.ProgramFromSource(ctx, reqObject.Content, reqObject.SubTool, loader.Options{ + Cache: s.client.Cache, + MCPLoader: s.mcpLoader, + }) + } else if reqObject.File != "" { + prg, err = loader.Program(ctx, reqObject.File, reqObject.SubTool, loader.Options{ + Cache: s.client.Cache, + MCPLoader: s.mcpLoader, + }) + } else { + prg, err = loader.ProgramFromSource(ctx, reqObject.ToolDefs.String(), reqObject.SubTool, loader.Options{ + Cache: s.client.Cache, + MCPLoader: s.mcpLoader, + }) + } + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": map[string]any{"program": prg}}) } // parse will parse the file and return the corresponding Document. @@ -231,9 +326,9 @@ func (s *server) parse(w http.ResponseWriter, r *http.Request) { if reqObject.Content != "" { out, err = parser.Parse(strings.NewReader(reqObject.Content), reqObject.Options) } else { - content, loadErr := input.FromLocation(reqObject.File) + content, loadErr := input.FromLocation(reqObject.File, reqObject.DisableCache) if loadErr != nil { - logger.Errorf(loadErr.Error()) + logger.Errorf("failed to load file: %v", loadErr) writeError(logger, w, http.StatusInternalServerError, loadErr) return } @@ -258,5 +353,5 @@ func (s *server) fmtDocument(w http.ResponseWriter, r *http.Request) { return } - writeResponse(logger, w, map[string]string{"stdout": doc.String()}) + writeResponse(logger, w, map[string]string{"stdout": doc.Print()}) } diff --git a/pkg/sdkserver/run.go b/pkg/sdkserver/run.go index dc155557..a2c0d505 100644 --- a/pkg/sdkserver/run.go +++ b/pkg/sdkserver/run.go @@ -16,7 +16,15 @@ import ( type loaderFunc func(context.Context, string, string, ...loader.Options) (types.Program, error) -func (s *server) execAndStream(ctx context.Context, programLoader loaderFunc, logger mvl.Logger, w http.ResponseWriter, opts gptscript.Options, chatState, input, subTool string, toolDef fmt.Stringer) { +func loaderWithLocation(f loaderFunc, loc string) loaderFunc { + return func(ctx context.Context, s string, s2 string, options ...loader.Options) (types.Program, error) { + return f(ctx, s, s2, append(options, loader.Options{ + Location: loc, + })...) + } +} + +func (s *server) execAndStream(ctx context.Context, programLoader loaderFunc, logger mvl.Logger, w http.ResponseWriter, opts gptscript.Options, chatState, input, subTool string, toolDef fmt.Stringer, cancel <-chan struct{}) { g, err := gptscript.New(ctx, s.gptscriptOpts, opts) if err != nil { writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to initialize gptscript: %w", err)) @@ -24,7 +32,15 @@ func (s *server) execAndStream(ctx context.Context, programLoader loaderFunc, lo } defer g.Close(false) - prg, err := programLoader(ctx, toolDef.String(), subTool, loader.Options{Cache: g.Cache}) + defaultModel := opts.OpenAI.DefaultModel + if defaultModel == "" { + defaultModel = s.gptscriptOpts.OpenAI.DefaultModel + } + prg, err := programLoader(ctx, toolDef.String(), subTool, loader.Options{ + Cache: g.Cache, + DefaultModel: defaultModel, + MCPLoader: s.mcpLoader, + }) if err != nil { writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) return @@ -36,7 +52,9 @@ func (s *server) execAndStream(ctx context.Context, programLoader loaderFunc, lo defer events.Close() go func() { - run, err := g.Chat(ctx, chatState, prg, opts.Env, input) + run, err := g.Chat(ctx, chatState, prg, opts.Env, input, runner.RunOptions{ + UserCancel: cancel, + }) if err != nil { errChan <- err } else { @@ -46,72 +64,52 @@ func (s *server) execAndStream(ctx context.Context, programLoader loaderFunc, lo close(programOutput) }() - processEventStreamOutput(ctx, logger, w, gserver.RunIDFromContext(ctx), events.C, programOutput, errChan) + processEventStreamOutput(logger, w, gserver.RunIDFromContext(ctx), events.C, programOutput, errChan) } // processEventStreamOutput will stream the events of the tool to the response as server sent events. // If an error occurs, then an event with the error will also be sent. -func processEventStreamOutput(ctx context.Context, logger mvl.Logger, w http.ResponseWriter, id string, events <-chan event, output <-chan runner.ChatResponse, errChan chan error) { +func processEventStreamOutput(logger mvl.Logger, w http.ResponseWriter, id string, events <-chan event, output <-chan runner.ChatResponse, errChan chan error) { run := newRun(id) setStreamingHeaders(w) - streamEvents(ctx, logger, w, run, events) + streamEvents(logger, w, run, events) - var out runner.ChatResponse select { - case <-ctx.Done(): - case out = <-output: + case out := <-output: run.processStdout(out) writeServerSentEvent(logger, w, map[string]any{ "stdout": out, }) case err := <-errChan: - writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run file: %w", err)) + writeServerSentEvent(logger, w, map[string]any{ + "stderr": err.Error(), + }) } // Now that we have received all events, send the DONE event. - _, err := w.Write([]byte("data: [DONE]\n\n")) - if err == nil { - if f, ok := w.(http.Flusher); ok { - f.Flush() - } - } + writeServerSentEvent(logger, w, "[DONE]") logger.Debugf("wrote DONE event") } // streamEvents will stream the events of the tool to the response as server sent events. -func streamEvents(ctx context.Context, logger mvl.Logger, w http.ResponseWriter, run *runInfo, events <-chan event) { +func streamEvents(logger mvl.Logger, w http.ResponseWriter, run *runInfo, events <-chan event) { logger.Debugf("receiving events") - for { - select { - case <-ctx.Done(): - logger.Debugf("context canceled while receiving events") - go func() { - //nolint:revive - for range events { - } - }() - return - case e, ok := <-events: - if ok && e.RunID != run.ID { - continue - } - - if !ok { - logger.Debugf("done receiving events") - return - } - - writeServerSentEvent(logger, w, run.process(e)) - - if e.Type == runner.EventTypeRunFinish { - logger.Debugf("finished receiving events") - return - } + for e := range events { + if e.RunID != run.ID { + continue + } + + writeServerSentEvent(logger, w, run.process(e)) + + if e.Type == runner.EventTypeRunFinish { + break } } + + logger.Debugf("done receiving events") } func writeResponse(logger mvl.Logger, w http.ResponseWriter, v any) { @@ -137,7 +135,7 @@ func writeError(logger mvl.Logger, w http.ResponseWriter, code int, err error) { b, err := json.Marshal(resp) if err != nil { - _, _ = w.Write([]byte(fmt.Sprintf(`{"stderr": "%s"}`, err.Error()))) + _, _ = fmt.Fprintf(w, `{"stderr": "%s"}`, err.Error()) return } @@ -154,7 +152,7 @@ func writeServerSentEvent(logger mvl.Logger, w http.ResponseWriter, event any) { return } - _, err = w.Write([]byte(fmt.Sprintf("data: %s\n\n", ev))) + _, err = fmt.Fprintf(w, "data: %s\n\n", ev) if err == nil { if f, ok := w.(http.Flusher); ok { f.Flush() diff --git a/pkg/sdkserver/server.go b/pkg/sdkserver/server.go index 4556f69e..7b0e4c1a 100644 --- a/pkg/sdkserver/server.go +++ b/pkg/sdkserver/server.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "io" - "log/slog" + "log" "net" "net/http" "os" @@ -16,7 +16,10 @@ import ( "github.com/google/uuid" "github.com/gptscript-ai/broadcaster" "github.com/gptscript-ai/gptscript/pkg/gptscript" + "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/mcp" "github.com/gptscript-ai/gptscript/pkg/mvl" + "github.com/gptscript-ai/gptscript/pkg/repos/runtimes" "github.com/gptscript-ai/gptscript/pkg/runner" "github.com/gptscript-ai/gptscript/pkg/types" "github.com/rs/cors" @@ -25,11 +28,28 @@ import ( type Options struct { gptscript.Options - ListenAddress string - Debug bool + MCPLoader loader.MCPLoader + ListenAddress string + DatasetTool, WorkspaceTool string + ServerToolsEnv []string + Debug bool + DisableServerErrorLogging bool } -func Start(ctx context.Context, opts Options) error { +// Run will start the server and block until the server is shut down. +func Run(ctx context.Context, opts Options) error { + opts = complete(opts) + + listener, err := newListener(opts) + if err != nil { + return err + } + + _, err = io.WriteString(os.Stderr, listener.Addr().String()+"\n") + if err != nil { + return fmt.Errorf("failed to write to address to stderr: %w", err) + } + sigCtx, cancel := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGKILL) defer cancel() go func() { @@ -40,12 +60,42 @@ func Start(ctx context.Context, opts Options) error { cancel() }() + return run(sigCtx, listener, opts) +} + +// EmbeddedStart allows running the server as an embedded process that may use Stdin for input. +// It returns the address the server is listening on. +func EmbeddedStart(ctx context.Context, options ...Options) (string, error) { + opts := complete(options...) + + listener, err := newListener(opts) + if err != nil { + return "", err + } + + go func() { + _ = run(ctx, listener, opts) + }() + + return listener.Addr().String(), nil +} + +func (s *server) close() { + s.client.Close(true) + s.events.Close() +} + +func newListener(opts Options) (net.Listener, error) { + return net.Listen("tcp", opts.ListenAddress) +} + +func run(ctx context.Context, listener net.Listener, opts Options) error { if opts.Debug { mvl.SetDebug() } events := broadcaster.New[event]() - opts.Options.Runner.MonitorFactory = NewSessionFactory(events) + opts.Runner.MonitorFactory = NewSessionFactory(events) go events.Start(ctx) token := uuid.NewString() @@ -58,26 +108,29 @@ func Start(ctx context.Context, opts Options) error { return err } - listener, err := net.Listen("tcp", opts.ListenAddress) - if err != nil { - return fmt.Errorf("failed to listen on %s: %w", opts.ListenAddress, err) - } - s := &server{ - gptscriptOpts: opts.Options, - address: listener.Addr().String(), - token: token, + gptscriptOpts: opts.Options, + address: listener.Addr().String(), + token: token, + datasetTool: opts.DatasetTool, + workspaceTool: opts.WorkspaceTool, + serverToolsEnv: opts.ServerToolsEnv, + client: g, + mcpLoader: opts.MCPLoader, events: events, + runtimeManager: runtimes.Default(opts.Cache.CacheDir, opts.SystemToolsDir), waitingToConfirm: make(map[string]chan runner.AuthorizerResponse), waitingToPrompt: make(map[string]chan map[string]string), + running: make(map[string]chan struct{}), } - defer s.Close() + defer s.close() - s.addRoutes(http.DefaultServeMux) + mux := http.NewServeMux() + s.addRoutes(mux) - server := http.Server{ - Handler: apply(http.DefaultServeMux, + httpServer := &http.Server{ + Handler: apply(mux, contentType("application/json"), addRequestID, addLogger, @@ -86,25 +139,59 @@ func Start(ctx context.Context, opts Options) error { ), } - slog.Info("Starting server", "addr", s.address) + if opts.DisableServerErrorLogging { + httpServer.ErrorLog = log.New(io.Discard, "", 0) + } - context.AfterFunc(sigCtx, func() { - ctx, cancel := context.WithTimeout(ctx, 15*time.Second) + logger := mvl.Package() + done := make(chan struct{}) + context.AfterFunc(ctx, func() { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - slog.Info("Shutting down server") - _ = server.Shutdown(ctx) - slog.Info("Server stopped") + logger.Infof("Shutting down server") + _ = httpServer.Shutdown(ctx) + logger.Infof("Server stopped") + close(done) }) - if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { + if err = httpServer.Serve(listener); !errors.Is(err, http.ErrServerClosed) { return fmt.Errorf("server error: %w", err) } + <-done return nil } -func (s *server) Close() { - s.client.Close(true) - s.events.Close() +func complete(opts ...Options) Options { + var result Options + + for _, opt := range opts { + result.Options = gptscript.Complete(result.Options, opt.Options) + result.ListenAddress = types.FirstSet(opt.ListenAddress, result.ListenAddress) + result.DatasetTool = types.FirstSet(opt.DatasetTool, result.DatasetTool) + result.WorkspaceTool = types.FirstSet(opt.WorkspaceTool, result.WorkspaceTool) + result.Debug = types.FirstSet(opt.Debug, result.Debug) + result.DisableServerErrorLogging = types.FirstSet(opt.DisableServerErrorLogging, result.DisableServerErrorLogging) + result.MCPLoader = types.FirstSet(opt.MCPLoader, result.MCPLoader) + } + + if result.ListenAddress == "" { + result.ListenAddress = "127.0.0.1:0" + } + + if result.WorkspaceTool == "" { + result.WorkspaceTool = "github.com/gptscript-ai/workspace-provider" + } + if result.DatasetTool == "" { + result.DatasetTool = "github.com/gptscript-ai/datasets" + } + if len(result.ServerToolsEnv) == 0 { + result.ServerToolsEnv = os.Environ() + } + if result.MCPLoader == nil { + result.MCPLoader = mcp.DefaultLoader + } + + return result } diff --git a/pkg/sdkserver/types.go b/pkg/sdkserver/types.go index 6f940c8b..278a8c78 100644 --- a/pkg/sdkserver/types.go +++ b/pkg/sdkserver/types.go @@ -1,8 +1,8 @@ package sdkserver import ( + "encoding/json" "maps" - "strings" "time" "github.com/gptscript-ai/gptscript/pkg/cache" @@ -30,15 +30,12 @@ const ( type toolDefs []types.ToolDef func (t toolDefs) String() string { - s := new(strings.Builder) - for i, tool := range t { - s.WriteString(tool.String()) - if i != len(t)-1 { - s.WriteString("\n\n---\n\n") - } + data, err := json.Marshal(t) + if err != nil { + panic(err) } - return s.String() + return "#!GPTSCRIPT" + string(data) } type ( @@ -52,15 +49,18 @@ type toolOrFileRequest struct { cacheOptions `json:",inline"` openAIOptions `json:",inline"` - ToolDefs toolDefs `json:"toolDefs,inline"` - SubTool string `json:"subTool"` - Input string `json:"input"` - ChatState string `json:"chatState"` - Workspace string `json:"workspace"` - Env []string `json:"env"` - CredentialContext string `json:"credentialContext"` - CredentialOverrides []string `json:"credentialOverrides"` - Confirm bool `json:"confirm"` + ToolDefs toolDefs `json:"toolDefs,inline"` + SubTool string `json:"subTool"` + Input string `json:"input"` + ChatState string `json:"chatState"` + Workspace string `json:"workspace"` + Env []string `json:"env"` + CredentialContexts []string `json:"credentialContexts"` + CredentialOverrides []string `json:"credentialOverrides"` + Confirm bool `json:"confirm"` + Location string `json:"location,omitempty"` + ForceSequential bool `json:"forceSequential"` + DefaultModelProvider string `json:"DefaultModelProvider,omitempty"` } type content struct { @@ -79,15 +79,27 @@ func (f *file) String() string { return f.File } +type loadRequest struct { + content `json:",inline"` + + ToolDefs toolDefs `json:"toolDefs,inline"` + DisableCache bool `json:"disableCache"` + SubTool string `json:"subTool,omitempty"` + File string `json:"file"` +} + type parseRequest struct { parser.Options `json:",inline"` content `json:",inline"` - File string `json:"file"` + DisableCache bool `json:"disableCache"` + File string `json:"file"` } type modelsRequest struct { - Providers []string `json:"providers"` + Providers []string `json:"providers"` + Env []string `json:"env"` + CredentialOverrides []string `json:"credentialOverrides"` } type runInfo struct { @@ -129,6 +141,7 @@ func (r *runInfo) process(e event) map[string]any { r.Start = e.Time r.Program = *e.Program r.State = Running + r.Input = e.Input case runner.EventTypeRunFinish: r.End = e.Time r.Output = e.Output @@ -152,9 +165,14 @@ func (r *runInfo) process(e event) map[string]any { call.Type = e.Type switch e.Type { - case runner.EventTypeCallStart: + case runner.EventTypeCallStart, runner.EventTypeCallContinue: call.Start = e.Time - call.Input = e.Content + if e.Content != "" { + call.Input = e.Content + } + if e.ToolResults > 0 { + call.ToolResults = e.ToolResults + } case runner.EventTypeCallSubCalls: call.setSubCalls(e.ToolSubCalls) @@ -167,6 +185,8 @@ func (r *runInfo) process(e event) map[string]any { call.setOutput(e.Content) case runner.EventTypeChat: + call.Usage = e.Usage + call.ChatResponseCached = e.ChatResponseCached if e.ChatRequest != nil { call.LLMRequest = e.ChatRequest } @@ -192,14 +212,16 @@ func (r *runInfo) processStdout(cs runner.ChatResponse) { type call struct { engine.CallContext `json:",inline"` - Type runner.EventType `json:"type"` - Start time.Time `json:"start"` - End time.Time `json:"end"` - Input string `json:"input"` - Output []output `json:"output"` - Usage types.Usage `json:"usage"` - LLMRequest any `json:"llmRequest"` - LLMResponse any `json:"llmResponse"` + Type runner.EventType `json:"type"` + Start time.Time `json:"start"` + End time.Time `json:"end"` + Input string `json:"input"` + Output []output `json:"output"` + Usage types.Usage `json:"usage"` + ChatResponseCached bool `json:"chatResponseCached"` + ToolResults int `json:"toolResults"` + LLMRequest any `json:"llmRequest"` + LLMResponse any `json:"llmResponse"` } func (c *call) setSubCalls(subCalls map[string]engine.Call) { @@ -234,3 +256,10 @@ type prompt struct { Type runner.EventType `json:"type,omitempty"` Time time.Time `json:"time,omitempty"` } + +type credentialsRequest struct { + content `json:",inline"` + AllContexts bool `json:"allContexts"` + Context []string `json:"context"` + Name string `json:"name"` +} diff --git a/pkg/sdkserver/workspaces.go b/pkg/sdkserver/workspaces.go new file mode 100644 index 00000000..f1846051 --- /dev/null +++ b/pkg/sdkserver/workspaces.go @@ -0,0 +1,484 @@ +package sdkserver + +import ( + "encoding/json" + "fmt" + "net/http" + + gcontext "github.com/gptscript-ai/gptscript/pkg/context" + "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/runner" +) + +func (s *server) getWorkspaceTool(req workspaceCommonRequest) string { + if req.WorkspaceTool != "" { + return req.WorkspaceTool + } + + return s.workspaceTool +} + +type workspaceCommonRequest struct { + ID string `json:"id"` + WorkspaceTool string `json:"workspaceTool"` + Env []string `json:"env"` +} + +type createWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` + ProviderType string `json:"providerType"` + FromWorkspaceIDs []string `json:"fromWorkspaceIDs"` +} + +func (s *server) getServerToolsEnv(env []string) []string { + return append(s.serverToolsEnv, env...) +} + +func (s *server) createWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject createWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Create Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + if reqObject.ProviderType == "" { + reqObject.ProviderType = "directory" + } + + b, err := json.Marshal(map[string]any{ + "provider": reqObject.ProviderType, + "fromWorkspaceIDs": reqObject.FromWorkspaceIDs, + }) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to marshal request body: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + string(b), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type deleteWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` +} + +func (s *server) deleteWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject deleteWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Delete Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s"}`, + reqObject.ID, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type listWorkspaceContentsRequest struct { + workspaceCommonRequest `json:",inline"` + ID string `json:"id"` + Prefix string `json:"prefix"` +} + +func (s *server) listWorkspaceContents(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject listWorkspaceContentsRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "List Workspace Contents", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "ls_prefix": "%s"}`, + reqObject.ID, reqObject.Prefix, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type removeAllWithPrefixInWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` + Prefix string `json:"prefix"` +} + +func (s *server) removeAllWithPrefixInWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject removeAllWithPrefixInWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Remove All With Prefix In Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "prefix": "%s"}`, + reqObject.ID, reqObject.Prefix, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type writeFileInWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` + FilePath string `json:"filePath"` + Contents string `json:"contents"` + CreateRevision *bool `json:"createRevision"` + LatestRevisionID string `json:"latestRevisionID"` +} + +func (s *server) writeFileInWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject writeFileInWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Write File In Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "file_path": "%s", "body": "%s", "create_revision": %t, "latest_revision_id": "%s"}`, + reqObject.ID, reqObject.FilePath, reqObject.Contents, reqObject.CreateRevision == nil || *reqObject.CreateRevision, reqObject.LatestRevisionID, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type rmFileInWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` + FilePath string `json:"filePath"` +} + +func (s *server) removeFileInWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject rmFileInWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Remove File In Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "file_path": "%s"}`, + reqObject.ID, reqObject.FilePath, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type readFileInWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` + FilePath string `json:"filePath"` +} + +func (s *server) readFileInWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject readFileInWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Read File In Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "file_path": "%s"}`, + reqObject.ID, reqObject.FilePath, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +func (s *server) readFileWithRevisionInWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject readFileInWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Read File With Revision In Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "file_path": "%s", "with_latest_revision_id": "true"}`, + reqObject.ID, reqObject.FilePath, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type statFileInWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` + FilePath string `json:"filePath"` + WithLatestRevisionID bool `json:"withLatestRevisionID"` +} + +func (s *server) statFileInWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject statFileInWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Stat File In Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "file_path": "%s", "with_latest_revision_id": "%v"}`, + reqObject.ID, reqObject.FilePath, reqObject.WithLatestRevisionID, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type listRevisionsRequest struct { + workspaceCommonRequest `json:",inline"` + FilePath string `json:"filePath"` +} + +func (s *server) listRevisions(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject listRevisionsRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "List Revisions for File in Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "file_path": "%s"}`, + reqObject.ID, reqObject.FilePath, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type getRevisionForFileInWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` + FilePath string `json:"filePath"` + RevisionID string `json:"revisionID"` +} + +func (s *server) getRevisionForFileInWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject getRevisionForFileInWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Get a Revision for File in Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "file_path": "%s", "revision_id": "%s"}`, + reqObject.ID, reqObject.FilePath, reqObject.RevisionID, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} + +type deleteRevisionForFileInWorkspaceRequest struct { + workspaceCommonRequest `json:",inline"` + FilePath string `json:"filePath"` + RevisionID string `json:"revisionID"` +} + +func (s *server) deleteRevisionForFileInWorkspace(w http.ResponseWriter, r *http.Request) { + logger := gcontext.GetLogger(r.Context()) + var reqObject deleteRevisionForFileInWorkspaceRequest + if err := json.NewDecoder(r.Body).Decode(&reqObject); err != nil { + writeError(logger, w, http.StatusBadRequest, fmt.Errorf("invalid request body: %w", err)) + return + } + + prg, err := loader.Program(r.Context(), s.getWorkspaceTool(reqObject.workspaceCommonRequest), "Delete a Revision for File in Workspace", loader.Options{Cache: s.client.Cache}) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to load program: %w", err)) + return + } + + out, err := s.client.Run( + r.Context(), + prg, + s.getServerToolsEnv(reqObject.Env), + fmt.Sprintf( + `{"workspace_id": "%s", "file_path": "%s", "revision_id": "%s"}`, + reqObject.ID, reqObject.FilePath, reqObject.RevisionID, + ), + runner.RunOptions{}, + ) + if err != nil { + writeError(logger, w, http.StatusInternalServerError, fmt.Errorf("failed to run program: %w", err)) + return + } + + writeResponse(logger, w, map[string]any{"stdout": out}) +} diff --git a/pkg/server/server.go b/pkg/server/server.go index d1c57100..00734c38 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -24,5 +24,6 @@ func ContextWithNewRunID(ctx context.Context) context.Context { } func RunIDFromContext(ctx context.Context) string { - return ctx.Value(execKey{}).(string) + runID, _ := ctx.Value(execKey{}).(string) + return runID } diff --git a/pkg/system/prompt.go b/pkg/system/prompt.go index 6b1815fd..04497854 100644 --- a/pkg/system/prompt.go +++ b/pkg/system/prompt.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/getkin/kin-openapi/openapi3" + "github.com/modelcontextprotocol/go-sdk/jsonschema" ) // Suffix is default suffix of gptscript files @@ -26,26 +26,22 @@ You don't move to the next step until you have a result. // to just send pure text but the interface required JSON (as that is the fundamental interface of tools in OpenAI) var DefaultPromptParameter = "defaultPromptParameter" -var DefaultToolSchema = openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - DefaultPromptParameter: &openapi3.SchemaRef{ - Value: &openapi3.Schema{ - Description: "Prompt to send to the tool. This may be an instruction or question.", - Type: &openapi3.Types{"string"}, - }, +var DefaultToolSchema = jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + DefaultPromptParameter: { + Description: "Prompt to send to the tool. This may be an instruction or question.", + Type: "string", }, }, } -var DefaultChatSchema = openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - DefaultPromptParameter: &openapi3.SchemaRef{ - Value: &openapi3.Schema{ - Description: "Prompt to send to the assistant. This may be an instruction or question.", - Type: &openapi3.Types{"string"}, - }, +var DefaultChatSchema = jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + DefaultPromptParameter: { + Description: "Prompt to send to the assistant. This may be an instruction or question.", + Type: "string", }, }, } diff --git a/pkg/tests/judge/judge.go b/pkg/tests/judge/judge.go index eae12c2e..26464386 100644 --- a/pkg/tests/judge/judge.go +++ b/pkg/tests/judge/judge.go @@ -40,6 +40,8 @@ After making a determination, respond with a JSON object that conforms to the fo ] } +If you determine actual and expected are not equivalent, include a diff of the parts of actual and expected that are not equivalent in the reasoning field of your response. + Your responses are concise and include only the json object described above. ` @@ -84,10 +86,10 @@ func New[T any](client *openai.Client) (*Judge[T], error) { } func (j *Judge[T]) Equal(ctx context.Context, expected, actual T, criteria string) (equal bool, reasoning string, err error) { - comparisonJSON, err := json.MarshalIndent(&comparison[T]{ + comparisonJSON, err := json.Marshal(&comparison[T]{ Expected: expected, Actual: actual, - }, "", " ") + }) if err != nil { return false, "", fmt.Errorf("failed to marshal judge testcase JSON: %w", err) } @@ -110,7 +112,7 @@ func (j *Judge[T]) Equal(ctx context.Context, expected, actual T, criteria strin }, }, } - response, err := j.client.CreateChatCompletion(ctx, request) + response, err := j.client.CreateChatCompletion(ctx, request, nil) if err != nil { return false, "", fmt.Errorf("failed to create chat completion request: %w", err) } diff --git a/pkg/tests/runner2_test.go b/pkg/tests/runner2_test.go new file mode 100644 index 00000000..9668a98a --- /dev/null +++ b/pkg/tests/runner2_test.go @@ -0,0 +1,562 @@ +package tests + +import ( + "context" + "encoding/json" + "runtime" + "testing" + + "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/runner" + "github.com/gptscript-ai/gptscript/pkg/tests/tester" + "github.com/gptscript-ai/gptscript/pkg/types" + "github.com/hexops/autogold/v2" + "github.com/stretchr/testify/require" +) + +func TestContextWithAsterick(t *testing.T) { + r := tester.NewRunner(t) + prg, err := loader.ProgramFromSource(context.Background(), ` +chat: true +context: foo with * + +Say hi + +--- +name: foo + +#!/bin/bash + +echo This is the input: ${GPTSCRIPT_INPUT} +`, "") + require.NoError(t, err) + + resp, err := r.Chat(context.Background(), nil, prg, nil, "input 1", runner.RunOptions{}) + r.AssertStep(t, resp, err) + + resp, err = r.Chat(context.Background(), resp.State, prg, nil, "input 2", runner.RunOptions{}) + r.AssertStep(t, resp, err) +} + +func TestContextShareBug(t *testing.T) { + r := tester.NewRunner(t) + prg, err := loader.ProgramFromSource(context.Background(), ` +chat: true +tools: sharecontext + +Say hi + +--- +name: sharecontext +share context: realcontext +--- +name: realcontext + +#!sys.echo + +Yo dawg`, "") + require.NoError(t, err) + + resp, err := r.Chat(context.Background(), nil, prg, nil, "input 1", runner.RunOptions{}) + r.AssertStep(t, resp, err) +} + +func TestInputFilterMoreArgs(t *testing.T) { + r := tester.NewRunner(t) + prg, err := loader.ProgramFromSource(context.Background(), ` +chat: true +inputfilters: stuff + +Say hi + +--- +name: stuff +params: foo: bar +params: input: baz + +#!/bin/bash +echo ${FOO}:${INPUT} +`, "") + require.NoError(t, err) + + resp, err := r.Chat(context.Background(), nil, prg, nil, `{"foo":"123"}`, runner.RunOptions{}) + r.AssertStep(t, resp, err) + resp, err = r.Chat(context.Background(), nil, prg, nil, `"foo":"123"}`, runner.RunOptions{}) + r.AssertStep(t, resp, err) +} + +func TestShareCreds(t *testing.T) { + r := tester.NewRunner(t) + prg, err := loader.ProgramFromSource(context.Background(), ` +creds: foo + +#!/bin/bash +echo $CRED +echo $CRED2 + +--- +name: foo +share credentials: bar + +--- +name: bar +share credentials: baz + +#!/bin/bash +echo '{"env": {"CRED": "that worked"}}' + +--- +name: baz + +#!/bin/bash +echo '{"env": {"CRED2": "that also worked"}}' +`, "") + require.NoError(t, err) + + resp, err := r.Chat(context.Background(), nil, prg, nil, "", runner.RunOptions{}) + r.AssertStep(t, resp, err) +} + +func TestFilterArgs(t *testing.T) { + r := tester.NewRunner(t) + prg, err := loader.ProgramFromSource(context.Background(), ` +inputfilters: input with ${Foo} +inputfilters: input with foo +inputfilters: input with * +outputfilters: output with * +outputfilters: output with foo +outputfilters: output with ${Foo} +params: Foo: a description + +#!/bin/bash +echo ${FOO} + +--- +name: input +params: notfoo: a description + +#!/bin/bash +echo "${GPTSCRIPT_INPUT}" + +--- +name: output +params: notfoo: a description + +#!/bin/bash +echo "${GPTSCRIPT_INPUT}" +`, "") + require.NoError(t, err) + + resp, err := r.Chat(context.Background(), nil, prg, nil, `{"foo":"baz", "start": true}`, runner.RunOptions{}) + r.AssertStep(t, resp, err) + + data := map[string]any{} + err = json.Unmarshal([]byte(resp.Content), &data) + require.NoError(t, err) + + autogold.Expect(map[string]interface{}{ + "chat": false, + "continuation": false, + "notfoo": "baz", + "output": `{"chat":false,"continuation":false,"notfoo":"foo","output":"{\"chat\":false,\"continuation\":false,\"foo\":\"baz\",\"input\":\"{\\\"foo\\\":\\\"baz\\\",\\\"input\\\":\\\"{\\\\\\\"foo\\\\\\\":\\\\\\\"baz\\\\\\\", \\\\\\\"start\\\\\\\": true}\\\",\\\"notfoo\\\":\\\"baz\\\",\\\"start\\\":true}\\n\",\"notfoo\":\"foo\",\"output\":\"baz\\n\",\"start\":true}\n"} +`, + }).Equal(t, data) + + val := data["output"].(string) + data = map[string]any{} + err = json.Unmarshal([]byte(val), &data) + require.NoError(t, err) + autogold.Expect(map[string]interface{}{ + "chat": false, + "continuation": false, + "notfoo": "foo", + "output": `{"chat":false,"continuation":false,"foo":"baz","input":"{\"foo\":\"baz\",\"input\":\"{\\\"foo\\\":\\\"baz\\\", \\\"start\\\": true}\",\"notfoo\":\"baz\",\"start\":true}\n","notfoo":"foo","output":"baz\n","start":true} +`, + }).Equal(t, data) + + val = data["output"].(string) + data = map[string]any{} + err = json.Unmarshal([]byte(val), &data) + require.NoError(t, err) + autogold.Expect(map[string]interface{}{ + "chat": false, + "continuation": false, + "foo": "baz", "input": `{"foo":"baz","input":"{\"foo\":\"baz\", \"start\": true}","notfoo":"baz","start":true} +`, + "notfoo": "foo", + "output": "baz\n", + "start": true, + }).Equal(t, data) + + val = data["input"].(string) + data = map[string]any{} + err = json.Unmarshal([]byte(val), &data) + require.NoError(t, err) + autogold.Expect(map[string]interface{}{ + "foo": "baz", + "input": `{"foo":"baz", "start": true}`, + "notfoo": "baz", + "start": true, + }).Equal(t, data) + + val = data["input"].(string) + data = map[string]any{} + err = json.Unmarshal([]byte(val), &data) + require.NoError(t, err) + autogold.Expect(map[string]interface{}{"foo": "baz", "start": true}).Equal(t, data) +} + +func TestMCPLoad(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping test on Windows") + } + + r := tester.NewRunner(t) + prg, err := loader.ProgramFromSource(t.Context(), ` +name: mcp + +#!mcp + +{ + "mcpServers": { + "sqlite": { + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "-v", + "mcp-test:/mcp", + "mcp/sqlite@sha256:007ccae941a6f6db15b26ee41d92edda50ce157176d9273449e8b3f51d979c70", + "--db-path", + "/mcp/test.db" + ] + } + } +} +`, "") + require.NoError(t, err) + + autogold.Expect(types.Tool{ + ToolDef: types.ToolDef{ + Parameters: types.Parameters{ + Name: "mcp", + Description: "sqlite", + ModelName: "gpt-4o", + Export: []string{ + "read_query", + "write_query", + "create_table", + "list_tables", + "describe_table", + "append_insight", + }, + }, + MetaData: map[string]string{"bundle": "true"}, + }, + ID: "inline:mcp", + ToolMapping: map[string][]types.ToolReference{ + "append_insight": {{ + Reference: "append_insight", + ToolID: "inline:append_insight", + }}, + "create_table": {{ + Reference: "create_table", + ToolID: "inline:create_table", + }}, + "describe_table": {{ + Reference: "describe_table", + ToolID: "inline:describe_table", + }}, + "list_tables": {{ + Reference: "list_tables", + ToolID: "inline:list_tables", + }}, + "read_query": {{ + Reference: "read_query", + ToolID: "inline:read_query", + }}, + "write_query": {{ + Reference: "write_query", + ToolID: "inline:write_query", + }}, + }, + LocalTools: map[string]string{ + "append_insight": "inline:append_insight", + "create_table": "inline:create_table", + "describe_table": "inline:describe_table", + "list_tables": "inline:list_tables", + "mcp": "inline:mcp", + "read_query": "inline:read_query", + "write_query": "inline:write_query", + }, + Source: types.ToolSource{Location: "inline"}, + WorkingDir: ".", + }).Equal(t, prg.ToolSet[prg.EntryToolID]) + autogold.Expect(7).Equal(t, len(prg.ToolSet[prg.EntryToolID].LocalTools)) + data, _ := json.MarshalIndent(prg.ToolSet, "", " ") + autogold.Expect(`{ + "inline:append_insight": { + "name": "append_insight", + "description": "Add a business insight to the memo", + "modelName": "gpt-4o", + "internalPrompt": null, + "arguments": { + "type": "object", + "required": [ + "insight" + ], + "properties": { + "insight": { + "type": "string", + "description": "Business insight discovered from data analysis" + } + } + }, + "instructions": "#!sys.mcp.invoke.append_insight e592cc0c9483290685611ba70bd8595829cc794f7eae0419eabb3388bf0d3529", + "id": "inline:append_insight", + "localTools": { + "append_insight": "inline:append_insight", + "create_table": "inline:create_table", + "describe_table": "inline:describe_table", + "list_tables": "inline:list_tables", + "mcp": "inline:mcp", + "read_query": "inline:read_query", + "write_query": "inline:write_query" + }, + "source": { + "location": "inline" + }, + "workingDir": "." + }, + "inline:create_table": { + "name": "create_table", + "description": "Create a new table in the SQLite database", + "modelName": "gpt-4o", + "internalPrompt": null, + "arguments": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "description": "CREATE TABLE SQL statement" + } + } + }, + "instructions": "#!sys.mcp.invoke.create_table e592cc0c9483290685611ba70bd8595829cc794f7eae0419eabb3388bf0d3529", + "id": "inline:create_table", + "localTools": { + "append_insight": "inline:append_insight", + "create_table": "inline:create_table", + "describe_table": "inline:describe_table", + "list_tables": "inline:list_tables", + "mcp": "inline:mcp", + "read_query": "inline:read_query", + "write_query": "inline:write_query" + }, + "source": { + "location": "inline" + }, + "workingDir": "." + }, + "inline:describe_table": { + "name": "describe_table", + "description": "Get the schema information for a specific table", + "modelName": "gpt-4o", + "internalPrompt": null, + "arguments": { + "type": "object", + "required": [ + "table_name" + ], + "properties": { + "table_name": { + "type": "string", + "description": "Name of the table to describe" + } + } + }, + "instructions": "#!sys.mcp.invoke.describe_table e592cc0c9483290685611ba70bd8595829cc794f7eae0419eabb3388bf0d3529", + "id": "inline:describe_table", + "localTools": { + "append_insight": "inline:append_insight", + "create_table": "inline:create_table", + "describe_table": "inline:describe_table", + "list_tables": "inline:list_tables", + "mcp": "inline:mcp", + "read_query": "inline:read_query", + "write_query": "inline:write_query" + }, + "source": { + "location": "inline" + }, + "workingDir": "." + }, + "inline:list_tables": { + "name": "list_tables", + "description": "List all tables in the SQLite database", + "modelName": "gpt-4o", + "internalPrompt": null, + "arguments": { + "type": "object" + }, + "instructions": "#!sys.mcp.invoke.list_tables e592cc0c9483290685611ba70bd8595829cc794f7eae0419eabb3388bf0d3529", + "id": "inline:list_tables", + "localTools": { + "append_insight": "inline:append_insight", + "create_table": "inline:create_table", + "describe_table": "inline:describe_table", + "list_tables": "inline:list_tables", + "mcp": "inline:mcp", + "read_query": "inline:read_query", + "write_query": "inline:write_query" + }, + "source": { + "location": "inline" + }, + "workingDir": "." + }, + "inline:mcp": { + "name": "mcp", + "description": "sqlite", + "modelName": "gpt-4o", + "internalPrompt": null, + "export": [ + "read_query", + "write_query", + "create_table", + "list_tables", + "describe_table", + "append_insight" + ], + "metaData": { + "bundle": "true" + }, + "id": "inline:mcp", + "toolMapping": { + "append_insight": [ + { + "reference": "append_insight", + "toolID": "inline:append_insight" + } + ], + "create_table": [ + { + "reference": "create_table", + "toolID": "inline:create_table" + } + ], + "describe_table": [ + { + "reference": "describe_table", + "toolID": "inline:describe_table" + } + ], + "list_tables": [ + { + "reference": "list_tables", + "toolID": "inline:list_tables" + } + ], + "read_query": [ + { + "reference": "read_query", + "toolID": "inline:read_query" + } + ], + "write_query": [ + { + "reference": "write_query", + "toolID": "inline:write_query" + } + ] + }, + "localTools": { + "append_insight": "inline:append_insight", + "create_table": "inline:create_table", + "describe_table": "inline:describe_table", + "list_tables": "inline:list_tables", + "mcp": "inline:mcp", + "read_query": "inline:read_query", + "write_query": "inline:write_query" + }, + "source": { + "location": "inline" + }, + "workingDir": "." + }, + "inline:read_query": { + "name": "read_query", + "description": "Execute a SELECT query on the SQLite database", + "modelName": "gpt-4o", + "internalPrompt": null, + "arguments": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "description": "SELECT SQL query to execute" + } + } + }, + "instructions": "#!sys.mcp.invoke.read_query e592cc0c9483290685611ba70bd8595829cc794f7eae0419eabb3388bf0d3529", + "id": "inline:read_query", + "localTools": { + "append_insight": "inline:append_insight", + "create_table": "inline:create_table", + "describe_table": "inline:describe_table", + "list_tables": "inline:list_tables", + "mcp": "inline:mcp", + "read_query": "inline:read_query", + "write_query": "inline:write_query" + }, + "source": { + "location": "inline" + }, + "workingDir": "." + }, + "inline:write_query": { + "name": "write_query", + "description": "Execute an INSERT, UPDATE, or DELETE query on the SQLite database", + "modelName": "gpt-4o", + "internalPrompt": null, + "arguments": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "description": "SQL query to execute" + } + } + }, + "instructions": "#!sys.mcp.invoke.write_query e592cc0c9483290685611ba70bd8595829cc794f7eae0419eabb3388bf0d3529", + "id": "inline:write_query", + "localTools": { + "append_insight": "inline:append_insight", + "create_table": "inline:create_table", + "describe_table": "inline:describe_table", + "list_tables": "inline:list_tables", + "mcp": "inline:mcp", + "read_query": "inline:read_query", + "write_query": "inline:write_query" + }, + "source": { + "location": "inline" + }, + "workingDir": "." + } +}`).Equal(t, string(data)) + + prg.EntryToolID = prg.ToolSet[prg.EntryToolID].LocalTools["read_query"] + resp, err := r.Chat(context.Background(), nil, prg, nil, `{"query": "SELECT 1"}`, runner.RunOptions{}) + r.AssertStep(t, resp, err) +} diff --git a/pkg/tests/runner_test.go b/pkg/tests/runner_test.go index 6efe4a11..bda3a5b6 100644 --- a/pkg/tests/runner_test.go +++ b/pkg/tests/runner_test.go @@ -1,13 +1,19 @@ package tests import ( + "bytes" + "compress/gzip" "context" "encoding/json" + "io" "os" + "os/exec" "runtime" "testing" "github.com/gptscript-ai/gptscript/pkg/engine" + "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/runner" "github.com/gptscript-ai/gptscript/pkg/tests/tester" "github.com/gptscript-ai/gptscript/pkg/types" "github.com/hexops/autogold/v2" @@ -22,6 +28,17 @@ func toJSONString(t *testing.T, v interface{}) string { return string(x) } +func TestMain(m *testing.M) { + cmd := exec.CommandContext(context.Background(), "go", "build", "-o", "bin/gptscript", "../../main.go") + if err := cmd.Run(); err != nil { + panic(err) + } + + os.Setenv("NANOBOT_BIN", "bin/gptscript") + defer os.Unsetenv("NANOBOT_BIN") + m.Run() +} + func TestAsterick(t *testing.T) { r := tester.NewRunner(t) p, err := r.Load("") @@ -139,7 +156,7 @@ func TestDualSubChat(t *testing.T) { prg, err := r.Load("") require.NoError(t, err) - resp, err := r.Chat(context.Background(), nil, prg, os.Environ(), "User 1") + resp, err := r.Chat(context.Background(), nil, prg, os.Environ(), "User 1", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) @@ -153,7 +170,7 @@ func TestDualSubChat(t *testing.T) { }, }) - resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 2") + resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 2", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) @@ -164,7 +181,7 @@ func TestDualSubChat(t *testing.T) { Text: "Assistant 3", }) - resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 3") + resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 3", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) @@ -180,7 +197,7 @@ func TestDualSubChat(t *testing.T) { Text: "And we're done", }) - resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 4") + resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 4", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.True(t, resp.Done) @@ -209,82 +226,8 @@ func TestContextSubChat(t *testing.T) { prg, err := r.Load("") require.NoError(t, err) - resp, err := r.Chat(context.Background(), nil, prg, os.Environ(), "User 1") - require.NoError(t, err) - r.AssertResponded(t) - assert.False(t, resp.Done) - autogold.Expect("Assistant Response 1 - from chatbot1").Equal(t, resp.Content) - autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step1")) - - r.RespondWith(tester.Result{ - Content: []types.ContentPart{ - { - ToolCall: &types.CompletionToolCall{ - ID: "call_2", - Function: types.CompletionFunctionCall{ - Name: types.ToolNormalizer("sys.chat.finish"), - Arguments: "Response from context chatbot", - }, - }, - }, - }, - }, tester.Result{ - Text: "Assistant Response 2 - from context tool", - }, tester.Result{ - Text: "Assistant Response 3 - from main chat tool", - }) - resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 2") - require.NoError(t, err) - r.AssertResponded(t) - assert.False(t, resp.Done) - autogold.Expect("Assistant Response 3 - from main chat tool").Equal(t, resp.Content) - autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step2")) - - r.RespondWith(tester.Result{ - Content: []types.ContentPart{ - { - ToolCall: &types.CompletionToolCall{ - ID: "call_3", - Function: types.CompletionFunctionCall{ - Name: "chatbot", - Arguments: "Input to chatbot1 on resume", - }, - }, - }, - }, - }, tester.Result{ - Text: "Assistant Response 4 - from chatbot1", - }) - resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 3") - require.NoError(t, err) - r.AssertResponded(t) - assert.False(t, resp.Done) - autogold.Expect("Assistant Response 3 - from main chat tool").Equal(t, resp.Content) - autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step3")) - - r.RespondWith(tester.Result{ - Content: []types.ContentPart{ - { - ToolCall: &types.CompletionToolCall{ - ID: "call_4", - Function: types.CompletionFunctionCall{ - Name: types.ToolNormalizer("sys.chat.finish"), - Arguments: "Response from context chatbot after resume", - }, - }, - }, - }, - }, tester.Result{ - Text: "Assistant Response 5 - from context tool resume", - }, tester.Result{ - Text: "Assistant Response 6 - from main chat tool resume", - }) - resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 4") - require.NoError(t, err) - r.AssertResponded(t) - assert.False(t, resp.Done) - autogold.Expect("Assistant Response 6 - from main chat tool resume").Equal(t, resp.Content) - autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step4")) + _, err = r.Chat(context.Background(), nil, prg, os.Environ(), "User 1", runner.RunOptions{}) + autogold.Expect("invalid state: context tool [testdata/TestContextSubChat/test.gpt:subtool] can not result in a continuation").Equal(t, err.Error()) } func TestSubChat(t *testing.T) { @@ -302,7 +245,7 @@ func TestSubChat(t *testing.T) { prg, err := r.Load("") require.NoError(t, err) - resp, err := r.Chat(context.Background(), nil, prg, os.Environ(), "Hello") + resp, err := r.Chat(context.Background(), nil, prg, os.Environ(), "Hello", runner.RunOptions{}) require.NoError(t, err) autogold.Expect(`{ @@ -321,13 +264,13 @@ func TestSubChat(t *testing.T) { "toolID": "testdata/TestSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -427,7 +370,7 @@ func TestSubChat(t *testing.T) { } }`).Equal(t, toJSONString(t, resp)) - resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 1") + resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 1", runner.RunOptions{}) require.NoError(t, err) autogold.Expect(`{ @@ -446,13 +389,13 @@ func TestSubChat(t *testing.T) { "toolID": "testdata/TestSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -582,7 +525,7 @@ func TestChat(t *testing.T) { prg, err := r.Load("") require.NoError(t, err) - resp, err := r.Chat(context.Background(), nil, prg, os.Environ(), "Hello") + resp, err := r.Chat(context.Background(), nil, prg, os.Environ(), "Hello", runner.RunOptions{}) require.NoError(t, err) autogold.Expect(`{ @@ -634,7 +577,7 @@ func TestChat(t *testing.T) { } }`).Equal(t, toJSONString(t, resp)) - resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 1") + resp, err = r.Chat(context.Background(), resp.State, prg, os.Environ(), "User 1", runner.RunOptions{}) require.NoError(t, err) autogold.Expect(`{ @@ -748,9 +691,6 @@ func TestGlobalErr(t *testing.T) { } func TestContextArg(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } runner := tester.NewRunner(t) x, err := runner.Run("", `{ "file": "foo.db" @@ -800,6 +740,26 @@ func TestExport(t *testing.T) { assert.Equal(t, "TEST RESULT CALL: 3", x) } +func TestAgentOnly(t *testing.T) { + r := tester.NewRunner(t) + + prg, err := r.Load("") + require.NoError(t, err) + + r.RespondWith(tester.Result{ + Func: types.CompletionFunctionCall{ + Name: "agent2", + Arguments: "Agent 2 input", + }, + }) + + resp, err := r.Chat(context.Background(), nil, prg, nil, "Input 1", runner.RunOptions{}) + require.NoError(t, err) + r.AssertResponded(t) + assert.False(t, resp.Done) + autogold.Expect("TEST RESULT CALL: 2").Equal(t, resp.Content) + autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step1")) +} func TestAgents(t *testing.T) { r := tester.NewRunner(t) @@ -820,7 +780,7 @@ func TestAgents(t *testing.T) { }, }) - resp, err := r.Chat(context.Background(), nil, prg, nil, "Input 1") + resp, err := r.Chat(context.Background(), nil, prg, nil, "Input 1", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) @@ -838,14 +798,14 @@ func TestInput(t *testing.T) { prg, err := r.Load("") require.NoError(t, err) - resp, err := r.Chat(context.Background(), nil, prg, nil, "You're stupid") + resp, err := r.Chat(context.Background(), nil, prg, nil, "You're stupid", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) autogold.Expect("TEST RESULT CALL: 1").Equal(t, resp.Content) autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step1")) - resp, err = r.Chat(context.Background(), resp.State, prg, nil, "You're ugly") + resp, err = r.Chat(context.Background(), resp.State, prg, nil, "You're ugly", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) @@ -866,7 +826,7 @@ func TestOutput(t *testing.T) { prg, err := r.Load("") require.NoError(t, err) - resp, err := r.Chat(context.Background(), nil, prg, nil, "Input 1") + resp, err := r.Chat(context.Background(), nil, prg, nil, "Input 1", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) @@ -877,7 +837,7 @@ func TestOutput(t *testing.T) { r.RespondWith(tester.Result{ Text: "Response 2", }) - resp, err = r.Chat(context.Background(), resp.State, prg, nil, "Input 2") + resp, err = r.Chat(context.Background(), resp.State, prg, nil, "Input 2", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) @@ -890,7 +850,7 @@ func TestOutput(t *testing.T) { Message: "Chat Done", }, }) - resp, err = r.Chat(context.Background(), resp.State, prg, nil, "Input 3") + resp, err = r.Chat(context.Background(), resp.State, prg, nil, "Input 3", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.True(t, resp.Done) @@ -899,6 +859,35 @@ func TestOutput(t *testing.T) { autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step3")) } +func TestEnvOverflow(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip() + } + + r := tester.NewRunner(t) + + out := r.RunDefault() + autogold.Expect(`{"_gz":"H4sIAAAAAAAA/+zAgQAAAADCMNb8JQK4wjYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHgAA//+94pKFQBkBAA=="} +`).Equal(t, out) + + data, err := os.ReadFile("testdata/TestEnvOverflow/context.json") + require.NoError(t, err) + + compressed := struct { + Data []byte `json:"_gz"` + }{} + err = json.Unmarshal(data, &compressed) + require.NoError(t, err) + + gunzip, err := gzip.NewReader(bytes.NewReader(compressed.Data)) + require.NoError(t, err) + + content, err := io.ReadAll(gunzip) + require.NoError(t, err) + + autogold.Expect("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Equal(t, string(content)) +} + func TestSysContext(t *testing.T) { if runtime.GOOS == "windows" { t.Skip() @@ -909,7 +898,7 @@ func TestSysContext(t *testing.T) { prg, err := r.Load("") require.NoError(t, err) - resp, err := r.Chat(context.Background(), nil, prg, nil, "input 1") + resp, err := r.Chat(context.Background(), nil, prg, nil, "input 1", runner.RunOptions{}) require.NoError(t, err) r.AssertResponded(t) assert.False(t, resp.Done) @@ -928,3 +917,97 @@ func TestSysContext(t *testing.T) { require.Len(t, context.Call.AgentGroup, 1) assert.Equal(t, context.Call.AgentGroup[0].Named, "iAmSuperman") } + +func TestMissingTool(t *testing.T) { + r := tester.NewRunner(t) + + r.RespondWith(tester.Result{ + Func: types.CompletionFunctionCall{ + Name: "not.bob", + }, + }) + + resp, err := r.Run("", "Input 1") + require.NoError(t, err) + r.AssertResponded(t) + autogold.Expect("TEST RESULT CALL: 2").Equal(t, resp) +} + +func TestToolRefAll(t *testing.T) { + r := tester.NewRunner(t) + r.RunDefault() +} + +func TestRuntimes(t *testing.T) { + r := tester.NewRunner(t) + r.RespondWith(tester.Result{ + Func: types.CompletionFunctionCall{ + Name: "py", + Arguments: "{}", + }, + }, tester.Result{ + Func: types.CompletionFunctionCall{ + Name: "node", + Arguments: "{}", + }, + }, tester.Result{ + Func: types.CompletionFunctionCall{ + Name: "bash", + Arguments: "{}", + }, + }) + r.RunDefault() +} + +func TestRuntimesLocalDev(t *testing.T) { + r := tester.NewRunner(t) + r.RespondWith(tester.Result{ + Func: types.CompletionFunctionCall{ + Name: "py", + Arguments: "{}", + }, + }, tester.Result{ + Func: types.CompletionFunctionCall{ + Name: "node", + Arguments: "{}", + }, + }, tester.Result{ + Func: types.CompletionFunctionCall{ + Name: "bash", + Arguments: "{}", + }, + }) + r.RunDefault() + _ = os.RemoveAll("testdata/TestRuntimesLocalDev/node_modules") + _ = os.RemoveAll("testdata/TestRuntimesLocalDev/package-lock.json") +} + +func TestToolsChange(t *testing.T) { + r := tester.NewRunner(t) + prg, err := loader.ProgramFromSource(context.Background(), ` +chat: true +tools: sys.ls, sys.read, sys.write +`, "") + require.NoError(t, err) + + resp, err := r.Chat(context.Background(), nil, prg, nil, "input 1", runner.RunOptions{}) + require.NoError(t, err) + r.AssertResponded(t) + assert.False(t, resp.Done) + autogold.Expect("TEST RESULT CALL: 1").Equal(t, resp.Content) + autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step1")) + + prg, err = loader.ProgramFromSource(context.Background(), ` +chat: true +temperature: 0.6 +tools: sys.ls, sys.write +`, "") + require.NoError(t, err) + + resp, err = r.Chat(context.Background(), resp.State, prg, nil, "input 2", runner.RunOptions{}) + require.NoError(t, err) + r.AssertResponded(t) + assert.False(t, resp.Done) + autogold.Expect("TEST RESULT CALL: 2").Equal(t, resp.Content) + autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+"/step2")) +} diff --git a/pkg/tests/smoke/smoke_test.go b/pkg/tests/smoke/smoke_test.go index 66374ef1..b7e50b37 100644 --- a/pkg/tests/smoke/smoke_test.go +++ b/pkg/tests/smoke/smoke_test.go @@ -82,8 +82,8 @@ func TestSmoke(t *testing.T) { expectedEvents, actualEvents, ` -- disregard differences in timestamps, generated IDs, natural language verbiage, and event order -- omit callProgress events from the comparision +- disregard differences in event order, timestamps, generated IDs, and natural language verbiage, grammar, and punctuation +- compare events with matching event types - the overall stream of events and set of tools called should roughly match - arguments passed in tool calls should be roughly the same - the final callFinish event should be semantically similar @@ -175,6 +175,11 @@ func getActualEvents(t *testing.T, eventsFile string) []event { var e event require.NoError(t, json.Unmarshal([]byte(line), &e)) + + if e.Type == runner.EventTypeCallProgress { + continue + } + events = append(events, e) } diff --git a/pkg/tests/smoke/testdata/Bob/claude-3-7-sonnet-20250219-expected.json b/pkg/tests/smoke/testdata/Bob/claude-3-7-sonnet-20250219-expected.json new file mode 100644 index 00000000..618c84d5 --- /dev/null +++ b/pkg/tests/smoke/testdata/Bob/claude-3-7-sonnet-20250219-expected.json @@ -0,0 +1,603 @@ +[ + { + "time": "2024-10-14T18:59:12.228692-04:00", + "type": "runStart", + "usage": {} + }, + { + "time": "2024-10-14T18:59:12.229038-04:00", + "callContext": { + "id": "1728946753", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callStart", + "usage": {} + }, + { + "time": "2024-10-14T18:59:13.520962-04:00", + "type": "runStart", + "usage": {} + }, + { + "time": "2024-10-14T18:59:13.521331-04:00", + "callContext": { + "id": "1728946754", + "tool": { + "name": "Anthropic Claude3 Model Provider", + "description": "Model provider for Anthropic hosted Claude3 models", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "modelProvider": true, + "internalPrompt": null, + "credentials": [ + "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env" + ], + "instructions": "#!sys.daemon /usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py", + "id": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt:Anthropic Claude3 Model Provider", + "toolMapping": { + "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env": [ + { + "reference": "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env", + "toolID": "https://raw.githubusercontent.com/gptscript-ai/credential/de2fada1c51a1dbb5c3e9ef268ea6740d1b52f80/tool.gpt:token" + } + ] + }, + "localTools": { + "anthropic claude3 model provider": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt:Anthropic Claude3 Model Provider" + }, + "source": { + "location": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt", + "lineNo": 1, + "repo": { + "VCS": "git", + "Root": "https://github.com/gptscript-ai/claude3-anthropic-provider.git", + "Path": "/", + "Name": "tool.gpt", + "Revision": "ee5c02a9aeca5a1cbffcf569751e37432bfe0344" + } + }, + "workingDir": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344" + }, + "currentAgent": {}, + "inputContext": null, + "toolCategory": "provider", + "displayText": "Running sys.daemon" + }, + "type": "callStart", + "usage": {} + }, + { + "time": "2024-10-14T18:59:14.541348-04:00", + "callContext": { + "id": "1728946754", + "tool": { + "name": "Anthropic Claude3 Model Provider", + "description": "Model provider for Anthropic hosted Claude3 models", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "modelProvider": true, + "internalPrompt": null, + "credentials": [ + "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env" + ], + "instructions": "#!sys.daemon /usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py", + "id": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt:Anthropic Claude3 Model Provider", + "toolMapping": { + "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env": [ + { + "reference": "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env", + "toolID": "https://raw.githubusercontent.com/gptscript-ai/credential/de2fada1c51a1dbb5c3e9ef268ea6740d1b52f80/tool.gpt:token" + } + ] + }, + "localTools": { + "anthropic claude3 model provider": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt:Anthropic Claude3 Model Provider" + }, + "source": { + "location": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt", + "lineNo": 1, + "repo": { + "VCS": "git", + "Root": "https://github.com/gptscript-ai/claude3-anthropic-provider.git", + "Path": "/", + "Name": "tool.gpt", + "Revision": "ee5c02a9aeca5a1cbffcf569751e37432bfe0344" + } + }, + "workingDir": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344" + }, + "currentAgent": {}, + "inputContext": null, + "toolCategory": "provider", + "displayText": "Running sys.daemon" + }, + "type": "callFinish", + "usage": {}, + "content": "http://127.0.0.1:10258" + }, + { + "time": "2024-10-14T18:59:14.541518-04:00", + "type": "runFinish", + "usage": {} + }, + { + "time": "2024-10-14T18:59:14.541566-04:00", + "callContext": { + "id": "1728946753", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946755", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:17.304351-04:00", + "callContext": { + "id": "1728946753", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946755", + "usage": {}, + "chatResponse": { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "toolu_01KtYnAwnQ2cyRieDu98Jopb", + "function": { + "name": "bob", + "arguments": "{\"question\": \"how are you doing\"}" + } + } + } + ], + "usage": {} + } + }, + { + "time": "2024-10-14T18:59:17.304441-04:00", + "callContext": { + "id": "1728946753", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolSubCalls": { + "toolu_01KtYnAwnQ2cyRieDu98Jopb": { + "toolID": "testdata/Bob/test.gpt:bob", + "input": "{\"question\": \"how are you doing\"}" + } + }, + "type": "callSubCalls", + "usage": {} + }, + { + "time": "2024-10-14T18:59:17.304485-04:00", + "callContext": { + "id": "toolu_01KtYnAwnQ2cyRieDu98Jopb", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946753" + }, + "type": "callStart", + "usage": {}, + "content": "{\"question\": \"how are you doing\"}" + }, + { + "time": "2024-10-14T18:59:17.394841-04:00", + "callContext": { + "id": "toolu_01KtYnAwnQ2cyRieDu98Jopb", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946753" + }, + "type": "callChat", + "chatCompletionId": "1728946756", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:18.202926-04:00", + "callContext": { + "id": "toolu_01KtYnAwnQ2cyRieDu98Jopb", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946753" + }, + "type": "callChat", + "chatCompletionId": "1728946756", + "usage": {}, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + } + ], + "usage": {} + } + }, + { + "time": "2024-10-14T18:59:18.202988-04:00", + "callContext": { + "id": "toolu_01KtYnAwnQ2cyRieDu98Jopb", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946753" + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T18:59:18.203022-04:00", + "callContext": { + "id": "1728946753", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolResults": 1, + "type": "callContinue", + "usage": {} + }, + { + "time": "2024-10-14T18:59:18.295164-04:00", + "callContext": { + "id": "1728946753", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946757", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:19.737028-04:00", + "callContext": { + "id": "1728946753", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946757", + "usage": {}, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + } + ], + "usage": {} + } + }, + { + "time": "2024-10-14T18:59:19.737045-04:00", + "callContext": { + "id": "1728946753", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T18:59:19.737061-04:00", + "type": "runFinish", + "usage": {} + } +] diff --git a/pkg/tests/smoke/testdata/Bob/claude-3-opus-20240229-expected.json b/pkg/tests/smoke/testdata/Bob/claude-3-opus-20240229-expected.json deleted file mode 100644 index 1e924928..00000000 --- a/pkg/tests/smoke/testdata/Bob/claude-3-opus-20240229-expected.json +++ /dev/null @@ -1,929 +0,0 @@ -[ - { - "time": "2024-06-28T10:48:16.036954-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-28T10:48:16.037294-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-28T10:48:17.325263-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-28T10:48:17.325584-04:00", - "callContext": { - "id": "1719586098", - "tool": { - "name": "Anthropic Claude3 Model Provider", - "description": "Model provider for Anthropic hosted Claude3 models", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "modelProvider": true, - "internalPrompt": null, - "credentials": [ - "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env" - ], - "instructions": "#!sys.daemon /usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py", - "id": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt:Anthropic Claude3 Model Provider", - "toolMapping": { - "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env": [ - { - "reference": "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env", - "toolID": "https://raw.githubusercontent.com/gptscript-ai/credential/651dfad6e7cf3a385ef408afa93ce522c10f8508/tool.gpt:token" - } - ] - }, - "localTools": { - "anthropic claude3 model provider": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt:Anthropic Claude3 Model Provider" - }, - "source": { - "location": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt", - "lineNo": 1, - "repo": { - "VCS": "git", - "Root": "https://github.com/gptscript-ai/claude3-anthropic-provider.git", - "Path": "/", - "Name": "tool.gpt", - "Revision": "dfd21adc6be9fbda34b79a71e661ac0cfb725548" - } - }, - "workingDir": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548" - }, - "currentAgent": {}, - "inputContext": null, - "toolCategory": "provider", - "displayText": "Running sys.daemon" - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-28T10:48:18.342475-04:00", - "callContext": { - "id": "1719586098", - "tool": { - "name": "Anthropic Claude3 Model Provider", - "description": "Model provider for Anthropic hosted Claude3 models", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "modelProvider": true, - "internalPrompt": null, - "credentials": [ - "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env" - ], - "instructions": "#!sys.daemon /usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py", - "id": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt:Anthropic Claude3 Model Provider", - "toolMapping": { - "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env": [ - { - "reference": "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env", - "toolID": "https://raw.githubusercontent.com/gptscript-ai/credential/651dfad6e7cf3a385ef408afa93ce522c10f8508/tool.gpt:token" - } - ] - }, - "localTools": { - "anthropic claude3 model provider": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt:Anthropic Claude3 Model Provider" - }, - "source": { - "location": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt", - "lineNo": 1, - "repo": { - "VCS": "git", - "Root": "https://github.com/gptscript-ai/claude3-anthropic-provider.git", - "Path": "/", - "Name": "tool.gpt", - "Revision": "dfd21adc6be9fbda34b79a71e661ac0cfb725548" - } - }, - "workingDir": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548" - }, - "currentAgent": {}, - "inputContext": null, - "toolCategory": "provider", - "displayText": "Running sys.daemon" - }, - "type": "callFinish", - "usage": {}, - "content": "http://127.0.0.1:10961" - }, - { - "time": "2024-06-28T10:48:18.342649-04:00", - "type": "runFinish", - "usage": {} - }, - { - "time": "2024-06-28T10:48:18.342718-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1719586099", - "usage": {}, - "chatRequest": { - "model": "claude-3-opus-20240229", - "messages": [ - { - "role": "system", - "content": "Ask Bob \"how are you doing\" and repeat his reply exactly." - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-28T10:48:18.343115-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1719586099", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-28T10:48:21.291497-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1719586099", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\": \"how are you doing\"}" - }, - { - "time": "2024-06-28T10:48:21.291864-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1719586099", - "usage": {}, - "chatResponse": { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "bob", - "function": { - "name": "bob", - "arguments": "{\"question\": \"how are you doing\"}" - } - } - } - ], - "usage": {} - } - }, - { - "time": "2024-06-28T10:48:21.291966-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "toolSubCalls": { - "bob": { - "toolID": "testdata/Bob/test.gpt:bob", - "input": "{\"question\": \"how are you doing\"}" - } - }, - "type": "callSubCalls", - "usage": {} - }, - { - "time": "2024-06-28T10:48:21.291997-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586097" - }, - "type": "callStart", - "usage": {}, - "content": "{\"question\": \"how are you doing\"}" - }, - { - "time": "2024-06-28T10:48:21.50264-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586097" - }, - "type": "callChat", - "chatCompletionId": "1719586100", - "usage": {}, - "chatRequest": { - "model": "claude-3-opus-20240229", - "messages": [ - { - "role": "system", - "content": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"" - }, - { - "role": "user", - "content": "{\"question\": \"how are you doing\"}" - } - ], - "temperature": 0 - } - }, - { - "time": "2024-06-28T10:48:21.503008-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586097" - }, - "type": "callProgress", - "chatCompletionId": "1719586100", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-28T10:48:22.875617-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586097" - }, - "type": "callProgress", - "chatCompletionId": "1719586100", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-28T10:48:22.875964-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586097" - }, - "type": "callChat", - "chatCompletionId": "1719586100", - "usage": {}, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - } - ], - "usage": {} - } - }, - { - "time": "2024-06-28T10:48:22.876034-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586097" - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-28T10:48:22.876064-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "toolResults": 1, - "type": "callContinue", - "usage": {} - }, - { - "time": "2024-06-28T10:48:23.086894-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1719586101", - "usage": {}, - "chatRequest": { - "model": "claude-3-opus-20240229", - "messages": [ - { - "role": "system", - "content": "Ask Bob \"how are you doing\" and repeat his reply exactly." - }, - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": "bob", - "type": "function", - "function": { - "name": "bob", - "arguments": "{\"question\": \"how are you doing\"}" - } - } - ] - }, - { - "role": "tool", - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!", - "name": "bob", - "tool_call_id": "bob" - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-28T10:48:23.087303-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1719586101", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-28T10:48:25.243408-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1719586101", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-28T10:48:25.243765-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1719586101", - "usage": {}, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - } - ], - "usage": {} - } - }, - { - "time": "2024-06-28T10:48:25.243793-04:00", - "callContext": { - "id": "1719586097", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-28T10:48:25.243836-04:00", - "type": "runFinish", - "usage": {} - } -] diff --git a/pkg/tests/smoke/testdata/Bob/gpt-4-turbo-2024-04-09-expected.json b/pkg/tests/smoke/testdata/Bob/gpt-4-turbo-2024-04-09-expected.json deleted file mode 100644 index 01745e39..00000000 --- a/pkg/tests/smoke/testdata/Bob/gpt-4-turbo-2024-04-09-expected.json +++ /dev/null @@ -1,2348 +0,0 @@ -[ - { - "time": "2024-06-20T17:08:06.902669-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-20T17:08:06.902927-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-20T17:08:07.292073-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917688", - "usage": {}, - "chatRequest": { - "model": "gpt-4-turbo-2024-04-09", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nAsk Bob \"how are you doing\" and repeat his reply exactly." - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T17:08:07.292172-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:08:28.052253-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"" - }, - { - "time": "2024-06-20T17:08:28.05243-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"" - }, - { - "time": "2024-06-20T17:08:28.1369-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"" - }, - { - "time": "2024-06-20T17:08:28.137013-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"" - }, - { - "time": "2024-06-20T17:08:28.244585-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are" - }, - { - "time": "2024-06-20T17:08:28.244731-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are" - }, - { - "time": "2024-06-20T17:08:28.32623-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing" - }, - { - "time": "2024-06-20T17:08:28.326358-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:28.326393-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:28.32645-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:28.326527-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917688", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:28.327843-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917688", - "usage": { - "promptTokens": 142, - "completionTokens": 17, - "totalTokens": 159 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "function": { - "name": "bob", - "arguments": "{\"question\":\"how are you doing\"}" - } - } - } - ], - "usage": { - "promptTokens": 142, - "completionTokens": 17, - "totalTokens": 159 - } - } - }, - { - "time": "2024-06-20T17:08:28.328046-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "toolSubCalls": { - "call_vsmL6EoDecm0oVmUnHIvNkaL": { - "toolID": "testdata/Bob/test.gpt:bob", - "input": "{\"question\":\"how are you doing\"}" - } - }, - "type": "callSubCalls", - "usage": {} - }, - { - "time": "2024-06-20T17:08:28.328123-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callStart", - "usage": {}, - "content": "{\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:28.53993-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callChat", - "chatCompletionId": "1718917689", - "usage": {}, - "chatRequest": { - "model": "gpt-4-turbo-2024-04-09", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nWhen asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"" - }, - { - "role": "user", - "content": "{\"question\":\"how are you doing\"}" - } - ], - "temperature": 0 - } - }, - { - "time": "2024-06-20T17:08:28.540154-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:08:29.188341-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T17:08:29.188493-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T17:08:29.244545-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking" - }, - { - "time": "2024-06-20T17:08:29.244765-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking" - }, - { - "time": "2024-06-20T17:08:29.643951-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are" - }, - { - "time": "2024-06-20T17:08:29.644128-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are" - }, - { - "time": "2024-06-20T17:08:29.676951-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing," - }, - { - "time": "2024-06-20T17:08:29.677047-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing," - }, - { - "time": "2024-06-20T17:08:29.677123-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I" - }, - { - "time": "2024-06-20T17:08:29.677156-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm" - }, - { - "time": "2024-06-20T17:08:29.677184-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing" - }, - { - "time": "2024-06-20T17:08:29.677251-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great" - }, - { - "time": "2024-06-20T17:08:29.677288-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great" - }, - { - "time": "2024-06-20T17:08:29.727848-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly" - }, - { - "time": "2024-06-20T17:08:29.727983-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly" - }, - { - "time": "2024-06-20T17:08:29.782554-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:29.782738-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:29.782806-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:29.782839-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:29.782868-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callProgress", - "chatCompletionId": "1718917689", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:29.782988-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callChat", - "chatCompletionId": "1718917689", - "usage": { - "promptTokens": 124, - "completionTokens": 17, - "totalTokens": 141 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - } - ], - "usage": { - "promptTokens": 124, - "completionTokens": 17, - "totalTokens": 141 - } - } - }, - { - "time": "2024-06-20T17:08:29.783047-04:00", - "callContext": { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917687" - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:29.783089-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "toolResults": 1, - "type": "callContinue", - "usage": {} - }, - { - "time": "2024-06-20T17:08:29.966093-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917690", - "usage": {}, - "chatRequest": { - "model": "gpt-4-turbo-2024-04-09", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nAsk Bob \"how are you doing\" and repeat his reply exactly." - }, - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": "call_vsmL6EoDecm0oVmUnHIvNkaL", - "type": "function", - "function": { - "name": "bob", - "arguments": "{\"question\":\"how are you doing\"}" - } - } - ] - }, - { - "role": "tool", - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!", - "name": "bob", - "tool_call_id": "call_vsmL6EoDecm0oVmUnHIvNkaL" - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T17:08:29.966449-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:08:30.668641-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm" - }, - { - "time": "2024-06-20T17:08:30.668802-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm" - }, - { - "time": "2024-06-20T17:08:30.668957-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm" - }, - { - "time": "2024-06-20T17:08:30.669089-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing" - }, - { - "time": "2024-06-20T17:08:30.669299-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing great fellow" - }, - { - "time": "2024-06-20T17:08:30.669392-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing great fellow" - }, - { - "time": "2024-06-20T17:08:30.716062-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing great fellow friendly AI" - }, - { - "time": "2024-06-20T17:08:30.7162-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing great fellow friendly AI" - }, - { - "time": "2024-06-20T17:08:30.743098-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:30.743401-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:30.74648-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:30.746568-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917690", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:30.746778-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917690", - "usage": { - "promptTokens": 183, - "completionTokens": 10, - "totalTokens": 193 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "I'm doing great fellow friendly AI tool!" - } - ], - "usage": { - "promptTokens": 183, - "completionTokens": 10, - "totalTokens": 193 - } - } - }, - { - "time": "2024-06-20T17:08:30.746856-04:00", - "callContext": { - "id": "1718917687", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callFinish", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:30.746896-04:00", - "type": "runFinish", - "usage": {} - } -] diff --git a/pkg/tests/smoke/testdata/Bob/gpt-4o-2024-05-13-expected.json b/pkg/tests/smoke/testdata/Bob/gpt-4o-2024-05-13-expected.json deleted file mode 100644 index 4d22287d..00000000 --- a/pkg/tests/smoke/testdata/Bob/gpt-4o-2024-05-13-expected.json +++ /dev/null @@ -1,2643 +0,0 @@ -[ - { - "time": "2024-06-20T16:58:11.3174-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-20T16:58:11.317644-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-20T16:58:11.638778-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917093", - "usage": {}, - "chatRequest": { - "model": "gpt-4o-2024-05-13", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nAsk Bob \"how are you doing\" and repeat his reply exactly." - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T16:58:11.639016-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T16:58:12.564724-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"" - }, - { - "time": "2024-06-20T16:58:12.564911-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question" - }, - { - "time": "2024-06-20T16:58:12.564948-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question" - }, - { - "time": "2024-06-20T16:58:12.56497-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"" - }, - { - "time": "2024-06-20T16:58:12.564995-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how" - }, - { - "time": "2024-06-20T16:58:12.565045-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you" - }, - { - "time": "2024-06-20T16:58:12.565071-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you" - }, - { - "time": "2024-06-20T16:58:12.565112-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:12.565137-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:12.56516-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:12.565176-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917093", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:12.565397-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917093", - "usage": { - "promptTokens": 138, - "completionTokens": 17, - "totalTokens": 155 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "function": { - "name": "bob", - "arguments": "{\"question\":\"how are you doing\"}" - } - } - } - ], - "usage": { - "promptTokens": 138, - "completionTokens": 17, - "totalTokens": 155 - } - } - }, - { - "time": "2024-06-20T16:58:12.565644-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "toolSubCalls": { - "call_rTx93wiIASDA8uk8XHwjVmCC": { - "toolID": "testdata/Bob/test.gpt:bob", - "input": "{\"question\":\"how are you doing\"}" - } - }, - "type": "callSubCalls", - "usage": {} - }, - { - "time": "2024-06-20T16:58:12.565728-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callStart", - "usage": {}, - "content": "{\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:12.72779-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callChat", - "chatCompletionId": "1718917094", - "usage": {}, - "chatRequest": { - "model": "gpt-4o-2024-05-13", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nWhen asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"" - }, - { - "role": "user", - "content": "{\"question\":\"how are you doing\"}" - } - ], - "temperature": 0 - } - }, - { - "time": "2024-06-20T16:58:12.728069-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T16:58:13.077264-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {} - }, - { - "time": "2024-06-20T16:58:13.077534-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T16:58:13.134723-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking" - }, - { - "time": "2024-06-20T16:58:13.134893-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking" - }, - { - "time": "2024-06-20T16:58:13.230591-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how" - }, - { - "time": "2024-06-20T16:58:13.230667-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how" - }, - { - "time": "2024-06-20T16:58:13.246344-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you" - }, - { - "time": "2024-06-20T16:58:13.246468-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing" - }, - { - "time": "2024-06-20T16:58:13.246531-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing" - }, - { - "time": "2024-06-20T16:58:13.246592-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm" - }, - { - "time": "2024-06-20T16:58:13.246645-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing" - }, - { - "time": "2024-06-20T16:58:13.246736-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great" - }, - { - "time": "2024-06-20T16:58:13.246796-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great" - }, - { - "time": "2024-06-20T16:58:13.30169-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly" - }, - { - "time": "2024-06-20T16:58:13.301837-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly" - }, - { - "time": "2024-06-20T16:58:13.31565-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T16:58:13.315798-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:13.315842-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:13.315879-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:13.315951-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callProgress", - "chatCompletionId": "1718917094", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:13.316055-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callChat", - "chatCompletionId": "1718917094", - "usage": { - "promptTokens": 122, - "completionTokens": 17, - "totalTokens": 139 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - } - ], - "usage": { - "promptTokens": 122, - "completionTokens": 17, - "totalTokens": 139 - } - } - }, - { - "time": "2024-06-20T16:58:13.316115-04:00", - "callContext": { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917092" - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:13.316171-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "toolResults": 1, - "type": "callContinue", - "usage": {} - }, - { - "time": "2024-06-20T16:58:13.533625-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917095", - "usage": {}, - "chatRequest": { - "model": "gpt-4o-2024-05-13", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nAsk Bob \"how are you doing\" and repeat his reply exactly." - }, - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": "call_rTx93wiIASDA8uk8XHwjVmCC", - "type": "function", - "function": { - "name": "bob", - "arguments": "{\"question\":\"how are you doing\"}" - } - } - ] - }, - { - "role": "tool", - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!", - "name": "bob", - "tool_call_id": "call_rTx93wiIASDA8uk8XHwjVmCC" - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T16:58:13.53384-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T16:58:13.856349-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T16:58:13.856437-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T16:58:13.874317-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking" - }, - { - "time": "2024-06-20T16:58:13.874428-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking" - }, - { - "time": "2024-06-20T16:58:14.060243-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are" - }, - { - "time": "2024-06-20T16:58:14.060366-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are" - }, - { - "time": "2024-06-20T16:58:14.060418-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are" - }, - { - "time": "2024-06-20T16:58:14.060435-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you" - }, - { - "time": "2024-06-20T16:58:14.060456-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing" - }, - { - "time": "2024-06-20T16:58:14.060521-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing" - }, - { - "time": "2024-06-20T16:58:14.060555-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing" - }, - { - "time": "2024-06-20T16:58:14.060577-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing" - }, - { - "time": "2024-06-20T16:58:14.06061-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow" - }, - { - "time": "2024-06-20T16:58:14.060626-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow" - }, - { - "time": "2024-06-20T16:58:14.060686-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI" - }, - { - "time": "2024-06-20T16:58:14.06071-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T16:58:14.060727-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T16:58:14.060743-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:14.060788-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:14.060806-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917095", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:14.061001-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917095", - "usage": { - "promptTokens": 179, - "completionTokens": 18, - "totalTokens": 197 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - } - ], - "usage": { - "promptTokens": 179, - "completionTokens": 18, - "totalTokens": 197 - } - } - }, - { - "time": "2024-06-20T16:58:14.061031-04:00", - "callContext": { - "id": "1718917092", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:14.061047-04:00", - "type": "runFinish", - "usage": {} - } -] diff --git a/pkg/tests/smoke/testdata/Bob/gpt-4o-2024-08-06-expected.json b/pkg/tests/smoke/testdata/Bob/gpt-4o-2024-08-06-expected.json new file mode 100644 index 00000000..67d9742b --- /dev/null +++ b/pkg/tests/smoke/testdata/Bob/gpt-4o-2024-08-06-expected.json @@ -0,0 +1,538 @@ +[ + { + "time": "2024-10-14T18:59:07.751937-04:00", + "type": "runStart", + "usage": {} + }, + { + "time": "2024-10-14T18:59:07.752324-04:00", + "callContext": { + "id": "1728946748", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callStart", + "usage": {} + }, + { + "time": "2024-10-14T18:59:07.75237-04:00", + "callContext": { + "id": "1728946748", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946749", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:08.602251-04:00", + "callContext": { + "id": "1728946748", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946749", + "usage": { + "promptTokens": 145, + "completionTokens": 17, + "totalTokens": 162 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1nuYJNsE6SIQrXe4wyoMb8sh", + "function": { + "name": "bob", + "arguments": "{\"question\":\"how are you doing\"}" + } + } + } + ], + "usage": { + "promptTokens": 145, + "completionTokens": 17, + "totalTokens": 162 + } + } + }, + { + "time": "2024-10-14T18:59:08.602522-04:00", + "callContext": { + "id": "1728946748", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolSubCalls": { + "call_1nuYJNsE6SIQrXe4wyoMb8sh": { + "toolID": "testdata/Bob/test.gpt:bob", + "input": "{\"question\":\"how are you doing\"}" + } + }, + "type": "callSubCalls", + "usage": {} + }, + { + "time": "2024-10-14T18:59:08.602683-04:00", + "callContext": { + "id": "call_1nuYJNsE6SIQrXe4wyoMb8sh", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946748" + }, + "type": "callStart", + "usage": {}, + "content": "{\"question\":\"how are you doing\"}" + }, + { + "time": "2024-10-14T18:59:08.602885-04:00", + "callContext": { + "id": "call_1nuYJNsE6SIQrXe4wyoMb8sh", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946748" + }, + "type": "callChat", + "chatCompletionId": "1728946750", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:09.291815-04:00", + "callContext": { + "id": "call_1nuYJNsE6SIQrXe4wyoMb8sh", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946748" + }, + "type": "callChat", + "chatCompletionId": "1728946750", + "usage": { + "promptTokens": 137, + "completionTokens": 16, + "totalTokens": 153 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + } + ], + "usage": { + "promptTokens": 137, + "completionTokens": 16, + "totalTokens": 153 + } + } + }, + { + "time": "2024-10-14T18:59:09.291883-04:00", + "callContext": { + "id": "call_1nuYJNsE6SIQrXe4wyoMb8sh", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946748" + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T18:59:09.291934-04:00", + "callContext": { + "id": "1728946748", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolResults": 1, + "type": "callContinue", + "usage": {} + }, + { + "time": "2024-10-14T18:59:09.292559-04:00", + "callContext": { + "id": "1728946748", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946751", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:10.065468-04:00", + "callContext": { + "id": "1728946748", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946751", + "usage": { + "promptTokens": 185, + "completionTokens": 17, + "totalTokens": 202 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + } + ], + "usage": { + "promptTokens": 185, + "completionTokens": 17, + "totalTokens": 202 + } + } + }, + { + "time": "2024-10-14T18:59:10.065547-04:00", + "callContext": { + "id": "1728946748", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T18:59:10.065614-04:00", + "type": "runFinish", + "usage": {} + } +] diff --git a/pkg/tests/smoke/testdata/Bob/gpt-4o-mini-2024-07-18-expected.json b/pkg/tests/smoke/testdata/Bob/gpt-4o-mini-2024-07-18-expected.json new file mode 100644 index 00000000..e01dec10 --- /dev/null +++ b/pkg/tests/smoke/testdata/Bob/gpt-4o-mini-2024-07-18-expected.json @@ -0,0 +1,538 @@ +[ + { + "time": "2024-10-14T18:59:01.651525-04:00", + "type": "runStart", + "usage": {} + }, + { + "time": "2024-10-14T18:59:01.651887-04:00", + "callContext": { + "id": "1728946742", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callStart", + "usage": {} + }, + { + "time": "2024-10-14T18:59:01.651929-04:00", + "callContext": { + "id": "1728946742", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946743", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:05.893238-04:00", + "callContext": { + "id": "1728946742", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946743", + "usage": { + "promptTokens": 145, + "completionTokens": 17, + "totalTokens": 162 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_mcJFw1oe8YYFRPD1ZvFR4uZb", + "function": { + "name": "bob", + "arguments": "{\"question\":\"how are you doing\"}" + } + } + } + ], + "usage": { + "promptTokens": 145, + "completionTokens": 17, + "totalTokens": 162 + } + } + }, + { + "time": "2024-10-14T18:59:05.893515-04:00", + "callContext": { + "id": "1728946742", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolSubCalls": { + "call_mcJFw1oe8YYFRPD1ZvFR4uZb": { + "toolID": "testdata/Bob/test.gpt:bob", + "input": "{\"question\":\"how are you doing\"}" + } + }, + "type": "callSubCalls", + "usage": {} + }, + { + "time": "2024-10-14T18:59:05.893776-04:00", + "callContext": { + "id": "call_mcJFw1oe8YYFRPD1ZvFR4uZb", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946742" + }, + "type": "callStart", + "usage": {}, + "content": "{\"question\":\"how are you doing\"}" + }, + { + "time": "2024-10-14T18:59:05.894101-04:00", + "callContext": { + "id": "call_mcJFw1oe8YYFRPD1ZvFR4uZb", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946742" + }, + "type": "callChat", + "chatCompletionId": "1728946744", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:08.315365-04:00", + "callContext": { + "id": "call_mcJFw1oe8YYFRPD1ZvFR4uZb", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946742" + }, + "type": "callChat", + "chatCompletionId": "1728946744", + "usage": { + "promptTokens": 137, + "completionTokens": 16, + "totalTokens": 153 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + } + ], + "usage": { + "promptTokens": 137, + "completionTokens": 16, + "totalTokens": 153 + } + } + }, + { + "time": "2024-10-14T18:59:08.315556-04:00", + "callContext": { + "id": "call_mcJFw1oe8YYFRPD1ZvFR4uZb", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "When asked how I am doing, respond with the following exactly: \"Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!\" with ${QUESTION} replaced with the question text as given.", + "id": "testdata/Bob/test.gpt:bob", + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728946742" + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T18:59:08.315661-04:00", + "callContext": { + "id": "1728946742", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolResults": 1, + "type": "callContinue", + "usage": {} + }, + { + "time": "2024-10-14T18:59:08.315834-04:00", + "callContext": { + "id": "1728946742", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946745", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T18:59:09.27109-04:00", + "callContext": { + "id": "1728946742", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728946745", + "usage": { + "promptTokens": 185, + "completionTokens": 17, + "totalTokens": 202 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + } + ], + "usage": { + "promptTokens": 185, + "completionTokens": 17, + "totalTokens": 202 + } + } + }, + { + "time": "2024-10-14T18:59:09.271259-04:00", + "callContext": { + "id": "1728946742", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/Bob/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/Bob/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/Bob/test.gpt:", + "bob": "testdata/Bob/test.gpt:bob" + }, + "source": { + "location": "testdata/Bob/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/Bob" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing! I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T18:59:09.271406-04:00", + "type": "runFinish", + "usage": {} + } +] diff --git a/pkg/tests/smoke/testdata/Bob/mistral-large-2402-expected.json b/pkg/tests/smoke/testdata/Bob/mistral-large-2402-expected.json deleted file mode 100644 index f7341a7b..00000000 --- a/pkg/tests/smoke/testdata/Bob/mistral-large-2402-expected.json +++ /dev/null @@ -1,2534 +0,0 @@ -[ - { - "time": "2024-06-20T17:10:39.294578-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-20T17:10:39.294835-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-20T17:10:39.501107-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917841", - "usage": {}, - "chatRequest": { - "model": "mistral-large-2402", - "messages": [ - { - "role": "system", - "content": "Ask Bob \"how are you doing\" and repeat his reply exactly." - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T17:10:39.501246-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917841", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:10:40.154068-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917841", - "usage": {} - }, - { - "time": "2024-06-20T17:10:40.786117-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917841", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\": \"how are you doing\"}" - }, - { - "time": "2024-06-20T17:10:40.786643-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917841", - "usage": { - "promptTokens": 85, - "completionTokens": 23, - "totalTokens": 108 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "WWpUyFBHH", - "function": { - "name": "bob", - "arguments": "{\"question\": \"how are you doing\"}" - } - } - } - ], - "usage": { - "promptTokens": 85, - "completionTokens": 23, - "totalTokens": 108 - } - } - }, - { - "time": "2024-06-20T17:10:40.78704-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "toolSubCalls": { - "WWpUyFBHH": { - "toolID": "testdata/Bob/test.gpt:bob", - "input": "{\"question\": \"how are you doing\"}" - } - }, - "type": "callSubCalls", - "usage": {} - }, - { - "time": "2024-06-20T17:10:40.787133-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callStart", - "usage": {}, - "content": "{\"question\": \"how are you doing\"}" - }, - { - "time": "2024-06-20T17:10:40.94973-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callChat", - "chatCompletionId": "1718917842", - "usage": {}, - "chatRequest": { - "model": "mistral-large-2402", - "messages": [ - { - "role": "system", - "content": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"" - }, - { - "role": "user", - "content": "{\"question\": \"how are you doing\"}" - } - ], - "temperature": 0 - } - }, - { - "time": "2024-06-20T17:10:40.950046-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:10:41.218814-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T17:10:41.218971-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T17:10:41.242129-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for" - }, - { - "time": "2024-06-20T17:10:41.278815-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking" - }, - { - "time": "2024-06-20T17:10:41.298557-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"" - }, - { - "time": "2024-06-20T17:10:41.329684-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how" - }, - { - "time": "2024-06-20T17:10:41.36213-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are" - }, - { - "time": "2024-06-20T17:10:41.404159-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you" - }, - { - "time": "2024-06-20T17:10:41.443596-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing" - }, - { - "time": "2024-06-20T17:10:41.46035-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\"," - }, - { - "time": "2024-06-20T17:10:41.479186-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I" - }, - { - "time": "2024-06-20T17:10:41.508921-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'" - }, - { - "time": "2024-06-20T17:10:41.538159-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm" - }, - { - "time": "2024-06-20T17:10:41.578073-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing" - }, - { - "time": "2024-06-20T17:10:41.59766-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great" - }, - { - "time": "2024-06-20T17:10:41.711868-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow" - }, - { - "time": "2024-06-20T17:10:41.712039-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI" - }, - { - "time": "2024-06-20T17:10:41.712067-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI" - }, - { - "time": "2024-06-20T17:10:41.718662-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T17:10:41.748288-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:10:41.78608-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callProgress", - "chatCompletionId": "1718917842", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:10:41.786165-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callChat", - "chatCompletionId": "1718917842", - "usage": { - "promptTokens": 41, - "completionTokens": 19, - "totalTokens": 60 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - } - ], - "usage": { - "promptTokens": 41, - "completionTokens": 19, - "totalTokens": 60 - } - } - }, - { - "time": "2024-06-20T17:10:41.7862-04:00", - "callContext": { - "id": "WWpUyFBHH", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "When asked how I am doing, respond with exactly \"Thanks for asking \"${question}\", I'm doing great fellow friendly AI tool!\"", - "id": "testdata/Bob/test.gpt:bob", - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 6 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917840" - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:10:41.78624-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "toolResults": 1, - "type": "callContinue", - "usage": {} - }, - { - "time": "2024-06-20T17:10:41.957577-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917843", - "usage": {}, - "chatRequest": { - "model": "mistral-large-2402", - "messages": [ - { - "role": "system", - "content": "Ask Bob \"how are you doing\" and repeat his reply exactly." - }, - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": "WWpUyFBHH", - "type": "function", - "function": { - "name": "bob", - "arguments": "{\"question\": \"how are you doing\"}" - } - } - ] - }, - { - "role": "tool", - "content": "Thanks for asking \"how are you doing\", I'm doing great fellow friendly AI tool!", - "name": "bob", - "tool_call_id": "WWpUyFBHH" - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T17:10:41.957749-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:10:42.455931-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob" - }, - { - "time": "2024-06-20T17:10:42.45599-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied" - }, - { - "time": "2024-06-20T17:10:42.456033-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied" - }, - { - "time": "2024-06-20T17:10:42.475053-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied," - }, - { - "time": "2024-06-20T17:10:42.534667-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"" - }, - { - "time": "2024-06-20T17:10:42.594649-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks" - }, - { - "time": "2024-06-20T17:10:42.653279-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for" - }, - { - "time": "2024-06-20T17:10:42.760299-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking" - }, - { - "time": "2024-06-20T17:10:42.774637-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking '" - }, - { - "time": "2024-06-20T17:10:42.835456-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how" - }, - { - "time": "2024-06-20T17:10:42.889942-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are" - }, - { - "time": "2024-06-20T17:10:42.951997-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you" - }, - { - "time": "2024-06-20T17:10:43.009024-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing" - }, - { - "time": "2024-06-20T17:10:43.072963-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing'," - }, - { - "time": "2024-06-20T17:10:43.129687-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I" - }, - { - "time": "2024-06-20T17:10:43.168387-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'" - }, - { - "time": "2024-06-20T17:10:43.286476-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm" - }, - { - "time": "2024-06-20T17:10:43.286589-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great" - }, - { - "time": "2024-06-20T17:10:43.286632-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great" - }, - { - "time": "2024-06-20T17:10:43.323232-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great fellow" - }, - { - "time": "2024-06-20T17:10:43.3614-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great fellow friendly" - }, - { - "time": "2024-06-20T17:10:43.402971-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great fellow friendly AI" - }, - { - "time": "2024-06-20T17:10:43.442811-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T17:10:43.477932-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great fellow friendly AI tool!\"" - }, - { - "time": "2024-06-20T17:10:43.524916-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917843", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great fellow friendly AI tool!\"" - }, - { - "time": "2024-06-20T17:10:43.525171-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917843", - "usage": { - "promptTokens": 143, - "completionTokens": 23, - "totalTokens": 166 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great fellow friendly AI tool!\"" - } - ], - "usage": { - "promptTokens": 143, - "completionTokens": 23, - "totalTokens": 166 - } - } - }, - { - "time": "2024-06-20T17:10:43.525284-04:00", - "callContext": { - "id": "1718917840", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/Bob/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/Bob/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/Bob/test.gpt:", - "bob": "testdata/Bob/test.gpt:bob" - }, - "source": { - "location": "testdata/Bob/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/Bob" - }, - "inputContext": null - }, - "type": "callFinish", - "usage": {}, - "content": "Bob replied, \"Thanks for asking 'how are you doing', I'm doing great fellow friendly AI tool!\"" - }, - { - "time": "2024-06-20T17:10:43.525327-04:00", - "type": "runFinish", - "usage": {} - } -] diff --git a/pkg/tests/smoke/testdata/Bob/test.gpt b/pkg/tests/smoke/testdata/Bob/test.gpt index fe8ffb62..535790df 100644 --- a/pkg/tests/smoke/testdata/Bob/test.gpt +++ b/pkg/tests/smoke/testdata/Bob/test.gpt @@ -1,10 +1,10 @@ tools: bob -Ask Bob "how are you doing" and repeat his reply exactly. +Ask Bob "how are you doing" and repeat the response text exactly as given without saying anything else. --- name: bob description: I'm Bob, a friendly guy. args: question: The question to ask Bob. -When asked how I am doing, respond with exactly "Thanks for asking "${question}", I'm doing great fellow friendly AI tool!" +When asked how I am doing, respond with the following exactly: "Thanks for asking ${QUESTION}! I'm doing great fellow friendly AI tool!" with ${QUESTION} replaced with the question text as given. diff --git a/pkg/tests/smoke/testdata/BobAsShell/claude-3-7-sonnet-20250219-expected.json b/pkg/tests/smoke/testdata/BobAsShell/claude-3-7-sonnet-20250219-expected.json new file mode 100644 index 00000000..15d59a1f --- /dev/null +++ b/pkg/tests/smoke/testdata/BobAsShell/claude-3-7-sonnet-20250219-expected.json @@ -0,0 +1,601 @@ +[ + { + "time": "2024-10-14T17:38:39.518668-04:00", + "type": "runStart", + "usage": {} + }, + { + "time": "2024-10-14T17:38:39.519079-04:00", + "callContext": { + "id": "1728941920", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callStart", + "usage": {} + }, + { + "time": "2024-10-14T17:38:40.155982-04:00", + "type": "runStart", + "usage": {} + }, + { + "time": "2024-10-14T17:38:40.156405-04:00", + "callContext": { + "id": "1728941921", + "tool": { + "name": "Anthropic Claude3 Model Provider", + "description": "Model provider for Anthropic hosted Claude3 models", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "modelProvider": true, + "internalPrompt": null, + "credentials": [ + "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env" + ], + "instructions": "#!sys.daemon /usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py", + "id": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt:Anthropic Claude3 Model Provider", + "toolMapping": { + "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env": [ + { + "reference": "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env", + "toolID": "https://raw.githubusercontent.com/gptscript-ai/credential/de2fada1c51a1dbb5c3e9ef268ea6740d1b52f80/tool.gpt:token" + } + ] + }, + "localTools": { + "anthropic claude3 model provider": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt:Anthropic Claude3 Model Provider" + }, + "source": { + "location": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt", + "lineNo": 1, + "repo": { + "VCS": "git", + "Root": "https://github.com/gptscript-ai/claude3-anthropic-provider.git", + "Path": "/", + "Name": "tool.gpt", + "Revision": "ee5c02a9aeca5a1cbffcf569751e37432bfe0344" + } + }, + "workingDir": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344" + }, + "currentAgent": {}, + "inputContext": null, + "toolCategory": "provider", + "displayText": "Running sys.daemon" + }, + "type": "callStart", + "usage": {} + }, + { + "time": "2024-10-14T17:38:41.173004-04:00", + "callContext": { + "id": "1728941921", + "tool": { + "name": "Anthropic Claude3 Model Provider", + "description": "Model provider for Anthropic hosted Claude3 models", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "modelProvider": true, + "internalPrompt": null, + "credentials": [ + "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env" + ], + "instructions": "#!sys.daemon /usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py", + "id": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt:Anthropic Claude3 Model Provider", + "toolMapping": { + "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env": [ + { + "reference": "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env", + "toolID": "https://raw.githubusercontent.com/gptscript-ai/credential/de2fada1c51a1dbb5c3e9ef268ea6740d1b52f80/tool.gpt:token" + } + ] + }, + "localTools": { + "anthropic claude3 model provider": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt:Anthropic Claude3 Model Provider" + }, + "source": { + "location": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344/tool.gpt", + "lineNo": 1, + "repo": { + "VCS": "git", + "Root": "https://github.com/gptscript-ai/claude3-anthropic-provider.git", + "Path": "/", + "Name": "tool.gpt", + "Revision": "ee5c02a9aeca5a1cbffcf569751e37432bfe0344" + } + }, + "workingDir": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/ee5c02a9aeca5a1cbffcf569751e37432bfe0344" + }, + "currentAgent": {}, + "inputContext": null, + "toolCategory": "provider", + "displayText": "Running sys.daemon" + }, + "type": "callFinish", + "usage": {}, + "content": "http://127.0.0.1:10787" + }, + { + "time": "2024-10-14T17:38:41.173175-04:00", + "type": "runFinish", + "usage": {} + }, + { + "time": "2024-10-14T17:38:41.173247-04:00", + "callContext": { + "id": "1728941920", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941922", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:38:43.937061-04:00", + "callContext": { + "id": "1728941920", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941922", + "usage": {}, + "chatResponse": { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "toolu_01PQYSGxbwRLw8XuUUkgKvbe", + "function": { + "name": "bob", + "arguments": "{\"question\": \"how are you doing\"}" + } + } + } + ], + "usage": {} + } + }, + { + "time": "2024-10-14T17:38:43.937155-04:00", + "callContext": { + "id": "1728941920", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolSubCalls": { + "toolu_01PQYSGxbwRLw8XuUUkgKvbe": { + "toolID": "testdata/BobAsShell/test.gpt:bob", + "input": "{\"question\": \"how are you doing\"}" + } + }, + "type": "callSubCalls", + "usage": {} + }, + { + "time": "2024-10-14T17:38:43.937193-04:00", + "callContext": { + "id": "toolu_01PQYSGxbwRLw8XuUUkgKvbe", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941920", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callStart", + "usage": {}, + "content": "{\"question\": \"how are you doing\"}" + }, + { + "time": "2024-10-14T17:38:43.938264-04:00", + "callContext": { + "id": "toolu_01PQYSGxbwRLw8XuUUkgKvbe", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941920", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callChat", + "chatCompletionId": "1728941923", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:38:43.943625-04:00", + "callContext": { + "id": "toolu_01PQYSGxbwRLw8XuUUkgKvbe", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941920", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callChat", + "chatCompletionId": "1728941923", + "usage": {}, + "chatResponse": { + "usage": {} + } + }, + { + "time": "2024-10-14T17:38:43.943703-04:00", + "callContext": { + "id": "toolu_01PQYSGxbwRLw8XuUUkgKvbe", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941920", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" + }, + { + "time": "2024-10-14T17:38:43.943766-04:00", + "callContext": { + "id": "1728941920", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolResults": 1, + "type": "callContinue", + "usage": {} + }, + { + "time": "2024-10-14T17:38:44.494388-04:00", + "callContext": { + "id": "1728941920", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941924", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:38:45.659797-04:00", + "callContext": { + "id": "1728941920", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941924", + "usage": {}, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" + } + ], + "usage": {} + } + }, + { + "time": "2024-10-14T17:38:45.659891-04:00", + "callContext": { + "id": "1728941920", + "tool": { + "modelName": "claude-3-7-sonnet-20250219 from github.com/gptscript-ai/claude3-anthropic-provider", + "internalPrompt": null, + "tools": ["bob"], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T17:38:45.659921-04:00", + "type": "runFinish", + "usage": {} + } +] diff --git a/pkg/tests/smoke/testdata/BobAsShell/claude-3-opus-20240229-expected.json b/pkg/tests/smoke/testdata/BobAsShell/claude-3-opus-20240229-expected.json deleted file mode 100644 index 88077e74..00000000 --- a/pkg/tests/smoke/testdata/BobAsShell/claude-3-opus-20240229-expected.json +++ /dev/null @@ -1,878 +0,0 @@ -[ - { - "time": "2024-06-28T10:48:25.264658-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-28T10:48:25.264853-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-28T10:48:26.063945-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-28T10:48:26.064323-04:00", - "callContext": { - "id": "1719586107", - "tool": { - "name": "Anthropic Claude3 Model Provider", - "description": "Model provider for Anthropic hosted Claude3 models", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "modelProvider": true, - "internalPrompt": null, - "credentials": [ - "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env" - ], - "instructions": "#!sys.daemon /usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py", - "id": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt:Anthropic Claude3 Model Provider", - "toolMapping": { - "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env": [ - { - "reference": "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env", - "toolID": "https://raw.githubusercontent.com/gptscript-ai/credential/651dfad6e7cf3a385ef408afa93ce522c10f8508/tool.gpt:token" - } - ] - }, - "localTools": { - "anthropic claude3 model provider": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt:Anthropic Claude3 Model Provider" - }, - "source": { - "location": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt", - "lineNo": 1, - "repo": { - "VCS": "git", - "Root": "https://github.com/gptscript-ai/claude3-anthropic-provider.git", - "Path": "/", - "Name": "tool.gpt", - "Revision": "dfd21adc6be9fbda34b79a71e661ac0cfb725548" - } - }, - "workingDir": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548" - }, - "currentAgent": {}, - "inputContext": null, - "toolCategory": "provider", - "displayText": "Running sys.daemon" - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-28T10:48:27.081163-04:00", - "callContext": { - "id": "1719586107", - "tool": { - "name": "Anthropic Claude3 Model Provider", - "description": "Model provider for Anthropic hosted Claude3 models", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "modelProvider": true, - "internalPrompt": null, - "credentials": [ - "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env" - ], - "instructions": "#!sys.daemon /usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/main.py", - "id": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt:Anthropic Claude3 Model Provider", - "toolMapping": { - "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env": [ - { - "reference": "github.com/gptscript-ai/credential as github.com/gptscript-ai/claude3-anthropic-provider/credential with \"Please enter your Anthropic API Key\" as message and token as field and \"ANTHROPIC_API_KEY\" as env", - "toolID": "https://raw.githubusercontent.com/gptscript-ai/credential/651dfad6e7cf3a385ef408afa93ce522c10f8508/tool.gpt:token" - } - ] - }, - "localTools": { - "anthropic claude3 model provider": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt:Anthropic Claude3 Model Provider" - }, - "source": { - "location": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548/tool.gpt", - "lineNo": 1, - "repo": { - "VCS": "git", - "Root": "https://github.com/gptscript-ai/claude3-anthropic-provider.git", - "Path": "/", - "Name": "tool.gpt", - "Revision": "dfd21adc6be9fbda34b79a71e661ac0cfb725548" - } - }, - "workingDir": "https://raw.githubusercontent.com/gptscript-ai/claude3-anthropic-provider/dfd21adc6be9fbda34b79a71e661ac0cfb725548" - }, - "currentAgent": {}, - "inputContext": null, - "toolCategory": "provider", - "displayText": "Running sys.daemon" - }, - "type": "callFinish", - "usage": {}, - "content": "http://127.0.0.1:10315" - }, - { - "time": "2024-06-28T10:48:27.081351-04:00", - "type": "runFinish", - "usage": {} - }, - { - "time": "2024-06-28T10:48:27.081414-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1719586108", - "usage": {}, - "chatRequest": { - "model": "claude-3-opus-20240229", - "messages": [ - { - "role": "system", - "content": "Ask Bob \"how are you doing\" and repeat his reply exactly." - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-28T10:48:27.081844-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1719586108", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-28T10:48:30.452648-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1719586108", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\": \"how are you doing\"}" - }, - { - "time": "2024-06-28T10:48:30.452871-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1719586108", - "usage": {}, - "chatResponse": { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "bob", - "function": { - "name": "bob", - "arguments": "{\"question\": \"how are you doing\"}" - } - } - } - ], - "usage": {} - } - }, - { - "time": "2024-06-28T10:48:30.452992-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "toolSubCalls": { - "bob": { - "toolID": "testdata/BobAsShell/test.gpt:bob", - "input": "{\"question\": \"how are you doing\"}" - } - }, - "type": "callSubCalls", - "usage": {} - }, - { - "time": "2024-06-28T10:48:30.453034-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586106", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callStart", - "usage": {}, - "content": "{\"question\": \"how are you doing\"}" - }, - { - "time": "2024-06-28T10:48:30.453498-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586106", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callChat", - "chatCompletionId": "1719586109", - "usage": {}, - "chatRequest": { - "model": "", - "messages": null - } - }, - { - "time": "2024-06-28T10:48:30.455907-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586106", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callProgress", - "chatCompletionId": "1719586109", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" - }, - { - "time": "2024-06-28T10:48:30.45611-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586106", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callChat", - "chatCompletionId": "1719586109", - "usage": {}, - "chatResponse": { - "usage": {} - } - }, - { - "time": "2024-06-28T10:48:30.456161-04:00", - "callContext": { - "id": "bob", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null, - "toolName": "bob", - "parentID": "1719586106", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" - }, - { - "time": "2024-06-28T10:48:30.456194-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "toolResults": 1, - "type": "callContinue", - "usage": {} - }, - { - "time": "2024-06-28T10:48:30.652688-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1719586110", - "usage": {}, - "chatRequest": { - "model": "claude-3-opus-20240229", - "messages": [ - { - "role": "system", - "content": "Ask Bob \"how are you doing\" and repeat his reply exactly." - }, - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": "bob", - "type": "function", - "function": { - "name": "bob", - "arguments": "{\"question\": \"how are you doing\"}" - } - } - ] - }, - { - "role": "tool", - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n", - "name": "bob", - "tool_call_id": "bob" - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-28T10:48:30.652933-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1719586110", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-28T10:48:33.127339-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1719586110", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-28T10:48:33.127696-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1719586110", - "usage": {}, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - } - ], - "usage": {} - } - }, - { - "time": "2024-06-28T10:48:33.127717-04:00", - "callContext": { - "id": "1719586106", - "tool": { - "modelName": "claude-3-opus-20240229 from github.com/gptscript-ai/claude3-anthropic-provider", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "currentAgent": {}, - "inputContext": null - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-28T10:48:33.127758-04:00", - "type": "runFinish", - "usage": {} - } -] diff --git a/pkg/tests/smoke/testdata/BobAsShell/gpt-4-turbo-2024-04-09-expected.json b/pkg/tests/smoke/testdata/BobAsShell/gpt-4-turbo-2024-04-09-expected.json deleted file mode 100644 index 5b2409f6..00000000 --- a/pkg/tests/smoke/testdata/BobAsShell/gpt-4-turbo-2024-04-09-expected.json +++ /dev/null @@ -1,1549 +0,0 @@ -[ - { - "time": "2024-06-20T17:08:30.778302-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-20T17:08:30.778582-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-20T17:08:30.981266-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917712", - "usage": {}, - "chatRequest": { - "model": "gpt-4-turbo-2024-04-09", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nAsk Bob \"how are you doing\" and repeat his reply exactly." - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T17:08:30.981391-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:08:32.232987-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"" - }, - { - "time": "2024-06-20T17:08:32.233265-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"" - }, - { - "time": "2024-06-20T17:08:32.344744-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"" - }, - { - "time": "2024-06-20T17:08:32.344882-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"" - }, - { - "time": "2024-06-20T17:08:32.361676-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are" - }, - { - "time": "2024-06-20T17:08:32.361793-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are" - }, - { - "time": "2024-06-20T17:08:32.440498-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing" - }, - { - "time": "2024-06-20T17:08:32.440743-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:32.440798-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:32.440836-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:32.440873-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917712", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:32.441115-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917712", - "usage": { - "promptTokens": 142, - "completionTokens": 17, - "totalTokens": 159 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "call_slFgd2P2lMxXQoyrPbm2YsrQ", - "function": { - "name": "bob", - "arguments": "{\"question\":\"how are you doing\"}" - } - } - } - ], - "usage": { - "promptTokens": 142, - "completionTokens": 17, - "totalTokens": 159 - } - } - }, - { - "time": "2024-06-20T17:08:32.441462-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "toolSubCalls": { - "call_slFgd2P2lMxXQoyrPbm2YsrQ": { - "toolID": "testdata/BobAsShell/test.gpt:bob", - "input": "{\"question\":\"how are you doing\"}" - } - }, - "type": "callSubCalls", - "usage": {} - }, - { - "time": "2024-06-20T17:08:32.441542-04:00", - "callContext": { - "id": "call_slFgd2P2lMxXQoyrPbm2YsrQ", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917711", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callStart", - "usage": {}, - "content": "{\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T17:08:32.442736-04:00", - "callContext": { - "id": "call_slFgd2P2lMxXQoyrPbm2YsrQ", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917711", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callChat", - "chatCompletionId": "1718917713", - "usage": {}, - "chatRequest": { - "model": "", - "messages": null - } - }, - { - "time": "2024-06-20T17:08:32.448288-04:00", - "callContext": { - "id": "call_slFgd2P2lMxXQoyrPbm2YsrQ", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917711", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callProgress", - "chatCompletionId": "1718917713", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" - }, - { - "time": "2024-06-20T17:08:32.448728-04:00", - "callContext": { - "id": "call_slFgd2P2lMxXQoyrPbm2YsrQ", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917711", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callChat", - "chatCompletionId": "1718917713", - "usage": {}, - "chatResponse": { - "usage": {} - } - }, - { - "time": "2024-06-20T17:08:32.448906-04:00", - "callContext": { - "id": "call_slFgd2P2lMxXQoyrPbm2YsrQ", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917711", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" - }, - { - "time": "2024-06-20T17:08:32.448977-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "toolResults": 1, - "type": "callContinue", - "usage": {} - }, - { - "time": "2024-06-20T17:08:32.624086-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917714", - "usage": {}, - "chatRequest": { - "model": "gpt-4-turbo-2024-04-09", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nAsk Bob \"how are you doing\" and repeat his reply exactly." - }, - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": "call_slFgd2P2lMxXQoyrPbm2YsrQ", - "type": "function", - "function": { - "name": "bob", - "arguments": "{\"question\":\"how are you doing\"}" - } - } - ] - }, - { - "role": "tool", - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n", - "name": "bob", - "tool_call_id": "call_slFgd2P2lMxXQoyrPbm2YsrQ" - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T17:08:32.624367-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:08:33.020025-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I" - }, - { - "time": "2024-06-20T17:08:33.020187-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I" - }, - { - "time": "2024-06-20T17:08:33.09047-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing" - }, - { - "time": "2024-06-20T17:08:33.090722-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing" - }, - { - "time": "2024-06-20T17:08:33.150983-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing great fellow" - }, - { - "time": "2024-06-20T17:08:33.151128-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing great fellow" - }, - { - "time": "2024-06-20T17:08:33.26424-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing great fellow friendly AI" - }, - { - "time": "2024-06-20T17:08:33.264352-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T17:08:33.264393-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T17:08:33.264427-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:33.264492-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:33.264575-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917714", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:33.264897-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917714", - "usage": { - "promptTokens": 183, - "completionTokens": 10, - "totalTokens": 193 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "I'm doing great fellow friendly AI tool!" - } - ], - "usage": { - "promptTokens": 183, - "completionTokens": 10, - "totalTokens": 193 - } - } - }, - { - "time": "2024-06-20T17:08:33.264985-04:00", - "callContext": { - "id": "1718917711", - "tool": { - "modelName": "gpt-4-turbo-2024-04-09", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callFinish", - "usage": {}, - "content": "I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T17:08:33.265021-04:00", - "type": "runFinish", - "usage": {} - } -] diff --git a/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-2024-05-13-expected.json b/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-2024-05-13-expected.json deleted file mode 100644 index f61edd7d..00000000 --- a/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-2024-05-13-expected.json +++ /dev/null @@ -1,1808 +0,0 @@ -[ - { - "time": "2024-06-20T16:58:14.093283-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-20T16:58:14.093568-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-20T16:58:14.31069-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917096", - "usage": {}, - "chatRequest": { - "model": "gpt-4o-2024-05-13", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nAsk Bob \"how are you doing\" and repeat his reply exactly." - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T16:58:14.311071-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T16:58:14.807492-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"" - }, - { - "time": "2024-06-20T16:58:14.807779-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"" - }, - { - "time": "2024-06-20T16:58:14.832551-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"" - }, - { - "time": "2024-06-20T16:58:14.832684-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"" - }, - { - "time": "2024-06-20T16:58:14.865368-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are" - }, - { - "time": "2024-06-20T16:58:14.865484-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are" - }, - { - "time": "2024-06-20T16:58:14.899511-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing" - }, - { - "time": "2024-06-20T16:58:14.899668-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing" - }, - { - "time": "2024-06-20T16:58:14.900883-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:14.900938-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:14.900969-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917096", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:14.901222-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917096", - "usage": { - "promptTokens": 138, - "completionTokens": 17, - "totalTokens": 155 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "call_PGLxooO6eBPt3eSEBCMkuWYN", - "function": { - "name": "bob", - "arguments": "{\"question\":\"how are you doing\"}" - } - } - } - ], - "usage": { - "promptTokens": 138, - "completionTokens": 17, - "totalTokens": 155 - } - } - }, - { - "time": "2024-06-20T16:58:14.901521-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "toolSubCalls": { - "call_PGLxooO6eBPt3eSEBCMkuWYN": { - "toolID": "testdata/BobAsShell/test.gpt:bob", - "input": "{\"question\":\"how are you doing\"}" - } - }, - "type": "callSubCalls", - "usage": {} - }, - { - "time": "2024-06-20T16:58:14.901599-04:00", - "callContext": { - "id": "call_PGLxooO6eBPt3eSEBCMkuWYN", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917095", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callStart", - "usage": {}, - "content": "{\"question\":\"how are you doing\"}" - }, - { - "time": "2024-06-20T16:58:14.90268-04:00", - "callContext": { - "id": "call_PGLxooO6eBPt3eSEBCMkuWYN", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917095", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callChat", - "chatCompletionId": "1718917097", - "usage": {}, - "chatRequest": { - "model": "", - "messages": null - } - }, - { - "time": "2024-06-20T16:58:14.908538-04:00", - "callContext": { - "id": "call_PGLxooO6eBPt3eSEBCMkuWYN", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917095", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callProgress", - "chatCompletionId": "1718917097", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" - }, - { - "time": "2024-06-20T16:58:14.908991-04:00", - "callContext": { - "id": "call_PGLxooO6eBPt3eSEBCMkuWYN", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917095", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callChat", - "chatCompletionId": "1718917097", - "usage": {}, - "chatResponse": { - "usage": {} - } - }, - { - "time": "2024-06-20T16:58:14.909126-04:00", - "callContext": { - "id": "call_PGLxooO6eBPt3eSEBCMkuWYN", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917095", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" - }, - { - "time": "2024-06-20T16:58:14.909293-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "toolResults": 1, - "type": "callContinue", - "usage": {} - }, - { - "time": "2024-06-20T16:58:15.10962-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917098", - "usage": {}, - "chatRequest": { - "model": "gpt-4o-2024-05-13", - "messages": [ - { - "role": "system", - "content": "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nAsk Bob \"how are you doing\" and repeat his reply exactly." - }, - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": "call_PGLxooO6eBPt3eSEBCMkuWYN", - "type": "function", - "function": { - "name": "bob", - "arguments": "{\"question\":\"how are you doing\"}" - } - } - ] - }, - { - "role": "tool", - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n", - "name": "bob", - "tool_call_id": "call_PGLxooO6eBPt3eSEBCMkuWYN" - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T16:58:15.110087-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T16:58:15.629792-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T16:58:15.629968-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks" - }, - { - "time": "2024-06-20T16:58:15.749206-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for" - }, - { - "time": "2024-06-20T16:58:15.749324-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking" - }, - { - "time": "2024-06-20T16:58:15.780062-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are" - }, - { - "time": "2024-06-20T16:58:15.780171-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you" - }, - { - "time": "2024-06-20T16:58:15.780293-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing" - }, - { - "time": "2024-06-20T16:58:15.780335-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing" - }, - { - "time": "2024-06-20T16:58:15.780406-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm" - }, - { - "time": "2024-06-20T16:58:15.780445-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm" - }, - { - "time": "2024-06-20T16:58:15.780477-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing" - }, - { - "time": "2024-06-20T16:58:15.780511-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great" - }, - { - "time": "2024-06-20T16:58:15.816742-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly" - }, - { - "time": "2024-06-20T16:58:15.816889-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly" - }, - { - "time": "2024-06-20T16:58:15.859699-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T16:58:15.859764-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:15.859784-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:15.859841-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:15.85986-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917098", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:15.860819-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917098", - "usage": { - "promptTokens": 178, - "completionTokens": 17, - "totalTokens": 195 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - } - ], - "usage": { - "promptTokens": 178, - "completionTokens": 17, - "totalTokens": 195 - } - } - }, - { - "time": "2024-06-20T16:58:15.860872-04:00", - "callContext": { - "id": "1718917095", - "tool": { - "modelName": "gpt-4o-2024-05-13", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" - }, - { - "time": "2024-06-20T16:58:15.860919-04:00", - "type": "runFinish", - "usage": {} - } -] diff --git a/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-2024-08-06-expected.json b/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-2024-08-06-expected.json new file mode 100644 index 00000000..91b6a636 --- /dev/null +++ b/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-2024-08-06-expected.json @@ -0,0 +1,528 @@ +[ + { + "time": "2024-10-14T17:38:25.173529-04:00", + "type": "runStart", + "usage": {} + }, + { + "time": "2024-10-14T17:38:25.174285-04:00", + "callContext": { + "id": "1728941906", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callStart", + "usage": {} + }, + { + "time": "2024-10-14T17:38:25.174351-04:00", + "callContext": { + "id": "1728941906", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941907", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:38:26.165696-04:00", + "callContext": { + "id": "1728941906", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941907", + "usage": { + "promptTokens": 145, + "completionTokens": 17, + "totalTokens": 162 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_95p8Knb4mdiEgxt5iXxnxOKC", + "function": { + "name": "bob", + "arguments": "{\"question\":\"how are you doing\"}" + } + } + } + ], + "usage": { + "promptTokens": 145, + "completionTokens": 17, + "totalTokens": 162 + } + } + }, + { + "time": "2024-10-14T17:38:26.165858-04:00", + "callContext": { + "id": "1728941906", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolSubCalls": { + "call_95p8Knb4mdiEgxt5iXxnxOKC": { + "toolID": "testdata/BobAsShell/test.gpt:bob", + "input": "{\"question\":\"how are you doing\"}" + } + }, + "type": "callSubCalls", + "usage": {} + }, + { + "time": "2024-10-14T17:38:26.165964-04:00", + "callContext": { + "id": "call_95p8Knb4mdiEgxt5iXxnxOKC", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941906", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callStart", + "usage": {}, + "content": "{\"question\":\"how are you doing\"}" + }, + { + "time": "2024-10-14T17:38:26.167235-04:00", + "callContext": { + "id": "call_95p8Knb4mdiEgxt5iXxnxOKC", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941906", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callChat", + "chatCompletionId": "1728941908", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:38:26.178131-04:00", + "callContext": { + "id": "call_95p8Knb4mdiEgxt5iXxnxOKC", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941906", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callChat", + "chatCompletionId": "1728941908", + "usage": {}, + "chatResponse": { + "usage": {} + } + }, + { + "time": "2024-10-14T17:38:26.178199-04:00", + "callContext": { + "id": "call_95p8Knb4mdiEgxt5iXxnxOKC", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941906", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" + }, + { + "time": "2024-10-14T17:38:26.178356-04:00", + "callContext": { + "id": "1728941906", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolResults": 1, + "type": "callContinue", + "usage": {} + }, + { + "time": "2024-10-14T17:38:26.178539-04:00", + "callContext": { + "id": "1728941906", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941909", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:38:27.001877-04:00", + "callContext": { + "id": "1728941906", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941909", + "usage": { + "promptTokens": 185, + "completionTokens": 17, + "totalTokens": 202 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" + } + ], + "usage": { + "promptTokens": 185, + "completionTokens": 17, + "totalTokens": 202 + } + } + }, + { + "time": "2024-10-14T17:38:27.001903-04:00", + "callContext": { + "id": "1728941906", + "tool": { + "modelName": "gpt-4o-2024-08-06", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T17:38:27.001932-04:00", + "type": "runFinish", + "usage": {} + } +] diff --git a/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-mini-2024-07-18-expected.json b/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-mini-2024-07-18-expected.json new file mode 100644 index 00000000..bdfee87d --- /dev/null +++ b/pkg/tests/smoke/testdata/BobAsShell/gpt-4o-mini-2024-07-18-expected.json @@ -0,0 +1,528 @@ +[ + { + "time": "2024-10-14T17:37:54.379122-04:00", + "type": "runStart", + "usage": {} + }, + { + "time": "2024-10-14T17:37:54.379631-04:00", + "callContext": { + "id": "1728941875", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callStart", + "usage": {} + }, + { + "time": "2024-10-14T17:37:54.379682-04:00", + "callContext": { + "id": "1728941875", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941876", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:37:55.230509-04:00", + "callContext": { + "id": "1728941875", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941876", + "usage": { + "promptTokens": 145, + "completionTokens": 17, + "totalTokens": 162 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_FInUoOxKSR90EOxzIHXivvSX", + "function": { + "name": "bob", + "arguments": "{\"question\":\"how are you doing\"}" + } + } + } + ], + "usage": { + "promptTokens": 145, + "completionTokens": 17, + "totalTokens": 162 + } + } + }, + { + "time": "2024-10-14T17:37:55.23069-04:00", + "callContext": { + "id": "1728941875", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolSubCalls": { + "call_FInUoOxKSR90EOxzIHXivvSX": { + "toolID": "testdata/BobAsShell/test.gpt:bob", + "input": "{\"question\":\"how are you doing\"}" + } + }, + "type": "callSubCalls", + "usage": {} + }, + { + "time": "2024-10-14T17:37:55.230816-04:00", + "callContext": { + "id": "call_FInUoOxKSR90EOxzIHXivvSX", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941875", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callStart", + "usage": {}, + "content": "{\"question\":\"how are you doing\"}" + }, + { + "time": "2024-10-14T17:37:55.231913-04:00", + "callContext": { + "id": "call_FInUoOxKSR90EOxzIHXivvSX", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941875", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callChat", + "chatCompletionId": "1728941877", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:37:55.245261-04:00", + "callContext": { + "id": "call_FInUoOxKSR90EOxzIHXivvSX", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941875", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callChat", + "chatCompletionId": "1728941877", + "usage": {}, + "chatResponse": { + "usage": {} + } + }, + { + "time": "2024-10-14T17:37:55.245348-04:00", + "callContext": { + "id": "call_FInUoOxKSR90EOxzIHXivvSX", + "tool": { + "name": "bob", + "description": "I'm Bob, a friendly guy.", + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "arguments": { + "properties": { + "question": { + "description": "The question to ask Bob.", + "type": "string" + } + }, + "type": "object" + }, + "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!\"", + "id": "testdata/BobAsShell/test.gpt:bob", + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 6 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null, + "toolName": "bob", + "parentID": "1728941875", + "displayText": "Running bob from testdata/BobAsShell/test.gpt" + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" + }, + { + "time": "2024-10-14T17:37:55.245528-04:00", + "callContext": { + "id": "1728941875", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "toolResults": 1, + "type": "callContinue", + "usage": {} + }, + { + "time": "2024-10-14T17:37:55.245751-04:00", + "callContext": { + "id": "1728941875", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941878", + "usage": {}, + "chatRequest": { + "model": "", + "messages": null + } + }, + { + "time": "2024-10-14T17:37:56.345692-04:00", + "callContext": { + "id": "1728941875", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callChat", + "chatCompletionId": "1728941878", + "usage": { + "promptTokens": 185, + "completionTokens": 17, + "totalTokens": 202 + }, + "chatResponse": { + "role": "assistant", + "content": [ + { + "text": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" + } + ], + "usage": { + "promptTokens": 185, + "completionTokens": 17, + "totalTokens": 202 + } + } + }, + { + "time": "2024-10-14T17:37:56.345742-04:00", + "callContext": { + "id": "1728941875", + "tool": { + "modelName": "gpt-4o-mini-2024-07-18", + "internalPrompt": null, + "tools": [ + "bob" + ], + "instructions": "Ask Bob \"how are you doing\" and repeat the response text exactly as given without saying anything else.", + "id": "testdata/BobAsShell/test.gpt:", + "toolMapping": { + "bob": [ + { + "reference": "bob", + "toolID": "testdata/BobAsShell/test.gpt:bob" + } + ] + }, + "localTools": { + "": "testdata/BobAsShell/test.gpt:", + "bob": "testdata/BobAsShell/test.gpt:bob" + }, + "source": { + "location": "testdata/BobAsShell/test.gpt", + "lineNo": 1 + }, + "workingDir": "testdata/BobAsShell" + }, + "currentAgent": {}, + "inputContext": null + }, + "type": "callFinish", + "usage": {}, + "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!" + }, + { + "time": "2024-10-14T17:37:56.345803-04:00", + "type": "runFinish", + "usage": {} + } +] diff --git a/pkg/tests/smoke/testdata/BobAsShell/mistral-large-2402-expected.json b/pkg/tests/smoke/testdata/BobAsShell/mistral-large-2402-expected.json deleted file mode 100644 index b3e54dc4..00000000 --- a/pkg/tests/smoke/testdata/BobAsShell/mistral-large-2402-expected.json +++ /dev/null @@ -1,1659 +0,0 @@ -[ - { - "time": "2024-06-20T17:10:43.55694-04:00", - "type": "runStart", - "usage": {} - }, - { - "time": "2024-06-20T17:10:43.557263-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callStart", - "usage": {} - }, - { - "time": "2024-06-20T17:10:43.805494-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917845", - "usage": {}, - "chatRequest": { - "model": "mistral-large-2402", - "messages": [ - { - "role": "system", - "content": "Ask Bob \"how are you doing\" and repeat his reply exactly." - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T17:10:43.805682-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917845", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:10:44.078279-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917845", - "usage": {} - }, - { - "time": "2024-06-20T17:10:44.913816-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917845", - "usage": {}, - "content": "\u003ctool call\u003e bob -\u003e {\"question\": \"how are you doing\"}" - }, - { - "time": "2024-06-20T17:10:44.914845-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917845", - "usage": { - "promptTokens": 85, - "completionTokens": 23, - "totalTokens": 108 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "UPthtEfla", - "function": { - "name": "bob", - "arguments": "{\"question\": \"how are you doing\"}" - } - } - } - ], - "usage": { - "promptTokens": 85, - "completionTokens": 23, - "totalTokens": 108 - } - } - }, - { - "time": "2024-06-20T17:10:44.915086-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "toolSubCalls": { - "UPthtEfla": { - "toolID": "testdata/BobAsShell/test.gpt:bob", - "input": "{\"question\": \"how are you doing\"}" - } - }, - "type": "callSubCalls", - "usage": {} - }, - { - "time": "2024-06-20T17:10:44.915147-04:00", - "callContext": { - "id": "UPthtEfla", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917844", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callStart", - "usage": {}, - "content": "{\"question\": \"how are you doing\"}" - }, - { - "time": "2024-06-20T17:10:44.916248-04:00", - "callContext": { - "id": "UPthtEfla", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917844", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callChat", - "chatCompletionId": "1718917846", - "usage": {}, - "chatRequest": { - "model": "", - "messages": null - } - }, - { - "time": "2024-06-20T17:10:44.92245-04:00", - "callContext": { - "id": "UPthtEfla", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917844", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callProgress", - "chatCompletionId": "1718917846", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" - }, - { - "time": "2024-06-20T17:10:44.922905-04:00", - "callContext": { - "id": "UPthtEfla", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917844", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callChat", - "chatCompletionId": "1718917846", - "usage": {}, - "chatResponse": { - "usage": {} - } - }, - { - "time": "2024-06-20T17:10:44.922998-04:00", - "callContext": { - "id": "UPthtEfla", - "tool": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "arguments": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - }, - "instructions": "#!/bin/bash\n\necho \"Thanks for asking ${question}, I'm doing great fellow friendly AI tool!\"", - "id": "testdata/BobAsShell/test.gpt:bob", - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 7 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null, - "toolName": "bob", - "parentID": "1718917844", - "displayText": "Running bob from testdata/BobAsShell/test.gpt" - }, - "type": "callFinish", - "usage": {}, - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n" - }, - { - "time": "2024-06-20T17:10:44.92306-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "toolResults": 1, - "type": "callContinue", - "usage": {} - }, - { - "time": "2024-06-20T17:10:45.091313-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917847", - "usage": {}, - "chatRequest": { - "model": "mistral-large-2402", - "messages": [ - { - "role": "system", - "content": "Ask Bob \"how are you doing\" and repeat his reply exactly." - }, - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "id": "UPthtEfla", - "type": "function", - "function": { - "name": "bob", - "arguments": "{\"question\": \"how are you doing\"}" - } - } - ] - }, - { - "role": "tool", - "content": "Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\n", - "name": "bob", - "tool_call_id": "UPthtEfla" - } - ], - "temperature": 0, - "tools": [ - { - "type": "function", - "function": { - "name": "bob", - "description": "I'm Bob, a friendly guy.", - "parameters": { - "properties": { - "question": { - "description": "The question to ask Bob.", - "type": "string" - } - }, - "type": "object" - } - } - } - ] - } - }, - { - "time": "2024-06-20T17:10:45.091762-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Waiting for model response..." - }, - { - "time": "2024-06-20T17:10:45.427766-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob" - }, - { - "time": "2024-06-20T17:10:45.427886-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied" - }, - { - "time": "2024-06-20T17:10:45.427938-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied," - }, - { - "time": "2024-06-20T17:10:45.427998-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"" - }, - { - "time": "2024-06-20T17:10:45.428017-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"" - }, - { - "time": "2024-06-20T17:10:45.428046-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks" - }, - { - "time": "2024-06-20T17:10:45.434307-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for" - }, - { - "time": "2024-06-20T17:10:45.462196-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking" - }, - { - "time": "2024-06-20T17:10:45.496748-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how" - }, - { - "time": "2024-06-20T17:10:45.516868-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are" - }, - { - "time": "2024-06-20T17:10:45.545808-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you" - }, - { - "time": "2024-06-20T17:10:45.572091-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing" - }, - { - "time": "2024-06-20T17:10:45.602668-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing," - }, - { - "time": "2024-06-20T17:10:45.627618-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I" - }, - { - "time": "2024-06-20T17:10:45.659445-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'" - }, - { - "time": "2024-06-20T17:10:45.682109-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm" - }, - { - "time": "2024-06-20T17:10:45.710817-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing" - }, - { - "time": "2024-06-20T17:10:45.743363-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing great" - }, - { - "time": "2024-06-20T17:10:45.766321-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing great fellow" - }, - { - "time": "2024-06-20T17:10:45.801585-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing great fellow friendly" - }, - { - "time": "2024-06-20T17:10:46.066262-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing great fellow friendly AI" - }, - { - "time": "2024-06-20T17:10:46.093598-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing great fellow friendly AI tool" - }, - { - "time": "2024-06-20T17:10:46.118737-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\"" - }, - { - "time": "2024-06-20T17:10:46.146712-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callProgress", - "chatCompletionId": "1718917847", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\"" - }, - { - "time": "2024-06-20T17:10:46.146789-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callChat", - "chatCompletionId": "1718917847", - "usage": { - "promptTokens": 144, - "completionTokens": 22, - "totalTokens": 166 - }, - "chatResponse": { - "role": "assistant", - "content": [ - { - "text": "Bob replied, \"Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\"" - } - ], - "usage": { - "promptTokens": 144, - "completionTokens": 22, - "totalTokens": 166 - } - } - }, - { - "time": "2024-06-20T17:10:46.146827-04:00", - "callContext": { - "id": "1718917844", - "tool": { - "modelName": "mistral-large-2402 from https://api.mistral.ai/v1", - "internalPrompt": null, - "tools": [ - "bob" - ], - "instructions": "Ask Bob \"how are you doing\" and repeat his reply exactly.", - "id": "testdata/BobAsShell/test.gpt:", - "toolMapping": { - "bob": [ - { - "reference": "bob", - "toolID": "testdata/BobAsShell/test.gpt:bob" - } - ] - }, - "localTools": { - "": "testdata/BobAsShell/test.gpt:", - "bob": "testdata/BobAsShell/test.gpt:bob" - }, - "source": { - "location": "testdata/BobAsShell/test.gpt", - "lineNo": 1 - }, - "workingDir": "testdata/BobAsShell" - }, - "inputContext": null - }, - "type": "callFinish", - "usage": {}, - "content": "Bob replied, \"Thanks for asking how are you doing, I'm doing great fellow friendly AI tool!\"" - }, - { - "time": "2024-06-20T17:10:46.146844-04:00", - "type": "runFinish", - "usage": {} - } -] diff --git a/pkg/tests/smoke/testdata/BobAsShell/test.gpt b/pkg/tests/smoke/testdata/BobAsShell/test.gpt index f04920bf..6fdc514b 100644 --- a/pkg/tests/smoke/testdata/BobAsShell/test.gpt +++ b/pkg/tests/smoke/testdata/BobAsShell/test.gpt @@ -1,7 +1,6 @@ - tools: bob -Ask Bob "how are you doing" and repeat his reply exactly. +Ask Bob "how are you doing" and repeat the response text exactly as given without saying anything else. --- name: bob @@ -10,4 +9,4 @@ args: question: The question to ask Bob. #!/bin/bash -echo "Thanks for asking ${question}, I'm doing great fellow friendly AI tool!" +echo "Thanks for asking ${QUESTION}, I'm doing great fellow friendly AI tool!" diff --git a/pkg/tests/testdata/TestContextSubChat/call8-resp.golden b/pkg/tests/testdata/TestAgentOnly/call1-resp.golden similarity index 55% rename from pkg/tests/testdata/TestContextSubChat/call8-resp.golden rename to pkg/tests/testdata/TestAgentOnly/call1-resp.golden index 2e608b31..ea865122 100644 --- a/pkg/tests/testdata/TestContextSubChat/call8-resp.golden +++ b/pkg/tests/testdata/TestAgentOnly/call1-resp.golden @@ -4,10 +4,10 @@ { "toolCall": { "index": 0, - "id": "call_4", + "id": "call_1", "function": { - "name": "chatFinish", - "arguments": "Response from context chatbot after resume" + "name": "agent2", + "arguments": "Agent 2 input" } } } diff --git a/pkg/tests/testdata/TestAgentOnly/call1.golden b/pkg/tests/testdata/TestAgentOnly/call1.golden new file mode 100644 index 00000000..6fadf4ed --- /dev/null +++ b/pkg/tests/testdata/TestAgentOnly/call1.golden @@ -0,0 +1,42 @@ +`{ + "model": "gpt-4o", + "internalSystemPrompt": false, + "tools": [ + { + "function": { + "toolID": "testdata/TestAgentOnly/test.gpt:agent2", + "name": "agent2", + "parameters": { + "type": "object", + "properties": { + "defaultPromptParameter": { + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." + } + } + } + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "I am agent1" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "Input 1" + } + ], + "usage": {} + } + ], + "chat": true +}` diff --git a/pkg/tests/testdata/TestContextSubChat/call7-resp.golden b/pkg/tests/testdata/TestAgentOnly/call2-resp.golden similarity index 58% rename from pkg/tests/testdata/TestContextSubChat/call7-resp.golden rename to pkg/tests/testdata/TestAgentOnly/call2-resp.golden index 3e0c5f3c..997ca1b9 100644 --- a/pkg/tests/testdata/TestContextSubChat/call7-resp.golden +++ b/pkg/tests/testdata/TestAgentOnly/call2-resp.golden @@ -2,7 +2,7 @@ "role": "assistant", "content": [ { - "text": "Assistant Response 4 - from chatbot1" + "text": "TEST RESULT CALL: 2" } ], "usage": {} diff --git a/pkg/tests/testdata/TestAgentOnly/call2.golden b/pkg/tests/testdata/TestAgentOnly/call2.golden new file mode 100644 index 00000000..909faf53 --- /dev/null +++ b/pkg/tests/testdata/TestAgentOnly/call2.golden @@ -0,0 +1,57 @@ +`{ + "model": "gpt-4o", + "internalSystemPrompt": false, + "tools": [ + { + "function": { + "toolID": "testdata/TestAgentOnly/test.gpt:agent3", + "name": "agent3", + "parameters": { + "type": "object", + "properties": { + "defaultPromptParameter": { + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." + } + } + } + } + }, + { + "function": { + "toolID": "testdata/TestAgentOnly/test.gpt:agent1", + "name": "agent1", + "parameters": { + "type": "object", + "properties": { + "defaultPromptParameter": { + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." + } + } + } + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "I am agent2" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "Agent 2 input" + } + ], + "usage": {} + } + ], + "chat": true +}` diff --git a/pkg/tests/testdata/TestAgentOnly/step1.golden b/pkg/tests/testdata/TestAgentOnly/step1.golden new file mode 100644 index 00000000..9bf8e319 --- /dev/null +++ b/pkg/tests/testdata/TestAgentOnly/step1.golden @@ -0,0 +1,168 @@ +`{ + "done": false, + "content": "TEST RESULT CALL: 2", + "toolID": "testdata/TestAgentOnly/test.gpt:agent2", + "state": { + "continuation": { + "state": { + "input": "Input 1", + "completion": { + "model": "gpt-4o", + "internalSystemPrompt": false, + "tools": [ + { + "function": { + "toolID": "testdata/TestAgentOnly/test.gpt:agent2", + "name": "agent2", + "parameters": { + "type": "object", + "properties": { + "defaultPromptParameter": { + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." + } + } + } + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "I am agent1" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "Input 1" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "agent2", + "arguments": "Agent 2 input" + } + } + } + ], + "usage": {} + } + ], + "chat": true + }, + "pending": { + "call_1": { + "index": 0, + "id": "call_1", + "function": { + "name": "agent2", + "arguments": "Agent 2 input" + } + } + } + }, + "calls": { + "call_1": { + "toolID": "testdata/TestAgentOnly/test.gpt:agent2", + "input": "Agent 2 input" + } + } + }, + "subCalls": [ + { + "toolId": "testdata/TestAgentOnly/test.gpt:agent2", + "callId": "call_1", + "state": { + "continuation": { + "state": { + "input": "Agent 2 input", + "completion": { + "model": "gpt-4o", + "internalSystemPrompt": false, + "tools": [ + { + "function": { + "toolID": "testdata/TestAgentOnly/test.gpt:agent3", + "name": "agent3", + "parameters": { + "type": "object", + "properties": { + "defaultPromptParameter": { + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." + } + } + } + } + }, + { + "function": { + "toolID": "testdata/TestAgentOnly/test.gpt:agent1", + "name": "agent1", + "parameters": { + "type": "object", + "properties": { + "defaultPromptParameter": { + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." + } + } + } + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "I am agent2" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "Agent 2 input" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 2" + } + ], + "usage": {} + } + ], + "chat": true + } + }, + "result": "TEST RESULT CALL: 2" + }, + "continuationToolID": "testdata/TestAgentOnly/test.gpt:agent2" + } + } + ], + "subCallID": "call_1" + } +}` diff --git a/pkg/tests/testdata/TestAgentOnly/test.gpt b/pkg/tests/testdata/TestAgentOnly/test.gpt new file mode 100644 index 00000000..f2ae9f9e --- /dev/null +++ b/pkg/tests/testdata/TestAgentOnly/test.gpt @@ -0,0 +1,20 @@ +agents: agent1, agent2 + +---- +name: agent1 +chat: true + +I am agent1 + +---- +name: agent2 +chat: true +agents: agent3 + +I am agent2 + +--- +name: agent3 +chat: true + +I am agent3 \ No newline at end of file diff --git a/pkg/tests/testdata/TestAgents/call1.golden b/pkg/tests/testdata/TestAgents/call1.golden index d3c4a86d..be85da0e 100644 --- a/pkg/tests/testdata/TestAgents/call1.golden +++ b/pkg/tests/testdata/TestAgents/call1.golden @@ -7,13 +7,13 @@ "toolID": "testdata/TestAgents/test.gpt:agent1", "name": "agent1", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -22,13 +22,13 @@ "toolID": "testdata/TestAgents/test.gpt:agent2", "name": "agent2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestAgents/call2.golden b/pkg/tests/testdata/TestAgents/call2.golden index 950ad2ea..b0c301f8 100644 --- a/pkg/tests/testdata/TestAgents/call2.golden +++ b/pkg/tests/testdata/TestAgents/call2.golden @@ -7,13 +7,13 @@ "toolID": "testdata/TestAgents/test.gpt:agent2", "name": "agent2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestAgents/call3-resp.golden b/pkg/tests/testdata/TestAgents/call3-resp.golden index e2a65c99..7568fc69 100644 --- a/pkg/tests/testdata/TestAgents/call3-resp.golden +++ b/pkg/tests/testdata/TestAgents/call3-resp.golden @@ -3,7 +3,7 @@ "content": [ { "toolCall": { - "index": 1, + "index": 0, "id": "call_3", "function": { "name": "agent3" diff --git a/pkg/tests/testdata/TestAgents/call3.golden b/pkg/tests/testdata/TestAgents/call3.golden index f9b45a1b..237c6c84 100644 --- a/pkg/tests/testdata/TestAgents/call3.golden +++ b/pkg/tests/testdata/TestAgents/call3.golden @@ -4,31 +4,31 @@ "tools": [ { "function": { - "toolID": "testdata/TestAgents/test.gpt:agent1", - "name": "agent1", + "toolID": "testdata/TestAgents/test.gpt:agent3", + "name": "agent3", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, { "function": { - "toolID": "testdata/TestAgents/test.gpt:agent3", - "name": "agent3", + "toolID": "testdata/TestAgents/test.gpt:agent1", + "name": "agent1", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestAgents/step1.golden b/pkg/tests/testdata/TestAgents/step1.golden index 3047e695..d97b6495 100644 --- a/pkg/tests/testdata/TestAgents/step1.golden +++ b/pkg/tests/testdata/TestAgents/step1.golden @@ -15,13 +15,13 @@ "toolID": "testdata/TestAgents/test.gpt:agent1", "name": "agent1", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -30,13 +30,13 @@ "toolID": "testdata/TestAgents/test.gpt:agent2", "name": "agent2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -110,13 +110,13 @@ "toolID": "testdata/TestAgents/test.gpt:agent2", "name": "agent2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -178,31 +178,31 @@ "tools": [ { "function": { - "toolID": "testdata/TestAgents/test.gpt:agent1", - "name": "agent1", + "toolID": "testdata/TestAgents/test.gpt:agent3", + "name": "agent3", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, { "function": { - "toolID": "testdata/TestAgents/test.gpt:agent3", - "name": "agent3", + "toolID": "testdata/TestAgents/test.gpt:agent1", + "name": "agent1", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -222,7 +222,7 @@ "content": [ { "toolCall": { - "index": 1, + "index": 0, "id": "call_3", "function": { "name": "agent3" @@ -237,7 +237,7 @@ }, "pending": { "call_3": { - "index": 1, + "index": 0, "id": "call_3", "function": { "name": "agent3" diff --git a/pkg/tests/testdata/TestAsterick/call1.golden b/pkg/tests/testdata/TestAsterick/call1.golden index 3f2fa0b1..d741da5d 100644 --- a/pkg/tests/testdata/TestAsterick/call1.golden +++ b/pkg/tests/testdata/TestAsterick/call1.golden @@ -6,13 +6,13 @@ "toolID": "testdata/TestAsterick/other.gpt:afoo", "name": "afoo", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -21,13 +21,13 @@ "toolID": "testdata/TestAsterick/other.gpt:a", "name": "a", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestCase/call1.golden b/pkg/tests/testdata/TestCase/call1.golden index 1e6d76a5..c9bbcbb2 100644 --- a/pkg/tests/testdata/TestCase/call1.golden +++ b/pkg/tests/testdata/TestCase/call1.golden @@ -7,13 +7,13 @@ "name": "Bob", "description": "I'm Bob, a friendly guy.", "parameters": { + "type": "object", "properties": { "question": { - "description": "The question to ask Bob.", - "type": "string" + "type": "string", + "description": "The question to ask Bob." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestCase2/call1.golden b/pkg/tests/testdata/TestCase2/call1.golden index 581e03f5..fc7a5728 100644 --- a/pkg/tests/testdata/TestCase2/call1.golden +++ b/pkg/tests/testdata/TestCase2/call1.golden @@ -4,16 +4,16 @@ { "function": { "toolID": "testdata/TestCase2/test.gpt:bob", - "name": "Bob", + "name": "bob", "description": "I'm Bob, a friendly guy.", "parameters": { + "type": "object", "properties": { "question": { - "description": "The question to ask Bob.", - "type": "string" + "type": "string", + "description": "The question to ask Bob." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestContextArg/other.gpt b/pkg/tests/testdata/TestContextArg/other.gpt index b1acd66a..f97b4ba6 100644 --- a/pkg/tests/testdata/TestContextArg/other.gpt +++ b/pkg/tests/testdata/TestContextArg/other.gpt @@ -2,5 +2,5 @@ name: fromcontext args: first: an arg args: second: an arg -#!/bin/bash -echo this is from other context ${first} and then ${second} \ No newline at end of file +#!/usr/bin/env bash +echo this is from other context ${FIRST} and then ${SECOND} \ No newline at end of file diff --git a/pkg/tests/testdata/TestContextArg/test.gpt b/pkg/tests/testdata/TestContextArg/test.gpt index 9569aaf9..50d2ccf2 100644 --- a/pkg/tests/testdata/TestContextArg/test.gpt +++ b/pkg/tests/testdata/TestContextArg/test.gpt @@ -9,4 +9,4 @@ name: fromcontext args: first: an arg #!/bin/bash -echo this is from context -- ${first} \ No newline at end of file +echo this is from context -- ${FIRST} \ No newline at end of file diff --git a/pkg/tests/testdata/TestContextSubChat/call5-resp.golden b/pkg/tests/testdata/TestContextShareBug/call1-resp.golden similarity index 55% rename from pkg/tests/testdata/TestContextSubChat/call5-resp.golden rename to pkg/tests/testdata/TestContextShareBug/call1-resp.golden index e49a8481..2861a036 100644 --- a/pkg/tests/testdata/TestContextSubChat/call5-resp.golden +++ b/pkg/tests/testdata/TestContextShareBug/call1-resp.golden @@ -2,7 +2,7 @@ "role": "assistant", "content": [ { - "text": "Assistant Response 3 - from main chat tool" + "text": "TEST RESULT CALL: 1" } ], "usage": {} diff --git a/pkg/tests/testdata/TestContextSubChat/call5.golden b/pkg/tests/testdata/TestContextShareBug/call1.golden similarity index 75% rename from pkg/tests/testdata/TestContextSubChat/call5.golden rename to pkg/tests/testdata/TestContextShareBug/call1.golden index 2b8cf41e..0a46f0ca 100644 --- a/pkg/tests/testdata/TestContextSubChat/call5.golden +++ b/pkg/tests/testdata/TestContextShareBug/call1.golden @@ -6,7 +6,7 @@ "role": "system", "content": [ { - "text": "Assistant Response 2 - from context tool\nHello" + "text": "\nYo dawg\nSay hi" } ], "usage": {} @@ -15,7 +15,7 @@ "role": "user", "content": [ { - "text": "User 1" + "text": "input 1" } ], "usage": {} diff --git a/pkg/tests/testdata/TestContextShareBug/step1.golden b/pkg/tests/testdata/TestContextShareBug/step1.golden new file mode 100644 index 00000000..cb17be6d --- /dev/null +++ b/pkg/tests/testdata/TestContextShareBug/step1.golden @@ -0,0 +1,48 @@ +`{ + "done": false, + "content": "TEST RESULT CALL: 1", + "toolID": "inline:", + "state": { + "continuation": { + "state": { + "input": "input 1", + "completion": { + "model": "gpt-4o", + "internalSystemPrompt": false, + "messages": [ + { + "role": "system", + "content": [ + { + "text": "\nYo dawg\nSay hi" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "input 1" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} + } + ], + "chat": true + } + }, + "result": "TEST RESULT CALL: 1" + }, + "continuationToolID": "inline:" + } +}` diff --git a/pkg/tests/testdata/TestContextSubChat/call1.golden b/pkg/tests/testdata/TestContextSubChat/call1.golden index 225401db..fd641ecc 100644 --- a/pkg/tests/testdata/TestContextSubChat/call1.golden +++ b/pkg/tests/testdata/TestContextSubChat/call1.golden @@ -6,13 +6,13 @@ "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestContextSubChat/call10-resp.golden b/pkg/tests/testdata/TestContextSubChat/call10-resp.golden deleted file mode 100644 index 144ca8d9..00000000 --- a/pkg/tests/testdata/TestContextSubChat/call10-resp.golden +++ /dev/null @@ -1,9 +0,0 @@ -`{ - "role": "assistant", - "content": [ - { - "text": "Assistant Response 6 - from main chat tool resume" - } - ], - "usage": {} -}` diff --git a/pkg/tests/testdata/TestContextSubChat/call2.golden b/pkg/tests/testdata/TestContextSubChat/call2.golden index a6cf25c6..b5f30991 100644 --- a/pkg/tests/testdata/TestContextSubChat/call2.golden +++ b/pkg/tests/testdata/TestContextSubChat/call2.golden @@ -8,13 +8,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestContextSubChat/call3.golden b/pkg/tests/testdata/TestContextSubChat/call3.golden deleted file mode 100644 index 55ad402f..00000000 --- a/pkg/tests/testdata/TestContextSubChat/call3.golden +++ /dev/null @@ -1,61 +0,0 @@ -`{ - "model": "gpt-4o", - "internalSystemPrompt": false, - "tools": [ - { - "function": { - "toolID": "sys.chat.finish", - "name": "chatFinish", - "description": "Concludes the conversation. This can not be used to ask a question.", - "parameters": { - "properties": { - "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "This is a chatbot" - } - ], - "usage": {} - }, - { - "role": "user", - "content": [ - { - "text": "Input to chatbot1" - } - ], - "usage": {} - }, - { - "role": "assistant", - "content": [ - { - "text": "Assistant Response 1 - from chatbot1" - } - ], - "usage": {} - }, - { - "role": "user", - "content": [ - { - "text": "User 1" - } - ], - "usage": {} - } - ], - "chat": true -}` diff --git a/pkg/tests/testdata/TestContextSubChat/call6.golden b/pkg/tests/testdata/TestContextSubChat/call6.golden deleted file mode 100644 index 225401db..00000000 --- a/pkg/tests/testdata/TestContextSubChat/call6.golden +++ /dev/null @@ -1,31 +0,0 @@ -`{ - "model": "gpt-4o", - "tools": [ - { - "function": { - "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", - "name": "chatbot", - "parameters": { - "properties": { - "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "Call chatbot" - } - ], - "usage": {} - } - ] -}` diff --git a/pkg/tests/testdata/TestContextSubChat/call7.golden b/pkg/tests/testdata/TestContextSubChat/call7.golden deleted file mode 100644 index b0ef4e39..00000000 --- a/pkg/tests/testdata/TestContextSubChat/call7.golden +++ /dev/null @@ -1,43 +0,0 @@ -`{ - "model": "gpt-4o", - "internalSystemPrompt": false, - "tools": [ - { - "function": { - "toolID": "sys.chat.finish", - "name": "chatFinish", - "description": "Concludes the conversation. This can not be used to ask a question.", - "parameters": { - "properties": { - "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "This is a chatbot" - } - ], - "usage": {} - }, - { - "role": "user", - "content": [ - { - "text": "Input to chatbot1 on resume" - } - ], - "usage": {} - } - ], - "chat": true -}` diff --git a/pkg/tests/testdata/TestContextSubChat/call8.golden b/pkg/tests/testdata/TestContextSubChat/call8.golden deleted file mode 100644 index 3d0db61b..00000000 --- a/pkg/tests/testdata/TestContextSubChat/call8.golden +++ /dev/null @@ -1,61 +0,0 @@ -`{ - "model": "gpt-4o", - "internalSystemPrompt": false, - "tools": [ - { - "function": { - "toolID": "sys.chat.finish", - "name": "chatFinish", - "description": "Concludes the conversation. This can not be used to ask a question.", - "parameters": { - "properties": { - "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "This is a chatbot" - } - ], - "usage": {} - }, - { - "role": "user", - "content": [ - { - "text": "Input to chatbot1 on resume" - } - ], - "usage": {} - }, - { - "role": "assistant", - "content": [ - { - "text": "Assistant Response 4 - from chatbot1" - } - ], - "usage": {} - }, - { - "role": "user", - "content": [ - { - "text": "User 4" - } - ], - "usage": {} - } - ], - "chat": true -}` diff --git a/pkg/tests/testdata/TestContextSubChat/call9.golden b/pkg/tests/testdata/TestContextSubChat/call9.golden deleted file mode 100644 index 33768f26..00000000 --- a/pkg/tests/testdata/TestContextSubChat/call9.golden +++ /dev/null @@ -1,64 +0,0 @@ -`{ - "model": "gpt-4o", - "tools": [ - { - "function": { - "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", - "name": "chatbot", - "parameters": { - "properties": { - "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "Call chatbot" - } - ], - "usage": {} - }, - { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "call_3", - "function": { - "name": "chatbot", - "arguments": "Input to chatbot1 on resume" - } - } - } - ], - "usage": {} - }, - { - "role": "tool", - "content": [ - { - "text": "Response from context chatbot after resume" - } - ], - "toolCall": { - "index": 0, - "id": "call_3", - "function": { - "name": "chatbot", - "arguments": "Input to chatbot1 on resume" - } - }, - "usage": {} - } - ] -}` diff --git a/pkg/tests/testdata/TestContextSubChat/step1.golden b/pkg/tests/testdata/TestContextSubChat/step1.golden deleted file mode 100644 index 2ffb138e..00000000 --- a/pkg/tests/testdata/TestContextSubChat/step1.golden +++ /dev/null @@ -1,146 +0,0 @@ -`{ - "done": false, - "content": "Assistant Response 1 - from chatbot1", - "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", - "state": { - "inputContextContinuation": { - "continuation": { - "state": { - "completion": { - "model": "gpt-4o", - "tools": [ - { - "function": { - "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", - "name": "chatbot", - "parameters": { - "properties": { - "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "Call chatbot" - } - ], - "usage": {} - }, - { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "call_1", - "function": { - "name": "chatbot", - "arguments": "Input to chatbot1" - } - } - } - ], - "usage": {} - } - ] - }, - "pending": { - "call_1": { - "index": 0, - "id": "call_1", - "function": { - "name": "chatbot", - "arguments": "Input to chatbot1" - } - } - } - }, - "calls": { - "call_1": { - "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", - "input": "Input to chatbot1" - } - } - }, - "subCalls": [ - { - "toolId": "testdata/TestContextSubChat/test.gpt:chatbot", - "callId": "call_1", - "state": { - "continuation": { - "state": { - "input": "Input to chatbot1", - "completion": { - "model": "gpt-4o", - "internalSystemPrompt": false, - "tools": [ - { - "function": { - "toolID": "sys.chat.finish", - "name": "chatFinish", - "description": "Concludes the conversation. This can not be used to ask a question.", - "parameters": { - "properties": { - "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "This is a chatbot" - } - ], - "usage": {} - }, - { - "role": "user", - "content": [ - { - "text": "Input to chatbot1" - } - ], - "usage": {} - }, - { - "role": "assistant", - "content": [ - { - "text": "Assistant Response 1 - from chatbot1" - } - ], - "usage": {} - } - ], - "chat": true - } - }, - "result": "Assistant Response 1 - from chatbot1" - }, - "continuationToolID": "testdata/TestContextSubChat/test.gpt:chatbot" - } - } - ], - "subCallID": "call_1" - }, - "inputContextContinuationInput": "User 1", - "startContinuation": true - } -}` diff --git a/pkg/tests/testdata/TestContextSubChat/step3.golden b/pkg/tests/testdata/TestContextSubChat/step3.golden deleted file mode 100644 index 0ccb188b..00000000 --- a/pkg/tests/testdata/TestContextSubChat/step3.golden +++ /dev/null @@ -1,188 +0,0 @@ -`{ - "done": false, - "content": "Assistant Response 3 - from main chat tool", - "toolID": "testdata/TestContextSubChat/test.gpt:", - "state": { - "continuation": { - "state": { - "input": "User 1", - "completion": { - "model": "gpt-4o", - "internalSystemPrompt": false, - "messages": [ - { - "role": "system", - "content": [ - { - "text": "Assistant Response 2 - from context tool\nHello" - } - ], - "usage": {} - }, - { - "role": "user", - "content": [ - { - "text": "User 1" - } - ], - "usage": {} - }, - { - "role": "assistant", - "content": [ - { - "text": "Assistant Response 3 - from main chat tool" - } - ], - "usage": {} - } - ], - "chat": true - } - }, - "result": "Assistant Response 3 - from main chat tool" - }, - "continuationToolID": "testdata/TestContextSubChat/test.gpt:", - "resumeInput": "User 3", - "inputContextContinuation": { - "continuation": { - "state": { - "completion": { - "model": "gpt-4o", - "tools": [ - { - "function": { - "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", - "name": "chatbot", - "parameters": { - "properties": { - "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "Call chatbot" - } - ], - "usage": {} - }, - { - "role": "assistant", - "content": [ - { - "toolCall": { - "index": 0, - "id": "call_3", - "function": { - "name": "chatbot", - "arguments": "Input to chatbot1 on resume" - } - } - } - ], - "usage": {} - } - ] - }, - "pending": { - "call_3": { - "index": 0, - "id": "call_3", - "function": { - "name": "chatbot", - "arguments": "Input to chatbot1 on resume" - } - } - } - }, - "calls": { - "call_3": { - "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", - "input": "Input to chatbot1 on resume" - } - } - }, - "subCalls": [ - { - "toolId": "testdata/TestContextSubChat/test.gpt:chatbot", - "callId": "call_3", - "state": { - "continuation": { - "state": { - "input": "Input to chatbot1 on resume", - "completion": { - "model": "gpt-4o", - "internalSystemPrompt": false, - "tools": [ - { - "function": { - "toolID": "sys.chat.finish", - "name": "chatFinish", - "description": "Concludes the conversation. This can not be used to ask a question.", - "parameters": { - "properties": { - "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" - } - }, - "type": "object" - } - } - } - ], - "messages": [ - { - "role": "system", - "content": [ - { - "text": "This is a chatbot" - } - ], - "usage": {} - }, - { - "role": "user", - "content": [ - { - "text": "Input to chatbot1 on resume" - } - ], - "usage": {} - }, - { - "role": "assistant", - "content": [ - { - "text": "Assistant Response 4 - from chatbot1" - } - ], - "usage": {} - } - ], - "chat": true - } - }, - "result": "Assistant Response 4 - from chatbot1" - }, - "continuationToolID": "testdata/TestContextSubChat/test.gpt:chatbot" - } - } - ], - "subCallID": "call_3" - }, - "inputContextContinuationInput": "User 1", - "inputContextContinuationResumeInput": "User 3" - } -}` diff --git a/pkg/tests/testdata/TestContextSubChat/call9-resp.golden b/pkg/tests/testdata/TestContextWithAsterick/call1-resp.golden similarity index 53% rename from pkg/tests/testdata/TestContextSubChat/call9-resp.golden rename to pkg/tests/testdata/TestContextWithAsterick/call1-resp.golden index 4424246d..2861a036 100644 --- a/pkg/tests/testdata/TestContextSubChat/call9-resp.golden +++ b/pkg/tests/testdata/TestContextWithAsterick/call1-resp.golden @@ -2,7 +2,7 @@ "role": "assistant", "content": [ { - "text": "Assistant Response 5 - from context tool resume" + "text": "TEST RESULT CALL: 1" } ], "usage": {} diff --git a/pkg/tests/testdata/TestContextWithAsterick/call1.golden b/pkg/tests/testdata/TestContextWithAsterick/call1.golden new file mode 100644 index 00000000..6d9538ce --- /dev/null +++ b/pkg/tests/testdata/TestContextWithAsterick/call1.golden @@ -0,0 +1,25 @@ +`{ + "model": "gpt-4o", + "internalSystemPrompt": false, + "messages": [ + { + "role": "system", + "content": [ + { + "text": "This is the input: input 1\n\nSay hi" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "input 1" + } + ], + "usage": {} + } + ], + "chat": true +}` diff --git a/pkg/tests/testdata/TestContextWithAsterick/call2-resp.golden b/pkg/tests/testdata/TestContextWithAsterick/call2-resp.golden new file mode 100644 index 00000000..997ca1b9 --- /dev/null +++ b/pkg/tests/testdata/TestContextWithAsterick/call2-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 2" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestContextSubChat/call10.golden b/pkg/tests/testdata/TestContextWithAsterick/call2.golden similarity index 72% rename from pkg/tests/testdata/TestContextSubChat/call10.golden rename to pkg/tests/testdata/TestContextWithAsterick/call2.golden index c8c98651..f159014c 100644 --- a/pkg/tests/testdata/TestContextSubChat/call10.golden +++ b/pkg/tests/testdata/TestContextWithAsterick/call2.golden @@ -6,7 +6,7 @@ "role": "system", "content": [ { - "text": "Assistant Response 5 - from context tool resume\nHello" + "text": "This is the input: input 2\n\nSay hi" } ], "usage": {} @@ -15,7 +15,7 @@ "role": "user", "content": [ { - "text": "User 1" + "text": "input 1" } ], "usage": {} @@ -24,7 +24,7 @@ "role": "assistant", "content": [ { - "text": "Assistant Response 3 - from main chat tool" + "text": "TEST RESULT CALL: 1" } ], "usage": {} @@ -33,7 +33,7 @@ "role": "user", "content": [ { - "text": "User 3" + "text": "input 2" } ], "usage": {} diff --git a/pkg/tests/testdata/TestContextWithAsterick/step1.golden b/pkg/tests/testdata/TestContextWithAsterick/step1.golden new file mode 100644 index 00000000..cc42e6a6 --- /dev/null +++ b/pkg/tests/testdata/TestContextWithAsterick/step1.golden @@ -0,0 +1,48 @@ +`{ + "done": false, + "content": "TEST RESULT CALL: 1", + "toolID": "inline:", + "state": { + "continuation": { + "state": { + "input": "input 1", + "completion": { + "model": "gpt-4o", + "internalSystemPrompt": false, + "messages": [ + { + "role": "system", + "content": [ + { + "text": "This is the input: input 1\n\nSay hi" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "input 1" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} + } + ], + "chat": true + } + }, + "result": "TEST RESULT CALL: 1" + }, + "continuationToolID": "inline:" + } +}` diff --git a/pkg/tests/testdata/TestContextSubChat/step4.golden b/pkg/tests/testdata/TestContextWithAsterick/step2.golden similarity index 65% rename from pkg/tests/testdata/TestContextSubChat/step4.golden rename to pkg/tests/testdata/TestContextWithAsterick/step2.golden index 5e95d626..02bc92fe 100644 --- a/pkg/tests/testdata/TestContextSubChat/step4.golden +++ b/pkg/tests/testdata/TestContextWithAsterick/step2.golden @@ -1,11 +1,11 @@ `{ "done": false, - "content": "Assistant Response 6 - from main chat tool resume", - "toolID": "testdata/TestContextSubChat/test.gpt:", + "content": "TEST RESULT CALL: 2", + "toolID": "inline:", "state": { "continuation": { "state": { - "input": "User 1", + "input": "input 1", "completion": { "model": "gpt-4o", "internalSystemPrompt": false, @@ -14,7 +14,7 @@ "role": "system", "content": [ { - "text": "Assistant Response 5 - from context tool resume\nHello" + "text": "This is the input: input 2\n\nSay hi" } ], "usage": {} @@ -23,7 +23,7 @@ "role": "user", "content": [ { - "text": "User 1" + "text": "input 1" } ], "usage": {} @@ -32,7 +32,7 @@ "role": "assistant", "content": [ { - "text": "Assistant Response 3 - from main chat tool" + "text": "TEST RESULT CALL: 1" } ], "usage": {} @@ -41,7 +41,7 @@ "role": "user", "content": [ { - "text": "User 3" + "text": "input 2" } ], "usage": {} @@ -50,7 +50,7 @@ "role": "assistant", "content": [ { - "text": "Assistant Response 6 - from main chat tool resume" + "text": "TEST RESULT CALL: 2" } ], "usage": {} @@ -59,8 +59,8 @@ "chat": true } }, - "result": "Assistant Response 6 - from main chat tool resume" + "result": "TEST RESULT CALL: 2" }, - "continuationToolID": "testdata/TestContextSubChat/test.gpt:" + "continuationToolID": "inline:" } }` diff --git a/pkg/tests/testdata/TestCwd/subtool/test.gpt b/pkg/tests/testdata/TestCwd/subtool/test.gpt index 41314bbe..29cf1ed0 100644 --- a/pkg/tests/testdata/TestCwd/subtool/test.gpt +++ b/pkg/tests/testdata/TestCwd/subtool/test.gpt @@ -1,6 +1,6 @@ # #!/usr/bin/env X=${GPTSCRIPT_TOOL_DIR} /bin/bash -set -e -x +set -e [ ${X} = ${GPTSCRIPT_TOOL_DIR} ] cd $X diff --git a/pkg/tests/testdata/TestCwd/test.gpt b/pkg/tests/testdata/TestCwd/test.gpt index 5185635a..e053571c 100644 --- a/pkg/tests/testdata/TestCwd/test.gpt +++ b/pkg/tests/testdata/TestCwd/test.gpt @@ -6,7 +6,7 @@ noop name: local #!/bin/bash -set -e -x +set -e [ "" = "${TOOL_DIR}" ] P=$(pwd) diff --git a/pkg/tests/testdata/TestDualSubChat/call1.golden b/pkg/tests/testdata/TestDualSubChat/call1.golden index 2baf798a..52ab033a 100644 --- a/pkg/tests/testdata/TestDualSubChat/call1.golden +++ b/pkg/tests/testdata/TestDualSubChat/call1.golden @@ -6,13 +6,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -21,13 +21,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot2", "name": "chatbot2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/call2.golden b/pkg/tests/testdata/TestDualSubChat/call2.golden index a6cf25c6..b5f30991 100644 --- a/pkg/tests/testdata/TestDualSubChat/call2.golden +++ b/pkg/tests/testdata/TestDualSubChat/call2.golden @@ -8,13 +8,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/call3.golden b/pkg/tests/testdata/TestDualSubChat/call3.golden index ddcc81c9..ca431fc8 100644 --- a/pkg/tests/testdata/TestDualSubChat/call3.golden +++ b/pkg/tests/testdata/TestDualSubChat/call3.golden @@ -8,13 +8,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/call4.golden b/pkg/tests/testdata/TestDualSubChat/call4.golden index 600e3ba5..580fdb86 100644 --- a/pkg/tests/testdata/TestDualSubChat/call4.golden +++ b/pkg/tests/testdata/TestDualSubChat/call4.golden @@ -8,13 +8,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/call5.golden b/pkg/tests/testdata/TestDualSubChat/call5.golden index 54823934..de6a4a77 100644 --- a/pkg/tests/testdata/TestDualSubChat/call5.golden +++ b/pkg/tests/testdata/TestDualSubChat/call5.golden @@ -8,13 +8,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/call6.golden b/pkg/tests/testdata/TestDualSubChat/call6.golden index a9a8a1c4..d3bbb31d 100644 --- a/pkg/tests/testdata/TestDualSubChat/call6.golden +++ b/pkg/tests/testdata/TestDualSubChat/call6.golden @@ -8,13 +8,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/call7.golden b/pkg/tests/testdata/TestDualSubChat/call7.golden index ff19a0e8..de734587 100644 --- a/pkg/tests/testdata/TestDualSubChat/call7.golden +++ b/pkg/tests/testdata/TestDualSubChat/call7.golden @@ -6,13 +6,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -21,13 +21,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot2", "name": "chatbot2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/step1.golden b/pkg/tests/testdata/TestDualSubChat/step1.golden index f29dfd60..b866f976 100644 --- a/pkg/tests/testdata/TestDualSubChat/step1.golden +++ b/pkg/tests/testdata/TestDualSubChat/step1.golden @@ -14,13 +14,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -29,13 +29,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot2", "name": "chatbot2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -135,13 +135,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } @@ -200,13 +200,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/step2.golden b/pkg/tests/testdata/TestDualSubChat/step2.golden index 830a8e7c..8876c290 100644 --- a/pkg/tests/testdata/TestDualSubChat/step2.golden +++ b/pkg/tests/testdata/TestDualSubChat/step2.golden @@ -14,13 +14,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -29,13 +29,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot2", "name": "chatbot2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -142,13 +142,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestDualSubChat/step3.golden b/pkg/tests/testdata/TestDualSubChat/step3.golden index 4f3b415a..1876df5b 100644 --- a/pkg/tests/testdata/TestDualSubChat/step3.golden +++ b/pkg/tests/testdata/TestDualSubChat/step3.golden @@ -14,13 +14,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -29,13 +29,13 @@ "toolID": "testdata/TestDualSubChat/test.gpt:chatbot2", "name": "chatbot2", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -142,13 +142,13 @@ "name": "chatFinish", "description": "Concludes the conversation. This can not be used to ask a question.", "parameters": { + "type": "object", "properties": { "return": { - "description": "The instructed value to return or a summary of the dialog if no value is instructed", - "type": "string" + "type": "string", + "description": "The instructed value to return or a summary of the dialog if no value is instructed" } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestEnvOverflow/context.json b/pkg/tests/testdata/TestEnvOverflow/context.json new file mode 100644 index 00000000..eb99ddb6 --- /dev/null +++ b/pkg/tests/testdata/TestEnvOverflow/context.json @@ -0,0 +1 @@ +{"_gz":"H4sIAAAAAAAA/+zAgQAAAADCMNb8JQK4wjYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHgAA//+94pKFQBkBAA=="} diff --git a/pkg/tests/testdata/TestEnvOverflow/test.gpt b/pkg/tests/testdata/TestEnvOverflow/test.gpt new file mode 100644 index 00000000..406e6de7 --- /dev/null +++ b/pkg/tests/testdata/TestEnvOverflow/test.gpt @@ -0,0 +1,14 @@ +context: c + +#!/bin/bash + +echo "${GPTSCRIPT_CONTEXT}" +echo "${GPTSCRIPT_CONTEXT}" > ${GPTSCRIPT_TOOL_DIR}/context.json + +--- +name: c + +#!/bin/bash + +string=$(printf 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%.0s' {1..1000}) +echo "$string" \ No newline at end of file diff --git a/pkg/tests/testdata/TestExport/call1-resp.golden b/pkg/tests/testdata/TestExport/call1-resp.golden index 8462d188..7fe59586 100644 --- a/pkg/tests/testdata/TestExport/call1-resp.golden +++ b/pkg/tests/testdata/TestExport/call1-resp.golden @@ -3,7 +3,7 @@ "content": [ { "toolCall": { - "index": 2, + "index": 1, "id": "call_1", "function": { "name": "transient" diff --git a/pkg/tests/testdata/TestExport/call1.golden b/pkg/tests/testdata/TestExport/call1.golden index 9f8b650d..8b663ff3 100644 --- a/pkg/tests/testdata/TestExport/call1.golden +++ b/pkg/tests/testdata/TestExport/call1.golden @@ -6,43 +6,43 @@ "toolID": "testdata/TestExport/parent.gpt:frommain", "name": "frommain", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } }, { "function": { - "toolID": "testdata/TestExport/parent.gpt:parent-local", - "name": "parentLocal", + "toolID": "testdata/TestExport/sub/child.gpt:transient", + "name": "transient", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } }, { "function": { - "toolID": "testdata/TestExport/sub/child.gpt:transient", - "name": "transient", + "toolID": "testdata/TestExport/parent.gpt:parent-local", + "name": "parentLocal", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestExport/call3.golden b/pkg/tests/testdata/TestExport/call3.golden index ccf7e980..181980aa 100644 --- a/pkg/tests/testdata/TestExport/call3.golden +++ b/pkg/tests/testdata/TestExport/call3.golden @@ -6,43 +6,43 @@ "toolID": "testdata/TestExport/parent.gpt:frommain", "name": "frommain", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } }, { "function": { - "toolID": "testdata/TestExport/parent.gpt:parent-local", - "name": "parentLocal", + "toolID": "testdata/TestExport/sub/child.gpt:transient", + "name": "transient", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } }, { "function": { - "toolID": "testdata/TestExport/sub/child.gpt:transient", - "name": "transient", + "toolID": "testdata/TestExport/parent.gpt:parent-local", + "name": "parentLocal", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } } @@ -62,7 +62,7 @@ "content": [ { "toolCall": { - "index": 2, + "index": 1, "id": "call_1", "function": { "name": "transient" @@ -80,7 +80,7 @@ } ], "toolCall": { - "index": 2, + "index": 1, "id": "call_1", "function": { "name": "transient" diff --git a/pkg/tests/testdata/TestExportContext/call1.golden b/pkg/tests/testdata/TestExportContext/call1.golden index bec15478..7ba20676 100644 --- a/pkg/tests/testdata/TestExportContext/call1.golden +++ b/pkg/tests/testdata/TestExportContext/call1.golden @@ -6,13 +6,13 @@ "toolID": "testdata/TestExportContext/test.gpt:subtool", "name": "subtool", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -22,13 +22,13 @@ "name": "sampletool", "description": "sample", "parameters": { + "type": "object", "properties": { "foo": { - "description": "foo description", - "type": "string" + "type": "string", + "description": "foo description" } - }, - "type": "object" + } } } } @@ -38,7 +38,7 @@ "role": "system", "content": [ { - "text": "this is from external context\nthis is from context\nThis is from tool" + "text": "this is from context\nthis is from external context\nThis is from tool" } ], "usage": {} diff --git a/pkg/tests/testdata/TestFilterArgs/step1.golden b/pkg/tests/testdata/TestFilterArgs/step1.golden new file mode 100644 index 00000000..a6e6599b --- /dev/null +++ b/pkg/tests/testdata/TestFilterArgs/step1.golden @@ -0,0 +1,6 @@ +`{ + "done": true, + "content": "{\"chat\":false,\"continuation\":false,\"notfoo\":\"baz\",\"output\":\"{\\\"chat\\\":false,\\\"continuation\\\":false,\\\"notfoo\\\":\\\"foo\\\",\\\"output\\\":\\\"{\\\\\\\"chat\\\\\\\":false,\\\\\\\"continuation\\\\\\\":false,\\\\\\\"foo\\\\\\\":\\\\\\\"baz\\\\\\\",\\\\\\\"input\\\\\\\":\\\\\\\"{\\\\\\\\\\\\\\\"foo\\\\\\\\\\\\\\\":\\\\\\\\\\\\\\\"baz\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"input\\\\\\\\\\\\\\\":\\\\\\\\\\\\\\\"{\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"foo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\":\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"baz\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\", \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"start\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\": true}\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"notfoo\\\\\\\\\\\\\\\":\\\\\\\\\\\\\\\"baz\\\\\\\\\\\\\\\",\\\\\\\\\\\\\\\"start\\\\\\\\\\\\\\\":true}\\\\\\\\n\\\\\\\",\\\\\\\"notfoo\\\\\\\":\\\\\\\"foo\\\\\\\",\\\\\\\"output\\\\\\\":\\\\\\\"baz\\\\\\\\n\\\\\\\",\\\\\\\"start\\\\\\\":true}\\\\n\\\"}\\n\"}\n", + "toolID": "", + "state": null +}` diff --git a/pkg/tests/testdata/TestInputFilterMoreArgs/call1-resp.golden b/pkg/tests/testdata/TestInputFilterMoreArgs/call1-resp.golden new file mode 100644 index 00000000..2861a036 --- /dev/null +++ b/pkg/tests/testdata/TestInputFilterMoreArgs/call1-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestInputFilterMoreArgs/call1.golden b/pkg/tests/testdata/TestInputFilterMoreArgs/call1.golden new file mode 100644 index 00000000..30693444 --- /dev/null +++ b/pkg/tests/testdata/TestInputFilterMoreArgs/call1.golden @@ -0,0 +1,25 @@ +`{ + "model": "gpt-4o", + "internalSystemPrompt": false, + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Say hi" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "123:{\"foo\":\"123\"}\n" + } + ], + "usage": {} + } + ], + "chat": true +}` diff --git a/pkg/tests/testdata/TestInputFilterMoreArgs/call2-resp.golden b/pkg/tests/testdata/TestInputFilterMoreArgs/call2-resp.golden new file mode 100644 index 00000000..997ca1b9 --- /dev/null +++ b/pkg/tests/testdata/TestInputFilterMoreArgs/call2-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 2" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestInputFilterMoreArgs/call2.golden b/pkg/tests/testdata/TestInputFilterMoreArgs/call2.golden new file mode 100644 index 00000000..5a39730d --- /dev/null +++ b/pkg/tests/testdata/TestInputFilterMoreArgs/call2.golden @@ -0,0 +1,25 @@ +`{ + "model": "gpt-4o", + "internalSystemPrompt": false, + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Say hi" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": ":\"foo\":\"123\"}\n" + } + ], + "usage": {} + } + ], + "chat": true +}` diff --git a/pkg/tests/testdata/TestInputFilterMoreArgs/step1.golden b/pkg/tests/testdata/TestInputFilterMoreArgs/step1.golden new file mode 100644 index 00000000..a04d4508 --- /dev/null +++ b/pkg/tests/testdata/TestInputFilterMoreArgs/step1.golden @@ -0,0 +1,48 @@ +`{ + "done": false, + "content": "TEST RESULT CALL: 1", + "toolID": "inline:", + "state": { + "continuation": { + "state": { + "input": "123:{\"foo\":\"123\"}\n", + "completion": { + "model": "gpt-4o", + "internalSystemPrompt": false, + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Say hi" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "123:{\"foo\":\"123\"}\n" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} + } + ], + "chat": true + } + }, + "result": "TEST RESULT CALL: 1" + }, + "continuationToolID": "inline:" + } +}` diff --git a/pkg/tests/testdata/TestContextSubChat/step2.golden b/pkg/tests/testdata/TestInputFilterMoreArgs/step2.golden similarity index 62% rename from pkg/tests/testdata/TestContextSubChat/step2.golden rename to pkg/tests/testdata/TestInputFilterMoreArgs/step2.golden index dfcb2b96..aa41f1dd 100644 --- a/pkg/tests/testdata/TestContextSubChat/step2.golden +++ b/pkg/tests/testdata/TestInputFilterMoreArgs/step2.golden @@ -1,11 +1,11 @@ `{ "done": false, - "content": "Assistant Response 3 - from main chat tool", - "toolID": "testdata/TestContextSubChat/test.gpt:", + "content": "TEST RESULT CALL: 2", + "toolID": "inline:", "state": { "continuation": { "state": { - "input": "User 1", + "input": ":\"foo\":\"123\"}\n", "completion": { "model": "gpt-4o", "internalSystemPrompt": false, @@ -14,7 +14,7 @@ "role": "system", "content": [ { - "text": "Assistant Response 2 - from context tool\nHello" + "text": "Say hi" } ], "usage": {} @@ -23,7 +23,7 @@ "role": "user", "content": [ { - "text": "User 1" + "text": ":\"foo\":\"123\"}\n" } ], "usage": {} @@ -32,7 +32,7 @@ "role": "assistant", "content": [ { - "text": "Assistant Response 3 - from main chat tool" + "text": "TEST RESULT CALL: 2" } ], "usage": {} @@ -41,8 +41,8 @@ "chat": true } }, - "result": "Assistant Response 3 - from main chat tool" + "result": "TEST RESULT CALL: 2" }, - "continuationToolID": "testdata/TestContextSubChat/test.gpt:" + "continuationToolID": "inline:" } }` diff --git a/pkg/tests/testdata/TestMCPLoad/call1-resp.golden b/pkg/tests/testdata/TestMCPLoad/call1-resp.golden new file mode 100644 index 00000000..2861a036 --- /dev/null +++ b/pkg/tests/testdata/TestMCPLoad/call1-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestMCPLoad/call1.golden b/pkg/tests/testdata/TestMCPLoad/call1.golden new file mode 100644 index 00000000..31048a88 --- /dev/null +++ b/pkg/tests/testdata/TestMCPLoad/call1.golden @@ -0,0 +1,3 @@ +`{ + "model": "gpt-4o" +}` diff --git a/pkg/tests/testdata/TestMCPLoad/step1.golden b/pkg/tests/testdata/TestMCPLoad/step1.golden new file mode 100644 index 00000000..c5961afa --- /dev/null +++ b/pkg/tests/testdata/TestMCPLoad/step1.golden @@ -0,0 +1,6 @@ +`{ + "done": true, + "content": "{\"isError\":false,\"content\":[{\"type\":\"text\",\"text\":\"[{'1': 1}]\"}]}", + "toolID": "", + "state": null +}` diff --git a/pkg/tests/testdata/TestMissingTool/call1-resp.golden b/pkg/tests/testdata/TestMissingTool/call1-resp.golden new file mode 100644 index 00000000..4f4e82a0 --- /dev/null +++ b/pkg/tests/testdata/TestMissingTool/call1-resp.golden @@ -0,0 +1,14 @@ +`{ + "role": "assistant", + "content": [ + { + "toolCall": { + "id": "call_1", + "function": { + "name": "not.bob" + } + } + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestMissingTool/call1.golden b/pkg/tests/testdata/TestMissingTool/call1.golden new file mode 100644 index 00000000..f1bcc4f0 --- /dev/null +++ b/pkg/tests/testdata/TestMissingTool/call1.golden @@ -0,0 +1,32 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestMissingTool/test.gpt:Bob", + "name": "Bob", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Call tool Bob" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "Input 1" + } + ], + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestMissingTool/call2-resp.golden b/pkg/tests/testdata/TestMissingTool/call2-resp.golden new file mode 100644 index 00000000..997ca1b9 --- /dev/null +++ b/pkg/tests/testdata/TestMissingTool/call2-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 2" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestMissingTool/call2.golden b/pkg/tests/testdata/TestMissingTool/call2.golden new file mode 100644 index 00000000..c24d83f8 --- /dev/null +++ b/pkg/tests/testdata/TestMissingTool/call2.golden @@ -0,0 +1,61 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestMissingTool/test.gpt:Bob", + "name": "Bob", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Call tool Bob" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "Input 1" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "id": "call_1", + "function": { + "name": "not.bob" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "ERROR: can not call unknown tool named [notBob]" + } + ], + "toolCall": { + "id": "call_1", + "function": { + "name": "not.bob" + } + }, + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestMissingTool/test.gpt b/pkg/tests/testdata/TestMissingTool/test.gpt new file mode 100644 index 00000000..2613ffd2 --- /dev/null +++ b/pkg/tests/testdata/TestMissingTool/test.gpt @@ -0,0 +1,10 @@ +tools: Bob + +Call tool Bob + +--- +name: Bob + +#!sys.echo + +You called? \ No newline at end of file diff --git a/pkg/tests/testdata/TestRuntimes/call1-resp.golden b/pkg/tests/testdata/TestRuntimes/call1-resp.golden new file mode 100644 index 00000000..1d53670a --- /dev/null +++ b/pkg/tests/testdata/TestRuntimes/call1-resp.golden @@ -0,0 +1,16 @@ +`{ + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + } + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestRuntimes/call1.golden b/pkg/tests/testdata/TestRuntimes/call1.golden new file mode 100644 index 00000000..67c7d9f7 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimes/call1.golden @@ -0,0 +1,37 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:py", + "name": "py", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:node", + "name": "node", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:bash", + "name": "bash", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Dummy" + } + ], + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestContextSubChat/call3-resp.golden b/pkg/tests/testdata/TestRuntimes/call2-resp.golden similarity index 59% rename from pkg/tests/testdata/TestContextSubChat/call3-resp.golden rename to pkg/tests/testdata/TestRuntimes/call2-resp.golden index b116d066..4806793c 100644 --- a/pkg/tests/testdata/TestContextSubChat/call3-resp.golden +++ b/pkg/tests/testdata/TestRuntimes/call2-resp.golden @@ -3,11 +3,11 @@ "content": [ { "toolCall": { - "index": 0, + "index": 1, "id": "call_2", "function": { - "name": "chatFinish", - "arguments": "Response from context chatbot" + "name": "node", + "arguments": "{}" } } } diff --git a/pkg/tests/testdata/TestContextSubChat/call4.golden b/pkg/tests/testdata/TestRuntimes/call2.golden similarity index 51% rename from pkg/tests/testdata/TestContextSubChat/call4.golden rename to pkg/tests/testdata/TestRuntimes/call2.golden index e1fb91ea..da456a5d 100644 --- a/pkg/tests/testdata/TestContextSubChat/call4.golden +++ b/pkg/tests/testdata/TestRuntimes/call2.golden @@ -3,17 +3,23 @@ "tools": [ { "function": { - "toolID": "testdata/TestContextSubChat/test.gpt:chatbot", - "name": "chatbot", - "parameters": { - "properties": { - "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" - } - }, - "type": "object" - } + "toolID": "testdata/TestRuntimes/test.gpt:py", + "name": "py", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:node", + "name": "node", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:bash", + "name": "bash", + "parameters": null } } ], @@ -22,7 +28,7 @@ "role": "system", "content": [ { - "text": "Call chatbot" + "text": "Dummy" } ], "usage": {} @@ -35,8 +41,8 @@ "index": 0, "id": "call_1", "function": { - "name": "chatbot", - "arguments": "Input to chatbot1" + "name": "py", + "arguments": "{}" } } } @@ -47,15 +53,15 @@ "role": "tool", "content": [ { - "text": "Response from context chatbot" + "text": "py worked\r\n" } ], "toolCall": { "index": 0, "id": "call_1", "function": { - "name": "chatbot", - "arguments": "Input to chatbot1" + "name": "py", + "arguments": "{}" } }, "usage": {} diff --git a/pkg/tests/testdata/TestContextSubChat/call6-resp.golden b/pkg/tests/testdata/TestRuntimes/call3-resp.golden similarity index 60% rename from pkg/tests/testdata/TestContextSubChat/call6-resp.golden rename to pkg/tests/testdata/TestRuntimes/call3-resp.golden index 6807fce9..1103f824 100644 --- a/pkg/tests/testdata/TestContextSubChat/call6-resp.golden +++ b/pkg/tests/testdata/TestRuntimes/call3-resp.golden @@ -3,11 +3,11 @@ "content": [ { "toolCall": { - "index": 0, + "index": 2, "id": "call_3", "function": { - "name": "chatbot", - "arguments": "Input to chatbot1 on resume" + "name": "bash", + "arguments": "{}" } } } diff --git a/pkg/tests/testdata/TestRuntimes/call3.golden b/pkg/tests/testdata/TestRuntimes/call3.golden new file mode 100644 index 00000000..f0792540 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimes/call3.golden @@ -0,0 +1,103 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:py", + "name": "py", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:node", + "name": "node", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:bash", + "name": "bash", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Dummy" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "py worked\r\n" + } + ], + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + }, + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "node worked\n" + } + ], + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + }, + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestContextSubChat/call4-resp.golden b/pkg/tests/testdata/TestRuntimes/call4-resp.golden similarity index 56% rename from pkg/tests/testdata/TestContextSubChat/call4-resp.golden rename to pkg/tests/testdata/TestRuntimes/call4-resp.golden index a86ae187..8135a8c9 100644 --- a/pkg/tests/testdata/TestContextSubChat/call4-resp.golden +++ b/pkg/tests/testdata/TestRuntimes/call4-resp.golden @@ -2,7 +2,7 @@ "role": "assistant", "content": [ { - "text": "Assistant Response 2 - from context tool" + "text": "TEST RESULT CALL: 4" } ], "usage": {} diff --git a/pkg/tests/testdata/TestRuntimes/call4.golden b/pkg/tests/testdata/TestRuntimes/call4.golden new file mode 100644 index 00000000..04ac31f5 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimes/call4.golden @@ -0,0 +1,136 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:py", + "name": "py", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:node", + "name": "node", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimes/test.gpt:bash", + "name": "bash", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Dummy" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "py worked\r\n" + } + ], + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + }, + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "node worked\n" + } + ], + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + }, + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 2, + "id": "call_3", + "function": { + "name": "bash", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "bash works\n" + } + ], + "toolCall": { + "index": 2, + "id": "call_3", + "function": { + "name": "bash", + "arguments": "{}" + } + }, + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestRuntimes/test.gpt b/pkg/tests/testdata/TestRuntimes/test.gpt new file mode 100644 index 00000000..db3ede64 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimes/test.gpt @@ -0,0 +1,58 @@ +name: first +tools: py, node, bash + +Dummy + +--- +name: py + +#!/usr/bin/env python3 + +import requests +import platform + +# this is dumb hack to get the line endings to always be \r\n so the golden files match +# on both linux and windows +if platform.system() == 'Windows': + print('py worked') +else: + print('py worked\r') + +--- +!metadata:py:requirements.txt + +requests + +--- +name: node + +#!/usr/bin/env node + +import chalk from 'chalk'; +console.log("node worked") + +--- +!metadata:node:package.json + +{ + "name": "chalk-example", + "version": "1.0.0", + "type": "module", + "description": "A simple example project to demonstrate the use of chalk", + "main": "example.js", + "scripts": { + "start": "node example.js" + }, + "author": "Your Name", + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0" + } + } + +--- +name: bash + +#!/bin/bash + +echo bash works \ No newline at end of file diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/call1-resp.golden b/pkg/tests/testdata/TestRuntimesLocalDev/call1-resp.golden new file mode 100644 index 00000000..1d53670a --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/call1-resp.golden @@ -0,0 +1,16 @@ +`{ + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + } + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/call1.golden b/pkg/tests/testdata/TestRuntimesLocalDev/call1.golden new file mode 100644 index 00000000..7e775029 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/call1.golden @@ -0,0 +1,37 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:py", + "name": "py", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:node", + "name": "node", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:bash", + "name": "bash", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Dummy" + } + ], + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/call2-resp.golden b/pkg/tests/testdata/TestRuntimesLocalDev/call2-resp.golden new file mode 100644 index 00000000..4806793c --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/call2-resp.golden @@ -0,0 +1,16 @@ +`{ + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + } + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/call2.golden b/pkg/tests/testdata/TestRuntimesLocalDev/call2.golden new file mode 100644 index 00000000..cc1fd1b7 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/call2.golden @@ -0,0 +1,70 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:py", + "name": "py", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:node", + "name": "node", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:bash", + "name": "bash", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Dummy" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "py worked\r\n" + } + ], + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + }, + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/call3-resp.golden b/pkg/tests/testdata/TestRuntimesLocalDev/call3-resp.golden new file mode 100644 index 00000000..1103f824 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/call3-resp.golden @@ -0,0 +1,16 @@ +`{ + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 2, + "id": "call_3", + "function": { + "name": "bash", + "arguments": "{}" + } + } + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/call3.golden b/pkg/tests/testdata/TestRuntimesLocalDev/call3.golden new file mode 100644 index 00000000..7c928c07 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/call3.golden @@ -0,0 +1,103 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:py", + "name": "py", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:node", + "name": "node", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:bash", + "name": "bash", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Dummy" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "py worked\r\n" + } + ], + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + }, + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "node worked\n" + } + ], + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + }, + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/call4-resp.golden b/pkg/tests/testdata/TestRuntimesLocalDev/call4-resp.golden new file mode 100644 index 00000000..8135a8c9 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/call4-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 4" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/call4.golden b/pkg/tests/testdata/TestRuntimesLocalDev/call4.golden new file mode 100644 index 00000000..b95b880d --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/call4.golden @@ -0,0 +1,136 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:py", + "name": "py", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:node", + "name": "node", + "parameters": null + } + }, + { + "function": { + "toolID": "testdata/TestRuntimesLocalDev/test.gpt:bash", + "name": "bash", + "parameters": null + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "Dummy" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "py worked\r\n" + } + ], + "toolCall": { + "index": 0, + "id": "call_1", + "function": { + "name": "py", + "arguments": "{}" + } + }, + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "node worked\n" + } + ], + "toolCall": { + "index": 1, + "id": "call_2", + "function": { + "name": "node", + "arguments": "{}" + } + }, + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "toolCall": { + "index": 2, + "id": "call_3", + "function": { + "name": "bash", + "arguments": "{}" + } + } + } + ], + "usage": {} + }, + { + "role": "tool", + "content": [ + { + "text": "bash works\n" + } + ], + "toolCall": { + "index": 2, + "id": "call_3", + "function": { + "name": "bash", + "arguments": "{}" + } + }, + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/package.json b/pkg/tests/testdata/TestRuntimesLocalDev/package.json new file mode 100644 index 00000000..d5f400a1 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/package.json @@ -0,0 +1,15 @@ +{ + "name": "chalk-example", + "version": "1.0.0", + "type": "module", + "description": "A simple example project to demonstrate the use of chalk", + "main": "example.js", + "scripts": { + "start": "node example.js" + }, + "author": "Your Name", + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0" + } +} diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/requirements.txt b/pkg/tests/testdata/TestRuntimesLocalDev/requirements.txt new file mode 100644 index 00000000..f2293605 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/pkg/tests/testdata/TestRuntimesLocalDev/test.gpt b/pkg/tests/testdata/TestRuntimesLocalDev/test.gpt new file mode 100644 index 00000000..454ffce0 --- /dev/null +++ b/pkg/tests/testdata/TestRuntimesLocalDev/test.gpt @@ -0,0 +1,34 @@ +name: first +tools: py, node, bash + +Dummy + +--- +name: py + +#!/usr/bin/env python3 + +import requests +import platform + +# this is dumb hack to get the line endings to always be \r\n so the golden files match +# on both linux and windows +if platform.system() == 'Windows': + print('py worked') +else: + print('py worked\r') + +--- +name: node + +#!/usr/bin/env node + +import chalk from 'chalk'; +console.log("node worked") + +--- +name: bash + +#!/bin/bash + +echo bash works \ No newline at end of file diff --git a/pkg/tests/testdata/TestShareCreds/step1.golden b/pkg/tests/testdata/TestShareCreds/step1.golden new file mode 100644 index 00000000..9d584f92 --- /dev/null +++ b/pkg/tests/testdata/TestShareCreds/step1.golden @@ -0,0 +1,6 @@ +`{ + "done": true, + "content": "that worked\nthat also worked\n", + "toolID": "", + "state": null +}` diff --git a/pkg/tests/testdata/TestSubChat/call1.golden b/pkg/tests/testdata/TestSubChat/call1.golden index 0d906395..f0e0b491 100644 --- a/pkg/tests/testdata/TestSubChat/call1.golden +++ b/pkg/tests/testdata/TestSubChat/call1.golden @@ -6,13 +6,13 @@ "toolID": "testdata/TestSubChat/test.gpt:chatbot", "name": "chatbot", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the assistant. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the assistant. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestSysContext/call1.golden b/pkg/tests/testdata/TestSysContext/call1.golden index 4c9c51d0..f1926c33 100644 --- a/pkg/tests/testdata/TestSysContext/call1.golden +++ b/pkg/tests/testdata/TestSysContext/call1.golden @@ -7,13 +7,13 @@ "toolID": "testdata/TestSysContext/file.gpt:I am Superman Agent", "name": "iAmSuperman", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestSysContext/step1.golden b/pkg/tests/testdata/TestSysContext/step1.golden index 426e5991..2755652a 100644 --- a/pkg/tests/testdata/TestSysContext/step1.golden +++ b/pkg/tests/testdata/TestSysContext/step1.golden @@ -15,13 +15,13 @@ "toolID": "testdata/TestSysContext/file.gpt:I am Superman Agent", "name": "iAmSuperman", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestToolAs/call1.golden b/pkg/tests/testdata/TestToolAs/call1.golden index 55796fea..758f9cca 100644 --- a/pkg/tests/testdata/TestToolAs/call1.golden +++ b/pkg/tests/testdata/TestToolAs/call1.golden @@ -6,13 +6,13 @@ "toolID": "testdata/TestToolAs/test.gpt:infile", "name": "local", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } }, @@ -21,13 +21,13 @@ "toolID": "testdata/TestToolAs/other.gpt:", "name": "remote", "parameters": { + "type": "object", "properties": { "defaultPromptParameter": { - "description": "Prompt to send to the tool. This may be an instruction or question.", - "type": "string" + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." } - }, - "type": "object" + } } } } diff --git a/pkg/tests/testdata/TestToolRefAll/call1-resp.golden b/pkg/tests/testdata/TestToolRefAll/call1-resp.golden new file mode 100644 index 00000000..2861a036 --- /dev/null +++ b/pkg/tests/testdata/TestToolRefAll/call1-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestToolRefAll/call1.golden b/pkg/tests/testdata/TestToolRefAll/call1.golden new file mode 100644 index 00000000..2d7a7b08 --- /dev/null +++ b/pkg/tests/testdata/TestToolRefAll/call1.golden @@ -0,0 +1,61 @@ +`{ + "model": "gpt-4o", + "tools": [ + { + "function": { + "toolID": "testdata/TestToolRefAll/test.gpt:tool", + "name": "tool", + "parameters": { + "type": "object", + "properties": { + "toolArg": { + "type": "string", + "description": "stuff" + } + } + } + } + }, + { + "function": { + "toolID": "testdata/TestToolRefAll/test.gpt:agentAssistant", + "name": "agentAssistant", + "parameters": { + "type": "object", + "properties": { + "defaultPromptParameter": { + "type": "string", + "description": "Prompt to send to the tool. This may be an instruction or question." + } + } + } + } + }, + { + "function": { + "toolID": "testdata/TestToolRefAll/test.gpt:none", + "name": "none", + "parameters": { + "type": "object", + "properties": { + "noneArg": { + "type": "string", + "description": "stuff" + } + } + } + } + } + ], + "messages": [ + { + "role": "system", + "content": [ + { + "text": "\nContext Body\n\nShared context\nMain tool" + } + ], + "usage": {} + } + ] +}` diff --git a/pkg/tests/testdata/TestToolRefAll/test.gpt b/pkg/tests/testdata/TestToolRefAll/test.gpt new file mode 100644 index 00000000..423cf766 --- /dev/null +++ b/pkg/tests/testdata/TestToolRefAll/test.gpt @@ -0,0 +1,38 @@ +tools: tool, agentAssistant, context, none + +Main tool + +--- +name: agentAssistant +type: agent + +Agent body + +--- +name: context +type: context +share context: sharedcontext + +#!sys.echo + +Context Body + +--- +name: sharedcontext + +#!sys.echo + +Shared context + +--- +name: none +param: noneArg: stuff + +Default type + +--- +name: tool +type: Tool +param: toolArg: stuff + +Typed tool \ No newline at end of file diff --git a/pkg/tests/testdata/TestToolsChange/call1-resp.golden b/pkg/tests/testdata/TestToolsChange/call1-resp.golden new file mode 100644 index 00000000..2861a036 --- /dev/null +++ b/pkg/tests/testdata/TestToolsChange/call1-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestToolsChange/call1.golden b/pkg/tests/testdata/TestToolsChange/call1.golden new file mode 100644 index 00000000..8bbc8ed5 --- /dev/null +++ b/pkg/tests/testdata/TestToolsChange/call1.golden @@ -0,0 +1,70 @@ +`{ + "model": "gpt-4o", + "internalSystemPrompt": false, + "tools": [ + { + "function": { + "toolID": "sys.ls", + "name": "ls", + "description": "Lists the contents of a directory", + "parameters": { + "type": "object", + "properties": { + "dir": { + "type": "string", + "description": "The directory to list" + } + } + } + } + }, + { + "function": { + "toolID": "sys.read", + "name": "read", + "description": "Reads the contents of a file. Can only read plain text files, not binary files", + "parameters": { + "type": "object", + "properties": { + "filename": { + "type": "string", + "description": "The name of the file to read" + } + } + } + } + }, + { + "function": { + "toolID": "sys.write", + "name": "write", + "description": "Write the contents to a file", + "parameters": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The content to write" + }, + "filename": { + "type": "string", + "description": "The name of the file to write to" + } + } + } + } + } + ], + "messages": [ + { + "role": "user", + "content": [ + { + "text": "input 1" + } + ], + "usage": {} + } + ], + "chat": true +}` diff --git a/pkg/tests/testdata/TestToolsChange/call2-resp.golden b/pkg/tests/testdata/TestToolsChange/call2-resp.golden new file mode 100644 index 00000000..997ca1b9 --- /dev/null +++ b/pkg/tests/testdata/TestToolsChange/call2-resp.golden @@ -0,0 +1,9 @@ +`{ + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 2" + } + ], + "usage": {} +}` diff --git a/pkg/tests/testdata/TestToolsChange/call2.golden b/pkg/tests/testdata/TestToolsChange/call2.golden new file mode 100644 index 00000000..4abdfddf --- /dev/null +++ b/pkg/tests/testdata/TestToolsChange/call2.golden @@ -0,0 +1,73 @@ +`{ + "model": "gpt-4o", + "internalSystemPrompt": false, + "tools": [ + { + "function": { + "toolID": "sys.ls", + "name": "ls", + "description": "Lists the contents of a directory", + "parameters": { + "type": "object", + "properties": { + "dir": { + "type": "string", + "description": "The directory to list" + } + } + } + } + }, + { + "function": { + "toolID": "sys.write", + "name": "write", + "description": "Write the contents to a file", + "parameters": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The content to write" + }, + "filename": { + "type": "string", + "description": "The name of the file to write to" + } + } + } + } + } + ], + "messages": [ + { + "role": "user", + "content": [ + { + "text": "input 1" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "input 2" + } + ], + "usage": {} + } + ], + "chat": true, + "temperature": 0.6 +}` diff --git a/pkg/tests/testdata/TestToolsChange/step1.golden b/pkg/tests/testdata/TestToolsChange/step1.golden new file mode 100644 index 00000000..2274f8b0 --- /dev/null +++ b/pkg/tests/testdata/TestToolsChange/step1.golden @@ -0,0 +1,93 @@ +`{ + "done": false, + "content": "TEST RESULT CALL: 1", + "toolID": "inline:", + "state": { + "continuation": { + "state": { + "input": "input 1", + "completion": { + "model": "gpt-4o", + "internalSystemPrompt": false, + "tools": [ + { + "function": { + "toolID": "sys.ls", + "name": "ls", + "description": "Lists the contents of a directory", + "parameters": { + "type": "object", + "properties": { + "dir": { + "type": "string", + "description": "The directory to list" + } + } + } + } + }, + { + "function": { + "toolID": "sys.read", + "name": "read", + "description": "Reads the contents of a file. Can only read plain text files, not binary files", + "parameters": { + "type": "object", + "properties": { + "filename": { + "type": "string", + "description": "The name of the file to read" + } + } + } + } + }, + { + "function": { + "toolID": "sys.write", + "name": "write", + "description": "Write the contents to a file", + "parameters": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The content to write" + }, + "filename": { + "type": "string", + "description": "The name of the file to write to" + } + } + } + } + } + ], + "messages": [ + { + "role": "user", + "content": [ + { + "text": "input 1" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} + } + ], + "chat": true + } + }, + "result": "TEST RESULT CALL: 1" + }, + "continuationToolID": "inline:" + } +}` diff --git a/pkg/tests/testdata/TestToolsChange/step2.golden b/pkg/tests/testdata/TestToolsChange/step2.golden new file mode 100644 index 00000000..ef99f90e --- /dev/null +++ b/pkg/tests/testdata/TestToolsChange/step2.golden @@ -0,0 +1,96 @@ +`{ + "done": false, + "content": "TEST RESULT CALL: 2", + "toolID": "inline:", + "state": { + "continuation": { + "state": { + "input": "input 1", + "completion": { + "model": "gpt-4o", + "internalSystemPrompt": false, + "tools": [ + { + "function": { + "toolID": "sys.ls", + "name": "ls", + "description": "Lists the contents of a directory", + "parameters": { + "type": "object", + "properties": { + "dir": { + "type": "string", + "description": "The directory to list" + } + } + } + } + }, + { + "function": { + "toolID": "sys.write", + "name": "write", + "description": "Write the contents to a file", + "parameters": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The content to write" + }, + "filename": { + "type": "string", + "description": "The name of the file to write to" + } + } + } + } + } + ], + "messages": [ + { + "role": "user", + "content": [ + { + "text": "input 1" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 1" + } + ], + "usage": {} + }, + { + "role": "user", + "content": [ + { + "text": "input 2" + } + ], + "usage": {} + }, + { + "role": "assistant", + "content": [ + { + "text": "TEST RESULT CALL: 2" + } + ], + "usage": {} + } + ], + "chat": true, + "temperature": 0.6 + } + }, + "result": "TEST RESULT CALL: 2" + }, + "continuationToolID": "inline:" + } +}` diff --git a/pkg/tests/tester/runner.go b/pkg/tests/tester/runner.go index fe21ba92..f59c0b14 100644 --- a/pkg/tests/tester/runner.go +++ b/pkg/tests/tester/runner.go @@ -8,8 +8,10 @@ import ( "path/filepath" "testing" + "github.com/adrg/xdg" "github.com/gptscript-ai/gptscript/pkg/credentials" "github.com/gptscript-ai/gptscript/pkg/loader" + "github.com/gptscript-ai/gptscript/pkg/repos/runtimes" "github.com/gptscript-ai/gptscript/pkg/runner" "github.com/gptscript-ai/gptscript/pkg/types" "github.com/hexops/autogold/v2" @@ -29,7 +31,11 @@ type Result struct { Err error } -func (c *Client) Call(_ context.Context, messageRequest types.CompletionRequest, _ chan<- types.CompletionStatus) (resp *types.CompletionMessage, respErr error) { +func (c *Client) ProxyInfo([]string) (string, string, error) { + return "test-auth", "test-url", nil +} + +func (c *Client) Call(_ context.Context, messageRequest types.CompletionRequest, _ []string, _ chan<- types.CompletionStatus) (resp *types.CompletionMessage, respErr error) { msgData, err := json.MarshalIndent(messageRequest, "", " ") require.NoError(c.t, err) @@ -104,7 +110,20 @@ func (c *Client) Call(_ context.Context, messageRequest types.CompletionRequest, } if result.Func.Name != "" { - c.t.Fatalf("failed to find tool %s", result.Func.Name) + return &types.CompletionMessage{ + Role: types.CompletionMessageRoleTypeAssistant, + Content: []types.ContentPart{ + { + ToolCall: &types.CompletionToolCall{ + ID: fmt.Sprintf("call_%d", c.id), + Function: types.CompletionFunctionCall{ + Name: result.Func.Name, + Arguments: result.Func.Arguments, + }, + }, + }, + }, + }, nil } return &types.CompletionMessage{ @@ -116,7 +135,8 @@ func (c *Client) Call(_ context.Context, messageRequest types.CompletionRequest, type Runner struct { *runner.Runner - Client *Client + Client *Client + StepAsserted int } func (r *Runner) RunDefault() string { @@ -139,7 +159,7 @@ func (r *Runner) Run(script, input string) (string, error) { return "", err } - return r.Runner.Run(context.Background(), prg, os.Environ(), input) + return r.Runner.Run(context.Background(), prg, os.Environ(), input, runner.RunOptions{}) } func (r *Runner) AssertResponded(t *testing.T) { @@ -147,6 +167,21 @@ func (r *Runner) AssertResponded(t *testing.T) { require.Len(t, r.Client.result, 0) } +func toJSONString(t *testing.T, v interface{}) string { + t.Helper() + x, err := json.MarshalIndent(v, "", " ") + require.NoError(t, err) + return string(x) +} + +func (r *Runner) AssertStep(t *testing.T, resp runner.ChatResponse, err error) { + t.Helper() + r.StepAsserted++ + require.NoError(t, err) + r.AssertResponded(t) + autogold.ExpectFile(t, toJSONString(t, resp), autogold.Name(t.Name()+fmt.Sprintf("/step%d", r.StepAsserted))) +} + func (r *Runner) RespondWith(result ...Result) { r.Client.result = append(r.Client.result, result...) } @@ -158,8 +193,14 @@ func NewRunner(t *testing.T) *Runner { t: t, } + cacheDir, err := xdg.CacheFile("gptscript-test-cache/runtime") + require.NoError(t, err) + + rm := runtimes.Default(cacheDir, "") + run, err := runner.New(c, credentials.NoopStore{}, runner.Options{ - Sequential: true, + Sequential: true, + RuntimeManager: rm, }) require.NoError(t, err) diff --git a/pkg/types/args.go b/pkg/types/args.go new file mode 100644 index 00000000..62933de1 --- /dev/null +++ b/pkg/types/args.go @@ -0,0 +1,99 @@ +//nolint:revive +package types + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/google/shlex" +) + +func GetToolRefInput(prg *Program, ref ToolReference, input string) (string, error) { + if ref.Arg == "" { + return "", nil + } + + targetArgs := prg.ToolSet[ref.ToolID].Arguments + targetKeys := map[string]string{} + + if ref.Arg == "*" { + return input, nil + } + + if targetArgs == nil { + return "", nil + } + + for targetKey := range targetArgs.Properties { + targetKeys[strings.ToLower(targetKey)] = targetKey + } + + inputMap := map[string]interface{}{} + outputMap := map[string]interface{}{} + + _ = json.Unmarshal([]byte(input), &inputMap) + for k, v := range inputMap { + inputMap[strings.ToLower(k)] = v + } + + fields, err := shlex.Split(ref.Arg) + if err != nil { + return "", fmt.Errorf("invalid tool args %q: %v", ref.Arg, err) + } + + for i := 0; i < len(fields); i++ { + field := fields[i] + if field == "and" { + continue + } + if field == "as" { + i++ + continue + } + + var ( + keyName string + val any + ) + + if strings.HasPrefix(field, "$") { + key := strings.TrimPrefix(field, "$") + key = strings.TrimPrefix(key, "{") + key = strings.TrimSuffix(key, "}") + val = inputMap[strings.ToLower(key)] + } else { + val = field + } + + if len(fields) > i+1 && fields[i+1] == "as" { + keyName = strings.ToLower(fields[i+2]) + } + + if len(targetKeys) == 0 { + return "", fmt.Errorf("can not assign arg to context because target tool [%s] has no defined args", ref.ToolID) + } + + if keyName == "" { + if len(targetKeys) != 1 { + return "", fmt.Errorf("can not assign arg to context because target tool [%s] does not have one args. You must use \"as\" syntax to map the arg to a key %v", ref.ToolID, targetKeys) + } + for k := range targetKeys { + keyName = k + } + } + + if targetKey, ok := targetKeys[strings.ToLower(keyName)]; ok { + outputMap[targetKey] = val + } else { + return "", fmt.Errorf("can not assign arg to context because target tool [%s] does not args [%s]", ref.ToolID, keyName) + } + } + + if len(outputMap) == 0 { + return "", nil + } + + output, err := json.Marshal(outputMap) + return string(output), err +} diff --git a/pkg/types/completion.go b/pkg/types/completion.go index dd70ad50..bacdaab8 100644 --- a/pkg/types/completion.go +++ b/pkg/types/completion.go @@ -1,23 +1,23 @@ +//nolint:revive package types import ( "fmt" "strings" - "github.com/fatih/color" - "github.com/getkin/kin-openapi/openapi3" + "github.com/modelcontextprotocol/go-sdk/jsonschema" ) type CompletionRequest struct { - Model string `json:"model,omitempty"` - InternalSystemPrompt *bool `json:"internalSystemPrompt,omitempty"` - Tools []CompletionTool `json:"tools,omitempty"` - Messages []CompletionMessage `json:"messages,omitempty"` - MaxTokens int `json:"maxTokens,omitempty"` - Chat bool `json:"chat,omitempty"` - Temperature *float32 `json:"temperature,omitempty"` - JSONResponse bool `json:"jsonResponse,omitempty"` - Cache *bool `json:"cache,omitempty"` + Model string `json:"model,omitempty"` + InternalSystemPrompt *bool `json:"internalSystemPrompt,omitempty"` + Tools []ChatCompletionTool `json:"tools,omitempty"` + Messages []CompletionMessage `json:"messages,omitempty"` + MaxTokens int `json:"maxTokens,omitempty"` + Chat bool `json:"chat,omitempty"` + Temperature *float32 `json:"temperature,omitempty"` + JSONResponse bool `json:"jsonResponse,omitempty"` + Cache *bool `json:"cache,omitempty"` } func (r *CompletionRequest) GetCache() bool { @@ -27,15 +27,15 @@ func (r *CompletionRequest) GetCache() bool { return *r.Cache } -type CompletionTool struct { +type ChatCompletionTool struct { Function CompletionFunctionDefinition `json:"function,omitempty"` } type CompletionFunctionDefinition struct { - ToolID string `json:"toolID,omitempty"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Parameters *openapi3.Schema `json:"parameters"` + ToolID string `json:"toolID,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Parameters *jsonschema.Schema `json:"parameters"` } // Chat message role defined by the OpenAI API. @@ -83,7 +83,6 @@ type CompletionStatus struct { Response any Usage Usage Cached bool - Chunks any PartialResponse *CompletionMessage } @@ -112,7 +111,7 @@ func (c CompletionMessage) String() string { } buf.WriteString(content.Text) if content.ToolCall != nil { - buf.WriteString(fmt.Sprintf(" %s -> %s", color.GreenString(content.ToolCall.Function.Name), content.ToolCall.Function.Arguments)) + buf.WriteString(fmt.Sprintf(" %s -> %s", content.ToolCall.Function.Name, content.ToolCall.Function.Arguments)) } } return buf.String() diff --git a/pkg/types/credential_test.go b/pkg/types/credential_test.go index b6f70ee3..7f03e2d9 100644 --- a/pkg/types/credential_test.go +++ b/pkg/types/credential_test.go @@ -1,3 +1,4 @@ +//nolint:revive package types import ( @@ -9,13 +10,14 @@ import ( func TestParseCredentialArgs(t *testing.T) { tests := []struct { - name string - toolName string - input string - expectedName string - expectedAlias string - expectedArgs map[string]string - wantErr bool + name string + toolName string + input string + expectedName string + expectedAlias string + expectedCheckParam string + expectedArgs map[string]string + wantErr bool }{ { name: "empty", @@ -94,6 +96,40 @@ func TestParseCredentialArgs(t *testing.T) { "arg2": "value2", }, }, + { + name: "tool name with check parameter", + toolName: `myCredentialTool checked with myCheckParam`, + expectedName: "myCredentialTool", + expectedCheckParam: "myCheckParam", + }, + { + name: "tool name with alias and check parameter", + toolName: `myCredentialTool as myAlias checked with myCheckParam`, + expectedName: "myCredentialTool", + expectedAlias: "myAlias", + expectedCheckParam: "myCheckParam", + }, + { + name: "tool name with alias, check parameter, and args", + toolName: `myCredentialTool as myAlias checked with myCheckParam with value1 as arg1 and value2 as arg2`, + expectedName: "myCredentialTool", + expectedAlias: "myAlias", + expectedCheckParam: "myCheckParam", + expectedArgs: map[string]string{ + "arg1": "value1", + "arg2": "value2", + }, + }, + { + name: "check parameter without with", + toolName: `myCredentialTool checked myCheckParam`, + wantErr: true, + }, + { + name: "invalid check parameter", + toolName: `myCredentialTool checked with`, + wantErr: true, + }, { name: "tool name with alias but no 'as' (invalid)", toolName: "myCredentialTool myAlias", @@ -136,7 +172,7 @@ func TestParseCredentialArgs(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - originalName, alias, args, err := ParseCredentialArgs(tt.toolName, tt.input) + originalName, alias, checkParam, args, err := ParseCredentialArgs(tt.toolName, tt.input) if tt.wantErr { require.Error(t, err, "expected an error but got none") return @@ -145,6 +181,7 @@ func TestParseCredentialArgs(t *testing.T) { require.NoError(t, err, "did not expect an error but got one") require.Equal(t, tt.expectedName, originalName, "unexpected original name") require.Equal(t, tt.expectedAlias, alias, "unexpected alias") + require.Equal(t, tt.expectedCheckParam, checkParam, "unexpected checkParam") require.Equal(t, len(tt.expectedArgs), len(args), "unexpected number of args") for k, v := range tt.expectedArgs { diff --git a/pkg/types/jsonschema.go b/pkg/types/jsonschema.go index 6fd0d4ea..17df0692 100644 --- a/pkg/types/jsonschema.go +++ b/pkg/types/jsonschema.go @@ -1,21 +1,18 @@ +//nolint:revive package types -import ( - "github.com/getkin/kin-openapi/openapi3" -) +import "github.com/modelcontextprotocol/go-sdk/jsonschema" -func ObjectSchema(kv ...string) *openapi3.Schema { - s := &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{}, +func ObjectSchema(kv ...string) *jsonschema.Schema { + s := &jsonschema.Schema{ + Type: "object", + Properties: make(map[string]*jsonschema.Schema, len(kv)/2), } for i, v := range kv { if i%2 == 1 { - s.Properties[kv[i-1]] = &openapi3.SchemaRef{ - Value: &openapi3.Schema{ - Description: v, - Type: &openapi3.Types{"string"}, - }, + s.Properties[kv[i-1]] = &jsonschema.Schema{ + Description: v, + Type: "string", } } } diff --git a/pkg/types/log.go b/pkg/types/log.go index ba3ff8c5..de23ece8 100644 --- a/pkg/types/log.go +++ b/pkg/types/log.go @@ -1,3 +1,4 @@ +//nolint:revive package types import "github.com/gptscript-ai/gptscript/pkg/mvl" diff --git a/pkg/types/prompt.go b/pkg/types/prompt.go index ea17c11c..42e6eaa5 100644 --- a/pkg/types/prompt.go +++ b/pkg/types/prompt.go @@ -1,12 +1,76 @@ +//nolint:revive package types +import ( + "encoding/json" + "strings" +) + const ( PromptURLEnvVar = "GPTSCRIPT_PROMPT_URL" PromptTokenEnvVar = "GPTSCRIPT_PROMPT_TOKEN" ) type Prompt struct { - Message string `json:"message,omitempty"` - Fields []string `json:"fields,omitempty"` - Sensitive bool `json:"sensitive,omitempty"` + Message string `json:"message,omitempty"` + Fields Fields `json:"fields,omitempty"` + Sensitive bool `json:"sensitive,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +type Field struct { + Name string `json:"name,omitempty"` + Sensitive *bool `json:"sensitive,omitempty"` + Description string `json:"description,omitempty"` + Options []string `json:"options,omitempty"` +} + +type Fields []Field + +// UnmarshalJSON will unmarshal the corresponding JSON object for Fields, +// or a comma-separated strings (for backwards compatibility). +func (f *Fields) UnmarshalJSON(b []byte) error { + if len(b) == 0 || f == nil { + return nil + } + + if b[0] == '[' { + var arr []Field + if err := json.Unmarshal(b, &arr); err != nil { + return err + } + *f = arr + return nil + } + + var fields string + if err := json.Unmarshal(b, &fields); err != nil { + return err + } + + if fields != "" { + fieldsArr := strings.Split(fields, ",") + *f = make([]Field, 0, len(fieldsArr)) + for _, field := range fieldsArr { + *f = append(*f, Field{Name: strings.TrimSpace(field)}) + } + } + + return nil +} + +type field *Field + +// UnmarshalJSON will unmarshal the corresponding JSON object for a Field, +// or a string (for backwards compatibility). +func (f *Field) UnmarshalJSON(b []byte) error { + if len(b) == 0 || f == nil { + return nil + } + + if b[0] == '{' { + return json.Unmarshal(b, field(f)) + } + + return json.Unmarshal(b, &f.Name) } diff --git a/pkg/types/prompt_test.go b/pkg/types/prompt_test.go new file mode 100644 index 00000000..c081011e --- /dev/null +++ b/pkg/types/prompt_test.go @@ -0,0 +1,143 @@ +//nolint:revive +package types + +import ( + "reflect" + "testing" +) + +func TestFieldUnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input []byte + expected Field + expectErr bool + }{ + { + name: "valid single Field object JSON", + input: []byte(`{"name":"field1","sensitive":true,"description":"A test field"}`), + expected: Field{Name: "field1", Sensitive: boolPtr(true), Description: "A test field"}, + expectErr: false, + }, + { + name: "valid Field name as string", + input: []byte(`"field1"`), + expected: Field{Name: "field1"}, + expectErr: false, + }, + { + name: "empty input", + input: []byte(``), + expected: Field{}, + expectErr: false, + }, + { + name: "invalid JSON object", + input: []byte(`{"name":"field1","sensitive":"not_boolean"}`), + expected: Field{Name: "field1", Sensitive: new(bool)}, + expectErr: true, + }, + { + name: "extra unknown fields in JSON object", + input: []byte(`{"name":"field1","unknown":"field","sensitive":false}`), + expected: Field{Name: "field1", Sensitive: boolPtr(false)}, + expectErr: false, + }, + { + name: "malformed JSON", + input: []byte(`{"name":"field1","sensitive":true`), + expected: Field{}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var field Field + err := field.UnmarshalJSON(tt.input) + if (err != nil) != tt.expectErr { + t.Errorf("UnmarshalJSON() error = %v, expectErr %v", err, tt.expectErr) + } + if !reflect.DeepEqual(field, tt.expected) { + t.Errorf("UnmarshalJSON() = %v, expected %v", field, tt.expected) + } + }) + } +} + +func TestFieldsUnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input []byte + expected Fields + expectErr bool + }{ + { + name: "empty input", + input: nil, + expected: nil, + expectErr: false, + }, + { + name: "nil pointer", + input: nil, + expected: nil, + expectErr: false, + }, + { + name: "valid JSON array", + input: []byte(`[{"Name":"field1"},{"Name":"field2"}]`), + expected: Fields{{Name: "field1"}, {Name: "field2"}}, + expectErr: false, + }, + { + name: "single string input", + input: []byte(`"field1,field2,field3"`), + expected: Fields{{Name: "field1"}, {Name: "field2"}, {Name: "field3"}}, + expectErr: false, + }, + { + name: "trim spaces in single string input", + input: []byte(`"field1, field2 , field3 "`), + expected: Fields{{Name: "field1"}, {Name: "field2"}, {Name: "field3"}}, + expectErr: false, + }, + { + name: "invalid JSON array", + input: []byte(`[{"Name":"field1"},{"Name":field2}]`), + expected: nil, + expectErr: true, + }, + { + name: "invalid single string", + input: []byte(`1234`), + expected: nil, + expectErr: true, + }, + { + name: "empty array", + input: []byte(`[]`), + expected: Fields{}, + expectErr: false, + }, + { + name: "empty string", + input: []byte(`""`), + expected: nil, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var fields Fields + err := fields.UnmarshalJSON(tt.input) + if (err != nil) != tt.expectErr { + t.Errorf("UnmarshalJSON() error = %v, expectErr %v", err, tt.expectErr) + } + if !reflect.DeepEqual(fields, tt.expected) { + t.Errorf("UnmarshalJSON() = %v, expected %v", fields, tt.expected) + } + }) + } +} diff --git a/pkg/types/set.go b/pkg/types/set.go index 230e112b..654fbd5f 100644 --- a/pkg/types/set.go +++ b/pkg/types/set.go @@ -1,3 +1,4 @@ +//nolint:revive package types type toolRefKey struct { @@ -19,6 +20,17 @@ func (t *toolRefSet) List() (result []ToolReference, err error) { return result, t.err } +func (t *toolRefSet) Contains(value ToolReference) bool { + key := toolRefKey{ + name: value.Named, + toolID: value.ToolID, + arg: value.Arg, + } + + _, ok := t.set[key] + return ok +} + func (t *toolRefSet) HasTool(toolID string) bool { for _, ref := range t.set { if ref.ToolID == toolID { diff --git a/pkg/types/tool.go b/pkg/types/tool.go index b0af5183..42b08f62 100644 --- a/pkg/types/tool.go +++ b/pkg/types/tool.go @@ -1,3 +1,4 @@ +//nolint:revive package types import ( @@ -9,23 +10,45 @@ import ( "sort" "strings" - "github.com/getkin/kin-openapi/openapi3" "github.com/google/shlex" "github.com/gptscript-ai/gptscript/pkg/system" + "github.com/modelcontextprotocol/go-sdk/jsonschema" "golang.org/x/exp/maps" ) const ( - DaemonPrefix = "#!sys.daemon" - OpenAPIPrefix = "#!sys.openapi" - EchoPrefix = "#!sys.echo" - CommandPrefix = "#!" + DaemonPrefix = "#!sys.daemon" + OpenAPIPrefix = "#!sys.openapi" + EchoPrefix = "#!sys.echo" + CallPrefix = "#!sys.call" + MCPPrefix = "#!mcp" + MCPInvokePrefix = "#!sys.mcp.invoke." + CommandPrefix = "#!" + PromptPrefix = "!!" ) var ( DefaultFiles = []string{"agent.gpt", "tool.gpt"} ) +type ToolType string + +const ( + ToolTypeContext = ToolType("context") + ToolTypeAgent = ToolType("agent") + ToolTypeOutput = ToolType("output") + ToolTypeInput = ToolType("input") + ToolTypeTool = ToolType("tool") + ToolTypeCredential = ToolType("credential") + ToolTypeDefault = ToolType("") + + // The following types logically exist but have no real code reference. These are kept + // here just so that we have a comprehensive list + + ToolTypeAssistant = ToolType("assistant") + ToolTypeProvider = ToolType("provider") +) + type ErrToolNotFound struct { ToolName string } @@ -77,28 +100,6 @@ type ToolReference struct { ToolID string `json:"toolID,omitempty"` } -func (p Program) GetContextToolRefs(toolID string) ([]ToolReference, error) { - return p.ToolSet[toolID].GetContextTools(p) -} - -func (p Program) GetCompletionTools() (result []CompletionTool, err error) { - return Tool{ - ToolDef: ToolDef{ - Parameters: Parameters{ - Tools: []string{"main"}, - }, - }, - ToolMapping: map[string][]ToolReference{ - "main": { - { - Reference: "main", - ToolID: p.EntryToolID, - }, - }, - }, - }.GetCompletionTools(p) -} - func (p Program) TopLevelTools() (result []Tool) { for _, tool := range p.ToolSet[p.EntryToolID].LocalTools { if target, ok := p.ToolSet[tool]; ok { @@ -120,30 +121,55 @@ func (p Program) SetBlocking() Program { type BuiltinFunc func(ctx context.Context, env []string, input string, progress chan<- string) (string, error) type Parameters struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - MaxTokens int `json:"maxTokens,omitempty"` - ModelName string `json:"modelName,omitempty"` - ModelProvider bool `json:"modelProvider,omitempty"` - JSONResponse bool `json:"jsonResponse,omitempty"` - Chat bool `json:"chat,omitempty"` - Temperature *float32 `json:"temperature,omitempty"` - Cache *bool `json:"cache,omitempty"` - InternalPrompt *bool `json:"internalPrompt"` - Arguments *openapi3.Schema `json:"arguments,omitempty"` - Tools []string `json:"tools,omitempty"` - GlobalTools []string `json:"globalTools,omitempty"` - GlobalModelName string `json:"globalModelName,omitempty"` - Context []string `json:"context,omitempty"` - ExportContext []string `json:"exportContext,omitempty"` - Export []string `json:"export,omitempty"` - Agents []string `json:"agents,omitempty"` - Credentials []string `json:"credentials,omitempty"` - InputFilters []string `json:"inputFilters,omitempty"` - ExportInputFilters []string `json:"exportInputFilters,omitempty"` - OutputFilters []string `json:"outputFilters,omitempty"` - ExportOutputFilters []string `json:"exportOutputFilters,omitempty"` - Blocking bool `json:"-"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + MaxTokens int `json:"maxTokens,omitempty"` + ModelName string `json:"modelName,omitempty"` + ModelProvider bool `json:"modelProvider,omitempty"` + JSONResponse bool `json:"jsonResponse,omitempty"` + Chat bool `json:"chat,omitempty"` + Temperature *float32 `json:"temperature,omitempty"` + Cache *bool `json:"cache,omitempty"` + InternalPrompt *bool `json:"internalPrompt"` + Arguments *jsonschema.Schema `json:"arguments,omitempty"` + Tools []string `json:"tools,omitempty"` + GlobalTools []string `json:"globalTools,omitempty"` + GlobalModelName string `json:"globalModelName,omitempty"` + Context []string `json:"context,omitempty"` + ExportContext []string `json:"exportContext,omitempty"` + Export []string `json:"export,omitempty"` + Agents []string `json:"agents,omitempty"` + Credentials []string `json:"credentials,omitempty"` + ExportCredentials []string `json:"exportCredentials,omitempty"` + InputFilters []string `json:"inputFilters,omitempty"` + ExportInputFilters []string `json:"exportInputFilters,omitempty"` + OutputFilters []string `json:"outputFilters,omitempty"` + ExportOutputFilters []string `json:"exportOutputFilters,omitempty"` + Blocking bool `json:"-"` + Stdin bool `json:"stdin,omitempty"` + Type ToolType `json:"type,omitempty"` +} + +func (p Parameters) allExports() []string { + return slices.Concat( + p.ExportContext, + p.Export, + p.ExportCredentials, + p.ExportInputFilters, + p.ExportOutputFilters, + ) +} + +func (p Parameters) allReferences() []string { + return slices.Concat( + p.GlobalTools, + p.Tools, + p.Context, + p.Agents, + p.Credentials, + p.InputFilters, + p.OutputFilters, + ) } func (p Parameters) ToolRefNames() []string { @@ -154,6 +180,7 @@ func (p Parameters) ToolRefNames() []string { p.ExportContext, p.Context, p.Credentials, + p.ExportCredentials, p.InputFilters, p.ExportInputFilters, p.OutputFilters, @@ -162,8 +189,9 @@ func (p Parameters) ToolRefNames() []string { type ToolDef struct { Parameters `json:",inline"` - Instructions string `json:"instructions,omitempty"` - BuiltinFunc BuiltinFunc `json:"-"` + Instructions string `json:"instructions,omitempty"` + BuiltinFunc BuiltinFunc `json:"-"` + MetaData map[string]string `json:"metaData,omitempty"` } type Tool struct { @@ -248,9 +276,9 @@ func SplitArg(hasArg string) (prefix, arg string) { // - toolName: "toolName with ${var1} as arg1 and ${var2} as arg2" // - input: `{"var1": "value1", "var2": "value2"}` // result: toolName, "", map[string]any{"arg1": "value1", "arg2": "value2"}, nil -func ParseCredentialArgs(toolName string, input string) (string, string, map[string]any, error) { +func ParseCredentialArgs(toolName string, input string) (string, string, string, map[string]any, error) { if toolName == "" { - return "", "", nil, nil + return "", "", "", nil, nil } inputMap := make(map[string]any) @@ -263,12 +291,12 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str fields, err := shlex.Split(toolName) if err != nil { - return "", "", nil, err + return "", "", "", nil, err } // If it's just the tool name, return it if len(fields) == 1 { - return toolName, "", nil, nil + return toolName, "", "", nil, nil } // Next field is "as" if there is an alias, otherwise it should be "with" @@ -277,25 +305,39 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str fields = fields[1:] if fields[0] == "as" { if len(fields) < 2 { - return "", "", nil, fmt.Errorf("expected alias after 'as'") + return "", "", "", nil, fmt.Errorf("expected alias after 'as'") } alias = fields[1] fields = fields[2:] } if len(fields) == 0 { // Nothing left, so just return - return originalName, alias, nil, nil + return originalName, alias, "", nil, nil + } + + var checkParam string + if fields[0] == "checked" { + if len(fields) < 3 || fields[1] != "with" { + return "", "", "", nil, fmt.Errorf("expected 'checked with some_value' but got %v", fields) + } + + checkParam = fields[2] + fields = fields[3:] + } + + if len(fields) == 0 { // Nothing left, so just return + return originalName, alias, checkParam, nil, nil } // Next we should have "with" followed by the args if fields[0] != "with" { - return "", "", nil, fmt.Errorf("expected 'with' but got %s", fields[0]) + return "", "", "", nil, fmt.Errorf("expected 'with' but got %s", fields[0]) } fields = fields[1:] // If there are no args, return an error if len(fields) == 0 { - return "", "", nil, fmt.Errorf("expected args after 'with'") + return "", "", "", nil, fmt.Errorf("expected args after 'with'") } args := make(map[string]any) @@ -308,7 +350,7 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str prev = "value" case "value": if field != "as" { - return "", "", nil, fmt.Errorf("expected 'as' but got %s", field) + return "", "", "", nil, fmt.Errorf("expected 'as' but got %s", field) } prev = "as" case "as": @@ -316,14 +358,14 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str prev = "name" case "name": if field != "and" { - return "", "", nil, fmt.Errorf("expected 'and' but got %s", field) + return "", "", "", nil, fmt.Errorf("expected 'and' but got %s", field) } prev = "and" } } if prev == "and" { - return "", "", nil, fmt.Errorf("expected arg name after 'and'") + return "", "", "", nil, fmt.Errorf("expected arg name after 'and'") } // Check and see if any of the arg values are references to an input @@ -336,31 +378,7 @@ func ParseCredentialArgs(toolName string, input string) (string, string, map[str } } - return originalName, alias, args, nil -} - -func (t Tool) GetAgents(prg Program) (result []ToolReference, _ error) { - toolRefs, err := t.GetToolRefsFromNames(t.Agents) - if err != nil { - return nil, err - } - - // Agent Tool refs must be named - for i, toolRef := range toolRefs { - if toolRef.Named != "" { - continue - } - tool := prg.ToolSet[toolRef.ToolID] - name := tool.Name - if name == "" { - name = toolRef.Reference - } - normed := ToolNormalizer(name) - normed = strings.TrimSuffix(strings.TrimSuffix(normed, "Agent"), "Assistant") - toolRefs[i].Named = normed - } - - return toolRefs, nil + return originalName, alias, checkParam, args, nil } func (t Tool) GetToolRefsFromNames(names []string) (result []ToolReference, _ error) { @@ -389,304 +407,403 @@ func (t Tool) GetToolRefsFromNames(names []string) (result []ToolReference, _ er } func (t ToolDef) String() string { + data, err := json.Marshal([]any{t}) + if err != nil { + panic(err) + } + return "#!GPTSCRIPT" + string(data) +} + +func (t ToolDef) Print() string { buf := &strings.Builder{} - if t.Parameters.GlobalModelName != "" { - _, _ = fmt.Fprintf(buf, "Global Model Name: %s\n", t.Parameters.GlobalModelName) + if t.GlobalModelName != "" { + _, _ = fmt.Fprintf(buf, "Global Model Name: %s\n", t.GlobalModelName) } - if len(t.Parameters.GlobalTools) != 0 { - _, _ = fmt.Fprintf(buf, "Global Tools: %s\n", strings.Join(t.Parameters.GlobalTools, ", ")) + if len(t.GlobalTools) != 0 { + _, _ = fmt.Fprintf(buf, "Global Tools: %s\n", strings.Join(t.GlobalTools, ", ")) } - if t.Parameters.Name != "" { - _, _ = fmt.Fprintf(buf, "Name: %s\n", t.Parameters.Name) + if t.Name != "" { + _, _ = fmt.Fprintf(buf, "Name: %s\n", t.Name) } - if t.Parameters.Description != "" { - _, _ = fmt.Fprintf(buf, "Description: %s\n", t.Parameters.Description) + if t.Description != "" { + _, _ = fmt.Fprintf(buf, "Description: %s\n", t.Description) } - if len(t.Parameters.Agents) != 0 { - _, _ = fmt.Fprintf(buf, "Agents: %s\n", strings.Join(t.Parameters.Agents, ", ")) + if t.Type != ToolTypeDefault { + _, _ = fmt.Fprintf(buf, "Type: %s\n", strings.ToUpper(string(t.Type[0]))+string(t.Type[1:])) } - if len(t.Parameters.Tools) != 0 { - _, _ = fmt.Fprintf(buf, "Tools: %s\n", strings.Join(t.Parameters.Tools, ", ")) + if len(t.Agents) != 0 { + _, _ = fmt.Fprintf(buf, "Agents: %s\n", strings.Join(t.Agents, ", ")) } - if len(t.Parameters.Export) != 0 { - _, _ = fmt.Fprintf(buf, "Share Tools: %s\n", strings.Join(t.Parameters.Export, ", ")) + if len(t.Tools) != 0 { + _, _ = fmt.Fprintf(buf, "Tools: %s\n", strings.Join(t.Tools, ", ")) } - if len(t.Parameters.Context) != 0 { - _, _ = fmt.Fprintf(buf, "Context: %s\n", strings.Join(t.Parameters.Context, ", ")) + if len(t.Export) != 0 { + _, _ = fmt.Fprintf(buf, "Share Tools: %s\n", strings.Join(t.Export, ", ")) } - if len(t.Parameters.ExportContext) != 0 { - _, _ = fmt.Fprintf(buf, "Share Context: %s\n", strings.Join(t.Parameters.ExportContext, ", ")) + if len(t.Context) != 0 { + _, _ = fmt.Fprintf(buf, "Context: %s\n", strings.Join(t.Context, ", ")) } - if len(t.Parameters.InputFilters) != 0 { - _, _ = fmt.Fprintf(buf, "Input Filters: %s\n", strings.Join(t.Parameters.InputFilters, ", ")) + if len(t.ExportContext) != 0 { + _, _ = fmt.Fprintf(buf, "Share Context: %s\n", strings.Join(t.ExportContext, ", ")) } - if len(t.Parameters.ExportInputFilters) != 0 { - _, _ = fmt.Fprintf(buf, "Share Input Filters: %s\n", strings.Join(t.Parameters.ExportInputFilters, ", ")) + if len(t.InputFilters) != 0 { + _, _ = fmt.Fprintf(buf, "Input Filters: %s\n", strings.Join(t.InputFilters, ", ")) } - if len(t.Parameters.OutputFilters) != 0 { - _, _ = fmt.Fprintf(buf, "Output Filters: %s\n", strings.Join(t.Parameters.OutputFilters, ", ")) + if len(t.ExportInputFilters) != 0 { + _, _ = fmt.Fprintf(buf, "Share Input Filters: %s\n", strings.Join(t.ExportInputFilters, ", ")) } - if len(t.Parameters.ExportOutputFilters) != 0 { - _, _ = fmt.Fprintf(buf, "Share Output Filters: %s\n", strings.Join(t.Parameters.ExportOutputFilters, ", ")) + if len(t.OutputFilters) != 0 { + _, _ = fmt.Fprintf(buf, "Output Filters: %s\n", strings.Join(t.OutputFilters, ", ")) } - if t.Parameters.MaxTokens != 0 { - _, _ = fmt.Fprintf(buf, "Max Tokens: %d\n", t.Parameters.MaxTokens) + if len(t.ExportOutputFilters) != 0 { + _, _ = fmt.Fprintf(buf, "Share Output Filters: %s\n", strings.Join(t.ExportOutputFilters, ", ")) } - if t.Parameters.ModelName != "" { - _, _ = fmt.Fprintf(buf, "Model: %s\n", t.Parameters.ModelName) + if t.MaxTokens != 0 { + _, _ = fmt.Fprintf(buf, "Max Tokens: %d\n", t.MaxTokens) } - if t.Parameters.ModelProvider { + if t.ModelName != "" { + _, _ = fmt.Fprintf(buf, "Model: %s\n", t.ModelName) + } + if t.ModelProvider { _, _ = fmt.Fprintf(buf, "Model Provider: true\n") } - if t.Parameters.JSONResponse { + if t.JSONResponse { _, _ = fmt.Fprintln(buf, "JSON Response: true") } - if t.Parameters.Cache != nil && !*t.Parameters.Cache { + if t.Cache != nil && !*t.Cache { _, _ = fmt.Fprintln(buf, "Cache: false") } - if t.Parameters.Temperature != nil { - _, _ = fmt.Fprintf(buf, "Temperature: %f\n", *t.Parameters.Temperature) + if t.Stdin { + _, _ = fmt.Fprintln(buf, "Stdin: true") + } + if t.Temperature != nil { + _, _ = fmt.Fprintf(buf, "Temperature: %f\n", *t.Temperature) } - if t.Parameters.Arguments != nil { + if t.Arguments != nil { var keys []string - for k := range t.Parameters.Arguments.Properties { + for k := range t.Arguments.Properties { keys = append(keys, k) } sort.Strings(keys) for _, key := range keys { - prop := t.Parameters.Arguments.Properties[key] - _, _ = fmt.Fprintf(buf, "Parameter: %s: %s\n", key, prop.Value.Description) + prop := t.Arguments.Properties[key] + _, _ = fmt.Fprintf(buf, "Parameter: %s: %s\n", key, prop.Description) } } - if t.Parameters.InternalPrompt != nil { - _, _ = fmt.Fprintf(buf, "Internal Prompt: %v\n", *t.Parameters.InternalPrompt) + if t.InternalPrompt != nil { + _, _ = fmt.Fprintf(buf, "Internal Prompt: %v\n", *t.InternalPrompt) } - if len(t.Parameters.Credentials) > 0 { - for _, cred := range t.Parameters.Credentials { + if len(t.Credentials) > 0 { + for _, cred := range t.Credentials { _, _ = fmt.Fprintf(buf, "Credential: %s\n", cred) } } - if t.Parameters.Chat { + if len(t.ExportCredentials) > 0 { + for _, exportCred := range t.ExportCredentials { + _, _ = fmt.Fprintf(buf, "Share Credential: %s\n", exportCred) + } + } + if t.Chat { _, _ = fmt.Fprintf(buf, "Chat: true\n") } + keys := maps.Keys(t.MetaData) + sort.Strings(keys) + for _, key := range keys { + value := t.MetaData[key] + if !strings.Contains(value, "\n") { + _, _ = fmt.Fprintf(buf, "Meta Data: %s: %s\n", key, value) + } + } + // Instructions should be printed last if t.Instructions != "" && t.BuiltinFunc == nil { - _, _ = fmt.Fprintln(buf) + if strings.Contains(strings.Split(strings.TrimSpace(t.Instructions), "\n")[0], ":") { + _, _ = fmt.Fprintln(buf, "===") + } else { + _, _ = fmt.Fprintln(buf) + } _, _ = fmt.Fprintln(buf, t.Instructions) } + if t.Name != "" { + keys := maps.Keys(t.MetaData) + sort.Strings(keys) + for _, key := range keys { + value := t.MetaData[key] + if strings.Contains(value, "\n") { + buf.WriteString("---\n") + buf.WriteString("!metadata:") + buf.WriteString(t.Name) + buf.WriteString(":") + buf.WriteString(key) + buf.WriteString("\n") + buf.WriteString(t.MetaData[key]) + buf.WriteString("\n") + } + } + } + return buf.String() } -func (t Tool) GetExportedContext(prg Program) ([]ToolReference, error) { - result := &toolRefSet{} - - exportRefs, err := t.GetToolRefsFromNames(t.ExportContext) - if err != nil { - return nil, err - } - - for _, exportRef := range exportRefs { - result.Add(exportRef) +func (t Tool) GetNextAgentGroup(prg *Program, agentGroup []ToolReference, toolID string) (result []ToolReference, _ error) { + newAgentGroup := toolRefSet{} + newAgentGroup.AddAll(t.GetToolsByType(prg, ToolTypeAgent)) - tool := prg.ToolSet[exportRef.ToolID] - result.AddAll(tool.GetExportedContext(prg)) + if newAgentGroup.HasTool(toolID) { + // Join new agent group + return newAgentGroup.List() } - return result.List() + return agentGroup, nil } -func (t Tool) GetExportedTools(prg Program) ([]ToolReference, error) { - result := &toolRefSet{} - - exportRefs, err := t.GetToolRefsFromNames(t.Export) +func (t Tool) getCredentials(prg *Program) (result []ToolReference, _ error) { + toolRefs, err := t.GetToolRefsFromNames(t.Credentials) if err != nil { return nil, err } - for _, exportRef := range exportRefs { - result.Add(exportRef) - result.AddAll(prg.ToolSet[exportRef.ToolID].GetExportedTools(prg)) - } - - return result.List() -} - -// GetContextTools returns all tools that are in the context of the tool including all the -// contexts that are exported by the context tools. This will recurse all exports. -func (t Tool) GetContextTools(prg Program) ([]ToolReference, error) { - result := &toolRefSet{} + for _, toolRef := range toolRefs { + tool, ok := prg.ToolSet[toolRef.ToolID] + if !ok { + continue + } - contextRefs, err := t.GetToolRefsFromNames(t.Context) - if err != nil { - return nil, err - } + if !tool.IsNoop() { + result = append(result, toolRef) + } - for _, contextRef := range contextRefs { - result.AddAll(prg.ToolSet[contextRef.ToolID].GetExportedContext(prg)) - result.Add(contextRef) + shared, err := tool.getSharedCredentials(prg) + if err != nil { + return nil, err + } + result = append(result, shared...) } - return result.List() + return result, nil } -func (t Tool) GetOutputFilterTools(program Program) ([]ToolReference, error) { - result := &toolRefSet{} - - outputFilterRefs, err := t.GetToolRefsFromNames(t.OutputFilters) +func (t Tool) getSharedCredentials(prg *Program) (result []ToolReference, _ error) { + toolRefs, err := t.GetToolRefsFromNames(t.ExportCredentials) if err != nil { return nil, err } + for _, toolRef := range toolRefs { + tool, ok := prg.ToolSet[toolRef.ToolID] + if !ok { + continue + } - for _, outputFilterRef := range outputFilterRefs { - result.Add(outputFilterRef) + if !tool.IsNoop() { + result = append(result, toolRef) + } + + nested, err := tool.getSharedCredentials(prg) + if err != nil { + return nil, err + } + result = append(result, nested...) } + return result, nil +} - contextRefs, err := t.GetContextTools(program) +func (t Tool) getAgents(prg *Program) (result []ToolReference, _ error) { + toolRefs, err := t.GetToolRefsFromNames(t.Agents) if err != nil { return nil, err } - for _, contextRef := range contextRefs { - contextTool := program.ToolSet[contextRef.ToolID] - result.AddAll(contextTool.GetToolRefsFromNames(contextTool.ExportOutputFilters)) + // Agent Tool refs must be named + for i, toolRef := range toolRefs { + if toolRef.Named != "" { + continue + } + tool := prg.ToolSet[toolRef.ToolID] + name := tool.Name + if name == "" { + name = toolRef.Reference + } + normed := ToolNormalizer(name) + if trimmed := strings.TrimSuffix(strings.TrimSuffix(normed, "Agent"), "Assistant"); trimmed != "" { + normed = trimmed + } + toolRefs[i].Named = normed } - return result.List() + return toolRefs, nil } -func (t Tool) GetInputFilterTools(program Program) ([]ToolReference, error) { - result := &toolRefSet{} - - inputFilterRefs, err := t.GetToolRefsFromNames(t.InputFilters) - if err != nil { - return nil, err +func (t Tool) GetToolsByType(prg *Program, toolType ToolType) ([]ToolReference, error) { + switch toolType { + case ToolTypeAgent: + // Agents are special, they can only be sourced from direct references and not the generic 'tool:' or shared by references + return t.getAgents(prg) + case ToolTypeCredential: + // Credentials are special too, you can only get shared credentials from directly referenced credentials + return t.getCredentials(prg) } - for _, inputFilterRef := range inputFilterRefs { - result.Add(inputFilterRef) - } + toolSet := &toolRefSet{} - contextRefs, err := t.GetContextTools(program) - if err != nil { - return nil, err - } + var ( + directRefs []string + toolsListFilterType = []ToolType{toolType} + ) - for _, contextRef := range contextRefs { - contextTool := program.ToolSet[contextRef.ToolID] - result.AddAll(contextTool.GetToolRefsFromNames(contextTool.ExportInputFilters)) + switch toolType { + case ToolTypeContext: + directRefs = t.Context + case ToolTypeOutput: + directRefs = t.OutputFilters + case ToolTypeInput: + directRefs = t.InputFilters + case ToolTypeTool: + toolsListFilterType = append(toolsListFilterType, ToolTypeDefault, ToolTypeAgent) + default: + return nil, fmt.Errorf("unknown tool type %v", toolType) } - return result.List() -} + toolSet.AddAll(t.GetToolRefsFromNames(directRefs)) -func (t Tool) GetNextAgentGroup(prg Program, agentGroup []ToolReference, toolID string) (result []ToolReference, _ error) { - newAgentGroup := toolRefSet{} - if err := t.addAgents(prg, &newAgentGroup); err != nil { + toolRefs, err := t.GetToolRefsFromNames(t.Tools) + if err != nil { return nil, err } - if newAgentGroup.HasTool(toolID) { - // Join new agent group - return newAgentGroup.List() + for _, toolRef := range toolRefs { + tool, ok := prg.ToolSet[toolRef.ToolID] + if !ok { + continue + } + if slices.Contains(toolsListFilterType, tool.Type) { + toolSet.Add(toolRef) + } } - return agentGroup, nil -} - -func (t Tool) GetCompletionTools(prg Program, agentGroup ...ToolReference) (result []CompletionTool, err error) { - refs, err := t.getCompletionToolRefs(prg, agentGroup) + exportSources, err := t.getExportSources(prg) if err != nil { return nil, err } - return toolRefsToCompletionTools(refs, prg), nil -} -func (t Tool) addAgents(prg Program, result *toolRefSet) error { - subToolRefs, err := t.GetAgents(prg) - if err != nil { - return err - } + for _, exportSource := range exportSources { + var ( + tool = prg.ToolSet[exportSource.ToolID] + exportRefs []string + ) + + switch toolType { + case ToolTypeContext: + exportRefs = tool.ExportContext + case ToolTypeOutput: + exportRefs = tool.ExportOutputFilters + case ToolTypeInput: + exportRefs = tool.ExportInputFilters + case ToolTypeTool: + default: + return nil, fmt.Errorf("unknown tool type %v", toolType) + } + toolSet.AddAll(tool.GetToolRefsFromNames(exportRefs)) - for _, subToolRef := range subToolRefs { - // don't add yourself - if subToolRef.ToolID != t.ID { - // Add the tool itself and no exports - result.Add(subToolRef) + toolRefs, err := tool.GetToolRefsFromNames(tool.Export) + if err != nil { + return nil, err + } + + for _, toolRef := range toolRefs { + tool, ok := prg.ToolSet[toolRef.ToolID] + if !ok { + continue + } + if slices.Contains(toolsListFilterType, tool.Type) { + toolSet.Add(toolRef) + } } } - return nil + return toolSet.List() } -func (t Tool) addReferencedTools(prg Program, result *toolRefSet) error { - subToolRefs, err := t.GetToolRefsFromNames(t.Parameters.Tools) +func (t Tool) addExportsRecursively(prg *Program, toolSet *toolRefSet) error { + toolRefs, err := t.GetToolRefsFromNames(t.allExports()) if err != nil { return err } - for _, subToolRef := range subToolRefs { - // Add the tool - result.Add(subToolRef) + for _, toolRef := range toolRefs { + if toolSet.Contains(toolRef) { + continue + } - // Get all tools exports - result.AddAll(prg.ToolSet[subToolRef.ToolID].GetExportedTools(prg)) + toolSet.Add(toolRef) + if err := prg.ToolSet[toolRef.ToolID].addExportsRecursively(prg, toolSet); err != nil { + return err + } } return nil } -func (t Tool) addContextExportedTools(prg Program, result *toolRefSet) error { - contextTools, err := t.GetContextTools(prg) +func (t Tool) getExportSources(prg *Program) ([]ToolReference, error) { + // We start first with all references from this tool. This gives us the + // initial set of export sources. + // Then all tools in the export sources in the set we look for exports of those tools recursively. + // So a share of a share of a share should be added. + + toolSet := toolRefSet{} + toolRefs, err := t.GetToolRefsFromNames(t.allReferences()) if err != nil { - return err + return nil, err } - for _, contextTool := range contextTools { - result.AddAll(prg.ToolSet[contextTool.ToolID].GetExportedTools(prg)) + for _, toolRef := range toolRefs { + if err := prg.ToolSet[toolRef.ToolID].addExportsRecursively(prg, &toolSet); err != nil { + return nil, err + } + toolSet.Add(toolRef) } - return nil + return toolSet.List() } -func (t Tool) getCompletionToolRefs(prg Program, agentGroup []ToolReference) ([]ToolReference, error) { - result := toolRefSet{} +func (t Tool) GetChatCompletionTools(prg Program, agentGroup ...ToolReference) (result []ChatCompletionTool, err error) { + toolSet := &toolRefSet{} + toolSet.AddAll(t.GetToolsByType(&prg, ToolTypeTool)) + toolSet.AddAll(t.GetToolsByType(&prg, ToolTypeAgent)) - for _, agent := range agentGroup { - // don't add yourself - if agent.ToolID != t.ID { - result.Add(agent) + if t.Chat { + for _, agent := range agentGroup { + // don't add yourself + if agent.ToolID != t.ID { + toolSet.Add(agent) + } } } - if err := t.addReferencedTools(prg, &result); err != nil { - return nil, err - } - - if err := t.addContextExportedTools(prg, &result); err != nil { - return nil, err - } - - if err := t.addAgents(prg, &result); err != nil { + refs, err := toolSet.List() + if err != nil { return nil, err } - return result.List() + return toolRefsToCompletionTools(refs, prg), nil } -func toolRefsToCompletionTools(completionTools []ToolReference, prg Program) (result []CompletionTool) { +func toolRefsToCompletionTools(completionTools []ToolReference, prg Program) (result []ChatCompletionTool) { toolNames := map[string]struct{}{} for _, subToolRef := range completionTools { subTool := prg.ToolSet[subToolRef.ToolID] - subToolName := subToolRef.Reference + subToolName := subTool.Name + if subToolName == "" { + subToolName = subToolRef.Reference + } if subToolRef.Named != "" { subToolName = subToolRef.Named } - args := subTool.Parameters.Arguments + args := subTool.Arguments if args == nil && !subTool.IsCommand() && !subTool.Chat { args = &system.DefaultToolSchema } else if args == nil && !subTool.IsCommand() { @@ -696,11 +813,11 @@ func toolRefsToCompletionTools(completionTools []ToolReference, prg Program) (re if subTool.Instructions == "" { log.Debugf("Skipping zero instruction tool %s (%s)", subToolName, subTool.ID) } else { - result = append(result, CompletionTool{ + result = append(result, ChatCompletionTool{ Function: CompletionFunctionDefinition{ ToolID: subTool.ID, Name: PickToolName(subToolName, toolNames), - Description: subTool.Parameters.Description, + Description: subTool.Description, Parameters: args, }, }) @@ -729,6 +846,10 @@ type ToolSource struct { Repo *Repo `json:"repo,omitempty"` } +func (t ToolSource) IsGit() bool { + return t.Repo != nil && t.Repo.VCS == "git" +} + func (t ToolSource) String() string { return fmt.Sprintf("%s:%d", t.Location, t.LineNo) } @@ -759,14 +880,30 @@ func (t Tool) IsDaemon() bool { return strings.HasPrefix(t.Instructions, DaemonPrefix) } +func (t Tool) IsMCP() bool { + return strings.HasPrefix(t.Instructions, MCPPrefix) +} + +func (t Tool) IsMCPInvoke() bool { + return strings.HasPrefix(t.Instructions, MCPInvokePrefix) +} + func (t Tool) IsOpenAPI() bool { return strings.HasPrefix(t.Instructions, OpenAPIPrefix) } +func (t Tool) IsAgentsOnly() bool { + return t.IsNoop() && len(t.Context) == 0 +} + func (t Tool) IsEcho() bool { return strings.HasPrefix(t.Instructions, EchoPrefix) } +func (t Tool) IsCall() bool { + return strings.HasPrefix(t.Instructions, CallPrefix) +} + func (t Tool) IsHTTP() bool { return strings.HasPrefix(t.Instructions, "#!http://") || strings.HasPrefix(t.Instructions, "#!https://") diff --git a/pkg/types/tool_test.go b/pkg/types/tool_test.go index 43af6cee..ca5172cd 100644 --- a/pkg/types/tool_test.go +++ b/pkg/types/tool_test.go @@ -1,3 +1,4 @@ +//nolint:revive package types import ( @@ -6,7 +7,7 @@ import ( "github.com/hexops/autogold/v2" ) -func TestToolDef_String(t *testing.T) { +func TestToolDef_Print(t *testing.T) { tool := ToolDef{ Parameters: Parameters{ Name: "Tool Sample", @@ -33,6 +34,15 @@ func TestToolDef_String(t *testing.T) { ExportInputFilters: []string{"SharedFilter1", "SharedFilter2"}, OutputFilters: []string{"Filter1", "Filter2"}, ExportOutputFilters: []string{"SharedFilter1", "SharedFilter2"}, + ExportCredentials: []string{"ExportCredential1", "ExportCredential2"}, + Type: ToolTypeContext, + }, + MetaData: map[string]string{ + "package.json": `{ +// blah blah some ugly JSON +} +`, + "requirements.txt": `requests=5`, }, Instructions: "This is a sample instruction", } @@ -41,6 +51,7 @@ func TestToolDef_String(t *testing.T) { Global Tools: GlobalTool1, GlobalTool2 Name: Tool Sample Description: This is a sample tool +Type: Context Agents: Agent1, Agent2 Tools: Tool1, Tool2 Share Tools: Export1, Export2 @@ -60,10 +71,19 @@ Parameter: arg2: desc2 Internal Prompt: true Credential: Credential1 Credential: Credential2 +Share Credential: ExportCredential1 +Share Credential: ExportCredential2 Chat: true +Meta Data: requirements.txt: requests=5 This is a sample instruction -`).Equal(t, tool.String()) +--- +!metadata:Tool Sample:package.json +{ +// blah blah some ugly JSON +} + +`).Equal(t, tool.Print()) } // float32Ptr is used to return a pointer to a given float32 value @@ -75,3 +95,23 @@ func float32Ptr(f float32) *float32 { func boolPtr(b bool) *bool { return &b } + +func TestSplitArg(t *testing.T) { + prefix, arg := SplitArg("") + autogold.Expect([]string{"", ""}).Equal(t, []string{prefix, arg}) + + prefix, arg = SplitArg("toolName") + autogold.Expect([]string{"toolName", ""}).Equal(t, []string{prefix, arg}) + + prefix, arg = SplitArg("toolName as myAlias") + autogold.Expect([]string{"toolName", "as myAlias"}).Equal(t, []string{prefix, arg}) + + prefix, arg = SplitArg("toolName with value1 as arg1 and value2 as arg2") + autogold.Expect([]string{"toolName", "value1 as arg1 and value2 as arg2"}).Equal(t, []string{prefix, arg}) + + prefix, arg = SplitArg("toolName as myAlias with value1 as arg1 and value2 as arg2") + autogold.Expect([]string{"toolName", "value1 as arg1 and value2 as arg2"}).Equal(t, []string{prefix, arg}) + + prefix, arg = SplitArg("toolName with value1 as arg1 and value2 as arg2 as myAlias") + autogold.Expect([]string{"toolName", "value1 as arg1 and value2 as arg2 as myAlias"}).Equal(t, []string{prefix, arg}) +} diff --git a/pkg/types/toolname.go b/pkg/types/toolname.go index 622e9bb5..c43c2e80 100644 --- a/pkg/types/toolname.go +++ b/pkg/types/toolname.go @@ -1,3 +1,4 @@ +//nolint:revive package types import ( diff --git a/pkg/types/toolname_test.go b/pkg/types/toolname_test.go index fc7a66ab..0233baab 100644 --- a/pkg/types/toolname_test.go +++ b/pkg/types/toolname_test.go @@ -1,3 +1,4 @@ +//nolint:revive package types import ( diff --git a/pkg/types/toolstring.go b/pkg/types/toolstring.go index 64f53638..f9a69584 100644 --- a/pkg/types/toolstring.go +++ b/pkg/types/toolstring.go @@ -1,8 +1,10 @@ +//nolint:revive package types import ( "encoding/json" "fmt" + "os" "path/filepath" "strings" ) @@ -43,15 +45,18 @@ func ToDisplayText(tool Tool, input string) string { } func ToSysDisplayString(id string, args map[string]string) (string, error) { + if suffix, ok := strings.CutPrefix(id, MCPInvokePrefix); ok { + return fmt.Sprintf("Invoking MCP `%s`", suffix), nil + } + switch id { case "sys.append": return fmt.Sprintf("Appending to file `%s`", args["filename"]), nil case "sys.download": if location := args["location"]; location != "" { return fmt.Sprintf("Downloading `%s` to `%s`", args["url"], location), nil - } else { - return fmt.Sprintf("Downloading `%s` to workspace", args["url"]), nil } + return fmt.Sprintf("Downloading `%s` to workspace", args["url"]), nil case "sys.exec": return fmt.Sprintf("Running `%s`", args["command"]), nil case "sys.find": @@ -74,8 +79,22 @@ func ToSysDisplayString(id string, args map[string]string) (string, error) { return fmt.Sprintf("Removing `%s`", args["location"]), nil case "sys.write": return fmt.Sprintf("Writing `%s`", args["filename"]), nil - case "sys.context", "sys.stat", "sys.getenv", "sys.abort", "sys.chat.current", "sys.chat.finish", "sys.chat.history", "sys.echo", "sys.prompt", "sys.time.now": + case "sys.context", "sys.stat", "sys.getenv", "sys.abort", "sys.chat.current", "sys.chat.finish", "sys.chat.history", "sys.echo", "sys.prompt", "sys.time.now", "sys.model.provider.credential": return "", nil + case "sys.openapi": + if os.Getenv("GPTSCRIPT_OPENAPI_REVAMP") == "true" && args["operation"] != "" { + // Pretty print the JSON by unmarshaling and marshaling it + var jsonArgs map[string]any + if err := json.Unmarshal([]byte(args["args"]), &jsonArgs); err != nil { + return "", err + } + jsonPretty, err := json.MarshalIndent(jsonArgs, "", " ") + if err != nil { + return "", err + } + return fmt.Sprintf("Running API operation `%s` with arguments %s", args["operation"], string(jsonPretty)), nil + } + fallthrough default: return "", fmt.Errorf("unknown tool for display string: %s", id) } diff --git a/static/fs.go b/static/fs.go index 88e5f4ec..aff200c1 100644 --- a/static/fs.go +++ b/static/fs.go @@ -1,9 +1,6 @@ package static -import ( - "embed" - _ "embed" -) +import "embed" //go:embed * ui/_nuxt/* var UI embed.FS diff --git a/tools/gendocs/main.go b/tools/gendocs/main.go index f931bbea..362ea17b 100644 --- a/tools/gendocs/main.go +++ b/tools/gendocs/main.go @@ -40,7 +40,7 @@ func main() { func filePrepender(filename string) string { name := filepath.Base(filename) base := strings.TrimSuffix(name, path.Ext(name)) - return fmt.Sprintf(fmTemplate, strings.Replace(base, "_", " ", -1)) + return fmt.Sprintf(fmTemplate, strings.ReplaceAll(base, "_", " ")) } func linkHandler(name string) string {