From 33cef075d228e80aefb44671ec68e1989c2855a8 Mon Sep 17 00:00:00 2001 From: Dax Date: Thu, 31 Jul 2025 01:00:29 -0400 Subject: ci: new publish method (#1451) --- packages/opencode/package.json | 2 +- packages/opencode/script/publish.ts | 18 +- packages/opencode/src/format/formatter.ts | 50 +- packages/opencode/src/server/server.ts | 26 + packages/sdk/.devcontainer/devcontainer.json | 15 - packages/sdk/.prettierignore | 7 - packages/sdk/.prettierrc.json | 7 - packages/sdk/.release-please-manifest.json | 3 - packages/sdk/.stats.yml | 4 - packages/sdk/Brewfile | 1 - packages/sdk/CHANGELOG.md | 196 -- packages/sdk/CONTRIBUTING.md | 107 - packages/sdk/LICENSE | 7 - packages/sdk/README.md | 370 --- packages/sdk/SECURITY.md | 27 - packages/sdk/api.md | 139 - packages/sdk/bin/check-release-environment | 22 - packages/sdk/bin/publish-npm | 61 - packages/sdk/eslint.config.mjs | 42 - packages/sdk/examples/.keep | 4 - packages/sdk/go/.devcontainer/devcontainer.json | 7 + packages/sdk/go/.github/workflows/ci.yml | 49 + packages/sdk/go/.gitignore | 4 + packages/sdk/go/.release-please-manifest.json | 3 + packages/sdk/go/.stats.yml | 4 + packages/sdk/go/Brewfile | 1 + packages/sdk/go/CHANGELOG.md | 73 + packages/sdk/go/CONTRIBUTING.md | 66 + packages/sdk/go/LICENSE | 7 + packages/sdk/go/README.md | 354 ++ packages/sdk/go/SECURITY.md | 27 + packages/sdk/go/aliases.go | 43 + packages/sdk/go/api.md | 128 + packages/sdk/go/app.go | 368 ++ packages/sdk/go/app_test.go | 131 + packages/sdk/go/client.go | 125 + packages/sdk/go/client_test.go | 332 ++ packages/sdk/go/config.go | 887 +++++ packages/sdk/go/config_test.go | 36 + packages/sdk/go/event.go | 1373 ++++++++ packages/sdk/go/examples/.keep | 4 + packages/sdk/go/field.go | 50 + packages/sdk/go/file.go | 142 + packages/sdk/go/file_test.go | 60 + packages/sdk/go/find.go | 326 ++ packages/sdk/go/find_test.go | 86 + packages/sdk/go/go.mod | 13 + packages/sdk/go/go.sum | 10 + packages/sdk/go/internal/apierror/apierror.go | 53 + packages/sdk/go/internal/apiform/encoder.go | 383 +++ packages/sdk/go/internal/apiform/form.go | 5 + packages/sdk/go/internal/apiform/form_test.go | 440 +++ packages/sdk/go/internal/apiform/tag.go | 48 + packages/sdk/go/internal/apijson/decoder.go | 670 ++++ packages/sdk/go/internal/apijson/encoder.go | 398 +++ packages/sdk/go/internal/apijson/field.go | 41 + packages/sdk/go/internal/apijson/field_test.go | 66 + packages/sdk/go/internal/apijson/json_test.go | 617 ++++ packages/sdk/go/internal/apijson/port.go | 120 + packages/sdk/go/internal/apijson/port_test.go | 257 ++ packages/sdk/go/internal/apijson/registry.go | 41 + packages/sdk/go/internal/apijson/tag.go | 47 + packages/sdk/go/internal/apiquery/encoder.go | 341 ++ packages/sdk/go/internal/apiquery/query.go | 50 + packages/sdk/go/internal/apiquery/query_test.go | 335 ++ packages/sdk/go/internal/apiquery/tag.go | 41 + packages/sdk/go/internal/param/field.go | 29 + .../sdk/go/internal/requestconfig/requestconfig.go | 629 ++++ packages/sdk/go/internal/testutil/testutil.go | 27 + packages/sdk/go/internal/version.go | 5 + packages/sdk/go/lib/.keep | 4 + packages/sdk/go/option/middleware.go | 38 + packages/sdk/go/option/requestoption.go | 267 ++ packages/sdk/go/packages/ssestream/ssestream.go | 181 + packages/sdk/go/release-please-config.json | 67 + packages/sdk/go/scripts/bootstrap | 16 + packages/sdk/go/scripts/format | 8 + packages/sdk/go/scripts/lint | 11 + packages/sdk/go/scripts/mock | 41 + packages/sdk/go/scripts/test | 56 + packages/sdk/go/session.go | 2117 ++++++++++++ packages/sdk/go/session_test.go | 323 ++ packages/sdk/go/shared/shared.go | 173 + packages/sdk/go/tui.go | 56 + packages/sdk/go/tui_test.go | 60 + packages/sdk/go/usage_test.go | 32 + packages/sdk/jest.config.ts | 23 - packages/sdk/js/package.json | 17 + packages/sdk/js/script/generate.ts | 41 + packages/sdk/js/script/publish.ts | 24 + packages/sdk/js/src/gen/client.gen.ts | 18 + packages/sdk/js/src/gen/client/client.ts | 195 ++ packages/sdk/js/src/gen/client/index.ts | 22 + packages/sdk/js/src/gen/client/types.ts | 222 ++ packages/sdk/js/src/gen/client/utils.ts | 417 +++ packages/sdk/js/src/gen/core/auth.ts | 40 + packages/sdk/js/src/gen/core/bodySerializer.ts | 88 + packages/sdk/js/src/gen/core/params.ts | 151 + packages/sdk/js/src/gen/core/pathSerializer.ts | 179 + packages/sdk/js/src/gen/core/types.ts | 118 + packages/sdk/js/src/gen/sdk.gen.ts | 339 ++ packages/sdk/js/src/gen/types.gen.ts | 1450 ++++++++ packages/sdk/js/src/index.ts | 8 + packages/sdk/js/tsconfig.json | 13 + packages/sdk/package.json | 69 - packages/sdk/release-please-config.json | 64 - packages/sdk/scripts/bootstrap | 18 - packages/sdk/scripts/build | 51 - packages/sdk/scripts/format | 12 - packages/sdk/scripts/lint | 21 - packages/sdk/scripts/mock | 41 - packages/sdk/scripts/test | 56 - packages/sdk/scripts/utils/attw-report.cjs | 24 - .../sdk/scripts/utils/check-is-in-git-install.sh | 9 - packages/sdk/scripts/utils/check-version.cjs | 20 - packages/sdk/scripts/utils/fix-index-exports.cjs | 17 - packages/sdk/scripts/utils/git-swap.sh | 13 - .../sdk/scripts/utils/make-dist-package-json.cjs | 21 - packages/sdk/scripts/utils/postprocess-files.cjs | 94 - packages/sdk/scripts/utils/upload-artifact.sh | 25 - packages/sdk/src/api-promise.ts | 2 - packages/sdk/src/client.ts | 862 ----- packages/sdk/src/core/README.md | 3 - packages/sdk/src/core/api-promise.ts | 92 - packages/sdk/src/core/error.ts | 130 - packages/sdk/src/core/resource.ts | 11 - packages/sdk/src/core/streaming.ts | 315 -- packages/sdk/src/core/uploads.ts | 2 - packages/sdk/src/error.ts | 2 - packages/sdk/src/index.ts | 22 - packages/sdk/src/internal/README.md | 3 - packages/sdk/src/internal/builtin-types.ts | 93 - packages/sdk/src/internal/decoders/line.ts | 135 - packages/sdk/src/internal/detect-platform.ts | 196 -- packages/sdk/src/internal/errors.ts | 33 - packages/sdk/src/internal/headers.ts | 97 - packages/sdk/src/internal/parse.ts | 64 - packages/sdk/src/internal/request-options.ts | 93 - packages/sdk/src/internal/shim-types.ts | 26 - packages/sdk/src/internal/shims.ts | 107 - packages/sdk/src/internal/to-file.ts | 154 - packages/sdk/src/internal/types.ts | 95 - packages/sdk/src/internal/uploads.ts | 187 -- packages/sdk/src/internal/utils.ts | 8 - packages/sdk/src/internal/utils/base64.ts | 40 - packages/sdk/src/internal/utils/bytes.ts | 32 - packages/sdk/src/internal/utils/env.ts | 18 - packages/sdk/src/internal/utils/log.ts | 126 - packages/sdk/src/internal/utils/path.ts | 88 - packages/sdk/src/internal/utils/sleep.ts | 3 - packages/sdk/src/internal/utils/uuid.ts | 17 - packages/sdk/src/internal/utils/values.ts | 105 - packages/sdk/src/lib/.keep | 4 - packages/sdk/src/resource.ts | 2 - packages/sdk/src/resources.ts | 1 - packages/sdk/src/resources/app.ts | 192 -- packages/sdk/src/resources/config.ts | 492 --- packages/sdk/src/resources/event.ts | 267 -- packages/sdk/src/resources/file.ts | 52 - packages/sdk/src/resources/find.ts | 134 - packages/sdk/src/resources/index.ts | 81 - packages/sdk/src/resources/session.ts | 605 ---- packages/sdk/src/resources/shared.ts | 33 - packages/sdk/src/resources/tui.ts | 37 - packages/sdk/src/streaming.ts | 2 - packages/sdk/src/uploads.ts | 2 - packages/sdk/src/version.ts | 1 - packages/sdk/sst-env.d.ts | 9 - packages/sdk/stainless/generate.ts | 15 + packages/sdk/stainless/stainless-workspace.json | 5 + packages/sdk/stainless/stainless.yml | 151 + packages/sdk/tests/api-resources/app.test.ts | 77 - packages/sdk/tests/api-resources/config.test.ts | 19 - packages/sdk/tests/api-resources/event.test.ts | 19 - packages/sdk/tests/api-resources/file.test.ts | 36 - packages/sdk/tests/api-resources/find.test.ts | 58 - packages/sdk/tests/api-resources/session.test.ts | 191 -- packages/sdk/tests/api-resources/tui.test.ts | 36 - packages/sdk/tests/base64.test.ts | 80 - packages/sdk/tests/buildHeaders.test.ts | 88 - packages/sdk/tests/form.test.ts | 85 - packages/sdk/tests/index.test.ts | 690 ---- packages/sdk/tests/internal/decoders/line.test.ts | 128 - packages/sdk/tests/path.test.ts | 462 --- packages/sdk/tests/streaming.test.ts | 219 -- packages/sdk/tests/stringifyQuery.test.ts | 29 - packages/sdk/tests/uploads.test.ts | 104 - packages/sdk/tsc-multi.json | 15 - packages/sdk/tsconfig.build.json | 18 - packages/sdk/tsconfig.deno.json | 15 - packages/sdk/tsconfig.dist-src.json | 11 - packages/sdk/tsconfig.json | 38 - packages/sdk/yarn.lock | 3500 -------------------- 193 files changed, 16393 insertions(+), 12601 deletions(-) delete mode 100644 packages/sdk/.devcontainer/devcontainer.json delete mode 100644 packages/sdk/.prettierignore delete mode 100644 packages/sdk/.prettierrc.json delete mode 100644 packages/sdk/.release-please-manifest.json delete mode 100644 packages/sdk/.stats.yml delete mode 100644 packages/sdk/Brewfile delete mode 100644 packages/sdk/CHANGELOG.md delete mode 100644 packages/sdk/CONTRIBUTING.md delete mode 100644 packages/sdk/LICENSE delete mode 100644 packages/sdk/README.md delete mode 100644 packages/sdk/SECURITY.md delete mode 100644 packages/sdk/api.md delete mode 100644 packages/sdk/bin/check-release-environment delete mode 100644 packages/sdk/bin/publish-npm delete mode 100644 packages/sdk/eslint.config.mjs delete mode 100644 packages/sdk/examples/.keep create mode 100644 packages/sdk/go/.devcontainer/devcontainer.json create mode 100644 packages/sdk/go/.github/workflows/ci.yml create mode 100644 packages/sdk/go/.gitignore create mode 100644 packages/sdk/go/.release-please-manifest.json create mode 100644 packages/sdk/go/.stats.yml create mode 100644 packages/sdk/go/Brewfile create mode 100644 packages/sdk/go/CHANGELOG.md create mode 100644 packages/sdk/go/CONTRIBUTING.md create mode 100644 packages/sdk/go/LICENSE create mode 100644 packages/sdk/go/README.md create mode 100644 packages/sdk/go/SECURITY.md create mode 100644 packages/sdk/go/aliases.go create mode 100644 packages/sdk/go/api.md create mode 100644 packages/sdk/go/app.go create mode 100644 packages/sdk/go/app_test.go create mode 100644 packages/sdk/go/client.go create mode 100644 packages/sdk/go/client_test.go create mode 100644 packages/sdk/go/config.go create mode 100644 packages/sdk/go/config_test.go create mode 100644 packages/sdk/go/event.go create mode 100644 packages/sdk/go/examples/.keep create mode 100644 packages/sdk/go/field.go create mode 100644 packages/sdk/go/file.go create mode 100644 packages/sdk/go/file_test.go create mode 100644 packages/sdk/go/find.go create mode 100644 packages/sdk/go/find_test.go create mode 100644 packages/sdk/go/go.mod create mode 100644 packages/sdk/go/go.sum create mode 100644 packages/sdk/go/internal/apierror/apierror.go create mode 100644 packages/sdk/go/internal/apiform/encoder.go create mode 100644 packages/sdk/go/internal/apiform/form.go create mode 100644 packages/sdk/go/internal/apiform/form_test.go create mode 100644 packages/sdk/go/internal/apiform/tag.go create mode 100644 packages/sdk/go/internal/apijson/decoder.go create mode 100644 packages/sdk/go/internal/apijson/encoder.go create mode 100644 packages/sdk/go/internal/apijson/field.go create mode 100644 packages/sdk/go/internal/apijson/field_test.go create mode 100644 packages/sdk/go/internal/apijson/json_test.go create mode 100644 packages/sdk/go/internal/apijson/port.go create mode 100644 packages/sdk/go/internal/apijson/port_test.go create mode 100644 packages/sdk/go/internal/apijson/registry.go create mode 100644 packages/sdk/go/internal/apijson/tag.go create mode 100644 packages/sdk/go/internal/apiquery/encoder.go create mode 100644 packages/sdk/go/internal/apiquery/query.go create mode 100644 packages/sdk/go/internal/apiquery/query_test.go create mode 100644 packages/sdk/go/internal/apiquery/tag.go create mode 100644 packages/sdk/go/internal/param/field.go create mode 100644 packages/sdk/go/internal/requestconfig/requestconfig.go create mode 100644 packages/sdk/go/internal/testutil/testutil.go create mode 100644 packages/sdk/go/internal/version.go create mode 100644 packages/sdk/go/lib/.keep create mode 100644 packages/sdk/go/option/middleware.go create mode 100644 packages/sdk/go/option/requestoption.go create mode 100644 packages/sdk/go/packages/ssestream/ssestream.go create mode 100644 packages/sdk/go/release-please-config.json create mode 100755 packages/sdk/go/scripts/bootstrap create mode 100755 packages/sdk/go/scripts/format create mode 100755 packages/sdk/go/scripts/lint create mode 100755 packages/sdk/go/scripts/mock create mode 100755 packages/sdk/go/scripts/test create mode 100644 packages/sdk/go/session.go create mode 100644 packages/sdk/go/session_test.go create mode 100644 packages/sdk/go/shared/shared.go create mode 100644 packages/sdk/go/tui.go create mode 100644 packages/sdk/go/tui_test.go create mode 100644 packages/sdk/go/usage_test.go delete mode 100644 packages/sdk/jest.config.ts create mode 100644 packages/sdk/js/package.json create mode 100755 packages/sdk/js/script/generate.ts create mode 100644 packages/sdk/js/script/publish.ts create mode 100644 packages/sdk/js/src/gen/client.gen.ts create mode 100644 packages/sdk/js/src/gen/client/client.ts create mode 100644 packages/sdk/js/src/gen/client/index.ts create mode 100644 packages/sdk/js/src/gen/client/types.ts create mode 100644 packages/sdk/js/src/gen/client/utils.ts create mode 100644 packages/sdk/js/src/gen/core/auth.ts create mode 100644 packages/sdk/js/src/gen/core/bodySerializer.ts create mode 100644 packages/sdk/js/src/gen/core/params.ts create mode 100644 packages/sdk/js/src/gen/core/pathSerializer.ts create mode 100644 packages/sdk/js/src/gen/core/types.ts create mode 100644 packages/sdk/js/src/gen/sdk.gen.ts create mode 100644 packages/sdk/js/src/gen/types.gen.ts create mode 100644 packages/sdk/js/src/index.ts create mode 100644 packages/sdk/js/tsconfig.json delete mode 100644 packages/sdk/package.json delete mode 100644 packages/sdk/release-please-config.json delete mode 100755 packages/sdk/scripts/bootstrap delete mode 100755 packages/sdk/scripts/build delete mode 100755 packages/sdk/scripts/format delete mode 100755 packages/sdk/scripts/lint delete mode 100755 packages/sdk/scripts/mock delete mode 100755 packages/sdk/scripts/test delete mode 100644 packages/sdk/scripts/utils/attw-report.cjs delete mode 100755 packages/sdk/scripts/utils/check-is-in-git-install.sh delete mode 100644 packages/sdk/scripts/utils/check-version.cjs delete mode 100644 packages/sdk/scripts/utils/fix-index-exports.cjs delete mode 100755 packages/sdk/scripts/utils/git-swap.sh delete mode 100644 packages/sdk/scripts/utils/make-dist-package-json.cjs delete mode 100644 packages/sdk/scripts/utils/postprocess-files.cjs delete mode 100755 packages/sdk/scripts/utils/upload-artifact.sh delete mode 100644 packages/sdk/src/api-promise.ts delete mode 100644 packages/sdk/src/client.ts delete mode 100644 packages/sdk/src/core/README.md delete mode 100644 packages/sdk/src/core/api-promise.ts delete mode 100644 packages/sdk/src/core/error.ts delete mode 100644 packages/sdk/src/core/resource.ts delete mode 100644 packages/sdk/src/core/streaming.ts delete mode 100644 packages/sdk/src/core/uploads.ts delete mode 100644 packages/sdk/src/error.ts delete mode 100644 packages/sdk/src/index.ts delete mode 100644 packages/sdk/src/internal/README.md delete mode 100644 packages/sdk/src/internal/builtin-types.ts delete mode 100644 packages/sdk/src/internal/decoders/line.ts delete mode 100644 packages/sdk/src/internal/detect-platform.ts delete mode 100644 packages/sdk/src/internal/errors.ts delete mode 100644 packages/sdk/src/internal/headers.ts delete mode 100644 packages/sdk/src/internal/parse.ts delete mode 100644 packages/sdk/src/internal/request-options.ts delete mode 100644 packages/sdk/src/internal/shim-types.ts delete mode 100644 packages/sdk/src/internal/shims.ts delete mode 100644 packages/sdk/src/internal/to-file.ts delete mode 100644 packages/sdk/src/internal/types.ts delete mode 100644 packages/sdk/src/internal/uploads.ts delete mode 100644 packages/sdk/src/internal/utils.ts delete mode 100644 packages/sdk/src/internal/utils/base64.ts delete mode 100644 packages/sdk/src/internal/utils/bytes.ts delete mode 100644 packages/sdk/src/internal/utils/env.ts delete mode 100644 packages/sdk/src/internal/utils/log.ts delete mode 100644 packages/sdk/src/internal/utils/path.ts delete mode 100644 packages/sdk/src/internal/utils/sleep.ts delete mode 100644 packages/sdk/src/internal/utils/uuid.ts delete mode 100644 packages/sdk/src/internal/utils/values.ts delete mode 100644 packages/sdk/src/lib/.keep delete mode 100644 packages/sdk/src/resource.ts delete mode 100644 packages/sdk/src/resources.ts delete mode 100644 packages/sdk/src/resources/app.ts delete mode 100644 packages/sdk/src/resources/config.ts delete mode 100644 packages/sdk/src/resources/event.ts delete mode 100644 packages/sdk/src/resources/file.ts delete mode 100644 packages/sdk/src/resources/find.ts delete mode 100644 packages/sdk/src/resources/index.ts delete mode 100644 packages/sdk/src/resources/session.ts delete mode 100644 packages/sdk/src/resources/shared.ts delete mode 100644 packages/sdk/src/resources/tui.ts delete mode 100644 packages/sdk/src/streaming.ts delete mode 100644 packages/sdk/src/uploads.ts delete mode 100644 packages/sdk/src/version.ts delete mode 100644 packages/sdk/sst-env.d.ts create mode 100755 packages/sdk/stainless/generate.ts create mode 100644 packages/sdk/stainless/stainless-workspace.json create mode 100644 packages/sdk/stainless/stainless.yml delete mode 100644 packages/sdk/tests/api-resources/app.test.ts delete mode 100644 packages/sdk/tests/api-resources/config.test.ts delete mode 100644 packages/sdk/tests/api-resources/event.test.ts delete mode 100644 packages/sdk/tests/api-resources/file.test.ts delete mode 100644 packages/sdk/tests/api-resources/find.test.ts delete mode 100644 packages/sdk/tests/api-resources/session.test.ts delete mode 100644 packages/sdk/tests/api-resources/tui.test.ts delete mode 100644 packages/sdk/tests/base64.test.ts delete mode 100644 packages/sdk/tests/buildHeaders.test.ts delete mode 100644 packages/sdk/tests/form.test.ts delete mode 100644 packages/sdk/tests/index.test.ts delete mode 100644 packages/sdk/tests/internal/decoders/line.test.ts delete mode 100644 packages/sdk/tests/path.test.ts delete mode 100644 packages/sdk/tests/streaming.test.ts delete mode 100644 packages/sdk/tests/stringifyQuery.test.ts delete mode 100644 packages/sdk/tests/uploads.test.ts delete mode 100644 packages/sdk/tsc-multi.json delete mode 100644 packages/sdk/tsconfig.build.json delete mode 100644 packages/sdk/tsconfig.deno.json delete mode 100644 packages/sdk/tsconfig.dist-src.json delete mode 100644 packages/sdk/tsconfig.json delete mode 100644 packages/sdk/yarn.lock (limited to 'packages') diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 0ed936774..cc31e83b7 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.0.5", + "version": "0.0.0", "name": "opencode", "type": "module", "private": true, diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts index 12295be65..f7133da4c 100755 --- a/packages/opencode/script/publish.ts +++ b/packages/opencode/script/publish.ts @@ -1,21 +1,13 @@ #!/usr/bin/env bun - +const dir = new URL("..", import.meta.url).pathname +process.chdir(dir) import { $ } from "bun" import pkg from "../package.json" -const dry = process.argv.includes("--dry") -const snapshot = process.argv.includes("--snapshot") - -const version = snapshot - ? `0.0.0-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}` - : await $`git describe --tags --abbrev=0` - .text() - .then((x) => x.substring(1).trim()) - .catch(() => { - console.error("tag not found") - process.exit(1) - }) +const dry = process.env["OPENCODE_DRY"] === "true" +const version = process.env["OPENCODE_VERSION"]! +const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true" console.log(`publishing ${version}`) diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index 83e359f6f..8cb77df2b 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -1,7 +1,6 @@ import { App } from "../app/app" import { BunProc } from "../bun" import { Filesystem } from "../util/filesystem" -import path from "path" export interface Info { name: string @@ -65,14 +64,57 @@ export const prettier: Info = { ], async enabled() { const app = App.info() - const nms = await Filesystem.findUp("node_modules", app.path.cwd, app.path.root) - for (const item of nms) { - if (await Bun.file(path.join(item, ".bin", "prettier")).exists()) return true + const items = await Filesystem.findUp("package.json", app.path.cwd, app.path.root) + for (const item of items) { + const json = await Bun.file(item).json() + if (json.dependencies?.prettier) return true + if (json.devDependencies?.prettier) return true } return false }, } +export const biome: Info = { + name: "biome", + command: [BunProc.which(), "x", "biome", "format", "--write", "$FILE"], + environment: { + BUN_BE_BUN: "1", + }, + extensions: [ + ".js", + ".jsx", + ".mjs", + ".cjs", + ".ts", + ".tsx", + ".mts", + ".cts", + ".html", + ".htm", + ".css", + ".scss", + ".sass", + ".less", + ".vue", + ".svelte", + ".json", + ".jsonc", + ".yaml", + ".yml", + ".toml", + ".xml", + ".md", + ".mdx", + ".graphql", + ".gql", + ], + async enabled() { + const app = App.info() + const items = await Filesystem.findUp("biome.json", app.path.cwd, app.path.root) + return items.length > 0 + }, +} + export const zig: Info = { name: "zig", command: ["zig", "fmt", "$FILE"], diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 1c4f19b67..c3e42c4f6 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -94,6 +94,7 @@ export namespace Server { "/event", describeRoute({ description: "Get events", + operationId: "event.subscribe", responses: { 200: { description: "Event stream", @@ -137,6 +138,7 @@ export namespace Server { "/app", describeRoute({ description: "Get app info", + operationId: "app.get", responses: { 200: { description: "200", @@ -156,6 +158,7 @@ export namespace Server { "/app/init", describeRoute({ description: "Initialize the app", + operationId: "app.init", responses: { 200: { description: "Initialize the app", @@ -176,6 +179,7 @@ export namespace Server { "/config", describeRoute({ description: "Get config info", + operationId: "config.get", responses: { 200: { description: "Get config info", @@ -195,6 +199,7 @@ export namespace Server { "/session", describeRoute({ description: "List all sessions", + operationId: "session.list", responses: { 200: { description: "List of sessions", @@ -216,6 +221,7 @@ export namespace Server { "/session", describeRoute({ description: "Create a new session", + operationId: "session.create", responses: { ...ERRORS, 200: { @@ -237,6 +243,7 @@ export namespace Server { "/session/:id", describeRoute({ description: "Delete a session and all its data", + operationId: "session.delete", responses: { 200: { description: "Successfully deleted session", @@ -263,6 +270,7 @@ export namespace Server { "/session/:id/init", describeRoute({ description: "Analyze the app and create an AGENTS.md file", + operationId: "session.init", responses: { 200: { description: "200", @@ -299,6 +307,7 @@ export namespace Server { "/session/:id/abort", describeRoute({ description: "Abort a session", + operationId: "session.abort", responses: { 200: { description: "Aborted session", @@ -324,6 +333,7 @@ export namespace Server { "/session/:id/share", describeRoute({ description: "Share a session", + operationId: "session.share", responses: { 200: { description: "Successfully shared session", @@ -352,6 +362,7 @@ export namespace Server { "/session/:id/share", describeRoute({ description: "Unshare the session", + operationId: "session.unshare", responses: { 200: { description: "Successfully unshared session", @@ -380,6 +391,7 @@ export namespace Server { "/session/:id/summarize", describeRoute({ description: "Summarize the session", + operationId: "session.summarize", responses: { 200: { description: "Summarized session", @@ -415,6 +427,7 @@ export namespace Server { "/session/:id/message", describeRoute({ description: "List messages for a session", + operationId: "session.messages", responses: { 200: { description: "List of messages", @@ -448,6 +461,7 @@ export namespace Server { "/session/:id/message", describeRoute({ description: "Create and send a new message to a session", + operationId: "session.chat", responses: { 200: { description: "Created message", @@ -477,6 +491,7 @@ export namespace Server { "/session/:id/revert", describeRoute({ description: "Revert a message", + operationId: "session.revert", responses: { 200: { description: "Updated session", @@ -506,6 +521,7 @@ export namespace Server { "/session/:id/unrevert", describeRoute({ description: "Restore all reverted messages", + operationId: "session.unrevert", responses: { 200: { description: "Updated session", @@ -533,6 +549,7 @@ export namespace Server { "/config/providers", describeRoute({ description: "List all providers", + operationId: "config.providers", responses: { 200: { description: "List of providers", @@ -561,6 +578,7 @@ export namespace Server { "/find", describeRoute({ description: "Find text in files", + operationId: "find.text", responses: { 200: { description: "Matches", @@ -593,6 +611,7 @@ export namespace Server { "/find/file", describeRoute({ description: "Find files", + operationId: "find.files", responses: { 200: { description: "File paths", @@ -625,6 +644,7 @@ export namespace Server { "/find/symbol", describeRoute({ description: "Find workspace symbols", + operationId: "find.symbols", responses: { 200: { description: "Symbols", @@ -652,6 +672,7 @@ export namespace Server { "/file", describeRoute({ description: "Read a file", + operationId: "file.read", responses: { 200: { description: "File content", @@ -688,6 +709,7 @@ export namespace Server { "/file/status", describeRoute({ description: "Get file status", + operationId: "file.status", responses: { 200: { description: "File status", @@ -708,6 +730,7 @@ export namespace Server { "/log", describeRoute({ description: "Write a log entry to the server logs", + operationId: "app.log", responses: { 200: { description: "Log entry written successfully", @@ -757,6 +780,7 @@ export namespace Server { "/mode", describeRoute({ description: "List all modes", + operationId: "app.modes", responses: { 200: { description: "List of modes", @@ -777,6 +801,7 @@ export namespace Server { "/tui/append-prompt", describeRoute({ description: "Append prompt to the TUI", + operationId: "tui.appendPrompt", responses: { 200: { description: "Prompt processed successfully", @@ -800,6 +825,7 @@ export namespace Server { "/tui/open-help", describeRoute({ description: "Open the help dialog", + operationId: "tui.openHelp", responses: { 200: { description: "Help dialog opened successfully", diff --git a/packages/sdk/.devcontainer/devcontainer.json b/packages/sdk/.devcontainer/devcontainer.json deleted file mode 100644 index 43fd5a73f..000000000 --- a/packages/sdk/.devcontainer/devcontainer.json +++ /dev/null @@ -1,15 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/debian -{ - "name": "Development", - "image": "mcr.microsoft.com/devcontainers/typescript-node:latest", - "features": { - "ghcr.io/devcontainers/features/node:1": {} - }, - "postCreateCommand": "yarn install", - "customizations": { - "vscode": { - "extensions": ["esbenp.prettier-vscode"] - } - } -} diff --git a/packages/sdk/.prettierignore b/packages/sdk/.prettierignore deleted file mode 100644 index 3548c5af9..000000000 --- a/packages/sdk/.prettierignore +++ /dev/null @@ -1,7 +0,0 @@ -CHANGELOG.md -/ecosystem-tests/*/** -/node_modules -/deno - -# don't format tsc output, will break source maps -/dist diff --git a/packages/sdk/.prettierrc.json b/packages/sdk/.prettierrc.json deleted file mode 100644 index af75adaf6..000000000 --- a/packages/sdk/.prettierrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "arrowParens": "always", - "experimentalTernaries": true, - "printWidth": 110, - "singleQuote": true, - "trailingComma": "all" -} diff --git a/packages/sdk/.release-please-manifest.json b/packages/sdk/.release-please-manifest.json deleted file mode 100644 index de4ea38f0..000000000 --- a/packages/sdk/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "0.1.0-alpha.20" -} diff --git a/packages/sdk/.stats.yml b/packages/sdk/.stats.yml deleted file mode 100644 index 3f719fabf..000000000 --- a/packages/sdk/.stats.yml +++ /dev/null @@ -1,4 +0,0 @@ -configured_endpoints: 26 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-62d8fccba4eb8dc3a80434e0849eab3352e49fb96a718bb7b6d17ed8e582b716.yml -openapi_spec_hash: 4ff9376cf9634e91731e63fe482ea532 -config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3 diff --git a/packages/sdk/Brewfile b/packages/sdk/Brewfile deleted file mode 100644 index e4feee601..000000000 --- a/packages/sdk/Brewfile +++ /dev/null @@ -1 +0,0 @@ -brew "node" diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md deleted file mode 100644 index c51e0a038..000000000 --- a/packages/sdk/CHANGELOG.md +++ /dev/null @@ -1,196 +0,0 @@ -# Changelog - -## 0.1.0-alpha.20 (2025-07-16) - -Full Changelog: [v0.1.0-alpha.19...v0.1.0-alpha.20](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.19...v0.1.0-alpha.20) - -### Features - -* **api:** api update ([d296473](https://github.com/sst/opencode-sdk-js/commit/d296473db58378932b85d1afaa60942ac5599c49)) -* **api:** api update ([af2b587](https://github.com/sst/opencode-sdk-js/commit/af2b5875534a4782fac186542ecb9b04393c9b0a)) - -## 0.1.0-alpha.19 (2025-07-16) - -Full Changelog: [v0.1.0-alpha.18...v0.1.0-alpha.19](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.18...v0.1.0-alpha.19) - -### Features - -* **api:** api update ([2e505ef](https://github.com/sst/opencode-sdk-js/commit/2e505ef451fdcf49358189c5f76bdc42fb821352)) - -## 0.1.0-alpha.18 (2025-07-15) - -Full Changelog: [v0.1.0-alpha.17...v0.1.0-alpha.18](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.17...v0.1.0-alpha.18) - -### Features - -* **api:** api update ([25a23e5](https://github.com/sst/opencode-sdk-js/commit/25a23e599f1180754910961df65f0cc044aa2935)) - -## 0.1.0-alpha.17 (2025-07-15) - -Full Changelog: [v0.1.0-alpha.16...v0.1.0-alpha.17](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.16...v0.1.0-alpha.17) - -### Features - -* **api:** api update ([8b5d592](https://github.com/sst/opencode-sdk-js/commit/8b5d59243a0212f98269412f4483e729e2367a77)) -* **api:** api update ([ebd8986](https://github.com/sst/opencode-sdk-js/commit/ebd89862c48be2742eda727c83c01430413e00c0)) - -## 0.1.0-alpha.16 (2025-07-15) - -Full Changelog: [v0.1.0-alpha.15...v0.1.0-alpha.16](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.15...v0.1.0-alpha.16) - -### Features - -* **api:** api update ([f26379d](https://github.com/sst/opencode-sdk-js/commit/f26379d83ae7094d6ba91c6705a97a3fbd88a55a)) - - -### Chores - -* make some internal functions async ([36b1db9](https://github.com/sst/opencode-sdk-js/commit/36b1db9ca9d47d9199e2eab5f0b454b7cd31f58f)) - -## 0.1.0-alpha.15 (2025-07-05) - -Full Changelog: [v0.1.0-alpha.14...v0.1.0-alpha.15](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.14...v0.1.0-alpha.15) - -### Features - -* **api:** manual updates ([f6ee467](https://github.com/sst/opencode-sdk-js/commit/f6ee46752d0c174c8b934894cf2b140864864208)) - - -### Chores - -* **internal:** codegen related update ([47a1a97](https://github.com/sst/opencode-sdk-js/commit/47a1a972e755735d6b5472c61f726ab2face9e62)) - -## 0.1.0-alpha.14 (2025-07-03) - -Full Changelog: [v0.1.0-alpha.13...v0.1.0-alpha.14](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.13...v0.1.0-alpha.14) - -### Features - -* **api:** api update ([a1d7cf9](https://github.com/sst/opencode-sdk-js/commit/a1d7cf948a2ff47ce4e98b4a52d0e4d213b87bf6)) - - -### Chores - -* **internal:** version bump ([f8ad145](https://github.com/sst/opencode-sdk-js/commit/f8ad145b9af0c4a465642630043e59236d5f4e8d)) - -## 0.1.0-alpha.13 (2025-07-03) - -Full Changelog: [v0.1.0-alpha.12...v0.1.0-alpha.13](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.12...v0.1.0-alpha.13) - -### Bug Fixes - -* avoid console usage ([f96ac97](https://github.com/sst/opencode-sdk-js/commit/f96ac97fbaf7417efda306d8727654d1a4138386)) - - -### Chores - -* add docs to RequestOptions type ([1ca6677](https://github.com/sst/opencode-sdk-js/commit/1ca667765c22b706732c61ea3d9d2823aeda0a8e)) - -## 0.1.0-alpha.12 (2025-07-02) - -Full Changelog: [v0.1.0-alpha.11...v0.1.0-alpha.12](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.11...v0.1.0-alpha.12) - -### Features - -* **api:** update via SDK Studio ([7739340](https://github.com/sst/opencode-sdk-js/commit/77393403648067fe937637c39e80067c347a8c5b)) - -## 0.1.0-alpha.11 (2025-06-30) - -Full Changelog: [v0.1.0-alpha.10...v0.1.0-alpha.11](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.10...v0.1.0-alpha.11) - -### Features - -* **api:** update via SDK Studio ([2ce98e5](https://github.com/sst/opencode-sdk-js/commit/2ce98e55bf330cca0c38f60f011ffd9063b34ea0)) - -## 0.1.0-alpha.10 (2025-06-30) - -Full Changelog: [v0.1.0-alpha.9...v0.1.0-alpha.10](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.9...v0.1.0-alpha.10) - -### Features - -* **api:** update via SDK Studio ([fa7c91c](https://github.com/sst/opencode-sdk-js/commit/fa7c91cc2fe52d42be7365ca2c4ce3e48c2e76ac)) - - -### Chores - -* **ci:** only run for pushes and fork pull requests ([0e850e5](https://github.com/sst/opencode-sdk-js/commit/0e850e51daac413dcf2d5e30c0ea7a1cd5346c4b)) -* **client:** improve path param validation ([bc3ff0e](https://github.com/sst/opencode-sdk-js/commit/bc3ff0ee2de9af8be42deae87d12f003fb5f7aa5)) - -## 0.1.0-alpha.9 (2025-06-27) - -Full Changelog: [v0.1.0-alpha.8...v0.1.0-alpha.9](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.8...v0.1.0-alpha.9) - -### Features - -* **api:** update via SDK Studio ([7009d10](https://github.com/sst/opencode-sdk-js/commit/7009d10aab99be7102371cee49013ab3edae4450)) -* **api:** update via SDK Studio ([e60aa00](https://github.com/sst/opencode-sdk-js/commit/e60aa0024079671e3725ee6f6bfbf8c2dad78da2)) - -## 0.1.0-alpha.8 (2025-06-27) - -Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.7...v0.1.0-alpha.8) - -### Features - -* **api:** update via SDK Studio ([171e3d5](https://github.com/sst/opencode-sdk-js/commit/171e3d5f3ba69ff9ba8547dac90d85b1a0a137c1)) - -## 0.1.0-alpha.7 (2025-06-27) - -Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.6...v0.1.0-alpha.7) - -### Features - -* **api:** update via SDK Studio ([14d2d04](https://github.com/sst/opencode-sdk-js/commit/14d2d04d80c1d5880940c9c70a5c1ea200df2ebc)) - -## 0.1.0-alpha.6 (2025-06-27) - -Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.5...v0.1.0-alpha.6) - -### Features - -* **api:** update via SDK Studio ([45e78b2](https://github.com/sst/opencode-sdk-js/commit/45e78b2f0fca18f537de9986e358aa876fb0b686)) - -## 0.1.0-alpha.5 (2025-06-27) - -Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.4...v0.1.0-alpha.5) - -### Features - -* **api:** update via SDK Studio ([10a5be9](https://github.com/sst/opencode-sdk-js/commit/10a5be9261c4ba8aeece7bb6921752f5fa6d9f28)) - -## 0.1.0-alpha.4 (2025-06-27) - -Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.3...v0.1.0-alpha.4) - -### Features - -* **api:** update via SDK Studio ([20dcd17](https://github.com/sst/opencode-sdk-js/commit/20dcd171405b05801e5a56f1b40fd635259b6a94)) - -## 0.1.0-alpha.3 (2025-06-27) - -Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.2...v0.1.0-alpha.3) - -### Bug Fixes - -* **ci:** release-doctor — report correct token name ([128884f](https://github.com/sst/opencode-sdk-js/commit/128884f4bc64e618177a0b090cd6d52b122a059a)) - -## 0.1.0-alpha.2 (2025-06-24) - -Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencode-sdk-js/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) - -### Features - -* **api:** update via SDK Studio ([2320f32](https://github.com/sst/opencode-sdk-js/commit/2320f32190ab58d15d00d7c3328f9fba2421536c)) - -## 0.1.0-alpha.1 (2025-06-24) - -Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencode-sdk-js/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) - -### Features - -* **api:** update via SDK Studio ([e448306](https://github.com/sst/opencode-sdk-js/commit/e4483068738cbb10233fca5a9d9d44a9c9815c8b)) -* **api:** update via SDK Studio ([b222c96](https://github.com/sst/opencode-sdk-js/commit/b222c96a679a8aeecb60bcf92c247fef90c75b3d)) - - -### Chores - -* update SDK settings ([c1481ea](https://github.com/sst/opencode-sdk-js/commit/c1481ea7949c1422bedaeac278600b4ec3f58038)) diff --git a/packages/sdk/CONTRIBUTING.md b/packages/sdk/CONTRIBUTING.md deleted file mode 100644 index e582e41a7..000000000 --- a/packages/sdk/CONTRIBUTING.md +++ /dev/null @@ -1,107 +0,0 @@ -## Setting up the environment - -This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). -Other package managers may work but are not officially supported for development. - -To set up the repository, run: - -```sh -$ yarn -$ yarn build -``` - -This will install all the required dependencies and build output files to `dist/`. - -## Modifying/Adding code - -Most of the SDK is generated code. Modifications to code will be persisted between generations, but may -result in merge conflicts between manual patches and changes from the generator. The generator will never -modify the contents of the `src/lib/` and `examples/` directories. - -## Adding and running examples - -All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. - -```ts -// add an example to examples/.ts - -#!/usr/bin/env -S npm run tsn -T -… -``` - -```sh -$ chmod +x examples/.ts -# run the example against your api -$ yarn tsn -T examples/.ts -``` - -## Using the repository from source - -If you’d like to use the repository from source, you can either install from git or link to a cloned repository: - -To install via git: - -```sh -$ npm install git+ssh://git@github.com:sst/opencode-sdk-js.git -``` - -Alternatively, to link a local copy of the repo: - -```sh -# Clone -$ git clone https://www.github.com/sst/opencode-sdk-js -$ cd opencode-sdk-js - -# With yarn -$ yarn link -$ cd ../my-package -$ yarn link @opencode-ai/sdk - -# With pnpm -$ pnpm link --global -$ cd ../my-package -$ pnpm link -—global @opencode-ai/sdk -``` - -## Running tests - -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. - -```sh -$ npx prism mock path/to/your/openapi.yml -``` - -```sh -$ yarn run test -``` - -## Linting and formatting - -This repository uses [prettier](https://www.npmjs.com/package/prettier) and -[eslint](https://www.npmjs.com/package/eslint) to format the code in the repository. - -To lint: - -```sh -$ yarn lint -``` - -To format and fix all lint issues automatically: - -```sh -$ yarn fix -``` - -## Publishing and releases - -Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If -the changes aren't made through the automated pipeline, you may want to make releases manually. - -### Publish with a GitHub workflow - -You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/sst/opencode-sdk-js/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. - -### Publish manually - -If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on -the environment. diff --git a/packages/sdk/LICENSE b/packages/sdk/LICENSE deleted file mode 100644 index 821edebd5..000000000 --- a/packages/sdk/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright 2025 opencode - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/sdk/README.md b/packages/sdk/README.md deleted file mode 100644 index a06009b37..000000000 --- a/packages/sdk/README.md +++ /dev/null @@ -1,370 +0,0 @@ -# Opencode TypeScript API Library - -[![NPM version]()](https://npmjs.org/package/@opencode-ai/sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@opencode-ai/sdk) - -This library provides convenient access to the Opencode REST API from server-side TypeScript or JavaScript. - -The REST API documentation can be found on [opencode.ai](https://opencode.ai/docs). The full API of this library can be found in [api.md](api.md). - -It is generated with [Stainless](https://www.stainless.com/). - -## Installation - -```sh -npm install @opencode-ai/sdk -``` - -## Usage - -The full API of this library can be found in [api.md](api.md). - - -```js -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode(); - -const sessions = await client.session.list(); -``` - -## Streaming responses - -We provide support for streaming responses using Server Sent Events (SSE). - -```ts -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode(); - -const stream = await client.event.list(); -for await (const eventListResponse of stream) { - console.log(eventListResponse); -} -``` - -If you need to cancel a stream, you can `break` from the loop -or call `stream.controller.abort()`. - -### Request & Response types - -This library includes TypeScript definitions for all request params and response fields. You may import and use them like so: - - -```ts -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode(); - -const sessions: Opencode.SessionListResponse = await client.session.list(); -``` - -Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. - -## Handling errors - -When the library is unable to connect to the API, -or if the API returns a non-success status code (i.e., 4xx or 5xx response), -a subclass of `APIError` will be thrown: - - -```ts -const sessions = await client.session.list().catch(async (err) => { - if (err instanceof Opencode.APIError) { - console.log(err.status); // 400 - console.log(err.name); // BadRequestError - console.log(err.headers); // {server: 'nginx', ...} - } else { - throw err; - } -}); -``` - -Error codes are as follows: - -| Status Code | Error Type | -| ----------- | -------------------------- | -| 400 | `BadRequestError` | -| 401 | `AuthenticationError` | -| 403 | `PermissionDeniedError` | -| 404 | `NotFoundError` | -| 422 | `UnprocessableEntityError` | -| 429 | `RateLimitError` | -| >=500 | `InternalServerError` | -| N/A | `APIConnectionError` | - -### Retries - -Certain errors will be automatically retried 2 times by default, with a short exponential backoff. -Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, -429 Rate Limit, and >=500 Internal errors will all be retried by default. - -You can use the `maxRetries` option to configure or disable this: - - -```js -// Configure the default for all requests: -const client = new Opencode({ - maxRetries: 0, // default is 2 -}); - -// Or, configure per-request: -await client.session.list({ - maxRetries: 5, -}); -``` - -### Timeouts - -Requests time out after 1 minute by default. You can configure this with a `timeout` option: - - -```ts -// Configure the default for all requests: -const client = new Opencode({ - timeout: 20 * 1000, // 20 seconds (default is 1 minute) -}); - -// Override per-request: -await client.session.list({ - timeout: 5 * 1000, -}); -``` - -On timeout, an `APIConnectionTimeoutError` is thrown. - -Note that requests which time out will be [retried twice by default](#retries). - -## Advanced Usage - -### Accessing raw Response data (e.g., headers) - -The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return. -This method returns as soon as the headers for a successful response are received and does not consume the response body, so you are free to write custom parsing or streaming logic. - -You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data. -Unlike `.asResponse()` this method consumes the body, returning once it is parsed. - - -```ts -const client = new Opencode(); - -const response = await client.session.list().asResponse(); -console.log(response.headers.get('X-My-Header')); -console.log(response.statusText); // access the underlying Response object - -const { data: sessions, response: raw } = await client.session.list().withResponse(); -console.log(raw.headers.get('X-My-Header')); -console.log(sessions); -``` - -### Logging - -> [!IMPORTANT] -> All log messages are intended for debugging only. The format and content of log messages -> may change between releases. - -#### Log levels - -The log level can be configured in two ways: - -1. Via the `OPENCODE_LOG` environment variable -2. Using the `logLevel` client option (overrides the environment variable if set) - -```ts -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ - logLevel: 'debug', // Show all log messages -}); -``` - -Available log levels, from most to least verbose: - -- `'debug'` - Show debug messages, info, warnings, and errors -- `'info'` - Show info messages, warnings, and errors -- `'warn'` - Show warnings and errors (default) -- `'error'` - Show only errors -- `'off'` - Disable all logging - -At the `'debug'` level, all HTTP requests and responses are logged, including headers and bodies. -Some authentication-related headers are redacted, but sensitive data in request and response bodies -may still be visible. - -#### Custom logger - -By default, this library logs to `globalThis.console`. You can also provide a custom logger. -Most logging libraries are supported, including [pino](https://www.npmjs.com/package/pino), [winston](https://www.npmjs.com/package/winston), [bunyan](https://www.npmjs.com/package/bunyan), [consola](https://www.npmjs.com/package/consola), [signale](https://www.npmjs.com/package/signale), and [@std/log](https://jsr.io/@std/log). If your logger doesn't work, please open an issue. - -When providing a custom logger, the `logLevel` option still controls which messages are emitted, messages -below the configured level will not be sent to your logger. - -```ts -import Opencode from '@opencode-ai/sdk'; -import pino from 'pino'; - -const logger = pino(); - -const client = new Opencode({ - logger: logger.child({ name: 'Opencode' }), - logLevel: 'debug', // Send all messages to pino, allowing it to filter -}); -``` - -### Making custom/undocumented requests - -This library is typed for convenient access to the documented API. If you need to access undocumented -endpoints, params, or response properties, the library can still be used. - -#### Undocumented endpoints - -To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs. -Options on the client, such as retries, will be respected when making these requests. - -```ts -await client.post('/some/path', { - body: { some_prop: 'foo' }, - query: { some_query_arg: 'bar' }, -}); -``` - -#### Undocumented request params - -To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented -parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you -send will be sent as-is. - -```ts -client.session.list({ - // ... - // @ts-expect-error baz is not yet public - baz: 'undocumented option', -}); -``` - -For requests with the `GET` verb, any extra params will be in the query, all other requests will send the -extra param in the body. - -If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request -options. - -#### Undocumented response properties - -To access undocumented response properties, you may access the response object with `// @ts-expect-error` on -the response object, or cast the response object to the requisite type. Like the request params, we do not -validate or strip extra properties from the response from the API. - -### Customizing the fetch client - -By default, this library expects a global `fetch` function is defined. - -If you want to use a different `fetch` function, you can either polyfill the global: - -```ts -import fetch from 'my-fetch'; - -globalThis.fetch = fetch; -``` - -Or pass it to the client: - -```ts -import Opencode from '@opencode-ai/sdk'; -import fetch from 'my-fetch'; - -const client = new Opencode({ fetch }); -``` - -### Fetch options - -If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.) - -```ts -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ - fetchOptions: { - // `RequestInit` options - }, -}); -``` - -#### Configuring proxies - -To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy -options to requests: - - **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] - -```ts -import Opencode from '@opencode-ai/sdk'; -import * as undici from 'undici'; - -const proxyAgent = new undici.ProxyAgent('http://localhost:8888'); -const client = new Opencode({ - fetchOptions: { - dispatcher: proxyAgent, - }, -}); -``` - - **Bun** [[docs](https://bun.sh/guides/http/proxy)] - -```ts -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ - fetchOptions: { - proxy: 'http://localhost:8888', - }, -}); -``` - - **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)] - -```ts -import Opencode from 'npm:@opencode-ai/sdk'; - -const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } }); -const client = new Opencode({ - fetchOptions: { - client: httpClient, - }, -}); -``` - -## Frequently Asked Questions - -## Semantic versioning - -This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: - -1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ -3. Changes that we do not expect to impact the vast majority of users in practice. - -We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. - -We are keen for your feedback; please open an [issue](https://www.github.com/sst/opencode-sdk-js/issues) with questions, bugs, or suggestions. - -## Requirements - -TypeScript >= 4.9 is supported. - -The following runtimes are supported: - -- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) -- Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. -- Deno v1.28.0 or higher. -- Bun 1.0 or later. -- Cloudflare Workers. -- Vercel Edge Runtime. -- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time). -- Nitro v2.6 or greater. - -Note that React Native is not supported at this time. - -If you are interested in other runtime environments, please open or upvote an issue on GitHub. - -## Contributing - -See [the contributing documentation](./CONTRIBUTING.md). diff --git a/packages/sdk/SECURITY.md b/packages/sdk/SECURITY.md deleted file mode 100644 index 6912e12bc..000000000 --- a/packages/sdk/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# Security Policy - -## Reporting Security Issues - -This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. - -To report a security issue, please contact the Stainless team at security@stainless.com. - -## Responsible Disclosure - -We appreciate the efforts of security researchers and individuals who help us maintain the security of -SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible -disclosure practices by allowing us a reasonable amount of time to investigate and address the issue -before making any information public. - -## Reporting Non-SDK Related Security Issues - -If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Opencode, please follow the respective company's security reporting guidelines. - -### Opencode Terms and Policies - -Please contact support@sst.dev for any questions or concerns regarding the security of our services. - ---- - -Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/packages/sdk/api.md b/packages/sdk/api.md deleted file mode 100644 index c0526f063..000000000 --- a/packages/sdk/api.md +++ /dev/null @@ -1,139 +0,0 @@ -# Shared - -Types: - -- MessageAbortedError -- ProviderAuthError -- UnknownError - -# Event - -Types: - -- EventListResponse - -Methods: - -- client.event.list() -> EventListResponse - -# App - -Types: - -- App -- Mode -- Model -- Provider -- AppInitResponse -- AppLogResponse -- AppModesResponse -- AppProvidersResponse - -Methods: - -- client.app.get() -> App -- client.app.init() -> AppInitResponse -- client.app.log({ ...params }) -> AppLogResponse -- client.app.modes() -> AppModesResponse -- client.app.providers() -> AppProvidersResponse - -# Find - -Types: - -- Symbol -- FindFilesResponse -- FindSymbolsResponse -- FindTextResponse - -Methods: - -- client.find.files({ ...params }) -> FindFilesResponse -- client.find.symbols({ ...params }) -> FindSymbolsResponse -- client.find.text({ ...params }) -> FindTextResponse - -# File - -Types: - -- File -- FileReadResponse -- FileStatusResponse - -Methods: - -- client.file.read({ ...params }) -> FileReadResponse -- client.file.status() -> FileStatusResponse - -# Config - -Types: - -- Config -- KeybindsConfig -- McpLocalConfig -- McpRemoteConfig -- ModeConfig - -Methods: - -- client.config.get() -> Config - -# Session - -Types: - -- AssistantMessage -- FilePart -- FilePartInput -- FilePartSource -- FilePartSourceText -- FileSource -- Message -- Part -- Session -- SnapshotPart -- StepFinishPart -- StepStartPart -- SymbolSource -- TextPart -- TextPartInput -- ToolPart -- ToolStateCompleted -- ToolStateError -- ToolStatePending -- ToolStateRunning -- UserMessage -- SessionListResponse -- SessionDeleteResponse -- SessionAbortResponse -- SessionInitResponse -- SessionMessagesResponse -- SessionSummarizeResponse - -Methods: - -- client.session.create() -> Session -- client.session.list() -> SessionListResponse -- client.session.delete(id) -> SessionDeleteResponse -- client.session.abort(id) -> SessionAbortResponse -- client.session.chat(id, { ...params }) -> AssistantMessage -- client.session.init(id, { ...params }) -> SessionInitResponse -- client.session.messages(id) -> SessionMessagesResponse -- client.session.revert(id, { ...params }) -> Session -- client.session.share(id) -> Session -- client.session.summarize(id, { ...params }) -> SessionSummarizeResponse -- client.session.unrevert(id) -> Session -- client.session.unshare(id) -> Session - -# Tui - -Types: - -- TuiAppendPromptResponse -- TuiOpenHelpResponse - -Methods: - -- client.tui.appendPrompt({ ...params }) -> TuiAppendPromptResponse -- client.tui.openHelp() -> TuiOpenHelpResponse diff --git a/packages/sdk/bin/check-release-environment b/packages/sdk/bin/check-release-environment deleted file mode 100644 index e4b6d58ea..000000000 --- a/packages/sdk/bin/check-release-environment +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -errors=() - -if [ -z "${NPM_TOKEN}" ]; then - errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") -fi - -lenErrors=${#errors[@]} - -if [[ lenErrors -gt 0 ]]; then - echo -e "Found the following errors in the release environment:\n" - - for error in "${errors[@]}"; do - echo -e "- $error\n" - done - - exit 1 -fi - -echo "The environment is ready to push releases!" - diff --git a/packages/sdk/bin/publish-npm b/packages/sdk/bin/publish-npm deleted file mode 100644 index fa2243d24..000000000 --- a/packages/sdk/bin/publish-npm +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" - -yarn build -cd dist - -# Get package name and version from package.json -PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" -VERSION="$(jq -r -e '.version' ./package.json)" - -# Get latest version from npm -# -# If the package doesn't exist, npm will return: -# { -# "error": { -# "code": "E404", -# "summary": "Unpublished on 2025-06-05T09:54:53.528Z", -# "detail": "'the_package' is not in this registry..." -# } -# } -NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" - -# Check if we got an E404 error -if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then - # Package doesn't exist yet, no last version - LAST_VERSION="" -elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then - # Report other errors - echo "ERROR: npm returned unexpected data:" - echo "$NPM_INFO" - exit 1 -else - # Success - get the version - LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes -fi - -# Check if current version is pre-release (e.g. alpha / beta / rc) -CURRENT_IS_PRERELEASE=false -if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then - CURRENT_IS_PRERELEASE=true - CURRENT_TAG="${BASH_REMATCH[1]}" -fi - -# Check if last version is a stable release -LAST_IS_STABLE_RELEASE=true -if [[ -z "$LAST_VERSION" || "$LAST_VERSION" =~ -([a-zA-Z]+) ]]; then - LAST_IS_STABLE_RELEASE=false -fi - -# Use a corresponding alpha/beta tag if there already is a stable release and we're publishing a prerelease. -if $CURRENT_IS_PRERELEASE && $LAST_IS_STABLE_RELEASE; then - TAG="$CURRENT_TAG" -else - TAG="latest" -fi - -# Publish with the appropriate tag -yarn publish --access public --tag "$TAG" diff --git a/packages/sdk/eslint.config.mjs b/packages/sdk/eslint.config.mjs deleted file mode 100644 index 818092eda..000000000 --- a/packages/sdk/eslint.config.mjs +++ /dev/null @@ -1,42 +0,0 @@ -// @ts-check -import tseslint from 'typescript-eslint'; -import unusedImports from 'eslint-plugin-unused-imports'; -import prettier from 'eslint-plugin-prettier'; - -export default tseslint.config( - { - languageOptions: { - parser: tseslint.parser, - parserOptions: { sourceType: 'module' }, - }, - files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.js', '**/*.mjs', '**/*.cjs'], - ignores: ['dist/'], - plugins: { - '@typescript-eslint': tseslint.plugin, - 'unused-imports': unusedImports, - prettier, - }, - rules: { - 'no-unused-vars': 'off', - 'prettier/prettier': 'error', - 'unused-imports/no-unused-imports': 'error', - 'no-restricted-imports': [ - 'error', - { - patterns: [ - { - regex: '^@opencode-ai/sdk(/.*)?', - message: 'Use a relative import, not a package import.', - }, - ], - }, - ], - }, - }, - { - files: ['tests/**', 'examples/**'], - rules: { - 'no-restricted-imports': 'off', - }, - }, -); diff --git a/packages/sdk/examples/.keep b/packages/sdk/examples/.keep deleted file mode 100644 index 0651c89c0..000000000 --- a/packages/sdk/examples/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store example files demonstrating usage of this SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/packages/sdk/go/.devcontainer/devcontainer.json b/packages/sdk/go/.devcontainer/devcontainer.json new file mode 100644 index 000000000..889ae3472 --- /dev/null +++ b/packages/sdk/go/.devcontainer/devcontainer.json @@ -0,0 +1,7 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "Development", + "image": "mcr.microsoft.com/devcontainers/go:1.23-bookworm", + "postCreateCommand": "go mod tidy" +} diff --git a/packages/sdk/go/.github/workflows/ci.yml b/packages/sdk/go/.github/workflows/ci.yml new file mode 100644 index 000000000..4bf1e907c --- /dev/null +++ b/packages/sdk/go/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI +on: + push: + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'stl-preview-head/**' + - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' + +jobs: + lint: + timeout-minutes: 10 + name: lint + runs-on: ${{ github.repository == 'stainless-sdks/opencode-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + + steps: + - uses: actions/checkout@v4 + + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + + - name: Run lints + run: ./scripts/lint + test: + timeout-minutes: 10 + name: test + runs-on: ${{ github.repository == 'stainless-sdks/opencode-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + steps: + - uses: actions/checkout@v4 + + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + + - name: Bootstrap + run: ./scripts/bootstrap + + - name: Run tests + run: ./scripts/test diff --git a/packages/sdk/go/.gitignore b/packages/sdk/go/.gitignore new file mode 100644 index 000000000..c6d050151 --- /dev/null +++ b/packages/sdk/go/.gitignore @@ -0,0 +1,4 @@ +.prism.log +codegen.log +Brewfile.lock.json +.idea/ diff --git a/packages/sdk/go/.release-please-manifest.json b/packages/sdk/go/.release-please-manifest.json new file mode 100644 index 000000000..c373724dc --- /dev/null +++ b/packages/sdk/go/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0-alpha.8" +} \ No newline at end of file diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml new file mode 100644 index 000000000..a42ec8ee2 --- /dev/null +++ b/packages/sdk/go/.stats.yml @@ -0,0 +1,4 @@ +configured_endpoints: 26 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-5bf6a39123d248d306490c1dee61b46ba113ea2c415a4de1a631c76462769c49.yml +openapi_spec_hash: 3c5b25f121429281275ffd70c9d5cfe4 +config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3 diff --git a/packages/sdk/go/Brewfile b/packages/sdk/go/Brewfile new file mode 100644 index 000000000..577e34a4b --- /dev/null +++ b/packages/sdk/go/Brewfile @@ -0,0 +1 @@ +brew "go" diff --git a/packages/sdk/go/CHANGELOG.md b/packages/sdk/go/CHANGELOG.md new file mode 100644 index 000000000..bc407fad9 --- /dev/null +++ b/packages/sdk/go/CHANGELOG.md @@ -0,0 +1,73 @@ +# Changelog + +## 0.1.0-alpha.8 (2025-07-02) + +Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.7...v0.1.0-alpha.8) + +### Features + +* **api:** update via SDK Studio ([651e937](https://github.com/sst/opencode-sdk-go/commit/651e937c334e1caba3b968e6cac865c219879519)) + +## 0.1.0-alpha.7 (2025-06-30) + +Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.6...v0.1.0-alpha.7) + +### Features + +* **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775)) +* **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf)) + + +### Chores + +* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d)) + +## 0.1.0-alpha.6 (2025-06-28) + +Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.5...v0.1.0-alpha.6) + +### Bug Fixes + +* don't try to deserialize as json when ResponseBodyInto is []byte ([5988d04](https://github.com/sst/opencode-sdk-go/commit/5988d04839cb78b6613057280b91b72a60fef33d)) + +## 0.1.0-alpha.5 (2025-06-27) + +Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.4...v0.1.0-alpha.5) + +### Features + +* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e)) + +## 0.1.0-alpha.4 (2025-06-27) + +Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.3...v0.1.0-alpha.4) + +### Features + +* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52)) + +## 0.1.0-alpha.3 (2025-06-27) + +Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.2...v0.1.0-alpha.3) + +### Features + +* **api:** update via SDK Studio ([57f3230](https://github.com/sst/opencode-sdk-go/commit/57f32309023cc1f0f20c20d02a3907e390a71f61)) + +## 0.1.0-alpha.2 (2025-06-27) + +Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencode-sdk-go/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) + +### Features + +* **api:** update via SDK Studio ([a766f1c](https://github.com/sst/opencode-sdk-go/commit/a766f1c54f02bbc1380151b0e22d97cc2c5892e6)) + +## 0.1.0-alpha.1 (2025-06-27) + +Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencode-sdk-go/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) + +### Features + +* **api:** update via SDK Studio ([27b7376](https://github.com/sst/opencode-sdk-go/commit/27b7376310466ee17a63f2104f546b53a2b8361a)) +* **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397)) +* **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844)) diff --git a/packages/sdk/go/CONTRIBUTING.md b/packages/sdk/go/CONTRIBUTING.md new file mode 100644 index 000000000..34620a3c1 --- /dev/null +++ b/packages/sdk/go/CONTRIBUTING.md @@ -0,0 +1,66 @@ +## Setting up the environment + +To set up the repository, run: + +```sh +$ ./scripts/bootstrap +$ ./scripts/build +``` + +This will install all the required dependencies and build the SDK. + +You can also [install go 1.18+ manually](https://go.dev/doc/install). + +## Modifying/Adding code + +Most of the SDK is generated code. Modifications to code will be persisted between generations, but may +result in merge conflicts between manual patches and changes from the generator. The generator will never +modify the contents of the `lib/` and `examples/` directories. + +## Adding and running examples + +All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. + +```go +# add an example to examples//main.go + +package main + +func main() { + // ... +} +``` + +```sh +$ go run ./examples/ +``` + +## Using the repository from source + +To use a local version of this library from source in another project, edit the `go.mod` with a replace +directive. This can be done through the CLI with the following: + +```sh +$ go mod edit -replace github.com/sst/opencode-sdk-go=/path/to/opencode-sdk-go +``` + +## Running tests + +Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. + +```sh +# you will need npm installed +$ npx prism mock path/to/your/openapi.yml +``` + +```sh +$ ./scripts/test +``` + +## Formatting + +This library uses the standard gofmt code formatter: + +```sh +$ ./scripts/format +``` diff --git a/packages/sdk/go/LICENSE b/packages/sdk/go/LICENSE new file mode 100644 index 000000000..821edebd5 --- /dev/null +++ b/packages/sdk/go/LICENSE @@ -0,0 +1,7 @@ +Copyright 2025 opencode + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/sdk/go/README.md b/packages/sdk/go/README.md new file mode 100644 index 000000000..2588b6147 --- /dev/null +++ b/packages/sdk/go/README.md @@ -0,0 +1,354 @@ +# Opencode Go API Library + +Go Reference + +The Opencode Go library provides convenient access to the [Opencode REST API](https://opencode.ai/docs) +from applications written in Go. + +It is generated with [Stainless](https://www.stainless.com/). + +## Installation + + + +```go +import ( + "github.com/sst/opencode-sdk-go" // imported as opencode +) +``` + + + +Or to pin the version: + + + +```sh +go get -u 'github.com/sst/opencode-sdk-go@v0.1.0-alpha.8' +``` + + + +## Requirements + +This library requires Go 1.18+. + +## Usage + +The full API of this library can be found in [api.md](api.md). + +```go +package main + +import ( + "context" + "fmt" + + "github.com/sst/opencode-sdk-go" +) + +func main() { + client := opencode.NewClient() + sessions, err := client.Session.List(context.TODO()) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", sessions) +} + +``` + +### Request fields + +All request parameters are wrapped in a generic `Field` type, +which we use to distinguish zero values from null or omitted fields. + +This prevents accidentally sending a zero value if you forget a required parameter, +and enables explicitly sending `null`, `false`, `''`, or `0` on optional parameters. +Any field not specified is not sent. + +To construct fields with values, use the helpers `String()`, `Int()`, `Float()`, or most commonly, the generic `F[T]()`. +To send a null, use `Null[T]()`, and to send a nonconforming value, use `Raw[T](any)`. For example: + +```go +params := FooParams{ + Name: opencode.F("hello"), + + // Explicitly send `"description": null` + Description: opencode.Null[string](), + + Point: opencode.F(opencode.Point{ + X: opencode.Int(0), + Y: opencode.Int(1), + + // In cases where the API specifies a given type, + // but you want to send something else, use `Raw`: + Z: opencode.Raw[int64](0.01), // sends a float + }), +} +``` + +### Response objects + +All fields in response structs are value types (not pointers or wrappers). + +If a given field is `null`, not present, or invalid, the corresponding field +will simply be its zero value. + +All response structs also include a special `JSON` field, containing more detailed +information about each property, which you can use like so: + +```go +if res.Name == "" { + // true if `"name"` is either not present or explicitly null + res.JSON.Name.IsNull() + + // true if the `"name"` key was not present in the response JSON at all + res.JSON.Name.IsMissing() + + // When the API returns data that cannot be coerced to the expected type: + if res.JSON.Name.IsInvalid() { + raw := res.JSON.Name.Raw() + + legacyName := struct{ + First string `json:"first"` + Last string `json:"last"` + }{} + json.Unmarshal([]byte(raw), &legacyName) + name = legacyName.First + " " + legacyName.Last + } +} +``` + +These `.JSON` structs also include an `Extras` map containing +any properties in the json response that were not specified +in the struct. This can be useful for API features not yet +present in the SDK. + +```go +body := res.JSON.ExtraFields["my_unexpected_field"].Raw() +``` + +### RequestOptions + +This library uses the functional options pattern. Functions defined in the +`option` package return a `RequestOption`, which is a closure that mutates a +`RequestConfig`. These options can be supplied to the client or at individual +requests. For example: + +```go +client := opencode.NewClient( + // Adds a header to every request made by the client + option.WithHeader("X-Some-Header", "custom_header_info"), +) + +client.Session.List(context.TODO(), ..., + // Override the header + option.WithHeader("X-Some-Header", "some_other_custom_header_info"), + // Add an undocumented field to the request body, using sjson syntax + option.WithJSONSet("some.json.path", map[string]string{"my": "object"}), +) +``` + +See the [full list of request options](https://pkg.go.dev/github.com/sst/opencode-sdk-go/option). + +### Pagination + +This library provides some conveniences for working with paginated list endpoints. + +You can use `.ListAutoPaging()` methods to iterate through items across all pages: + +Or you can use simple `.List()` methods to fetch a single page and receive a standard response object +with additional helper methods like `.GetNextPage()`, e.g.: + +### Errors + +When the API returns a non-success status code, we return an error with type +`*opencode.Error`. This contains the `StatusCode`, `*http.Request`, and +`*http.Response` values of the request, as well as the JSON of the error body +(much like other response objects in the SDK). + +To handle errors, we recommend that you use the `errors.As` pattern: + +```go +_, err := client.Session.List(context.TODO()) +if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + println(string(apierr.DumpRequest(true))) // Prints the serialized HTTP request + println(string(apierr.DumpResponse(true))) // Prints the serialized HTTP response + } + panic(err.Error()) // GET "/session": 400 Bad Request { ... } +} +``` + +When other errors occur, they are returned unwrapped; for example, +if HTTP transport fails, you might receive `*url.Error` wrapping `*net.OpError`. + +### Timeouts + +Requests do not time out by default; use context to configure a timeout for a request lifecycle. + +Note that if a request is [retried](#retries), the context timeout does not start over. +To set a per-retry timeout, use `option.WithRequestTimeout()`. + +```go +// This sets the timeout for the request, including all the retries. +ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) +defer cancel() +client.Session.List( + ctx, + // This sets the per-retry timeout + option.WithRequestTimeout(20*time.Second), +) +``` + +### File uploads + +Request parameters that correspond to file uploads in multipart requests are typed as +`param.Field[io.Reader]`. The contents of the `io.Reader` will by default be sent as a multipart form +part with the file name of "anonymous_file" and content-type of "application/octet-stream". + +The file name and content-type can be customized by implementing `Name() string` or `ContentType() +string` on the run-time type of `io.Reader`. Note that `os.File` implements `Name() string`, so a +file returned by `os.Open` will be sent with the file name on disk. + +We also provide a helper `opencode.FileParam(reader io.Reader, filename string, contentType string)` +which can be used to wrap any `io.Reader` with the appropriate file name and content type. + +### Retries + +Certain errors will be automatically retried 2 times by default, with a short exponential backoff. +We retry by default all connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit, +and >=500 Internal errors. + +You can use the `WithMaxRetries` option to configure or disable this: + +```go +// Configure the default for all requests: +client := opencode.NewClient( + option.WithMaxRetries(0), // default is 2 +) + +// Override per-request: +client.Session.List(context.TODO(), option.WithMaxRetries(5)) +``` + +### Accessing raw response data (e.g. response headers) + +You can access the raw HTTP response data by using the `option.WithResponseInto()` request option. This is useful when +you need to examine response headers, status codes, or other details. + +```go +// Create a variable to store the HTTP response +var response *http.Response +sessions, err := client.Session.List(context.TODO(), option.WithResponseInto(&response)) +if err != nil { + // handle error +} +fmt.Printf("%+v\n", sessions) + +fmt.Printf("Status Code: %d\n", response.StatusCode) +fmt.Printf("Headers: %+#v\n", response.Header) +``` + +### Making custom/undocumented requests + +This library is typed for convenient access to the documented API. If you need to access undocumented +endpoints, params, or response properties, the library can still be used. + +#### Undocumented endpoints + +To make requests to undocumented endpoints, you can use `client.Get`, `client.Post`, and other HTTP verbs. +`RequestOptions` on the client, such as retries, will be respected when making these requests. + +```go +var ( + // params can be an io.Reader, a []byte, an encoding/json serializable object, + // or a "…Params" struct defined in this library. + params map[string]interface{} + + // result can be an []byte, *http.Response, a encoding/json deserializable object, + // or a model defined in this library. + result *http.Response +) +err := client.Post(context.Background(), "/unspecified", params, &result) +if err != nil { + … +} +``` + +#### Undocumented request params + +To make requests using undocumented parameters, you may use either the `option.WithQuerySet()` +or the `option.WithJSONSet()` methods. + +```go +params := FooNewParams{ + ID: opencode.F("id_xxxx"), + Data: opencode.F(FooNewParamsData{ + FirstName: opencode.F("John"), + }), +} +client.Foo.New(context.Background(), params, option.WithJSONSet("data.last_name", "Doe")) +``` + +#### Undocumented response properties + +To access undocumented response properties, you may either access the raw JSON of the response as a string +with `result.JSON.RawJSON()`, or get the raw JSON of a particular field on the result with +`result.JSON.Foo.Raw()`. + +Any fields that are not present on the response struct will be saved and can be accessed by `result.JSON.ExtraFields()` which returns the extra fields as a `map[string]Field`. + +### Middleware + +We provide `option.WithMiddleware` which applies the given +middleware to requests. + +```go +func Logger(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) { + // Before the request + start := time.Now() + LogReq(req) + + // Forward the request to the next handler + res, err = next(req) + + // Handle stuff after the request + end := time.Now() + LogRes(res, err, start - end) + + return res, err +} + +client := opencode.NewClient( + option.WithMiddleware(Logger), +) +``` + +When multiple middlewares are provided as variadic arguments, the middlewares +are applied left to right. If `option.WithMiddleware` is given +multiple times, for example first in the client then the method, the +middleware in the client will run first and the middleware given in the method +will run next. + +You may also replace the default `http.Client` with +`option.WithHTTPClient(client)`. Only one http client is +accepted (this overwrites any previous client) and receives requests after any +middleware has been applied. + +## Semantic versioning + +This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: + +1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ +2. Changes that we do not expect to impact the vast majority of users in practice. + +We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. + +We are keen for your feedback; please open an [issue](https://www.github.com/sst/opencode-sdk-go/issues) with questions, bugs, or suggestions. + +## Contributing + +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/packages/sdk/go/SECURITY.md b/packages/sdk/go/SECURITY.md new file mode 100644 index 000000000..6912e12bc --- /dev/null +++ b/packages/sdk/go/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Reporting Security Issues + +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. + +To report a security issue, please contact the Stainless team at security@stainless.com. + +## Responsible Disclosure + +We appreciate the efforts of security researchers and individuals who help us maintain the security of +SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible +disclosure practices by allowing us a reasonable amount of time to investigate and address the issue +before making any information public. + +## Reporting Non-SDK Related Security Issues + +If you encounter security issues that are not directly related to SDKs but pertain to the services +or products provided by Opencode, please follow the respective company's security reporting guidelines. + +### Opencode Terms and Policies + +Please contact support@sst.dev for any questions or concerns regarding the security of our services. + +--- + +Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/packages/sdk/go/aliases.go b/packages/sdk/go/aliases.go new file mode 100644 index 000000000..6ab36d04c --- /dev/null +++ b/packages/sdk/go/aliases.go @@ -0,0 +1,43 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "github.com/sst/opencode-sdk-go/internal/apierror" + "github.com/sst/opencode-sdk-go/shared" +) + +type Error = apierror.Error + +// This is an alias to an internal type. +type MessageAbortedError = shared.MessageAbortedError + +// This is an alias to an internal type. +type MessageAbortedErrorName = shared.MessageAbortedErrorName + +// This is an alias to an internal value. +const MessageAbortedErrorNameMessageAbortedError = shared.MessageAbortedErrorNameMessageAbortedError + +// This is an alias to an internal type. +type ProviderAuthError = shared.ProviderAuthError + +// This is an alias to an internal type. +type ProviderAuthErrorData = shared.ProviderAuthErrorData + +// This is an alias to an internal type. +type ProviderAuthErrorName = shared.ProviderAuthErrorName + +// This is an alias to an internal value. +const ProviderAuthErrorNameProviderAuthError = shared.ProviderAuthErrorNameProviderAuthError + +// This is an alias to an internal type. +type UnknownError = shared.UnknownError + +// This is an alias to an internal type. +type UnknownErrorData = shared.UnknownErrorData + +// This is an alias to an internal type. +type UnknownErrorName = shared.UnknownErrorName + +// This is an alias to an internal value. +const UnknownErrorNameUnknownError = shared.UnknownErrorNameUnknownError diff --git a/packages/sdk/go/api.md b/packages/sdk/go/api.md new file mode 100644 index 000000000..fb3db9c53 --- /dev/null +++ b/packages/sdk/go/api.md @@ -0,0 +1,128 @@ +# Shared Response Types + +- shared.MessageAbortedError +- shared.ProviderAuthError +- shared.UnknownError + +# Event + +Response Types: + +- opencode.EventListResponse + +Methods: + +- client.Event.List(ctx context.Context) (opencode.EventListResponse, error) + +# App + +Response Types: + +- opencode.App +- opencode.Mode +- opencode.Model +- opencode.Provider +- opencode.AppProvidersResponse + +Methods: + +- client.App.Get(ctx context.Context) (opencode.App, error) +- client.App.Init(ctx context.Context) (bool, error) +- client.App.Log(ctx context.Context, body opencode.AppLogParams) (bool, error) +- client.App.Modes(ctx context.Context) ([]opencode.Mode, error) +- client.App.Providers(ctx context.Context) (opencode.AppProvidersResponse, error) + +# Find + +Response Types: + +- opencode.Symbol +- opencode.FindTextResponse + +Methods: + +- client.Find.Files(ctx context.Context, query opencode.FindFilesParams) ([]string, error) +- client.Find.Symbols(ctx context.Context, query opencode.FindSymbolsParams) ([]opencode.Symbol, error) +- client.Find.Text(ctx context.Context, query opencode.FindTextParams) ([]opencode.FindTextResponse, error) + +# File + +Response Types: + +- opencode.File +- opencode.FileReadResponse + +Methods: + +- client.File.Read(ctx context.Context, query opencode.FileReadParams) (opencode.FileReadResponse, error) +- client.File.Status(ctx context.Context) ([]opencode.File, error) + +# Config + +Response Types: + +- opencode.Config +- opencode.KeybindsConfig +- opencode.McpLocalConfig +- opencode.McpRemoteConfig +- opencode.ModeConfig + +Methods: + +- client.Config.Get(ctx context.Context) (opencode.Config, error) + +# Session + +Params Types: + +- opencode.FilePartInputParam +- opencode.FilePartSourceUnionParam +- opencode.FilePartSourceTextParam +- opencode.FileSourceParam +- opencode.SymbolSourceParam +- opencode.TextPartInputParam + +Response Types: + +- opencode.AssistantMessage +- opencode.FilePart +- opencode.FilePartSource +- opencode.FilePartSourceText +- opencode.FileSource +- opencode.Message +- opencode.Part +- opencode.Session +- opencode.SnapshotPart +- opencode.StepFinishPart +- opencode.StepStartPart +- opencode.SymbolSource +- opencode.TextPart +- opencode.ToolPart +- opencode.ToolStateCompleted +- opencode.ToolStateError +- opencode.ToolStatePending +- opencode.ToolStateRunning +- opencode.UserMessage +- opencode.SessionMessagesResponse + +Methods: + +- client.Session.New(ctx context.Context) (opencode.Session, error) +- client.Session.List(ctx context.Context) ([]opencode.Session, error) +- client.Session.Delete(ctx context.Context, id string) (bool, error) +- client.Session.Abort(ctx context.Context, id string) (bool, error) +- client.Session.Chat(ctx context.Context, id string, body opencode.SessionChatParams) (opencode.AssistantMessage, error) +- client.Session.Init(ctx context.Context, id string, body opencode.SessionInitParams) (bool, error) +- client.Session.Messages(ctx context.Context, id string) ([]opencode.SessionMessagesResponse, error) +- client.Session.Revert(ctx context.Context, id string, body opencode.SessionRevertParams) (opencode.Session, error) +- client.Session.Share(ctx context.Context, id string) (opencode.Session, error) +- client.Session.Summarize(ctx context.Context, id string, body opencode.SessionSummarizeParams) (bool, error) +- client.Session.Unrevert(ctx context.Context, id string) (opencode.Session, error) +- client.Session.Unshare(ctx context.Context, id string) (opencode.Session, error) + +# Tui + +Methods: + +- client.Tui.AppendPrompt(ctx context.Context, body opencode.TuiAppendPromptParams) (bool, error) +- client.Tui.OpenHelp(ctx context.Context) (bool, error) diff --git a/packages/sdk/go/app.go b/packages/sdk/go/app.go new file mode 100644 index 000000000..479011a23 --- /dev/null +++ b/packages/sdk/go/app.go @@ -0,0 +1,368 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "context" + "net/http" + + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/param" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" +) + +// AppService contains methods and other services that help with interacting with +// the opencode API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewAppService] method instead. +type AppService struct { + Options []option.RequestOption +} + +// NewAppService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewAppService(opts ...option.RequestOption) (r *AppService) { + r = &AppService{} + r.Options = opts + return +} + +// Get app info +func (r *AppService) Get(ctx context.Context, opts ...option.RequestOption) (res *App, err error) { + opts = append(r.Options[:], opts...) + path := "app" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +// Initialize the app +func (r *AppService) Init(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) { + opts = append(r.Options[:], opts...) + path := "app/init" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) + return +} + +// Write a log entry to the server logs +func (r *AppService) Log(ctx context.Context, body AppLogParams, opts ...option.RequestOption) (res *bool, err error) { + opts = append(r.Options[:], opts...) + path := "log" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// List all modes +func (r *AppService) Modes(ctx context.Context, opts ...option.RequestOption) (res *[]Mode, err error) { + opts = append(r.Options[:], opts...) + path := "mode" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +// List all providers +func (r *AppService) Providers(ctx context.Context, opts ...option.RequestOption) (res *AppProvidersResponse, err error) { + opts = append(r.Options[:], opts...) + path := "config/providers" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +type App struct { + Git bool `json:"git,required"` + Hostname string `json:"hostname,required"` + Path AppPath `json:"path,required"` + Time AppTime `json:"time,required"` + JSON appJSON `json:"-"` +} + +// appJSON contains the JSON metadata for the struct [App] +type appJSON struct { + Git apijson.Field + Hostname apijson.Field + Path apijson.Field + Time apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *App) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r appJSON) RawJSON() string { + return r.raw +} + +type AppPath struct { + Config string `json:"config,required"` + Cwd string `json:"cwd,required"` + Data string `json:"data,required"` + Root string `json:"root,required"` + State string `json:"state,required"` + JSON appPathJSON `json:"-"` +} + +// appPathJSON contains the JSON metadata for the struct [AppPath] +type appPathJSON struct { + Config apijson.Field + Cwd apijson.Field + Data apijson.Field + Root apijson.Field + State apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AppPath) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r appPathJSON) RawJSON() string { + return r.raw +} + +type AppTime struct { + Initialized float64 `json:"initialized"` + JSON appTimeJSON `json:"-"` +} + +// appTimeJSON contains the JSON metadata for the struct [AppTime] +type appTimeJSON struct { + Initialized apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AppTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r appTimeJSON) RawJSON() string { + return r.raw +} + +type Mode struct { + Name string `json:"name,required"` + Tools map[string]bool `json:"tools,required"` + Model ModeModel `json:"model"` + Prompt string `json:"prompt"` + Temperature float64 `json:"temperature"` + JSON modeJSON `json:"-"` +} + +// modeJSON contains the JSON metadata for the struct [Mode] +type modeJSON struct { + Name apijson.Field + Tools apijson.Field + Model apijson.Field + Prompt apijson.Field + Temperature apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Mode) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r modeJSON) RawJSON() string { + return r.raw +} + +type ModeModel struct { + ModelID string `json:"modelID,required"` + ProviderID string `json:"providerID,required"` + JSON modeModelJSON `json:"-"` +} + +// modeModelJSON contains the JSON metadata for the struct [ModeModel] +type modeModelJSON struct { + ModelID apijson.Field + ProviderID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ModeModel) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r modeModelJSON) RawJSON() string { + return r.raw +} + +type Model struct { + ID string `json:"id,required"` + Attachment bool `json:"attachment,required"` + Cost ModelCost `json:"cost,required"` + Limit ModelLimit `json:"limit,required"` + Name string `json:"name,required"` + Options map[string]interface{} `json:"options,required"` + Reasoning bool `json:"reasoning,required"` + ReleaseDate string `json:"release_date,required"` + Temperature bool `json:"temperature,required"` + ToolCall bool `json:"tool_call,required"` + JSON modelJSON `json:"-"` +} + +// modelJSON contains the JSON metadata for the struct [Model] +type modelJSON struct { + ID apijson.Field + Attachment apijson.Field + Cost apijson.Field + Limit apijson.Field + Name apijson.Field + Options apijson.Field + Reasoning apijson.Field + ReleaseDate apijson.Field + Temperature apijson.Field + ToolCall apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Model) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r modelJSON) RawJSON() string { + return r.raw +} + +type ModelCost struct { + Input float64 `json:"input,required"` + Output float64 `json:"output,required"` + CacheRead float64 `json:"cache_read"` + CacheWrite float64 `json:"cache_write"` + JSON modelCostJSON `json:"-"` +} + +// modelCostJSON contains the JSON metadata for the struct [ModelCost] +type modelCostJSON struct { + Input apijson.Field + Output apijson.Field + CacheRead apijson.Field + CacheWrite apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ModelCost) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r modelCostJSON) RawJSON() string { + return r.raw +} + +type ModelLimit struct { + Context float64 `json:"context,required"` + Output float64 `json:"output,required"` + JSON modelLimitJSON `json:"-"` +} + +// modelLimitJSON contains the JSON metadata for the struct [ModelLimit] +type modelLimitJSON struct { + Context apijson.Field + Output apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ModelLimit) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r modelLimitJSON) RawJSON() string { + return r.raw +} + +type Provider struct { + ID string `json:"id,required"` + Env []string `json:"env,required"` + Models map[string]Model `json:"models,required"` + Name string `json:"name,required"` + API string `json:"api"` + Npm string `json:"npm"` + JSON providerJSON `json:"-"` +} + +// providerJSON contains the JSON metadata for the struct [Provider] +type providerJSON struct { + ID apijson.Field + Env apijson.Field + Models apijson.Field + Name apijson.Field + API apijson.Field + Npm apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Provider) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r providerJSON) RawJSON() string { + return r.raw +} + +type AppProvidersResponse struct { + Default map[string]string `json:"default,required"` + Providers []Provider `json:"providers,required"` + JSON appProvidersResponseJSON `json:"-"` +} + +// appProvidersResponseJSON contains the JSON metadata for the struct +// [AppProvidersResponse] +type appProvidersResponseJSON struct { + Default apijson.Field + Providers apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AppProvidersResponse) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r appProvidersResponseJSON) RawJSON() string { + return r.raw +} + +type AppLogParams struct { + // Log level + Level param.Field[AppLogParamsLevel] `json:"level,required"` + // Log message + Message param.Field[string] `json:"message,required"` + // Service name for the log entry + Service param.Field[string] `json:"service,required"` + // Additional metadata for the log entry + Extra param.Field[map[string]interface{}] `json:"extra"` +} + +func (r AppLogParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +// Log level +type AppLogParamsLevel string + +const ( + AppLogParamsLevelDebug AppLogParamsLevel = "debug" + AppLogParamsLevelInfo AppLogParamsLevel = "info" + AppLogParamsLevelError AppLogParamsLevel = "error" + AppLogParamsLevelWarn AppLogParamsLevel = "warn" +) + +func (r AppLogParamsLevel) IsKnown() bool { + switch r { + case AppLogParamsLevelDebug, AppLogParamsLevelInfo, AppLogParamsLevelError, AppLogParamsLevelWarn: + return true + } + return false +} diff --git a/packages/sdk/go/app_test.go b/packages/sdk/go/app_test.go new file mode 100644 index 000000000..16bb8ff88 --- /dev/null +++ b/packages/sdk/go/app_test.go @@ -0,0 +1,131 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" +) + +func TestAppGet(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.App.Get(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestAppInit(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.App.Init(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestAppLogWithOptionalParams(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.App.Log(context.TODO(), opencode.AppLogParams{ + Level: opencode.F(opencode.AppLogParamsLevelDebug), + Message: opencode.F("message"), + Service: opencode.F("service"), + Extra: opencode.F(map[string]interface{}{ + "foo": "bar", + }), + }) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestAppModes(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.App.Modes(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestAppProviders(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.App.Providers(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/packages/sdk/go/client.go b/packages/sdk/go/client.go new file mode 100644 index 000000000..6baf21a8f --- /dev/null +++ b/packages/sdk/go/client.go @@ -0,0 +1,125 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "context" + "net/http" + "os" + + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" +) + +// Client creates a struct with services and top level methods that help with +// interacting with the opencode API. You should not instantiate this client +// directly, and instead use the [NewClient] method instead. +type Client struct { + Options []option.RequestOption + Event *EventService + App *AppService + Find *FindService + File *FileService + Config *ConfigService + Session *SessionService + Tui *TuiService +} + +// DefaultClientOptions read from the environment (OPENCODE_BASE_URL). This should +// be used to initialize new clients. +func DefaultClientOptions() []option.RequestOption { + defaults := []option.RequestOption{option.WithEnvironmentProduction()} + if o, ok := os.LookupEnv("OPENCODE_BASE_URL"); ok { + defaults = append(defaults, option.WithBaseURL(o)) + } + return defaults +} + +// NewClient generates a new client with the default option read from the +// environment (OPENCODE_BASE_URL). The option passed in as arguments are applied +// after these default arguments, and all option will be passed down to the +// services and requests that this client makes. +func NewClient(opts ...option.RequestOption) (r *Client) { + opts = append(DefaultClientOptions(), opts...) + + r = &Client{Options: opts} + + r.Event = NewEventService(opts...) + r.App = NewAppService(opts...) + r.Find = NewFindService(opts...) + r.File = NewFileService(opts...) + r.Config = NewConfigService(opts...) + r.Session = NewSessionService(opts...) + r.Tui = NewTuiService(opts...) + + return +} + +// Execute makes a request with the given context, method, URL, request params, +// response, and request options. This is useful for hitting undocumented endpoints +// while retaining the base URL, auth, retries, and other options from the client. +// +// If a byte slice or an [io.Reader] is supplied to params, it will be used as-is +// for the request body. +// +// The params is by default serialized into the body using [encoding/json]. If your +// type implements a MarshalJSON function, it will be used instead to serialize the +// request. If a URLQuery method is implemented, the returned [url.Values] will be +// used as query strings to the url. +// +// If your params struct uses [param.Field], you must provide either [MarshalJSON], +// [URLQuery], and/or [MarshalForm] functions. It is undefined behavior to use a +// struct uses [param.Field] without specifying how it is serialized. +// +// Any "…Params" object defined in this library can be used as the request +// argument. Note that 'path' arguments will not be forwarded into the url. +// +// The response body will be deserialized into the res variable, depending on its +// type: +// +// - A pointer to a [*http.Response] is populated by the raw response. +// - A pointer to a byte array will be populated with the contents of the request +// body. +// - A pointer to any other type uses this library's default JSON decoding, which +// respects UnmarshalJSON if it is defined on the type. +// - A nil value will not read the response body. +// +// For even greater flexibility, see [option.WithResponseInto] and +// [option.WithResponseBodyInto]. +func (r *Client) Execute(ctx context.Context, method string, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { + opts = append(r.Options, opts...) + return requestconfig.ExecuteNewRequest(ctx, method, path, params, res, opts...) +} + +// Get makes a GET request with the given URL, params, and optionally deserializes +// to a response. See [Execute] documentation on the params and response. +func (r *Client) Get(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { + return r.Execute(ctx, http.MethodGet, path, params, res, opts...) +} + +// Post makes a POST request with the given URL, params, and optionally +// deserializes to a response. See [Execute] documentation on the params and +// response. +func (r *Client) Post(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { + return r.Execute(ctx, http.MethodPost, path, params, res, opts...) +} + +// Put makes a PUT request with the given URL, params, and optionally deserializes +// to a response. See [Execute] documentation on the params and response. +func (r *Client) Put(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { + return r.Execute(ctx, http.MethodPut, path, params, res, opts...) +} + +// Patch makes a PATCH request with the given URL, params, and optionally +// deserializes to a response. See [Execute] documentation on the params and +// response. +func (r *Client) Patch(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { + return r.Execute(ctx, http.MethodPatch, path, params, res, opts...) +} + +// Delete makes a DELETE request with the given URL, params, and optionally +// deserializes to a response. See [Execute] documentation on the params and +// response. +func (r *Client) Delete(ctx context.Context, path string, params interface{}, res interface{}, opts ...option.RequestOption) error { + return r.Execute(ctx, http.MethodDelete, path, params, res, opts...) +} diff --git a/packages/sdk/go/client_test.go b/packages/sdk/go/client_test.go new file mode 100644 index 000000000..0f5b8205d --- /dev/null +++ b/packages/sdk/go/client_test.go @@ -0,0 +1,332 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode_test + +import ( + "context" + "fmt" + "io" + "net/http" + "reflect" + "testing" + "time" + + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal" + "github.com/sst/opencode-sdk-go/option" +) + +type closureTransport struct { + fn func(req *http.Request) (*http.Response, error) +} + +func (t *closureTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return t.fn(req) +} + +func TestUserAgentHeader(t *testing.T) { + var userAgent string + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + userAgent = req.Header.Get("User-Agent") + return &http.Response{ + StatusCode: http.StatusOK, + }, nil + }, + }, + }), + ) + client.Session.List(context.Background()) + if userAgent != fmt.Sprintf("Opencode/Go %s", internal.PackageVersion) { + t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent) + } +} + +func TestRetryAfter(t *testing.T) { + retryCountHeaders := make([]string, 0) + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count")) + return &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{ + http.CanonicalHeaderKey("Retry-After"): []string{"0.1"}, + }, + }, nil + }, + }, + }), + ) + _, err := client.Session.List(context.Background()) + if err == nil { + t.Error("Expected there to be a cancel error") + } + + attempts := len(retryCountHeaders) + if attempts != 3 { + t.Errorf("Expected %d attempts, got %d", 3, attempts) + } + + expectedRetryCountHeaders := []string{"0", "1", "2"} + if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) { + t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders) + } +} + +func TestDeleteRetryCountHeader(t *testing.T) { + retryCountHeaders := make([]string, 0) + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count")) + return &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{ + http.CanonicalHeaderKey("Retry-After"): []string{"0.1"}, + }, + }, nil + }, + }, + }), + option.WithHeaderDel("X-Stainless-Retry-Count"), + ) + _, err := client.Session.List(context.Background()) + if err == nil { + t.Error("Expected there to be a cancel error") + } + + expectedRetryCountHeaders := []string{"", "", ""} + if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) { + t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders) + } +} + +func TestOverwriteRetryCountHeader(t *testing.T) { + retryCountHeaders := make([]string, 0) + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count")) + return &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{ + http.CanonicalHeaderKey("Retry-After"): []string{"0.1"}, + }, + }, nil + }, + }, + }), + option.WithHeader("X-Stainless-Retry-Count", "42"), + ) + _, err := client.Session.List(context.Background()) + if err == nil { + t.Error("Expected there to be a cancel error") + } + + expectedRetryCountHeaders := []string{"42", "42", "42"} + if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) { + t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders) + } +} + +func TestRetryAfterMs(t *testing.T) { + attempts := 0 + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + attempts++ + return &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{ + http.CanonicalHeaderKey("Retry-After-Ms"): []string{"100"}, + }, + }, nil + }, + }, + }), + ) + _, err := client.Session.List(context.Background()) + if err == nil { + t.Error("Expected there to be a cancel error") + } + if want := 3; attempts != want { + t.Errorf("Expected %d attempts, got %d", want, attempts) + } +} + +func TestContextCancel(t *testing.T) { + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + <-req.Context().Done() + return nil, req.Context().Err() + }, + }, + }), + ) + cancelCtx, cancel := context.WithCancel(context.Background()) + cancel() + _, err := client.Session.List(cancelCtx) + if err == nil { + t.Error("Expected there to be a cancel error") + } +} + +func TestContextCancelDelay(t *testing.T) { + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + <-req.Context().Done() + return nil, req.Context().Err() + }, + }, + }), + ) + cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err := client.Session.List(cancelCtx) + if err == nil { + t.Error("expected there to be a cancel error") + } +} + +func TestContextDeadline(t *testing.T) { + testTimeout := time.After(3 * time.Second) + testDone := make(chan struct{}) + + deadline := time.Now().Add(100 * time.Millisecond) + deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline) + defer cancel() + + go func() { + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + <-req.Context().Done() + return nil, req.Context().Err() + }, + }, + }), + ) + _, err := client.Session.List(deadlineCtx) + if err == nil { + t.Error("expected there to be a deadline error") + } + close(testDone) + }() + + select { + case <-testTimeout: + t.Fatal("client didn't finish in time") + case <-testDone: + if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff { + t.Fatalf("client did not return within 30ms of context deadline, got %s", diff) + } + } +} + +func TestContextDeadlineStreaming(t *testing.T) { + testTimeout := time.After(3 * time.Second) + testDone := make(chan struct{}) + + deadline := time.Now().Add(100 * time.Millisecond) + deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline) + defer cancel() + + go func() { + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: 200, + Status: "200 OK", + Body: io.NopCloser( + io.Reader(readerFunc(func([]byte) (int, error) { + <-req.Context().Done() + return 0, req.Context().Err() + })), + ), + }, nil + }, + }, + }), + ) + stream := client.Event.ListStreaming(deadlineCtx) + for stream.Next() { + _ = stream.Current() + } + if stream.Err() == nil { + t.Error("expected there to be a deadline error") + } + close(testDone) + }() + + select { + case <-testTimeout: + t.Fatal("client didn't finish in time") + case <-testDone: + if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff { + t.Fatalf("client did not return within 30ms of context deadline, got %s", diff) + } + } +} + +func TestContextDeadlineStreamingWithRequestTimeout(t *testing.T) { + testTimeout := time.After(3 * time.Second) + testDone := make(chan struct{}) + deadline := time.Now().Add(100 * time.Millisecond) + + go func() { + client := opencode.NewClient( + option.WithHTTPClient(&http.Client{ + Transport: &closureTransport{ + fn: func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: 200, + Status: "200 OK", + Body: io.NopCloser( + io.Reader(readerFunc(func([]byte) (int, error) { + <-req.Context().Done() + return 0, req.Context().Err() + })), + ), + }, nil + }, + }, + }), + ) + stream := client.Event.ListStreaming(context.Background(), option.WithRequestTimeout((100 * time.Millisecond))) + for stream.Next() { + _ = stream.Current() + } + if stream.Err() == nil { + t.Error("expected there to be a deadline error") + } + close(testDone) + }() + + select { + case <-testTimeout: + t.Fatal("client didn't finish in time") + case <-testDone: + if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff { + t.Fatalf("client did not return within 30ms of context deadline, got %s", diff) + } + } +} + +type readerFunc func([]byte) (int, error) + +func (f readerFunc) Read(p []byte) (int, error) { return f(p) } +func (f readerFunc) Close() error { return nil } diff --git a/packages/sdk/go/config.go b/packages/sdk/go/config.go new file mode 100644 index 000000000..5fcfd1edc --- /dev/null +++ b/packages/sdk/go/config.go @@ -0,0 +1,887 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "context" + "net/http" + "reflect" + + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" + "github.com/tidwall/gjson" +) + +// ConfigService contains methods and other services that help with interacting +// with the opencode API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewConfigService] method instead. +type ConfigService struct { + Options []option.RequestOption +} + +// NewConfigService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewConfigService(opts ...option.RequestOption) (r *ConfigService) { + r = &ConfigService{} + r.Options = opts + return +} + +// Get config info +func (r *ConfigService) Get(ctx context.Context, opts ...option.RequestOption) (res *Config, err error) { + opts = append(r.Options[:], opts...) + path := "config" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +type Config struct { + // JSON schema reference for configuration validation + Schema string `json:"$schema"` + // Modes configuration, see https://opencode.ai/docs/modes + Agent ConfigAgent `json:"agent"` + // @deprecated Use 'share' field instead. Share newly created sessions + // automatically + Autoshare bool `json:"autoshare"` + // Automatically update to the latest version + Autoupdate bool `json:"autoupdate"` + // Disable providers that are loaded automatically + DisabledProviders []string `json:"disabled_providers"` + Experimental ConfigExperimental `json:"experimental"` + // Additional instruction files or patterns to include + Instructions []string `json:"instructions"` + // Custom keybind configurations + Keybinds KeybindsConfig `json:"keybinds"` + // @deprecated Always uses stretch layout. + Layout ConfigLayout `json:"layout"` + // MCP (Model Context Protocol) server configurations + Mcp map[string]ConfigMcp `json:"mcp"` + // Modes configuration, see https://opencode.ai/docs/modes + Mode ConfigMode `json:"mode"` + // Model to use in the format of provider/model, eg anthropic/claude-2 + Model string `json:"model"` + Permission ConfigPermission `json:"permission"` + // Custom provider configurations and model overrides + Provider map[string]ConfigProvider `json:"provider"` + // Control sharing behavior:'manual' allows manual sharing via commands, 'auto' + // enables automatic sharing, 'disabled' disables all sharing + Share ConfigShare `json:"share"` + // Small model to use for tasks like summarization and title generation in the + // format of provider/model + SmallModel string `json:"small_model"` + // Theme name to use for the interface + Theme string `json:"theme"` + // Custom username to display in conversations instead of system username + Username string `json:"username"` + JSON configJSON `json:"-"` +} + +// configJSON contains the JSON metadata for the struct [Config] +type configJSON struct { + Schema apijson.Field + Agent apijson.Field + Autoshare apijson.Field + Autoupdate apijson.Field + DisabledProviders apijson.Field + Experimental apijson.Field + Instructions apijson.Field + Keybinds apijson.Field + Layout apijson.Field + Mcp apijson.Field + Mode apijson.Field + Model apijson.Field + Permission apijson.Field + Provider apijson.Field + Share apijson.Field + SmallModel apijson.Field + Theme apijson.Field + Username apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Config) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configJSON) RawJSON() string { + return r.raw +} + +// Modes configuration, see https://opencode.ai/docs/modes +type ConfigAgent struct { + General ConfigAgentGeneral `json:"general"` + ExtraFields map[string]ConfigAgent `json:"-,extras"` + JSON configAgentJSON `json:"-"` +} + +// configAgentJSON contains the JSON metadata for the struct [ConfigAgent] +type configAgentJSON struct { + General apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigAgent) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configAgentJSON) RawJSON() string { + return r.raw +} + +type ConfigAgentGeneral struct { + Description string `json:"description,required"` + JSON configAgentGeneralJSON `json:"-"` + ModeConfig +} + +// configAgentGeneralJSON contains the JSON metadata for the struct +// [ConfigAgentGeneral] +type configAgentGeneralJSON struct { + Description apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigAgentGeneral) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configAgentGeneralJSON) RawJSON() string { + return r.raw +} + +type ConfigExperimental struct { + Hook ConfigExperimentalHook `json:"hook"` + JSON configExperimentalJSON `json:"-"` +} + +// configExperimentalJSON contains the JSON metadata for the struct +// [ConfigExperimental] +type configExperimentalJSON struct { + Hook apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigExperimental) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configExperimentalJSON) RawJSON() string { + return r.raw +} + +type ConfigExperimentalHook struct { + FileEdited map[string][]ConfigExperimentalHookFileEdited `json:"file_edited"` + SessionCompleted []ConfigExperimentalHookSessionCompleted `json:"session_completed"` + JSON configExperimentalHookJSON `json:"-"` +} + +// configExperimentalHookJSON contains the JSON metadata for the struct +// [ConfigExperimentalHook] +type configExperimentalHookJSON struct { + FileEdited apijson.Field + SessionCompleted apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigExperimentalHook) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configExperimentalHookJSON) RawJSON() string { + return r.raw +} + +type ConfigExperimentalHookFileEdited struct { + Command []string `json:"command,required"` + Environment map[string]string `json:"environment"` + JSON configExperimentalHookFileEditedJSON `json:"-"` +} + +// configExperimentalHookFileEditedJSON contains the JSON metadata for the struct +// [ConfigExperimentalHookFileEdited] +type configExperimentalHookFileEditedJSON struct { + Command apijson.Field + Environment apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigExperimentalHookFileEdited) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configExperimentalHookFileEditedJSON) RawJSON() string { + return r.raw +} + +type ConfigExperimentalHookSessionCompleted struct { + Command []string `json:"command,required"` + Environment map[string]string `json:"environment"` + JSON configExperimentalHookSessionCompletedJSON `json:"-"` +} + +// configExperimentalHookSessionCompletedJSON contains the JSON metadata for the +// struct [ConfigExperimentalHookSessionCompleted] +type configExperimentalHookSessionCompletedJSON struct { + Command apijson.Field + Environment apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigExperimentalHookSessionCompleted) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configExperimentalHookSessionCompletedJSON) RawJSON() string { + return r.raw +} + +// @deprecated Always uses stretch layout. +type ConfigLayout string + +const ( + ConfigLayoutAuto ConfigLayout = "auto" + ConfigLayoutStretch ConfigLayout = "stretch" +) + +func (r ConfigLayout) IsKnown() bool { + switch r { + case ConfigLayoutAuto, ConfigLayoutStretch: + return true + } + return false +} + +type ConfigMcp struct { + // Type of MCP server connection + Type ConfigMcpType `json:"type,required"` + // This field can have the runtime type of [[]string]. + Command interface{} `json:"command"` + // Enable or disable the MCP server on startup + Enabled bool `json:"enabled"` + // This field can have the runtime type of [map[string]string]. + Environment interface{} `json:"environment"` + // This field can have the runtime type of [map[string]string]. + Headers interface{} `json:"headers"` + // URL of the remote MCP server + URL string `json:"url"` + JSON configMcpJSON `json:"-"` + union ConfigMcpUnion +} + +// configMcpJSON contains the JSON metadata for the struct [ConfigMcp] +type configMcpJSON struct { + Type apijson.Field + Command apijson.Field + Enabled apijson.Field + Environment apijson.Field + Headers apijson.Field + URL apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r configMcpJSON) RawJSON() string { + return r.raw +} + +func (r *ConfigMcp) UnmarshalJSON(data []byte) (err error) { + *r = ConfigMcp{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [ConfigMcpUnion] interface which you can cast to the specific +// types for more type safety. +// +// Possible runtime types of the union are [McpLocalConfig], [McpRemoteConfig]. +func (r ConfigMcp) AsUnion() ConfigMcpUnion { + return r.union +} + +// Union satisfied by [McpLocalConfig] or [McpRemoteConfig]. +type ConfigMcpUnion interface { + implementsConfigMcp() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*ConfigMcpUnion)(nil)).Elem(), + "type", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(McpLocalConfig{}), + DiscriminatorValue: "local", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(McpRemoteConfig{}), + DiscriminatorValue: "remote", + }, + ) +} + +// Type of MCP server connection +type ConfigMcpType string + +const ( + ConfigMcpTypeLocal ConfigMcpType = "local" + ConfigMcpTypeRemote ConfigMcpType = "remote" +) + +func (r ConfigMcpType) IsKnown() bool { + switch r { + case ConfigMcpTypeLocal, ConfigMcpTypeRemote: + return true + } + return false +} + +// Modes configuration, see https://opencode.ai/docs/modes +type ConfigMode struct { + Build ModeConfig `json:"build"` + Plan ModeConfig `json:"plan"` + ExtraFields map[string]ModeConfig `json:"-,extras"` + JSON configModeJSON `json:"-"` +} + +// configModeJSON contains the JSON metadata for the struct [ConfigMode] +type configModeJSON struct { + Build apijson.Field + Plan apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigMode) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configModeJSON) RawJSON() string { + return r.raw +} + +type ConfigPermission struct { + Bash ConfigPermissionBashUnion `json:"bash"` + Edit ConfigPermissionEdit `json:"edit"` + JSON configPermissionJSON `json:"-"` +} + +// configPermissionJSON contains the JSON metadata for the struct +// [ConfigPermission] +type configPermissionJSON struct { + Bash apijson.Field + Edit apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigPermission) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configPermissionJSON) RawJSON() string { + return r.raw +} + +// Union satisfied by [ConfigPermissionBashString] or [ConfigPermissionBashMap]. +type ConfigPermissionBashUnion interface { + implementsConfigPermissionBashUnion() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*ConfigPermissionBashUnion)(nil)).Elem(), + "", + apijson.UnionVariant{ + TypeFilter: gjson.String, + Type: reflect.TypeOf(ConfigPermissionBashString("")), + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ConfigPermissionBashMap{}), + }, + ) +} + +type ConfigPermissionBashString string + +const ( + ConfigPermissionBashStringAsk ConfigPermissionBashString = "ask" + ConfigPermissionBashStringAllow ConfigPermissionBashString = "allow" +) + +func (r ConfigPermissionBashString) IsKnown() bool { + switch r { + case ConfigPermissionBashStringAsk, ConfigPermissionBashStringAllow: + return true + } + return false +} + +func (r ConfigPermissionBashString) implementsConfigPermissionBashUnion() {} + +type ConfigPermissionBashMap map[string]ConfigPermissionBashMapItem + +func (r ConfigPermissionBashMap) implementsConfigPermissionBashUnion() {} + +type ConfigPermissionBashMapItem string + +const ( + ConfigPermissionBashMapAsk ConfigPermissionBashMapItem = "ask" + ConfigPermissionBashMapAllow ConfigPermissionBashMapItem = "allow" +) + +func (r ConfigPermissionBashMapItem) IsKnown() bool { + switch r { + case ConfigPermissionBashMapAsk, ConfigPermissionBashMapAllow: + return true + } + return false +} + +type ConfigPermissionEdit string + +const ( + ConfigPermissionEditAsk ConfigPermissionEdit = "ask" + ConfigPermissionEditAllow ConfigPermissionEdit = "allow" +) + +func (r ConfigPermissionEdit) IsKnown() bool { + switch r { + case ConfigPermissionEditAsk, ConfigPermissionEditAllow: + return true + } + return false +} + +type ConfigProvider struct { + Models map[string]ConfigProviderModel `json:"models,required"` + ID string `json:"id"` + API string `json:"api"` + Env []string `json:"env"` + Name string `json:"name"` + Npm string `json:"npm"` + Options ConfigProviderOptions `json:"options"` + JSON configProviderJSON `json:"-"` +} + +// configProviderJSON contains the JSON metadata for the struct [ConfigProvider] +type configProviderJSON struct { + Models apijson.Field + ID apijson.Field + API apijson.Field + Env apijson.Field + Name apijson.Field + Npm apijson.Field + Options apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProvider) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configProviderJSON) RawJSON() string { + return r.raw +} + +type ConfigProviderModel struct { + ID string `json:"id"` + Attachment bool `json:"attachment"` + Cost ConfigProviderModelsCost `json:"cost"` + Limit ConfigProviderModelsLimit `json:"limit"` + Name string `json:"name"` + Options map[string]interface{} `json:"options"` + Reasoning bool `json:"reasoning"` + ReleaseDate string `json:"release_date"` + Temperature bool `json:"temperature"` + ToolCall bool `json:"tool_call"` + JSON configProviderModelJSON `json:"-"` +} + +// configProviderModelJSON contains the JSON metadata for the struct +// [ConfigProviderModel] +type configProviderModelJSON struct { + ID apijson.Field + Attachment apijson.Field + Cost apijson.Field + Limit apijson.Field + Name apijson.Field + Options apijson.Field + Reasoning apijson.Field + ReleaseDate apijson.Field + Temperature apijson.Field + ToolCall apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProviderModel) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configProviderModelJSON) RawJSON() string { + return r.raw +} + +type ConfigProviderModelsCost struct { + Input float64 `json:"input,required"` + Output float64 `json:"output,required"` + CacheRead float64 `json:"cache_read"` + CacheWrite float64 `json:"cache_write"` + JSON configProviderModelsCostJSON `json:"-"` +} + +// configProviderModelsCostJSON contains the JSON metadata for the struct +// [ConfigProviderModelsCost] +type configProviderModelsCostJSON struct { + Input apijson.Field + Output apijson.Field + CacheRead apijson.Field + CacheWrite apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProviderModelsCost) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configProviderModelsCostJSON) RawJSON() string { + return r.raw +} + +type ConfigProviderModelsLimit struct { + Context float64 `json:"context,required"` + Output float64 `json:"output,required"` + JSON configProviderModelsLimitJSON `json:"-"` +} + +// configProviderModelsLimitJSON contains the JSON metadata for the struct +// [ConfigProviderModelsLimit] +type configProviderModelsLimitJSON struct { + Context apijson.Field + Output apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProviderModelsLimit) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configProviderModelsLimitJSON) RawJSON() string { + return r.raw +} + +type ConfigProviderOptions struct { + APIKey string `json:"apiKey"` + BaseURL string `json:"baseURL"` + ExtraFields map[string]interface{} `json:"-,extras"` + JSON configProviderOptionsJSON `json:"-"` +} + +// configProviderOptionsJSON contains the JSON metadata for the struct +// [ConfigProviderOptions] +type configProviderOptionsJSON struct { + APIKey apijson.Field + BaseURL apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ConfigProviderOptions) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r configProviderOptionsJSON) RawJSON() string { + return r.raw +} + +// Control sharing behavior:'manual' allows manual sharing via commands, 'auto' +// enables automatic sharing, 'disabled' disables all sharing +type ConfigShare string + +const ( + ConfigShareManual ConfigShare = "manual" + ConfigShareAuto ConfigShare = "auto" + ConfigShareDisabled ConfigShare = "disabled" +) + +func (r ConfigShare) IsKnown() bool { + switch r { + case ConfigShareManual, ConfigShareAuto, ConfigShareDisabled: + return true + } + return false +} + +type KeybindsConfig struct { + // Exit the application + AppExit string `json:"app_exit,required"` + // Show help dialog + AppHelp string `json:"app_help,required"` + // Open external editor + EditorOpen string `json:"editor_open,required"` + // Close file + FileClose string `json:"file_close,required"` + // Split/unified diff + FileDiffToggle string `json:"file_diff_toggle,required"` + // List files + FileList string `json:"file_list,required"` + // Search file + FileSearch string `json:"file_search,required"` + // Clear input field + InputClear string `json:"input_clear,required"` + // Insert newline in input + InputNewline string `json:"input_newline,required"` + // Paste from clipboard + InputPaste string `json:"input_paste,required"` + // Submit input + InputSubmit string `json:"input_submit,required"` + // Leader key for keybind combinations + Leader string `json:"leader,required"` + // Copy message + MessagesCopy string `json:"messages_copy,required"` + // Navigate to first message + MessagesFirst string `json:"messages_first,required"` + // Scroll messages down by half page + MessagesHalfPageDown string `json:"messages_half_page_down,required"` + // Scroll messages up by half page + MessagesHalfPageUp string `json:"messages_half_page_up,required"` + // Navigate to last message + MessagesLast string `json:"messages_last,required"` + // Toggle layout + MessagesLayoutToggle string `json:"messages_layout_toggle,required"` + // Navigate to next message + MessagesNext string `json:"messages_next,required"` + // Scroll messages down by one page + MessagesPageDown string `json:"messages_page_down,required"` + // Scroll messages up by one page + MessagesPageUp string `json:"messages_page_up,required"` + // Navigate to previous message + MessagesPrevious string `json:"messages_previous,required"` + // Redo message + MessagesRedo string `json:"messages_redo,required"` + // @deprecated use messages_undo. Revert message + MessagesRevert string `json:"messages_revert,required"` + // Undo message + MessagesUndo string `json:"messages_undo,required"` + // List available models + ModelList string `json:"model_list,required"` + // Create/update AGENTS.md + ProjectInit string `json:"project_init,required"` + // Compact the session + SessionCompact string `json:"session_compact,required"` + // Export session to editor + SessionExport string `json:"session_export,required"` + // Interrupt current session + SessionInterrupt string `json:"session_interrupt,required"` + // List all sessions + SessionList string `json:"session_list,required"` + // Create a new session + SessionNew string `json:"session_new,required"` + // Share current session + SessionShare string `json:"session_share,required"` + // Unshare current session + SessionUnshare string `json:"session_unshare,required"` + // Next mode + SwitchMode string `json:"switch_mode,required"` + // Previous Mode + SwitchModeReverse string `json:"switch_mode_reverse,required"` + // List available themes + ThemeList string `json:"theme_list,required"` + // Toggle tool details + ToolDetails string `json:"tool_details,required"` + JSON keybindsConfigJSON `json:"-"` +} + +// keybindsConfigJSON contains the JSON metadata for the struct [KeybindsConfig] +type keybindsConfigJSON struct { + AppExit apijson.Field + AppHelp apijson.Field + EditorOpen apijson.Field + FileClose apijson.Field + FileDiffToggle apijson.Field + FileList apijson.Field + FileSearch apijson.Field + InputClear apijson.Field + InputNewline apijson.Field + InputPaste apijson.Field + InputSubmit apijson.Field + Leader apijson.Field + MessagesCopy apijson.Field + MessagesFirst apijson.Field + MessagesHalfPageDown apijson.Field + MessagesHalfPageUp apijson.Field + MessagesLast apijson.Field + MessagesLayoutToggle apijson.Field + MessagesNext apijson.Field + MessagesPageDown apijson.Field + MessagesPageUp apijson.Field + MessagesPrevious apijson.Field + MessagesRedo apijson.Field + MessagesRevert apijson.Field + MessagesUndo apijson.Field + ModelList apijson.Field + ProjectInit apijson.Field + SessionCompact apijson.Field + SessionExport apijson.Field + SessionInterrupt apijson.Field + SessionList apijson.Field + SessionNew apijson.Field + SessionShare apijson.Field + SessionUnshare apijson.Field + SwitchMode apijson.Field + SwitchModeReverse apijson.Field + ThemeList apijson.Field + ToolDetails apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *KeybindsConfig) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r keybindsConfigJSON) RawJSON() string { + return r.raw +} + +type McpLocalConfig struct { + // Command and arguments to run the MCP server + Command []string `json:"command,required"` + // Type of MCP server connection + Type McpLocalConfigType `json:"type,required"` + // Enable or disable the MCP server on startup + Enabled bool `json:"enabled"` + // Environment variables to set when running the MCP server + Environment map[string]string `json:"environment"` + JSON mcpLocalConfigJSON `json:"-"` +} + +// mcpLocalConfigJSON contains the JSON metadata for the struct [McpLocalConfig] +type mcpLocalConfigJSON struct { + Command apijson.Field + Type apijson.Field + Enabled apijson.Field + Environment apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *McpLocalConfig) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r mcpLocalConfigJSON) RawJSON() string { + return r.raw +} + +func (r McpLocalConfig) implementsConfigMcp() {} + +// Type of MCP server connection +type McpLocalConfigType string + +const ( + McpLocalConfigTypeLocal McpLocalConfigType = "local" +) + +func (r McpLocalConfigType) IsKnown() bool { + switch r { + case McpLocalConfigTypeLocal: + return true + } + return false +} + +type McpRemoteConfig struct { + // Type of MCP server connection + Type McpRemoteConfigType `json:"type,required"` + // URL of the remote MCP server + URL string `json:"url,required"` + // Enable or disable the MCP server on startup + Enabled bool `json:"enabled"` + // Headers to send with the request + Headers map[string]string `json:"headers"` + JSON mcpRemoteConfigJSON `json:"-"` +} + +// mcpRemoteConfigJSON contains the JSON metadata for the struct [McpRemoteConfig] +type mcpRemoteConfigJSON struct { + Type apijson.Field + URL apijson.Field + Enabled apijson.Field + Headers apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *McpRemoteConfig) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r mcpRemoteConfigJSON) RawJSON() string { + return r.raw +} + +func (r McpRemoteConfig) implementsConfigMcp() {} + +// Type of MCP server connection +type McpRemoteConfigType string + +const ( + McpRemoteConfigTypeRemote McpRemoteConfigType = "remote" +) + +func (r McpRemoteConfigType) IsKnown() bool { + switch r { + case McpRemoteConfigTypeRemote: + return true + } + return false +} + +type ModeConfig struct { + Disable bool `json:"disable"` + Model string `json:"model"` + Prompt string `json:"prompt"` + Temperature float64 `json:"temperature"` + Tools map[string]bool `json:"tools"` + JSON modeConfigJSON `json:"-"` +} + +// modeConfigJSON contains the JSON metadata for the struct [ModeConfig] +type modeConfigJSON struct { + Disable apijson.Field + Model apijson.Field + Prompt apijson.Field + Temperature apijson.Field + Tools apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ModeConfig) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r modeConfigJSON) RawJSON() string { + return r.raw +} diff --git a/packages/sdk/go/config_test.go b/packages/sdk/go/config_test.go new file mode 100644 index 000000000..86e058a9a --- /dev/null +++ b/packages/sdk/go/config_test.go @@ -0,0 +1,36 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" +) + +func TestConfigGet(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Config.Get(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/packages/sdk/go/event.go b/packages/sdk/go/event.go new file mode 100644 index 000000000..5203ab23a --- /dev/null +++ b/packages/sdk/go/event.go @@ -0,0 +1,1373 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "context" + "net/http" + "reflect" + + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" + "github.com/sst/opencode-sdk-go/packages/ssestream" + "github.com/sst/opencode-sdk-go/shared" + "github.com/tidwall/gjson" +) + +// EventService contains methods and other services that help with interacting with +// the opencode API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewEventService] method instead. +type EventService struct { + Options []option.RequestOption +} + +// NewEventService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewEventService(opts ...option.RequestOption) (r *EventService) { + r = &EventService{} + r.Options = opts + return +} + +// Get events +func (r *EventService) ListStreaming(ctx context.Context, opts ...option.RequestOption) (stream *ssestream.Stream[EventListResponse]) { + var ( + raw *http.Response + err error + ) + opts = append(r.Options[:], opts...) + path := "event" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...) + return ssestream.NewStream[EventListResponse](ssestream.NewDecoder(raw), err) +} + +type EventListResponse struct { + // This field can have the runtime type of + // [EventListResponseEventInstallationUpdatedProperties], + // [EventListResponseEventLspClientDiagnosticsProperties], + // [EventListResponseEventMessageUpdatedProperties], + // [EventListResponseEventMessageRemovedProperties], + // [EventListResponseEventMessagePartUpdatedProperties], + // [EventListResponseEventMessagePartRemovedProperties], + // [EventListResponseEventStorageWriteProperties], + // [EventListResponseEventPermissionUpdatedProperties], + // [EventListResponseEventFileEditedProperties], + // [EventListResponseEventSessionUpdatedProperties], + // [EventListResponseEventSessionDeletedProperties], + // [EventListResponseEventSessionIdleProperties], + // [EventListResponseEventSessionErrorProperties], [interface{}], + // [EventListResponseEventFileWatcherUpdatedProperties], + // [EventListResponseEventIdeInstalledProperties]. + Properties interface{} `json:"properties,required"` + Type EventListResponseType `json:"type,required"` + JSON eventListResponseJSON `json:"-"` + union EventListResponseUnion +} + +// eventListResponseJSON contains the JSON metadata for the struct +// [EventListResponse] +type eventListResponseJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r eventListResponseJSON) RawJSON() string { + return r.raw +} + +func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) { + *r = EventListResponse{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [EventListResponseUnion] interface which you can cast to the +// specific types for more type safety. +// +// Possible runtime types of the union are +// [EventListResponseEventInstallationUpdated], +// [EventListResponseEventLspClientDiagnostics], +// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved], +// [EventListResponseEventMessagePartUpdated], +// [EventListResponseEventMessagePartRemoved], +// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated], +// [EventListResponseEventFileEdited], [EventListResponseEventSessionUpdated], +// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle], +// [EventListResponseEventSessionError], [EventListResponseEventServerConnected], +// [EventListResponseEventFileWatcherUpdated], +// [EventListResponseEventIdeInstalled]. +func (r EventListResponse) AsUnion() EventListResponseUnion { + return r.union +} + +// Union satisfied by [EventListResponseEventInstallationUpdated], +// [EventListResponseEventLspClientDiagnostics], +// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved], +// [EventListResponseEventMessagePartUpdated], +// [EventListResponseEventMessagePartRemoved], +// [EventListResponseEventStorageWrite], [EventListResponseEventPermissionUpdated], +// [EventListResponseEventFileEdited], [EventListResponseEventSessionUpdated], +// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionIdle], +// [EventListResponseEventSessionError], [EventListResponseEventServerConnected], +// [EventListResponseEventFileWatcherUpdated] or +// [EventListResponseEventIdeInstalled]. +type EventListResponseUnion interface { + implementsEventListResponse() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*EventListResponseUnion)(nil)).Elem(), + "type", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventInstallationUpdated{}), + DiscriminatorValue: "installation.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventLspClientDiagnostics{}), + DiscriminatorValue: "lsp.client.diagnostics", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventMessageUpdated{}), + DiscriminatorValue: "message.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventMessageRemoved{}), + DiscriminatorValue: "message.removed", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventMessagePartUpdated{}), + DiscriminatorValue: "message.part.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventMessagePartRemoved{}), + DiscriminatorValue: "message.part.removed", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventStorageWrite{}), + DiscriminatorValue: "storage.write", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}), + DiscriminatorValue: "permission.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventFileEdited{}), + DiscriminatorValue: "file.edited", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionUpdated{}), + DiscriminatorValue: "session.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionDeleted{}), + DiscriminatorValue: "session.deleted", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionIdle{}), + DiscriminatorValue: "session.idle", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionError{}), + DiscriminatorValue: "session.error", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventServerConnected{}), + DiscriminatorValue: "server.connected", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}), + DiscriminatorValue: "file.watcher.updated", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventIdeInstalled{}), + DiscriminatorValue: "ide.installed", + }, + ) +} + +type EventListResponseEventInstallationUpdated struct { + Properties EventListResponseEventInstallationUpdatedProperties `json:"properties,required"` + Type EventListResponseEventInstallationUpdatedType `json:"type,required"` + JSON eventListResponseEventInstallationUpdatedJSON `json:"-"` +} + +// eventListResponseEventInstallationUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventInstallationUpdated] +type eventListResponseEventInstallationUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventInstallationUpdated) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventInstallationUpdatedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventInstallationUpdated) implementsEventListResponse() {} + +type EventListResponseEventInstallationUpdatedProperties struct { + Version string `json:"version,required"` + JSON eventListResponseEventInstallationUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventInstallationUpdatedPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventInstallationUpdatedProperties] +type eventListResponseEventInstallationUpdatedPropertiesJSON struct { + Version apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventInstallationUpdatedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventInstallationUpdatedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventInstallationUpdatedType string + +const ( + EventListResponseEventInstallationUpdatedTypeInstallationUpdated EventListResponseEventInstallationUpdatedType = "installation.updated" +) + +func (r EventListResponseEventInstallationUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventInstallationUpdatedTypeInstallationUpdated: + return true + } + return false +} + +type EventListResponseEventLspClientDiagnostics struct { + Properties EventListResponseEventLspClientDiagnosticsProperties `json:"properties,required"` + Type EventListResponseEventLspClientDiagnosticsType `json:"type,required"` + JSON eventListResponseEventLspClientDiagnosticsJSON `json:"-"` +} + +// eventListResponseEventLspClientDiagnosticsJSON contains the JSON metadata for +// the struct [EventListResponseEventLspClientDiagnostics] +type eventListResponseEventLspClientDiagnosticsJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventLspClientDiagnostics) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventLspClientDiagnosticsJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventLspClientDiagnostics) implementsEventListResponse() {} + +type EventListResponseEventLspClientDiagnosticsProperties struct { + Path string `json:"path,required"` + ServerID string `json:"serverID,required"` + JSON eventListResponseEventLspClientDiagnosticsPropertiesJSON `json:"-"` +} + +// eventListResponseEventLspClientDiagnosticsPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventLspClientDiagnosticsProperties] +type eventListResponseEventLspClientDiagnosticsPropertiesJSON struct { + Path apijson.Field + ServerID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventLspClientDiagnosticsProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventLspClientDiagnosticsPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventLspClientDiagnosticsType string + +const ( + EventListResponseEventLspClientDiagnosticsTypeLspClientDiagnostics EventListResponseEventLspClientDiagnosticsType = "lsp.client.diagnostics" +) + +func (r EventListResponseEventLspClientDiagnosticsType) IsKnown() bool { + switch r { + case EventListResponseEventLspClientDiagnosticsTypeLspClientDiagnostics: + return true + } + return false +} + +type EventListResponseEventMessageUpdated struct { + Properties EventListResponseEventMessageUpdatedProperties `json:"properties,required"` + Type EventListResponseEventMessageUpdatedType `json:"type,required"` + JSON eventListResponseEventMessageUpdatedJSON `json:"-"` +} + +// eventListResponseEventMessageUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventMessageUpdated] +type eventListResponseEventMessageUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessageUpdated) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventMessageUpdatedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventMessageUpdated) implementsEventListResponse() {} + +type EventListResponseEventMessageUpdatedProperties struct { + Info Message `json:"info,required"` + JSON eventListResponseEventMessageUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventMessageUpdatedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventMessageUpdatedProperties] +type eventListResponseEventMessageUpdatedPropertiesJSON struct { + Info apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessageUpdatedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventMessageUpdatedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventMessageUpdatedType string + +const ( + EventListResponseEventMessageUpdatedTypeMessageUpdated EventListResponseEventMessageUpdatedType = "message.updated" +) + +func (r EventListResponseEventMessageUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventMessageUpdatedTypeMessageUpdated: + return true + } + return false +} + +type EventListResponseEventMessageRemoved struct { + Properties EventListResponseEventMessageRemovedProperties `json:"properties,required"` + Type EventListResponseEventMessageRemovedType `json:"type,required"` + JSON eventListResponseEventMessageRemovedJSON `json:"-"` +} + +// eventListResponseEventMessageRemovedJSON contains the JSON metadata for the +// struct [EventListResponseEventMessageRemoved] +type eventListResponseEventMessageRemovedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessageRemoved) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventMessageRemovedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventMessageRemoved) implementsEventListResponse() {} + +type EventListResponseEventMessageRemovedProperties struct { + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + JSON eventListResponseEventMessageRemovedPropertiesJSON `json:"-"` +} + +// eventListResponseEventMessageRemovedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventMessageRemovedProperties] +type eventListResponseEventMessageRemovedPropertiesJSON struct { + MessageID apijson.Field + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessageRemovedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventMessageRemovedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventMessageRemovedType string + +const ( + EventListResponseEventMessageRemovedTypeMessageRemoved EventListResponseEventMessageRemovedType = "message.removed" +) + +func (r EventListResponseEventMessageRemovedType) IsKnown() bool { + switch r { + case EventListResponseEventMessageRemovedTypeMessageRemoved: + return true + } + return false +} + +type EventListResponseEventMessagePartUpdated struct { + Properties EventListResponseEventMessagePartUpdatedProperties `json:"properties,required"` + Type EventListResponseEventMessagePartUpdatedType `json:"type,required"` + JSON eventListResponseEventMessagePartUpdatedJSON `json:"-"` +} + +// eventListResponseEventMessagePartUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventMessagePartUpdated] +type eventListResponseEventMessagePartUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessagePartUpdated) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventMessagePartUpdatedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventMessagePartUpdated) implementsEventListResponse() {} + +type EventListResponseEventMessagePartUpdatedProperties struct { + Part Part `json:"part,required"` + JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventMessagePartUpdatedPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventMessagePartUpdatedProperties] +type eventListResponseEventMessagePartUpdatedPropertiesJSON struct { + Part apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessagePartUpdatedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventMessagePartUpdatedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventMessagePartUpdatedType string + +const ( + EventListResponseEventMessagePartUpdatedTypeMessagePartUpdated EventListResponseEventMessagePartUpdatedType = "message.part.updated" +) + +func (r EventListResponseEventMessagePartUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventMessagePartUpdatedTypeMessagePartUpdated: + return true + } + return false +} + +type EventListResponseEventMessagePartRemoved struct { + Properties EventListResponseEventMessagePartRemovedProperties `json:"properties,required"` + Type EventListResponseEventMessagePartRemovedType `json:"type,required"` + JSON eventListResponseEventMessagePartRemovedJSON `json:"-"` +} + +// eventListResponseEventMessagePartRemovedJSON contains the JSON metadata for the +// struct [EventListResponseEventMessagePartRemoved] +type eventListResponseEventMessagePartRemovedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessagePartRemoved) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventMessagePartRemovedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventMessagePartRemoved) implementsEventListResponse() {} + +type EventListResponseEventMessagePartRemovedProperties struct { + MessageID string `json:"messageID,required"` + PartID string `json:"partID,required"` + SessionID string `json:"sessionID,required"` + JSON eventListResponseEventMessagePartRemovedPropertiesJSON `json:"-"` +} + +// eventListResponseEventMessagePartRemovedPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventMessagePartRemovedProperties] +type eventListResponseEventMessagePartRemovedPropertiesJSON struct { + MessageID apijson.Field + PartID apijson.Field + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventMessagePartRemovedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventMessagePartRemovedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventMessagePartRemovedType string + +const ( + EventListResponseEventMessagePartRemovedTypeMessagePartRemoved EventListResponseEventMessagePartRemovedType = "message.part.removed" +) + +func (r EventListResponseEventMessagePartRemovedType) IsKnown() bool { + switch r { + case EventListResponseEventMessagePartRemovedTypeMessagePartRemoved: + return true + } + return false +} + +type EventListResponseEventStorageWrite struct { + Properties EventListResponseEventStorageWriteProperties `json:"properties,required"` + Type EventListResponseEventStorageWriteType `json:"type,required"` + JSON eventListResponseEventStorageWriteJSON `json:"-"` +} + +// eventListResponseEventStorageWriteJSON contains the JSON metadata for the struct +// [EventListResponseEventStorageWrite] +type eventListResponseEventStorageWriteJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventStorageWrite) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventStorageWriteJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventStorageWrite) implementsEventListResponse() {} + +type EventListResponseEventStorageWriteProperties struct { + Key string `json:"key,required"` + Content interface{} `json:"content"` + JSON eventListResponseEventStorageWritePropertiesJSON `json:"-"` +} + +// eventListResponseEventStorageWritePropertiesJSON contains the JSON metadata for +// the struct [EventListResponseEventStorageWriteProperties] +type eventListResponseEventStorageWritePropertiesJSON struct { + Key apijson.Field + Content apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventStorageWriteProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventStorageWritePropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventStorageWriteType string + +const ( + EventListResponseEventStorageWriteTypeStorageWrite EventListResponseEventStorageWriteType = "storage.write" +) + +func (r EventListResponseEventStorageWriteType) IsKnown() bool { + switch r { + case EventListResponseEventStorageWriteTypeStorageWrite: + return true + } + return false +} + +type EventListResponseEventPermissionUpdated struct { + Properties EventListResponseEventPermissionUpdatedProperties `json:"properties,required"` + Type EventListResponseEventPermissionUpdatedType `json:"type,required"` + JSON eventListResponseEventPermissionUpdatedJSON `json:"-"` +} + +// eventListResponseEventPermissionUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventPermissionUpdated] +type eventListResponseEventPermissionUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventPermissionUpdated) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventPermissionUpdatedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {} + +type EventListResponseEventPermissionUpdatedProperties struct { + ID string `json:"id,required"` + Metadata map[string]interface{} `json:"metadata,required"` + SessionID string `json:"sessionID,required"` + Time EventListResponseEventPermissionUpdatedPropertiesTime `json:"time,required"` + Title string `json:"title,required"` + JSON eventListResponseEventPermissionUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventPermissionUpdatedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventPermissionUpdatedProperties] +type eventListResponseEventPermissionUpdatedPropertiesJSON struct { + ID apijson.Field + Metadata apijson.Field + SessionID apijson.Field + Time apijson.Field + Title apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventPermissionUpdatedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventPermissionUpdatedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventPermissionUpdatedPropertiesTime struct { + Created float64 `json:"created,required"` + JSON eventListResponseEventPermissionUpdatedPropertiesTimeJSON `json:"-"` +} + +// eventListResponseEventPermissionUpdatedPropertiesTimeJSON contains the JSON +// metadata for the struct [EventListResponseEventPermissionUpdatedPropertiesTime] +type eventListResponseEventPermissionUpdatedPropertiesTimeJSON struct { + Created apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventPermissionUpdatedPropertiesTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventPermissionUpdatedPropertiesTimeJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventPermissionUpdatedType string + +const ( + EventListResponseEventPermissionUpdatedTypePermissionUpdated EventListResponseEventPermissionUpdatedType = "permission.updated" +) + +func (r EventListResponseEventPermissionUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventPermissionUpdatedTypePermissionUpdated: + return true + } + return false +} + +type EventListResponseEventFileEdited struct { + Properties EventListResponseEventFileEditedProperties `json:"properties,required"` + Type EventListResponseEventFileEditedType `json:"type,required"` + JSON eventListResponseEventFileEditedJSON `json:"-"` +} + +// eventListResponseEventFileEditedJSON contains the JSON metadata for the struct +// [EventListResponseEventFileEdited] +type eventListResponseEventFileEditedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventFileEdited) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventFileEditedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventFileEdited) implementsEventListResponse() {} + +type EventListResponseEventFileEditedProperties struct { + File string `json:"file,required"` + JSON eventListResponseEventFileEditedPropertiesJSON `json:"-"` +} + +// eventListResponseEventFileEditedPropertiesJSON contains the JSON metadata for +// the struct [EventListResponseEventFileEditedProperties] +type eventListResponseEventFileEditedPropertiesJSON struct { + File apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventFileEditedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventFileEditedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventFileEditedType string + +const ( + EventListResponseEventFileEditedTypeFileEdited EventListResponseEventFileEditedType = "file.edited" +) + +func (r EventListResponseEventFileEditedType) IsKnown() bool { + switch r { + case EventListResponseEventFileEditedTypeFileEdited: + return true + } + return false +} + +type EventListResponseEventSessionUpdated struct { + Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"` + Type EventListResponseEventSessionUpdatedType `json:"type,required"` + JSON eventListResponseEventSessionUpdatedJSON `json:"-"` +} + +// eventListResponseEventSessionUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventSessionUpdated] +type eventListResponseEventSessionUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionUpdated) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionUpdatedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventSessionUpdated) implementsEventListResponse() {} + +type EventListResponseEventSessionUpdatedProperties struct { + Info Session `json:"info,required"` + JSON eventListResponseEventSessionUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventSessionUpdatedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventSessionUpdatedProperties] +type eventListResponseEventSessionUpdatedPropertiesJSON struct { + Info apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionUpdatedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionUpdatedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventSessionUpdatedType string + +const ( + EventListResponseEventSessionUpdatedTypeSessionUpdated EventListResponseEventSessionUpdatedType = "session.updated" +) + +func (r EventListResponseEventSessionUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventSessionUpdatedTypeSessionUpdated: + return true + } + return false +} + +type EventListResponseEventSessionDeleted struct { + Properties EventListResponseEventSessionDeletedProperties `json:"properties,required"` + Type EventListResponseEventSessionDeletedType `json:"type,required"` + JSON eventListResponseEventSessionDeletedJSON `json:"-"` +} + +// eventListResponseEventSessionDeletedJSON contains the JSON metadata for the +// struct [EventListResponseEventSessionDeleted] +type eventListResponseEventSessionDeletedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionDeleted) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionDeletedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventSessionDeleted) implementsEventListResponse() {} + +type EventListResponseEventSessionDeletedProperties struct { + Info Session `json:"info,required"` + JSON eventListResponseEventSessionDeletedPropertiesJSON `json:"-"` +} + +// eventListResponseEventSessionDeletedPropertiesJSON contains the JSON metadata +// for the struct [EventListResponseEventSessionDeletedProperties] +type eventListResponseEventSessionDeletedPropertiesJSON struct { + Info apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionDeletedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionDeletedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventSessionDeletedType string + +const ( + EventListResponseEventSessionDeletedTypeSessionDeleted EventListResponseEventSessionDeletedType = "session.deleted" +) + +func (r EventListResponseEventSessionDeletedType) IsKnown() bool { + switch r { + case EventListResponseEventSessionDeletedTypeSessionDeleted: + return true + } + return false +} + +type EventListResponseEventSessionIdle struct { + Properties EventListResponseEventSessionIdleProperties `json:"properties,required"` + Type EventListResponseEventSessionIdleType `json:"type,required"` + JSON eventListResponseEventSessionIdleJSON `json:"-"` +} + +// eventListResponseEventSessionIdleJSON contains the JSON metadata for the struct +// [EventListResponseEventSessionIdle] +type eventListResponseEventSessionIdleJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionIdle) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionIdleJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventSessionIdle) implementsEventListResponse() {} + +type EventListResponseEventSessionIdleProperties struct { + SessionID string `json:"sessionID,required"` + JSON eventListResponseEventSessionIdlePropertiesJSON `json:"-"` +} + +// eventListResponseEventSessionIdlePropertiesJSON contains the JSON metadata for +// the struct [EventListResponseEventSessionIdleProperties] +type eventListResponseEventSessionIdlePropertiesJSON struct { + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionIdleProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionIdlePropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventSessionIdleType string + +const ( + EventListResponseEventSessionIdleTypeSessionIdle EventListResponseEventSessionIdleType = "session.idle" +) + +func (r EventListResponseEventSessionIdleType) IsKnown() bool { + switch r { + case EventListResponseEventSessionIdleTypeSessionIdle: + return true + } + return false +} + +type EventListResponseEventSessionError struct { + Properties EventListResponseEventSessionErrorProperties `json:"properties,required"` + Type EventListResponseEventSessionErrorType `json:"type,required"` + JSON eventListResponseEventSessionErrorJSON `json:"-"` +} + +// eventListResponseEventSessionErrorJSON contains the JSON metadata for the struct +// [EventListResponseEventSessionError] +type eventListResponseEventSessionErrorJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionError) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionErrorJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventSessionError) implementsEventListResponse() {} + +type EventListResponseEventSessionErrorProperties struct { + Error EventListResponseEventSessionErrorPropertiesError `json:"error"` + SessionID string `json:"sessionID"` + JSON eventListResponseEventSessionErrorPropertiesJSON `json:"-"` +} + +// eventListResponseEventSessionErrorPropertiesJSON contains the JSON metadata for +// the struct [EventListResponseEventSessionErrorProperties] +type eventListResponseEventSessionErrorPropertiesJSON struct { + Error apijson.Field + SessionID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionErrorProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionErrorPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventSessionErrorPropertiesError struct { + // This field can have the runtime type of [shared.ProviderAuthErrorData], + // [shared.UnknownErrorData], [interface{}]. + Data interface{} `json:"data,required"` + Name EventListResponseEventSessionErrorPropertiesErrorName `json:"name,required"` + JSON eventListResponseEventSessionErrorPropertiesErrorJSON `json:"-"` + union EventListResponseEventSessionErrorPropertiesErrorUnion +} + +// eventListResponseEventSessionErrorPropertiesErrorJSON contains the JSON metadata +// for the struct [EventListResponseEventSessionErrorPropertiesError] +type eventListResponseEventSessionErrorPropertiesErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r eventListResponseEventSessionErrorPropertiesErrorJSON) RawJSON() string { + return r.raw +} + +func (r *EventListResponseEventSessionErrorPropertiesError) UnmarshalJSON(data []byte) (err error) { + *r = EventListResponseEventSessionErrorPropertiesError{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [EventListResponseEventSessionErrorPropertiesErrorUnion] +// interface which you can cast to the specific types for more type safety. +// +// Possible runtime types of the union are [shared.ProviderAuthError], +// [shared.UnknownError], +// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError], +// [shared.MessageAbortedError]. +func (r EventListResponseEventSessionErrorPropertiesError) AsUnion() EventListResponseEventSessionErrorPropertiesErrorUnion { + return r.union +} + +// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError], +// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError] or +// [shared.MessageAbortedError]. +type EventListResponseEventSessionErrorPropertiesErrorUnion interface { + ImplementsEventListResponseEventSessionErrorPropertiesError() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*EventListResponseEventSessionErrorPropertiesErrorUnion)(nil)).Elem(), + "name", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.ProviderAuthError{}), + DiscriminatorValue: "ProviderAuthError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.UnknownError{}), + DiscriminatorValue: "UnknownError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError{}), + DiscriminatorValue: "MessageOutputLengthError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.MessageAbortedError{}), + DiscriminatorValue: "MessageAbortedError", + }, + ) +} + +type EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError struct { + Data interface{} `json:"data,required"` + Name EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName `json:"name,required"` + JSON eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON `json:"-"` +} + +// eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON +// contains the JSON metadata for the struct +// [EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError] +type eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError) ImplementsEventListResponseEventSessionErrorPropertiesError() { +} + +type EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName string + +const ( + EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorNameMessageOutputLengthError EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName = "MessageOutputLengthError" +) + +func (r EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorName) IsKnown() bool { + switch r { + case EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthErrorNameMessageOutputLengthError: + return true + } + return false +} + +type EventListResponseEventSessionErrorPropertiesErrorName string + +const ( + EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError EventListResponseEventSessionErrorPropertiesErrorName = "ProviderAuthError" + EventListResponseEventSessionErrorPropertiesErrorNameUnknownError EventListResponseEventSessionErrorPropertiesErrorName = "UnknownError" + EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError EventListResponseEventSessionErrorPropertiesErrorName = "MessageOutputLengthError" + EventListResponseEventSessionErrorPropertiesErrorNameMessageAbortedError EventListResponseEventSessionErrorPropertiesErrorName = "MessageAbortedError" +) + +func (r EventListResponseEventSessionErrorPropertiesErrorName) IsKnown() bool { + switch r { + case EventListResponseEventSessionErrorPropertiesErrorNameProviderAuthError, EventListResponseEventSessionErrorPropertiesErrorNameUnknownError, EventListResponseEventSessionErrorPropertiesErrorNameMessageOutputLengthError, EventListResponseEventSessionErrorPropertiesErrorNameMessageAbortedError: + return true + } + return false +} + +type EventListResponseEventSessionErrorType string + +const ( + EventListResponseEventSessionErrorTypeSessionError EventListResponseEventSessionErrorType = "session.error" +) + +func (r EventListResponseEventSessionErrorType) IsKnown() bool { + switch r { + case EventListResponseEventSessionErrorTypeSessionError: + return true + } + return false +} + +type EventListResponseEventServerConnected struct { + Properties interface{} `json:"properties,required"` + Type EventListResponseEventServerConnectedType `json:"type,required"` + JSON eventListResponseEventServerConnectedJSON `json:"-"` +} + +// eventListResponseEventServerConnectedJSON contains the JSON metadata for the +// struct [EventListResponseEventServerConnected] +type eventListResponseEventServerConnectedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventServerConnected) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventServerConnectedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventServerConnected) implementsEventListResponse() {} + +type EventListResponseEventServerConnectedType string + +const ( + EventListResponseEventServerConnectedTypeServerConnected EventListResponseEventServerConnectedType = "server.connected" +) + +func (r EventListResponseEventServerConnectedType) IsKnown() bool { + switch r { + case EventListResponseEventServerConnectedTypeServerConnected: + return true + } + return false +} + +type EventListResponseEventFileWatcherUpdated struct { + Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"` + Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"` + JSON eventListResponseEventFileWatcherUpdatedJSON `json:"-"` +} + +// eventListResponseEventFileWatcherUpdatedJSON contains the JSON metadata for the +// struct [EventListResponseEventFileWatcherUpdated] +type eventListResponseEventFileWatcherUpdatedJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventFileWatcherUpdated) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventFileWatcherUpdatedJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventFileWatcherUpdated) implementsEventListResponse() {} + +type EventListResponseEventFileWatcherUpdatedProperties struct { + Event EventListResponseEventFileWatcherUpdatedPropertiesEvent `json:"event,required"` + File string `json:"file,required"` + JSON eventListResponseEventFileWatcherUpdatedPropertiesJSON `json:"-"` +} + +// eventListResponseEventFileWatcherUpdatedPropertiesJSON contains the JSON +// metadata for the struct [EventListResponseEventFileWatcherUpdatedProperties] +type eventListResponseEventFileWatcherUpdatedPropertiesJSON struct { + Event apijson.Field + File apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventFileWatcherUpdatedProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventFileWatcherUpdatedPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventFileWatcherUpdatedPropertiesEvent string + +const ( + EventListResponseEventFileWatcherUpdatedPropertiesEventRename EventListResponseEventFileWatcherUpdatedPropertiesEvent = "rename" + EventListResponseEventFileWatcherUpdatedPropertiesEventChange EventListResponseEventFileWatcherUpdatedPropertiesEvent = "change" +) + +func (r EventListResponseEventFileWatcherUpdatedPropertiesEvent) IsKnown() bool { + switch r { + case EventListResponseEventFileWatcherUpdatedPropertiesEventRename, EventListResponseEventFileWatcherUpdatedPropertiesEventChange: + return true + } + return false +} + +type EventListResponseEventFileWatcherUpdatedType string + +const ( + EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated EventListResponseEventFileWatcherUpdatedType = "file.watcher.updated" +) + +func (r EventListResponseEventFileWatcherUpdatedType) IsKnown() bool { + switch r { + case EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated: + return true + } + return false +} + +type EventListResponseEventIdeInstalled struct { + Properties EventListResponseEventIdeInstalledProperties `json:"properties,required"` + Type EventListResponseEventIdeInstalledType `json:"type,required"` + JSON eventListResponseEventIdeInstalledJSON `json:"-"` +} + +// eventListResponseEventIdeInstalledJSON contains the JSON metadata for the struct +// [EventListResponseEventIdeInstalled] +type eventListResponseEventIdeInstalledJSON struct { + Properties apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventIdeInstalled) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventIdeInstalledJSON) RawJSON() string { + return r.raw +} + +func (r EventListResponseEventIdeInstalled) implementsEventListResponse() {} + +type EventListResponseEventIdeInstalledProperties struct { + Ide string `json:"ide,required"` + JSON eventListResponseEventIdeInstalledPropertiesJSON `json:"-"` +} + +// eventListResponseEventIdeInstalledPropertiesJSON contains the JSON metadata for +// the struct [EventListResponseEventIdeInstalledProperties] +type eventListResponseEventIdeInstalledPropertiesJSON struct { + Ide apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *EventListResponseEventIdeInstalledProperties) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r eventListResponseEventIdeInstalledPropertiesJSON) RawJSON() string { + return r.raw +} + +type EventListResponseEventIdeInstalledType string + +const ( + EventListResponseEventIdeInstalledTypeIdeInstalled EventListResponseEventIdeInstalledType = "ide.installed" +) + +func (r EventListResponseEventIdeInstalledType) IsKnown() bool { + switch r { + case EventListResponseEventIdeInstalledTypeIdeInstalled: + return true + } + return false +} + +type EventListResponseType string + +const ( + EventListResponseTypeInstallationUpdated EventListResponseType = "installation.updated" + EventListResponseTypeLspClientDiagnostics EventListResponseType = "lsp.client.diagnostics" + EventListResponseTypeMessageUpdated EventListResponseType = "message.updated" + EventListResponseTypeMessageRemoved EventListResponseType = "message.removed" + EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated" + EventListResponseTypeMessagePartRemoved EventListResponseType = "message.part.removed" + EventListResponseTypeStorageWrite EventListResponseType = "storage.write" + EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated" + EventListResponseTypeFileEdited EventListResponseType = "file.edited" + EventListResponseTypeSessionUpdated EventListResponseType = "session.updated" + EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted" + EventListResponseTypeSessionIdle EventListResponseType = "session.idle" + EventListResponseTypeSessionError EventListResponseType = "session.error" + EventListResponseTypeServerConnected EventListResponseType = "server.connected" + EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated" + EventListResponseTypeIdeInstalled EventListResponseType = "ide.installed" +) + +func (r EventListResponseType) IsKnown() bool { + switch r { + case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeStorageWrite, EventListResponseTypePermissionUpdated, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled: + return true + } + return false +} diff --git a/packages/sdk/go/examples/.keep b/packages/sdk/go/examples/.keep new file mode 100644 index 000000000..d8c73e937 --- /dev/null +++ b/packages/sdk/go/examples/.keep @@ -0,0 +1,4 @@ +File generated from our OpenAPI spec by Stainless. + +This directory can be used to store example files demonstrating usage of this SDK. +It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/packages/sdk/go/field.go b/packages/sdk/go/field.go new file mode 100644 index 000000000..56d2f8903 --- /dev/null +++ b/packages/sdk/go/field.go @@ -0,0 +1,50 @@ +package opencode + +import ( + "github.com/sst/opencode-sdk-go/internal/param" + "io" +) + +// F is a param field helper used to initialize a [param.Field] generic struct. +// This helps specify null, zero values, and overrides, as well as normal values. +// You can read more about this in our [README]. +// +// [README]: https://pkg.go.dev/github.com/sst/opencode-sdk-go#readme-request-fields +func F[T any](value T) param.Field[T] { return param.Field[T]{Value: value, Present: true} } + +// Null is a param field helper which explicitly sends null to the API. +func Null[T any]() param.Field[T] { return param.Field[T]{Null: true, Present: true} } + +// Raw is a param field helper for specifying values for fields when the +// type you are looking to send is different from the type that is specified in +// the SDK. For example, if the type of the field is an integer, but you want +// to send a float, you could do that by setting the corresponding field with +// Raw[int](0.5). +func Raw[T any](value any) param.Field[T] { return param.Field[T]{Raw: value, Present: true} } + +// Int is a param field helper which helps specify integers. This is +// particularly helpful when specifying integer constants for fields. +func Int(value int64) param.Field[int64] { return F(value) } + +// String is a param field helper which helps specify strings. +func String(value string) param.Field[string] { return F(value) } + +// Float is a param field helper which helps specify floats. +func Float(value float64) param.Field[float64] { return F(value) } + +// Bool is a param field helper which helps specify bools. +func Bool(value bool) param.Field[bool] { return F(value) } + +// FileParam is a param field helper which helps files with a mime content-type. +func FileParam(reader io.Reader, filename string, contentType string) param.Field[io.Reader] { + return F[io.Reader](&file{reader, filename, contentType}) +} + +type file struct { + io.Reader + name string + contentType string +} + +func (f *file) ContentType() string { return f.contentType } +func (f *file) Filename() string { return f.name } diff --git a/packages/sdk/go/file.go b/packages/sdk/go/file.go new file mode 100644 index 000000000..0a8a4b2b6 --- /dev/null +++ b/packages/sdk/go/file.go @@ -0,0 +1,142 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "context" + "net/http" + "net/url" + + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/apiquery" + "github.com/sst/opencode-sdk-go/internal/param" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" +) + +// FileService contains methods and other services that help with interacting with +// the opencode API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewFileService] method instead. +type FileService struct { + Options []option.RequestOption +} + +// NewFileService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewFileService(opts ...option.RequestOption) (r *FileService) { + r = &FileService{} + r.Options = opts + return +} + +// Read a file +func (r *FileService) Read(ctx context.Context, query FileReadParams, opts ...option.RequestOption) (res *FileReadResponse, err error) { + opts = append(r.Options[:], opts...) + path := "file" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return +} + +// Get file status +func (r *FileService) Status(ctx context.Context, opts ...option.RequestOption) (res *[]File, err error) { + opts = append(r.Options[:], opts...) + path := "file/status" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +type File struct { + Added int64 `json:"added,required"` + Path string `json:"path,required"` + Removed int64 `json:"removed,required"` + Status FileStatus `json:"status,required"` + JSON fileJSON `json:"-"` +} + +// fileJSON contains the JSON metadata for the struct [File] +type fileJSON struct { + Added apijson.Field + Path apijson.Field + Removed apijson.Field + Status apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *File) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r fileJSON) RawJSON() string { + return r.raw +} + +type FileStatus string + +const ( + FileStatusAdded FileStatus = "added" + FileStatusDeleted FileStatus = "deleted" + FileStatusModified FileStatus = "modified" +) + +func (r FileStatus) IsKnown() bool { + switch r { + case FileStatusAdded, FileStatusDeleted, FileStatusModified: + return true + } + return false +} + +type FileReadResponse struct { + Content string `json:"content,required"` + Type FileReadResponseType `json:"type,required"` + JSON fileReadResponseJSON `json:"-"` +} + +// fileReadResponseJSON contains the JSON metadata for the struct +// [FileReadResponse] +type fileReadResponseJSON struct { + Content apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FileReadResponse) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r fileReadResponseJSON) RawJSON() string { + return r.raw +} + +type FileReadResponseType string + +const ( + FileReadResponseTypeRaw FileReadResponseType = "raw" + FileReadResponseTypePatch FileReadResponseType = "patch" +) + +func (r FileReadResponseType) IsKnown() bool { + switch r { + case FileReadResponseTypeRaw, FileReadResponseTypePatch: + return true + } + return false +} + +type FileReadParams struct { + Path param.Field[string] `query:"path,required"` +} + +// URLQuery serializes [FileReadParams]'s query parameters as `url.Values`. +func (r FileReadParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatComma, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/packages/sdk/go/file_test.go b/packages/sdk/go/file_test.go new file mode 100644 index 000000000..60212ea24 --- /dev/null +++ b/packages/sdk/go/file_test.go @@ -0,0 +1,60 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" +) + +func TestFileRead(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.File.Read(context.TODO(), opencode.FileReadParams{ + Path: opencode.F("path"), + }) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestFileStatus(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.File.Status(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/packages/sdk/go/find.go b/packages/sdk/go/find.go new file mode 100644 index 000000000..a993a353f --- /dev/null +++ b/packages/sdk/go/find.go @@ -0,0 +1,326 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "context" + "net/http" + "net/url" + + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/apiquery" + "github.com/sst/opencode-sdk-go/internal/param" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" +) + +// FindService contains methods and other services that help with interacting with +// the opencode API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewFindService] method instead. +type FindService struct { + Options []option.RequestOption +} + +// NewFindService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewFindService(opts ...option.RequestOption) (r *FindService) { + r = &FindService{} + r.Options = opts + return +} + +// Find files +func (r *FindService) Files(ctx context.Context, query FindFilesParams, opts ...option.RequestOption) (res *[]string, err error) { + opts = append(r.Options[:], opts...) + path := "find/file" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return +} + +// Find workspace symbols +func (r *FindService) Symbols(ctx context.Context, query FindSymbolsParams, opts ...option.RequestOption) (res *[]Symbol, err error) { + opts = append(r.Options[:], opts...) + path := "find/symbol" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return +} + +// Find text in files +func (r *FindService) Text(ctx context.Context, query FindTextParams, opts ...option.RequestOption) (res *[]FindTextResponse, err error) { + opts = append(r.Options[:], opts...) + path := "find" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return +} + +type Symbol struct { + Kind float64 `json:"kind,required"` + Location SymbolLocation `json:"location,required"` + Name string `json:"name,required"` + JSON symbolJSON `json:"-"` +} + +// symbolJSON contains the JSON metadata for the struct [Symbol] +type symbolJSON struct { + Kind apijson.Field + Location apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Symbol) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolJSON) RawJSON() string { + return r.raw +} + +type SymbolLocation struct { + Range SymbolLocationRange `json:"range,required"` + Uri string `json:"uri,required"` + JSON symbolLocationJSON `json:"-"` +} + +// symbolLocationJSON contains the JSON metadata for the struct [SymbolLocation] +type symbolLocationJSON struct { + Range apijson.Field + Uri apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolLocation) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolLocationJSON) RawJSON() string { + return r.raw +} + +type SymbolLocationRange struct { + End SymbolLocationRangeEnd `json:"end,required"` + Start SymbolLocationRangeStart `json:"start,required"` + JSON symbolLocationRangeJSON `json:"-"` +} + +// symbolLocationRangeJSON contains the JSON metadata for the struct +// [SymbolLocationRange] +type symbolLocationRangeJSON struct { + End apijson.Field + Start apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolLocationRange) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolLocationRangeJSON) RawJSON() string { + return r.raw +} + +type SymbolLocationRangeEnd struct { + Character float64 `json:"character,required"` + Line float64 `json:"line,required"` + JSON symbolLocationRangeEndJSON `json:"-"` +} + +// symbolLocationRangeEndJSON contains the JSON metadata for the struct +// [SymbolLocationRangeEnd] +type symbolLocationRangeEndJSON struct { + Character apijson.Field + Line apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolLocationRangeEnd) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolLocationRangeEndJSON) RawJSON() string { + return r.raw +} + +type SymbolLocationRangeStart struct { + Character float64 `json:"character,required"` + Line float64 `json:"line,required"` + JSON symbolLocationRangeStartJSON `json:"-"` +} + +// symbolLocationRangeStartJSON contains the JSON metadata for the struct +// [SymbolLocationRangeStart] +type symbolLocationRangeStartJSON struct { + Character apijson.Field + Line apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolLocationRangeStart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolLocationRangeStartJSON) RawJSON() string { + return r.raw +} + +type FindTextResponse struct { + AbsoluteOffset float64 `json:"absolute_offset,required"` + LineNumber float64 `json:"line_number,required"` + Lines FindTextResponseLines `json:"lines,required"` + Path FindTextResponsePath `json:"path,required"` + Submatches []FindTextResponseSubmatch `json:"submatches,required"` + JSON findTextResponseJSON `json:"-"` +} + +// findTextResponseJSON contains the JSON metadata for the struct +// [FindTextResponse] +type findTextResponseJSON struct { + AbsoluteOffset apijson.Field + LineNumber apijson.Field + Lines apijson.Field + Path apijson.Field + Submatches apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponse) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r findTextResponseJSON) RawJSON() string { + return r.raw +} + +type FindTextResponseLines struct { + Text string `json:"text,required"` + JSON findTextResponseLinesJSON `json:"-"` +} + +// findTextResponseLinesJSON contains the JSON metadata for the struct +// [FindTextResponseLines] +type findTextResponseLinesJSON struct { + Text apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponseLines) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r findTextResponseLinesJSON) RawJSON() string { + return r.raw +} + +type FindTextResponsePath struct { + Text string `json:"text,required"` + JSON findTextResponsePathJSON `json:"-"` +} + +// findTextResponsePathJSON contains the JSON metadata for the struct +// [FindTextResponsePath] +type findTextResponsePathJSON struct { + Text apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponsePath) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r findTextResponsePathJSON) RawJSON() string { + return r.raw +} + +type FindTextResponseSubmatch struct { + End float64 `json:"end,required"` + Match FindTextResponseSubmatchesMatch `json:"match,required"` + Start float64 `json:"start,required"` + JSON findTextResponseSubmatchJSON `json:"-"` +} + +// findTextResponseSubmatchJSON contains the JSON metadata for the struct +// [FindTextResponseSubmatch] +type findTextResponseSubmatchJSON struct { + End apijson.Field + Match apijson.Field + Start apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponseSubmatch) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r findTextResponseSubmatchJSON) RawJSON() string { + return r.raw +} + +type FindTextResponseSubmatchesMatch struct { + Text string `json:"text,required"` + JSON findTextResponseSubmatchesMatchJSON `json:"-"` +} + +// findTextResponseSubmatchesMatchJSON contains the JSON metadata for the struct +// [FindTextResponseSubmatchesMatch] +type findTextResponseSubmatchesMatchJSON struct { + Text apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FindTextResponseSubmatchesMatch) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r findTextResponseSubmatchesMatchJSON) RawJSON() string { + return r.raw +} + +type FindFilesParams struct { + Query param.Field[string] `query:"query,required"` +} + +// URLQuery serializes [FindFilesParams]'s query parameters as `url.Values`. +func (r FindFilesParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatComma, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + +type FindSymbolsParams struct { + Query param.Field[string] `query:"query,required"` +} + +// URLQuery serializes [FindSymbolsParams]'s query parameters as `url.Values`. +func (r FindSymbolsParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatComma, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + +type FindTextParams struct { + Pattern param.Field[string] `query:"pattern,required"` +} + +// URLQuery serializes [FindTextParams]'s query parameters as `url.Values`. +func (r FindTextParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatComma, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/packages/sdk/go/find_test.go b/packages/sdk/go/find_test.go new file mode 100644 index 000000000..e2f1caa16 --- /dev/null +++ b/packages/sdk/go/find_test.go @@ -0,0 +1,86 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" +) + +func TestFindFiles(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Find.Files(context.TODO(), opencode.FindFilesParams{ + Query: opencode.F("query"), + }) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestFindSymbols(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Find.Symbols(context.TODO(), opencode.FindSymbolsParams{ + Query: opencode.F("query"), + }) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestFindText(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Find.Text(context.TODO(), opencode.FindTextParams{ + Pattern: opencode.F("pattern"), + }) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/packages/sdk/go/go.mod b/packages/sdk/go/go.mod new file mode 100644 index 000000000..2817d3013 --- /dev/null +++ b/packages/sdk/go/go.mod @@ -0,0 +1,13 @@ +module github.com/sst/opencode-sdk-go + +go 1.21 + +require ( + github.com/tidwall/gjson v1.14.4 + github.com/tidwall/sjson v1.2.5 +) + +require ( + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect +) diff --git a/packages/sdk/go/go.sum b/packages/sdk/go/go.sum new file mode 100644 index 000000000..a70a5e0a8 --- /dev/null +++ b/packages/sdk/go/go.sum @@ -0,0 +1,10 @@ +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= diff --git a/packages/sdk/go/internal/apierror/apierror.go b/packages/sdk/go/internal/apierror/apierror.go new file mode 100644 index 000000000..24307fc36 --- /dev/null +++ b/packages/sdk/go/internal/apierror/apierror.go @@ -0,0 +1,53 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package apierror + +import ( + "fmt" + "net/http" + "net/http/httputil" + + "github.com/sst/opencode-sdk-go/internal/apijson" +) + +// Error represents an error that originates from the API, i.e. when a request is +// made and the API returns a response with a HTTP status code. Other errors are +// not wrapped by this SDK. +type Error struct { + JSON errorJSON `json:"-"` + StatusCode int + Request *http.Request + Response *http.Response +} + +// errorJSON contains the JSON metadata for the struct [Error] +type errorJSON struct { + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Error) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r errorJSON) RawJSON() string { + return r.raw +} + +func (r *Error) Error() string { + // Attempt to re-populate the response body + return fmt.Sprintf("%s \"%s\": %d %s %s", r.Request.Method, r.Request.URL, r.Response.StatusCode, http.StatusText(r.Response.StatusCode), r.JSON.RawJSON()) +} + +func (r *Error) DumpRequest(body bool) []byte { + if r.Request.GetBody != nil { + r.Request.Body, _ = r.Request.GetBody() + } + out, _ := httputil.DumpRequestOut(r.Request, body) + return out +} + +func (r *Error) DumpResponse(body bool) []byte { + out, _ := httputil.DumpResponse(r.Response, body) + return out +} diff --git a/packages/sdk/go/internal/apiform/encoder.go b/packages/sdk/go/internal/apiform/encoder.go new file mode 100644 index 000000000..243a1a123 --- /dev/null +++ b/packages/sdk/go/internal/apiform/encoder.go @@ -0,0 +1,383 @@ +package apiform + +import ( + "fmt" + "io" + "mime/multipart" + "net/textproto" + "path" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/sst/opencode-sdk-go/internal/param" +) + +var encoders sync.Map // map[encoderEntry]encoderFunc + +func Marshal(value interface{}, writer *multipart.Writer) error { + e := &encoder{dateFormat: time.RFC3339} + return e.marshal(value, writer) +} + +func MarshalRoot(value interface{}, writer *multipart.Writer) error { + e := &encoder{root: true, dateFormat: time.RFC3339} + return e.marshal(value, writer) +} + +type encoder struct { + dateFormat string + root bool +} + +type encoderFunc func(key string, value reflect.Value, writer *multipart.Writer) error + +type encoderField struct { + tag parsedStructTag + fn encoderFunc + idx []int +} + +type encoderEntry struct { + reflect.Type + dateFormat string + root bool +} + +func (e *encoder) marshal(value interface{}, writer *multipart.Writer) error { + val := reflect.ValueOf(value) + if !val.IsValid() { + return nil + } + typ := val.Type() + enc := e.typeEncoder(typ) + return enc("", val, writer) +} + +func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { + entry := encoderEntry{ + Type: t, + dateFormat: e.dateFormat, + root: e.root, + } + + if fi, ok := encoders.Load(entry); ok { + return fi.(encoderFunc) + } + + // To deal with recursive types, populate the map with an + // indirect func before we build it. This type waits on the + // real func (f) to be ready and then calls it. This indirect + // func is only used for recursive types. + var ( + wg sync.WaitGroup + f encoderFunc + ) + wg.Add(1) + fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value, writer *multipart.Writer) error { + wg.Wait() + return f(key, v, writer) + })) + if loaded { + return fi.(encoderFunc) + } + + // Compute the real encoder and replace the indirect func with it. + f = e.newTypeEncoder(t) + wg.Done() + encoders.Store(entry, f) + return f +} + +func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { + if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { + return e.newTimeTypeEncoder() + } + if t.ConvertibleTo(reflect.TypeOf((*io.Reader)(nil)).Elem()) { + return e.newReaderTypeEncoder() + } + e.root = false + switch t.Kind() { + case reflect.Pointer: + inner := t.Elem() + + innerEncoder := e.typeEncoder(inner) + return func(key string, v reflect.Value, writer *multipart.Writer) error { + if !v.IsValid() || v.IsNil() { + return nil + } + return innerEncoder(key, v.Elem(), writer) + } + case reflect.Struct: + return e.newStructTypeEncoder(t) + case reflect.Slice, reflect.Array: + return e.newArrayTypeEncoder(t) + case reflect.Map: + return e.newMapEncoder(t) + case reflect.Interface: + return e.newInterfaceEncoder() + default: + return e.newPrimitiveTypeEncoder(t) + } +} + +func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { + switch t.Kind() { + // Note that we could use `gjson` to encode these types but it would complicate our + // code more and this current code shouldn't cause any issues + case reflect.String: + return func(key string, v reflect.Value, writer *multipart.Writer) error { + return writer.WriteField(key, v.String()) + } + case reflect.Bool: + return func(key string, v reflect.Value, writer *multipart.Writer) error { + if v.Bool() { + return writer.WriteField(key, "true") + } + return writer.WriteField(key, "false") + } + case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: + return func(key string, v reflect.Value, writer *multipart.Writer) error { + return writer.WriteField(key, strconv.FormatInt(v.Int(), 10)) + } + case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return func(key string, v reflect.Value, writer *multipart.Writer) error { + return writer.WriteField(key, strconv.FormatUint(v.Uint(), 10)) + } + case reflect.Float32: + return func(key string, v reflect.Value, writer *multipart.Writer) error { + return writer.WriteField(key, strconv.FormatFloat(v.Float(), 'f', -1, 32)) + } + case reflect.Float64: + return func(key string, v reflect.Value, writer *multipart.Writer) error { + return writer.WriteField(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) + } + default: + return func(key string, v reflect.Value, writer *multipart.Writer) error { + return fmt.Errorf("unknown type received at primitive encoder: %s", t.String()) + } + } +} + +func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { + itemEncoder := e.typeEncoder(t.Elem()) + + return func(key string, v reflect.Value, writer *multipart.Writer) error { + if key != "" { + key = key + "." + } + for i := 0; i < v.Len(); i++ { + err := itemEncoder(key+strconv.Itoa(i), v.Index(i), writer) + if err != nil { + return err + } + } + return nil + } +} + +func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { + if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) { + return e.newFieldTypeEncoder(t) + } + + encoderFields := []encoderField{} + extraEncoder := (*encoderField)(nil) + + // This helper allows us to recursively collect field encoders into a flat + // array. The parameter `index` keeps track of the access patterns necessary + // to get to some field. + var collectEncoderFields func(r reflect.Type, index []int) + collectEncoderFields = func(r reflect.Type, index []int) { + for i := 0; i < r.NumField(); i++ { + idx := append(index, i) + field := t.FieldByIndex(idx) + if !field.IsExported() { + continue + } + // If this is an embedded struct, traverse one level deeper to extract + // the field and get their encoders as well. + if field.Anonymous { + collectEncoderFields(field.Type, idx) + continue + } + // If json tag is not present, then we skip, which is intentionally + // different behavior from the stdlib. + ptag, ok := parseFormStructTag(field) + if !ok { + continue + } + // We only want to support unexported field if they're tagged with + // `extras` because that field shouldn't be part of the public API. We + // also want to only keep the top level extras + if ptag.extras && len(index) == 0 { + extraEncoder = &encoderField{ptag, e.typeEncoder(field.Type.Elem()), idx} + continue + } + if ptag.name == "-" { + continue + } + + dateFormat, ok := parseFormatStructTag(field) + oldFormat := e.dateFormat + if ok { + switch dateFormat { + case "date-time": + e.dateFormat = time.RFC3339 + case "date": + e.dateFormat = "2006-01-02" + } + } + encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx}) + e.dateFormat = oldFormat + } + } + collectEncoderFields(t, []int{}) + + // Ensure deterministic output by sorting by lexicographic order + sort.Slice(encoderFields, func(i, j int) bool { + return encoderFields[i].tag.name < encoderFields[j].tag.name + }) + + return func(key string, value reflect.Value, writer *multipart.Writer) error { + if key != "" { + key = key + "." + } + + for _, ef := range encoderFields { + field := value.FieldByIndex(ef.idx) + err := ef.fn(key+ef.tag.name, field, writer) + if err != nil { + return err + } + } + + if extraEncoder != nil { + err := e.encodeMapEntries(key, value.FieldByIndex(extraEncoder.idx), writer) + if err != nil { + return err + } + } + + return nil + } +} + +func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc { + f, _ := t.FieldByName("Value") + enc := e.typeEncoder(f.Type) + + return func(key string, value reflect.Value, writer *multipart.Writer) error { + present := value.FieldByName("Present") + if !present.Bool() { + return nil + } + null := value.FieldByName("Null") + if null.Bool() { + return nil + } + raw := value.FieldByName("Raw") + if !raw.IsNil() { + return e.typeEncoder(raw.Type())(key, raw, writer) + } + return enc(key, value.FieldByName("Value"), writer) + } +} + +func (e *encoder) newTimeTypeEncoder() encoderFunc { + format := e.dateFormat + return func(key string, value reflect.Value, writer *multipart.Writer) error { + return writer.WriteField(key, value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format)) + } +} + +func (e encoder) newInterfaceEncoder() encoderFunc { + return func(key string, value reflect.Value, writer *multipart.Writer) error { + value = value.Elem() + if !value.IsValid() { + return nil + } + return e.typeEncoder(value.Type())(key, value, writer) + } +} + +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +} + +func (e *encoder) newReaderTypeEncoder() encoderFunc { + return func(key string, value reflect.Value, writer *multipart.Writer) error { + reader := value.Convert(reflect.TypeOf((*io.Reader)(nil)).Elem()).Interface().(io.Reader) + filename := "anonymous_file" + contentType := "application/octet-stream" + if named, ok := reader.(interface{ Filename() string }); ok { + filename = named.Filename() + } else if named, ok := reader.(interface{ Name() string }); ok { + filename = path.Base(named.Name()) + } + if typed, ok := reader.(interface{ ContentType() string }); ok { + contentType = typed.ContentType() + } + + // Below is taken almost 1-for-1 from [multipart.CreateFormFile] + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(key), escapeQuotes(filename))) + h.Set("Content-Type", contentType) + filewriter, err := writer.CreatePart(h) + if err != nil { + return err + } + _, err = io.Copy(filewriter, reader) + return err + } +} + +// Given a []byte of json (may either be an empty object or an object that already contains entries) +// encode all of the entries in the map to the json byte array. +func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipart.Writer) error { + type mapPair struct { + key string + value reflect.Value + } + + if key != "" { + key = key + "." + } + + pairs := []mapPair{} + + iter := v.MapRange() + for iter.Next() { + if iter.Key().Type().Kind() == reflect.String { + pairs = append(pairs, mapPair{key: iter.Key().String(), value: iter.Value()}) + } else { + return fmt.Errorf("cannot encode a map with a non string key") + } + } + + // Ensure deterministic output + sort.Slice(pairs, func(i, j int) bool { + return pairs[i].key < pairs[j].key + }) + + elementEncoder := e.typeEncoder(v.Type().Elem()) + for _, p := range pairs { + err := elementEncoder(key+string(p.key), p.value, writer) + if err != nil { + return err + } + } + + return nil +} + +func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { + return func(key string, value reflect.Value, writer *multipart.Writer) error { + return e.encodeMapEntries(key, value, writer) + } +} diff --git a/packages/sdk/go/internal/apiform/form.go b/packages/sdk/go/internal/apiform/form.go new file mode 100644 index 000000000..5445116e9 --- /dev/null +++ b/packages/sdk/go/internal/apiform/form.go @@ -0,0 +1,5 @@ +package apiform + +type Marshaler interface { + MarshalMultipart() ([]byte, string, error) +} diff --git a/packages/sdk/go/internal/apiform/form_test.go b/packages/sdk/go/internal/apiform/form_test.go new file mode 100644 index 000000000..39d1460c0 --- /dev/null +++ b/packages/sdk/go/internal/apiform/form_test.go @@ -0,0 +1,440 @@ +package apiform + +import ( + "bytes" + "mime/multipart" + "strings" + "testing" + "time" +) + +func P[T any](v T) *T { return &v } + +type Primitives struct { + A bool `form:"a"` + B int `form:"b"` + C uint `form:"c"` + D float64 `form:"d"` + E float32 `form:"e"` + F []int `form:"f"` +} + +type PrimitivePointers struct { + A *bool `form:"a"` + B *int `form:"b"` + C *uint `form:"c"` + D *float64 `form:"d"` + E *float32 `form:"e"` + F *[]int `form:"f"` +} + +type Slices struct { + Slice []Primitives `form:"slices"` +} + +type DateTime struct { + Date time.Time `form:"date" format:"date"` + DateTime time.Time `form:"date-time" format:"date-time"` +} + +type AdditionalProperties struct { + A bool `form:"a"` + Extras map[string]interface{} `form:"-,extras"` +} + +type TypedAdditionalProperties struct { + A bool `form:"a"` + Extras map[string]int `form:"-,extras"` +} + +type EmbeddedStructs struct { + AdditionalProperties + A *int `form:"number2"` + Extras map[string]interface{} `form:"-,extras"` +} + +type Recursive struct { + Name string `form:"name"` + Child *Recursive `form:"child"` +} + +type UnknownStruct struct { + Unknown interface{} `form:"unknown"` +} + +type UnionStruct struct { + Union Union `form:"union" format:"date"` +} + +type Union interface { + union() +} + +type UnionInteger int64 + +func (UnionInteger) union() {} + +type UnionStructA struct { + Type string `form:"type"` + A string `form:"a"` + B string `form:"b"` +} + +func (UnionStructA) union() {} + +type UnionStructB struct { + Type string `form:"type"` + A string `form:"a"` +} + +func (UnionStructB) union() {} + +type UnionTime time.Time + +func (UnionTime) union() {} + +type ReaderStruct struct { +} + +var tests = map[string]struct { + buf string + val interface{} +}{ + "map_string": { + `--xxx +Content-Disposition: form-data; name="foo" + +bar +--xxx-- +`, + map[string]string{"foo": "bar"}, + }, + + "map_interface": { + `--xxx +Content-Disposition: form-data; name="a" + +1 +--xxx +Content-Disposition: form-data; name="b" + +str +--xxx +Content-Disposition: form-data; name="c" + +false +--xxx-- +`, + map[string]interface{}{"a": float64(1), "b": "str", "c": false}, + }, + + "primitive_struct": { + `--xxx +Content-Disposition: form-data; name="a" + +false +--xxx +Content-Disposition: form-data; name="b" + +237628372683 +--xxx +Content-Disposition: form-data; name="c" + +654 +--xxx +Content-Disposition: form-data; name="d" + +9999.43 +--xxx +Content-Disposition: form-data; name="e" + +43.76 +--xxx +Content-Disposition: form-data; name="f.0" + +1 +--xxx +Content-Disposition: form-data; name="f.1" + +2 +--xxx +Content-Disposition: form-data; name="f.2" + +3 +--xxx +Content-Disposition: form-data; name="f.3" + +4 +--xxx-- +`, + Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + }, + + "slices": { + `--xxx +Content-Disposition: form-data; name="slices.0.a" + +false +--xxx +Content-Disposition: form-data; name="slices.0.b" + +237628372683 +--xxx +Content-Disposition: form-data; name="slices.0.c" + +654 +--xxx +Content-Disposition: form-data; name="slices.0.d" + +9999.43 +--xxx +Content-Disposition: form-data; name="slices.0.e" + +43.76 +--xxx +Content-Disposition: form-data; name="slices.0.f.0" + +1 +--xxx +Content-Disposition: form-data; name="slices.0.f.1" + +2 +--xxx +Content-Disposition: form-data; name="slices.0.f.2" + +3 +--xxx +Content-Disposition: form-data; name="slices.0.f.3" + +4 +--xxx-- +`, + Slices{ + Slice: []Primitives{{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}}, + }, + }, + + "primitive_pointer_struct": { + `--xxx +Content-Disposition: form-data; name="a" + +false +--xxx +Content-Disposition: form-data; name="b" + +237628372683 +--xxx +Content-Disposition: form-data; name="c" + +654 +--xxx +Content-Disposition: form-data; name="d" + +9999.43 +--xxx +Content-Disposition: form-data; name="e" + +43.76 +--xxx +Content-Disposition: form-data; name="f.0" + +1 +--xxx +Content-Disposition: form-data; name="f.1" + +2 +--xxx +Content-Disposition: form-data; name="f.2" + +3 +--xxx +Content-Disposition: form-data; name="f.3" + +4 +--xxx +Content-Disposition: form-data; name="f.4" + +5 +--xxx-- +`, + PrimitivePointers{ + A: P(false), + B: P(237628372683), + C: P(uint(654)), + D: P(9999.43), + E: P(float32(43.76)), + F: &[]int{1, 2, 3, 4, 5}, + }, + }, + + "datetime_struct": { + `--xxx +Content-Disposition: form-data; name="date" + +2006-01-02 +--xxx +Content-Disposition: form-data; name="date-time" + +2006-01-02T15:04:05Z +--xxx-- +`, + DateTime{ + Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC), + DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC), + }, + }, + + "additional_properties": { + `--xxx +Content-Disposition: form-data; name="a" + +true +--xxx +Content-Disposition: form-data; name="bar" + +value +--xxx +Content-Disposition: form-data; name="foo" + +true +--xxx-- +`, + AdditionalProperties{ + A: true, + Extras: map[string]interface{}{ + "bar": "value", + "foo": true, + }, + }, + }, + + "recursive_struct": { + `--xxx +Content-Disposition: form-data; name="child.name" + +Alex +--xxx +Content-Disposition: form-data; name="name" + +Robert +--xxx-- +`, + Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, + }, + + "unknown_struct_number": { + `--xxx +Content-Disposition: form-data; name="unknown" + +12 +--xxx-- +`, + UnknownStruct{ + Unknown: 12., + }, + }, + + "unknown_struct_map": { + `--xxx +Content-Disposition: form-data; name="unknown.foo" + +bar +--xxx-- +`, + UnknownStruct{ + Unknown: map[string]interface{}{ + "foo": "bar", + }, + }, + }, + + "union_integer": { + `--xxx +Content-Disposition: form-data; name="union" + +12 +--xxx-- +`, + UnionStruct{ + Union: UnionInteger(12), + }, + }, + + "union_struct_discriminated_a": { + `--xxx +Content-Disposition: form-data; name="union.a" + +foo +--xxx +Content-Disposition: form-data; name="union.b" + +bar +--xxx +Content-Disposition: form-data; name="union.type" + +typeA +--xxx-- +`, + + UnionStruct{ + Union: UnionStructA{ + Type: "typeA", + A: "foo", + B: "bar", + }, + }, + }, + + "union_struct_discriminated_b": { + `--xxx +Content-Disposition: form-data; name="union.a" + +foo +--xxx +Content-Disposition: form-data; name="union.type" + +typeB +--xxx-- +`, + UnionStruct{ + Union: UnionStructB{ + Type: "typeB", + A: "foo", + }, + }, + }, + + "union_struct_time": { + `--xxx +Content-Disposition: form-data; name="union" + +2010-05-23 +--xxx-- +`, + UnionStruct{ + Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), + }, + }, +} + +func TestEncode(t *testing.T) { + for name, test := range tests { + t.Run(name, func(t *testing.T) { + buf := bytes.NewBuffer(nil) + writer := multipart.NewWriter(buf) + writer.SetBoundary("xxx") + err := Marshal(test.val, writer) + if err != nil { + t.Errorf("serialization of %v failed with error %v", test.val, err) + } + err = writer.Close() + if err != nil { + t.Errorf("serialization of %v failed with error %v", test.val, err) + } + raw := buf.Bytes() + if string(raw) != strings.ReplaceAll(test.buf, "\n", "\r\n") { + t.Errorf("expected %+#v to serialize to '%s' but got '%s'", test.val, test.buf, string(raw)) + } + }) + } +} diff --git a/packages/sdk/go/internal/apiform/tag.go b/packages/sdk/go/internal/apiform/tag.go new file mode 100644 index 000000000..b22e054fc --- /dev/null +++ b/packages/sdk/go/internal/apiform/tag.go @@ -0,0 +1,48 @@ +package apiform + +import ( + "reflect" + "strings" +) + +const jsonStructTag = "json" +const formStructTag = "form" +const formatStructTag = "format" + +type parsedStructTag struct { + name string + required bool + extras bool + metadata bool +} + +func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { + raw, ok := field.Tag.Lookup(formStructTag) + if !ok { + raw, ok = field.Tag.Lookup(jsonStructTag) + } + if !ok { + return + } + parts := strings.Split(raw, ",") + if len(parts) == 0 { + return tag, false + } + tag.name = parts[0] + for _, part := range parts[1:] { + switch part { + case "required": + tag.required = true + case "extras": + tag.extras = true + case "metadata": + tag.metadata = true + } + } + return +} + +func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { + format, ok = field.Tag.Lookup(formatStructTag) + return +} diff --git a/packages/sdk/go/internal/apijson/decoder.go b/packages/sdk/go/internal/apijson/decoder.go new file mode 100644 index 000000000..68b7ed6be --- /dev/null +++ b/packages/sdk/go/internal/apijson/decoder.go @@ -0,0 +1,670 @@ +package apijson + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "sync" + "time" + "unsafe" + + "github.com/tidwall/gjson" +) + +// decoders is a synchronized map with roughly the following type: +// map[reflect.Type]decoderFunc +var decoders sync.Map + +// Unmarshal is similar to [encoding/json.Unmarshal] and parses the JSON-encoded +// data and stores it in the given pointer. +func Unmarshal(raw []byte, to any) error { + d := &decoderBuilder{dateFormat: time.RFC3339} + return d.unmarshal(raw, to) +} + +// UnmarshalRoot is like Unmarshal, but doesn't try to call MarshalJSON on the +// root element. Useful if a struct's UnmarshalJSON is overrode to use the +// behavior of this encoder versus the standard library. +func UnmarshalRoot(raw []byte, to any) error { + d := &decoderBuilder{dateFormat: time.RFC3339, root: true} + return d.unmarshal(raw, to) +} + +// decoderBuilder contains the 'compile-time' state of the decoder. +type decoderBuilder struct { + // Whether or not this is the first element and called by [UnmarshalRoot], see + // the documentation there to see why this is necessary. + root bool + // The dateFormat (a format string for [time.Format]) which is chosen by the + // last struct tag that was seen. + dateFormat string +} + +// decoderState contains the 'run-time' state of the decoder. +type decoderState struct { + strict bool + exactness exactness +} + +// Exactness refers to how close to the type the result was if deserialization +// was successful. This is useful in deserializing unions, where you want to try +// each entry, first with strict, then with looser validation, without actually +// having to do a lot of redundant work by marshalling twice (or maybe even more +// times). +type exactness int8 + +const ( + // Some values had to fudged a bit, for example by converting a string to an + // int, or an enum with extra values. + loose exactness = iota + // There are some extra arguments, but other wise it matches the union. + extras + // Exactly right. + exact +) + +type decoderFunc func(node gjson.Result, value reflect.Value, state *decoderState) error + +type decoderField struct { + tag parsedStructTag + fn decoderFunc + idx []int + goname string +} + +type decoderEntry struct { + reflect.Type + dateFormat string + root bool +} + +func (d *decoderBuilder) unmarshal(raw []byte, to any) error { + value := reflect.ValueOf(to).Elem() + result := gjson.ParseBytes(raw) + if !value.IsValid() { + return fmt.Errorf("apijson: cannot marshal into invalid value") + } + return d.typeDecoder(value.Type())(result, value, &decoderState{strict: false, exactness: exact}) +} + +func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc { + entry := decoderEntry{ + Type: t, + dateFormat: d.dateFormat, + root: d.root, + } + + if fi, ok := decoders.Load(entry); ok { + return fi.(decoderFunc) + } + + // To deal with recursive types, populate the map with an + // indirect func before we build it. This type waits on the + // real func (f) to be ready and then calls it. This indirect + // func is only used for recursive types. + var ( + wg sync.WaitGroup + f decoderFunc + ) + wg.Add(1) + fi, loaded := decoders.LoadOrStore(entry, decoderFunc(func(node gjson.Result, v reflect.Value, state *decoderState) error { + wg.Wait() + return f(node, v, state) + })) + if loaded { + return fi.(decoderFunc) + } + + // Compute the real decoder and replace the indirect func with it. + f = d.newTypeDecoder(t) + wg.Done() + decoders.Store(entry, f) + return f +} + +func indirectUnmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error { + return v.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw)) +} + +func unmarshalerDecoder(n gjson.Result, v reflect.Value, state *decoderState) error { + if v.Kind() == reflect.Pointer && v.CanSet() { + v.Set(reflect.New(v.Type().Elem())) + } + return v.Interface().(json.Unmarshaler).UnmarshalJSON([]byte(n.Raw)) +} + +func (d *decoderBuilder) newTypeDecoder(t reflect.Type) decoderFunc { + if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { + return d.newTimeTypeDecoder(t) + } + if !d.root && t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) { + return unmarshalerDecoder + } + if !d.root && reflect.PointerTo(t).Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) { + if _, ok := unionVariants[t]; !ok { + return indirectUnmarshalerDecoder + } + } + d.root = false + + if _, ok := unionRegistry[t]; ok { + return d.newUnionDecoder(t) + } + + switch t.Kind() { + case reflect.Pointer: + inner := t.Elem() + innerDecoder := d.typeDecoder(inner) + + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + if !v.IsValid() { + return fmt.Errorf("apijson: unexpected invalid reflection value %+#v", v) + } + + newValue := reflect.New(inner).Elem() + err := innerDecoder(n, newValue, state) + if err != nil { + return err + } + + v.Set(newValue.Addr()) + return nil + } + case reflect.Struct: + return d.newStructTypeDecoder(t) + case reflect.Array: + fallthrough + case reflect.Slice: + return d.newArrayTypeDecoder(t) + case reflect.Map: + return d.newMapDecoder(t) + case reflect.Interface: + return func(node gjson.Result, value reflect.Value, state *decoderState) error { + if !value.IsValid() { + return fmt.Errorf("apijson: unexpected invalid value %+#v", value) + } + if node.Value() != nil && value.CanSet() { + value.Set(reflect.ValueOf(node.Value())) + } + return nil + } + default: + return d.newPrimitiveTypeDecoder(t) + } +} + +// newUnionDecoder returns a decoderFunc that deserializes into a union using an +// algorithm roughly similar to Pydantic's [smart algorithm]. +// +// Conceptually this is equivalent to choosing the best schema based on how 'exact' +// the deserialization is for each of the schemas. +// +// If there is a tie in the level of exactness, then the tie is broken +// left-to-right. +// +// [smart algorithm]: https://docs.pydantic.dev/latest/concepts/unions/#smart-mode +func (d *decoderBuilder) newUnionDecoder(t reflect.Type) decoderFunc { + unionEntry, ok := unionRegistry[t] + if !ok { + panic("apijson: couldn't find union of type " + t.String() + " in union registry") + } + decoders := []decoderFunc{} + for _, variant := range unionEntry.variants { + decoder := d.typeDecoder(variant.Type) + decoders = append(decoders, decoder) + } + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + // If there is a discriminator match, circumvent the exactness logic entirely + for idx, variant := range unionEntry.variants { + decoder := decoders[idx] + if variant.TypeFilter != n.Type { + continue + } + + if len(unionEntry.discriminatorKey) != 0 { + discriminatorValue := n.Get(unionEntry.discriminatorKey).Value() + if discriminatorValue == variant.DiscriminatorValue { + inner := reflect.New(variant.Type).Elem() + err := decoder(n, inner, state) + v.Set(inner) + return err + } + } + } + + // Set bestExactness to worse than loose + bestExactness := loose - 1 + for idx, variant := range unionEntry.variants { + decoder := decoders[idx] + if variant.TypeFilter != n.Type { + continue + } + sub := decoderState{strict: state.strict, exactness: exact} + inner := reflect.New(variant.Type).Elem() + err := decoder(n, inner, &sub) + if err != nil { + continue + } + if sub.exactness == exact { + v.Set(inner) + return nil + } + if sub.exactness > bestExactness { + v.Set(inner) + bestExactness = sub.exactness + } + } + + if bestExactness < loose { + return errors.New("apijson: was not able to coerce type as union") + } + + if guardStrict(state, bestExactness != exact) { + return errors.New("apijson: was not able to coerce type as union strictly") + } + + return nil + } +} + +func (d *decoderBuilder) newMapDecoder(t reflect.Type) decoderFunc { + keyType := t.Key() + itemType := t.Elem() + itemDecoder := d.typeDecoder(itemType) + + return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) { + mapValue := reflect.MakeMapWithSize(t, len(node.Map())) + + node.ForEach(func(key, value gjson.Result) bool { + // It's fine for us to just use `ValueOf` here because the key types will + // always be primitive types so we don't need to decode it using the standard pattern + keyValue := reflect.ValueOf(key.Value()) + if !keyValue.IsValid() { + if err == nil { + err = fmt.Errorf("apijson: received invalid key type %v", keyValue.String()) + } + return false + } + if keyValue.Type() != keyType { + if err == nil { + err = fmt.Errorf("apijson: expected key type %v but got %v", keyType, keyValue.Type()) + } + return false + } + + itemValue := reflect.New(itemType).Elem() + itemerr := itemDecoder(value, itemValue, state) + if itemerr != nil { + if err == nil { + err = itemerr + } + return false + } + + mapValue.SetMapIndex(keyValue, itemValue) + return true + }) + + if err != nil { + return err + } + value.Set(mapValue) + return nil + } +} + +func (d *decoderBuilder) newArrayTypeDecoder(t reflect.Type) decoderFunc { + itemDecoder := d.typeDecoder(t.Elem()) + + return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) { + if !node.IsArray() { + return fmt.Errorf("apijson: could not deserialize to an array") + } + + arrayNode := node.Array() + + arrayValue := reflect.MakeSlice(reflect.SliceOf(t.Elem()), len(arrayNode), len(arrayNode)) + for i, itemNode := range arrayNode { + err = itemDecoder(itemNode, arrayValue.Index(i), state) + if err != nil { + return err + } + } + + value.Set(arrayValue) + return nil + } +} + +func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc { + // map of json field name to struct field decoders + decoderFields := map[string]decoderField{} + anonymousDecoders := []decoderField{} + extraDecoder := (*decoderField)(nil) + inlineDecoder := (*decoderField)(nil) + + for i := 0; i < t.NumField(); i++ { + idx := []int{i} + field := t.FieldByIndex(idx) + if !field.IsExported() { + continue + } + // If this is an embedded struct, traverse one level deeper to extract + // the fields and get their encoders as well. + if field.Anonymous { + anonymousDecoders = append(anonymousDecoders, decoderField{ + fn: d.typeDecoder(field.Type), + idx: idx[:], + }) + continue + } + // If json tag is not present, then we skip, which is intentionally + // different behavior from the stdlib. + ptag, ok := parseJSONStructTag(field) + if !ok { + continue + } + // We only want to support unexported fields if they're tagged with + // `extras` because that field shouldn't be part of the public API. + if ptag.extras { + extraDecoder = &decoderField{ptag, d.typeDecoder(field.Type.Elem()), idx, field.Name} + continue + } + if ptag.inline { + inlineDecoder = &decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name} + continue + } + if ptag.metadata { + continue + } + + oldFormat := d.dateFormat + dateFormat, ok := parseFormatStructTag(field) + if ok { + switch dateFormat { + case "date-time": + d.dateFormat = time.RFC3339 + case "date": + d.dateFormat = "2006-01-02" + } + } + decoderFields[ptag.name] = decoderField{ptag, d.typeDecoder(field.Type), idx, field.Name} + d.dateFormat = oldFormat + } + + return func(node gjson.Result, value reflect.Value, state *decoderState) (err error) { + if field := value.FieldByName("JSON"); field.IsValid() { + if raw := field.FieldByName("raw"); raw.IsValid() { + setUnexportedField(raw, node.Raw) + } + } + + for _, decoder := range anonymousDecoders { + // ignore errors + decoder.fn(node, value.FieldByIndex(decoder.idx), state) + } + + if inlineDecoder != nil { + var meta Field + dest := value.FieldByIndex(inlineDecoder.idx) + isValid := false + if dest.IsValid() && node.Type != gjson.Null { + err = inlineDecoder.fn(node, dest, state) + if err == nil { + isValid = true + } + } + + if node.Type == gjson.Null { + meta = Field{ + raw: node.Raw, + status: null, + } + } else if !isValid { + meta = Field{ + raw: node.Raw, + status: invalid, + } + } else if isValid { + meta = Field{ + raw: node.Raw, + status: valid, + } + } + if metadata := getSubField(value, inlineDecoder.idx, inlineDecoder.goname); metadata.IsValid() { + metadata.Set(reflect.ValueOf(meta)) + } + return err + } + + typedExtraType := reflect.Type(nil) + typedExtraFields := reflect.Value{} + if extraDecoder != nil { + typedExtraType = value.FieldByIndex(extraDecoder.idx).Type() + typedExtraFields = reflect.MakeMap(typedExtraType) + } + untypedExtraFields := map[string]Field{} + + for fieldName, itemNode := range node.Map() { + df, explicit := decoderFields[fieldName] + var ( + dest reflect.Value + fn decoderFunc + meta Field + ) + if explicit { + fn = df.fn + dest = value.FieldByIndex(df.idx) + } + if !explicit && extraDecoder != nil { + dest = reflect.New(typedExtraType.Elem()).Elem() + fn = extraDecoder.fn + } + + isValid := false + if dest.IsValid() && itemNode.Type != gjson.Null { + err = fn(itemNode, dest, state) + if err == nil { + isValid = true + } + } + + if itemNode.Type == gjson.Null { + meta = Field{ + raw: itemNode.Raw, + status: null, + } + } else if !isValid { + meta = Field{ + raw: itemNode.Raw, + status: invalid, + } + } else if isValid { + meta = Field{ + raw: itemNode.Raw, + status: valid, + } + } + + if explicit { + if metadata := getSubField(value, df.idx, df.goname); metadata.IsValid() { + metadata.Set(reflect.ValueOf(meta)) + } + } + if !explicit { + untypedExtraFields[fieldName] = meta + } + if !explicit && extraDecoder != nil { + typedExtraFields.SetMapIndex(reflect.ValueOf(fieldName), dest) + } + } + + if extraDecoder != nil && typedExtraFields.Len() > 0 { + value.FieldByIndex(extraDecoder.idx).Set(typedExtraFields) + } + + // Set exactness to 'extras' if there are untyped, extra fields. + if len(untypedExtraFields) > 0 && state.exactness > extras { + state.exactness = extras + } + + if metadata := getSubField(value, []int{-1}, "ExtraFields"); metadata.IsValid() && len(untypedExtraFields) > 0 { + metadata.Set(reflect.ValueOf(untypedExtraFields)) + } + return nil + } +} + +func (d *decoderBuilder) newPrimitiveTypeDecoder(t reflect.Type) decoderFunc { + switch t.Kind() { + case reflect.String: + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + v.SetString(n.String()) + if guardStrict(state, n.Type != gjson.String) { + return fmt.Errorf("apijson: failed to parse string strictly") + } + // Everything that is not an object can be loosely stringified. + if n.Type == gjson.JSON { + return fmt.Errorf("apijson: failed to parse string") + } + if guardUnknown(state, v) { + return fmt.Errorf("apijson: failed string enum validation") + } + return nil + } + case reflect.Bool: + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + v.SetBool(n.Bool()) + if guardStrict(state, n.Type != gjson.True && n.Type != gjson.False) { + return fmt.Errorf("apijson: failed to parse bool strictly") + } + // Numbers and strings that are either 'true' or 'false' can be loosely + // deserialized as bool. + if n.Type == gjson.String && (n.Raw != "true" && n.Raw != "false") || n.Type == gjson.JSON { + return fmt.Errorf("apijson: failed to parse bool") + } + if guardUnknown(state, v) { + return fmt.Errorf("apijson: failed bool enum validation") + } + return nil + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + v.SetInt(n.Int()) + if guardStrict(state, n.Type != gjson.Number || n.Num != float64(int(n.Num))) { + return fmt.Errorf("apijson: failed to parse int strictly") + } + // Numbers, booleans, and strings that maybe look like numbers can be + // loosely deserialized as numbers. + if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) { + return fmt.Errorf("apijson: failed to parse int") + } + if guardUnknown(state, v) { + return fmt.Errorf("apijson: failed int enum validation") + } + return nil + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + v.SetUint(n.Uint()) + if guardStrict(state, n.Type != gjson.Number || n.Num != float64(int(n.Num)) || n.Num < 0) { + return fmt.Errorf("apijson: failed to parse uint strictly") + } + // Numbers, booleans, and strings that maybe look like numbers can be + // loosely deserialized as uint. + if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) { + return fmt.Errorf("apijson: failed to parse uint") + } + if guardUnknown(state, v) { + return fmt.Errorf("apijson: failed uint enum validation") + } + return nil + } + case reflect.Float32, reflect.Float64: + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + v.SetFloat(n.Float()) + if guardStrict(state, n.Type != gjson.Number) { + return fmt.Errorf("apijson: failed to parse float strictly") + } + // Numbers, booleans, and strings that maybe look like numbers can be + // loosely deserialized as floats. + if n.Type == gjson.JSON || (n.Type == gjson.String && !canParseAsNumber(n.Str)) { + return fmt.Errorf("apijson: failed to parse float") + } + if guardUnknown(state, v) { + return fmt.Errorf("apijson: failed float enum validation") + } + return nil + } + default: + return func(node gjson.Result, v reflect.Value, state *decoderState) error { + return fmt.Errorf("unknown type received at primitive decoder: %s", t.String()) + } + } +} + +func (d *decoderBuilder) newTimeTypeDecoder(t reflect.Type) decoderFunc { + format := d.dateFormat + return func(n gjson.Result, v reflect.Value, state *decoderState) error { + parsed, err := time.Parse(format, n.Str) + if err == nil { + v.Set(reflect.ValueOf(parsed).Convert(t)) + return nil + } + + if guardStrict(state, true) { + return err + } + + layouts := []string{ + "2006-01-02", + "2006-01-02T15:04:05Z07:00", + "2006-01-02T15:04:05Z0700", + "2006-01-02T15:04:05", + "2006-01-02 15:04:05Z07:00", + "2006-01-02 15:04:05Z0700", + "2006-01-02 15:04:05", + } + + for _, layout := range layouts { + parsed, err := time.Parse(layout, n.Str) + if err == nil { + v.Set(reflect.ValueOf(parsed).Convert(t)) + return nil + } + } + + return fmt.Errorf("unable to leniently parse date-time string: %s", n.Str) + } +} + +func setUnexportedField(field reflect.Value, value interface{}) { + reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(reflect.ValueOf(value)) +} + +func guardStrict(state *decoderState, cond bool) bool { + if !cond { + return false + } + + if state.strict { + return true + } + + state.exactness = loose + return false +} + +func canParseAsNumber(str string) bool { + _, err := strconv.ParseFloat(str, 64) + return err == nil +} + +func guardUnknown(state *decoderState, v reflect.Value) bool { + if have, ok := v.Interface().(interface{ IsKnown() bool }); guardStrict(state, ok && !have.IsKnown()) { + return true + } + return false +} diff --git a/packages/sdk/go/internal/apijson/encoder.go b/packages/sdk/go/internal/apijson/encoder.go new file mode 100644 index 000000000..0e5f89e17 --- /dev/null +++ b/packages/sdk/go/internal/apijson/encoder.go @@ -0,0 +1,398 @@ +package apijson + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/tidwall/sjson" + + "github.com/sst/opencode-sdk-go/internal/param" +) + +var encoders sync.Map // map[encoderEntry]encoderFunc + +func Marshal(value interface{}) ([]byte, error) { + e := &encoder{dateFormat: time.RFC3339} + return e.marshal(value) +} + +func MarshalRoot(value interface{}) ([]byte, error) { + e := &encoder{root: true, dateFormat: time.RFC3339} + return e.marshal(value) +} + +type encoder struct { + dateFormat string + root bool +} + +type encoderFunc func(value reflect.Value) ([]byte, error) + +type encoderField struct { + tag parsedStructTag + fn encoderFunc + idx []int +} + +type encoderEntry struct { + reflect.Type + dateFormat string + root bool +} + +func (e *encoder) marshal(value interface{}) ([]byte, error) { + val := reflect.ValueOf(value) + if !val.IsValid() { + return nil, nil + } + typ := val.Type() + enc := e.typeEncoder(typ) + return enc(val) +} + +func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { + entry := encoderEntry{ + Type: t, + dateFormat: e.dateFormat, + root: e.root, + } + + if fi, ok := encoders.Load(entry); ok { + return fi.(encoderFunc) + } + + // To deal with recursive types, populate the map with an + // indirect func before we build it. This type waits on the + // real func (f) to be ready and then calls it. This indirect + // func is only used for recursive types. + var ( + wg sync.WaitGroup + f encoderFunc + ) + wg.Add(1) + fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(v reflect.Value) ([]byte, error) { + wg.Wait() + return f(v) + })) + if loaded { + return fi.(encoderFunc) + } + + // Compute the real encoder and replace the indirect func with it. + f = e.newTypeEncoder(t) + wg.Done() + encoders.Store(entry, f) + return f +} + +func marshalerEncoder(v reflect.Value) ([]byte, error) { + return v.Interface().(json.Marshaler).MarshalJSON() +} + +func indirectMarshalerEncoder(v reflect.Value) ([]byte, error) { + return v.Addr().Interface().(json.Marshaler).MarshalJSON() +} + +func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { + if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { + return e.newTimeTypeEncoder() + } + if !e.root && t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) { + return marshalerEncoder + } + if !e.root && reflect.PointerTo(t).Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) { + return indirectMarshalerEncoder + } + e.root = false + switch t.Kind() { + case reflect.Pointer: + inner := t.Elem() + + innerEncoder := e.typeEncoder(inner) + return func(v reflect.Value) ([]byte, error) { + if !v.IsValid() || v.IsNil() { + return nil, nil + } + return innerEncoder(v.Elem()) + } + case reflect.Struct: + return e.newStructTypeEncoder(t) + case reflect.Array: + fallthrough + case reflect.Slice: + return e.newArrayTypeEncoder(t) + case reflect.Map: + return e.newMapEncoder(t) + case reflect.Interface: + return e.newInterfaceEncoder() + default: + return e.newPrimitiveTypeEncoder(t) + } +} + +func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { + switch t.Kind() { + // Note that we could use `gjson` to encode these types but it would complicate our + // code more and this current code shouldn't cause any issues + case reflect.String: + return func(v reflect.Value) ([]byte, error) { + return json.Marshal(v.Interface()) + } + case reflect.Bool: + return func(v reflect.Value) ([]byte, error) { + if v.Bool() { + return []byte("true"), nil + } + return []byte("false"), nil + } + case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: + return func(v reflect.Value) ([]byte, error) { + return []byte(strconv.FormatInt(v.Int(), 10)), nil + } + case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return func(v reflect.Value) ([]byte, error) { + return []byte(strconv.FormatUint(v.Uint(), 10)), nil + } + case reflect.Float32: + return func(v reflect.Value) ([]byte, error) { + return []byte(strconv.FormatFloat(v.Float(), 'f', -1, 32)), nil + } + case reflect.Float64: + return func(v reflect.Value) ([]byte, error) { + return []byte(strconv.FormatFloat(v.Float(), 'f', -1, 64)), nil + } + default: + return func(v reflect.Value) ([]byte, error) { + return nil, fmt.Errorf("unknown type received at primitive encoder: %s", t.String()) + } + } +} + +func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { + itemEncoder := e.typeEncoder(t.Elem()) + + return func(value reflect.Value) ([]byte, error) { + json := []byte("[]") + for i := 0; i < value.Len(); i++ { + var value, err = itemEncoder(value.Index(i)) + if err != nil { + return nil, err + } + if value == nil { + // Assume that empty items should be inserted as `null` so that the output array + // will be the same length as the input array + value = []byte("null") + } + + json, err = sjson.SetRawBytes(json, "-1", value) + if err != nil { + return nil, err + } + } + + return json, nil + } +} + +func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { + if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) { + return e.newFieldTypeEncoder(t) + } + + encoderFields := []encoderField{} + extraEncoder := (*encoderField)(nil) + + // This helper allows us to recursively collect field encoders into a flat + // array. The parameter `index` keeps track of the access patterns necessary + // to get to some field. + var collectEncoderFields func(r reflect.Type, index []int) + collectEncoderFields = func(r reflect.Type, index []int) { + for i := 0; i < r.NumField(); i++ { + idx := append(index, i) + field := t.FieldByIndex(idx) + if !field.IsExported() { + continue + } + // If this is an embedded struct, traverse one level deeper to extract + // the field and get their encoders as well. + if field.Anonymous { + collectEncoderFields(field.Type, idx) + continue + } + // If json tag is not present, then we skip, which is intentionally + // different behavior from the stdlib. + ptag, ok := parseJSONStructTag(field) + if !ok { + continue + } + // We only want to support unexported field if they're tagged with + // `extras` because that field shouldn't be part of the public API. We + // also want to only keep the top level extras + if ptag.extras && len(index) == 0 { + extraEncoder = &encoderField{ptag, e.typeEncoder(field.Type.Elem()), idx} + continue + } + if ptag.name == "-" { + continue + } + + dateFormat, ok := parseFormatStructTag(field) + oldFormat := e.dateFormat + if ok { + switch dateFormat { + case "date-time": + e.dateFormat = time.RFC3339 + case "date": + e.dateFormat = "2006-01-02" + } + } + encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx}) + e.dateFormat = oldFormat + } + } + collectEncoderFields(t, []int{}) + + // Ensure deterministic output by sorting by lexicographic order + sort.Slice(encoderFields, func(i, j int) bool { + return encoderFields[i].tag.name < encoderFields[j].tag.name + }) + + return func(value reflect.Value) (json []byte, err error) { + json = []byte("{}") + + for _, ef := range encoderFields { + field := value.FieldByIndex(ef.idx) + encoded, err := ef.fn(field) + if err != nil { + return nil, err + } + if encoded == nil { + continue + } + json, err = sjson.SetRawBytes(json, ef.tag.name, encoded) + if err != nil { + return nil, err + } + } + + if extraEncoder != nil { + json, err = e.encodeMapEntries(json, value.FieldByIndex(extraEncoder.idx)) + if err != nil { + return nil, err + } + } + return + } +} + +func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc { + f, _ := t.FieldByName("Value") + enc := e.typeEncoder(f.Type) + + return func(value reflect.Value) (json []byte, err error) { + present := value.FieldByName("Present") + if !present.Bool() { + return nil, nil + } + null := value.FieldByName("Null") + if null.Bool() { + return []byte("null"), nil + } + raw := value.FieldByName("Raw") + if !raw.IsNil() { + return e.typeEncoder(raw.Type())(raw) + } + return enc(value.FieldByName("Value")) + } +} + +func (e *encoder) newTimeTypeEncoder() encoderFunc { + format := e.dateFormat + return func(value reflect.Value) (json []byte, err error) { + return []byte(`"` + value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format) + `"`), nil + } +} + +func (e encoder) newInterfaceEncoder() encoderFunc { + return func(value reflect.Value) ([]byte, error) { + value = value.Elem() + if !value.IsValid() { + return nil, nil + } + return e.typeEncoder(value.Type())(value) + } +} + +// Given a []byte of json (may either be an empty object or an object that already contains entries) +// encode all of the entries in the map to the json byte array. +func (e *encoder) encodeMapEntries(json []byte, v reflect.Value) ([]byte, error) { + type mapPair struct { + key []byte + value reflect.Value + } + + pairs := []mapPair{} + keyEncoder := e.typeEncoder(v.Type().Key()) + + iter := v.MapRange() + for iter.Next() { + var encodedKeyString string + if iter.Key().Type().Kind() == reflect.String { + encodedKeyString = iter.Key().String() + } else { + var err error + encodedKeyBytes, err := keyEncoder(iter.Key()) + if err != nil { + return nil, err + } + encodedKeyString = string(encodedKeyBytes) + } + encodedKey := []byte(sjsonReplacer.Replace(encodedKeyString)) + pairs = append(pairs, mapPair{key: encodedKey, value: iter.Value()}) + } + + // Ensure deterministic output + sort.Slice(pairs, func(i, j int) bool { + return bytes.Compare(pairs[i].key, pairs[j].key) < 0 + }) + + elementEncoder := e.typeEncoder(v.Type().Elem()) + for _, p := range pairs { + encodedValue, err := elementEncoder(p.value) + if err != nil { + return nil, err + } + if len(encodedValue) == 0 { + continue + } + json, err = sjson.SetRawBytes(json, string(p.key), encodedValue) + if err != nil { + return nil, err + } + } + + return json, nil +} + +func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { + return func(value reflect.Value) ([]byte, error) { + json := []byte("{}") + var err error + json, err = e.encodeMapEntries(json, value) + if err != nil { + return nil, err + } + return json, nil + } +} + +// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have +// special characters that sjson interprets as a path. +var sjsonReplacer *strings.Replacer = strings.NewReplacer(".", "\\.", ":", "\\:", "*", "\\*") diff --git a/packages/sdk/go/internal/apijson/field.go b/packages/sdk/go/internal/apijson/field.go new file mode 100644 index 000000000..3ef207c58 --- /dev/null +++ b/packages/sdk/go/internal/apijson/field.go @@ -0,0 +1,41 @@ +package apijson + +import "reflect" + +type status uint8 + +const ( + missing status = iota + null + invalid + valid +) + +type Field struct { + raw string + status status +} + +// Returns true if the field is explicitly `null` _or_ if it is not present at all (ie, missing). +// To check if the field's key is present in the JSON with an explicit null value, +// you must check `f.IsNull() && !f.IsMissing()`. +func (j Field) IsNull() bool { return j.status <= null } +func (j Field) IsMissing() bool { return j.status == missing } +func (j Field) IsInvalid() bool { return j.status == invalid } +func (j Field) Raw() string { return j.raw } + +func getSubField(root reflect.Value, index []int, name string) reflect.Value { + strct := root.FieldByIndex(index[:len(index)-1]) + if !strct.IsValid() { + panic("couldn't find encapsulating struct for field " + name) + } + meta := strct.FieldByName("JSON") + if !meta.IsValid() { + return reflect.Value{} + } + field := meta.FieldByName(name) + if !field.IsValid() { + return reflect.Value{} + } + return field +} diff --git a/packages/sdk/go/internal/apijson/field_test.go b/packages/sdk/go/internal/apijson/field_test.go new file mode 100644 index 000000000..2e170c76f --- /dev/null +++ b/packages/sdk/go/internal/apijson/field_test.go @@ -0,0 +1,66 @@ +package apijson + +import ( + "testing" + "time" + + "github.com/sst/opencode-sdk-go/internal/param" +) + +type Struct struct { + A string `json:"a"` + B int64 `json:"b"` +} + +type FieldStruct struct { + A param.Field[string] `json:"a"` + B param.Field[int64] `json:"b"` + C param.Field[Struct] `json:"c"` + D param.Field[time.Time] `json:"d" format:"date"` + E param.Field[time.Time] `json:"e" format:"date-time"` + F param.Field[int64] `json:"f"` +} + +func TestFieldMarshal(t *testing.T) { + tests := map[string]struct { + value interface{} + expected string + }{ + "null_string": {param.Field[string]{Present: true, Null: true}, "null"}, + "null_int": {param.Field[int]{Present: true, Null: true}, "null"}, + "null_int64": {param.Field[int64]{Present: true, Null: true}, "null"}, + "null_struct": {param.Field[Struct]{Present: true, Null: true}, "null"}, + + "string": {param.Field[string]{Present: true, Value: "string"}, `"string"`}, + "int": {param.Field[int]{Present: true, Value: 123}, "123"}, + "int64": {param.Field[int64]{Present: true, Value: int64(123456789123456789)}, "123456789123456789"}, + "struct": {param.Field[Struct]{Present: true, Value: Struct{A: "yo", B: 123}}, `{"a":"yo","b":123}`}, + + "string_raw": {param.Field[int]{Present: true, Raw: "string"}, `"string"`}, + "int_raw": {param.Field[int]{Present: true, Raw: 123}, "123"}, + "int64_raw": {param.Field[int]{Present: true, Raw: int64(123456789123456789)}, "123456789123456789"}, + "struct_raw": {param.Field[int]{Present: true, Raw: Struct{A: "yo", B: 123}}, `{"a":"yo","b":123}`}, + + "param_struct": { + FieldStruct{ + A: param.Field[string]{Present: true, Value: "hello"}, + B: param.Field[int64]{Present: true, Value: int64(12)}, + D: param.Field[time.Time]{Present: true, Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)}, + E: param.Field[time.Time]{Present: true, Value: time.Date(2023, time.March, 18, 14, 47, 38, 0, time.UTC)}, + }, + `{"a":"hello","b":12,"d":"2023-03-18","e":"2023-03-18T14:47:38Z"}`, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + b, err := Marshal(test.value) + if err != nil { + t.Fatalf("didn't expect error %v", err) + } + if string(b) != test.expected { + t.Fatalf("expected %s, received %s", test.expected, string(b)) + } + }) + } +} diff --git a/packages/sdk/go/internal/apijson/json_test.go b/packages/sdk/go/internal/apijson/json_test.go new file mode 100644 index 000000000..e6563448f --- /dev/null +++ b/packages/sdk/go/internal/apijson/json_test.go @@ -0,0 +1,617 @@ +package apijson + +import ( + "reflect" + "strings" + "testing" + "time" + + "github.com/tidwall/gjson" +) + +func P[T any](v T) *T { return &v } + +type Primitives struct { + A bool `json:"a"` + B int `json:"b"` + C uint `json:"c"` + D float64 `json:"d"` + E float32 `json:"e"` + F []int `json:"f"` +} + +type PrimitivePointers struct { + A *bool `json:"a"` + B *int `json:"b"` + C *uint `json:"c"` + D *float64 `json:"d"` + E *float32 `json:"e"` + F *[]int `json:"f"` +} + +type Slices struct { + Slice []Primitives `json:"slices"` +} + +type DateTime struct { + Date time.Time `json:"date" format:"date"` + DateTime time.Time `json:"date-time" format:"date-time"` +} + +type AdditionalProperties struct { + A bool `json:"a"` + ExtraFields map[string]interface{} `json:"-,extras"` +} + +type TypedAdditionalProperties struct { + A bool `json:"a"` + ExtraFields map[string]int `json:"-,extras"` +} + +type EmbeddedStruct struct { + A bool `json:"a"` + B string `json:"b"` + + JSON EmbeddedStructJSON +} + +type EmbeddedStructJSON struct { + A Field + B Field + ExtraFields map[string]Field + raw string +} + +type EmbeddedStructs struct { + EmbeddedStruct + A *int `json:"a"` + ExtraFields map[string]interface{} `json:"-,extras"` + + JSON EmbeddedStructsJSON +} + +type EmbeddedStructsJSON struct { + A Field + ExtraFields map[string]Field + raw string +} + +type Recursive struct { + Name string `json:"name"` + Child *Recursive `json:"child"` +} + +type JSONFieldStruct struct { + A bool `json:"a"` + B int64 `json:"b"` + C string `json:"c"` + D string `json:"d"` + ExtraFields map[string]int64 `json:"-,extras"` + JSON JSONFieldStructJSON `json:"-,metadata"` +} + +type JSONFieldStructJSON struct { + A Field + B Field + C Field + D Field + ExtraFields map[string]Field + raw string +} + +type UnknownStruct struct { + Unknown interface{} `json:"unknown"` +} + +type UnionStruct struct { + Union Union `json:"union" format:"date"` +} + +type Union interface { + union() +} + +type Inline struct { + InlineField Primitives `json:"-,inline"` + JSON InlineJSON `json:"-,metadata"` +} + +type InlineArray struct { + InlineField []string `json:"-,inline"` + JSON InlineJSON `json:"-,metadata"` +} + +type InlineJSON struct { + InlineField Field + raw string +} + +type UnionInteger int64 + +func (UnionInteger) union() {} + +type UnionStructA struct { + Type string `json:"type"` + A string `json:"a"` + B string `json:"b"` +} + +func (UnionStructA) union() {} + +type UnionStructB struct { + Type string `json:"type"` + A string `json:"a"` +} + +func (UnionStructB) union() {} + +type UnionTime time.Time + +func (UnionTime) union() {} + +func init() { + RegisterUnion(reflect.TypeOf((*Union)(nil)).Elem(), "type", + UnionVariant{ + TypeFilter: gjson.String, + Type: reflect.TypeOf(UnionTime{}), + }, + UnionVariant{ + TypeFilter: gjson.Number, + Type: reflect.TypeOf(UnionInteger(0)), + }, + UnionVariant{ + TypeFilter: gjson.JSON, + DiscriminatorValue: "typeA", + Type: reflect.TypeOf(UnionStructA{}), + }, + UnionVariant{ + TypeFilter: gjson.JSON, + DiscriminatorValue: "typeB", + Type: reflect.TypeOf(UnionStructB{}), + }, + ) +} + +type ComplexUnionStruct struct { + Union ComplexUnion `json:"union"` +} + +type ComplexUnion interface { + complexUnion() +} + +type ComplexUnionA struct { + Boo string `json:"boo"` + Foo bool `json:"foo"` +} + +func (ComplexUnionA) complexUnion() {} + +type ComplexUnionB struct { + Boo bool `json:"boo"` + Foo string `json:"foo"` +} + +func (ComplexUnionB) complexUnion() {} + +type ComplexUnionC struct { + Boo int64 `json:"boo"` +} + +func (ComplexUnionC) complexUnion() {} + +type ComplexUnionTypeA struct { + Baz int64 `json:"baz"` + Type TypeA `json:"type"` +} + +func (ComplexUnionTypeA) complexUnion() {} + +type TypeA string + +func (t TypeA) IsKnown() bool { + return t == "a" +} + +type ComplexUnionTypeB struct { + Baz int64 `json:"baz"` + Type TypeB `json:"type"` +} + +type TypeB string + +func (t TypeB) IsKnown() bool { + return t == "b" +} + +type UnmarshalStruct struct { + Foo string `json:"foo"` + prop bool `json:"-"` +} + +func (r *UnmarshalStruct) UnmarshalJSON(json []byte) error { + r.prop = true + return UnmarshalRoot(json, r) +} + +func (ComplexUnionTypeB) complexUnion() {} + +func init() { + RegisterUnion(reflect.TypeOf((*ComplexUnion)(nil)).Elem(), "", + UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ComplexUnionA{}), + }, + UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ComplexUnionB{}), + }, + UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ComplexUnionC{}), + }, + UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ComplexUnionTypeA{}), + }, + UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ComplexUnionTypeB{}), + }, + ) +} + +type MarshallingUnionStruct struct { + Union MarshallingUnion +} + +func (r *MarshallingUnionStruct) UnmarshalJSON(data []byte) (err error) { + *r = MarshallingUnionStruct{} + err = UnmarshalRoot(data, &r.Union) + return +} + +func (r MarshallingUnionStruct) MarshalJSON() (data []byte, err error) { + return MarshalRoot(r.Union) +} + +type MarshallingUnion interface { + marshallingUnion() +} + +type MarshallingUnionA struct { + Boo string `json:"boo"` +} + +func (MarshallingUnionA) marshallingUnion() {} + +func (r *MarshallingUnionA) UnmarshalJSON(data []byte) (err error) { + return UnmarshalRoot(data, r) +} + +type MarshallingUnionB struct { + Foo string `json:"foo"` +} + +func (MarshallingUnionB) marshallingUnion() {} + +func (r *MarshallingUnionB) UnmarshalJSON(data []byte) (err error) { + return UnmarshalRoot(data, r) +} + +func init() { + RegisterUnion( + reflect.TypeOf((*MarshallingUnion)(nil)).Elem(), + "", + UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(MarshallingUnionA{}), + }, + UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(MarshallingUnionB{}), + }, + ) +} + +var tests = map[string]struct { + buf string + val interface{} +}{ + "true": {"true", true}, + "false": {"false", false}, + "int": {"1", 1}, + "int_bigger": {"12324", 12324}, + "int_string_coerce": {`"65"`, 65}, + "int_boolean_coerce": {"true", 1}, + "int64": {"1", int64(1)}, + "int64_huge": {"123456789123456789", int64(123456789123456789)}, + "uint": {"1", uint(1)}, + "uint_bigger": {"12324", uint(12324)}, + "uint_coerce": {`"65"`, uint(65)}, + "float_1.54": {"1.54", float32(1.54)}, + "float_1.89": {"1.89", float64(1.89)}, + "string": {`"str"`, "str"}, + "string_int_coerce": {`12`, "12"}, + "array_string": {`["foo","bar"]`, []string{"foo", "bar"}}, + "array_int": {`[1,2]`, []int{1, 2}}, + "array_int_coerce": {`["1",2]`, []int{1, 2}}, + + "ptr_true": {"true", P(true)}, + "ptr_false": {"false", P(false)}, + "ptr_int": {"1", P(1)}, + "ptr_int_bigger": {"12324", P(12324)}, + "ptr_int_string_coerce": {`"65"`, P(65)}, + "ptr_int_boolean_coerce": {"true", P(1)}, + "ptr_int64": {"1", P(int64(1))}, + "ptr_int64_huge": {"123456789123456789", P(int64(123456789123456789))}, + "ptr_uint": {"1", P(uint(1))}, + "ptr_uint_bigger": {"12324", P(uint(12324))}, + "ptr_uint_coerce": {`"65"`, P(uint(65))}, + "ptr_float_1.54": {"1.54", P(float32(1.54))}, + "ptr_float_1.89": {"1.89", P(float64(1.89))}, + + "date_time": {`"2007-03-01T13:00:00Z"`, time.Date(2007, time.March, 1, 13, 0, 0, 0, time.UTC)}, + "date_time_nano_coerce": {`"2007-03-01T13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)}, + + "date_time_missing_t_coerce": {`"2007-03-01 13:03:05Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.UTC)}, + "date_time_missing_timezone_coerce": {`"2007-03-01T13:03:05"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.UTC)}, + // note: using -1200 to minimize probability of conflicting with the local timezone of the test runner + // see https://en.wikipedia.org/wiki/UTC%E2%88%9212:00 + "date_time_missing_timezone_colon_coerce": {`"2007-03-01T13:03:05-1200"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.FixedZone("", -12*60*60))}, + "date_time_nano_missing_t_coerce": {`"2007-03-01 13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)}, + + "map_string": {`{"foo":"bar"}`, map[string]string{"foo": "bar"}}, + "map_string_with_sjson_path_chars": {`{":a.b.c*:d*-1e.f":"bar"}`, map[string]string{":a.b.c*:d*-1e.f": "bar"}}, + "map_interface": {`{"a":1,"b":"str","c":false}`, map[string]interface{}{"a": float64(1), "b": "str", "c": false}}, + + "primitive_struct": { + `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`, + Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + }, + + "slices": { + `{"slices":[{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}]}`, + Slices{ + Slice: []Primitives{{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}}, + }, + }, + + "primitive_pointer_struct": { + `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4,5]}`, + PrimitivePointers{ + A: P(false), + B: P(237628372683), + C: P(uint(654)), + D: P(9999.43), + E: P(float32(43.76)), + F: &[]int{1, 2, 3, 4, 5}, + }, + }, + + "datetime_struct": { + `{"date":"2006-01-02","date-time":"2006-01-02T15:04:05Z"}`, + DateTime{ + Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC), + DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC), + }, + }, + + "additional_properties": { + `{"a":true,"bar":"value","foo":true}`, + AdditionalProperties{ + A: true, + ExtraFields: map[string]interface{}{ + "bar": "value", + "foo": true, + }, + }, + }, + + "embedded_struct": { + `{"a":1,"b":"bar"}`, + EmbeddedStructs{ + EmbeddedStruct: EmbeddedStruct{ + A: true, + B: "bar", + JSON: EmbeddedStructJSON{ + A: Field{raw: `1`, status: valid}, + B: Field{raw: `"bar"`, status: valid}, + raw: `{"a":1,"b":"bar"}`, + }, + }, + A: P(1), + ExtraFields: map[string]interface{}{"b": "bar"}, + JSON: EmbeddedStructsJSON{ + A: Field{raw: `1`, status: valid}, + ExtraFields: map[string]Field{ + "b": {raw: `"bar"`, status: valid}, + }, + raw: `{"a":1,"b":"bar"}`, + }, + }, + }, + + "recursive_struct": { + `{"child":{"name":"Alex"},"name":"Robert"}`, + Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, + }, + + "metadata_coerce": { + `{"a":"12","b":"12","c":null,"extra_typed":12,"extra_untyped":{"foo":"bar"}}`, + JSONFieldStruct{ + A: false, + B: 12, + C: "", + JSON: JSONFieldStructJSON{ + raw: `{"a":"12","b":"12","c":null,"extra_typed":12,"extra_untyped":{"foo":"bar"}}`, + A: Field{raw: `"12"`, status: invalid}, + B: Field{raw: `"12"`, status: valid}, + C: Field{raw: "null", status: null}, + D: Field{raw: "", status: missing}, + ExtraFields: map[string]Field{ + "extra_typed": { + raw: "12", + status: valid, + }, + "extra_untyped": { + raw: `{"foo":"bar"}`, + status: invalid, + }, + }, + }, + ExtraFields: map[string]int64{ + "extra_typed": 12, + "extra_untyped": 0, + }, + }, + }, + + "unknown_struct_number": { + `{"unknown":12}`, + UnknownStruct{ + Unknown: 12., + }, + }, + + "unknown_struct_map": { + `{"unknown":{"foo":"bar"}}`, + UnknownStruct{ + Unknown: map[string]interface{}{ + "foo": "bar", + }, + }, + }, + + "union_integer": { + `{"union":12}`, + UnionStruct{ + Union: UnionInteger(12), + }, + }, + + "union_struct_discriminated_a": { + `{"union":{"a":"foo","b":"bar","type":"typeA"}}`, + UnionStruct{ + Union: UnionStructA{ + Type: "typeA", + A: "foo", + B: "bar", + }, + }, + }, + + "union_struct_discriminated_b": { + `{"union":{"a":"foo","type":"typeB"}}`, + UnionStruct{ + Union: UnionStructB{ + Type: "typeB", + A: "foo", + }, + }, + }, + + "union_struct_time": { + `{"union":"2010-05-23"}`, + UnionStruct{ + Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), + }, + }, + + "complex_union_a": { + `{"union":{"boo":"12","foo":true}}`, + ComplexUnionStruct{Union: ComplexUnionA{Boo: "12", Foo: true}}, + }, + + "complex_union_b": { + `{"union":{"boo":true,"foo":"12"}}`, + ComplexUnionStruct{Union: ComplexUnionB{Boo: true, Foo: "12"}}, + }, + + "complex_union_c": { + `{"union":{"boo":12}}`, + ComplexUnionStruct{Union: ComplexUnionC{Boo: 12}}, + }, + + "complex_union_type_a": { + `{"union":{"baz":12,"type":"a"}}`, + ComplexUnionStruct{Union: ComplexUnionTypeA{Baz: 12, Type: TypeA("a")}}, + }, + + "complex_union_type_b": { + `{"union":{"baz":12,"type":"b"}}`, + ComplexUnionStruct{Union: ComplexUnionTypeB{Baz: 12, Type: TypeB("b")}}, + }, + + "marshalling_union_a": { + `{"boo":"hello"}`, + MarshallingUnionStruct{Union: MarshallingUnionA{Boo: "hello"}}, + }, + "marshalling_union_b": { + `{"foo":"hi"}`, + MarshallingUnionStruct{Union: MarshallingUnionB{Foo: "hi"}}, + }, + + "unmarshal": { + `{"foo":"hello"}`, + &UnmarshalStruct{Foo: "hello", prop: true}, + }, + + "array_of_unmarshal": { + `[{"foo":"hello"}]`, + []UnmarshalStruct{{Foo: "hello", prop: true}}, + }, + + "inline_coerce": { + `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`, + Inline{ + InlineField: Primitives{A: false, B: 237628372683, C: 0x28e, D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + JSON: InlineJSON{ + InlineField: Field{raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", status: 3}, + raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", + }, + }, + }, + + "inline_array_coerce": { + `["Hello","foo","bar"]`, + InlineArray{ + InlineField: []string{"Hello", "foo", "bar"}, + JSON: InlineJSON{ + InlineField: Field{raw: `["Hello","foo","bar"]`, status: 3}, + raw: `["Hello","foo","bar"]`, + }, + }, + }, +} + +func TestDecode(t *testing.T) { + for name, test := range tests { + t.Run(name, func(t *testing.T) { + result := reflect.New(reflect.TypeOf(test.val)) + if err := Unmarshal([]byte(test.buf), result.Interface()); err != nil { + t.Fatalf("deserialization of %v failed with error %v", result, err) + } + if !reflect.DeepEqual(result.Elem().Interface(), test.val) { + t.Fatalf("expected '%s' to deserialize to \n%#v\nbut got\n%#v", test.buf, test.val, result.Elem().Interface()) + } + }) + } +} + +func TestEncode(t *testing.T) { + for name, test := range tests { + if strings.HasSuffix(name, "_coerce") { + continue + } + t.Run(name, func(t *testing.T) { + raw, err := Marshal(test.val) + if err != nil { + t.Fatalf("serialization of %v failed with error %v", test.val, err) + } + if string(raw) != test.buf { + t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.buf, string(raw)) + } + }) + } +} diff --git a/packages/sdk/go/internal/apijson/port.go b/packages/sdk/go/internal/apijson/port.go new file mode 100644 index 000000000..502ab7780 --- /dev/null +++ b/packages/sdk/go/internal/apijson/port.go @@ -0,0 +1,120 @@ +package apijson + +import ( + "fmt" + "reflect" +) + +// Port copies over values from one struct to another struct. +func Port(from any, to any) error { + toVal := reflect.ValueOf(to) + fromVal := reflect.ValueOf(from) + + if toVal.Kind() != reflect.Ptr || toVal.IsNil() { + return fmt.Errorf("destination must be a non-nil pointer") + } + + for toVal.Kind() == reflect.Ptr { + toVal = toVal.Elem() + } + toType := toVal.Type() + + for fromVal.Kind() == reflect.Ptr { + fromVal = fromVal.Elem() + } + fromType := fromVal.Type() + + if toType.Kind() != reflect.Struct { + return fmt.Errorf("destination must be a non-nil pointer to a struct (%v %v)", toType, toType.Kind()) + } + + values := map[string]reflect.Value{} + fields := map[string]reflect.Value{} + + fromJSON := fromVal.FieldByName("JSON") + toJSON := toVal.FieldByName("JSON") + + // Iterate through the fields of v and load all the "normal" fields in the struct to the map of + // string to reflect.Value, as well as their raw .JSON.Foo counterpart indicated by j. + var getFields func(t reflect.Type, v reflect.Value) + getFields = func(t reflect.Type, v reflect.Value) { + j := v.FieldByName("JSON") + + // Recurse into anonymous fields first, since the fields on the object should win over the fields in the + // embedded object. + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.Anonymous { + getFields(field.Type, v.Field(i)) + continue + } + } + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + ptag, ok := parseJSONStructTag(field) + if !ok || ptag.name == "-" { + continue + } + values[ptag.name] = v.Field(i) + if j.IsValid() { + fields[ptag.name] = j.FieldByName(field.Name) + } + } + } + getFields(fromType, fromVal) + + // Use the values from the previous step to populate the 'to' struct. + for i := 0; i < toType.NumField(); i++ { + field := toType.Field(i) + ptag, ok := parseJSONStructTag(field) + if !ok { + continue + } + if ptag.name == "-" { + continue + } + if value, ok := values[ptag.name]; ok { + delete(values, ptag.name) + if field.Type.Kind() == reflect.Interface { + toVal.Field(i).Set(value) + } else { + switch value.Kind() { + case reflect.String: + toVal.Field(i).SetString(value.String()) + case reflect.Bool: + toVal.Field(i).SetBool(value.Bool()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + toVal.Field(i).SetInt(value.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + toVal.Field(i).SetUint(value.Uint()) + case reflect.Float32, reflect.Float64: + toVal.Field(i).SetFloat(value.Float()) + default: + toVal.Field(i).Set(value) + } + } + } + + if fromJSONField, ok := fields[ptag.name]; ok { + if toJSONField := toJSON.FieldByName(field.Name); toJSONField.IsValid() { + toJSONField.Set(fromJSONField) + } + } + } + + // Finally, copy over the .JSON.raw and .JSON.ExtraFields + if toJSON.IsValid() { + if raw := toJSON.FieldByName("raw"); raw.IsValid() { + setUnexportedField(raw, fromJSON.Interface().(interface{ RawJSON() string }).RawJSON()) + } + + if toExtraFields := toJSON.FieldByName("ExtraFields"); toExtraFields.IsValid() { + if fromExtraFields := fromJSON.FieldByName("ExtraFields"); fromExtraFields.IsValid() { + setUnexportedField(toExtraFields, fromExtraFields.Interface()) + } + } + } + + return nil +} diff --git a/packages/sdk/go/internal/apijson/port_test.go b/packages/sdk/go/internal/apijson/port_test.go new file mode 100644 index 000000000..115405335 --- /dev/null +++ b/packages/sdk/go/internal/apijson/port_test.go @@ -0,0 +1,257 @@ +package apijson + +import ( + "reflect" + "testing" +) + +type Metadata struct { + CreatedAt string `json:"created_at"` +} + +// Card is the "combined" type of CardVisa and CardMastercard +type Card struct { + Processor CardProcessor `json:"processor"` + Data any `json:"data"` + IsFoo bool `json:"is_foo"` + IsBar bool `json:"is_bar"` + Metadata Metadata `json:"metadata"` + Value interface{} `json:"value"` + + JSON cardJSON +} + +type cardJSON struct { + Processor Field + Data Field + IsFoo Field + IsBar Field + Metadata Field + Value Field + ExtraFields map[string]Field + raw string +} + +func (r cardJSON) RawJSON() string { return r.raw } + +type CardProcessor string + +// CardVisa +type CardVisa struct { + Processor CardVisaProcessor `json:"processor"` + Data CardVisaData `json:"data"` + IsFoo bool `json:"is_foo"` + Metadata Metadata `json:"metadata"` + Value string `json:"value"` + + JSON cardVisaJSON +} + +type cardVisaJSON struct { + Processor Field + Data Field + IsFoo Field + Metadata Field + Value Field + ExtraFields map[string]Field + raw string +} + +func (r cardVisaJSON) RawJSON() string { return r.raw } + +type CardVisaProcessor string + +type CardVisaData struct { + Foo string `json:"foo"` +} + +// CardMastercard +type CardMastercard struct { + Processor CardMastercardProcessor `json:"processor"` + Data CardMastercardData `json:"data"` + IsBar bool `json:"is_bar"` + Metadata Metadata `json:"metadata"` + Value bool `json:"value"` + + JSON cardMastercardJSON +} + +type cardMastercardJSON struct { + Processor Field + Data Field + IsBar Field + Metadata Field + Value Field + ExtraFields map[string]Field + raw string +} + +func (r cardMastercardJSON) RawJSON() string { return r.raw } + +type CardMastercardProcessor string + +type CardMastercardData struct { + Bar int64 `json:"bar"` +} + +type CommonFields struct { + Metadata Metadata `json:"metadata"` + Value string `json:"value"` + + JSON commonFieldsJSON +} + +type commonFieldsJSON struct { + Metadata Field + Value Field + ExtraFields map[string]Field + raw string +} + +type CardEmbedded struct { + CommonFields + Processor CardVisaProcessor `json:"processor"` + Data CardVisaData `json:"data"` + IsFoo bool `json:"is_foo"` + + JSON cardEmbeddedJSON +} + +type cardEmbeddedJSON struct { + Processor Field + Data Field + IsFoo Field + ExtraFields map[string]Field + raw string +} + +func (r cardEmbeddedJSON) RawJSON() string { return r.raw } + +var portTests = map[string]struct { + from any + to any +}{ + "visa to card": { + CardVisa{ + Processor: "visa", + IsFoo: true, + Data: CardVisaData{ + Foo: "foo", + }, + Metadata: Metadata{ + CreatedAt: "Mar 29 2024", + }, + Value: "value", + JSON: cardVisaJSON{ + raw: `{"processor":"visa","is_foo":true,"data":{"foo":"foo"}}`, + Processor: Field{raw: `"visa"`, status: valid}, + IsFoo: Field{raw: `true`, status: valid}, + Data: Field{raw: `{"foo":"foo"}`, status: valid}, + Value: Field{raw: `"value"`, status: valid}, + ExtraFields: map[string]Field{"extra": {raw: `"yo"`, status: valid}}, + }, + }, + Card{ + Processor: "visa", + IsFoo: true, + IsBar: false, + Data: CardVisaData{ + Foo: "foo", + }, + Metadata: Metadata{ + CreatedAt: "Mar 29 2024", + }, + Value: "value", + JSON: cardJSON{ + raw: `{"processor":"visa","is_foo":true,"data":{"foo":"foo"}}`, + Processor: Field{raw: `"visa"`, status: valid}, + IsFoo: Field{raw: `true`, status: valid}, + Data: Field{raw: `{"foo":"foo"}`, status: valid}, + Value: Field{raw: `"value"`, status: valid}, + ExtraFields: map[string]Field{"extra": {raw: `"yo"`, status: valid}}, + }, + }, + }, + "mastercard to card": { + CardMastercard{ + Processor: "mastercard", + IsBar: true, + Data: CardMastercardData{ + Bar: 13, + }, + Value: false, + }, + Card{ + Processor: "mastercard", + IsFoo: false, + IsBar: true, + Data: CardMastercardData{ + Bar: 13, + }, + Value: false, + }, + }, + "embedded to card": { + CardEmbedded{ + CommonFields: CommonFields{ + Metadata: Metadata{ + CreatedAt: "Mar 29 2024", + }, + Value: "embedded_value", + JSON: commonFieldsJSON{ + Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: valid}, + Value: Field{raw: `"embedded_value"`, status: valid}, + raw: `should not matter`, + }, + }, + Processor: "visa", + IsFoo: true, + Data: CardVisaData{ + Foo: "embedded_foo", + }, + JSON: cardEmbeddedJSON{ + raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"},"metadata":{"created_at":"Mar 29 2024"},"value":"embedded_value"}`, + Processor: Field{raw: `"visa"`, status: valid}, + IsFoo: Field{raw: `true`, status: valid}, + Data: Field{raw: `{"foo":"embedded_foo"}`, status: valid}, + }, + }, + Card{ + Processor: "visa", + IsFoo: true, + IsBar: false, + Data: CardVisaData{ + Foo: "embedded_foo", + }, + Metadata: Metadata{ + CreatedAt: "Mar 29 2024", + }, + Value: "embedded_value", + JSON: cardJSON{ + raw: `{"processor":"visa","is_foo":true,"data":{"foo":"embedded_foo"},"metadata":{"created_at":"Mar 29 2024"},"value":"embedded_value"}`, + Processor: Field{raw: `"visa"`, status: 0x3}, + IsFoo: Field{raw: "true", status: 0x3}, + Data: Field{raw: `{"foo":"embedded_foo"}`, status: 0x3}, + Metadata: Field{raw: `{"created_at":"Mar 29 2024"}`, status: 0x3}, + Value: Field{raw: `"embedded_value"`, status: 0x3}, + }, + }, + }, +} + +func TestPort(t *testing.T) { + for name, test := range portTests { + t.Run(name, func(t *testing.T) { + toVal := reflect.New(reflect.TypeOf(test.to)) + + err := Port(test.from, toVal.Interface()) + if err != nil { + t.Fatalf("port of %v failed with error %v", test.from, err) + } + + if !reflect.DeepEqual(toVal.Elem().Interface(), test.to) { + t.Fatalf("expected:\n%+#v\n\nto port to:\n%+#v\n\nbut got:\n%+#v", test.from, test.to, toVal.Elem().Interface()) + } + }) + } +} diff --git a/packages/sdk/go/internal/apijson/registry.go b/packages/sdk/go/internal/apijson/registry.go new file mode 100644 index 000000000..119cc5ff8 --- /dev/null +++ b/packages/sdk/go/internal/apijson/registry.go @@ -0,0 +1,41 @@ +package apijson + +import ( + "reflect" + + "github.com/tidwall/gjson" +) + +type UnionVariant struct { + TypeFilter gjson.Type + DiscriminatorValue interface{} + Type reflect.Type +} + +var unionRegistry = map[reflect.Type]unionEntry{} +var unionVariants = map[reflect.Type]interface{}{} + +type unionEntry struct { + discriminatorKey string + variants []UnionVariant +} + +func RegisterUnion(typ reflect.Type, discriminator string, variants ...UnionVariant) { + unionRegistry[typ] = unionEntry{ + discriminatorKey: discriminator, + variants: variants, + } + for _, variant := range variants { + unionVariants[variant.Type] = typ + } +} + +// Useful to wrap a union type to force it to use [apijson.UnmarshalJSON] since you cannot define an +// UnmarshalJSON function on the interface itself. +type UnionUnmarshaler[T any] struct { + Value T +} + +func (c *UnionUnmarshaler[T]) UnmarshalJSON(buf []byte) error { + return UnmarshalRoot(buf, &c.Value) +} diff --git a/packages/sdk/go/internal/apijson/tag.go b/packages/sdk/go/internal/apijson/tag.go new file mode 100644 index 000000000..812fb3caf --- /dev/null +++ b/packages/sdk/go/internal/apijson/tag.go @@ -0,0 +1,47 @@ +package apijson + +import ( + "reflect" + "strings" +) + +const jsonStructTag = "json" +const formatStructTag = "format" + +type parsedStructTag struct { + name string + required bool + extras bool + metadata bool + inline bool +} + +func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { + raw, ok := field.Tag.Lookup(jsonStructTag) + if !ok { + return + } + parts := strings.Split(raw, ",") + if len(parts) == 0 { + return tag, false + } + tag.name = parts[0] + for _, part := range parts[1:] { + switch part { + case "required": + tag.required = true + case "extras": + tag.extras = true + case "metadata": + tag.metadata = true + case "inline": + tag.inline = true + } + } + return +} + +func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { + format, ok = field.Tag.Lookup(formatStructTag) + return +} diff --git a/packages/sdk/go/internal/apiquery/encoder.go b/packages/sdk/go/internal/apiquery/encoder.go new file mode 100644 index 000000000..0922c2316 --- /dev/null +++ b/packages/sdk/go/internal/apiquery/encoder.go @@ -0,0 +1,341 @@ +package apiquery + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/sst/opencode-sdk-go/internal/param" +) + +var encoders sync.Map // map[reflect.Type]encoderFunc + +type encoder struct { + dateFormat string + root bool + settings QuerySettings +} + +type encoderFunc func(key string, value reflect.Value) []Pair + +type encoderField struct { + tag parsedStructTag + fn encoderFunc + idx []int +} + +type encoderEntry struct { + reflect.Type + dateFormat string + root bool + settings QuerySettings +} + +type Pair struct { + key string + value string +} + +func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { + entry := encoderEntry{ + Type: t, + dateFormat: e.dateFormat, + root: e.root, + settings: e.settings, + } + + if fi, ok := encoders.Load(entry); ok { + return fi.(encoderFunc) + } + + // To deal with recursive types, populate the map with an + // indirect func before we build it. This type waits on the + // real func (f) to be ready and then calls it. This indirect + // func is only used for recursive types. + var ( + wg sync.WaitGroup + f encoderFunc + ) + wg.Add(1) + fi, loaded := encoders.LoadOrStore(entry, encoderFunc(func(key string, v reflect.Value) []Pair { + wg.Wait() + return f(key, v) + })) + if loaded { + return fi.(encoderFunc) + } + + // Compute the real encoder and replace the indirect func with it. + f = e.newTypeEncoder(t) + wg.Done() + encoders.Store(entry, f) + return f +} + +func marshalerEncoder(key string, value reflect.Value) []Pair { + s, _ := value.Interface().(json.Marshaler).MarshalJSON() + return []Pair{{key, string(s)}} +} + +func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc { + if t.ConvertibleTo(reflect.TypeOf(time.Time{})) { + return e.newTimeTypeEncoder(t) + } + if !e.root && t.Implements(reflect.TypeOf((*json.Marshaler)(nil)).Elem()) { + return marshalerEncoder + } + e.root = false + switch t.Kind() { + case reflect.Pointer: + encoder := e.typeEncoder(t.Elem()) + return func(key string, value reflect.Value) (pairs []Pair) { + if !value.IsValid() || value.IsNil() { + return + } + pairs = encoder(key, value.Elem()) + return + } + case reflect.Struct: + return e.newStructTypeEncoder(t) + case reflect.Array: + fallthrough + case reflect.Slice: + return e.newArrayTypeEncoder(t) + case reflect.Map: + return e.newMapEncoder(t) + case reflect.Interface: + return e.newInterfaceEncoder() + default: + return e.newPrimitiveTypeEncoder(t) + } +} + +func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { + if t.Implements(reflect.TypeOf((*param.FieldLike)(nil)).Elem()) { + return e.newFieldTypeEncoder(t) + } + + encoderFields := []encoderField{} + + // This helper allows us to recursively collect field encoders into a flat + // array. The parameter `index` keeps track of the access patterns necessary + // to get to some field. + var collectEncoderFields func(r reflect.Type, index []int) + collectEncoderFields = func(r reflect.Type, index []int) { + for i := 0; i < r.NumField(); i++ { + idx := append(index, i) + field := t.FieldByIndex(idx) + if !field.IsExported() { + continue + } + // If this is an embedded struct, traverse one level deeper to extract + // the field and get their encoders as well. + if field.Anonymous { + collectEncoderFields(field.Type, idx) + continue + } + // If query tag is not present, then we skip, which is intentionally + // different behavior from the stdlib. + ptag, ok := parseQueryStructTag(field) + if !ok { + continue + } + + if ptag.name == "-" && !ptag.inline { + continue + } + + dateFormat, ok := parseFormatStructTag(field) + oldFormat := e.dateFormat + if ok { + switch dateFormat { + case "date-time": + e.dateFormat = time.RFC3339 + case "date": + e.dateFormat = "2006-01-02" + } + } + encoderFields = append(encoderFields, encoderField{ptag, e.typeEncoder(field.Type), idx}) + e.dateFormat = oldFormat + } + } + collectEncoderFields(t, []int{}) + + return func(key string, value reflect.Value) (pairs []Pair) { + for _, ef := range encoderFields { + var subkey string = e.renderKeyPath(key, ef.tag.name) + if ef.tag.inline { + subkey = key + } + + field := value.FieldByIndex(ef.idx) + pairs = append(pairs, ef.fn(subkey, field)...) + } + return + } +} + +func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc { + keyEncoder := e.typeEncoder(t.Key()) + elementEncoder := e.typeEncoder(t.Elem()) + return func(key string, value reflect.Value) (pairs []Pair) { + iter := value.MapRange() + for iter.Next() { + encodedKey := keyEncoder("", iter.Key()) + if len(encodedKey) != 1 { + panic("Unexpected number of parts for encoded map key. Are you using a non-primitive for this map?") + } + subkey := encodedKey[0].value + keyPath := e.renderKeyPath(key, subkey) + pairs = append(pairs, elementEncoder(keyPath, iter.Value())...) + } + return + } +} + +func (e *encoder) renderKeyPath(key string, subkey string) string { + if len(key) == 0 { + return subkey + } + if e.settings.NestedFormat == NestedQueryFormatDots { + return fmt.Sprintf("%s.%s", key, subkey) + } + return fmt.Sprintf("%s[%s]", key, subkey) +} + +func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { + switch e.settings.ArrayFormat { + case ArrayQueryFormatComma: + innerEncoder := e.typeEncoder(t.Elem()) + return func(key string, v reflect.Value) []Pair { + elements := []string{} + for i := 0; i < v.Len(); i++ { + for _, pair := range innerEncoder("", v.Index(i)) { + elements = append(elements, pair.value) + } + } + if len(elements) == 0 { + return []Pair{} + } + return []Pair{{key, strings.Join(elements, ",")}} + } + case ArrayQueryFormatRepeat: + innerEncoder := e.typeEncoder(t.Elem()) + return func(key string, value reflect.Value) (pairs []Pair) { + for i := 0; i < value.Len(); i++ { + pairs = append(pairs, innerEncoder(key, value.Index(i))...) + } + return pairs + } + case ArrayQueryFormatIndices: + panic("The array indices format is not supported yet") + case ArrayQueryFormatBrackets: + innerEncoder := e.typeEncoder(t.Elem()) + return func(key string, value reflect.Value) []Pair { + pairs := []Pair{} + for i := 0; i < value.Len(); i++ { + pairs = append(pairs, innerEncoder(key+"[]", value.Index(i))...) + } + return pairs + } + default: + panic(fmt.Sprintf("Unknown ArrayFormat value: %d", e.settings.ArrayFormat)) + } +} + +func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { + switch t.Kind() { + case reflect.Pointer: + inner := t.Elem() + + innerEncoder := e.newPrimitiveTypeEncoder(inner) + return func(key string, v reflect.Value) []Pair { + if !v.IsValid() || v.IsNil() { + return nil + } + return innerEncoder(key, v.Elem()) + } + case reflect.String: + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, v.String()}} + } + case reflect.Bool: + return func(key string, v reflect.Value) []Pair { + if v.Bool() { + return []Pair{{key, "true"}} + } + return []Pair{{key, "false"}} + } + case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, strconv.FormatInt(v.Int(), 10)}} + } + case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, strconv.FormatUint(v.Uint(), 10)}} + } + case reflect.Float32, reflect.Float64: + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, strconv.FormatFloat(v.Float(), 'f', -1, 64)}} + } + case reflect.Complex64, reflect.Complex128: + bitSize := 64 + if t.Kind() == reflect.Complex128 { + bitSize = 128 + } + return func(key string, v reflect.Value) []Pair { + return []Pair{{key, strconv.FormatComplex(v.Complex(), 'f', -1, bitSize)}} + } + default: + return func(key string, v reflect.Value) []Pair { + return nil + } + } +} + +func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc { + f, _ := t.FieldByName("Value") + enc := e.typeEncoder(f.Type) + + return func(key string, value reflect.Value) []Pair { + present := value.FieldByName("Present") + if !present.Bool() { + return nil + } + null := value.FieldByName("Null") + if null.Bool() { + // TODO: Error? + return nil + } + raw := value.FieldByName("Raw") + if !raw.IsNil() { + return e.typeEncoder(raw.Type())(key, raw) + } + return enc(key, value.FieldByName("Value")) + } +} + +func (e *encoder) newTimeTypeEncoder(t reflect.Type) encoderFunc { + format := e.dateFormat + return func(key string, value reflect.Value) []Pair { + return []Pair{{ + key, + value.Convert(reflect.TypeOf(time.Time{})).Interface().(time.Time).Format(format), + }} + } +} + +func (e encoder) newInterfaceEncoder() encoderFunc { + return func(key string, value reflect.Value) []Pair { + value = value.Elem() + if !value.IsValid() { + return nil + } + return e.typeEncoder(value.Type())(key, value) + } + +} diff --git a/packages/sdk/go/internal/apiquery/query.go b/packages/sdk/go/internal/apiquery/query.go new file mode 100644 index 000000000..6f90e9935 --- /dev/null +++ b/packages/sdk/go/internal/apiquery/query.go @@ -0,0 +1,50 @@ +package apiquery + +import ( + "net/url" + "reflect" + "time" +) + +func MarshalWithSettings(value interface{}, settings QuerySettings) url.Values { + e := encoder{time.RFC3339, true, settings} + kv := url.Values{} + val := reflect.ValueOf(value) + if !val.IsValid() { + return nil + } + typ := val.Type() + for _, pair := range e.typeEncoder(typ)("", val) { + kv.Add(pair.key, pair.value) + } + return kv +} + +func Marshal(value interface{}) url.Values { + return MarshalWithSettings(value, QuerySettings{}) +} + +type Queryer interface { + URLQuery() url.Values +} + +type QuerySettings struct { + NestedFormat NestedQueryFormat + ArrayFormat ArrayQueryFormat +} + +type NestedQueryFormat int + +const ( + NestedQueryFormatBrackets NestedQueryFormat = iota + NestedQueryFormatDots +) + +type ArrayQueryFormat int + +const ( + ArrayQueryFormatComma ArrayQueryFormat = iota + ArrayQueryFormatRepeat + ArrayQueryFormatIndices + ArrayQueryFormatBrackets +) diff --git a/packages/sdk/go/internal/apiquery/query_test.go b/packages/sdk/go/internal/apiquery/query_test.go new file mode 100644 index 000000000..1e740d6a9 --- /dev/null +++ b/packages/sdk/go/internal/apiquery/query_test.go @@ -0,0 +1,335 @@ +package apiquery + +import ( + "net/url" + "testing" + "time" +) + +func P[T any](v T) *T { return &v } + +type Primitives struct { + A bool `query:"a"` + B int `query:"b"` + C uint `query:"c"` + D float64 `query:"d"` + E float32 `query:"e"` + F []int `query:"f"` +} + +type PrimitivePointers struct { + A *bool `query:"a"` + B *int `query:"b"` + C *uint `query:"c"` + D *float64 `query:"d"` + E *float32 `query:"e"` + F *[]int `query:"f"` +} + +type Slices struct { + Slice []Primitives `query:"slices"` + Mixed []interface{} `query:"mixed"` +} + +type DateTime struct { + Date time.Time `query:"date" format:"date"` + DateTime time.Time `query:"date-time" format:"date-time"` +} + +type AdditionalProperties struct { + A bool `query:"a"` + Extras map[string]interface{} `query:"-,inline"` +} + +type Recursive struct { + Name string `query:"name"` + Child *Recursive `query:"child"` +} + +type UnknownStruct struct { + Unknown interface{} `query:"unknown"` +} + +type UnionStruct struct { + Union Union `query:"union" format:"date"` +} + +type Union interface { + union() +} + +type UnionInteger int64 + +func (UnionInteger) union() {} + +type UnionString string + +func (UnionString) union() {} + +type UnionStructA struct { + Type string `query:"type"` + A string `query:"a"` + B string `query:"b"` +} + +func (UnionStructA) union() {} + +type UnionStructB struct { + Type string `query:"type"` + A string `query:"a"` +} + +func (UnionStructB) union() {} + +type UnionTime time.Time + +func (UnionTime) union() {} + +type DeeplyNested struct { + A DeeplyNested1 `query:"a"` +} + +type DeeplyNested1 struct { + B DeeplyNested2 `query:"b"` +} + +type DeeplyNested2 struct { + C DeeplyNested3 `query:"c"` +} + +type DeeplyNested3 struct { + D *string `query:"d"` +} + +var tests = map[string]struct { + enc string + val interface{} + settings QuerySettings +}{ + "primitives": { + "a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4", + Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + QuerySettings{}, + }, + + "slices_brackets": { + `mixed[]=1&mixed[]=2.3&mixed[]=hello&slices[][a]=false&slices[][a]=false&slices[][b]=237628372683&slices[][b]=237628372683&slices[][c]=654&slices[][c]=654&slices[][d]=9999.43&slices[][d]=9999.43&slices[][e]=43.7599983215332&slices[][e]=43.7599983215332&slices[][f][]=1&slices[][f][]=2&slices[][f][]=3&slices[][f][]=4&slices[][f][]=1&slices[][f][]=2&slices[][f][]=3&slices[][f][]=4`, + Slices{ + Slice: []Primitives{ + {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + }, + Mixed: []interface{}{1, 2.3, "hello"}, + }, + QuerySettings{ArrayFormat: ArrayQueryFormatBrackets}, + }, + + "slices_comma": { + `mixed=1,2.3,hello`, + Slices{ + Mixed: []interface{}{1, 2.3, "hello"}, + }, + QuerySettings{ArrayFormat: ArrayQueryFormatComma}, + }, + + "slices_repeat": { + `mixed=1&mixed=2.3&mixed=hello&slices[a]=false&slices[a]=false&slices[b]=237628372683&slices[b]=237628372683&slices[c]=654&slices[c]=654&slices[d]=9999.43&slices[d]=9999.43&slices[e]=43.7599983215332&slices[e]=43.7599983215332&slices[f]=1&slices[f]=2&slices[f]=3&slices[f]=4&slices[f]=1&slices[f]=2&slices[f]=3&slices[f]=4`, + Slices{ + Slice: []Primitives{ + {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, + }, + Mixed: []interface{}{1, 2.3, "hello"}, + }, + QuerySettings{ArrayFormat: ArrayQueryFormatRepeat}, + }, + + "primitive_pointer_struct": { + "a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4,5", + PrimitivePointers{ + A: P(false), + B: P(237628372683), + C: P(uint(654)), + D: P(9999.43), + E: P(float32(43.76)), + F: &[]int{1, 2, 3, 4, 5}, + }, + QuerySettings{}, + }, + + "datetime_struct": { + `date=2006-01-02&date-time=2006-01-02T15:04:05Z`, + DateTime{ + Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC), + DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC), + }, + QuerySettings{}, + }, + + "additional_properties": { + `a=true&bar=value&foo=true`, + AdditionalProperties{ + A: true, + Extras: map[string]interface{}{ + "bar": "value", + "foo": true, + }, + }, + QuerySettings{}, + }, + + "recursive_struct_brackets": { + `child[name]=Alex&name=Robert`, + Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, + QuerySettings{NestedFormat: NestedQueryFormatBrackets}, + }, + + "recursive_struct_dots": { + `child.name=Alex&name=Robert`, + Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, + QuerySettings{NestedFormat: NestedQueryFormatDots}, + }, + + "unknown_struct_number": { + `unknown=12`, + UnknownStruct{ + Unknown: 12., + }, + QuerySettings{}, + }, + + "unknown_struct_map_brackets": { + `unknown[foo]=bar`, + UnknownStruct{ + Unknown: map[string]interface{}{ + "foo": "bar", + }, + }, + QuerySettings{NestedFormat: NestedQueryFormatBrackets}, + }, + + "unknown_struct_map_dots": { + `unknown.foo=bar`, + UnknownStruct{ + Unknown: map[string]interface{}{ + "foo": "bar", + }, + }, + QuerySettings{NestedFormat: NestedQueryFormatDots}, + }, + + "union_string": { + `union=hello`, + UnionStruct{ + Union: UnionString("hello"), + }, + QuerySettings{}, + }, + + "union_integer": { + `union=12`, + UnionStruct{ + Union: UnionInteger(12), + }, + QuerySettings{}, + }, + + "union_struct_discriminated_a": { + `union[a]=foo&union[b]=bar&union[type]=typeA`, + UnionStruct{ + Union: UnionStructA{ + Type: "typeA", + A: "foo", + B: "bar", + }, + }, + QuerySettings{}, + }, + + "union_struct_discriminated_b": { + `union[a]=foo&union[type]=typeB`, + UnionStruct{ + Union: UnionStructB{ + Type: "typeB", + A: "foo", + }, + }, + QuerySettings{}, + }, + + "union_struct_time": { + `union=2010-05-23`, + UnionStruct{ + Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), + }, + QuerySettings{}, + }, + + "deeply_nested_brackets": { + `a[b][c][d]=hello`, + DeeplyNested{ + A: DeeplyNested1{ + B: DeeplyNested2{ + C: DeeplyNested3{ + D: P("hello"), + }, + }, + }, + }, + QuerySettings{NestedFormat: NestedQueryFormatBrackets}, + }, + + "deeply_nested_dots": { + `a.b.c.d=hello`, + DeeplyNested{ + A: DeeplyNested1{ + B: DeeplyNested2{ + C: DeeplyNested3{ + D: P("hello"), + }, + }, + }, + }, + QuerySettings{NestedFormat: NestedQueryFormatDots}, + }, + + "deeply_nested_brackets_empty": { + ``, + DeeplyNested{ + A: DeeplyNested1{ + B: DeeplyNested2{ + C: DeeplyNested3{ + D: nil, + }, + }, + }, + }, + QuerySettings{NestedFormat: NestedQueryFormatBrackets}, + }, + + "deeply_nested_dots_empty": { + ``, + DeeplyNested{ + A: DeeplyNested1{ + B: DeeplyNested2{ + C: DeeplyNested3{ + D: nil, + }, + }, + }, + }, + QuerySettings{NestedFormat: NestedQueryFormatDots}, + }, +} + +func TestEncode(t *testing.T) { + for name, test := range tests { + t.Run(name, func(t *testing.T) { + values := MarshalWithSettings(test.val, test.settings) + str, _ := url.QueryUnescape(values.Encode()) + if str != test.enc { + t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.enc, str) + } + }) + } +} diff --git a/packages/sdk/go/internal/apiquery/tag.go b/packages/sdk/go/internal/apiquery/tag.go new file mode 100644 index 000000000..7ccd739c2 --- /dev/null +++ b/packages/sdk/go/internal/apiquery/tag.go @@ -0,0 +1,41 @@ +package apiquery + +import ( + "reflect" + "strings" +) + +const queryStructTag = "query" +const formatStructTag = "format" + +type parsedStructTag struct { + name string + omitempty bool + inline bool +} + +func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) { + raw, ok := field.Tag.Lookup(queryStructTag) + if !ok { + return + } + parts := strings.Split(raw, ",") + if len(parts) == 0 { + return tag, false + } + tag.name = parts[0] + for _, part := range parts[1:] { + switch part { + case "omitempty": + tag.omitempty = true + case "inline": + tag.inline = true + } + } + return +} + +func parseFormatStructTag(field reflect.StructField) (format string, ok bool) { + format, ok = field.Tag.Lookup(formatStructTag) + return +} diff --git a/packages/sdk/go/internal/param/field.go b/packages/sdk/go/internal/param/field.go new file mode 100644 index 000000000..4d0fd9c6f --- /dev/null +++ b/packages/sdk/go/internal/param/field.go @@ -0,0 +1,29 @@ +package param + +import ( + "fmt" +) + +type FieldLike interface{ field() } + +// Field is a wrapper used for all values sent to the API, +// to distinguish zero values from null or omitted fields. +// +// It also allows sending arbitrary deserializable values. +// +// To instantiate a Field, use the helpers exported from +// the package root: `F()`, `Null()`, `Raw()`, etc. +type Field[T any] struct { + FieldLike + Value T + Null bool + Present bool + Raw any +} + +func (f Field[T]) String() string { + if s, ok := any(f.Value).(fmt.Stringer); ok { + return s.String() + } + return fmt.Sprintf("%v", f.Value) +} diff --git a/packages/sdk/go/internal/requestconfig/requestconfig.go b/packages/sdk/go/internal/requestconfig/requestconfig.go new file mode 100644 index 000000000..91b70cca5 --- /dev/null +++ b/packages/sdk/go/internal/requestconfig/requestconfig.go @@ -0,0 +1,629 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package requestconfig + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "math" + "math/rand" + "mime" + "net/http" + "net/url" + "runtime" + "strconv" + "strings" + "time" + + "github.com/sst/opencode-sdk-go/internal" + "github.com/sst/opencode-sdk-go/internal/apierror" + "github.com/sst/opencode-sdk-go/internal/apiform" + "github.com/sst/opencode-sdk-go/internal/apiquery" + "github.com/sst/opencode-sdk-go/internal/param" +) + +func getDefaultHeaders() map[string]string { + return map[string]string{ + "User-Agent": fmt.Sprintf("Opencode/Go %s", internal.PackageVersion), + } +} + +func getNormalizedOS() string { + switch runtime.GOOS { + case "ios": + return "iOS" + case "android": + return "Android" + case "darwin": + return "MacOS" + case "window": + return "Windows" + case "freebsd": + return "FreeBSD" + case "openbsd": + return "OpenBSD" + case "linux": + return "Linux" + default: + return fmt.Sprintf("Other:%s", runtime.GOOS) + } +} + +func getNormalizedArchitecture() string { + switch runtime.GOARCH { + case "386": + return "x32" + case "amd64": + return "x64" + case "arm": + return "arm" + case "arm64": + return "arm64" + default: + return fmt.Sprintf("other:%s", runtime.GOARCH) + } +} + +func getPlatformProperties() map[string]string { + return map[string]string{ + "X-Stainless-Lang": "go", + "X-Stainless-Package-Version": internal.PackageVersion, + "X-Stainless-OS": getNormalizedOS(), + "X-Stainless-Arch": getNormalizedArchitecture(), + "X-Stainless-Runtime": "go", + "X-Stainless-Runtime-Version": runtime.Version(), + } +} + +type RequestOption interface { + Apply(*RequestConfig) error +} + +type RequestOptionFunc func(*RequestConfig) error +type PreRequestOptionFunc func(*RequestConfig) error + +func (s RequestOptionFunc) Apply(r *RequestConfig) error { return s(r) } +func (s PreRequestOptionFunc) Apply(r *RequestConfig) error { return s(r) } + +func NewRequestConfig(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...RequestOption) (*RequestConfig, error) { + var reader io.Reader + + contentType := "application/json" + hasSerializationFunc := false + + if body, ok := body.(json.Marshaler); ok { + content, err := body.MarshalJSON() + if err != nil { + return nil, err + } + reader = bytes.NewBuffer(content) + hasSerializationFunc = true + } + if body, ok := body.(apiform.Marshaler); ok { + var ( + content []byte + err error + ) + content, contentType, err = body.MarshalMultipart() + if err != nil { + return nil, err + } + reader = bytes.NewBuffer(content) + hasSerializationFunc = true + } + if body, ok := body.(apiquery.Queryer); ok { + hasSerializationFunc = true + params := body.URLQuery().Encode() + if params != "" { + u = u + "?" + params + } + } + if body, ok := body.([]byte); ok { + reader = bytes.NewBuffer(body) + hasSerializationFunc = true + } + if body, ok := body.(io.Reader); ok { + reader = body + hasSerializationFunc = true + } + + // Fallback to json serialization if none of the serialization functions that we expect + // to see is present. + if body != nil && !hasSerializationFunc { + content, err := json.Marshal(body) + if err != nil { + return nil, err + } + reader = bytes.NewBuffer(content) + } + + req, err := http.NewRequestWithContext(ctx, method, u, nil) + if err != nil { + return nil, err + } + if reader != nil { + req.Header.Set("Content-Type", contentType) + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("X-Stainless-Retry-Count", "0") + req.Header.Set("X-Stainless-Timeout", "0") + for k, v := range getDefaultHeaders() { + req.Header.Add(k, v) + } + + for k, v := range getPlatformProperties() { + req.Header.Add(k, v) + } + cfg := RequestConfig{ + MaxRetries: 2, + Context: ctx, + Request: req, + HTTPClient: http.DefaultClient, + Body: reader, + } + cfg.ResponseBodyInto = dst + err = cfg.Apply(opts...) + if err != nil { + return nil, err + } + + // This must run after `cfg.Apply(...)` above in case the request timeout gets modified. We also only + // apply our own logic for it if it's still "0" from above. If it's not, then it was deleted or modified + // by the user and we should respect that. + if req.Header.Get("X-Stainless-Timeout") == "0" { + if cfg.RequestTimeout == time.Duration(0) { + req.Header.Del("X-Stainless-Timeout") + } else { + req.Header.Set("X-Stainless-Timeout", strconv.Itoa(int(cfg.RequestTimeout.Seconds()))) + } + } + + return &cfg, nil +} + +func UseDefaultParam[T any](dst *param.Field[T], src *T) { + if !dst.Present && src != nil { + dst.Value = *src + dst.Present = true + } +} + +// This interface is primarily used to describe an [*http.Client], but also +// supports custom HTTP implementations. +type HTTPDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// RequestConfig represents all the state related to one request. +// +// Editing the variables inside RequestConfig directly is unstable api. Prefer +// composing the RequestOption instead if possible. +type RequestConfig struct { + MaxRetries int + RequestTimeout time.Duration + Context context.Context + Request *http.Request + BaseURL *url.URL + // DefaultBaseURL will be used if BaseURL is not explicitly overridden using + // WithBaseURL. + DefaultBaseURL *url.URL + CustomHTTPDoer HTTPDoer + HTTPClient *http.Client + Middlewares []middleware + // If ResponseBodyInto not nil, then we will attempt to deserialize into + // ResponseBodyInto. If Destination is a []byte, then it will return the body as + // is. + ResponseBodyInto interface{} + // ResponseInto copies the \*http.Response of the corresponding request into the + // given address + ResponseInto **http.Response + Body io.Reader +} + +// middleware is exactly the same type as the Middleware type found in the [option] package, +// but it is redeclared here for circular dependency issues. +type middleware = func(*http.Request, middlewareNext) (*http.Response, error) + +// middlewareNext is exactly the same type as the MiddlewareNext type found in the [option] package, +// but it is redeclared here for circular dependency issues. +type middlewareNext = func(*http.Request) (*http.Response, error) + +func applyMiddleware(middleware middleware, next middlewareNext) middlewareNext { + return func(req *http.Request) (res *http.Response, err error) { + return middleware(req, next) + } +} + +func shouldRetry(req *http.Request, res *http.Response) bool { + // If there is no way to recover the Body, then we shouldn't retry. + if req.Body != nil && req.GetBody == nil { + return false + } + + // If there is no response, that indicates that there is a connection error + // so we retry the request. + if res == nil { + return true + } + + // If the header explicitly wants a retry behavior, respect that over the + // http status code. + if res.Header.Get("x-should-retry") == "true" { + return true + } + if res.Header.Get("x-should-retry") == "false" { + return false + } + + return res.StatusCode == http.StatusRequestTimeout || + res.StatusCode == http.StatusConflict || + res.StatusCode == http.StatusTooManyRequests || + res.StatusCode >= http.StatusInternalServerError +} + +func parseRetryAfterHeader(resp *http.Response) (time.Duration, bool) { + if resp == nil { + return 0, false + } + + type retryData struct { + header string + units time.Duration + + // custom is used when the regular algorithm failed and is optional. + // the returned duration is used verbatim (units is not applied). + custom func(string) (time.Duration, bool) + } + + nop := func(string) (time.Duration, bool) { return 0, false } + + // the headers are listed in order of preference + retries := []retryData{ + { + header: "Retry-After-Ms", + units: time.Millisecond, + custom: nop, + }, + { + header: "Retry-After", + units: time.Second, + + // retry-after values are expressed in either number of + // seconds or an HTTP-date indicating when to try again + custom: func(ra string) (time.Duration, bool) { + t, err := time.Parse(time.RFC1123, ra) + if err != nil { + return 0, false + } + return time.Until(t), true + }, + }, + } + + for _, retry := range retries { + v := resp.Header.Get(retry.header) + if v == "" { + continue + } + if retryAfter, err := strconv.ParseFloat(v, 64); err == nil { + return time.Duration(retryAfter * float64(retry.units)), true + } + if d, ok := retry.custom(v); ok { + return d, true + } + } + + return 0, false +} + +// isBeforeContextDeadline reports whether the non-zero Time t is +// before ctx's deadline. If ctx does not have a deadline, it +// always reports true (the deadline is considered infinite). +func isBeforeContextDeadline(t time.Time, ctx context.Context) bool { + d, ok := ctx.Deadline() + if !ok { + return true + } + return t.Before(d) +} + +// bodyWithTimeout is an io.ReadCloser which can observe a context's cancel func +// to handle timeouts etc. It wraps an existing io.ReadCloser. +type bodyWithTimeout struct { + stop func() // stops the time.Timer waiting to cancel the request + rc io.ReadCloser +} + +func (b *bodyWithTimeout) Read(p []byte) (n int, err error) { + n, err = b.rc.Read(p) + if err == nil { + return n, nil + } + if err == io.EOF { + return n, err + } + return n, err +} + +func (b *bodyWithTimeout) Close() error { + err := b.rc.Close() + b.stop() + return err +} + +func retryDelay(res *http.Response, retryCount int) time.Duration { + // If the API asks us to wait a certain amount of time (and it's a reasonable amount), + // just do what it says. + + if retryAfterDelay, ok := parseRetryAfterHeader(res); ok && 0 <= retryAfterDelay && retryAfterDelay < time.Minute { + return retryAfterDelay + } + + maxDelay := 8 * time.Second + delay := time.Duration(0.5 * float64(time.Second) * math.Pow(2, float64(retryCount))) + if delay > maxDelay { + delay = maxDelay + } + + jitter := rand.Int63n(int64(delay / 4)) + delay -= time.Duration(jitter) + return delay +} + +func (cfg *RequestConfig) Execute() (err error) { + if cfg.BaseURL == nil { + if cfg.DefaultBaseURL != nil { + cfg.BaseURL = cfg.DefaultBaseURL + } else { + return fmt.Errorf("requestconfig: base url is not set") + } + } + + cfg.Request.URL, err = cfg.BaseURL.Parse(strings.TrimLeft(cfg.Request.URL.String(), "/")) + if err != nil { + return err + } + + if cfg.Body != nil && cfg.Request.Body == nil { + switch body := cfg.Body.(type) { + case *bytes.Buffer: + b := body.Bytes() + cfg.Request.ContentLength = int64(body.Len()) + cfg.Request.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil } + cfg.Request.Body, _ = cfg.Request.GetBody() + case *bytes.Reader: + cfg.Request.ContentLength = int64(body.Len()) + cfg.Request.GetBody = func() (io.ReadCloser, error) { + _, err := body.Seek(0, 0) + return io.NopCloser(body), err + } + cfg.Request.Body, _ = cfg.Request.GetBody() + default: + if rc, ok := body.(io.ReadCloser); ok { + cfg.Request.Body = rc + } else { + cfg.Request.Body = io.NopCloser(body) + } + } + } + + handler := cfg.HTTPClient.Do + if cfg.CustomHTTPDoer != nil { + handler = cfg.CustomHTTPDoer.Do + } + for i := len(cfg.Middlewares) - 1; i >= 0; i -= 1 { + handler = applyMiddleware(cfg.Middlewares[i], handler) + } + + // Don't send the current retry count in the headers if the caller modified the header defaults. + shouldSendRetryCount := cfg.Request.Header.Get("X-Stainless-Retry-Count") == "0" + + var res *http.Response + var cancel context.CancelFunc + for retryCount := 0; retryCount <= cfg.MaxRetries; retryCount += 1 { + ctx := cfg.Request.Context() + if cfg.RequestTimeout != time.Duration(0) && isBeforeContextDeadline(time.Now().Add(cfg.RequestTimeout), ctx) { + ctx, cancel = context.WithTimeout(ctx, cfg.RequestTimeout) + defer func() { + // The cancel function is nil if it was handed off to be handled in a different scope. + if cancel != nil { + cancel() + } + }() + } + + req := cfg.Request.Clone(ctx) + if shouldSendRetryCount { + req.Header.Set("X-Stainless-Retry-Count", strconv.Itoa(retryCount)) + } + + res, err = handler(req) + if ctx != nil && ctx.Err() != nil { + return ctx.Err() + } + if !shouldRetry(cfg.Request, res) || retryCount >= cfg.MaxRetries { + break + } + + // Prepare next request and wait for the retry delay + if cfg.Request.GetBody != nil { + cfg.Request.Body, err = cfg.Request.GetBody() + if err != nil { + return err + } + } + + // Can't actually refresh the body, so we don't attempt to retry here + if cfg.Request.GetBody == nil && cfg.Request.Body != nil { + break + } + + time.Sleep(retryDelay(res, retryCount)) + } + + // Save *http.Response if it is requested to, even if there was an error making the request. This is + // useful in cases where you might want to debug by inspecting the response. Note that if err != nil, + // the response should be generally be empty, but there are edge cases. + if cfg.ResponseInto != nil { + *cfg.ResponseInto = res + } + if responseBodyInto, ok := cfg.ResponseBodyInto.(**http.Response); ok { + *responseBodyInto = res + } + + // If there was a connection error in the final request or any other transport error, + // return that early without trying to coerce into an APIError. + if err != nil { + return err + } + + if res.StatusCode >= 400 { + contents, err := io.ReadAll(res.Body) + res.Body.Close() + if err != nil { + return err + } + + // If there is an APIError, re-populate the response body so that debugging + // utilities can conveniently dump the response without issue. + res.Body = io.NopCloser(bytes.NewBuffer(contents)) + + // Load the contents into the error format if it is provided. + aerr := apierror.Error{Request: cfg.Request, Response: res, StatusCode: res.StatusCode} + err = aerr.UnmarshalJSON(contents) + if err != nil { + return err + } + return &aerr + } + + _, intoCustomResponseBody := cfg.ResponseBodyInto.(**http.Response) + if cfg.ResponseBodyInto == nil || intoCustomResponseBody { + // We aren't reading the response body in this scope, but whoever is will need the + // cancel func from the context to observe request timeouts. + // Put the cancel function in the response body so it can be handled elsewhere. + if cancel != nil { + res.Body = &bodyWithTimeout{rc: res.Body, stop: cancel} + cancel = nil + } + return nil + } + + contents, err := io.ReadAll(res.Body) + res.Body.Close() + if err != nil { + return fmt.Errorf("error reading response body: %w", err) + } + + // If we are not json, return plaintext + contentType := res.Header.Get("content-type") + mediaType, _, _ := mime.ParseMediaType(contentType) + isJSON := strings.Contains(mediaType, "application/json") || strings.HasSuffix(mediaType, "+json") + if !isJSON { + switch dst := cfg.ResponseBodyInto.(type) { + case *string: + *dst = string(contents) + case **string: + tmp := string(contents) + *dst = &tmp + case *[]byte: + *dst = contents + default: + return fmt.Errorf("expected destination type of 'string' or '[]byte' for responses with content-type '%s' that is not 'application/json'", contentType) + } + return nil + } + + switch dst := cfg.ResponseBodyInto.(type) { + // If the response happens to be a byte array, deserialize the body as-is. + case *[]byte: + *dst = contents + default: + err = json.NewDecoder(bytes.NewReader(contents)).Decode(cfg.ResponseBodyInto) + if err != nil { + return fmt.Errorf("error parsing response json: %w", err) + } + } + + return nil +} + +func ExecuteNewRequest(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...RequestOption) error { + cfg, err := NewRequestConfig(ctx, method, u, body, dst, opts...) + if err != nil { + return err + } + return cfg.Execute() +} + +func (cfg *RequestConfig) Clone(ctx context.Context) *RequestConfig { + if cfg == nil { + return nil + } + req := cfg.Request.Clone(ctx) + var err error + if req.Body != nil { + req.Body, err = req.GetBody() + } + if err != nil { + return nil + } + new := &RequestConfig{ + MaxRetries: cfg.MaxRetries, + RequestTimeout: cfg.RequestTimeout, + Context: ctx, + Request: req, + BaseURL: cfg.BaseURL, + HTTPClient: cfg.HTTPClient, + Middlewares: cfg.Middlewares, + } + + return new +} + +func (cfg *RequestConfig) Apply(opts ...RequestOption) error { + for _, opt := range opts { + err := opt.Apply(cfg) + if err != nil { + return err + } + } + return nil +} + +// PreRequestOptions is used to collect all the options which need to be known before +// a call to [RequestConfig.ExecuteNewRequest], such as path parameters +// or global defaults. +// PreRequestOptions will return a [RequestConfig] with the options applied. +// +// Only request option functions of type [PreRequestOptionFunc] are applied. +func PreRequestOptions(opts ...RequestOption) (RequestConfig, error) { + cfg := RequestConfig{} + for _, opt := range opts { + if opt, ok := opt.(PreRequestOptionFunc); ok { + err := opt.Apply(&cfg) + if err != nil { + return cfg, err + } + } + } + return cfg, nil +} + +// WithDefaultBaseURL returns a RequestOption that sets the client's default Base URL. +// This is always overridden by setting a base URL with WithBaseURL. +// WithBaseURL should be used instead of WithDefaultBaseURL except in internal code. +func WithDefaultBaseURL(baseURL string) RequestOption { + u, err := url.Parse(baseURL) + return RequestOptionFunc(func(r *RequestConfig) error { + if err != nil { + return err + } + r.DefaultBaseURL = u + return nil + }) +} diff --git a/packages/sdk/go/internal/testutil/testutil.go b/packages/sdk/go/internal/testutil/testutil.go new file mode 100644 index 000000000..826d266f2 --- /dev/null +++ b/packages/sdk/go/internal/testutil/testutil.go @@ -0,0 +1,27 @@ +package testutil + +import ( + "net/http" + "os" + "strconv" + "testing" +) + +func CheckTestServer(t *testing.T, url string) bool { + if _, err := http.Get(url); err != nil { + const SKIP_MOCK_TESTS = "SKIP_MOCK_TESTS" + if str, ok := os.LookupEnv(SKIP_MOCK_TESTS); ok { + skip, err := strconv.ParseBool(str) + if err != nil { + t.Fatalf("strconv.ParseBool(os.LookupEnv(%s)) failed: %s", SKIP_MOCK_TESTS, err) + } + if skip { + t.Skip("The test will not run without a mock Prism server running against your OpenAPI spec") + return false + } + t.Errorf("The test will not run without a mock Prism server running against your OpenAPI spec. You can set the environment variable %s to true to skip running any tests that require the mock server", SKIP_MOCK_TESTS) + return false + } + } + return true +} diff --git a/packages/sdk/go/internal/version.go b/packages/sdk/go/internal/version.go new file mode 100644 index 000000000..64dcebbb4 --- /dev/null +++ b/packages/sdk/go/internal/version.go @@ -0,0 +1,5 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package internal + +const PackageVersion = "0.1.0-alpha.8" // x-release-please-version diff --git a/packages/sdk/go/lib/.keep b/packages/sdk/go/lib/.keep new file mode 100644 index 000000000..5e2c99fdb --- /dev/null +++ b/packages/sdk/go/lib/.keep @@ -0,0 +1,4 @@ +File generated from our OpenAPI spec by Stainless. + +This directory can be used to store custom files to expand the SDK. +It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/packages/sdk/go/option/middleware.go b/packages/sdk/go/option/middleware.go new file mode 100644 index 000000000..8ec9dd60b --- /dev/null +++ b/packages/sdk/go/option/middleware.go @@ -0,0 +1,38 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package option + +import ( + "log" + "net/http" + "net/http/httputil" +) + +// WithDebugLog logs the HTTP request and response content. +// If the logger parameter is nil, it uses the default logger. +// +// WithDebugLog is for debugging and development purposes only. +// It should not be used in production code. The behavior and interface +// of WithDebugLog is not guaranteed to be stable. +func WithDebugLog(logger *log.Logger) RequestOption { + return WithMiddleware(func(req *http.Request, nxt MiddlewareNext) (*http.Response, error) { + if logger == nil { + logger = log.Default() + } + + if reqBytes, err := httputil.DumpRequest(req, true); err == nil { + logger.Printf("Request Content:\n%s\n", reqBytes) + } + + resp, err := nxt(req) + if err != nil { + return resp, err + } + + if respBytes, err := httputil.DumpResponse(resp, true); err == nil { + logger.Printf("Response Content:\n%s\n", respBytes) + } + + return resp, err + }) +} diff --git a/packages/sdk/go/option/requestoption.go b/packages/sdk/go/option/requestoption.go new file mode 100644 index 000000000..68478066b --- /dev/null +++ b/packages/sdk/go/option/requestoption.go @@ -0,0 +1,267 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package option + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/tidwall/sjson" +) + +// RequestOption is an option for the requests made by the opencode API Client +// which can be supplied to clients, services, and methods. You can read more about this functional +// options pattern in our [README]. +// +// [README]: https://pkg.go.dev/github.com/sst/opencode-sdk-go#readme-requestoptions +type RequestOption = requestconfig.RequestOption + +// WithBaseURL returns a RequestOption that sets the BaseURL for the client. +// +// For security reasons, ensure that the base URL is trusted. +func WithBaseURL(base string) RequestOption { + u, err := url.Parse(base) + if err == nil && u.Path != "" && !strings.HasSuffix(u.Path, "/") { + u.Path += "/" + } + + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + if err != nil { + return fmt.Errorf("requestoption: WithBaseURL failed to parse url %s", err) + } + + r.BaseURL = u + return nil + }) +} + +// HTTPClient is primarily used to describe an [*http.Client], but also +// supports custom implementations. +// +// For bespoke implementations, prefer using an [*http.Client] with a +// custom transport. See [http.RoundTripper] for further information. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// WithHTTPClient returns a RequestOption that changes the underlying http client used to make this +// request, which by default is [http.DefaultClient]. +// +// For custom uses cases, it is recommended to provide an [*http.Client] with a custom +// [http.RoundTripper] as its transport, rather than directly implementing [HTTPClient]. +func WithHTTPClient(client HTTPClient) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + if client == nil { + return fmt.Errorf("requestoption: custom http client cannot be nil") + } + + if c, ok := client.(*http.Client); ok { + // Prefer the native client if possible. + r.HTTPClient = c + r.CustomHTTPDoer = nil + } else { + r.CustomHTTPDoer = client + } + + return nil + }) +} + +// MiddlewareNext is a function which is called by a middleware to pass an HTTP request +// to the next stage in the middleware chain. +type MiddlewareNext = func(*http.Request) (*http.Response, error) + +// Middleware is a function which intercepts HTTP requests, processing or modifying +// them, and then passing the request to the next middleware or handler +// in the chain by calling the provided MiddlewareNext function. +type Middleware = func(*http.Request, MiddlewareNext) (*http.Response, error) + +// WithMiddleware returns a RequestOption that applies the given middleware +// to the requests made. Each middleware will execute in the order they were given. +func WithMiddleware(middlewares ...Middleware) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + r.Middlewares = append(r.Middlewares, middlewares...) + return nil + }) +} + +// WithMaxRetries returns a RequestOption that sets the maximum number of retries that the client +// attempts to make. When given 0, the client only makes one request. By +// default, the client retries two times. +// +// WithMaxRetries panics when retries is negative. +func WithMaxRetries(retries int) RequestOption { + if retries < 0 { + panic("option: cannot have fewer than 0 retries") + } + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + r.MaxRetries = retries + return nil + }) +} + +// WithHeader returns a RequestOption that sets the header value to the associated key. It overwrites +// any value if there was one already present. +func WithHeader(key, value string) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + r.Request.Header.Set(key, value) + return nil + }) +} + +// WithHeaderAdd returns a RequestOption that adds the header value to the associated key. It appends +// onto any existing values. +func WithHeaderAdd(key, value string) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + r.Request.Header.Add(key, value) + return nil + }) +} + +// WithHeaderDel returns a RequestOption that deletes the header value(s) associated with the given key. +func WithHeaderDel(key string) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + r.Request.Header.Del(key) + return nil + }) +} + +// WithQuery returns a RequestOption that sets the query value to the associated key. It overwrites +// any value if there was one already present. +func WithQuery(key, value string) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + query := r.Request.URL.Query() + query.Set(key, value) + r.Request.URL.RawQuery = query.Encode() + return nil + }) +} + +// WithQueryAdd returns a RequestOption that adds the query value to the associated key. It appends +// onto any existing values. +func WithQueryAdd(key, value string) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + query := r.Request.URL.Query() + query.Add(key, value) + r.Request.URL.RawQuery = query.Encode() + return nil + }) +} + +// WithQueryDel returns a RequestOption that deletes the query value(s) associated with the key. +func WithQueryDel(key string) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + query := r.Request.URL.Query() + query.Del(key) + r.Request.URL.RawQuery = query.Encode() + return nil + }) +} + +// WithJSONSet returns a RequestOption that sets the body's JSON value associated with the key. +// The key accepts a string as defined by the [sjson format]. +// +// [sjson format]: https://github.com/tidwall/sjson +func WithJSONSet(key string, value interface{}) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) { + var b []byte + + if r.Body == nil { + b, err = sjson.SetBytes(nil, key, value) + if err != nil { + return err + } + } else if buffer, ok := r.Body.(*bytes.Buffer); ok { + b = buffer.Bytes() + b, err = sjson.SetBytes(b, key, value) + if err != nil { + return err + } + } else { + return fmt.Errorf("cannot use WithJSONSet on a body that is not serialized as *bytes.Buffer") + } + + r.Body = bytes.NewBuffer(b) + return nil + }) +} + +// WithJSONDel returns a RequestOption that deletes the body's JSON value associated with the key. +// The key accepts a string as defined by the [sjson format]. +// +// [sjson format]: https://github.com/tidwall/sjson +func WithJSONDel(key string) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) { + if buffer, ok := r.Body.(*bytes.Buffer); ok { + b := buffer.Bytes() + b, err = sjson.DeleteBytes(b, key) + if err != nil { + return err + } + r.Body = bytes.NewBuffer(b) + return nil + } + + return fmt.Errorf("cannot use WithJSONDel on a body that is not serialized as *bytes.Buffer") + }) +} + +// WithResponseBodyInto returns a RequestOption that overwrites the deserialization target with +// the given destination. If provided, we don't deserialize into the default struct. +func WithResponseBodyInto(dst any) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + r.ResponseBodyInto = dst + return nil + }) +} + +// WithResponseInto returns a RequestOption that copies the [*http.Response] into the given address. +func WithResponseInto(dst **http.Response) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + r.ResponseInto = dst + return nil + }) +} + +// WithRequestBody returns a RequestOption that provides a custom serialized body with the given +// content type. +// +// body accepts an io.Reader or raw []bytes. +func WithRequestBody(contentType string, body any) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + if reader, ok := body.(io.Reader); ok { + r.Body = reader + return r.Apply(WithHeader("Content-Type", contentType)) + } + + if b, ok := body.([]byte); ok { + r.Body = bytes.NewBuffer(b) + return r.Apply(WithHeader("Content-Type", contentType)) + } + + return fmt.Errorf("body must be a byte slice or implement io.Reader") + }) +} + +// WithRequestTimeout returns a RequestOption that sets the timeout for +// each request attempt. This should be smaller than the timeout defined in +// the context, which spans all retries. +func WithRequestTimeout(dur time.Duration) RequestOption { + return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error { + r.RequestTimeout = dur + return nil + }) +} + +// WithEnvironmentProduction returns a RequestOption that sets the current +// environment to be the "production" environment. An environment specifies which base URL +// to use by default. +func WithEnvironmentProduction() RequestOption { + return requestconfig.WithDefaultBaseURL("http://localhost:54321/") +} diff --git a/packages/sdk/go/packages/ssestream/ssestream.go b/packages/sdk/go/packages/ssestream/ssestream.go new file mode 100644 index 000000000..cc0afb7b1 --- /dev/null +++ b/packages/sdk/go/packages/ssestream/ssestream.go @@ -0,0 +1,181 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package ssestream + +import ( + "bufio" + "bytes" + "encoding/json" + "io" + "net/http" + "strings" +) + +type Decoder interface { + Event() Event + Next() bool + Close() error + Err() error +} + +func NewDecoder(res *http.Response) Decoder { + if res == nil || res.Body == nil { + return nil + } + + var decoder Decoder + contentType := res.Header.Get("content-type") + if t, ok := decoderTypes[contentType]; ok { + decoder = t(res.Body) + } else { + scn := bufio.NewScanner(res.Body) + scn.Buffer(nil, bufio.MaxScanTokenSize<<9) + decoder = &eventStreamDecoder{rc: res.Body, scn: scn} + } + return decoder +} + +var decoderTypes = map[string](func(io.ReadCloser) Decoder){} + +func RegisterDecoder(contentType string, decoder func(io.ReadCloser) Decoder) { + decoderTypes[strings.ToLower(contentType)] = decoder +} + +type Event struct { + Type string + Data []byte +} + +// A base implementation of a Decoder for text/event-stream. +type eventStreamDecoder struct { + evt Event + rc io.ReadCloser + scn *bufio.Scanner + err error +} + +func (s *eventStreamDecoder) Next() bool { + if s.err != nil { + return false + } + + event := "" + data := bytes.NewBuffer(nil) + + for s.scn.Scan() { + txt := s.scn.Bytes() + + // Dispatch event on an empty line + if len(txt) == 0 { + s.evt = Event{ + Type: event, + Data: data.Bytes(), + } + return true + } + + // Split a string like "event: bar" into name="event" and value=" bar". + name, value, _ := bytes.Cut(txt, []byte(":")) + + // Consume an optional space after the colon if it exists. + if len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + + switch string(name) { + case "": + // An empty line in the for ": something" is a comment and should be ignored. + continue + case "event": + event = string(value) + case "data": + _, s.err = data.Write(value) + if s.err != nil { + break + } + _, s.err = data.WriteRune('\n') + if s.err != nil { + break + } + } + } + + if s.scn.Err() != nil { + s.err = s.scn.Err() + } + + return false +} + +func (s *eventStreamDecoder) Event() Event { + return s.evt +} + +func (s *eventStreamDecoder) Close() error { + return s.rc.Close() +} + +func (s *eventStreamDecoder) Err() error { + return s.err +} + +type Stream[T any] struct { + decoder Decoder + cur T + err error +} + +func NewStream[T any](decoder Decoder, err error) *Stream[T] { + return &Stream[T]{ + decoder: decoder, + err: err, + } +} + +// Next returns false if the stream has ended or an error occurred. +// Call Stream.Current() to get the current value. +// Call Stream.Err() to get the error. +// +// for stream.Next() { +// data := stream.Current() +// } +// +// if stream.Err() != nil { +// ... +// } +func (s *Stream[T]) Next() bool { + if s.err != nil { + return false + } + + for s.decoder.Next() { + var nxt T + s.err = json.Unmarshal(s.decoder.Event().Data, &nxt) + if s.err != nil { + return false + } + s.cur = nxt + return true + } + + // decoder.Next() may be false because of an error + s.err = s.decoder.Err() + + return false +} + +func (s *Stream[T]) Current() T { + return s.cur +} + +func (s *Stream[T]) Err() error { + return s.err +} + +func (s *Stream[T]) Close() error { + if s.decoder == nil { + // already closed + return nil + } + return s.decoder.Close() +} diff --git a/packages/sdk/go/release-please-config.json b/packages/sdk/go/release-please-config.json new file mode 100644 index 000000000..a38198eca --- /dev/null +++ b/packages/sdk/go/release-please-config.json @@ -0,0 +1,67 @@ +{ + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "include-v-in-tag": true, + "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "pull-request-header": "Automated Release PR", + "pull-request-title-pattern": "release: ${version}", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "build", + "section": "Build System" + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "release-type": "go", + "extra-files": [ + "internal/version.go", + "README.md" + ] +} \ No newline at end of file diff --git a/packages/sdk/go/scripts/bootstrap b/packages/sdk/go/scripts/bootstrap new file mode 100755 index 000000000..d6ac16540 --- /dev/null +++ b/packages/sdk/go/scripts/bootstrap @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then + brew bundle check >/dev/null 2>&1 || { + echo "==> Installing Homebrew dependencies…" + brew bundle + } +fi + +echo "==> Installing Go dependencies…" + +go mod tidy -e diff --git a/packages/sdk/go/scripts/format b/packages/sdk/go/scripts/format new file mode 100755 index 000000000..db2a3fa29 --- /dev/null +++ b/packages/sdk/go/scripts/format @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running gofmt -s -w" +gofmt -s -w . diff --git a/packages/sdk/go/scripts/lint b/packages/sdk/go/scripts/lint new file mode 100755 index 000000000..7e03a7beb --- /dev/null +++ b/packages/sdk/go/scripts/lint @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +echo "==> Running Go build" +go build ./... + +echo "==> Checking tests compile" +go test -run=^$ ./... diff --git a/packages/sdk/go/scripts/mock b/packages/sdk/go/scripts/mock new file mode 100755 index 000000000..d2814ae6a --- /dev/null +++ b/packages/sdk/go/scripts/mock @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +if [[ -n "$1" && "$1" != '--'* ]]; then + URL="$1" + shift +else + URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" +fi + +# Check if the URL is empty +if [ -z "$URL" ]; then + echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" + exit 1 +fi + +echo "==> Starting mock server with URL ${URL}" + +# Run prism mock on the given spec +if [ "$1" == "--daemon" ]; then + npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & + + # Wait for server to come online + echo -n "Waiting for server" + while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + echo -n "." + sleep 0.1 + done + + if grep -q "✖ fatal" ".prism.log"; then + cat .prism.log + exit 1 + fi + + echo +else + npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" +fi diff --git a/packages/sdk/go/scripts/test b/packages/sdk/go/scripts/test new file mode 100755 index 000000000..efebceaee --- /dev/null +++ b/packages/sdk/go/scripts/test @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +function prism_is_running() { + curl --silent "http://localhost:4010" >/dev/null 2>&1 +} + +kill_server_on_port() { + pids=$(lsof -t -i tcp:"$1" || echo "") + if [ "$pids" != "" ]; then + kill "$pids" + echo "Stopped $pids." + fi +} + +function is_overriding_api_base_url() { + [ -n "$TEST_API_BASE_URL" ] +} + +if ! is_overriding_api_base_url && ! prism_is_running ; then + # When we exit this script, make sure to kill the background mock server process + trap 'kill_server_on_port 4010' EXIT + + # Start the dev server + ./scripts/mock --daemon +fi + +if is_overriding_api_base_url ; then + echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" + echo +elif ! prism_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" + echo -e "running against your OpenAPI spec." + echo + echo -e "To run the server, pass in the path or url of your OpenAPI" + echo -e "spec to the prism command:" + echo + echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" + echo + + exit 1 +else + echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo +fi + +echo "==> Running tests" +go test ./... "$@" diff --git a/packages/sdk/go/session.go b/packages/sdk/go/session.go new file mode 100644 index 000000000..2598d51c6 --- /dev/null +++ b/packages/sdk/go/session.go @@ -0,0 +1,2117 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "context" + "errors" + "fmt" + "net/http" + "reflect" + + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/param" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" + "github.com/sst/opencode-sdk-go/shared" + "github.com/tidwall/gjson" +) + +// SessionService contains methods and other services that help with interacting +// with the opencode API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewSessionService] method instead. +type SessionService struct { + Options []option.RequestOption +} + +// NewSessionService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewSessionService(opts ...option.RequestOption) (r *SessionService) { + r = &SessionService{} + r.Options = opts + return +} + +// Create a new session +func (r *SessionService) New(ctx context.Context, opts ...option.RequestOption) (res *Session, err error) { + opts = append(r.Options[:], opts...) + path := "session" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) + return +} + +// List all sessions +func (r *SessionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Session, err error) { + opts = append(r.Options[:], opts...) + path := "session" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +// Delete a session and all its data +func (r *SessionService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (res *bool, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...) + return +} + +// Abort a session +func (r *SessionService) Abort(ctx context.Context, id string, opts ...option.RequestOption) (res *bool, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/abort", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) + return +} + +// Create and send a new message to a session +func (r *SessionService) Chat(ctx context.Context, id string, body SessionChatParams, opts ...option.RequestOption) (res *AssistantMessage, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/message", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// Analyze the app and create an AGENTS.md file +func (r *SessionService) Init(ctx context.Context, id string, body SessionInitParams, opts ...option.RequestOption) (res *bool, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/init", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// List messages for a session +func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/message", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +// Revert a message +func (r *SessionService) Revert(ctx context.Context, id string, body SessionRevertParams, opts ...option.RequestOption) (res *Session, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/revert", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// Share a session +func (r *SessionService) Share(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/share", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) + return +} + +// Summarize the session +func (r *SessionService) Summarize(ctx context.Context, id string, body SessionSummarizeParams, opts ...option.RequestOption) (res *bool, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/summarize", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// Restore all reverted messages +func (r *SessionService) Unrevert(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/unrevert", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) + return +} + +// Unshare the session +func (r *SessionService) Unshare(ctx context.Context, id string, opts ...option.RequestOption) (res *Session, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/share", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...) + return +} + +type AssistantMessage struct { + ID string `json:"id,required"` + Cost float64 `json:"cost,required"` + Mode string `json:"mode,required"` + ModelID string `json:"modelID,required"` + Path AssistantMessagePath `json:"path,required"` + ProviderID string `json:"providerID,required"` + Role AssistantMessageRole `json:"role,required"` + SessionID string `json:"sessionID,required"` + System []string `json:"system,required"` + Time AssistantMessageTime `json:"time,required"` + Tokens AssistantMessageTokens `json:"tokens,required"` + Error AssistantMessageError `json:"error"` + Summary bool `json:"summary"` + JSON assistantMessageJSON `json:"-"` +} + +// assistantMessageJSON contains the JSON metadata for the struct +// [AssistantMessage] +type assistantMessageJSON struct { + ID apijson.Field + Cost apijson.Field + Mode apijson.Field + ModelID apijson.Field + Path apijson.Field + ProviderID apijson.Field + Role apijson.Field + SessionID apijson.Field + System apijson.Field + Time apijson.Field + Tokens apijson.Field + Error apijson.Field + Summary apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AssistantMessage) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r assistantMessageJSON) RawJSON() string { + return r.raw +} + +func (r AssistantMessage) implementsMessage() {} + +type AssistantMessagePath struct { + Cwd string `json:"cwd,required"` + Root string `json:"root,required"` + JSON assistantMessagePathJSON `json:"-"` +} + +// assistantMessagePathJSON contains the JSON metadata for the struct +// [AssistantMessagePath] +type assistantMessagePathJSON struct { + Cwd apijson.Field + Root apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AssistantMessagePath) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r assistantMessagePathJSON) RawJSON() string { + return r.raw +} + +type AssistantMessageRole string + +const ( + AssistantMessageRoleAssistant AssistantMessageRole = "assistant" +) + +func (r AssistantMessageRole) IsKnown() bool { + switch r { + case AssistantMessageRoleAssistant: + return true + } + return false +} + +type AssistantMessageTime struct { + Created float64 `json:"created,required"` + Completed float64 `json:"completed"` + JSON assistantMessageTimeJSON `json:"-"` +} + +// assistantMessageTimeJSON contains the JSON metadata for the struct +// [AssistantMessageTime] +type assistantMessageTimeJSON struct { + Created apijson.Field + Completed apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AssistantMessageTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r assistantMessageTimeJSON) RawJSON() string { + return r.raw +} + +type AssistantMessageTokens struct { + Cache AssistantMessageTokensCache `json:"cache,required"` + Input float64 `json:"input,required"` + Output float64 `json:"output,required"` + Reasoning float64 `json:"reasoning,required"` + JSON assistantMessageTokensJSON `json:"-"` +} + +// assistantMessageTokensJSON contains the JSON metadata for the struct +// [AssistantMessageTokens] +type assistantMessageTokensJSON struct { + Cache apijson.Field + Input apijson.Field + Output apijson.Field + Reasoning apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AssistantMessageTokens) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r assistantMessageTokensJSON) RawJSON() string { + return r.raw +} + +type AssistantMessageTokensCache struct { + Read float64 `json:"read,required"` + Write float64 `json:"write,required"` + JSON assistantMessageTokensCacheJSON `json:"-"` +} + +// assistantMessageTokensCacheJSON contains the JSON metadata for the struct +// [AssistantMessageTokensCache] +type assistantMessageTokensCacheJSON struct { + Read apijson.Field + Write apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AssistantMessageTokensCache) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r assistantMessageTokensCacheJSON) RawJSON() string { + return r.raw +} + +type AssistantMessageError struct { + // This field can have the runtime type of [shared.ProviderAuthErrorData], + // [shared.UnknownErrorData], [interface{}]. + Data interface{} `json:"data,required"` + Name AssistantMessageErrorName `json:"name,required"` + JSON assistantMessageErrorJSON `json:"-"` + union AssistantMessageErrorUnion +} + +// assistantMessageErrorJSON contains the JSON metadata for the struct +// [AssistantMessageError] +type assistantMessageErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r assistantMessageErrorJSON) RawJSON() string { + return r.raw +} + +func (r *AssistantMessageError) UnmarshalJSON(data []byte) (err error) { + *r = AssistantMessageError{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [AssistantMessageErrorUnion] interface which you can cast to +// the specific types for more type safety. +// +// Possible runtime types of the union are [shared.ProviderAuthError], +// [shared.UnknownError], [AssistantMessageErrorMessageOutputLengthError], +// [shared.MessageAbortedError]. +func (r AssistantMessageError) AsUnion() AssistantMessageErrorUnion { + return r.union +} + +// Union satisfied by [shared.ProviderAuthError], [shared.UnknownError], +// [AssistantMessageErrorMessageOutputLengthError] or [shared.MessageAbortedError]. +type AssistantMessageErrorUnion interface { + ImplementsAssistantMessageError() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*AssistantMessageErrorUnion)(nil)).Elem(), + "name", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.ProviderAuthError{}), + DiscriminatorValue: "ProviderAuthError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.UnknownError{}), + DiscriminatorValue: "UnknownError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(AssistantMessageErrorMessageOutputLengthError{}), + DiscriminatorValue: "MessageOutputLengthError", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(shared.MessageAbortedError{}), + DiscriminatorValue: "MessageAbortedError", + }, + ) +} + +type AssistantMessageErrorMessageOutputLengthError struct { + Data interface{} `json:"data,required"` + Name AssistantMessageErrorMessageOutputLengthErrorName `json:"name,required"` + JSON assistantMessageErrorMessageOutputLengthErrorJSON `json:"-"` +} + +// assistantMessageErrorMessageOutputLengthErrorJSON contains the JSON metadata for +// the struct [AssistantMessageErrorMessageOutputLengthError] +type assistantMessageErrorMessageOutputLengthErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *AssistantMessageErrorMessageOutputLengthError) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r assistantMessageErrorMessageOutputLengthErrorJSON) RawJSON() string { + return r.raw +} + +func (r AssistantMessageErrorMessageOutputLengthError) ImplementsAssistantMessageError() {} + +type AssistantMessageErrorMessageOutputLengthErrorName string + +const ( + AssistantMessageErrorMessageOutputLengthErrorNameMessageOutputLengthError AssistantMessageErrorMessageOutputLengthErrorName = "MessageOutputLengthError" +) + +func (r AssistantMessageErrorMessageOutputLengthErrorName) IsKnown() bool { + switch r { + case AssistantMessageErrorMessageOutputLengthErrorNameMessageOutputLengthError: + return true + } + return false +} + +type AssistantMessageErrorName string + +const ( + AssistantMessageErrorNameProviderAuthError AssistantMessageErrorName = "ProviderAuthError" + AssistantMessageErrorNameUnknownError AssistantMessageErrorName = "UnknownError" + AssistantMessageErrorNameMessageOutputLengthError AssistantMessageErrorName = "MessageOutputLengthError" + AssistantMessageErrorNameMessageAbortedError AssistantMessageErrorName = "MessageAbortedError" +) + +func (r AssistantMessageErrorName) IsKnown() bool { + switch r { + case AssistantMessageErrorNameProviderAuthError, AssistantMessageErrorNameUnknownError, AssistantMessageErrorNameMessageOutputLengthError, AssistantMessageErrorNameMessageAbortedError: + return true + } + return false +} + +type FilePart struct { + ID string `json:"id,required"` + MessageID string `json:"messageID,required"` + Mime string `json:"mime,required"` + SessionID string `json:"sessionID,required"` + Type FilePartType `json:"type,required"` + URL string `json:"url,required"` + Filename string `json:"filename"` + Source FilePartSource `json:"source"` + JSON filePartJSON `json:"-"` +} + +// filePartJSON contains the JSON metadata for the struct [FilePart] +type filePartJSON struct { + ID apijson.Field + MessageID apijson.Field + Mime apijson.Field + SessionID apijson.Field + Type apijson.Field + URL apijson.Field + Filename apijson.Field + Source apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FilePart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r filePartJSON) RawJSON() string { + return r.raw +} + +func (r FilePart) implementsPart() {} + +type FilePartType string + +const ( + FilePartTypeFile FilePartType = "file" +) + +func (r FilePartType) IsKnown() bool { + switch r { + case FilePartTypeFile: + return true + } + return false +} + +type FilePartInputParam struct { + Mime param.Field[string] `json:"mime,required"` + Type param.Field[FilePartInputType] `json:"type,required"` + URL param.Field[string] `json:"url,required"` + ID param.Field[string] `json:"id"` + Filename param.Field[string] `json:"filename"` + Source param.Field[FilePartSourceUnionParam] `json:"source"` +} + +func (r FilePartInputParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +func (r FilePartInputParam) implementsSessionChatParamsPartUnion() {} + +type FilePartInputType string + +const ( + FilePartInputTypeFile FilePartInputType = "file" +) + +func (r FilePartInputType) IsKnown() bool { + switch r { + case FilePartInputTypeFile: + return true + } + return false +} + +type FilePartSource struct { + Path string `json:"path,required"` + Text FilePartSourceText `json:"text,required"` + Type FilePartSourceType `json:"type,required"` + Kind int64 `json:"kind"` + Name string `json:"name"` + // This field can have the runtime type of [SymbolSourceRange]. + Range interface{} `json:"range"` + JSON filePartSourceJSON `json:"-"` + union FilePartSourceUnion +} + +// filePartSourceJSON contains the JSON metadata for the struct [FilePartSource] +type filePartSourceJSON struct { + Path apijson.Field + Text apijson.Field + Type apijson.Field + Kind apijson.Field + Name apijson.Field + Range apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r filePartSourceJSON) RawJSON() string { + return r.raw +} + +func (r *FilePartSource) UnmarshalJSON(data []byte) (err error) { + *r = FilePartSource{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [FilePartSourceUnion] interface which you can cast to the +// specific types for more type safety. +// +// Possible runtime types of the union are [FileSource], [SymbolSource]. +func (r FilePartSource) AsUnion() FilePartSourceUnion { + return r.union +} + +// Union satisfied by [FileSource] or [SymbolSource]. +type FilePartSourceUnion interface { + implementsFilePartSource() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*FilePartSourceUnion)(nil)).Elem(), + "type", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(FileSource{}), + DiscriminatorValue: "file", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(SymbolSource{}), + DiscriminatorValue: "symbol", + }, + ) +} + +type FilePartSourceType string + +const ( + FilePartSourceTypeFile FilePartSourceType = "file" + FilePartSourceTypeSymbol FilePartSourceType = "symbol" +) + +func (r FilePartSourceType) IsKnown() bool { + switch r { + case FilePartSourceTypeFile, FilePartSourceTypeSymbol: + return true + } + return false +} + +type FilePartSourceParam struct { + Path param.Field[string] `json:"path,required"` + Text param.Field[FilePartSourceTextParam] `json:"text,required"` + Type param.Field[FilePartSourceType] `json:"type,required"` + Kind param.Field[int64] `json:"kind"` + Name param.Field[string] `json:"name"` + Range param.Field[interface{}] `json:"range"` +} + +func (r FilePartSourceParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +func (r FilePartSourceParam) implementsFilePartSourceUnionParam() {} + +// Satisfied by [FileSourceParam], [SymbolSourceParam], [FilePartSourceParam]. +type FilePartSourceUnionParam interface { + implementsFilePartSourceUnionParam() +} + +type FilePartSourceText struct { + End int64 `json:"end,required"` + Start int64 `json:"start,required"` + Value string `json:"value,required"` + JSON filePartSourceTextJSON `json:"-"` +} + +// filePartSourceTextJSON contains the JSON metadata for the struct +// [FilePartSourceText] +type filePartSourceTextJSON struct { + End apijson.Field + Start apijson.Field + Value apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FilePartSourceText) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r filePartSourceTextJSON) RawJSON() string { + return r.raw +} + +type FilePartSourceTextParam struct { + End param.Field[int64] `json:"end,required"` + Start param.Field[int64] `json:"start,required"` + Value param.Field[string] `json:"value,required"` +} + +func (r FilePartSourceTextParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type FileSource struct { + Path string `json:"path,required"` + Text FilePartSourceText `json:"text,required"` + Type FileSourceType `json:"type,required"` + JSON fileSourceJSON `json:"-"` +} + +// fileSourceJSON contains the JSON metadata for the struct [FileSource] +type fileSourceJSON struct { + Path apijson.Field + Text apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *FileSource) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r fileSourceJSON) RawJSON() string { + return r.raw +} + +func (r FileSource) implementsFilePartSource() {} + +type FileSourceType string + +const ( + FileSourceTypeFile FileSourceType = "file" +) + +func (r FileSourceType) IsKnown() bool { + switch r { + case FileSourceTypeFile: + return true + } + return false +} + +type FileSourceParam struct { + Path param.Field[string] `json:"path,required"` + Text param.Field[FilePartSourceTextParam] `json:"text,required"` + Type param.Field[FileSourceType] `json:"type,required"` +} + +func (r FileSourceParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +func (r FileSourceParam) implementsFilePartSourceUnionParam() {} + +type Message struct { + ID string `json:"id,required"` + Role MessageRole `json:"role,required"` + SessionID string `json:"sessionID,required"` + // This field can have the runtime type of [UserMessageTime], + // [AssistantMessageTime]. + Time interface{} `json:"time,required"` + Cost float64 `json:"cost"` + // This field can have the runtime type of [AssistantMessageError]. + Error interface{} `json:"error"` + Mode string `json:"mode"` + ModelID string `json:"modelID"` + // This field can have the runtime type of [AssistantMessagePath]. + Path interface{} `json:"path"` + ProviderID string `json:"providerID"` + Summary bool `json:"summary"` + // This field can have the runtime type of [[]string]. + System interface{} `json:"system"` + // This field can have the runtime type of [AssistantMessageTokens]. + Tokens interface{} `json:"tokens"` + JSON messageJSON `json:"-"` + union MessageUnion +} + +// messageJSON contains the JSON metadata for the struct [Message] +type messageJSON struct { + ID apijson.Field + Role apijson.Field + SessionID apijson.Field + Time apijson.Field + Cost apijson.Field + Error apijson.Field + Mode apijson.Field + ModelID apijson.Field + Path apijson.Field + ProviderID apijson.Field + Summary apijson.Field + System apijson.Field + Tokens apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r messageJSON) RawJSON() string { + return r.raw +} + +func (r *Message) UnmarshalJSON(data []byte) (err error) { + *r = Message{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [MessageUnion] interface which you can cast to the specific +// types for more type safety. +// +// Possible runtime types of the union are [UserMessage], [AssistantMessage]. +func (r Message) AsUnion() MessageUnion { + return r.union +} + +// Union satisfied by [UserMessage] or [AssistantMessage]. +type MessageUnion interface { + implementsMessage() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*MessageUnion)(nil)).Elem(), + "role", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(UserMessage{}), + DiscriminatorValue: "user", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(AssistantMessage{}), + DiscriminatorValue: "assistant", + }, + ) +} + +type MessageRole string + +const ( + MessageRoleUser MessageRole = "user" + MessageRoleAssistant MessageRole = "assistant" +) + +func (r MessageRole) IsKnown() bool { + switch r { + case MessageRoleUser, MessageRoleAssistant: + return true + } + return false +} + +type Part struct { + ID string `json:"id,required"` + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + Type PartType `json:"type,required"` + CallID string `json:"callID"` + Cost float64 `json:"cost"` + Filename string `json:"filename"` + // This field can have the runtime type of [[]string]. + Files interface{} `json:"files"` + Hash string `json:"hash"` + Mime string `json:"mime"` + Snapshot string `json:"snapshot"` + Source FilePartSource `json:"source"` + // This field can have the runtime type of [ToolPartState]. + State interface{} `json:"state"` + Synthetic bool `json:"synthetic"` + Text string `json:"text"` + // This field can have the runtime type of [TextPartTime]. + Time interface{} `json:"time"` + // This field can have the runtime type of [StepFinishPartTokens]. + Tokens interface{} `json:"tokens"` + Tool string `json:"tool"` + URL string `json:"url"` + JSON partJSON `json:"-"` + union PartUnion +} + +// partJSON contains the JSON metadata for the struct [Part] +type partJSON struct { + ID apijson.Field + MessageID apijson.Field + SessionID apijson.Field + Type apijson.Field + CallID apijson.Field + Cost apijson.Field + Filename apijson.Field + Files apijson.Field + Hash apijson.Field + Mime apijson.Field + Snapshot apijson.Field + Source apijson.Field + State apijson.Field + Synthetic apijson.Field + Text apijson.Field + Time apijson.Field + Tokens apijson.Field + Tool apijson.Field + URL apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r partJSON) RawJSON() string { + return r.raw +} + +func (r *Part) UnmarshalJSON(data []byte) (err error) { + *r = Part{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [PartUnion] interface which you can cast to the specific types +// for more type safety. +// +// Possible runtime types of the union are [TextPart], [FilePart], [ToolPart], +// [StepStartPart], [StepFinishPart], [SnapshotPart], [PartPatchPart]. +func (r Part) AsUnion() PartUnion { + return r.union +} + +// Union satisfied by [TextPart], [FilePart], [ToolPart], [StepStartPart], +// [StepFinishPart], [SnapshotPart] or [PartPatchPart]. +type PartUnion interface { + implementsPart() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*PartUnion)(nil)).Elem(), + "type", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(TextPart{}), + DiscriminatorValue: "text", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(FilePart{}), + DiscriminatorValue: "file", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ToolPart{}), + DiscriminatorValue: "tool", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(StepStartPart{}), + DiscriminatorValue: "step-start", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(StepFinishPart{}), + DiscriminatorValue: "step-finish", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(SnapshotPart{}), + DiscriminatorValue: "snapshot", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(PartPatchPart{}), + DiscriminatorValue: "patch", + }, + ) +} + +type PartPatchPart struct { + ID string `json:"id,required"` + Files []string `json:"files,required"` + Hash string `json:"hash,required"` + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + Type PartPatchPartType `json:"type,required"` + JSON partPatchPartJSON `json:"-"` +} + +// partPatchPartJSON contains the JSON metadata for the struct [PartPatchPart] +type partPatchPartJSON struct { + ID apijson.Field + Files apijson.Field + Hash apijson.Field + MessageID apijson.Field + SessionID apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *PartPatchPart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r partPatchPartJSON) RawJSON() string { + return r.raw +} + +func (r PartPatchPart) implementsPart() {} + +type PartPatchPartType string + +const ( + PartPatchPartTypePatch PartPatchPartType = "patch" +) + +func (r PartPatchPartType) IsKnown() bool { + switch r { + case PartPatchPartTypePatch: + return true + } + return false +} + +type PartType string + +const ( + PartTypeText PartType = "text" + PartTypeFile PartType = "file" + PartTypeTool PartType = "tool" + PartTypeStepStart PartType = "step-start" + PartTypeStepFinish PartType = "step-finish" + PartTypeSnapshot PartType = "snapshot" + PartTypePatch PartType = "patch" +) + +func (r PartType) IsKnown() bool { + switch r { + case PartTypeText, PartTypeFile, PartTypeTool, PartTypeStepStart, PartTypeStepFinish, PartTypeSnapshot, PartTypePatch: + return true + } + return false +} + +type Session struct { + ID string `json:"id,required"` + Time SessionTime `json:"time,required"` + Title string `json:"title,required"` + Version string `json:"version,required"` + ParentID string `json:"parentID"` + Revert SessionRevert `json:"revert"` + Share SessionShare `json:"share"` + JSON sessionJSON `json:"-"` +} + +// sessionJSON contains the JSON metadata for the struct [Session] +type sessionJSON struct { + ID apijson.Field + Time apijson.Field + Title apijson.Field + Version apijson.Field + ParentID apijson.Field + Revert apijson.Field + Share apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *Session) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r sessionJSON) RawJSON() string { + return r.raw +} + +type SessionTime struct { + Created float64 `json:"created,required"` + Updated float64 `json:"updated,required"` + JSON sessionTimeJSON `json:"-"` +} + +// sessionTimeJSON contains the JSON metadata for the struct [SessionTime] +type sessionTimeJSON struct { + Created apijson.Field + Updated apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SessionTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r sessionTimeJSON) RawJSON() string { + return r.raw +} + +type SessionRevert struct { + MessageID string `json:"messageID,required"` + Diff string `json:"diff"` + PartID string `json:"partID"` + Snapshot string `json:"snapshot"` + JSON sessionRevertJSON `json:"-"` +} + +// sessionRevertJSON contains the JSON metadata for the struct [SessionRevert] +type sessionRevertJSON struct { + MessageID apijson.Field + Diff apijson.Field + PartID apijson.Field + Snapshot apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SessionRevert) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r sessionRevertJSON) RawJSON() string { + return r.raw +} + +type SessionShare struct { + URL string `json:"url,required"` + JSON sessionShareJSON `json:"-"` +} + +// sessionShareJSON contains the JSON metadata for the struct [SessionShare] +type sessionShareJSON struct { + URL apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SessionShare) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r sessionShareJSON) RawJSON() string { + return r.raw +} + +type SnapshotPart struct { + ID string `json:"id,required"` + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + Snapshot string `json:"snapshot,required"` + Type SnapshotPartType `json:"type,required"` + JSON snapshotPartJSON `json:"-"` +} + +// snapshotPartJSON contains the JSON metadata for the struct [SnapshotPart] +type snapshotPartJSON struct { + ID apijson.Field + MessageID apijson.Field + SessionID apijson.Field + Snapshot apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SnapshotPart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r snapshotPartJSON) RawJSON() string { + return r.raw +} + +func (r SnapshotPart) implementsPart() {} + +type SnapshotPartType string + +const ( + SnapshotPartTypeSnapshot SnapshotPartType = "snapshot" +) + +func (r SnapshotPartType) IsKnown() bool { + switch r { + case SnapshotPartTypeSnapshot: + return true + } + return false +} + +type StepFinishPart struct { + ID string `json:"id,required"` + Cost float64 `json:"cost,required"` + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + Tokens StepFinishPartTokens `json:"tokens,required"` + Type StepFinishPartType `json:"type,required"` + JSON stepFinishPartJSON `json:"-"` +} + +// stepFinishPartJSON contains the JSON metadata for the struct [StepFinishPart] +type stepFinishPartJSON struct { + ID apijson.Field + Cost apijson.Field + MessageID apijson.Field + SessionID apijson.Field + Tokens apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *StepFinishPart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r stepFinishPartJSON) RawJSON() string { + return r.raw +} + +func (r StepFinishPart) implementsPart() {} + +type StepFinishPartTokens struct { + Cache StepFinishPartTokensCache `json:"cache,required"` + Input float64 `json:"input,required"` + Output float64 `json:"output,required"` + Reasoning float64 `json:"reasoning,required"` + JSON stepFinishPartTokensJSON `json:"-"` +} + +// stepFinishPartTokensJSON contains the JSON metadata for the struct +// [StepFinishPartTokens] +type stepFinishPartTokensJSON struct { + Cache apijson.Field + Input apijson.Field + Output apijson.Field + Reasoning apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *StepFinishPartTokens) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r stepFinishPartTokensJSON) RawJSON() string { + return r.raw +} + +type StepFinishPartTokensCache struct { + Read float64 `json:"read,required"` + Write float64 `json:"write,required"` + JSON stepFinishPartTokensCacheJSON `json:"-"` +} + +// stepFinishPartTokensCacheJSON contains the JSON metadata for the struct +// [StepFinishPartTokensCache] +type stepFinishPartTokensCacheJSON struct { + Read apijson.Field + Write apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *StepFinishPartTokensCache) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r stepFinishPartTokensCacheJSON) RawJSON() string { + return r.raw +} + +type StepFinishPartType string + +const ( + StepFinishPartTypeStepFinish StepFinishPartType = "step-finish" +) + +func (r StepFinishPartType) IsKnown() bool { + switch r { + case StepFinishPartTypeStepFinish: + return true + } + return false +} + +type StepStartPart struct { + ID string `json:"id,required"` + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + Type StepStartPartType `json:"type,required"` + JSON stepStartPartJSON `json:"-"` +} + +// stepStartPartJSON contains the JSON metadata for the struct [StepStartPart] +type stepStartPartJSON struct { + ID apijson.Field + MessageID apijson.Field + SessionID apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *StepStartPart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r stepStartPartJSON) RawJSON() string { + return r.raw +} + +func (r StepStartPart) implementsPart() {} + +type StepStartPartType string + +const ( + StepStartPartTypeStepStart StepStartPartType = "step-start" +) + +func (r StepStartPartType) IsKnown() bool { + switch r { + case StepStartPartTypeStepStart: + return true + } + return false +} + +type SymbolSource struct { + Kind int64 `json:"kind,required"` + Name string `json:"name,required"` + Path string `json:"path,required"` + Range SymbolSourceRange `json:"range,required"` + Text FilePartSourceText `json:"text,required"` + Type SymbolSourceType `json:"type,required"` + JSON symbolSourceJSON `json:"-"` +} + +// symbolSourceJSON contains the JSON metadata for the struct [SymbolSource] +type symbolSourceJSON struct { + Kind apijson.Field + Name apijson.Field + Path apijson.Field + Range apijson.Field + Text apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolSource) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolSourceJSON) RawJSON() string { + return r.raw +} + +func (r SymbolSource) implementsFilePartSource() {} + +type SymbolSourceRange struct { + End SymbolSourceRangeEnd `json:"end,required"` + Start SymbolSourceRangeStart `json:"start,required"` + JSON symbolSourceRangeJSON `json:"-"` +} + +// symbolSourceRangeJSON contains the JSON metadata for the struct +// [SymbolSourceRange] +type symbolSourceRangeJSON struct { + End apijson.Field + Start apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolSourceRange) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolSourceRangeJSON) RawJSON() string { + return r.raw +} + +type SymbolSourceRangeEnd struct { + Character float64 `json:"character,required"` + Line float64 `json:"line,required"` + JSON symbolSourceRangeEndJSON `json:"-"` +} + +// symbolSourceRangeEndJSON contains the JSON metadata for the struct +// [SymbolSourceRangeEnd] +type symbolSourceRangeEndJSON struct { + Character apijson.Field + Line apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolSourceRangeEnd) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolSourceRangeEndJSON) RawJSON() string { + return r.raw +} + +type SymbolSourceRangeStart struct { + Character float64 `json:"character,required"` + Line float64 `json:"line,required"` + JSON symbolSourceRangeStartJSON `json:"-"` +} + +// symbolSourceRangeStartJSON contains the JSON metadata for the struct +// [SymbolSourceRangeStart] +type symbolSourceRangeStartJSON struct { + Character apijson.Field + Line apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SymbolSourceRangeStart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r symbolSourceRangeStartJSON) RawJSON() string { + return r.raw +} + +type SymbolSourceType string + +const ( + SymbolSourceTypeSymbol SymbolSourceType = "symbol" +) + +func (r SymbolSourceType) IsKnown() bool { + switch r { + case SymbolSourceTypeSymbol: + return true + } + return false +} + +type SymbolSourceParam struct { + Kind param.Field[int64] `json:"kind,required"` + Name param.Field[string] `json:"name,required"` + Path param.Field[string] `json:"path,required"` + Range param.Field[SymbolSourceRangeParam] `json:"range,required"` + Text param.Field[FilePartSourceTextParam] `json:"text,required"` + Type param.Field[SymbolSourceType] `json:"type,required"` +} + +func (r SymbolSourceParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +func (r SymbolSourceParam) implementsFilePartSourceUnionParam() {} + +type SymbolSourceRangeParam struct { + End param.Field[SymbolSourceRangeEndParam] `json:"end,required"` + Start param.Field[SymbolSourceRangeStartParam] `json:"start,required"` +} + +func (r SymbolSourceRangeParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type SymbolSourceRangeEndParam struct { + Character param.Field[float64] `json:"character,required"` + Line param.Field[float64] `json:"line,required"` +} + +func (r SymbolSourceRangeEndParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type SymbolSourceRangeStartParam struct { + Character param.Field[float64] `json:"character,required"` + Line param.Field[float64] `json:"line,required"` +} + +func (r SymbolSourceRangeStartParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type TextPart struct { + ID string `json:"id,required"` + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + Text string `json:"text,required"` + Type TextPartType `json:"type,required"` + Synthetic bool `json:"synthetic"` + Time TextPartTime `json:"time"` + JSON textPartJSON `json:"-"` +} + +// textPartJSON contains the JSON metadata for the struct [TextPart] +type textPartJSON struct { + ID apijson.Field + MessageID apijson.Field + SessionID apijson.Field + Text apijson.Field + Type apijson.Field + Synthetic apijson.Field + Time apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *TextPart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r textPartJSON) RawJSON() string { + return r.raw +} + +func (r TextPart) implementsPart() {} + +type TextPartType string + +const ( + TextPartTypeText TextPartType = "text" +) + +func (r TextPartType) IsKnown() bool { + switch r { + case TextPartTypeText: + return true + } + return false +} + +type TextPartTime struct { + Start float64 `json:"start,required"` + End float64 `json:"end"` + JSON textPartTimeJSON `json:"-"` +} + +// textPartTimeJSON contains the JSON metadata for the struct [TextPartTime] +type textPartTimeJSON struct { + Start apijson.Field + End apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *TextPartTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r textPartTimeJSON) RawJSON() string { + return r.raw +} + +type TextPartInputParam struct { + Text param.Field[string] `json:"text,required"` + Type param.Field[TextPartInputType] `json:"type,required"` + ID param.Field[string] `json:"id"` + Synthetic param.Field[bool] `json:"synthetic"` + Time param.Field[TextPartInputTimeParam] `json:"time"` +} + +func (r TextPartInputParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +func (r TextPartInputParam) implementsSessionChatParamsPartUnion() {} + +type TextPartInputType string + +const ( + TextPartInputTypeText TextPartInputType = "text" +) + +func (r TextPartInputType) IsKnown() bool { + switch r { + case TextPartInputTypeText: + return true + } + return false +} + +type TextPartInputTimeParam struct { + Start param.Field[float64] `json:"start,required"` + End param.Field[float64] `json:"end"` +} + +func (r TextPartInputTimeParam) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type ToolPart struct { + ID string `json:"id,required"` + CallID string `json:"callID,required"` + MessageID string `json:"messageID,required"` + SessionID string `json:"sessionID,required"` + State ToolPartState `json:"state,required"` + Tool string `json:"tool,required"` + Type ToolPartType `json:"type,required"` + JSON toolPartJSON `json:"-"` +} + +// toolPartJSON contains the JSON metadata for the struct [ToolPart] +type toolPartJSON struct { + ID apijson.Field + CallID apijson.Field + MessageID apijson.Field + SessionID apijson.Field + State apijson.Field + Tool apijson.Field + Type apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolPart) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolPartJSON) RawJSON() string { + return r.raw +} + +func (r ToolPart) implementsPart() {} + +type ToolPartState struct { + Status ToolPartStateStatus `json:"status,required"` + Error string `json:"error"` + // This field can have the runtime type of [interface{}], [map[string]interface{}]. + Input interface{} `json:"input"` + // This field can have the runtime type of [map[string]interface{}]. + Metadata interface{} `json:"metadata"` + Output string `json:"output"` + // This field can have the runtime type of [ToolStateRunningTime], + // [ToolStateCompletedTime], [ToolStateErrorTime]. + Time interface{} `json:"time"` + Title string `json:"title"` + JSON toolPartStateJSON `json:"-"` + union ToolPartStateUnion +} + +// toolPartStateJSON contains the JSON metadata for the struct [ToolPartState] +type toolPartStateJSON struct { + Status apijson.Field + Error apijson.Field + Input apijson.Field + Metadata apijson.Field + Output apijson.Field + Time apijson.Field + Title apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r toolPartStateJSON) RawJSON() string { + return r.raw +} + +func (r *ToolPartState) UnmarshalJSON(data []byte) (err error) { + *r = ToolPartState{} + err = apijson.UnmarshalRoot(data, &r.union) + if err != nil { + return err + } + return apijson.Port(r.union, &r) +} + +// AsUnion returns a [ToolPartStateUnion] interface which you can cast to the +// specific types for more type safety. +// +// Possible runtime types of the union are [ToolStatePending], [ToolStateRunning], +// [ToolStateCompleted], [ToolStateError]. +func (r ToolPartState) AsUnion() ToolPartStateUnion { + return r.union +} + +// Union satisfied by [ToolStatePending], [ToolStateRunning], [ToolStateCompleted] +// or [ToolStateError]. +type ToolPartStateUnion interface { + implementsToolPartState() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*ToolPartStateUnion)(nil)).Elem(), + "status", + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ToolStatePending{}), + DiscriminatorValue: "pending", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ToolStateRunning{}), + DiscriminatorValue: "running", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ToolStateCompleted{}), + DiscriminatorValue: "completed", + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(ToolStateError{}), + DiscriminatorValue: "error", + }, + ) +} + +type ToolPartStateStatus string + +const ( + ToolPartStateStatusPending ToolPartStateStatus = "pending" + ToolPartStateStatusRunning ToolPartStateStatus = "running" + ToolPartStateStatusCompleted ToolPartStateStatus = "completed" + ToolPartStateStatusError ToolPartStateStatus = "error" +) + +func (r ToolPartStateStatus) IsKnown() bool { + switch r { + case ToolPartStateStatusPending, ToolPartStateStatusRunning, ToolPartStateStatusCompleted, ToolPartStateStatusError: + return true + } + return false +} + +type ToolPartType string + +const ( + ToolPartTypeTool ToolPartType = "tool" +) + +func (r ToolPartType) IsKnown() bool { + switch r { + case ToolPartTypeTool: + return true + } + return false +} + +type ToolStateCompleted struct { + Input map[string]interface{} `json:"input,required"` + Metadata map[string]interface{} `json:"metadata,required"` + Output string `json:"output,required"` + Status ToolStateCompletedStatus `json:"status,required"` + Time ToolStateCompletedTime `json:"time,required"` + Title string `json:"title,required"` + JSON toolStateCompletedJSON `json:"-"` +} + +// toolStateCompletedJSON contains the JSON metadata for the struct +// [ToolStateCompleted] +type toolStateCompletedJSON struct { + Input apijson.Field + Metadata apijson.Field + Output apijson.Field + Status apijson.Field + Time apijson.Field + Title apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolStateCompleted) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolStateCompletedJSON) RawJSON() string { + return r.raw +} + +func (r ToolStateCompleted) implementsToolPartState() {} + +type ToolStateCompletedStatus string + +const ( + ToolStateCompletedStatusCompleted ToolStateCompletedStatus = "completed" +) + +func (r ToolStateCompletedStatus) IsKnown() bool { + switch r { + case ToolStateCompletedStatusCompleted: + return true + } + return false +} + +type ToolStateCompletedTime struct { + End float64 `json:"end,required"` + Start float64 `json:"start,required"` + JSON toolStateCompletedTimeJSON `json:"-"` +} + +// toolStateCompletedTimeJSON contains the JSON metadata for the struct +// [ToolStateCompletedTime] +type toolStateCompletedTimeJSON struct { + End apijson.Field + Start apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolStateCompletedTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolStateCompletedTimeJSON) RawJSON() string { + return r.raw +} + +type ToolStateError struct { + Error string `json:"error,required"` + Input map[string]interface{} `json:"input,required"` + Status ToolStateErrorStatus `json:"status,required"` + Time ToolStateErrorTime `json:"time,required"` + JSON toolStateErrorJSON `json:"-"` +} + +// toolStateErrorJSON contains the JSON metadata for the struct [ToolStateError] +type toolStateErrorJSON struct { + Error apijson.Field + Input apijson.Field + Status apijson.Field + Time apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolStateError) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolStateErrorJSON) RawJSON() string { + return r.raw +} + +func (r ToolStateError) implementsToolPartState() {} + +type ToolStateErrorStatus string + +const ( + ToolStateErrorStatusError ToolStateErrorStatus = "error" +) + +func (r ToolStateErrorStatus) IsKnown() bool { + switch r { + case ToolStateErrorStatusError: + return true + } + return false +} + +type ToolStateErrorTime struct { + End float64 `json:"end,required"` + Start float64 `json:"start,required"` + JSON toolStateErrorTimeJSON `json:"-"` +} + +// toolStateErrorTimeJSON contains the JSON metadata for the struct +// [ToolStateErrorTime] +type toolStateErrorTimeJSON struct { + End apijson.Field + Start apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolStateErrorTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolStateErrorTimeJSON) RawJSON() string { + return r.raw +} + +type ToolStatePending struct { + Status ToolStatePendingStatus `json:"status,required"` + JSON toolStatePendingJSON `json:"-"` +} + +// toolStatePendingJSON contains the JSON metadata for the struct +// [ToolStatePending] +type toolStatePendingJSON struct { + Status apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolStatePending) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolStatePendingJSON) RawJSON() string { + return r.raw +} + +func (r ToolStatePending) implementsToolPartState() {} + +type ToolStatePendingStatus string + +const ( + ToolStatePendingStatusPending ToolStatePendingStatus = "pending" +) + +func (r ToolStatePendingStatus) IsKnown() bool { + switch r { + case ToolStatePendingStatusPending: + return true + } + return false +} + +type ToolStateRunning struct { + Status ToolStateRunningStatus `json:"status,required"` + Time ToolStateRunningTime `json:"time,required"` + Input interface{} `json:"input"` + Metadata map[string]interface{} `json:"metadata"` + Title string `json:"title"` + JSON toolStateRunningJSON `json:"-"` +} + +// toolStateRunningJSON contains the JSON metadata for the struct +// [ToolStateRunning] +type toolStateRunningJSON struct { + Status apijson.Field + Time apijson.Field + Input apijson.Field + Metadata apijson.Field + Title apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolStateRunning) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolStateRunningJSON) RawJSON() string { + return r.raw +} + +func (r ToolStateRunning) implementsToolPartState() {} + +type ToolStateRunningStatus string + +const ( + ToolStateRunningStatusRunning ToolStateRunningStatus = "running" +) + +func (r ToolStateRunningStatus) IsKnown() bool { + switch r { + case ToolStateRunningStatusRunning: + return true + } + return false +} + +type ToolStateRunningTime struct { + Start float64 `json:"start,required"` + JSON toolStateRunningTimeJSON `json:"-"` +} + +// toolStateRunningTimeJSON contains the JSON metadata for the struct +// [ToolStateRunningTime] +type toolStateRunningTimeJSON struct { + Start apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ToolStateRunningTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r toolStateRunningTimeJSON) RawJSON() string { + return r.raw +} + +type UserMessage struct { + ID string `json:"id,required"` + Role UserMessageRole `json:"role,required"` + SessionID string `json:"sessionID,required"` + Time UserMessageTime `json:"time,required"` + JSON userMessageJSON `json:"-"` +} + +// userMessageJSON contains the JSON metadata for the struct [UserMessage] +type userMessageJSON struct { + ID apijson.Field + Role apijson.Field + SessionID apijson.Field + Time apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *UserMessage) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r userMessageJSON) RawJSON() string { + return r.raw +} + +func (r UserMessage) implementsMessage() {} + +type UserMessageRole string + +const ( + UserMessageRoleUser UserMessageRole = "user" +) + +func (r UserMessageRole) IsKnown() bool { + switch r { + case UserMessageRoleUser: + return true + } + return false +} + +type UserMessageTime struct { + Created float64 `json:"created,required"` + JSON userMessageTimeJSON `json:"-"` +} + +// userMessageTimeJSON contains the JSON metadata for the struct [UserMessageTime] +type userMessageTimeJSON struct { + Created apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *UserMessageTime) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r userMessageTimeJSON) RawJSON() string { + return r.raw +} + +type SessionMessagesResponse struct { + Info Message `json:"info,required"` + Parts []Part `json:"parts,required"` + JSON sessionMessagesResponseJSON `json:"-"` +} + +// sessionMessagesResponseJSON contains the JSON metadata for the struct +// [SessionMessagesResponse] +type sessionMessagesResponseJSON struct { + Info apijson.Field + Parts apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SessionMessagesResponse) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r sessionMessagesResponseJSON) RawJSON() string { + return r.raw +} + +type SessionChatParams struct { + ModelID param.Field[string] `json:"modelID,required"` + Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"` + ProviderID param.Field[string] `json:"providerID,required"` + MessageID param.Field[string] `json:"messageID"` + Mode param.Field[string] `json:"mode"` + System param.Field[string] `json:"system"` + Tools param.Field[map[string]bool] `json:"tools"` +} + +func (r SessionChatParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type SessionChatParamsPart struct { + Type param.Field[SessionChatParamsPartsType] `json:"type,required"` + ID param.Field[string] `json:"id"` + Filename param.Field[string] `json:"filename"` + Mime param.Field[string] `json:"mime"` + Source param.Field[FilePartSourceUnionParam] `json:"source"` + Synthetic param.Field[bool] `json:"synthetic"` + Text param.Field[string] `json:"text"` + Time param.Field[interface{}] `json:"time"` + URL param.Field[string] `json:"url"` +} + +func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {} + +// Satisfied by [TextPartInputParam], [FilePartInputParam], +// [SessionChatParamsPart]. +type SessionChatParamsPartUnion interface { + implementsSessionChatParamsPartUnion() +} + +type SessionChatParamsPartsType string + +const ( + SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text" + SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file" +) + +func (r SessionChatParamsPartsType) IsKnown() bool { + switch r { + case SessionChatParamsPartsTypeText, SessionChatParamsPartsTypeFile: + return true + } + return false +} + +type SessionInitParams struct { + MessageID param.Field[string] `json:"messageID,required"` + ModelID param.Field[string] `json:"modelID,required"` + ProviderID param.Field[string] `json:"providerID,required"` +} + +func (r SessionInitParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type SessionRevertParams struct { + MessageID param.Field[string] `json:"messageID,required"` + PartID param.Field[string] `json:"partID"` +} + +func (r SessionRevertParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +type SessionSummarizeParams struct { + ModelID param.Field[string] `json:"modelID,required"` + ProviderID param.Field[string] `json:"providerID,required"` +} + +func (r SessionSummarizeParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} diff --git a/packages/sdk/go/session_test.go b/packages/sdk/go/session_test.go new file mode 100644 index 000000000..295e9e7ce --- /dev/null +++ b/packages/sdk/go/session_test.go @@ -0,0 +1,323 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" +) + +func TestSessionNew(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.New(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionList(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.List(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionDelete(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Delete(context.TODO(), "id") + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionAbort(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Abort(context.TODO(), "id") + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionChatWithOptionalParams(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Chat( + context.TODO(), + "id", + opencode.SessionChatParams{ + ModelID: opencode.F("modelID"), + Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.TextPartInputParam{ + Text: opencode.F("text"), + Type: opencode.F(opencode.TextPartInputTypeText), + ID: opencode.F("id"), + Synthetic: opencode.F(true), + Time: opencode.F(opencode.TextPartInputTimeParam{ + Start: opencode.F(0.000000), + End: opencode.F(0.000000), + }), + }}), + ProviderID: opencode.F("providerID"), + MessageID: opencode.F("msg"), + Mode: opencode.F("mode"), + System: opencode.F("system"), + Tools: opencode.F(map[string]bool{ + "foo": true, + }), + }, + ) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionInit(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Init( + context.TODO(), + "id", + opencode.SessionInitParams{ + MessageID: opencode.F("messageID"), + ModelID: opencode.F("modelID"), + ProviderID: opencode.F("providerID"), + }, + ) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionMessages(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Messages(context.TODO(), "id") + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionRevertWithOptionalParams(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Revert( + context.TODO(), + "id", + opencode.SessionRevertParams{ + MessageID: opencode.F("msg"), + PartID: opencode.F("prt"), + }, + ) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionShare(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Share(context.TODO(), "id") + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionSummarize(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Summarize( + context.TODO(), + "id", + opencode.SessionSummarizeParams{ + ModelID: opencode.F("modelID"), + ProviderID: opencode.F("providerID"), + }, + ) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionUnrevert(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Unrevert(context.TODO(), "id") + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestSessionUnshare(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Session.Unshare(context.TODO(), "id") + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/packages/sdk/go/shared/shared.go b/packages/sdk/go/shared/shared.go new file mode 100644 index 000000000..58baf3d9c --- /dev/null +++ b/packages/sdk/go/shared/shared.go @@ -0,0 +1,173 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package shared + +import ( + "github.com/sst/opencode-sdk-go/internal/apijson" +) + +type MessageAbortedError struct { + Data interface{} `json:"data,required"` + Name MessageAbortedErrorName `json:"name,required"` + JSON messageAbortedErrorJSON `json:"-"` +} + +// messageAbortedErrorJSON contains the JSON metadata for the struct +// [MessageAbortedError] +type messageAbortedErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *MessageAbortedError) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r messageAbortedErrorJSON) RawJSON() string { + return r.raw +} + +func (r MessageAbortedError) ImplementsEventListResponseEventSessionErrorPropertiesError() {} + +func (r MessageAbortedError) ImplementsAssistantMessageError() {} + +type MessageAbortedErrorName string + +const ( + MessageAbortedErrorNameMessageAbortedError MessageAbortedErrorName = "MessageAbortedError" +) + +func (r MessageAbortedErrorName) IsKnown() bool { + switch r { + case MessageAbortedErrorNameMessageAbortedError: + return true + } + return false +} + +type ProviderAuthError struct { + Data ProviderAuthErrorData `json:"data,required"` + Name ProviderAuthErrorName `json:"name,required"` + JSON providerAuthErrorJSON `json:"-"` +} + +// providerAuthErrorJSON contains the JSON metadata for the struct +// [ProviderAuthError] +type providerAuthErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ProviderAuthError) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r providerAuthErrorJSON) RawJSON() string { + return r.raw +} + +func (r ProviderAuthError) ImplementsEventListResponseEventSessionErrorPropertiesError() {} + +func (r ProviderAuthError) ImplementsAssistantMessageError() {} + +type ProviderAuthErrorData struct { + Message string `json:"message,required"` + ProviderID string `json:"providerID,required"` + JSON providerAuthErrorDataJSON `json:"-"` +} + +// providerAuthErrorDataJSON contains the JSON metadata for the struct +// [ProviderAuthErrorData] +type providerAuthErrorDataJSON struct { + Message apijson.Field + ProviderID apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *ProviderAuthErrorData) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r providerAuthErrorDataJSON) RawJSON() string { + return r.raw +} + +type ProviderAuthErrorName string + +const ( + ProviderAuthErrorNameProviderAuthError ProviderAuthErrorName = "ProviderAuthError" +) + +func (r ProviderAuthErrorName) IsKnown() bool { + switch r { + case ProviderAuthErrorNameProviderAuthError: + return true + } + return false +} + +type UnknownError struct { + Data UnknownErrorData `json:"data,required"` + Name UnknownErrorName `json:"name,required"` + JSON unknownErrorJSON `json:"-"` +} + +// unknownErrorJSON contains the JSON metadata for the struct [UnknownError] +type unknownErrorJSON struct { + Data apijson.Field + Name apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *UnknownError) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r unknownErrorJSON) RawJSON() string { + return r.raw +} + +func (r UnknownError) ImplementsEventListResponseEventSessionErrorPropertiesError() {} + +func (r UnknownError) ImplementsAssistantMessageError() {} + +type UnknownErrorData struct { + Message string `json:"message,required"` + JSON unknownErrorDataJSON `json:"-"` +} + +// unknownErrorDataJSON contains the JSON metadata for the struct +// [UnknownErrorData] +type unknownErrorDataJSON struct { + Message apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *UnknownErrorData) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r unknownErrorDataJSON) RawJSON() string { + return r.raw +} + +type UnknownErrorName string + +const ( + UnknownErrorNameUnknownError UnknownErrorName = "UnknownError" +) + +func (r UnknownErrorName) IsKnown() bool { + switch r { + case UnknownErrorNameUnknownError: + return true + } + return false +} diff --git a/packages/sdk/go/tui.go b/packages/sdk/go/tui.go new file mode 100644 index 000000000..d5243599f --- /dev/null +++ b/packages/sdk/go/tui.go @@ -0,0 +1,56 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode + +import ( + "context" + "net/http" + + "github.com/sst/opencode-sdk-go/internal/apijson" + "github.com/sst/opencode-sdk-go/internal/param" + "github.com/sst/opencode-sdk-go/internal/requestconfig" + "github.com/sst/opencode-sdk-go/option" +) + +// TuiService contains methods and other services that help with interacting with +// the opencode API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewTuiService] method instead. +type TuiService struct { + Options []option.RequestOption +} + +// NewTuiService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewTuiService(opts ...option.RequestOption) (r *TuiService) { + r = &TuiService{} + r.Options = opts + return +} + +// Append prompt to the TUI +func (r *TuiService) AppendPrompt(ctx context.Context, body TuiAppendPromptParams, opts ...option.RequestOption) (res *bool, err error) { + opts = append(r.Options[:], opts...) + path := "tui/append-prompt" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// Open the help dialog +func (r *TuiService) OpenHelp(ctx context.Context, opts ...option.RequestOption) (res *bool, err error) { + opts = append(r.Options[:], opts...) + path := "tui/open-help" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...) + return +} + +type TuiAppendPromptParams struct { + Text param.Field[string] `json:"text,required"` +} + +func (r TuiAppendPromptParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} diff --git a/packages/sdk/go/tui_test.go b/packages/sdk/go/tui_test.go new file mode 100644 index 000000000..5283f37c5 --- /dev/null +++ b/packages/sdk/go/tui_test.go @@ -0,0 +1,60 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" +) + +func TestTuiAppendPrompt(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Tui.AppendPrompt(context.TODO(), opencode.TuiAppendPromptParams{ + Text: opencode.F("text"), + }) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestTuiOpenHelp(t *testing.T) { + t.Skip("skipped: tests are disabled for the time being") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + _, err := client.Tui.OpenHelp(context.TODO()) + if err != nil { + var apierr *opencode.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/packages/sdk/go/usage_test.go b/packages/sdk/go/usage_test.go new file mode 100644 index 000000000..ef7ce8bde --- /dev/null +++ b/packages/sdk/go/usage_test.go @@ -0,0 +1,32 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package opencode_test + +import ( + "context" + "os" + "testing" + + "github.com/sst/opencode-sdk-go" + "github.com/sst/opencode-sdk-go/internal/testutil" + "github.com/sst/opencode-sdk-go/option" +) + +func TestUsage(t *testing.T) { + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := opencode.NewClient( + option.WithBaseURL(baseURL), + ) + sessions, err := client.Session.List(context.TODO()) + if err != nil { + t.Error(err) + return + } + t.Logf("%+v\n", sessions) +} diff --git a/packages/sdk/jest.config.ts b/packages/sdk/jest.config.ts deleted file mode 100644 index 5131990f8..000000000 --- a/packages/sdk/jest.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { JestConfigWithTsJest } from 'ts-jest'; - -const config: JestConfigWithTsJest = { - preset: 'ts-jest/presets/default-esm', - testEnvironment: 'node', - transform: { - '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], - }, - moduleNameMapper: { - '^@opencode-ai/sdk$': '/src/index.ts', - '^@opencode-ai/sdk/(.*)$': '/src/$1', - }, - modulePathIgnorePatterns: [ - '/ecosystem-tests/', - '/dist/', - '/deno/', - '/deno_tests/', - '/packages/', - ], - testPathIgnorePatterns: ['scripts'], -}; - -export default config; diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json new file mode 100644 index 000000000..502f2b7bb --- /dev/null +++ b/packages/sdk/js/package.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@opencode-ai/sdk", + "version": "0.0.0", + "type": "module", + "exports": { + ".": "./dist/index.js" + }, + "files": [ + "dist" + ], + "devDependencies": { + "typescript": "catalog:", + "@hey-api/openapi-ts": "0.80.1", + "@tsconfig/node22": "catalog:" + } +} diff --git a/packages/sdk/js/script/generate.ts b/packages/sdk/js/script/generate.ts new file mode 100755 index 000000000..3f864682e --- /dev/null +++ b/packages/sdk/js/script/generate.ts @@ -0,0 +1,41 @@ +#!/usr/bin/env bun + +const dir = new URL("..", import.meta.url).pathname +process.chdir(dir) + +import { $ } from "bun" +import fs from "fs/promises" +import path from "path" + +console.log("=== Generating JS SDK ===") +console.log() + +import { createClient } from "@hey-api/openapi-ts" + +await fs.rm(path.join(dir, "src/gen"), { recursive: true, force: true }) +await $`bun run ../../opencode/src/index.ts generate > openapi.json` + +await createClient({ + input: "./openapi.json", + output: "./src/gen", + plugins: [ + { + name: "@hey-api/typescript", + exportFromIndex: false, + }, + { + name: "@hey-api/sdk", + instance: "OpencodeClient", + exportFromIndex: false, + auth: false, + }, + { + name: "@hey-api/client-fetch", + exportFromIndex: false, + baseUrl: "http://localhost:4096", + }, + ], +}) + +await $`rm -rf dist` +await $`bun tsc` diff --git a/packages/sdk/js/script/publish.ts b/packages/sdk/js/script/publish.ts new file mode 100644 index 000000000..39407f633 --- /dev/null +++ b/packages/sdk/js/script/publish.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env bun + +const dir = new URL("..", import.meta.url).pathname +process.chdir(dir) + +import { $ } from "bun" + +const version = process.env["OPENCODE_VERSION"] +if (!version) { + throw new Error("OPENCODE_VERSION is required") +} + +await import("./generate") + +const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true" + +await $`bun pm version --allow-same-version --no-git-tag-version ${version}` +if (snapshot) { + await $`bun publish --tag snapshot` +} +if (!snapshot) { + await $`bun publish` +} +await $`bun pm version 0.0.0 --no-git-tag-version` diff --git a/packages/sdk/js/src/gen/client.gen.ts b/packages/sdk/js/src/gen/client.gen.ts new file mode 100644 index 000000000..3bb22b24a --- /dev/null +++ b/packages/sdk/js/src/gen/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'http://localhost:4096' +})); \ No newline at end of file diff --git a/packages/sdk/js/src/gen/client/client.ts b/packages/sdk/js/src/gen/client/client.ts new file mode 100644 index 000000000..89d1e3158 --- /dev/null +++ b/packages/sdk/js/src/gen/client/client.ts @@ -0,0 +1,195 @@ +import type { Client, Config, RequestOptions } from './types'; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors< + Request, + Response, + unknown, + RequestOptions + >(); + + const request: Client['request'] = async (options) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body && opts.bodySerializer) { + opts.body = opts.bodySerializer(opts.body); + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.body === '') { + opts.headers.delete('Content-Type'); + } + + const url = buildUrl(opts); + const requestInit: ReqInit = { + redirect: 'follow', + ...opts, + }; + + let request = new Request(url, requestInit); + + for (const fn of interceptors.request._fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + let response = await _fetch(request); + + for (const fn of interceptors.response._fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + if ( + response.status === 204 || + response.headers.get('Content-Length') === '0' + ) { + return opts.responseStyle === 'data' + ? {} + : { + data: {}, + ...result, + }; + } + + const parseAs = + (opts.parseAs === 'auto' + ? getParseAs(response.headers.get('Content-Type')) + : opts.parseAs) ?? 'json'; + + let data: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'json': + case 'text': + data = await response[parseAs](); + break; + case 'stream': + return opts.responseStyle === 'data' + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === 'data' + ? data + : { + data, + ...result, + }; + } + + const textError = await response.text(); + let jsonError: unknown; + + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } + + const error = jsonError ?? textError; + let finalError = error; + + for (const fn of interceptors.error._fns) { + if (fn) { + finalError = (await fn(error, response, request, opts)) as string; + } + } + + finalError = finalError || ({} as string); + + if (opts.throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return opts.responseStyle === 'data' + ? undefined + : { + error: finalError, + ...result, + }; + }; + + return { + buildUrl, + connect: (options) => request({ ...options, method: 'CONNECT' }), + delete: (options) => request({ ...options, method: 'DELETE' }), + get: (options) => request({ ...options, method: 'GET' }), + getConfig, + head: (options) => request({ ...options, method: 'HEAD' }), + interceptors, + options: (options) => request({ ...options, method: 'OPTIONS' }), + patch: (options) => request({ ...options, method: 'PATCH' }), + post: (options) => request({ ...options, method: 'POST' }), + put: (options) => request({ ...options, method: 'PUT' }), + request, + setConfig, + trace: (options) => request({ ...options, method: 'TRACE' }), + }; +}; diff --git a/packages/sdk/js/src/gen/client/index.ts b/packages/sdk/js/src/gen/client/index.ts new file mode 100644 index 000000000..5da1f7aee --- /dev/null +++ b/packages/sdk/js/src/gen/client/index.ts @@ -0,0 +1,22 @@ +export type { Auth } from '../core/auth'; +export type { QuerySerializerOptions } from '../core/bodySerializer'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer'; +export { buildClientParams } from '../core/params'; +export { createClient } from './client'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + ResponseStyle, + TDataShape, +} from './types'; +export { createConfig, mergeHeaders } from './utils'; diff --git a/packages/sdk/js/src/gen/client/types.ts b/packages/sdk/js/src/gen/client/types.ts new file mode 100644 index 000000000..85295df07 --- /dev/null +++ b/packages/sdk/js/src/gen/client/types.ts @@ -0,0 +1,222 @@ +import type { Auth } from '../core/auth'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types'; +import type { Middleware } from './utils'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, + CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: (request: Request) => ReturnType; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: + | 'arrayBuffer' + | 'auto' + | 'blob' + | 'formData' + | 'json' + | 'stream' + | 'text'; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }> { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record + ? TData[keyof TData] + : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? + | (TData extends Record + ? TData[keyof TData] + : TData) + | undefined + : ( + | { + data: TData extends Record + ? TData[keyof TData] + : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + } + ) & { + request: Request; + response: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'>, +) => RequestResult; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick>, 'method'>, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Options, +) => string; + +export type Client = CoreClient & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys< + RequestOptions, + 'body' | 'path' | 'query' | 'url' +> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'body' | 'headers' | 'url' + > & + TData + : OmitKeys, 'body' | 'url'> & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys< + RequestOptions, + 'headers' | 'url' + > & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & TData; diff --git a/packages/sdk/js/src/gen/client/utils.ts b/packages/sdk/js/src/gen/client/utils.ts new file mode 100644 index 000000000..a52e67292 --- /dev/null +++ b/packages/sdk/js/src/gen/client/utils.ts @@ -0,0 +1,417 @@ +import { getAuthToken } from '../core/auth'; +import type { + QuerySerializer, + QuerySerializerOptions, +} from '../core/bodySerializer'; +import { jsonBodySerializer } from '../core/bodySerializer'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer'; +import type { Client, ClientOptions, Config, RequestOptions } from './types'; + +interface PathSerializer { + path: Record; + url: string; +} + +const PATH_PARAM_RE = /\{[^{}]+\}/g; + +type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +type ArraySeparatorStyle = ArrayStyle | MatrixStyle; + +const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = ( + contentType: string | null, +): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if ( + cleanContent.startsWith('application/json') || + cleanContent.endsWith('+json') + ) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if ( + ['application/', 'audio/', 'image/', 'video/'].some((type) => + cleanContent.startsWith(type), + ) + ) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Headers; + }) => { + for (const auth of security) { + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + + return; + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => { + const url = getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header || typeof header !== 'object') { + continue; + } + + const iterator = + header instanceof Headers ? header.entries() : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set( + key, + typeof value === 'object' ? JSON.stringify(value) : (value as string), + ); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + response: Res, + request: Req, + options: Options, +) => Err | Promise; + +type ReqInterceptor = ( + request: Req, + options: Options, +) => Req | Promise; + +type ResInterceptor = ( + response: Res, + request: Req, + options: Options, +) => Res | Promise; + +class Interceptors { + _fns: (Interceptor | null)[]; + + constructor() { + this._fns = []; + } + + clear() { + this._fns = []; + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this._fns[id] ? id : -1; + } else { + return this._fns.indexOf(id); + } + } + exists(id: number | Interceptor) { + const index = this.getInterceptorIndex(id); + return !!this._fns[index]; + } + + eject(id: number | Interceptor) { + const index = this.getInterceptorIndex(id); + if (this._fns[index]) { + this._fns[index] = null; + } + } + + update(id: number | Interceptor, fn: Interceptor) { + const index = this.getInterceptorIndex(id); + if (this._fns[index]) { + this._fns[index] = fn; + return id; + } else { + return false; + } + } + + use(fn: Interceptor) { + this._fns = [...this._fns, fn]; + return this._fns.length - 1; + } +} + +// `createInterceptors()` response, meant for external use as it does not +// expose internals +export interface Middleware { + error: Pick< + Interceptors>, + 'eject' | 'use' + >; + request: Pick>, 'eject' | 'use'>; + response: Pick< + Interceptors>, + 'eject' | 'use' + >; +} + +// do not add `Middleware` as return type so we can use _fns internally +export const createInterceptors = () => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/packages/sdk/js/src/gen/core/auth.ts b/packages/sdk/js/src/gen/core/auth.ts new file mode 100644 index 000000000..451c7f30f --- /dev/null +++ b/packages/sdk/js/src/gen/core/auth.ts @@ -0,0 +1,40 @@ +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/packages/sdk/js/src/gen/core/bodySerializer.ts b/packages/sdk/js/src/gen/core/bodySerializer.ts new file mode 100644 index 000000000..98ce7791f --- /dev/null +++ b/packages/sdk/js/src/gen/core/bodySerializer.ts @@ -0,0 +1,88 @@ +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/packages/sdk/js/src/gen/core/params.ts b/packages/sdk/js/src/gen/core/params.ts new file mode 100644 index 000000000..ba35263d8 --- /dev/null +++ b/packages/sdk/js/src/gen/core/params.ts @@ -0,0 +1,151 @@ +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/packages/sdk/js/src/gen/core/pathSerializer.ts b/packages/sdk/js/src/gen/core/pathSerializer.ts new file mode 100644 index 000000000..d692cf0a3 --- /dev/null +++ b/packages/sdk/js/src/gen/core/pathSerializer.ts @@ -0,0 +1,179 @@ +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/packages/sdk/js/src/gen/core/types.ts b/packages/sdk/js/src/gen/core/types.ts new file mode 100644 index 000000000..2dd4106fb --- /dev/null +++ b/packages/sdk/js/src/gen/core/types.ts @@ -0,0 +1,118 @@ +import type { Auth, AuthToken } from './auth'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer'; + +export interface Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, +> { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + connect: MethodFn; + delete: MethodFn; + get: MethodFn; + getConfig: () => Config; + head: MethodFn; + options: MethodFn; + patch: MethodFn; + post: MethodFn; + put: MethodFn; + request: RequestFn; + setConfig: (config: Config) => Config; + trace: MethodFn; +} + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: + | 'CONNECT' + | 'DELETE' + | 'GET' + | 'HEAD' + | 'OPTIONS' + | 'PATCH' + | 'POST' + | 'PUT' + | 'TRACE'; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts new file mode 100644 index 000000000..312084856 --- /dev/null +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -0,0 +1,339 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from './client'; +import type { EventSubscribeData, EventSubscribeResponses, AppGetData, AppGetResponses, AppInitData, AppInitResponses, ConfigGetData, ConfigGetResponses, SessionListData, SessionListResponses, SessionCreateData, SessionCreateResponses, SessionCreateErrors, SessionDeleteData, SessionDeleteResponses, SessionInitData, SessionInitResponses, SessionAbortData, SessionAbortResponses, SessionUnshareData, SessionUnshareResponses, SessionShareData, SessionShareResponses, SessionSummarizeData, SessionSummarizeResponses, SessionMessagesData, SessionMessagesResponses, SessionChatData, SessionChatResponses, SessionRevertData, SessionRevertResponses, SessionUnrevertData, SessionUnrevertResponses, ConfigProvidersData, ConfigProvidersResponses, FindTextData, FindTextResponses, FindFilesData, FindFilesResponses, FindSymbolsData, FindSymbolsResponses, FileReadData, FileReadResponses, FileStatusData, FileStatusResponses, AppLogData, AppLogResponses, AppModesData, AppModesResponses, TuiAppendPromptData, TuiAppendPromptResponses, TuiOpenHelpData, TuiOpenHelpResponses } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +class _HeyApiClient { + protected _client: Client = _heyApiClient; + + constructor(args?: { + client?: Client; + }) { + if (args?.client) { + this._client = args.client; + } + } +} + +class Event extends _HeyApiClient { + /** + * Get events + */ + public subscribe(options?: Options) { + return (options?.client ?? this._client).get({ + url: '/event', + ...options + }); + } +} + +class App extends _HeyApiClient { + /** + * Get app info + */ + public get(options?: Options) { + return (options?.client ?? this._client).get({ + url: '/app', + ...options + }); + } + + /** + * Initialize the app + */ + public init(options?: Options) { + return (options?.client ?? this._client).post({ + url: '/app/init', + ...options + }); + } + + /** + * Write a log entry to the server logs + */ + public log(options?: Options) { + return (options?.client ?? this._client).post({ + url: '/log', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); + } + + /** + * List all modes + */ + public modes(options?: Options) { + return (options?.client ?? this._client).get({ + url: '/mode', + ...options + }); + } +} + +class Config extends _HeyApiClient { + /** + * Get config info + */ + public get(options?: Options) { + return (options?.client ?? this._client).get({ + url: '/config', + ...options + }); + } + + /** + * List all providers + */ + public providers(options?: Options) { + return (options?.client ?? this._client).get({ + url: '/config/providers', + ...options + }); + } +} + +class Session extends _HeyApiClient { + /** + * List all sessions + */ + public list(options?: Options) { + return (options?.client ?? this._client).get({ + url: '/session', + ...options + }); + } + + /** + * Create a new session + */ + public create(options?: Options) { + return (options?.client ?? this._client).post({ + url: '/session', + ...options + }); + } + + /** + * Delete a session and all its data + */ + public delete(options: Options) { + return (options.client ?? this._client).delete({ + url: '/session/{id}', + ...options + }); + } + + /** + * Analyze the app and create an AGENTS.md file + */ + public init(options: Options) { + return (options.client ?? this._client).post({ + url: '/session/{id}/init', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Abort a session + */ + public abort(options: Options) { + return (options.client ?? this._client).post({ + url: '/session/{id}/abort', + ...options + }); + } + + /** + * Unshare the session + */ + public unshare(options: Options) { + return (options.client ?? this._client).delete({ + url: '/session/{id}/share', + ...options + }); + } + + /** + * Share a session + */ + public share(options: Options) { + return (options.client ?? this._client).post({ + url: '/session/{id}/share', + ...options + }); + } + + /** + * Summarize the session + */ + public summarize(options: Options) { + return (options.client ?? this._client).post({ + url: '/session/{id}/summarize', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * List messages for a session + */ + public messages(options: Options) { + return (options.client ?? this._client).get({ + url: '/session/{id}/message', + ...options + }); + } + + /** + * Create and send a new message to a session + */ + public chat(options: Options) { + return (options.client ?? this._client).post({ + url: '/session/{id}/message', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Revert a message + */ + public revert(options: Options) { + return (options.client ?? this._client).post({ + url: '/session/{id}/revert', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + } + + /** + * Restore all reverted messages + */ + public unrevert(options: Options) { + return (options.client ?? this._client).post({ + url: '/session/{id}/unrevert', + ...options + }); + } +} + +class Find extends _HeyApiClient { + /** + * Find text in files + */ + public text(options: Options) { + return (options.client ?? this._client).get({ + url: '/find', + ...options + }); + } + + /** + * Find files + */ + public files(options: Options) { + return (options.client ?? this._client).get({ + url: '/find/file', + ...options + }); + } + + /** + * Find workspace symbols + */ + public symbols(options: Options) { + return (options.client ?? this._client).get({ + url: '/find/symbol', + ...options + }); + } +} + +class File extends _HeyApiClient { + /** + * Read a file + */ + public read(options: Options) { + return (options.client ?? this._client).get({ + url: '/file', + ...options + }); + } + + /** + * Get file status + */ + public status(options?: Options) { + return (options?.client ?? this._client).get({ + url: '/file/status', + ...options + }); + } +} + +class Tui extends _HeyApiClient { + /** + * Append prompt to the TUI + */ + public appendPrompt(options?: Options) { + return (options?.client ?? this._client).post({ + url: '/tui/append-prompt', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); + } + + /** + * Open the help dialog + */ + public openHelp(options?: Options) { + return (options?.client ?? this._client).post({ + url: '/tui/open-help', + ...options + }); + } +} + +export class OpencodeClient extends _HeyApiClient { + event = new Event({ client: this._client }); + app = new App({ client: this._client }); + config = new Config({ client: this._client }); + session = new Session({ client: this._client }); + find = new Find({ client: this._client }); + file = new File({ client: this._client }); + tui = new Tui({ client: this._client }); +} \ No newline at end of file diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts new file mode 100644 index 000000000..6fcd1c415 --- /dev/null +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -0,0 +1,1450 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Event = ({ + type: 'installation.updated'; +} & EventInstallationUpdated) | ({ + type: 'lsp.client.diagnostics'; +} & EventLspClientDiagnostics) | ({ + type: 'message.updated'; +} & EventMessageUpdated) | ({ + type: 'message.removed'; +} & EventMessageRemoved) | ({ + type: 'message.part.updated'; +} & EventMessagePartUpdated) | ({ + type: 'message.part.removed'; +} & EventMessagePartRemoved) | ({ + type: 'storage.write'; +} & EventStorageWrite) | ({ + type: 'permission.updated'; +} & EventPermissionUpdated) | ({ + type: 'file.edited'; +} & EventFileEdited) | ({ + type: 'session.updated'; +} & EventSessionUpdated) | ({ + type: 'session.deleted'; +} & EventSessionDeleted) | ({ + type: 'session.idle'; +} & EventSessionIdle) | ({ + type: 'session.error'; +} & EventSessionError) | ({ + type: 'server.connected'; +} & EventServerConnected) | ({ + type: 'file.watcher.updated'; +} & EventFileWatcherUpdated) | ({ + type: 'ide.installed'; +} & EventIdeInstalled); + +export type EventInstallationUpdated = { + type: string; + properties: { + version: string; + }; +}; + +export type EventLspClientDiagnostics = { + type: string; + properties: { + serverID: string; + path: string; + }; +}; + +export type EventMessageUpdated = { + type: string; + properties: { + info: Message; + }; +}; + +export type Message = ({ + role: 'user'; +} & UserMessage) | ({ + role: 'assistant'; +} & AssistantMessage); + +export type UserMessage = { + id: string; + sessionID: string; + role: string; + time: { + created: number; + }; +}; + +export type AssistantMessage = { + id: string; + sessionID: string; + role: string; + time: { + created: number; + completed?: number; + }; + error?: ({ + name: 'ProviderAuthError'; + } & ProviderAuthError) | ({ + name: 'UnknownError'; + } & UnknownError) | ({ + name: 'MessageOutputLengthError'; + } & MessageOutputLengthError) | ({ + name: 'MessageAbortedError'; + } & MessageAbortedError); + system: Array; + modelID: string; + providerID: string; + mode: string; + path: { + cwd: string; + root: string; + }; + summary?: boolean; + cost: number; + tokens: { + input: number; + output: number; + reasoning: number; + cache: { + read: number; + write: number; + }; + }; +}; + +export type ProviderAuthError = { + name: string; + data: { + providerID: string; + message: string; + }; +}; + +export type UnknownError = { + name: string; + data: { + message: string; + }; +}; + +export type MessageOutputLengthError = { + name: string; + data: { + [key: string]: unknown; + }; +}; + +export type MessageAbortedError = { + name: string; + data: { + [key: string]: unknown; + }; +}; + +export type EventMessageRemoved = { + type: string; + properties: { + sessionID: string; + messageID: string; + }; +}; + +export type EventMessagePartUpdated = { + type: string; + properties: { + part: Part; + }; +}; + +export type Part = ({ + type: 'text'; +} & TextPart) | ({ + type: 'file'; +} & FilePart) | ({ + type: 'tool'; +} & ToolPart) | ({ + type: 'step-start'; +} & StepStartPart) | ({ + type: 'step-finish'; +} & StepFinishPart) | ({ + type: 'snapshot'; +} & SnapshotPart) | ({ + type: 'patch'; +} & PatchPart); + +export type TextPart = { + id: string; + sessionID: string; + messageID: string; + type: string; + text: string; + synthetic?: boolean; + time?: { + start: number; + end?: number; + }; +}; + +export type FilePart = { + id: string; + sessionID: string; + messageID: string; + type: string; + mime: string; + filename?: string; + url: string; + source?: FilePartSource; +}; + +export type FilePartSource = ({ + type: 'file'; +} & FileSource) | ({ + type: 'symbol'; +} & SymbolSource); + +export type FileSource = { + text: FilePartSourceText; + type: string; + path: string; +}; + +export type FilePartSourceText = { + value: string; + start: number; + end: number; +}; + +export type SymbolSource = { + text: FilePartSourceText; + type: string; + path: string; + range: Range; + name: string; + kind: number; +}; + +export type Range = { + start: { + line: number; + character: number; + }; + end: { + line: number; + character: number; + }; +}; + +export type ToolPart = { + id: string; + sessionID: string; + messageID: string; + type: string; + callID: string; + tool: string; + state: ToolState; +}; + +export type ToolState = ({ + status: 'pending'; +} & ToolStatePending) | ({ + status: 'running'; +} & ToolStateRunning) | ({ + status: 'completed'; +} & ToolStateCompleted) | ({ + status: 'error'; +} & ToolStateError); + +export type ToolStatePending = { + status: string; +}; + +export type ToolStateRunning = { + status: string; + input?: unknown; + title?: string; + metadata?: { + [key: string]: unknown; + }; + time: { + start: number; + }; +}; + +export type ToolStateCompleted = { + status: string; + input: { + [key: string]: unknown; + }; + output: string; + title: string; + metadata: { + [key: string]: unknown; + }; + time: { + start: number; + end: number; + }; +}; + +export type ToolStateError = { + status: string; + input: { + [key: string]: unknown; + }; + error: string; + time: { + start: number; + end: number; + }; +}; + +export type StepStartPart = { + id: string; + sessionID: string; + messageID: string; + type: string; +}; + +export type StepFinishPart = { + id: string; + sessionID: string; + messageID: string; + type: string; + cost: number; + tokens: { + input: number; + output: number; + reasoning: number; + cache: { + read: number; + write: number; + }; + }; +}; + +export type SnapshotPart = { + id: string; + sessionID: string; + messageID: string; + type: string; + snapshot: string; +}; + +export type PatchPart = { + id: string; + sessionID: string; + messageID: string; + type: string; + hash: string; + files: Array; +}; + +export type EventMessagePartRemoved = { + type: string; + properties: { + sessionID: string; + messageID: string; + partID: string; + }; +}; + +export type EventStorageWrite = { + type: string; + properties: { + key: string; + content?: unknown; + }; +}; + +export type EventPermissionUpdated = { + type: string; + properties: PermissionInfo; +}; + +export type PermissionInfo = { + id: string; + sessionID: string; + title: string; + metadata: { + [key: string]: unknown; + }; + time: { + created: number; + }; +}; + +export type EventFileEdited = { + type: string; + properties: { + file: string; + }; +}; + +export type EventSessionUpdated = { + type: string; + properties: { + info: Session; + }; +}; + +export type Session = { + id: string; + parentID?: string; + share?: { + url: string; + }; + title: string; + version: string; + time: { + created: number; + updated: number; + }; + revert?: { + messageID: string; + partID?: string; + snapshot?: string; + diff?: string; + }; +}; + +export type EventSessionDeleted = { + type: string; + properties: { + info: Session; + }; +}; + +export type EventSessionIdle = { + type: string; + properties: { + sessionID: string; + }; +}; + +export type EventSessionError = { + type: string; + properties: { + sessionID?: string; + error?: ({ + name: 'ProviderAuthError'; + } & ProviderAuthError) | ({ + name: 'UnknownError'; + } & UnknownError) | ({ + name: 'MessageOutputLengthError'; + } & MessageOutputLengthError) | ({ + name: 'MessageAbortedError'; + } & MessageAbortedError); + }; +}; + +export type EventServerConnected = { + type: string; + properties: { + [key: string]: unknown; + }; +}; + +export type EventFileWatcherUpdated = { + type: string; + properties: { + file: string; + event: string; + }; +}; + +export type EventIdeInstalled = { + type: string; + properties: { + ide: string; + }; +}; + +export type App = { + hostname: string; + git: boolean; + path: { + config: string; + data: string; + root: string; + cwd: string; + state: string; + }; + time: { + initialized?: number; + }; +}; + +export type Config = { + /** + * JSON schema reference for configuration validation + */ + $schema?: string; + /** + * Theme name to use for the interface + */ + theme?: string; + keybinds?: KeybindsConfig; + /** + * Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing + */ + share?: 'manual' | 'auto' | 'disabled'; + /** + * @deprecated Use 'share' field instead. Share newly created sessions automatically + */ + autoshare?: boolean; + /** + * Automatically update to the latest version + */ + autoupdate?: boolean; + /** + * Disable providers that are loaded automatically + */ + disabled_providers?: Array; + /** + * Model to use in the format of provider/model, eg anthropic/claude-2 + */ + model?: string; + /** + * Small model to use for tasks like summarization and title generation in the format of provider/model + */ + small_model?: string; + /** + * Custom username to display in conversations instead of system username + */ + username?: string; + /** + * Modes configuration, see https://opencode.ai/docs/modes + */ + mode?: { + build?: ModeConfig; + plan?: ModeConfig; + [key: string]: ModeConfig | undefined; + }; + /** + * Modes configuration, see https://opencode.ai/docs/modes + */ + agent?: { + general?: AgentConfig; + [key: string]: AgentConfig | undefined; + }; + /** + * Custom provider configurations and model overrides + */ + provider?: { + [key: string]: { + api?: string; + name?: string; + env?: Array; + id?: string; + npm?: string; + models: { + [key: string]: { + id?: string; + name?: string; + release_date?: string; + attachment?: boolean; + reasoning?: boolean; + temperature?: boolean; + tool_call?: boolean; + cost?: { + input: number; + output: number; + cache_read?: number; + cache_write?: number; + }; + limit?: { + context: number; + output: number; + }; + options?: { + [key: string]: unknown; + }; + }; + }; + options?: { + apiKey?: string; + baseURL?: string; + [key: string]: unknown | string | undefined; + }; + }; + }; + /** + * MCP (Model Context Protocol) server configurations + */ + mcp?: { + [key: string]: ({ + type: 'local'; + } & McpLocalConfig) | ({ + type: 'remote'; + } & McpRemoteConfig); + }; + /** + * Additional instruction files or patterns to include + */ + instructions?: Array; + layout?: LayoutConfig; + permission?: { + edit?: string; + bash?: string | { + [key: string]: string; + }; + }; + experimental?: { + hook?: { + file_edited?: { + [key: string]: Array<{ + command: Array; + environment?: { + [key: string]: string; + }; + }>; + }; + session_completed?: Array<{ + command: Array; + environment?: { + [key: string]: string; + }; + }>; + }; + }; +}; + +export type KeybindsConfig = { + /** + * Leader key for keybind combinations + */ + leader: string; + /** + * Show help dialog + */ + app_help: string; + /** + * Next mode + */ + switch_mode: string; + /** + * Previous Mode + */ + switch_mode_reverse: string; + /** + * Open external editor + */ + editor_open: string; + /** + * Export session to editor + */ + session_export: string; + /** + * Create a new session + */ + session_new: string; + /** + * List all sessions + */ + session_list: string; + /** + * Share current session + */ + session_share: string; + /** + * Unshare current session + */ + session_unshare: string; + /** + * Interrupt current session + */ + session_interrupt: string; + /** + * Compact the session + */ + session_compact: string; + /** + * Toggle tool details + */ + tool_details: string; + /** + * List available models + */ + model_list: string; + /** + * List available themes + */ + theme_list: string; + /** + * List files + */ + file_list: string; + /** + * Close file + */ + file_close: string; + /** + * Search file + */ + file_search: string; + /** + * Split/unified diff + */ + file_diff_toggle: string; + /** + * Create/update AGENTS.md + */ + project_init: string; + /** + * Clear input field + */ + input_clear: string; + /** + * Paste from clipboard + */ + input_paste: string; + /** + * Submit input + */ + input_submit: string; + /** + * Insert newline in input + */ + input_newline: string; + /** + * Scroll messages up by one page + */ + messages_page_up: string; + /** + * Scroll messages down by one page + */ + messages_page_down: string; + /** + * Scroll messages up by half page + */ + messages_half_page_up: string; + /** + * Scroll messages down by half page + */ + messages_half_page_down: string; + /** + * Navigate to previous message + */ + messages_previous: string; + /** + * Navigate to next message + */ + messages_next: string; + /** + * Navigate to first message + */ + messages_first: string; + /** + * Navigate to last message + */ + messages_last: string; + /** + * Toggle layout + */ + messages_layout_toggle: string; + /** + * Copy message + */ + messages_copy: string; + /** + * @deprecated use messages_undo. Revert message + */ + messages_revert: string; + /** + * Undo message + */ + messages_undo: string; + /** + * Redo message + */ + messages_redo: string; + /** + * Exit the application + */ + app_exit: string; +}; + +export type ModeConfig = { + model?: string; + temperature?: number; + prompt?: string; + tools?: { + [key: string]: boolean; + }; + disable?: boolean; +}; + +export type AgentConfig = ModeConfig & { + description: string; +}; + +export type Provider = { + api?: string; + name: string; + env: Array; + id: string; + npm?: string; + models: { + [key: string]: Model; + }; +}; + +export type Model = { + id: string; + name: string; + release_date: string; + attachment: boolean; + reasoning: boolean; + temperature: boolean; + tool_call: boolean; + cost: { + input: number; + output: number; + cache_read?: number; + cache_write?: number; + }; + limit: { + context: number; + output: number; + }; + options: { + [key: string]: unknown; + }; +}; + +export type McpLocalConfig = { + /** + * Type of MCP server connection + */ + type: string; + /** + * Command and arguments to run the MCP server + */ + command: Array; + /** + * Environment variables to set when running the MCP server + */ + environment?: { + [key: string]: string; + }; + /** + * Enable or disable the MCP server on startup + */ + enabled?: boolean; +}; + +export type McpRemoteConfig = { + /** + * Type of MCP server connection + */ + type: string; + /** + * URL of the remote MCP server + */ + url: string; + /** + * Enable or disable the MCP server on startup + */ + enabled?: boolean; + /** + * Headers to send with the request + */ + headers?: { + [key: string]: string; + }; +}; + +export type LayoutConfig = 'auto' | 'stretch'; + +export type _Error = { + data: { + [key: string]: unknown; + }; +}; + +export type TextPartInput = { + id?: string; + type: string; + text: string; + synthetic?: boolean; + time?: { + start: number; + end?: number; + }; +}; + +export type FilePartInput = { + id?: string; + type: string; + mime: string; + filename?: string; + url: string; + source?: FilePartSource; +}; + +export type Symbol = { + name: string; + kind: number; + location: { + uri: string; + range: Range; + }; +}; + +export type File = { + path: string; + added: number; + removed: number; + status: 'added' | 'deleted' | 'modified'; +}; + +export type Mode = { + name: string; + temperature?: number; + model?: { + modelID: string; + providerID: string; + }; + prompt?: string; + tools: { + [key: string]: boolean; + }; +}; + +export type EventSubscribeData = { + body?: never; + path?: never; + query?: never; + url: '/event'; +}; + +export type EventSubscribeResponses = { + /** + * Event stream + */ + 200: Event; +}; + +export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]; + +export type AppGetData = { + body?: never; + path?: never; + query?: never; + url: '/app'; +}; + +export type AppGetResponses = { + /** + * 200 + */ + 200: App; +}; + +export type AppGetResponse = AppGetResponses[keyof AppGetResponses]; + +export type AppInitData = { + body?: never; + path?: never; + query?: never; + url: '/app/init'; +}; + +export type AppInitResponses = { + /** + * Initialize the app + */ + 200: boolean; +}; + +export type AppInitResponse = AppInitResponses[keyof AppInitResponses]; + +export type ConfigGetData = { + body?: never; + path?: never; + query?: never; + url: '/config'; +}; + +export type ConfigGetResponses = { + /** + * Get config info + */ + 200: Config; +}; + +export type ConfigGetResponse = ConfigGetResponses[keyof ConfigGetResponses]; + +export type SessionListData = { + body?: never; + path?: never; + query?: never; + url: '/session'; +}; + +export type SessionListResponses = { + /** + * List of sessions + */ + 200: Array; +}; + +export type SessionListResponse = SessionListResponses[keyof SessionListResponses]; + +export type SessionCreateData = { + body?: never; + path?: never; + query?: never; + url: '/session'; +}; + +export type SessionCreateErrors = { + /** + * Bad request + */ + 400: _Error; +}; + +export type SessionCreateError = SessionCreateErrors[keyof SessionCreateErrors]; + +export type SessionCreateResponses = { + /** + * Successfully created session + */ + 200: Session; +}; + +export type SessionCreateResponse = SessionCreateResponses[keyof SessionCreateResponses]; + +export type SessionDeleteData = { + body?: never; + path: { + id: string; + }; + query?: never; + url: '/session/{id}'; +}; + +export type SessionDeleteResponses = { + /** + * Successfully deleted session + */ + 200: boolean; +}; + +export type SessionDeleteResponse = SessionDeleteResponses[keyof SessionDeleteResponses]; + +export type SessionInitData = { + body?: { + messageID: string; + providerID: string; + modelID: string; + }; + path: { + /** + * Session ID + */ + id: string; + }; + query?: never; + url: '/session/{id}/init'; +}; + +export type SessionInitResponses = { + /** + * 200 + */ + 200: boolean; +}; + +export type SessionInitResponse = SessionInitResponses[keyof SessionInitResponses]; + +export type SessionAbortData = { + body?: never; + path: { + id: string; + }; + query?: never; + url: '/session/{id}/abort'; +}; + +export type SessionAbortResponses = { + /** + * Aborted session + */ + 200: boolean; +}; + +export type SessionAbortResponse = SessionAbortResponses[keyof SessionAbortResponses]; + +export type SessionUnshareData = { + body?: never; + path: { + id: string; + }; + query?: never; + url: '/session/{id}/share'; +}; + +export type SessionUnshareResponses = { + /** + * Successfully unshared session + */ + 200: Session; +}; + +export type SessionUnshareResponse = SessionUnshareResponses[keyof SessionUnshareResponses]; + +export type SessionShareData = { + body?: never; + path: { + id: string; + }; + query?: never; + url: '/session/{id}/share'; +}; + +export type SessionShareResponses = { + /** + * Successfully shared session + */ + 200: Session; +}; + +export type SessionShareResponse = SessionShareResponses[keyof SessionShareResponses]; + +export type SessionSummarizeData = { + body?: { + providerID: string; + modelID: string; + }; + path: { + /** + * Session ID + */ + id: string; + }; + query?: never; + url: '/session/{id}/summarize'; +}; + +export type SessionSummarizeResponses = { + /** + * Summarized session + */ + 200: boolean; +}; + +export type SessionSummarizeResponse = SessionSummarizeResponses[keyof SessionSummarizeResponses]; + +export type SessionMessagesData = { + body?: never; + path: { + /** + * Session ID + */ + id: string; + }; + query?: never; + url: '/session/{id}/message'; +}; + +export type SessionMessagesResponses = { + /** + * List of messages + */ + 200: Array<{ + info: Message; + parts: Array; + }>; +}; + +export type SessionMessagesResponse = SessionMessagesResponses[keyof SessionMessagesResponses]; + +export type SessionChatData = { + body?: { + messageID?: string; + providerID: string; + modelID: string; + mode?: string; + system?: string; + tools?: { + [key: string]: boolean; + }; + parts: Array<({ + type: 'text'; + } & TextPartInput) | ({ + type: 'file'; + } & FilePartInput)>; + }; + path: { + /** + * Session ID + */ + id: string; + }; + query?: never; + url: '/session/{id}/message'; +}; + +export type SessionChatResponses = { + /** + * Created message + */ + 200: AssistantMessage; +}; + +export type SessionChatResponse = SessionChatResponses[keyof SessionChatResponses]; + +export type SessionRevertData = { + body?: { + messageID: string; + partID?: string; + }; + path: { + id: string; + }; + query?: never; + url: '/session/{id}/revert'; +}; + +export type SessionRevertResponses = { + /** + * Updated session + */ + 200: Session; +}; + +export type SessionRevertResponse = SessionRevertResponses[keyof SessionRevertResponses]; + +export type SessionUnrevertData = { + body?: never; + path: { + id: string; + }; + query?: never; + url: '/session/{id}/unrevert'; +}; + +export type SessionUnrevertResponses = { + /** + * Updated session + */ + 200: Session; +}; + +export type SessionUnrevertResponse = SessionUnrevertResponses[keyof SessionUnrevertResponses]; + +export type ConfigProvidersData = { + body?: never; + path?: never; + query?: never; + url: '/config/providers'; +}; + +export type ConfigProvidersResponses = { + /** + * List of providers + */ + 200: { + providers: Array; + default: { + [key: string]: string; + }; + }; +}; + +export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses]; + +export type FindTextData = { + body?: never; + path?: never; + query: { + pattern: string; + }; + url: '/find'; +}; + +export type FindTextResponses = { + /** + * Matches + */ + 200: Array<{ + path: { + text: string; + }; + lines: { + text: string; + }; + line_number: number; + absolute_offset: number; + submatches: Array<{ + match: { + text: string; + }; + start: number; + end: number; + }>; + }>; +}; + +export type FindTextResponse = FindTextResponses[keyof FindTextResponses]; + +export type FindFilesData = { + body?: never; + path?: never; + query: { + query: string; + }; + url: '/find/file'; +}; + +export type FindFilesResponses = { + /** + * File paths + */ + 200: Array; +}; + +export type FindFilesResponse = FindFilesResponses[keyof FindFilesResponses]; + +export type FindSymbolsData = { + body?: never; + path?: never; + query: { + query: string; + }; + url: '/find/symbol'; +}; + +export type FindSymbolsResponses = { + /** + * Symbols + */ + 200: Array; +}; + +export type FindSymbolsResponse = FindSymbolsResponses[keyof FindSymbolsResponses]; + +export type FileReadData = { + body?: never; + path?: never; + query: { + path: string; + }; + url: '/file'; +}; + +export type FileReadResponses = { + /** + * File content + */ + 200: { + type: 'raw' | 'patch'; + content: string; + }; +}; + +export type FileReadResponse = FileReadResponses[keyof FileReadResponses]; + +export type FileStatusData = { + body?: never; + path?: never; + query?: never; + url: '/file/status'; +}; + +export type FileStatusResponses = { + /** + * File status + */ + 200: Array; +}; + +export type FileStatusResponse = FileStatusResponses[keyof FileStatusResponses]; + +export type AppLogData = { + body?: { + /** + * Service name for the log entry + */ + service: string; + /** + * Log level + */ + level: 'debug' | 'info' | 'error' | 'warn'; + /** + * Log message + */ + message: string; + /** + * Additional metadata for the log entry + */ + extra?: { + [key: string]: unknown; + }; + }; + path?: never; + query?: never; + url: '/log'; +}; + +export type AppLogResponses = { + /** + * Log entry written successfully + */ + 200: boolean; +}; + +export type AppLogResponse = AppLogResponses[keyof AppLogResponses]; + +export type AppModesData = { + body?: never; + path?: never; + query?: never; + url: '/mode'; +}; + +export type AppModesResponses = { + /** + * List of modes + */ + 200: Array; +}; + +export type AppModesResponse = AppModesResponses[keyof AppModesResponses]; + +export type TuiAppendPromptData = { + body?: { + text: string; + }; + path?: never; + query?: never; + url: '/tui/append-prompt'; +}; + +export type TuiAppendPromptResponses = { + /** + * Prompt processed successfully + */ + 200: boolean; +}; + +export type TuiAppendPromptResponse = TuiAppendPromptResponses[keyof TuiAppendPromptResponses]; + +export type TuiOpenHelpData = { + body?: never; + path?: never; + query?: never; + url: '/tui/open-help'; +}; + +export type TuiOpenHelpResponses = { + /** + * Help dialog opened successfully + */ + 200: boolean; +}; + +export type TuiOpenHelpResponse = TuiOpenHelpResponses[keyof TuiOpenHelpResponses]; + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; \ No newline at end of file diff --git a/packages/sdk/js/src/index.ts b/packages/sdk/js/src/index.ts new file mode 100644 index 000000000..bdabf5f43 --- /dev/null +++ b/packages/sdk/js/src/index.ts @@ -0,0 +1,8 @@ +import { createClient } from "./gen/client/client" +import { type Config } from "./gen/client/types" +import { OpencodeClient } from "./gen/sdk.gen" + +export function createOpencodeClient(config?: Config) { + const client = createClient(config) + return new OpencodeClient({ client }) +} diff --git a/packages/sdk/js/tsconfig.json b/packages/sdk/js/tsconfig.json new file mode 100644 index 000000000..dbd7732e5 --- /dev/null +++ b/packages/sdk/js/tsconfig.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "@tsconfig/node22/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "module": "preserve", + "declaration": true, + "moduleResolution": "bundler" + }, + "include": [ + "src" + ] +} diff --git a/packages/sdk/package.json b/packages/sdk/package.json deleted file mode 100644 index 12f57b1d3..000000000 --- a/packages/sdk/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@opencode-ai/sdk", - "version": "0.1.0-alpha.20", - "description": "The official TypeScript library for the Opencode API", - "author": "Opencode ", - "types": "dist/index.d.ts", - "main": "dist/index.js", - "type": "commonjs", - "repository": "github:sst/opencode-sdk-js", - "license": "MIT", - "packageManager": "yarn@1.22.22", - "files": [ - "**/*" - ], - "private": false, - "scripts": { - "test": "./scripts/test", - "build": "./scripts/build", - "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", - "format": "./scripts/format", - "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi", - "tsn": "ts-node -r tsconfig-paths/register", - "lint": "./scripts/lint", - "fix": "./scripts/format" - }, - "dependencies": {}, - "devDependencies": { - "@arethetypeswrong/cli": "^0.17.0", - "@swc/core": "^1.3.102", - "@swc/jest": "^0.2.29", - "@types/jest": "^29.4.0", - "@types/node": "^20.17.6", - "@typescript-eslint/eslint-plugin": "8.31.1", - "@typescript-eslint/parser": "8.31.1", - "eslint": "^9.20.1", - "eslint-plugin-prettier": "^5.4.1", - "eslint-plugin-unused-imports": "^4.1.4", - "iconv-lite": "^0.6.3", - "jest": "^29.4.0", - "prettier": "^3.0.0", - "publint": "^0.2.12", - "ts-jest": "^29.1.0", - "ts-node": "^10.5.0", - "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz", - "tsconfig-paths": "^4.0.0", - "typescript": "catalog:", - "typescript-eslint": "8.31.1" - }, - "imports": { - "@opencode-ai/sdk": ".", - "@opencode-ai/sdk/*": "./src/*" - }, - "exports": { - ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.js" - }, - "./*.mjs": { - "default": "./dist/*.mjs" - }, - "./*.js": { - "default": "./dist/*.js" - }, - "./*": { - "import": "./dist/*.mjs", - "require": "./dist/*.js" - } - } -} diff --git a/packages/sdk/release-please-config.json b/packages/sdk/release-please-config.json deleted file mode 100644 index 1ebd0bde2..000000000 --- a/packages/sdk/release-please-config.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "packages": { - ".": {} - }, - "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", - "include-v-in-tag": true, - "include-component-in-tag": false, - "versioning": "prerelease", - "prerelease": true, - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": false, - "pull-request-header": "Automated Release PR", - "pull-request-title-pattern": "release: ${version}", - "changelog-sections": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - }, - { - "type": "perf", - "section": "Performance Improvements" - }, - { - "type": "revert", - "section": "Reverts" - }, - { - "type": "chore", - "section": "Chores" - }, - { - "type": "docs", - "section": "Documentation" - }, - { - "type": "style", - "section": "Styles" - }, - { - "type": "refactor", - "section": "Refactors" - }, - { - "type": "test", - "section": "Tests", - "hidden": true - }, - { - "type": "build", - "section": "Build System" - }, - { - "type": "ci", - "section": "Continuous Integration", - "hidden": true - } - ], - "release-type": "node", - "extra-files": ["src/version.ts", "README.md"] -} diff --git a/packages/sdk/scripts/bootstrap b/packages/sdk/scripts/bootstrap deleted file mode 100755 index 0af58e251..000000000 --- a/packages/sdk/scripts/bootstrap +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then - brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle - } -fi - -echo "==> Installing Node dependencies…" - -PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") - -$PACKAGE_MANAGER install diff --git a/packages/sdk/scripts/build b/packages/sdk/scripts/build deleted file mode 100755 index c65e0faec..000000000 --- a/packages/sdk/scripts/build +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash - -set -exuo pipefail - -cd "$(dirname "$0")/.." - -node scripts/utils/check-version.cjs - -# Build into dist and will publish the package from there, -# so that src/resources/foo.ts becomes /resources/foo.js -# This way importing from `"@opencode-ai/sdk/resources/foo"` works -# even with `"moduleResolution": "node"` - -rm -rf dist; mkdir dist -# Copy src to dist/src and build from dist/src into dist, so that -# the source map for index.js.map will refer to ./src/index.ts etc -cp -rp src README.md dist -for file in LICENSE CHANGELOG.md; do - if [ -e "${file}" ]; then cp "${file}" dist; fi -done -if [ -e "bin/cli" ]; then - mkdir -p dist/bin - cp -p "bin/cli" dist/bin/; -fi -if [ -e "bin/migration-config.json" ]; then - mkdir -p dist/bin - cp -p "bin/migration-config.json" dist/bin/; -fi -# this converts the export map paths for the dist directory -# and does a few other minor things -node scripts/utils/make-dist-package-json.cjs > dist/package.json - -# build to .js/.mjs/.d.ts files -./node_modules/.bin/tsc-multi -# we need to patch index.js so that `new module.exports()` works for cjs backwards -# compat. No way to get that from index.ts because it would cause compile errors -# when building .mjs -node scripts/utils/fix-index-exports.cjs -cp tsconfig.dist-src.json dist/src/tsconfig.json - -node scripts/utils/postprocess-files.cjs - -# make sure that nothing crashes when we require the output CJS or -# import the output ESM -(cd dist && node -e 'require("@opencode-ai/sdk")') -(cd dist && node -e 'import("@opencode-ai/sdk")' --input-type=module) - -if [ -e ./scripts/build-deno ] -then - ./scripts/build-deno -fi diff --git a/packages/sdk/scripts/format b/packages/sdk/scripts/format deleted file mode 100755 index 7a7564019..000000000 --- a/packages/sdk/scripts/format +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -echo "==> Running eslint --fix" -./node_modules/.bin/eslint --fix . - -echo "==> Running prettier --write" -# format things eslint didn't -./node_modules/.bin/prettier --write --cache --cache-strategy metadata . '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs' diff --git a/packages/sdk/scripts/lint b/packages/sdk/scripts/lint deleted file mode 100755 index 3ffb78a64..000000000 --- a/packages/sdk/scripts/lint +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -echo "==> Running eslint" -./node_modules/.bin/eslint . - -echo "==> Building" -./scripts/build - -echo "==> Checking types" -./node_modules/typescript/bin/tsc - -echo "==> Running Are The Types Wrong?" -./node_modules/.bin/attw --pack dist -f json >.attw.json || true -node scripts/utils/attw-report.cjs - -echo "==> Running publint" -./node_modules/.bin/publint dist diff --git a/packages/sdk/scripts/mock b/packages/sdk/scripts/mock deleted file mode 100755 index d2814ae6a..000000000 --- a/packages/sdk/scripts/mock +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [[ -n "$1" && "$1" != '--'* ]]; then - URL="$1" - shift -else - URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" -fi - -# Check if the URL is empty -if [ -z "$URL" ]; then - echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" - exit 1 -fi - -echo "==> Starting mock server with URL ${URL}" - -# Run prism mock on the given spec -if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & - - # Wait for server to come online - echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do - echo -n "." - sleep 0.1 - done - - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - - echo -else - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" -fi diff --git a/packages/sdk/scripts/test b/packages/sdk/scripts/test deleted file mode 100755 index 2049e31b0..000000000 --- a/packages/sdk/scripts/test +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 -} - -kill_server_on_port() { - pids=$(lsof -t -i tcp:"$1" || echo "") - if [ "$pids" != "" ]; then - kill "$pids" - echo "Stopped $pids." - fi -} - -function is_overriding_api_base_url() { - [ -n "$TEST_API_BASE_URL" ] -} - -if ! is_overriding_api_base_url && ! prism_is_running ; then - # When we exit this script, make sure to kill the background mock server process - trap 'kill_server_on_port 4010' EXIT - - # Start the dev server - ./scripts/mock --daemon -fi - -if is_overriding_api_base_url ; then - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" - echo -e "running against your OpenAPI spec." - echo - echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" - echo - echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" - echo - - exit 1 -else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" - echo -fi - -echo "==> Running tests" -./node_modules/.bin/jest "$@" diff --git a/packages/sdk/scripts/utils/attw-report.cjs b/packages/sdk/scripts/utils/attw-report.cjs deleted file mode 100644 index b3477c0ef..000000000 --- a/packages/sdk/scripts/utils/attw-report.cjs +++ /dev/null @@ -1,24 +0,0 @@ -const fs = require('fs'); -const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')).problems) - .flat() - .filter( - (problem) => - !( - // This is intentional, if the user specifies .mjs they get ESM. - ( - (problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) || - // This is intentional for backwards compat reasons. - (problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) || - // this is intentional, we deliberately attempt to import types that may not exist from parent node_modules - // folders to better support various runtimes without triggering automatic type acquisition. - (problem.kind === 'InternalResolutionError' && problem.moduleSpecifier.includes('node_modules')) - ) - ), - ); -fs.unlinkSync('.attw.json'); -if (problems.length) { - process.stdout.write('The types are wrong!\n' + JSON.stringify(problems, null, 2) + '\n'); - process.exitCode = 1; -} else { - process.stdout.write('Types ok!\n'); -} diff --git a/packages/sdk/scripts/utils/check-is-in-git-install.sh b/packages/sdk/scripts/utils/check-is-in-git-install.sh deleted file mode 100755 index 1354eb432..000000000 --- a/packages/sdk/scripts/utils/check-is-in-git-install.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# Check if you happen to call prepare for a repository that's already in node_modules. -[ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] || -# The name of the containing directory that 'npm` uses, which looks like -# $HOME/.npm/_cacache/git-cloneXXXXXX -[ "$(basename "$(dirname "$PWD")")" = 'tmp' ] || -# The name of the containing directory that 'yarn` uses, which looks like -# $(yarn cache dir)/.tmp/XXXXX -[ "$(basename "$(dirname "$PWD")")" = '.tmp' ] diff --git a/packages/sdk/scripts/utils/check-version.cjs b/packages/sdk/scripts/utils/check-version.cjs deleted file mode 100644 index 86c56dfd3..000000000 --- a/packages/sdk/scripts/utils/check-version.cjs +++ /dev/null @@ -1,20 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const main = () => { - const pkg = require('../../package.json'); - const version = pkg['version']; - if (!version) throw 'The version property is not set in the package.json file'; - if (typeof version !== 'string') { - throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`; - } - - const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts'); - const contents = fs.readFileSync(versionFile, 'utf8'); - const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`); - fs.writeFileSync(versionFile, output); -}; - -if (require.main === module) { - main(); -} diff --git a/packages/sdk/scripts/utils/fix-index-exports.cjs b/packages/sdk/scripts/utils/fix-index-exports.cjs deleted file mode 100644 index e5e10b3e7..000000000 --- a/packages/sdk/scripts/utils/fix-index-exports.cjs +++ /dev/null @@ -1,17 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const indexJs = - process.env['DIST_PATH'] ? - path.resolve(process.env['DIST_PATH'], 'index.js') - : path.resolve(__dirname, '..', '..', 'dist', 'index.js'); - -let before = fs.readFileSync(indexJs, 'utf8'); -let after = before.replace( - /^(\s*Object\.defineProperty\s*\(exports,\s*["']__esModule["'].+)$/m, - `exports = module.exports = function (...args) { - return new exports.default(...args) - } - $1`.replace(/^ /gm, ''), -); -fs.writeFileSync(indexJs, after, 'utf8'); diff --git a/packages/sdk/scripts/utils/git-swap.sh b/packages/sdk/scripts/utils/git-swap.sh deleted file mode 100755 index 79d1888eb..000000000 --- a/packages/sdk/scripts/utils/git-swap.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail -# the package is published to NPM from ./dist -# we want the final file structure for git installs to match the npm installs, so we - -# delete everything except ./dist and ./node_modules -find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' + - -# move everything from ./dist to . -mv dist/* . - -# delete the now-empty ./dist -rmdir dist diff --git a/packages/sdk/scripts/utils/make-dist-package-json.cjs b/packages/sdk/scripts/utils/make-dist-package-json.cjs deleted file mode 100644 index 7c24f56e2..000000000 --- a/packages/sdk/scripts/utils/make-dist-package-json.cjs +++ /dev/null @@ -1,21 +0,0 @@ -const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json'); - -function processExportMap(m) { - for (const key in m) { - const value = m[key]; - if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './'); - else processExportMap(value); - } -} -processExportMap(pkgJson.exports); - -for (const key of ['types', 'main', 'module']) { - if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); -} - -delete pkgJson.devDependencies; -delete pkgJson.scripts.prepack; -delete pkgJson.scripts.prepublishOnly; -delete pkgJson.scripts.prepare; - -console.log(JSON.stringify(pkgJson, null, 2)); diff --git a/packages/sdk/scripts/utils/postprocess-files.cjs b/packages/sdk/scripts/utils/postprocess-files.cjs deleted file mode 100644 index deae575e3..000000000 --- a/packages/sdk/scripts/utils/postprocess-files.cjs +++ /dev/null @@ -1,94 +0,0 @@ -// @ts-check -const fs = require('fs'); -const path = require('path'); - -const distDir = - process.env['DIST_PATH'] ? - path.resolve(process.env['DIST_PATH']) - : path.resolve(__dirname, '..', '..', 'dist'); - -async function* walk(dir) { - for await (const d of await fs.promises.opendir(dir)) { - const entry = path.join(dir, d.name); - if (d.isDirectory()) yield* walk(entry); - else if (d.isFile()) yield entry; - } -} - -async function postprocess() { - for await (const file of walk(distDir)) { - if (!/(\.d)?[cm]?ts$/.test(file)) continue; - - const code = await fs.promises.readFile(file, 'utf8'); - - // strip out lib="dom", types="node", and types="react" references; these - // are needed at build time, but would pollute the user's TS environment - const transformed = code.replace( - /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', - ); - - if (transformed !== code) { - console.error(`wrote ${path.relative(process.cwd(), file)}`); - await fs.promises.writeFile(file, transformed, 'utf8'); - } - } - - const newExports = { - '.': { - require: { - types: './index.d.ts', - default: './index.js', - }, - types: './index.d.mts', - default: './index.mjs', - }, - }; - - for (const entry of await fs.promises.readdir(distDir, { withFileTypes: true })) { - if (entry.isDirectory() && entry.name !== 'src' && entry.name !== 'internal' && entry.name !== 'bin') { - const subpath = './' + entry.name; - newExports[subpath + '/*.mjs'] = { - default: subpath + '/*.mjs', - }; - newExports[subpath + '/*.js'] = { - default: subpath + '/*.js', - }; - newExports[subpath + '/*'] = { - import: subpath + '/*.mjs', - require: subpath + '/*.js', - }; - } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) { - const { name, ext } = path.parse(entry.name); - const subpathWithoutExt = './' + name; - const subpath = './' + entry.name; - newExports[subpathWithoutExt] ||= { import: undefined, require: undefined }; - const isModule = ext[1] === 'm'; - if (isModule) { - newExports[subpathWithoutExt].import = subpath; - } else { - newExports[subpathWithoutExt].require = subpath; - } - newExports[subpath] = { - default: subpath, - }; - } - } - await fs.promises.writeFile( - 'dist/package.json', - JSON.stringify( - Object.assign( - /** @type {Record} */ ( - JSON.parse(await fs.promises.readFile('dist/package.json', 'utf-8')) - ), - { - exports: newExports, - }, - ), - null, - 2, - ), - ); -} -postprocess(); diff --git a/packages/sdk/scripts/utils/upload-artifact.sh b/packages/sdk/scripts/utils/upload-artifact.sh deleted file mode 100755 index ac113aac2..000000000 --- a/packages/sdk/scripts/utils/upload-artifact.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail - -RESPONSE=$(curl -X POST "$URL" \ - -H "Authorization: Bearer $AUTH" \ - -H "Content-Type: application/json") - -SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') - -if [[ "$SIGNED_URL" == "null" ]]; then - echo -e "\033[31mFailed to get signed URL.\033[0m" - exit 1 -fi - -UPLOAD_RESPONSE=$(tar -cz dist | curl -v -X PUT \ - -H "Content-Type: application/gzip" \ - --data-binary @- "$SIGNED_URL" 2>&1) - -if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then - echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/opencode-typescript/$SHA'\033[0m" -else - echo -e "\033[31mFailed to upload artifact.\033[0m" - exit 1 -fi diff --git a/packages/sdk/src/api-promise.ts b/packages/sdk/src/api-promise.ts deleted file mode 100644 index 8c775ee69..000000000 --- a/packages/sdk/src/api-promise.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/api-promise instead */ -export * from './core/api-promise'; diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts deleted file mode 100644 index 9241bd582..000000000 --- a/packages/sdk/src/client.ts +++ /dev/null @@ -1,862 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types'; -import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestInit } from './internal/types'; -import { uuid4 } from './internal/utils/uuid'; -import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values'; -import { sleep } from './internal/utils/sleep'; -export type { Logger, LogLevel } from './internal/utils/log'; -import { castToError, isAbortError } from './internal/errors'; -import type { APIResponseProps } from './internal/parse'; -import { getPlatformHeaders } from './internal/detect-platform'; -import * as Shims from './internal/shims'; -import * as Opts from './internal/request-options'; -import { VERSION } from './version'; -import * as Errors from './core/error'; -import * as Uploads from './core/uploads'; -import * as API from './resources/index'; -import { APIPromise } from './core/api-promise'; -import { - App, - AppInitResponse, - AppLogParams, - AppLogResponse, - AppModesResponse, - AppProvidersResponse, - AppResource, - Mode, - Model, - Provider, -} from './resources/app'; -import { - Config, - ConfigResource, - KeybindsConfig, - McpLocalConfig, - McpRemoteConfig, - ModeConfig, -} from './resources/config'; -import { Event, EventListResponse } from './resources/event'; -import { File, FileReadParams, FileReadResponse, FileResource, FileStatusResponse } from './resources/file'; -import { - Find, - FindFilesParams, - FindFilesResponse, - FindSymbolsParams, - FindSymbolsResponse, - FindTextParams, - FindTextResponse, - Symbol, -} from './resources/find'; -import { - AssistantMessage, - FilePart, - FilePartInput, - FilePartSource, - FilePartSourceText, - FileSource, - Message, - Part, - Session, - SessionAbortResponse, - SessionChatParams, - SessionDeleteResponse, - SessionInitParams, - SessionInitResponse, - SessionListResponse, - SessionMessagesResponse, - SessionResource, - SessionRevertParams, - SessionSummarizeParams, - SessionSummarizeResponse, - SnapshotPart, - StepFinishPart, - StepStartPart, - SymbolSource, - TextPart, - TextPartInput, - ToolPart, - ToolStateCompleted, - ToolStateError, - ToolStatePending, - ToolStateRunning, - UserMessage, -} from './resources/session'; -import { Tui, TuiAppendPromptParams, TuiAppendPromptResponse, TuiOpenHelpResponse } from './resources/tui'; -import { type Fetch } from './internal/builtin-types'; -import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; -import { FinalRequestOptions, RequestOptions } from './internal/request-options'; -import { readEnv } from './internal/utils/env'; -import { - type LogLevel, - type Logger, - formatRequestDetails, - loggerFor, - parseLogLevel, -} from './internal/utils/log'; -import { isEmptyObj } from './internal/utils/values'; - -export interface ClientOptions { - /** - * Override the default base URL for the API, e.g., "https://api.example.com/v2/" - * - * Defaults to process.env['OPENCODE_BASE_URL']. - */ - baseURL?: string | null | undefined; - - /** - * The maximum amount of time (in milliseconds) that the client should wait for a response - * from the server before timing out a single request. - * - * Note that request timeouts are retried by default, so in a worst-case scenario you may wait - * much longer than this timeout before the promise succeeds or fails. - * - * @unit milliseconds - */ - timeout?: number | undefined; - /** - * Additional `RequestInit` options to be passed to `fetch` calls. - * Properties will be overridden by per-request `fetchOptions`. - */ - fetchOptions?: MergedRequestInit | undefined; - - /** - * Specify a custom `fetch` function implementation. - * - * If not provided, we expect that `fetch` is defined globally. - */ - fetch?: Fetch | undefined; - - /** - * The maximum number of times that the client will retry a request in case of a - * temporary failure, like a network error or a 5XX error from the server. - * - * @default 2 - */ - maxRetries?: number | undefined; - - /** - * Default headers to include with every request to the API. - * - * These can be removed in individual requests by explicitly setting the - * header to `null` in request options. - */ - defaultHeaders?: HeadersLike | undefined; - - /** - * Default query parameters to include with every request to the API. - * - * These can be removed in individual requests by explicitly setting the - * param to `undefined` in request options. - */ - defaultQuery?: Record | undefined; - - /** - * Set the log level. - * - * Defaults to process.env['OPENCODE_LOG'] or 'warn' if it isn't set. - */ - logLevel?: LogLevel | undefined; - - /** - * Set the logger. - * - * Defaults to globalThis.console. - */ - logger?: Logger | undefined; -} - -/** - * API Client for interfacing with the Opencode API. - */ -export class Opencode { - baseURL: string; - maxRetries: number; - timeout: number; - logger: Logger | undefined; - logLevel: LogLevel | undefined; - fetchOptions: MergedRequestInit | undefined; - - private fetch: Fetch; - #encoder: Opts.RequestEncoder; - protected idempotencyHeader?: string; - private _options: ClientOptions; - - /** - * API Client for interfacing with the Opencode API. - * - * @param {string} [opts.baseURL=process.env['OPENCODE_BASE_URL'] ?? http://localhost:54321] - Override the default base URL for the API. - * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. - * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. - * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. - * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. - * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. - * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. - */ - constructor({ baseURL = readEnv('OPENCODE_BASE_URL'), ...opts }: ClientOptions = {}) { - const options: ClientOptions = { - ...opts, - baseURL: baseURL || `http://localhost:54321`, - }; - - this.baseURL = options.baseURL!; - this.timeout = options.timeout ?? Opencode.DEFAULT_TIMEOUT /* 1 minute */; - this.logger = options.logger ?? console; - const defaultLogLevel = 'warn'; - // Set default logLevel early so that we can log a warning in parseLogLevel. - this.logLevel = defaultLogLevel; - this.logLevel = - parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ?? - parseLogLevel(readEnv('OPENCODE_LOG'), "process.env['OPENCODE_LOG']", this) ?? - defaultLogLevel; - this.fetchOptions = options.fetchOptions; - this.maxRetries = options.maxRetries ?? 2; - this.fetch = options.fetch ?? Shims.getDefaultFetch(); - this.#encoder = Opts.FallbackEncoder; - - this._options = options; - } - - /** - * Create a new client instance re-using the same options given to the current client with optional overriding. - */ - withOptions(options: Partial): this { - const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({ - ...this._options, - baseURL: this.baseURL, - maxRetries: this.maxRetries, - timeout: this.timeout, - logger: this.logger, - logLevel: this.logLevel, - fetch: this.fetch, - fetchOptions: this.fetchOptions, - ...options, - }); - return client; - } - - /** - * Check whether the base URL is set to its default. - */ - #baseURLOverridden(): boolean { - return this.baseURL !== 'http://localhost:54321'; - } - - protected defaultQuery(): Record | undefined { - return this._options.defaultQuery; - } - - protected validateHeaders({ values, nulls }: NullableHeaders) { - return; - } - - /** - * Basic re-implementation of `qs.stringify` for primitive types. - */ - protected stringifyQuery(query: Record): string { - return Object.entries(query) - .filter(([_, value]) => typeof value !== 'undefined') - .map(([key, value]) => { - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - } - if (value === null) { - return `${encodeURIComponent(key)}=`; - } - throw new Errors.OpencodeError( - `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`, - ); - }) - .join('&'); - } - - private getUserAgent(): string { - return `${this.constructor.name}/JS ${VERSION}`; - } - - protected defaultIdempotencyKey(): string { - return `stainless-node-retry-${uuid4()}`; - } - - protected makeStatusError( - status: number, - error: Object, - message: string | undefined, - headers: Headers, - ): Errors.APIError { - return Errors.APIError.generate(status, error, message, headers); - } - - buildURL( - path: string, - query: Record | null | undefined, - defaultBaseURL?: string | undefined, - ): string { - const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; - const url = - isAbsoluteURL(path) ? - new URL(path) - : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); - - const defaultQuery = this.defaultQuery(); - if (!isEmptyObj(defaultQuery)) { - query = { ...defaultQuery, ...query }; - } - - if (typeof query === 'object' && query && !Array.isArray(query)) { - url.search = this.stringifyQuery(query as Record); - } - - return url.toString(); - } - - /** - * Used as a callback for mutating the given `FinalRequestOptions` object. - */ - protected async prepareOptions(options: FinalRequestOptions): Promise {} - - /** - * Used as a callback for mutating the given `RequestInit` object. - * - * This is useful for cases where you want to add certain headers based off of - * the request properties, e.g. `method` or `url`. - */ - protected async prepareRequest( - request: RequestInit, - { url, options }: { url: string; options: FinalRequestOptions }, - ): Promise {} - - get(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('get', path, opts); - } - - post(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('post', path, opts); - } - - patch(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('patch', path, opts); - } - - put(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('put', path, opts); - } - - delete(path: string, opts?: PromiseOrValue): APIPromise { - return this.methodRequest('delete', path, opts); - } - - private methodRequest( - method: HTTPMethod, - path: string, - opts?: PromiseOrValue, - ): APIPromise { - return this.request( - Promise.resolve(opts).then((opts) => { - return { method, path, ...opts }; - }), - ); - } - - request( - options: PromiseOrValue, - remainingRetries: number | null = null, - ): APIPromise { - return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined)); - } - - private async makeRequest( - optionsInput: PromiseOrValue, - retriesRemaining: number | null, - retryOfRequestLogID: string | undefined, - ): Promise { - const options = await optionsInput; - const maxRetries = options.maxRetries ?? this.maxRetries; - if (retriesRemaining == null) { - retriesRemaining = maxRetries; - } - - await this.prepareOptions(options); - - const { req, url, timeout } = await this.buildRequest(options, { - retryCount: maxRetries - retriesRemaining, - }); - - await this.prepareRequest(req, { url, options }); - - /** Not an API request ID, just for correlating local log entries. */ - const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0'); - const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`; - const startTime = Date.now(); - - loggerFor(this).debug( - `[${requestLogID}] sending request`, - formatRequestDetails({ - retryOfRequestLogID, - method: options.method, - url, - options, - headers: req.headers, - }), - ); - - if (options.signal?.aborted) { - throw new Errors.APIUserAbortError(); - } - - const controller = new AbortController(); - const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); - const headersTime = Date.now(); - - if (response instanceof Error) { - const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; - if (options.signal?.aborted) { - throw new Errors.APIUserAbortError(); - } - // detect native connection timeout errors - // deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)" - // undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)" - // others do not provide enough information to distinguish timeouts from other connection errors - const isTimeout = - isAbortError(response) || - /timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : '')); - if (retriesRemaining) { - loggerFor(this).info( - `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`, - ); - loggerFor(this).debug( - `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, - formatRequestDetails({ - retryOfRequestLogID, - url, - durationMs: headersTime - startTime, - message: response.message, - }), - ); - return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID); - } - loggerFor(this).info( - `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`, - ); - loggerFor(this).debug( - `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, - formatRequestDetails({ - retryOfRequestLogID, - url, - durationMs: headersTime - startTime, - message: response.message, - }), - ); - if (isTimeout) { - throw new Errors.APIConnectionTimeoutError(); - } - throw new Errors.APIConnectionError({ cause: response }); - } - - const responseInfo = `[${requestLogID}${retryLogStr}] ${req.method} ${url} ${ - response.ok ? 'succeeded' : 'failed' - } with status ${response.status} in ${headersTime - startTime}ms`; - - if (!response.ok) { - const shouldRetry = await this.shouldRetry(response); - if (retriesRemaining && shouldRetry) { - const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; - - // We don't need the body of this response. - await Shims.CancelReadableStream(response.body); - loggerFor(this).info(`${responseInfo} - ${retryMessage}`); - loggerFor(this).debug( - `[${requestLogID}] response error (${retryMessage})`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - headers: response.headers, - durationMs: headersTime - startTime, - }), - ); - return this.retryRequest( - options, - retriesRemaining, - retryOfRequestLogID ?? requestLogID, - response.headers, - ); - } - - const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`; - - loggerFor(this).info(`${responseInfo} - ${retryMessage}`); - - const errText = await response.text().catch((err: any) => castToError(err).message); - const errJSON = safeJSON(errText); - const errMessage = errJSON ? undefined : errText; - - loggerFor(this).debug( - `[${requestLogID}] response error (${retryMessage})`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - headers: response.headers, - message: errMessage, - durationMs: Date.now() - startTime, - }), - ); - - const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); - throw err; - } - - loggerFor(this).info(responseInfo); - loggerFor(this).debug( - `[${requestLogID}] response start`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - headers: response.headers, - durationMs: headersTime - startTime, - }), - ); - - return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; - } - - async fetchWithTimeout( - url: RequestInfo, - init: RequestInit | undefined, - ms: number, - controller: AbortController, - ): Promise { - const { signal, method, ...options } = init || {}; - if (signal) signal.addEventListener('abort', () => controller.abort()); - - const timeout = setTimeout(() => controller.abort(), ms); - - const isReadableBody = - ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || - (typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body); - - const fetchOptions: RequestInit = { - signal: controller.signal as any, - ...(isReadableBody ? { duplex: 'half' } : {}), - method: 'GET', - ...options, - }; - if (method) { - // Custom methods like 'patch' need to be uppercased - // See https://github.com/nodejs/undici/issues/2294 - fetchOptions.method = method.toUpperCase(); - } - - try { - // use undefined this binding; fetch errors if bound to something else in browser/cloudflare - return await this.fetch.call(undefined, url, fetchOptions); - } finally { - clearTimeout(timeout); - } - } - - private async shouldRetry(response: Response): Promise { - // Note this is not a standard header. - const shouldRetryHeader = response.headers.get('x-should-retry'); - - // If the server explicitly says whether or not to retry, obey. - if (shouldRetryHeader === 'true') return true; - if (shouldRetryHeader === 'false') return false; - - // Retry on request timeouts. - if (response.status === 408) return true; - - // Retry on lock timeouts. - if (response.status === 409) return true; - - // Retry on rate limits. - if (response.status === 429) return true; - - // Retry internal errors. - if (response.status >= 500) return true; - - return false; - } - - private async retryRequest( - options: FinalRequestOptions, - retriesRemaining: number, - requestLogID: string, - responseHeaders?: Headers | undefined, - ): Promise { - let timeoutMillis: number | undefined; - - // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. - const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms'); - if (retryAfterMillisHeader) { - const timeoutMs = parseFloat(retryAfterMillisHeader); - if (!Number.isNaN(timeoutMs)) { - timeoutMillis = timeoutMs; - } - } - - // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After - const retryAfterHeader = responseHeaders?.get('retry-after'); - if (retryAfterHeader && !timeoutMillis) { - const timeoutSeconds = parseFloat(retryAfterHeader); - if (!Number.isNaN(timeoutSeconds)) { - timeoutMillis = timeoutSeconds * 1000; - } else { - timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); - } - } - - // If the API asks us to wait a certain amount of time (and it's a reasonable amount), - // just do what it says, but otherwise calculate a default - if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { - const maxRetries = options.maxRetries ?? this.maxRetries; - timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); - } - await sleep(timeoutMillis); - - return this.makeRequest(options, retriesRemaining - 1, requestLogID); - } - - private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { - const initialRetryDelay = 0.5; - const maxRetryDelay = 8.0; - - const numRetries = maxRetries - retriesRemaining; - - // Apply exponential backoff, but not more than the max. - const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); - - // Apply some jitter, take up to at most 25 percent of the retry time. - const jitter = 1 - Math.random() * 0.25; - - return sleepSeconds * jitter * 1000; - } - - async buildRequest( - inputOptions: FinalRequestOptions, - { retryCount = 0 }: { retryCount?: number } = {}, - ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { - const options = { ...inputOptions }; - const { method, path, query, defaultBaseURL } = options; - - const url = this.buildURL(path!, query as Record, defaultBaseURL); - if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); - options.timeout = options.timeout ?? this.timeout; - const { bodyHeaders, body } = this.buildBody({ options }); - const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); - - const req: FinalizedRequestInit = { - method, - headers: reqHeaders, - ...(options.signal && { signal: options.signal }), - ...((globalThis as any).ReadableStream && - body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }), - ...(body && { body }), - ...((this.fetchOptions as any) ?? {}), - ...((options.fetchOptions as any) ?? {}), - }; - - return { req, url, timeout: options.timeout }; - } - - private async buildHeaders({ - options, - method, - bodyHeaders, - retryCount, - }: { - options: FinalRequestOptions; - method: HTTPMethod; - bodyHeaders: HeadersLike; - retryCount: number; - }): Promise { - let idempotencyHeaders: HeadersLike = {}; - if (this.idempotencyHeader && method !== 'get') { - if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); - idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; - } - - const headers = buildHeaders([ - idempotencyHeaders, - { - Accept: 'application/json', - 'User-Agent': this.getUserAgent(), - 'X-Stainless-Retry-Count': String(retryCount), - ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), - ...getPlatformHeaders(), - }, - this._options.defaultHeaders, - bodyHeaders, - options.headers, - ]); - - this.validateHeaders(headers); - - return headers.values; - } - - private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { - bodyHeaders: HeadersLike; - body: BodyInit | undefined; - } { - if (!body) { - return { bodyHeaders: undefined, body: undefined }; - } - const headers = buildHeaders([rawHeaders]); - if ( - // Pass raw type verbatim - ArrayBuffer.isView(body) || - body instanceof ArrayBuffer || - body instanceof DataView || - (typeof body === 'string' && - // Preserve legacy string encoding behavior for now - headers.values.has('content-type')) || - // `Blob` is superset of `File` - body instanceof Blob || - // `FormData` -> `multipart/form-data` - body instanceof FormData || - // `URLSearchParams` -> `application/x-www-form-urlencoded` - body instanceof URLSearchParams || - // Send chunked stream (each chunk has own `length`) - ((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) - ) { - return { bodyHeaders: undefined, body: body as BodyInit }; - } else if ( - typeof body === 'object' && - (Symbol.asyncIterator in body || - (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) - ) { - return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; - } else { - return this.#encoder({ body, headers }); - } - } - - static Opencode = this; - static DEFAULT_TIMEOUT = 60000; // 1 minute - - static OpencodeError = Errors.OpencodeError; - static APIError = Errors.APIError; - static APIConnectionError = Errors.APIConnectionError; - static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; - static APIUserAbortError = Errors.APIUserAbortError; - static NotFoundError = Errors.NotFoundError; - static ConflictError = Errors.ConflictError; - static RateLimitError = Errors.RateLimitError; - static BadRequestError = Errors.BadRequestError; - static AuthenticationError = Errors.AuthenticationError; - static InternalServerError = Errors.InternalServerError; - static PermissionDeniedError = Errors.PermissionDeniedError; - static UnprocessableEntityError = Errors.UnprocessableEntityError; - - static toFile = Uploads.toFile; - - event: API.Event = new API.Event(this); - app: API.AppResource = new API.AppResource(this); - find: API.Find = new API.Find(this); - file: API.FileResource = new API.FileResource(this); - config: API.ConfigResource = new API.ConfigResource(this); - session: API.SessionResource = new API.SessionResource(this); - tui: API.Tui = new API.Tui(this); -} -Opencode.Event = Event; -Opencode.AppResource = AppResource; -Opencode.Find = Find; -Opencode.FileResource = FileResource; -Opencode.ConfigResource = ConfigResource; -Opencode.SessionResource = SessionResource; -Opencode.Tui = Tui; -export declare namespace Opencode { - export type RequestOptions = Opts.RequestOptions; - - export { Event as Event, type EventListResponse as EventListResponse }; - - export { - AppResource as AppResource, - type App as App, - type Mode as Mode, - type Model as Model, - type Provider as Provider, - type AppInitResponse as AppInitResponse, - type AppLogResponse as AppLogResponse, - type AppModesResponse as AppModesResponse, - type AppProvidersResponse as AppProvidersResponse, - type AppLogParams as AppLogParams, - }; - - export { - Find as Find, - type Symbol as Symbol, - type FindFilesResponse as FindFilesResponse, - type FindSymbolsResponse as FindSymbolsResponse, - type FindTextResponse as FindTextResponse, - type FindFilesParams as FindFilesParams, - type FindSymbolsParams as FindSymbolsParams, - type FindTextParams as FindTextParams, - }; - - export { - FileResource as FileResource, - type File as File, - type FileReadResponse as FileReadResponse, - type FileStatusResponse as FileStatusResponse, - type FileReadParams as FileReadParams, - }; - - export { - ConfigResource as ConfigResource, - type Config as Config, - type KeybindsConfig as KeybindsConfig, - type McpLocalConfig as McpLocalConfig, - type McpRemoteConfig as McpRemoteConfig, - type ModeConfig as ModeConfig, - }; - - export { - SessionResource as SessionResource, - type AssistantMessage as AssistantMessage, - type FilePart as FilePart, - type FilePartInput as FilePartInput, - type FilePartSource as FilePartSource, - type FilePartSourceText as FilePartSourceText, - type FileSource as FileSource, - type Message as Message, - type Part as Part, - type Session as Session, - type SnapshotPart as SnapshotPart, - type StepFinishPart as StepFinishPart, - type StepStartPart as StepStartPart, - type SymbolSource as SymbolSource, - type TextPart as TextPart, - type TextPartInput as TextPartInput, - type ToolPart as ToolPart, - type ToolStateCompleted as ToolStateCompleted, - type ToolStateError as ToolStateError, - type ToolStatePending as ToolStatePending, - type ToolStateRunning as ToolStateRunning, - type UserMessage as UserMessage, - type SessionListResponse as SessionListResponse, - type SessionDeleteResponse as SessionDeleteResponse, - type SessionAbortResponse as SessionAbortResponse, - type SessionInitResponse as SessionInitResponse, - type SessionMessagesResponse as SessionMessagesResponse, - type SessionSummarizeResponse as SessionSummarizeResponse, - type SessionChatParams as SessionChatParams, - type SessionInitParams as SessionInitParams, - type SessionRevertParams as SessionRevertParams, - type SessionSummarizeParams as SessionSummarizeParams, - }; - - export { - Tui as Tui, - type TuiAppendPromptResponse as TuiAppendPromptResponse, - type TuiOpenHelpResponse as TuiOpenHelpResponse, - type TuiAppendPromptParams as TuiAppendPromptParams, - }; - - export type MessageAbortedError = API.MessageAbortedError; - export type ProviderAuthError = API.ProviderAuthError; - export type UnknownError = API.UnknownError; -} diff --git a/packages/sdk/src/core/README.md b/packages/sdk/src/core/README.md deleted file mode 100644 index 485fce861..000000000 --- a/packages/sdk/src/core/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `core` - -This directory holds public modules implementing non-resource-specific SDK functionality. diff --git a/packages/sdk/src/core/api-promise.ts b/packages/sdk/src/core/api-promise.ts deleted file mode 100644 index fc1c6dd76..000000000 --- a/packages/sdk/src/core/api-promise.ts +++ /dev/null @@ -1,92 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { type Opencode } from '../client'; - -import { type PromiseOrValue } from '../internal/types'; -import { APIResponseProps, defaultParseResponse } from '../internal/parse'; - -/** - * A subclass of `Promise` providing additional helper methods - * for interacting with the SDK. - */ -export class APIPromise extends Promise { - private parsedPromise: Promise | undefined; - #client: Opencode; - - constructor( - client: Opencode, - private responsePromise: Promise, - private parseResponse: ( - client: Opencode, - props: APIResponseProps, - ) => PromiseOrValue = defaultParseResponse, - ) { - super((resolve) => { - // this is maybe a bit weird but this has to be a no-op to not implicitly - // parse the response body; instead .then, .catch, .finally are overridden - // to parse the response - resolve(null as any); - }); - this.#client = client; - } - - _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { - return new APIPromise(this.#client, this.responsePromise, async (client, props) => - transform(await this.parseResponse(client, props), props), - ); - } - - /** - * Gets the raw `Response` instance instead of parsing the response - * data. - * - * If you want to parse the response body but still get the `Response` - * instance, you can use {@link withResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` - * to your `tsconfig.json`. - */ - asResponse(): Promise { - return this.responsePromise.then((p) => p.response); - } - - /** - * Gets the parsed response data and the raw `Response` instance. - * - * If you just want to get the raw `Response` instance without parsing it, - * you can use {@link asResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` - * to your `tsconfig.json`. - */ - async withResponse(): Promise<{ data: T; response: Response }> { - const [data, response] = await Promise.all([this.parse(), this.asResponse()]); - return { data, response }; - } - - private parse(): Promise { - if (!this.parsedPromise) { - this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data)); - } - return this.parsedPromise; - } - - override then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, - ): Promise { - return this.parse().then(onfulfilled, onrejected); - } - - override catch( - onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, - ): Promise { - return this.parse().catch(onrejected); - } - - override finally(onfinally?: (() => void) | undefined | null): Promise { - return this.parse().finally(onfinally); - } -} diff --git a/packages/sdk/src/core/error.ts b/packages/sdk/src/core/error.ts deleted file mode 100644 index 44698c739..000000000 --- a/packages/sdk/src/core/error.ts +++ /dev/null @@ -1,130 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { castToError } from '../internal/errors'; - -export class OpencodeError extends Error {} - -export class APIError< - TStatus extends number | undefined = number | undefined, - THeaders extends Headers | undefined = Headers | undefined, - TError extends Object | undefined = Object | undefined, -> extends OpencodeError { - /** HTTP status for the response that caused the error */ - readonly status: TStatus; - /** HTTP headers for the response that caused the error */ - readonly headers: THeaders; - /** JSON body of the response that caused the error */ - readonly error: TError; - - constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { - super(`${APIError.makeMessage(status, error, message)}`); - this.status = status; - this.headers = headers; - this.error = error; - } - - private static makeMessage(status: number | undefined, error: any, message: string | undefined) { - const msg = - error?.message ? - typeof error.message === 'string' ? - error.message - : JSON.stringify(error.message) - : error ? JSON.stringify(error) - : message; - - if (status && msg) { - return `${status} ${msg}`; - } - if (status) { - return `${status} status code (no body)`; - } - if (msg) { - return msg; - } - return '(no status code or body)'; - } - - static generate( - status: number | undefined, - errorResponse: Object | undefined, - message: string | undefined, - headers: Headers | undefined, - ): APIError { - if (!status || !headers) { - return new APIConnectionError({ message, cause: castToError(errorResponse) }); - } - - const error = errorResponse as Record; - - if (status === 400) { - return new BadRequestError(status, error, message, headers); - } - - if (status === 401) { - return new AuthenticationError(status, error, message, headers); - } - - if (status === 403) { - return new PermissionDeniedError(status, error, message, headers); - } - - if (status === 404) { - return new NotFoundError(status, error, message, headers); - } - - if (status === 409) { - return new ConflictError(status, error, message, headers); - } - - if (status === 422) { - return new UnprocessableEntityError(status, error, message, headers); - } - - if (status === 429) { - return new RateLimitError(status, error, message, headers); - } - - if (status >= 500) { - return new InternalServerError(status, error, message, headers); - } - - return new APIError(status, error, message, headers); - } -} - -export class APIUserAbortError extends APIError { - constructor({ message }: { message?: string } = {}) { - super(undefined, undefined, message || 'Request was aborted.', undefined); - } -} - -export class APIConnectionError extends APIError { - constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { - super(undefined, undefined, message || 'Connection error.', undefined); - // in some environments the 'cause' property is already declared - // @ts-ignore - if (cause) this.cause = cause; - } -} - -export class APIConnectionTimeoutError extends APIConnectionError { - constructor({ message }: { message?: string } = {}) { - super({ message: message ?? 'Request timed out.' }); - } -} - -export class BadRequestError extends APIError<400, Headers> {} - -export class AuthenticationError extends APIError<401, Headers> {} - -export class PermissionDeniedError extends APIError<403, Headers> {} - -export class NotFoundError extends APIError<404, Headers> {} - -export class ConflictError extends APIError<409, Headers> {} - -export class UnprocessableEntityError extends APIError<422, Headers> {} - -export class RateLimitError extends APIError<429, Headers> {} - -export class InternalServerError extends APIError {} diff --git a/packages/sdk/src/core/resource.ts b/packages/sdk/src/core/resource.ts deleted file mode 100644 index ccf9032e4..000000000 --- a/packages/sdk/src/core/resource.ts +++ /dev/null @@ -1,11 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { Opencode } from '../client'; - -export abstract class APIResource { - protected _client: Opencode; - - constructor(client: Opencode) { - this._client = client; - } -} diff --git a/packages/sdk/src/core/streaming.ts b/packages/sdk/src/core/streaming.ts deleted file mode 100644 index 33dcfd126..000000000 --- a/packages/sdk/src/core/streaming.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { OpencodeError } from './error'; -import { type ReadableStream } from '../internal/shim-types'; -import { makeReadableStream } from '../internal/shims'; -import { findDoubleNewlineIndex, LineDecoder } from '../internal/decoders/line'; -import { ReadableStreamToAsyncIterable } from '../internal/shims'; -import { isAbortError } from '../internal/errors'; -import { encodeUTF8 } from '../internal/utils/bytes'; -import { loggerFor } from '../internal/utils/log'; -import type { Opencode } from '../client'; - -type Bytes = string | ArrayBuffer | Uint8Array | null | undefined; - -export type ServerSentEvent = { - event: string | null; - data: string; - raw: string[]; -}; - -export class Stream implements AsyncIterable { - controller: AbortController; - #client: Opencode | undefined; - - constructor( - private iterator: () => AsyncIterator, - controller: AbortController, - client?: Opencode, - ) { - this.controller = controller; - this.#client = client; - } - - static fromSSEResponse( - response: Response, - controller: AbortController, - client?: Opencode, - ): Stream { - let consumed = false; - const logger = client ? loggerFor(client) : console; - - async function* iterator(): AsyncIterator { - if (consumed) { - throw new OpencodeError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); - } - consumed = true; - let done = false; - try { - for await (const sse of _iterSSEMessages(response, controller)) { - try { - yield JSON.parse(sse.data); - } catch (e) { - logger.error(`Could not parse message into JSON:`, sse.data); - logger.error(`From chunk:`, sse.raw); - throw e; - } - } - done = true; - } catch (e) { - // If the user calls `stream.controller.abort()`, we should exit without throwing. - if (isAbortError(e)) return; - throw e; - } finally { - // If the user `break`s, abort the ongoing request. - if (!done) controller.abort(); - } - } - - return new Stream(iterator, controller, client); - } - - /** - * Generates a Stream from a newline-separated ReadableStream - * where each item is a JSON value. - */ - static fromReadableStream( - readableStream: ReadableStream, - controller: AbortController, - client?: Opencode, - ): Stream { - let consumed = false; - - async function* iterLines(): AsyncGenerator { - const lineDecoder = new LineDecoder(); - - const iter = ReadableStreamToAsyncIterable(readableStream); - for await (const chunk of iter) { - for (const line of lineDecoder.decode(chunk)) { - yield line; - } - } - - for (const line of lineDecoder.flush()) { - yield line; - } - } - - async function* iterator(): AsyncIterator { - if (consumed) { - throw new OpencodeError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); - } - consumed = true; - let done = false; - try { - for await (const line of iterLines()) { - if (done) continue; - if (line) yield JSON.parse(line); - } - done = true; - } catch (e) { - // If the user calls `stream.controller.abort()`, we should exit without throwing. - if (isAbortError(e)) return; - throw e; - } finally { - // If the user `break`s, abort the ongoing request. - if (!done) controller.abort(); - } - } - - return new Stream(iterator, controller, client); - } - - [Symbol.asyncIterator](): AsyncIterator { - return this.iterator(); - } - - /** - * Splits the stream into two streams which can be - * independently read from at different speeds. - */ - tee(): [Stream, Stream] { - const left: Array>> = []; - const right: Array>> = []; - const iterator = this.iterator(); - - const teeIterator = (queue: Array>>): AsyncIterator => { - return { - next: () => { - if (queue.length === 0) { - const result = iterator.next(); - left.push(result); - right.push(result); - } - return queue.shift()!; - }, - }; - }; - - return [ - new Stream(() => teeIterator(left), this.controller, this.#client), - new Stream(() => teeIterator(right), this.controller, this.#client), - ]; - } - - /** - * Converts this stream to a newline-separated ReadableStream of - * JSON stringified values in the stream - * which can be turned back into a Stream with `Stream.fromReadableStream()`. - */ - toReadableStream(): ReadableStream { - const self = this; - let iter: AsyncIterator; - - return makeReadableStream({ - async start() { - iter = self[Symbol.asyncIterator](); - }, - async pull(ctrl: any) { - try { - const { value, done } = await iter.next(); - if (done) return ctrl.close(); - - const bytes = encodeUTF8(JSON.stringify(value) + '\n'); - - ctrl.enqueue(bytes); - } catch (err) { - ctrl.error(err); - } - }, - async cancel() { - await iter.return?.(); - }, - }); - } -} - -export async function* _iterSSEMessages( - response: Response, - controller: AbortController, -): AsyncGenerator { - if (!response.body) { - controller.abort(); - if ( - typeof (globalThis as any).navigator !== 'undefined' && - (globalThis as any).navigator.product === 'ReactNative' - ) { - throw new OpencodeError( - `The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`, - ); - } - throw new OpencodeError(`Attempted to iterate over a response with no body`); - } - - const sseDecoder = new SSEDecoder(); - const lineDecoder = new LineDecoder(); - - const iter = ReadableStreamToAsyncIterable(response.body); - for await (const sseChunk of iterSSEChunks(iter)) { - for (const line of lineDecoder.decode(sseChunk)) { - const sse = sseDecoder.decode(line); - if (sse) yield sse; - } - } - - for (const line of lineDecoder.flush()) { - const sse = sseDecoder.decode(line); - if (sse) yield sse; - } -} - -/** - * Given an async iterable iterator, iterates over it and yields full - * SSE chunks, i.e. yields when a double new-line is encountered. - */ -async function* iterSSEChunks(iterator: AsyncIterableIterator): AsyncGenerator { - let data = new Uint8Array(); - - for await (const chunk of iterator) { - if (chunk == null) { - continue; - } - - const binaryChunk = - chunk instanceof ArrayBuffer ? new Uint8Array(chunk) - : typeof chunk === 'string' ? encodeUTF8(chunk) - : chunk; - - let newData = new Uint8Array(data.length + binaryChunk.length); - newData.set(data); - newData.set(binaryChunk, data.length); - data = newData; - - let patternIndex; - while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) { - yield data.slice(0, patternIndex); - data = data.slice(patternIndex); - } - } - - if (data.length > 0) { - yield data; - } -} - -class SSEDecoder { - private data: string[]; - private event: string | null; - private chunks: string[]; - - constructor() { - this.event = null; - this.data = []; - this.chunks = []; - } - - decode(line: string) { - if (line.endsWith('\r')) { - line = line.substring(0, line.length - 1); - } - - if (!line) { - // empty line and we didn't previously encounter any messages - if (!this.event && !this.data.length) return null; - - const sse: ServerSentEvent = { - event: this.event, - data: this.data.join('\n'), - raw: this.chunks, - }; - - this.event = null; - this.data = []; - this.chunks = []; - - return sse; - } - - this.chunks.push(line); - - if (line.startsWith(':')) { - return null; - } - - let [fieldname, _, value] = partition(line, ':'); - - if (value.startsWith(' ')) { - value = value.substring(1); - } - - if (fieldname === 'event') { - this.event = value; - } else if (fieldname === 'data') { - this.data.push(value); - } - - return null; - } -} - -function partition(str: string, delimiter: string): [string, string, string] { - const index = str.indexOf(delimiter); - if (index !== -1) { - return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)]; - } - - return [str, '', '']; -} diff --git a/packages/sdk/src/core/uploads.ts b/packages/sdk/src/core/uploads.ts deleted file mode 100644 index 2882ca6d1..000000000 --- a/packages/sdk/src/core/uploads.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { type Uploadable } from '../internal/uploads'; -export { toFile, type ToFileInput } from '../internal/to-file'; diff --git a/packages/sdk/src/error.ts b/packages/sdk/src/error.ts deleted file mode 100644 index fc55f46c0..000000000 --- a/packages/sdk/src/error.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/error instead */ -export * from './core/error'; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts deleted file mode 100644 index 43d2f6363..000000000 --- a/packages/sdk/src/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { Opencode as default } from './client'; - -export { type Uploadable, toFile } from './core/uploads'; -export { APIPromise } from './core/api-promise'; -export { Opencode, type ClientOptions } from './client'; -export { - OpencodeError, - APIError, - APIConnectionError, - APIConnectionTimeoutError, - APIUserAbortError, - NotFoundError, - ConflictError, - RateLimitError, - BadRequestError, - AuthenticationError, - InternalServerError, - PermissionDeniedError, - UnprocessableEntityError, -} from './core/error'; diff --git a/packages/sdk/src/internal/README.md b/packages/sdk/src/internal/README.md deleted file mode 100644 index 3ef5a25ba..000000000 --- a/packages/sdk/src/internal/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `internal` - -The modules in this directory are not importable outside this package and will change between releases. diff --git a/packages/sdk/src/internal/builtin-types.ts b/packages/sdk/src/internal/builtin-types.ts deleted file mode 100644 index c23d3bded..000000000 --- a/packages/sdk/src/internal/builtin-types.ts +++ /dev/null @@ -1,93 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise; - -/** - * An alias to the builtin `RequestInit` type so we can - * easily alias it in import statements if there are name clashes. - * - * https://developer.mozilla.org/docs/Web/API/RequestInit - */ -type _RequestInit = RequestInit; - -/** - * An alias to the builtin `Response` type so we can - * easily alias it in import statements if there are name clashes. - * - * https://developer.mozilla.org/docs/Web/API/Response - */ -type _Response = Response; - -/** - * The type for the first argument to `fetch`. - * - * https://developer.mozilla.org/docs/Web/API/Window/fetch#resource - */ -type _RequestInfo = Request | URL | string; - -/** - * The type for constructing `RequestInit` Headers. - * - * https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers - */ -type _HeadersInit = RequestInit['headers']; - -/** - * The type for constructing `RequestInit` body. - * - * https://developer.mozilla.org/docs/Web/API/RequestInit#body - */ -type _BodyInit = RequestInit['body']; - -/** - * An alias to the builtin `Array` type so we can - * easily alias it in import statements if there are name clashes. - */ -type _Array = Array; - -/** - * An alias to the builtin `Record` type so we can - * easily alias it in import statements if there are name clashes. - */ -type _Record = Record; - -export type { - _Array as Array, - _BodyInit as BodyInit, - _HeadersInit as HeadersInit, - _Record as Record, - _RequestInfo as RequestInfo, - _RequestInit as RequestInit, - _Response as Response, -}; - -/** - * A copy of the builtin `EndingType` type as it isn't fully supported in certain - * environments and attempting to reference the global version will error. - * - * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941 - */ -type EndingType = 'native' | 'transparent'; - -/** - * A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain - * environments and attempting to reference the global version will error. - * - * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154 - * https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options - */ -export interface BlobPropertyBag { - endings?: EndingType; - type?: string; -} - -/** - * A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain - * environments and attempting to reference the global version will error. - * - * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503 - * https://developer.mozilla.org/en-US/docs/Web/API/File/File#options - */ -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} diff --git a/packages/sdk/src/internal/decoders/line.ts b/packages/sdk/src/internal/decoders/line.ts deleted file mode 100644 index b3bfa97cd..000000000 --- a/packages/sdk/src/internal/decoders/line.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { concatBytes, decodeUTF8, encodeUTF8 } from '../utils/bytes'; - -export type Bytes = string | ArrayBuffer | Uint8Array | null | undefined; - -/** - * A re-implementation of httpx's `LineDecoder` in Python that handles incrementally - * reading lines from text. - * - * https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258 - */ -export class LineDecoder { - // prettier-ignore - static NEWLINE_CHARS = new Set(['\n', '\r']); - static NEWLINE_REGEXP = /\r\n|[\n\r]/g; - - #buffer: Uint8Array; - #carriageReturnIndex: number | null; - - constructor() { - this.#buffer = new Uint8Array(); - this.#carriageReturnIndex = null; - } - - decode(chunk: Bytes): string[] { - if (chunk == null) { - return []; - } - - const binaryChunk = - chunk instanceof ArrayBuffer ? new Uint8Array(chunk) - : typeof chunk === 'string' ? encodeUTF8(chunk) - : chunk; - - this.#buffer = concatBytes([this.#buffer, binaryChunk]); - - const lines: string[] = []; - let patternIndex; - while ((patternIndex = findNewlineIndex(this.#buffer, this.#carriageReturnIndex)) != null) { - if (patternIndex.carriage && this.#carriageReturnIndex == null) { - // skip until we either get a corresponding `\n`, a new `\r` or nothing - this.#carriageReturnIndex = patternIndex.index; - continue; - } - - // we got double \r or \rtext\n - if ( - this.#carriageReturnIndex != null && - (patternIndex.index !== this.#carriageReturnIndex + 1 || patternIndex.carriage) - ) { - lines.push(decodeUTF8(this.#buffer.subarray(0, this.#carriageReturnIndex - 1))); - this.#buffer = this.#buffer.subarray(this.#carriageReturnIndex); - this.#carriageReturnIndex = null; - continue; - } - - const endIndex = - this.#carriageReturnIndex !== null ? patternIndex.preceding - 1 : patternIndex.preceding; - - const line = decodeUTF8(this.#buffer.subarray(0, endIndex)); - lines.push(line); - - this.#buffer = this.#buffer.subarray(patternIndex.index); - this.#carriageReturnIndex = null; - } - - return lines; - } - - flush(): string[] { - if (!this.#buffer.length) { - return []; - } - return this.decode('\n'); - } -} - -/** - * This function searches the buffer for the end patterns, (\r or \n) - * and returns an object with the index preceding the matched newline and the - * index after the newline char. `null` is returned if no new line is found. - * - * ```ts - * findNewLineIndex('abc\ndef') -> { preceding: 2, index: 3 } - * ``` - */ -function findNewlineIndex( - buffer: Uint8Array, - startIndex: number | null, -): { preceding: number; index: number; carriage: boolean } | null { - const newline = 0x0a; // \n - const carriage = 0x0d; // \r - - for (let i = startIndex ?? 0; i < buffer.length; i++) { - if (buffer[i] === newline) { - return { preceding: i, index: i + 1, carriage: false }; - } - - if (buffer[i] === carriage) { - return { preceding: i, index: i + 1, carriage: true }; - } - } - - return null; -} - -export function findDoubleNewlineIndex(buffer: Uint8Array): number { - // This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n) - // and returns the index right after the first occurrence of any pattern, - // or -1 if none of the patterns are found. - const newline = 0x0a; // \n - const carriage = 0x0d; // \r - - for (let i = 0; i < buffer.length - 1; i++) { - if (buffer[i] === newline && buffer[i + 1] === newline) { - // \n\n - return i + 2; - } - if (buffer[i] === carriage && buffer[i + 1] === carriage) { - // \r\r - return i + 2; - } - if ( - buffer[i] === carriage && - buffer[i + 1] === newline && - i + 3 < buffer.length && - buffer[i + 2] === carriage && - buffer[i + 3] === newline - ) { - // \r\n\r\n - return i + 4; - } - } - - return -1; -} diff --git a/packages/sdk/src/internal/detect-platform.ts b/packages/sdk/src/internal/detect-platform.ts deleted file mode 100644 index e82d95c92..000000000 --- a/packages/sdk/src/internal/detect-platform.ts +++ /dev/null @@ -1,196 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { VERSION } from '../version'; - -export const isRunningInBrowser = () => { - return ( - // @ts-ignore - typeof window !== 'undefined' && - // @ts-ignore - typeof window.document !== 'undefined' && - // @ts-ignore - typeof navigator !== 'undefined' - ); -}; - -type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown'; - -/** - * Note this does not detect 'browser'; for that, use getBrowserInfo(). - */ -function getDetectedPlatform(): DetectedPlatform { - if (typeof Deno !== 'undefined' && Deno.build != null) { - return 'deno'; - } - if (typeof EdgeRuntime !== 'undefined') { - return 'edge'; - } - if ( - Object.prototype.toString.call( - typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0, - ) === '[object process]' - ) { - return 'node'; - } - return 'unknown'; -} - -declare const Deno: any; -declare const EdgeRuntime: any; -type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; -type PlatformName = - | 'MacOS' - | 'Linux' - | 'Windows' - | 'FreeBSD' - | 'OpenBSD' - | 'iOS' - | 'Android' - | `Other:${string}` - | 'Unknown'; -type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; -type PlatformProperties = { - 'X-Stainless-Lang': 'js'; - 'X-Stainless-Package-Version': string; - 'X-Stainless-OS': PlatformName; - 'X-Stainless-Arch': Arch; - 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; - 'X-Stainless-Runtime-Version': string; -}; -const getPlatformProperties = (): PlatformProperties => { - const detectedPlatform = getDetectedPlatform(); - if (detectedPlatform === 'deno') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': normalizePlatform(Deno.build.os), - 'X-Stainless-Arch': normalizeArch(Deno.build.arch), - 'X-Stainless-Runtime': 'deno', - 'X-Stainless-Runtime-Version': - typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown', - }; - } - if (typeof EdgeRuntime !== 'undefined') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': `other:${EdgeRuntime}`, - 'X-Stainless-Runtime': 'edge', - 'X-Stainless-Runtime-Version': (globalThis as any).process.version, - }; - } - // Check if Node.js - if (detectedPlatform === 'node') { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'), - 'X-Stainless-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'), - 'X-Stainless-Runtime': 'node', - 'X-Stainless-Runtime-Version': (globalThis as any).process.version ?? 'unknown', - }; - } - - const browserInfo = getBrowserInfo(); - if (browserInfo) { - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': 'unknown', - 'X-Stainless-Runtime': `browser:${browserInfo.browser}`, - 'X-Stainless-Runtime-Version': browserInfo.version, - }; - } - - // TODO add support for Cloudflare workers, etc. - return { - 'X-Stainless-Lang': 'js', - 'X-Stainless-Package-Version': VERSION, - 'X-Stainless-OS': 'Unknown', - 'X-Stainless-Arch': 'unknown', - 'X-Stainless-Runtime': 'unknown', - 'X-Stainless-Runtime-Version': 'unknown', - }; -}; - -type BrowserInfo = { - browser: Browser; - version: string; -}; - -declare const navigator: { userAgent: string } | undefined; - -// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts -function getBrowserInfo(): BrowserInfo | null { - if (typeof navigator === 'undefined' || !navigator) { - return null; - } - - // NOTE: The order matters here! - const browserPatterns = [ - { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, - { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, - ]; - - // Find the FIRST matching browser - for (const { key, pattern } of browserPatterns) { - const match = pattern.exec(navigator.userAgent); - if (match) { - const major = match[1] || 0; - const minor = match[2] || 0; - const patch = match[3] || 0; - - return { browser: key, version: `${major}.${minor}.${patch}` }; - } - } - - return null; -} - -const normalizeArch = (arch: string): Arch => { - // Node docs: - // - https://nodejs.org/api/process.html#processarch - // Deno docs: - // - https://doc.deno.land/deno/stable/~/Deno.build - if (arch === 'x32') return 'x32'; - if (arch === 'x86_64' || arch === 'x64') return 'x64'; - if (arch === 'arm') return 'arm'; - if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; - if (arch) return `other:${arch}`; - return 'unknown'; -}; - -const normalizePlatform = (platform: string): PlatformName => { - // Node platforms: - // - https://nodejs.org/api/process.html#processplatform - // Deno platforms: - // - https://doc.deno.land/deno/stable/~/Deno.build - // - https://github.com/denoland/deno/issues/14799 - - platform = platform.toLowerCase(); - - // NOTE: this iOS check is untested and may not work - // Node does not work natively on IOS, there is a fork at - // https://github.com/nodejs-mobile/nodejs-mobile - // however it is unknown at the time of writing how to detect if it is running - if (platform.includes('ios')) return 'iOS'; - if (platform === 'android') return 'Android'; - if (platform === 'darwin') return 'MacOS'; - if (platform === 'win32') return 'Windows'; - if (platform === 'freebsd') return 'FreeBSD'; - if (platform === 'openbsd') return 'OpenBSD'; - if (platform === 'linux') return 'Linux'; - if (platform) return `Other:${platform}`; - return 'Unknown'; -}; - -let _platformHeaders: PlatformProperties; -export const getPlatformHeaders = () => { - return (_platformHeaders ??= getPlatformProperties()); -}; diff --git a/packages/sdk/src/internal/errors.ts b/packages/sdk/src/internal/errors.ts deleted file mode 100644 index 82c7b14d5..000000000 --- a/packages/sdk/src/internal/errors.ts +++ /dev/null @@ -1,33 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export function isAbortError(err: unknown) { - return ( - typeof err === 'object' && - err !== null && - // Spec-compliant fetch implementations - (('name' in err && (err as any).name === 'AbortError') || - // Expo fetch - ('message' in err && String((err as any).message).includes('FetchRequestCanceledException'))) - ); -} - -export const castToError = (err: any): Error => { - if (err instanceof Error) return err; - if (typeof err === 'object' && err !== null) { - try { - if (Object.prototype.toString.call(err) === '[object Error]') { - // @ts-ignore - not all envs have native support for cause yet - const error = new Error(err.message, err.cause ? { cause: err.cause } : {}); - if (err.stack) error.stack = err.stack; - // @ts-ignore - not all envs have native support for cause yet - if (err.cause && !error.cause) error.cause = err.cause; - if (err.name) error.name = err.name; - return error; - } - } catch {} - try { - return new Error(JSON.stringify(err)); - } catch {} - } - return new Error(err); -}; diff --git a/packages/sdk/src/internal/headers.ts b/packages/sdk/src/internal/headers.ts deleted file mode 100644 index c724a9d22..000000000 --- a/packages/sdk/src/internal/headers.ts +++ /dev/null @@ -1,97 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isReadonlyArray } from './utils/values'; - -type HeaderValue = string | undefined | null; -export type HeadersLike = - | Headers - | readonly HeaderValue[][] - | Record - | undefined - | null - | NullableHeaders; - -const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); - -/** - * @internal - * Users can pass explicit nulls to unset default headers. When we parse them - * into a standard headers type we need to preserve that information. - */ -export type NullableHeaders = { - /** Brand check, prevent users from creating a NullableHeaders. */ - [brand_privateNullableHeaders]: true; - /** Parsed headers. */ - values: Headers; - /** Set of lowercase header names explicitly set to null. */ - nulls: Set; -}; - -function* iterateHeaders(headers: HeadersLike): IterableIterator { - if (!headers) return; - - if (brand_privateNullableHeaders in headers) { - const { values, nulls } = headers; - yield* values.entries(); - for (const name of nulls) { - yield [name, null]; - } - return; - } - - let shouldClear = false; - let iter: Iterable; - if (headers instanceof Headers) { - iter = headers.entries(); - } else if (isReadonlyArray(headers)) { - iter = headers; - } else { - shouldClear = true; - iter = Object.entries(headers ?? {}); - } - for (let row of iter) { - const name = row[0]; - if (typeof name !== 'string') throw new TypeError('expected header name to be a string'); - const values = isReadonlyArray(row[1]) ? row[1] : [row[1]]; - let didClear = false; - for (const value of values) { - if (value === undefined) continue; - - // Objects keys always overwrite older headers, they never append. - // Yield a null to clear the header before adding the new values. - if (shouldClear && !didClear) { - didClear = true; - yield [name, null]; - } - yield [name, value]; - } - } -} - -export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => { - const targetHeaders = new Headers(); - const nullHeaders = new Set(); - for (const headers of newHeaders) { - const seenHeaders = new Set(); - for (const [name, value] of iterateHeaders(headers)) { - const lowerName = name.toLowerCase(); - if (!seenHeaders.has(lowerName)) { - targetHeaders.delete(name); - seenHeaders.add(lowerName); - } - if (value === null) { - targetHeaders.delete(name); - nullHeaders.add(lowerName); - } else { - targetHeaders.append(name, value); - nullHeaders.delete(lowerName); - } - } - } - return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders }; -}; - -export const isEmptyHeaders = (headers: HeadersLike) => { - for (const _ of iterateHeaders(headers)) return false; - return true; -}; diff --git a/packages/sdk/src/internal/parse.ts b/packages/sdk/src/internal/parse.ts deleted file mode 100644 index dcb4026e3..000000000 --- a/packages/sdk/src/internal/parse.ts +++ /dev/null @@ -1,64 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import type { FinalRequestOptions } from './request-options'; -import { Stream } from '../core/streaming'; -import { type Opencode } from '../client'; -import { formatRequestDetails, loggerFor } from './utils/log'; - -export type APIResponseProps = { - response: Response; - options: FinalRequestOptions; - controller: AbortController; - requestLogID: string; - retryOfRequestLogID: string | undefined; - startTime: number; -}; - -export async function defaultParseResponse(client: Opencode, props: APIResponseProps): Promise { - const { response, requestLogID, retryOfRequestLogID, startTime } = props; - const body = await (async () => { - if (props.options.stream) { - loggerFor(client).debug('response', response.status, response.url, response.headers, response.body); - - // Note: there is an invariant here that isn't represented in the type system - // that if you set `stream: true` the response type must also be `Stream` - - if (props.options.__streamClass) { - return props.options.__streamClass.fromSSEResponse(response, props.controller, client) as any; - } - - return Stream.fromSSEResponse(response, props.controller, client) as any; - } - - // fetch refuses to read the body when the status code is 204. - if (response.status === 204) { - return null as T; - } - - if (props.options.__binaryResponse) { - return response as unknown as T; - } - - const contentType = response.headers.get('content-type'); - const mediaType = contentType?.split(';')[0]?.trim(); - const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); - if (isJSON) { - const json = await response.json(); - return json as T; - } - - const text = await response.text(); - return text as unknown as T; - })(); - loggerFor(client).debug( - `[${requestLogID}] response parsed`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - body, - durationMs: Date.now() - startTime, - }), - ); - return body; -} diff --git a/packages/sdk/src/internal/request-options.ts b/packages/sdk/src/internal/request-options.ts deleted file mode 100644 index 56765e5aa..000000000 --- a/packages/sdk/src/internal/request-options.ts +++ /dev/null @@ -1,93 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { NullableHeaders } from './headers'; - -import type { BodyInit } from './builtin-types'; -import { Stream } from '../core/streaming'; -import type { HTTPMethod, MergedRequestInit } from './types'; -import { type HeadersLike } from './headers'; - -export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; - -export type RequestOptions = { - /** - * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete'). - */ - method?: HTTPMethod; - - /** - * The URL path for the request. - * - * @example "/v1/foo" - */ - path?: string; - - /** - * Query parameters to include in the request URL. - */ - query?: object | undefined | null; - - /** - * The request body. Can be a string, JSON object, FormData, or other supported types. - */ - body?: unknown; - - /** - * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples. - */ - headers?: HeadersLike; - - /** - * The maximum number of times that the client will retry a request in case of a - * temporary failure, like a network error or a 5XX error from the server. - * - * @default 2 - */ - maxRetries?: number; - - stream?: boolean | undefined; - - /** - * The maximum amount of time (in milliseconds) that the client should wait for a response - * from the server before timing out a single request. - * - * @unit milliseconds - */ - timeout?: number; - - /** - * Additional `RequestInit` options to be passed to the underlying `fetch` call. - * These options will be merged with the client's default fetch options. - */ - fetchOptions?: MergedRequestInit; - - /** - * An AbortSignal that can be used to cancel the request. - */ - signal?: AbortSignal | undefined | null; - - /** - * A unique key for this request to enable idempotency. - */ - idempotencyKey?: string; - - /** - * Override the default base URL for this specific request. - */ - defaultBaseURL?: string | undefined; - - __binaryResponse?: boolean | undefined; - __streamClass?: typeof Stream; -}; - -export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; -export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent; - -export const FallbackEncoder: RequestEncoder = ({ headers, body }) => { - return { - bodyHeaders: { - 'content-type': 'application/json', - }, - body: JSON.stringify(body), - }; -}; diff --git a/packages/sdk/src/internal/shim-types.ts b/packages/sdk/src/internal/shim-types.ts deleted file mode 100644 index 8ddf7b0ad..000000000 --- a/packages/sdk/src/internal/shim-types.ts +++ /dev/null @@ -1,26 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * Shims for types that we can't always rely on being available globally. - * - * Note: these only exist at the type-level, there is no corresponding runtime - * version for any of these symbols. - */ - -type NeverToAny = T extends never ? any : T; - -/** @ts-ignore */ -type _DOMReadableStream = globalThis.ReadableStream; - -/** @ts-ignore */ -type _NodeReadableStream = import('stream/web').ReadableStream; - -type _ConditionalNodeReadableStream = - typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream; - -type _ReadableStream = NeverToAny< - | ([0] extends [1 & _DOMReadableStream] ? never : _DOMReadableStream) - | ([0] extends [1 & _ConditionalNodeReadableStream] ? never : _ConditionalNodeReadableStream) ->; - -export type { _ReadableStream as ReadableStream }; diff --git a/packages/sdk/src/internal/shims.ts b/packages/sdk/src/internal/shims.ts deleted file mode 100644 index 17a7967aa..000000000 --- a/packages/sdk/src/internal/shims.ts +++ /dev/null @@ -1,107 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available. - * - * These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error - * messages in cases where an environment isn't fully supported. - */ - -import type { Fetch } from './builtin-types'; -import type { ReadableStream } from './shim-types'; - -export function getDefaultFetch(): Fetch { - if (typeof fetch !== 'undefined') { - return fetch as any; - } - - throw new Error( - '`fetch` is not defined as a global; Either pass `fetch` to the client, `new Opencode({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', - ); -} - -type ReadableStreamArgs = ConstructorParameters; - -export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream { - const ReadableStream = (globalThis as any).ReadableStream; - if (typeof ReadableStream === 'undefined') { - // Note: All of the platforms / runtimes we officially support already define - // `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes. - throw new Error( - '`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`', - ); - } - - return new ReadableStream(...args); -} - -export function ReadableStreamFrom(iterable: Iterable | AsyncIterable): ReadableStream { - let iter: AsyncIterator | Iterator = - Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator](); - - return makeReadableStream({ - start() {}, - async pull(controller: any) { - const { done, value } = await iter.next(); - if (done) { - controller.close(); - } else { - controller.enqueue(value); - } - }, - async cancel() { - await iter.return?.(); - }, - }); -} - -/** - * Most browsers don't yet have async iterable support for ReadableStream, - * and Node has a very different way of reading bytes from its "ReadableStream". - * - * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490 - */ -export function ReadableStreamToAsyncIterable(stream: any): AsyncIterableIterator { - if (stream[Symbol.asyncIterator]) return stream; - - const reader = stream.getReader(); - return { - async next() { - try { - const result = await reader.read(); - if (result?.done) reader.releaseLock(); // release lock when stream becomes closed - return result; - } catch (e) { - reader.releaseLock(); // release lock when stream becomes errored - throw e; - } - }, - async return() { - const cancelPromise = reader.cancel(); - reader.releaseLock(); - await cancelPromise; - return { done: true, value: undefined }; - }, - [Symbol.asyncIterator]() { - return this; - }, - }; -} - -/** - * Cancels a ReadableStream we don't need to consume. - * See https://undici.nodejs.org/#/?id=garbage-collection - */ -export async function CancelReadableStream(stream: any): Promise { - if (stream === null || typeof stream !== 'object') return; - - if (stream[Symbol.asyncIterator]) { - await stream[Symbol.asyncIterator]().return?.(); - return; - } - - const reader = stream.getReader(); - const cancelPromise = reader.cancel(); - reader.releaseLock(); - await cancelPromise; -} diff --git a/packages/sdk/src/internal/to-file.ts b/packages/sdk/src/internal/to-file.ts deleted file mode 100644 index 245e84933..000000000 --- a/packages/sdk/src/internal/to-file.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads'; -import type { FilePropertyBag } from './builtin-types'; -import { checkFileSupport } from './uploads'; - -type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView; - -/** - * Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc. - * Don't add arrayBuffer here, node-fetch doesn't have it - */ -interface BlobLike { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ - readonly size: number; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ - readonly type: string; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ - text(): Promise; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ - slice(start?: number, end?: number): BlobLike; -} - -/** - * This check adds the arrayBuffer() method type because it is available and used at runtime - */ -const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => - value != null && - typeof value === 'object' && - typeof value.size === 'number' && - typeof value.type === 'string' && - typeof value.text === 'function' && - typeof value.slice === 'function' && - typeof value.arrayBuffer === 'function'; - -/** - * Intended to match DOM File, node:buffer File, undici File, etc. - */ -interface FileLike extends BlobLike { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ - readonly lastModified: number; - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ - readonly name?: string | undefined; -} - -/** - * This check adds the arrayBuffer() method type because it is available and used at runtime - */ -const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise } => - value != null && - typeof value === 'object' && - typeof value.name === 'string' && - typeof value.lastModified === 'number' && - isBlobLike(value); - -/** - * Intended to match DOM Response, node-fetch Response, undici Response, etc. - */ -export interface ResponseLike { - url: string; - blob(): Promise; -} - -const isResponseLike = (value: any): value is ResponseLike => - value != null && - typeof value === 'object' && - typeof value.url === 'string' && - typeof value.blob === 'function'; - -export type ToFileInput = - | FileLike - | ResponseLike - | Exclude - | AsyncIterable; - -/** - * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats - * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s - * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible - * @param {Object=} options additional properties - * @param {string=} options.type the MIME type of the content - * @param {number=} options.lastModified the last modified timestamp - * @returns a {@link File} with the given properties - */ -export async function toFile( - value: ToFileInput | PromiseLike, - name?: string | null | undefined, - options?: FilePropertyBag | undefined, -): Promise { - checkFileSupport(); - - // If it's a promise, resolve it. - value = await value; - - // If we've been given a `File` we don't need to do anything - if (isFileLike(value)) { - if (value instanceof File) { - return value; - } - return makeFile([await value.arrayBuffer()], value.name); - } - - if (isResponseLike(value)) { - const blob = await value.blob(); - name ||= new URL(value.url).pathname.split(/[\\/]/).pop(); - - return makeFile(await getBytes(blob), name, options); - } - - const parts = await getBytes(value); - - name ||= getName(value); - - if (!options?.type) { - const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type); - if (typeof type === 'string') { - options = { ...options, type }; - } - } - - return makeFile(parts, name, options); -} - -async function getBytes(value: BlobLikePart | AsyncIterable): Promise> { - let parts: Array = []; - if ( - typeof value === 'string' || - ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. - value instanceof ArrayBuffer - ) { - parts.push(value); - } else if (isBlobLike(value)) { - parts.push(value instanceof Blob ? value : await value.arrayBuffer()); - } else if ( - isAsyncIterable(value) // includes Readable, ReadableStream, etc. - ) { - for await (const chunk of value) { - parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating? - } - } else { - const constructor = value?.constructor?.name; - throw new Error( - `Unexpected data type: ${typeof value}${ - constructor ? `; constructor: ${constructor}` : '' - }${propsForError(value)}`, - ); - } - - return parts; -} - -function propsForError(value: unknown): string { - if (typeof value !== 'object' || value === null) return ''; - const props = Object.getOwnPropertyNames(value); - return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`; -} diff --git a/packages/sdk/src/internal/types.ts b/packages/sdk/src/internal/types.ts deleted file mode 100644 index b668dfc0f..000000000 --- a/packages/sdk/src/internal/types.ts +++ /dev/null @@ -1,95 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export type PromiseOrValue = T | Promise; -export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; - -export type KeysEnum = { [P in keyof Required]: true }; - -export type FinalizedRequestInit = RequestInit & { headers: Headers }; - -type NotAny = [0] extends [1 & T] ? never : T; - -/** - * Some environments overload the global fetch function, and Parameters only gets the last signature. - */ -type OverloadedParameters = - T extends ( - { - (...args: infer A): unknown; - (...args: infer B): unknown; - (...args: infer C): unknown; - (...args: infer D): unknown; - } - ) ? - A | B | C | D - : T extends ( - { - (...args: infer A): unknown; - (...args: infer B): unknown; - (...args: infer C): unknown; - } - ) ? - A | B | C - : T extends ( - { - (...args: infer A): unknown; - (...args: infer B): unknown; - } - ) ? - A | B - : T extends (...args: infer A) => unknown ? A - : never; - -/* eslint-disable */ -/** - * These imports attempt to get types from a parent package's dependencies. - * Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which - * would cause typescript to show types not present at runtime. To avoid this, we import - * directly from parent node_modules folders. - * - * We need to check multiple levels because we don't know what directory structure we'll be in. - * For example, pnpm generates directories like this: - * ``` - * node_modules - * ├── .pnpm - * │ └── pkg@1.0.0 - * │ └── node_modules - * │ └── pkg - * │ └── internal - * │ └── types.d.ts - * ├── pkg -> .pnpm/pkg@1.0.0/node_modules/pkg - * └── undici - * ``` - * - * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition - */ -/** @ts-ignore For users with \@types/node */ -type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; -/** @ts-ignore For users with undici */ -type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; -/** @ts-ignore For users with \@types/bun */ -type BunRequestInit = globalThis.FetchRequestInit; -/** @ts-ignore For users with node-fetch@2 */ -type NodeFetch2RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; -/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */ -type NodeFetch3RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; -/** @ts-ignore For users who use Deno */ -type FetchRequestInit = NonNullable[1]>; -/* eslint-enable */ - -type RequestInits = - | NotAny - | NotAny - | NotAny - | NotAny - | NotAny - | NotAny - | NotAny; - -/** - * This type contains `RequestInit` options that may be available on the current runtime, - * including per-platform extensions like `dispatcher`, `agent`, `client`, etc. - */ -export type MergedRequestInit = RequestInits & - /** We don't include these in the types as they'll be overridden for every request. */ - Partial>; diff --git a/packages/sdk/src/internal/uploads.ts b/packages/sdk/src/internal/uploads.ts deleted file mode 100644 index eb55f834e..000000000 --- a/packages/sdk/src/internal/uploads.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { type RequestOptions } from './request-options'; -import type { FilePropertyBag, Fetch } from './builtin-types'; -import type { Opencode } from '../client'; -import { ReadableStreamFrom } from './shims'; - -export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView; -type FsReadStream = AsyncIterable & { path: string | { toString(): string } }; - -// https://github.com/oven-sh/bun/issues/5980 -interface BunFile extends Blob { - readonly name?: string | undefined; -} - -export const checkFileSupport = () => { - if (typeof File === 'undefined') { - const { process } = globalThis as any; - const isOldNode = - typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20; - throw new Error( - '`File` is not defined as a global, which is required for file uploads.' + - (isOldNode ? - " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`." - : ''), - ); - } -}; - -/** - * Typically, this is a native "File" class. - * - * We provide the {@link toFile} utility to convert a variety of objects - * into the File class. - * - * For convenience, you can also pass a fetch Response, or in Node, - * the result of fs.createReadStream(). - */ -export type Uploadable = File | Response | FsReadStream | BunFile; - -/** - * Construct a `File` instance. This is used to ensure a helpful error is thrown - * for environments that don't define a global `File` yet. - */ -export function makeFile( - fileBits: BlobPart[], - fileName: string | undefined, - options?: FilePropertyBag, -): File { - checkFileSupport(); - return new File(fileBits as any, fileName ?? 'unknown_file', options); -} - -export function getName(value: any): string | undefined { - return ( - ( - (typeof value === 'object' && - value !== null && - (('name' in value && value.name && String(value.name)) || - ('url' in value && value.url && String(value.url)) || - ('filename' in value && value.filename && String(value.filename)) || - ('path' in value && value.path && String(value.path)))) || - '' - ) - .split(/[\\/]/) - .pop() || undefined - ); -} - -export const isAsyncIterable = (value: any): value is AsyncIterable => - value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; - -/** - * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. - * Otherwise returns the request as is. - */ -export const maybeMultipartFormRequestOptions = async ( - opts: RequestOptions, - fetch: Opencode | Fetch, -): Promise => { - if (!hasUploadableValue(opts.body)) return opts; - - return { ...opts, body: await createForm(opts.body, fetch) }; -}; - -type MultipartFormRequestOptions = Omit & { body: unknown }; - -export const multipartFormRequestOptions = async ( - opts: MultipartFormRequestOptions, - fetch: Opencode | Fetch, -): Promise => { - return { ...opts, body: await createForm(opts.body, fetch) }; -}; - -const supportsFormDataMap = /* @__PURE__ */ new WeakMap>(); - -/** - * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending - * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]". - * This function detects if the fetch function provided supports the global FormData object to avoid - * confusing error messages later on. - */ -function supportsFormData(fetchObject: Opencode | Fetch): Promise { - const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch; - const cached = supportsFormDataMap.get(fetch); - if (cached) return cached; - const promise = (async () => { - try { - const FetchResponse = ( - 'Response' in fetch ? - fetch.Response - : (await fetch('data:,')).constructor) as typeof Response; - const data = new FormData(); - if (data.toString() === (await new FetchResponse(data).text())) { - return false; - } - return true; - } catch { - // avoid false negatives - return true; - } - })(); - supportsFormDataMap.set(fetch, promise); - return promise; -} - -export const createForm = async >( - body: T | undefined, - fetch: Opencode | Fetch, -): Promise => { - if (!(await supportsFormData(fetch))) { - throw new TypeError( - 'The provided fetch function does not support file uploads with the current global FormData class.', - ); - } - const form = new FormData(); - await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); - return form; -}; - -// We check for Blob not File because Bun.File doesn't inherit from File, -// but they both inherit from Blob and have a `name` property at runtime. -const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value; - -const isUploadable = (value: unknown) => - typeof value === 'object' && - value !== null && - (value instanceof Response || isAsyncIterable(value) || isNamedBlob(value)); - -const hasUploadableValue = (value: unknown): boolean => { - if (isUploadable(value)) return true; - if (Array.isArray(value)) return value.some(hasUploadableValue); - if (value && typeof value === 'object') { - for (const k in value) { - if (hasUploadableValue((value as any)[k])) return true; - } - } - return false; -}; - -const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { - if (value === undefined) return; - if (value == null) { - throw new TypeError( - `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, - ); - } - - // TODO: make nested formats configurable - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { - form.append(key, String(value)); - } else if (value instanceof Response) { - form.append(key, makeFile([await value.blob()], getName(value))); - } else if (isAsyncIterable(value)) { - form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value))); - } else if (isNamedBlob(value)) { - form.append(key, value, getName(value)); - } else if (Array.isArray(value)) { - await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); - } else if (typeof value === 'object') { - await Promise.all( - Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), - ); - } else { - throw new TypeError( - `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, - ); - } -}; diff --git a/packages/sdk/src/internal/utils.ts b/packages/sdk/src/internal/utils.ts deleted file mode 100644 index 3cbfacce2..000000000 --- a/packages/sdk/src/internal/utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export * from './utils/values'; -export * from './utils/base64'; -export * from './utils/env'; -export * from './utils/log'; -export * from './utils/uuid'; -export * from './utils/sleep'; diff --git a/packages/sdk/src/internal/utils/base64.ts b/packages/sdk/src/internal/utils/base64.ts deleted file mode 100644 index 05d7bd9b4..000000000 --- a/packages/sdk/src/internal/utils/base64.ts +++ /dev/null @@ -1,40 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { OpencodeError } from '../../core/error'; -import { encodeUTF8 } from './bytes'; - -export const toBase64 = (data: string | Uint8Array | null | undefined): string => { - if (!data) return ''; - - if (typeof (globalThis as any).Buffer !== 'undefined') { - return (globalThis as any).Buffer.from(data).toString('base64'); - } - - if (typeof data === 'string') { - data = encodeUTF8(data); - } - - if (typeof btoa !== 'undefined') { - return btoa(String.fromCharCode.apply(null, data as any)); - } - - throw new OpencodeError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); -}; - -export const fromBase64 = (str: string): Uint8Array => { - if (typeof (globalThis as any).Buffer !== 'undefined') { - const buf = (globalThis as any).Buffer.from(str, 'base64'); - return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); - } - - if (typeof atob !== 'undefined') { - const bstr = atob(str); - const buf = new Uint8Array(bstr.length); - for (let i = 0; i < bstr.length; i++) { - buf[i] = bstr.charCodeAt(i); - } - return buf; - } - - throw new OpencodeError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); -}; diff --git a/packages/sdk/src/internal/utils/bytes.ts b/packages/sdk/src/internal/utils/bytes.ts deleted file mode 100644 index 8da627abe..000000000 --- a/packages/sdk/src/internal/utils/bytes.ts +++ /dev/null @@ -1,32 +0,0 @@ -export function concatBytes(buffers: Uint8Array[]): Uint8Array { - let length = 0; - for (const buffer of buffers) { - length += buffer.length; - } - const output = new Uint8Array(length); - let index = 0; - for (const buffer of buffers) { - output.set(buffer, index); - index += buffer.length; - } - - return output; -} - -let encodeUTF8_: (str: string) => Uint8Array; -export function encodeUTF8(str: string) { - let encoder; - return ( - encodeUTF8_ ?? - ((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder))) - )(str); -} - -let decodeUTF8_: (bytes: Uint8Array) => string; -export function decodeUTF8(bytes: Uint8Array) { - let decoder; - return ( - decodeUTF8_ ?? - ((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder))) - )(bytes); -} diff --git a/packages/sdk/src/internal/utils/env.ts b/packages/sdk/src/internal/utils/env.ts deleted file mode 100644 index 2d8480077..000000000 --- a/packages/sdk/src/internal/utils/env.ts +++ /dev/null @@ -1,18 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * Read an environment variable. - * - * Trims beginning and trailing whitespace. - * - * Will return undefined if the environment variable doesn't exist or cannot be accessed. - */ -export const readEnv = (env: string): string | undefined => { - if (typeof (globalThis as any).process !== 'undefined') { - return (globalThis as any).process.env?.[env]?.trim() ?? undefined; - } - if (typeof (globalThis as any).Deno !== 'undefined') { - return (globalThis as any).Deno.env?.get?.(env)?.trim(); - } - return undefined; -}; diff --git a/packages/sdk/src/internal/utils/log.ts b/packages/sdk/src/internal/utils/log.ts deleted file mode 100644 index 44ac16a02..000000000 --- a/packages/sdk/src/internal/utils/log.ts +++ /dev/null @@ -1,126 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { hasOwn } from './values'; -import { type Opencode } from '../../client'; -import { RequestOptions } from '../request-options'; - -type LogFn = (message: string, ...rest: unknown[]) => void; -export type Logger = { - error: LogFn; - warn: LogFn; - info: LogFn; - debug: LogFn; -}; -export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug'; - -const levelNumbers = { - off: 0, - error: 200, - warn: 300, - info: 400, - debug: 500, -}; - -export const parseLogLevel = ( - maybeLevel: string | undefined, - sourceName: string, - client: Opencode, -): LogLevel | undefined => { - if (!maybeLevel) { - return undefined; - } - if (hasOwn(levelNumbers, maybeLevel)) { - return maybeLevel; - } - loggerFor(client).warn( - `${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify( - Object.keys(levelNumbers), - )}`, - ); - return undefined; -}; - -function noop() {} - -function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) { - if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) { - return noop; - } else { - // Don't wrap logger functions, we want the stacktrace intact! - return logger[fnLevel].bind(logger); - } -} - -const noopLogger = { - error: noop, - warn: noop, - info: noop, - debug: noop, -}; - -let cachedLoggers = /* @__PURE__ */ new WeakMap(); - -export function loggerFor(client: Opencode): Logger { - const logger = client.logger; - const logLevel = client.logLevel ?? 'off'; - if (!logger) { - return noopLogger; - } - - const cachedLogger = cachedLoggers.get(logger); - if (cachedLogger && cachedLogger[0] === logLevel) { - return cachedLogger[1]; - } - - const levelLogger = { - error: makeLogFn('error', logger, logLevel), - warn: makeLogFn('warn', logger, logLevel), - info: makeLogFn('info', logger, logLevel), - debug: makeLogFn('debug', logger, logLevel), - }; - - cachedLoggers.set(logger, [logLevel, levelLogger]); - - return levelLogger; -} - -export const formatRequestDetails = (details: { - options?: RequestOptions | undefined; - headers?: Headers | Record | undefined; - retryOfRequestLogID?: string | undefined; - retryOf?: string | undefined; - url?: string | undefined; - status?: number | undefined; - method?: string | undefined; - durationMs?: number | undefined; - message?: unknown; - body?: unknown; -}) => { - if (details.options) { - details.options = { ...details.options }; - delete details.options['headers']; // redundant + leaks internals - } - if (details.headers) { - details.headers = Object.fromEntries( - (details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map( - ([name, value]) => [ - name, - ( - name.toLowerCase() === 'authorization' || - name.toLowerCase() === 'cookie' || - name.toLowerCase() === 'set-cookie' - ) ? - '***' - : value, - ], - ), - ); - } - if ('retryOfRequestLogID' in details) { - if (details.retryOfRequestLogID) { - details.retryOf = details.retryOfRequestLogID; - } - delete details.retryOfRequestLogID; - } - return details; -}; diff --git a/packages/sdk/src/internal/utils/path.ts b/packages/sdk/src/internal/utils/path.ts deleted file mode 100644 index 6ca96e0dd..000000000 --- a/packages/sdk/src/internal/utils/path.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { OpencodeError } from '../../core/error'; - -/** - * Percent-encode everything that isn't safe to have in a path without encoding safe chars. - * - * Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3: - * > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" - * > pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - */ -export function encodeURIPath(str: string) { - return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent); -} - -const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null)); - -export const createPathTagFunction = (pathEncoder = encodeURIPath) => - function path(statics: readonly string[], ...params: readonly unknown[]): string { - // If there are no params, no processing is needed. - if (statics.length === 1) return statics[0]!; - - let postPath = false; - const invalidSegments = []; - const path = statics.reduce((previousValue, currentValue, index) => { - if (/[?#]/.test(currentValue)) { - postPath = true; - } - const value = params[index]; - let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value); - if ( - index !== params.length && - (value == null || - (typeof value === 'object' && - // handle values from other realms - value.toString === - Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY) - ?.toString)) - ) { - encoded = value + ''; - invalidSegments.push({ - start: previousValue.length + currentValue.length, - length: encoded.length, - error: `Value of type ${Object.prototype.toString - .call(value) - .slice(8, -1)} is not a valid path parameter`, - }); - } - return previousValue + currentValue + (index === params.length ? '' : encoded); - }, ''); - - const pathOnly = path.split(/[?#]/, 1)[0]!; - const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi; - let match; - - // Find all invalid segments - while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) { - invalidSegments.push({ - start: match.index, - length: match[0].length, - error: `Value "${match[0]}" can\'t be safely passed as a path parameter`, - }); - } - - invalidSegments.sort((a, b) => a.start - b.start); - - if (invalidSegments.length > 0) { - let lastEnd = 0; - const underline = invalidSegments.reduce((acc, segment) => { - const spaces = ' '.repeat(segment.start - lastEnd); - const arrows = '^'.repeat(segment.length); - lastEnd = segment.start + segment.length; - return acc + spaces + arrows; - }, ''); - - throw new OpencodeError( - `Path parameters result in path with invalid segments:\n${invalidSegments - .map((e) => e.error) - .join('\n')}\n${path}\n${underline}`, - ); - } - - return path; - }; - -/** - * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced. - */ -export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath); diff --git a/packages/sdk/src/internal/utils/sleep.ts b/packages/sdk/src/internal/utils/sleep.ts deleted file mode 100644 index 65e52962b..000000000 --- a/packages/sdk/src/internal/utils/sleep.ts +++ /dev/null @@ -1,3 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/packages/sdk/src/internal/utils/uuid.ts b/packages/sdk/src/internal/utils/uuid.ts deleted file mode 100644 index b0e53aaf7..000000000 --- a/packages/sdk/src/internal/utils/uuid.ts +++ /dev/null @@ -1,17 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -/** - * https://stackoverflow.com/a/2117523 - */ -export let uuid4 = function () { - const { crypto } = globalThis as any; - if (crypto?.randomUUID) { - uuid4 = crypto.randomUUID.bind(crypto); - return crypto.randomUUID(); - } - const u8 = new Uint8Array(1); - const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff; - return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => - (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16), - ); -}; diff --git a/packages/sdk/src/internal/utils/values.ts b/packages/sdk/src/internal/utils/values.ts deleted file mode 100644 index b2421fd88..000000000 --- a/packages/sdk/src/internal/utils/values.ts +++ /dev/null @@ -1,105 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { OpencodeError } from '../../core/error'; - -// https://url.spec.whatwg.org/#url-scheme-string -const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; - -export const isAbsoluteURL = (url: string): boolean => { - return startsWithSchemeRegexp.test(url); -}; - -export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val)); -export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[]; - -/** Returns an object if the given value isn't an object, otherwise returns as-is */ -export function maybeObj(x: unknown): object { - if (typeof x !== 'object') { - return {}; - } - - return x ?? {}; -} - -// https://stackoverflow.com/a/34491287 -export function isEmptyObj(obj: Object | null | undefined): boolean { - if (!obj) return true; - for (const _k in obj) return false; - return true; -} - -// https://eslint.org/docs/latest/rules/no-prototype-builtins -export function hasOwn(obj: T, key: PropertyKey): key is keyof T { - return Object.prototype.hasOwnProperty.call(obj, key); -} - -export function isObj(obj: unknown): obj is Record { - return obj != null && typeof obj === 'object' && !Array.isArray(obj); -} - -export const ensurePresent = (value: T | null | undefined): T => { - if (value == null) { - throw new OpencodeError(`Expected a value to be given but received ${value} instead.`); - } - - return value; -}; - -export const validatePositiveInteger = (name: string, n: unknown): number => { - if (typeof n !== 'number' || !Number.isInteger(n)) { - throw new OpencodeError(`${name} must be an integer`); - } - if (n < 0) { - throw new OpencodeError(`${name} must be a positive integer`); - } - return n; -}; - -export const coerceInteger = (value: unknown): number => { - if (typeof value === 'number') return Math.round(value); - if (typeof value === 'string') return parseInt(value, 10); - - throw new OpencodeError(`Could not coerce ${value} (type: ${typeof value}) into a number`); -}; - -export const coerceFloat = (value: unknown): number => { - if (typeof value === 'number') return value; - if (typeof value === 'string') return parseFloat(value); - - throw new OpencodeError(`Could not coerce ${value} (type: ${typeof value}) into a number`); -}; - -export const coerceBoolean = (value: unknown): boolean => { - if (typeof value === 'boolean') return value; - if (typeof value === 'string') return value === 'true'; - return Boolean(value); -}; - -export const maybeCoerceInteger = (value: unknown): number | undefined => { - if (value === undefined) { - return undefined; - } - return coerceInteger(value); -}; - -export const maybeCoerceFloat = (value: unknown): number | undefined => { - if (value === undefined) { - return undefined; - } - return coerceFloat(value); -}; - -export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { - if (value === undefined) { - return undefined; - } - return coerceBoolean(value); -}; - -export const safeJSON = (text: string) => { - try { - return JSON.parse(text); - } catch (err) { - return undefined; - } -}; diff --git a/packages/sdk/src/lib/.keep b/packages/sdk/src/lib/.keep deleted file mode 100644 index 7554f8b20..000000000 --- a/packages/sdk/src/lib/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store custom files to expand the SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. diff --git a/packages/sdk/src/resource.ts b/packages/sdk/src/resource.ts deleted file mode 100644 index 363e3516b..000000000 --- a/packages/sdk/src/resource.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/resource instead */ -export * from './core/resource'; diff --git a/packages/sdk/src/resources.ts b/packages/sdk/src/resources.ts deleted file mode 100644 index b283d5781..000000000 --- a/packages/sdk/src/resources.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './resources/index'; diff --git a/packages/sdk/src/resources/app.ts b/packages/sdk/src/resources/app.ts deleted file mode 100644 index 8ee56e320..000000000 --- a/packages/sdk/src/resources/app.ts +++ /dev/null @@ -1,192 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class AppResource extends APIResource { - /** - * Get app info - */ - get(options?: RequestOptions): APIPromise { - return this._client.get('/app', options); - } - - /** - * Initialize the app - */ - init(options?: RequestOptions): APIPromise { - return this._client.post('/app/init', options); - } - - /** - * Write a log entry to the server logs - */ - log(body: AppLogParams, options?: RequestOptions): APIPromise { - return this._client.post('/log', { body, ...options }); - } - - /** - * List all modes - */ - modes(options?: RequestOptions): APIPromise { - return this._client.get('/mode', options); - } - - /** - * List all providers - */ - providers(options?: RequestOptions): APIPromise { - return this._client.get('/config/providers', options); - } -} - -export interface App { - git: boolean; - - hostname: string; - - path: App.Path; - - time: App.Time; -} - -export namespace App { - export interface Path { - config: string; - - cwd: string; - - data: string; - - root: string; - - state: string; - } - - export interface Time { - initialized?: number; - } -} - -export interface Mode { - name: string; - - tools: { [key: string]: boolean }; - - model?: Mode.Model; - - prompt?: string; - - temperature?: number; -} - -export namespace Mode { - export interface Model { - modelID: string; - - providerID: string; - } -} - -export interface Model { - id: string; - - attachment: boolean; - - cost: Model.Cost; - - limit: Model.Limit; - - name: string; - - options: { [key: string]: unknown }; - - reasoning: boolean; - - release_date: string; - - temperature: boolean; - - tool_call: boolean; -} - -export namespace Model { - export interface Cost { - input: number; - - output: number; - - cache_read?: number; - - cache_write?: number; - } - - export interface Limit { - context: number; - - output: number; - } -} - -export interface Provider { - id: string; - - env: Array; - - models: { [key: string]: Model }; - - name: string; - - api?: string; - - npm?: string; -} - -export type AppInitResponse = boolean; - -export type AppLogResponse = boolean; - -export type AppModesResponse = Array; - -export interface AppProvidersResponse { - default: { [key: string]: string }; - - providers: Array; -} - -export interface AppLogParams { - /** - * Log level - */ - level: 'debug' | 'info' | 'error' | 'warn'; - - /** - * Log message - */ - message: string; - - /** - * Service name for the log entry - */ - service: string; - - /** - * Additional metadata for the log entry - */ - extra?: { [key: string]: unknown }; -} - -export declare namespace AppResource { - export { - type App as App, - type Mode as Mode, - type Model as Model, - type Provider as Provider, - type AppInitResponse as AppInitResponse, - type AppLogResponse as AppLogResponse, - type AppModesResponse as AppModesResponse, - type AppProvidersResponse as AppProvidersResponse, - type AppLogParams as AppLogParams, - }; -} diff --git a/packages/sdk/src/resources/config.ts b/packages/sdk/src/resources/config.ts deleted file mode 100644 index 11592f4aa..000000000 --- a/packages/sdk/src/resources/config.ts +++ /dev/null @@ -1,492 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as ConfigAPI from './config'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class ConfigResource extends APIResource { - /** - * Get config info - */ - get(options?: RequestOptions): APIPromise { - return this._client.get('/config', options); - } -} - -export interface Config { - /** - * JSON schema reference for configuration validation - */ - $schema?: string; - - /** - * Modes configuration, see https://opencode.ai/docs/modes - */ - agent?: Config.Agent; - - /** - * @deprecated Use 'share' field instead. Share newly created sessions - * automatically - */ - autoshare?: boolean; - - /** - * Automatically update to the latest version - */ - autoupdate?: boolean; - - /** - * Disable providers that are loaded automatically - */ - disabled_providers?: Array; - - experimental?: Config.Experimental; - - /** - * Additional instruction files or patterns to include - */ - instructions?: Array; - - /** - * Custom keybind configurations - */ - keybinds?: KeybindsConfig; - - /** - * @deprecated Always uses stretch layout. - */ - layout?: 'auto' | 'stretch'; - - /** - * MCP (Model Context Protocol) server configurations - */ - mcp?: { [key: string]: McpLocalConfig | McpRemoteConfig }; - - /** - * Modes configuration, see https://opencode.ai/docs/modes - */ - mode?: Config.Mode; - - /** - * Model to use in the format of provider/model, eg anthropic/claude-2 - */ - model?: string; - - /** - * Custom provider configurations and model overrides - */ - provider?: { [key: string]: Config.Provider }; - - /** - * Control sharing behavior:'manual' allows manual sharing via commands, 'auto' - * enables automatic sharing, 'disabled' disables all sharing - */ - share?: 'manual' | 'auto' | 'disabled'; - - /** - * Small model to use for tasks like summarization and title generation in the - * format of provider/model - */ - small_model?: string; - - /** - * Theme name to use for the interface - */ - theme?: string; - - /** - * Custom username to display in conversations instead of system username - */ - username?: string; -} - -export namespace Config { - /** - * Modes configuration, see https://opencode.ai/docs/modes - */ - export interface Agent { - general?: Agent.General; - - [k: string]: Agent.AgentConfig | undefined; - } - - export namespace Agent { - export interface General extends ConfigAPI.ModeConfig { - description: string; - } - - export interface AgentConfig extends ConfigAPI.ModeConfig { - description: string; - } - } - - export interface AgentConfig extends ConfigAPI.ModeConfig { - description: string; - } - - export interface AgentConfig extends ConfigAPI.ModeConfig { - description: string; - } - - export interface Experimental { - hook?: Experimental.Hook; - } - - export namespace Experimental { - export interface Hook { - file_edited?: { [key: string]: Array }; - - session_completed?: Array; - } - - export namespace Hook { - export interface FileEdited { - command: Array; - - environment?: { [key: string]: string }; - } - - export interface SessionCompleted { - command: Array; - - environment?: { [key: string]: string }; - } - } - } - - /** - * Modes configuration, see https://opencode.ai/docs/modes - */ - export interface Mode { - build?: ConfigAPI.ModeConfig; - - plan?: ConfigAPI.ModeConfig; - - [k: string]: ConfigAPI.ModeConfig | undefined; - } - - export interface Provider { - models: { [key: string]: Provider.Models }; - - id?: string; - - api?: string; - - env?: Array; - - name?: string; - - npm?: string; - - options?: Provider.Options; - } - - export namespace Provider { - export interface Models { - id?: string; - - attachment?: boolean; - - cost?: Models.Cost; - - limit?: Models.Limit; - - name?: string; - - options?: { [key: string]: unknown }; - - reasoning?: boolean; - - release_date?: string; - - temperature?: boolean; - - tool_call?: boolean; - } - - export namespace Models { - export interface Cost { - input: number; - - output: number; - - cache_read?: number; - - cache_write?: number; - } - - export interface Limit { - context: number; - - output: number; - } - } - - export interface Options { - apiKey?: string; - - baseURL?: string; - - [k: string]: unknown; - } - } -} - -export interface KeybindsConfig { - /** - * Exit the application - */ - app_exit: string; - - /** - * Show help dialog - */ - app_help: string; - - /** - * Open external editor - */ - editor_open: string; - - /** - * Close file - */ - file_close: string; - - /** - * Split/unified diff - */ - file_diff_toggle: string; - - /** - * List files - */ - file_list: string; - - /** - * Search file - */ - file_search: string; - - /** - * Clear input field - */ - input_clear: string; - - /** - * Insert newline in input - */ - input_newline: string; - - /** - * Paste from clipboard - */ - input_paste: string; - - /** - * Submit input - */ - input_submit: string; - - /** - * Leader key for keybind combinations - */ - leader: string; - - /** - * Copy message - */ - messages_copy: string; - - /** - * Navigate to first message - */ - messages_first: string; - - /** - * Scroll messages down by half page - */ - messages_half_page_down: string; - - /** - * Scroll messages up by half page - */ - messages_half_page_up: string; - - /** - * Navigate to last message - */ - messages_last: string; - - /** - * Toggle layout - */ - messages_layout_toggle: string; - - /** - * Navigate to next message - */ - messages_next: string; - - /** - * Scroll messages down by one page - */ - messages_page_down: string; - - /** - * Scroll messages up by one page - */ - messages_page_up: string; - - /** - * Navigate to previous message - */ - messages_previous: string; - - /** - * Redo message - */ - messages_redo: string; - - /** - * @deprecated use messages_undo. Revert message - */ - messages_revert: string; - - /** - * Undo message - */ - messages_undo: string; - - /** - * List available models - */ - model_list: string; - - /** - * Create/update AGENTS.md - */ - project_init: string; - - /** - * Compact the session - */ - session_compact: string; - - /** - * Export session to editor - */ - session_export: string; - - /** - * Interrupt current session - */ - session_interrupt: string; - - /** - * List all sessions - */ - session_list: string; - - /** - * Create a new session - */ - session_new: string; - - /** - * Share current session - */ - session_share: string; - - /** - * Unshare current session - */ - session_unshare: string; - - /** - * Next mode - */ - switch_mode: string; - - /** - * Previous Mode - */ - switch_mode_reverse: string; - - /** - * List available themes - */ - theme_list: string; - - /** - * Toggle tool details - */ - tool_details: string; -} - -export interface McpLocalConfig { - /** - * Command and arguments to run the MCP server - */ - command: Array; - - /** - * Type of MCP server connection - */ - type: 'local'; - - /** - * Enable or disable the MCP server on startup - */ - enabled?: boolean; - - /** - * Environment variables to set when running the MCP server - */ - environment?: { [key: string]: string }; -} - -export interface McpRemoteConfig { - /** - * Type of MCP server connection - */ - type: 'remote'; - - /** - * URL of the remote MCP server - */ - url: string; - - /** - * Enable or disable the MCP server on startup - */ - enabled?: boolean; - - /** - * Headers to send with the request - */ - headers?: { [key: string]: string }; -} - -export interface ModeConfig { - disable?: boolean; - - model?: string; - - prompt?: string; - - temperature?: number; - - tools?: { [key: string]: boolean }; -} - -export declare namespace ConfigResource { - export { - type Config as Config, - type KeybindsConfig as KeybindsConfig, - type McpLocalConfig as McpLocalConfig, - type McpRemoteConfig as McpRemoteConfig, - type ModeConfig as ModeConfig, - }; -} diff --git a/packages/sdk/src/resources/event.ts b/packages/sdk/src/resources/event.ts deleted file mode 100644 index bd51b22b3..000000000 --- a/packages/sdk/src/resources/event.ts +++ /dev/null @@ -1,267 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as SessionAPI from './session'; -import * as Shared from './shared'; -import { APIPromise } from '../core/api-promise'; -import { Stream } from '../core/streaming'; -import { RequestOptions } from '../internal/request-options'; - -export class Event extends APIResource { - /** - * Get events - */ - list(options?: RequestOptions): APIPromise> { - return this._client.get('/event', { ...options, stream: true }) as APIPromise>; - } -} - -export type EventListResponse = - | EventListResponse.EventInstallationUpdated - | EventListResponse.EventLspClientDiagnostics - | EventListResponse.EventMessageUpdated - | EventListResponse.EventMessageRemoved - | EventListResponse.EventMessagePartUpdated - | EventListResponse.EventMessagePartRemoved - | EventListResponse.EventStorageWrite - | EventListResponse.EventPermissionUpdated - | EventListResponse.EventFileEdited - | EventListResponse.EventSessionUpdated - | EventListResponse.EventSessionDeleted - | EventListResponse.EventSessionIdle - | EventListResponse.EventSessionError - | EventListResponse.EventServerConnected - | EventListResponse.EventFileWatcherUpdated - | EventListResponse.EventIdeInstalled; - -export namespace EventListResponse { - export interface EventInstallationUpdated { - properties: EventInstallationUpdated.Properties; - - type: 'installation.updated'; - } - - export namespace EventInstallationUpdated { - export interface Properties { - version: string; - } - } - - export interface EventLspClientDiagnostics { - properties: EventLspClientDiagnostics.Properties; - - type: 'lsp.client.diagnostics'; - } - - export namespace EventLspClientDiagnostics { - export interface Properties { - path: string; - - serverID: string; - } - } - - export interface EventMessageUpdated { - properties: EventMessageUpdated.Properties; - - type: 'message.updated'; - } - - export namespace EventMessageUpdated { - export interface Properties { - info: SessionAPI.Message; - } - } - - export interface EventMessageRemoved { - properties: EventMessageRemoved.Properties; - - type: 'message.removed'; - } - - export namespace EventMessageRemoved { - export interface Properties { - messageID: string; - - sessionID: string; - } - } - - export interface EventMessagePartUpdated { - properties: EventMessagePartUpdated.Properties; - - type: 'message.part.updated'; - } - - export namespace EventMessagePartUpdated { - export interface Properties { - part: SessionAPI.Part; - } - } - - export interface EventMessagePartRemoved { - properties: EventMessagePartRemoved.Properties; - - type: 'message.part.removed'; - } - - export namespace EventMessagePartRemoved { - export interface Properties { - messageID: string; - - partID: string; - - sessionID: string; - } - } - - export interface EventStorageWrite { - properties: EventStorageWrite.Properties; - - type: 'storage.write'; - } - - export namespace EventStorageWrite { - export interface Properties { - key: string; - - content?: unknown; - } - } - - export interface EventPermissionUpdated { - properties: EventPermissionUpdated.Properties; - - type: 'permission.updated'; - } - - export namespace EventPermissionUpdated { - export interface Properties { - id: string; - - metadata: { [key: string]: unknown }; - - sessionID: string; - - time: Properties.Time; - - title: string; - } - - export namespace Properties { - export interface Time { - created: number; - } - } - } - - export interface EventFileEdited { - properties: EventFileEdited.Properties; - - type: 'file.edited'; - } - - export namespace EventFileEdited { - export interface Properties { - file: string; - } - } - - export interface EventSessionUpdated { - properties: EventSessionUpdated.Properties; - - type: 'session.updated'; - } - - export namespace EventSessionUpdated { - export interface Properties { - info: SessionAPI.Session; - } - } - - export interface EventSessionDeleted { - properties: EventSessionDeleted.Properties; - - type: 'session.deleted'; - } - - export namespace EventSessionDeleted { - export interface Properties { - info: SessionAPI.Session; - } - } - - export interface EventSessionIdle { - properties: EventSessionIdle.Properties; - - type: 'session.idle'; - } - - export namespace EventSessionIdle { - export interface Properties { - sessionID: string; - } - } - - export interface EventSessionError { - properties: EventSessionError.Properties; - - type: 'session.error'; - } - - export namespace EventSessionError { - export interface Properties { - error?: - | Shared.ProviderAuthError - | Shared.UnknownError - | Properties.MessageOutputLengthError - | Shared.MessageAbortedError; - - sessionID?: string; - } - - export namespace Properties { - export interface MessageOutputLengthError { - data: unknown; - - name: 'MessageOutputLengthError'; - } - } - } - - export interface EventServerConnected { - properties: unknown; - - type: 'server.connected'; - } - - export interface EventFileWatcherUpdated { - properties: EventFileWatcherUpdated.Properties; - - type: 'file.watcher.updated'; - } - - export namespace EventFileWatcherUpdated { - export interface Properties { - event: 'rename' | 'change'; - - file: string; - } - } - - export interface EventIdeInstalled { - properties: EventIdeInstalled.Properties; - - type: 'ide.installed'; - } - - export namespace EventIdeInstalled { - export interface Properties { - ide: string; - } - } -} - -export declare namespace Event { - export { type EventListResponse as EventListResponse }; -} diff --git a/packages/sdk/src/resources/file.ts b/packages/sdk/src/resources/file.ts deleted file mode 100644 index fd7bdac69..000000000 --- a/packages/sdk/src/resources/file.ts +++ /dev/null @@ -1,52 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class FileResource extends APIResource { - /** - * Read a file - */ - read(query: FileReadParams, options?: RequestOptions): APIPromise { - return this._client.get('/file', { query, ...options }); - } - - /** - * Get file status - */ - status(options?: RequestOptions): APIPromise { - return this._client.get('/file/status', options); - } -} - -export interface File { - added: number; - - path: string; - - removed: number; - - status: 'added' | 'deleted' | 'modified'; -} - -export interface FileReadResponse { - content: string; - - type: 'raw' | 'patch'; -} - -export type FileStatusResponse = Array; - -export interface FileReadParams { - path: string; -} - -export declare namespace FileResource { - export { - type File as File, - type FileReadResponse as FileReadResponse, - type FileStatusResponse as FileStatusResponse, - type FileReadParams as FileReadParams, - }; -} diff --git a/packages/sdk/src/resources/find.ts b/packages/sdk/src/resources/find.ts deleted file mode 100644 index d9d6597e5..000000000 --- a/packages/sdk/src/resources/find.ts +++ /dev/null @@ -1,134 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class Find extends APIResource { - /** - * Find files - */ - files(query: FindFilesParams, options?: RequestOptions): APIPromise { - return this._client.get('/find/file', { query, ...options }); - } - - /** - * Find workspace symbols - */ - symbols(query: FindSymbolsParams, options?: RequestOptions): APIPromise { - return this._client.get('/find/symbol', { query, ...options }); - } - - /** - * Find text in files - */ - text(query: FindTextParams, options?: RequestOptions): APIPromise { - return this._client.get('/find', { query, ...options }); - } -} - -export interface Symbol { - kind: number; - - location: Symbol.Location; - - name: string; -} - -export namespace Symbol { - export interface Location { - range: Location.Range; - - uri: string; - } - - export namespace Location { - export interface Range { - end: Range.End; - - start: Range.Start; - } - - export namespace Range { - export interface End { - character: number; - - line: number; - } - - export interface Start { - character: number; - - line: number; - } - } - } -} - -export type FindFilesResponse = Array; - -export type FindSymbolsResponse = Array; - -export type FindTextResponse = Array; - -export namespace FindTextResponse { - export interface FindTextResponseItem { - absolute_offset: number; - - line_number: number; - - lines: FindTextResponseItem.Lines; - - path: FindTextResponseItem.Path; - - submatches: Array; - } - - export namespace FindTextResponseItem { - export interface Lines { - text: string; - } - - export interface Path { - text: string; - } - - export interface Submatch { - end: number; - - match: Submatch.Match; - - start: number; - } - - export namespace Submatch { - export interface Match { - text: string; - } - } - } -} - -export interface FindFilesParams { - query: string; -} - -export interface FindSymbolsParams { - query: string; -} - -export interface FindTextParams { - pattern: string; -} - -export declare namespace Find { - export { - type Symbol as Symbol, - type FindFilesResponse as FindFilesResponse, - type FindSymbolsResponse as FindSymbolsResponse, - type FindTextResponse as FindTextResponse, - type FindFilesParams as FindFilesParams, - type FindSymbolsParams as FindSymbolsParams, - type FindTextParams as FindTextParams, - }; -} diff --git a/packages/sdk/src/resources/index.ts b/packages/sdk/src/resources/index.ts deleted file mode 100644 index 4e7934af1..000000000 --- a/packages/sdk/src/resources/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export * from './shared'; -export { - AppResource, - type App, - type Mode, - type Model, - type Provider, - type AppInitResponse, - type AppLogResponse, - type AppModesResponse, - type AppProvidersResponse, - type AppLogParams, -} from './app'; -export { - ConfigResource, - type Config, - type KeybindsConfig, - type McpLocalConfig, - type McpRemoteConfig, - type ModeConfig, -} from './config'; -export { Event, type EventListResponse } from './event'; -export { - FileResource, - type File, - type FileReadResponse, - type FileStatusResponse, - type FileReadParams, -} from './file'; -export { - Find, - type Symbol, - type FindFilesResponse, - type FindSymbolsResponse, - type FindTextResponse, - type FindFilesParams, - type FindSymbolsParams, - type FindTextParams, -} from './find'; -export { - SessionResource, - type AssistantMessage, - type FilePart, - type FilePartInput, - type FilePartSource, - type FilePartSourceText, - type FileSource, - type Message, - type Part, - type Session, - type SnapshotPart, - type StepFinishPart, - type StepStartPart, - type SymbolSource, - type TextPart, - type TextPartInput, - type ToolPart, - type ToolStateCompleted, - type ToolStateError, - type ToolStatePending, - type ToolStateRunning, - type UserMessage, - type SessionListResponse, - type SessionDeleteResponse, - type SessionAbortResponse, - type SessionInitResponse, - type SessionMessagesResponse, - type SessionSummarizeResponse, - type SessionChatParams, - type SessionInitParams, - type SessionRevertParams, - type SessionSummarizeParams, -} from './session'; -export { - Tui, - type TuiAppendPromptResponse, - type TuiOpenHelpResponse, - type TuiAppendPromptParams, -} from './tui'; diff --git a/packages/sdk/src/resources/session.ts b/packages/sdk/src/resources/session.ts deleted file mode 100644 index 5dbdc31f8..000000000 --- a/packages/sdk/src/resources/session.ts +++ /dev/null @@ -1,605 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as SessionAPI from './session'; -import * as Shared from './shared'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; -import { path } from '../internal/utils/path'; - -export class SessionResource extends APIResource { - /** - * Create a new session - */ - create(options?: RequestOptions): APIPromise { - return this._client.post('/session', options); - } - - /** - * List all sessions - */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/session', options); - } - - /** - * Delete a session and all its data - */ - delete(id: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/session/${id}`, options); - } - - /** - * Abort a session - */ - abort(id: string, options?: RequestOptions): APIPromise { - return this._client.post(path`/session/${id}/abort`, options); - } - - /** - * Create and send a new message to a session - */ - chat(id: string, body: SessionChatParams, options?: RequestOptions): APIPromise { - return this._client.post(path`/session/${id}/message`, { body, ...options }); - } - - /** - * Analyze the app and create an AGENTS.md file - */ - init(id: string, body: SessionInitParams, options?: RequestOptions): APIPromise { - return this._client.post(path`/session/${id}/init`, { body, ...options }); - } - - /** - * List messages for a session - */ - messages(id: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/session/${id}/message`, options); - } - - /** - * Revert a message - */ - revert(id: string, body: SessionRevertParams, options?: RequestOptions): APIPromise { - return this._client.post(path`/session/${id}/revert`, { body, ...options }); - } - - /** - * Share a session - */ - share(id: string, options?: RequestOptions): APIPromise { - return this._client.post(path`/session/${id}/share`, options); - } - - /** - * Summarize the session - */ - summarize( - id: string, - body: SessionSummarizeParams, - options?: RequestOptions, - ): APIPromise { - return this._client.post(path`/session/${id}/summarize`, { body, ...options }); - } - - /** - * Restore all reverted messages - */ - unrevert(id: string, options?: RequestOptions): APIPromise { - return this._client.post(path`/session/${id}/unrevert`, options); - } - - /** - * Unshare the session - */ - unshare(id: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/session/${id}/share`, options); - } -} - -export interface AssistantMessage { - id: string; - - cost: number; - - mode: string; - - modelID: string; - - path: AssistantMessage.Path; - - providerID: string; - - role: 'assistant'; - - sessionID: string; - - system: Array; - - time: AssistantMessage.Time; - - tokens: AssistantMessage.Tokens; - - error?: - | Shared.ProviderAuthError - | Shared.UnknownError - | AssistantMessage.MessageOutputLengthError - | Shared.MessageAbortedError; - - summary?: boolean; -} - -export namespace AssistantMessage { - export interface Path { - cwd: string; - - root: string; - } - - export interface Time { - created: number; - - completed?: number; - } - - export interface Tokens { - cache: Tokens.Cache; - - input: number; - - output: number; - - reasoning: number; - } - - export namespace Tokens { - export interface Cache { - read: number; - - write: number; - } - } - - export interface MessageOutputLengthError { - data: unknown; - - name: 'MessageOutputLengthError'; - } -} - -export interface FilePart { - id: string; - - messageID: string; - - mime: string; - - sessionID: string; - - type: 'file'; - - url: string; - - filename?: string; - - source?: FilePartSource; -} - -export interface FilePartInput { - mime: string; - - type: 'file'; - - url: string; - - id?: string; - - filename?: string; - - source?: FilePartSource; -} - -export type FilePartSource = FileSource | SymbolSource; - -export interface FilePartSourceText { - end: number; - - start: number; - - value: string; -} - -export interface FileSource { - path: string; - - text: FilePartSourceText; - - type: 'file'; -} - -export type Message = UserMessage | AssistantMessage; - -export type Part = - | TextPart - | FilePart - | ToolPart - | StepStartPart - | StepFinishPart - | SnapshotPart - | Part.PatchPart; - -export namespace Part { - export interface PatchPart { - id: string; - - files: Array; - - hash: string; - - messageID: string; - - sessionID: string; - - type: 'patch'; - } -} - -export interface Session { - id: string; - - time: Session.Time; - - title: string; - - version: string; - - parentID?: string; - - revert?: Session.Revert; - - share?: Session.Share; -} - -export namespace Session { - export interface Time { - created: number; - - updated: number; - } - - export interface Revert { - messageID: string; - - diff?: string; - - partID?: string; - - snapshot?: string; - } - - export interface Share { - url: string; - } -} - -export interface SnapshotPart { - id: string; - - messageID: string; - - sessionID: string; - - snapshot: string; - - type: 'snapshot'; -} - -export interface StepFinishPart { - id: string; - - cost: number; - - messageID: string; - - sessionID: string; - - tokens: StepFinishPart.Tokens; - - type: 'step-finish'; -} - -export namespace StepFinishPart { - export interface Tokens { - cache: Tokens.Cache; - - input: number; - - output: number; - - reasoning: number; - } - - export namespace Tokens { - export interface Cache { - read: number; - - write: number; - } - } -} - -export interface StepStartPart { - id: string; - - messageID: string; - - sessionID: string; - - type: 'step-start'; -} - -export interface SymbolSource { - kind: number; - - name: string; - - path: string; - - range: SymbolSource.Range; - - text: FilePartSourceText; - - type: 'symbol'; -} - -export namespace SymbolSource { - export interface Range { - end: Range.End; - - start: Range.Start; - } - - export namespace Range { - export interface End { - character: number; - - line: number; - } - - export interface Start { - character: number; - - line: number; - } - } -} - -export interface TextPart { - id: string; - - messageID: string; - - sessionID: string; - - text: string; - - type: 'text'; - - synthetic?: boolean; - - time?: TextPart.Time; -} - -export namespace TextPart { - export interface Time { - start: number; - - end?: number; - } -} - -export interface TextPartInput { - text: string; - - type: 'text'; - - id?: string; - - synthetic?: boolean; - - time?: TextPartInput.Time; -} - -export namespace TextPartInput { - export interface Time { - start: number; - - end?: number; - } -} - -export interface ToolPart { - id: string; - - callID: string; - - messageID: string; - - sessionID: string; - - state: ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError; - - tool: string; - - type: 'tool'; -} - -export interface ToolStateCompleted { - input: { [key: string]: unknown }; - - metadata: { [key: string]: unknown }; - - output: string; - - status: 'completed'; - - time: ToolStateCompleted.Time; - - title: string; -} - -export namespace ToolStateCompleted { - export interface Time { - end: number; - - start: number; - } -} - -export interface ToolStateError { - error: string; - - input: { [key: string]: unknown }; - - status: 'error'; - - time: ToolStateError.Time; -} - -export namespace ToolStateError { - export interface Time { - end: number; - - start: number; - } -} - -export interface ToolStatePending { - status: 'pending'; -} - -export interface ToolStateRunning { - status: 'running'; - - time: ToolStateRunning.Time; - - input?: unknown; - - metadata?: { [key: string]: unknown }; - - title?: string; -} - -export namespace ToolStateRunning { - export interface Time { - start: number; - } -} - -export interface UserMessage { - id: string; - - role: 'user'; - - sessionID: string; - - time: UserMessage.Time; -} - -export namespace UserMessage { - export interface Time { - created: number; - } -} - -export type SessionListResponse = Array; - -export type SessionDeleteResponse = boolean; - -export type SessionAbortResponse = boolean; - -export type SessionInitResponse = boolean; - -export type SessionMessagesResponse = Array; - -export namespace SessionMessagesResponse { - export interface SessionMessagesResponseItem { - info: SessionAPI.Message; - - parts: Array; - } -} - -export type SessionSummarizeResponse = boolean; - -export interface SessionChatParams { - modelID: string; - - parts: Array; - - providerID: string; - - messageID?: string; - - mode?: string; - - system?: string; - - tools?: { [key: string]: boolean }; -} - -export interface SessionInitParams { - messageID: string; - - modelID: string; - - providerID: string; -} - -export interface SessionRevertParams { - messageID: string; - - partID?: string; -} - -export interface SessionSummarizeParams { - modelID: string; - - providerID: string; -} - -export declare namespace SessionResource { - export { - type AssistantMessage as AssistantMessage, - type FilePart as FilePart, - type FilePartInput as FilePartInput, - type FilePartSource as FilePartSource, - type FilePartSourceText as FilePartSourceText, - type FileSource as FileSource, - type Message as Message, - type Part as Part, - type Session as Session, - type SnapshotPart as SnapshotPart, - type StepFinishPart as StepFinishPart, - type StepStartPart as StepStartPart, - type SymbolSource as SymbolSource, - type TextPart as TextPart, - type TextPartInput as TextPartInput, - type ToolPart as ToolPart, - type ToolStateCompleted as ToolStateCompleted, - type ToolStateError as ToolStateError, - type ToolStatePending as ToolStatePending, - type ToolStateRunning as ToolStateRunning, - type UserMessage as UserMessage, - type SessionListResponse as SessionListResponse, - type SessionDeleteResponse as SessionDeleteResponse, - type SessionAbortResponse as SessionAbortResponse, - type SessionInitResponse as SessionInitResponse, - type SessionMessagesResponse as SessionMessagesResponse, - type SessionSummarizeResponse as SessionSummarizeResponse, - type SessionChatParams as SessionChatParams, - type SessionInitParams as SessionInitParams, - type SessionRevertParams as SessionRevertParams, - type SessionSummarizeParams as SessionSummarizeParams, - }; -} diff --git a/packages/sdk/src/resources/shared.ts b/packages/sdk/src/resources/shared.ts deleted file mode 100644 index 31378f6fe..000000000 --- a/packages/sdk/src/resources/shared.ts +++ /dev/null @@ -1,33 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export interface MessageAbortedError { - data: unknown; - - name: 'MessageAbortedError'; -} - -export interface ProviderAuthError { - data: ProviderAuthError.Data; - - name: 'ProviderAuthError'; -} - -export namespace ProviderAuthError { - export interface Data { - message: string; - - providerID: string; - } -} - -export interface UnknownError { - data: UnknownError.Data; - - name: 'UnknownError'; -} - -export namespace UnknownError { - export interface Data { - message: string; - } -} diff --git a/packages/sdk/src/resources/tui.ts b/packages/sdk/src/resources/tui.ts deleted file mode 100644 index c7efbdd6c..000000000 --- a/packages/sdk/src/resources/tui.ts +++ /dev/null @@ -1,37 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class Tui extends APIResource { - /** - * Append prompt to the TUI - */ - appendPrompt(body: TuiAppendPromptParams, options?: RequestOptions): APIPromise { - return this._client.post('/tui/append-prompt', { body, ...options }); - } - - /** - * Open the help dialog - */ - openHelp(options?: RequestOptions): APIPromise { - return this._client.post('/tui/open-help', options); - } -} - -export type TuiAppendPromptResponse = boolean; - -export type TuiOpenHelpResponse = boolean; - -export interface TuiAppendPromptParams { - text: string; -} - -export declare namespace Tui { - export { - type TuiAppendPromptResponse as TuiAppendPromptResponse, - type TuiOpenHelpResponse as TuiOpenHelpResponse, - type TuiAppendPromptParams as TuiAppendPromptParams, - }; -} diff --git a/packages/sdk/src/streaming.ts b/packages/sdk/src/streaming.ts deleted file mode 100644 index 9e6da1063..000000000 --- a/packages/sdk/src/streaming.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/streaming instead */ -export * from './core/streaming'; diff --git a/packages/sdk/src/uploads.ts b/packages/sdk/src/uploads.ts deleted file mode 100644 index b2ef64710..000000000 --- a/packages/sdk/src/uploads.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** @deprecated Import from ./core/uploads instead */ -export * from './core/uploads'; diff --git a/packages/sdk/src/version.ts b/packages/sdk/src/version.ts deleted file mode 100644 index 48ca0ae07..000000000 --- a/packages/sdk/src/version.ts +++ /dev/null @@ -1 +0,0 @@ -export const VERSION = '0.1.0-alpha.20'; // x-release-please-version diff --git a/packages/sdk/sst-env.d.ts b/packages/sdk/sst-env.d.ts deleted file mode 100644 index b6a7e9066..000000000 --- a/packages/sdk/sst-env.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* This file is auto-generated by SST. Do not edit. */ -/* tslint:disable */ -/* eslint-disable */ -/* deno-fmt-ignore-file */ - -/// - -import "sst" -export {} \ No newline at end of file diff --git a/packages/sdk/stainless/generate.ts b/packages/sdk/stainless/generate.ts new file mode 100755 index 000000000..0a766d0d7 --- /dev/null +++ b/packages/sdk/stainless/generate.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env bun +import { $ } from "bun" +const dir = new URL(".", import.meta.url).pathname +process.chdir(dir) + +console.log("=== Generating Stainless SDK ===") +console.log(process.cwd()) + +await $`rm -rf go` +await $`bun run ../../opencode/src/index.ts generate > openapi.json` +await $`stl builds create --branch dev --pull --allow-empty --+target go` + +await $`rm -rf ../go` +await $`mv opencode-go/ ../go` +await $`rm -rf ../go/.git` diff --git a/packages/sdk/stainless/stainless-workspace.json b/packages/sdk/stainless/stainless-workspace.json new file mode 100644 index 000000000..b4230b05e --- /dev/null +++ b/packages/sdk/stainless/stainless-workspace.json @@ -0,0 +1,5 @@ +{ + "project": "opencode", + "openapi_spec": "openapi.json", + "stainless_config": "stainless.yml" +} diff --git a/packages/sdk/stainless/stainless.yml b/packages/sdk/stainless/stainless.yml new file mode 100644 index 000000000..3bc7ab0e0 --- /dev/null +++ b/packages/sdk/stainless/stainless.yml @@ -0,0 +1,151 @@ +# yaml-language-server: $schema=https://app.stainless.com/config-internal.schema.json + +organization: + name: opencode + docs: "https://opencode.ai/docs" + contact: "support@sst.dev" + +targets: + typescript: + package_name: "@opencode-ai/sdk" + production_repo: "sst/opencode-sdk-js" + publish: + npm: true + go: + package_name: opencode + production_repo: sst/opencode-sdk-go + python: + project_name: opencode-ai + package_name: opencode_ai + production_repo: sst/opencode-sdk-python + publish: + pypi: true + +environments: + production: http://localhost:54321 + +streaming: + on_event: + - kind: fallthrough + handle: yield + +resources: + $shared: + models: + unknownError: UnknownError + providerAuthError: ProviderAuthError + messageAbortedError: MessageAbortedError + + event: + methods: + list: + endpoint: get /event + paginated: false + streaming: + # This method is always streaming. + param_discriminator: null + + app: + models: + app: App + logLevel: LogLevel + provider: Provider + model: Model + mode: Mode + methods: + get: get /app + init: post /app/init + log: post /log + modes: get /mode + providers: get /config/providers + + find: + models: + match: Match + symbol: Symbol + methods: + text: get /find + files: get /find/file + symbols: get /find/symbol + + file: + models: + file: File + methods: + read: get /file + status: get /file/status + + config: + models: + config: Config + keybindsConfig: KeybindsConfig + mcpLocalConfig: McpLocalConfig + mcpRemoteConfig: McpRemoteConfig + modeConfig: ModeConfig + methods: + get: get /config + + session: + models: + session: Session + message: Message + part: Part + textPart: TextPart + textPartInput: TextPartInput + filePart: FilePart + filePartInput: FilePartInput + filePartSourceText: FilePartSourceText + filePartSource: FilePartSource + fileSource: FileSource + symbolSource: SymbolSource + toolPart: ToolPart + stepStartPart: StepStartPart + stepFinishPart: StepFinishPart + snapshotPart: SnapshotPart + assistantMessage: AssistantMessage + userMessage: UserMessage + toolStatePending: ToolStatePending + toolStateRunning: ToolStateRunning + toolStateCompleted: ToolStateCompleted + toolStateError: ToolStateError + + methods: + list: get /session + create: post /session + delete: delete /session/{id} + init: post /session/{id}/init + abort: post /session/{id}/abort + share: post /session/{id}/share + unshare: delete /session/{id}/share + summarize: post /session/{id}/summarize + messages: get /session/{id}/message + chat: post /session/{id}/message + revert: post /session/{id}/revert + unrevert: post /session/{id}/unrevert + + tui: + methods: + appendPrompt: post /tui/append-prompt + openHelp: post /tui/open-help + +settings: + disable_mock_tests: true + license: MIT + +security: + - {} + +readme: + example_requests: + default: + type: request + endpoint: get /session + params: {} + headline: + type: request + endpoint: get /session + params: {} + streaming: + type: request + endpoint: get /event + params: {} diff --git a/packages/sdk/tests/api-resources/app.test.ts b/packages/sdk/tests/api-resources/app.test.ts deleted file mode 100644 index 9ccf4557c..000000000 --- a/packages/sdk/tests/api-resources/app.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); - -describe('resource app', () => { - // skipped: tests are disabled for the time being - test.skip('get', async () => { - const responsePromise = client.app.get(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('init', async () => { - const responsePromise = client.app.init(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('log: only required params', async () => { - const responsePromise = client.app.log({ level: 'debug', message: 'message', service: 'service' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('log: required and optional params', async () => { - const response = await client.app.log({ - level: 'debug', - message: 'message', - service: 'service', - extra: { foo: 'bar' }, - }); - }); - - // skipped: tests are disabled for the time being - test.skip('modes', async () => { - const responsePromise = client.app.modes(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('providers', async () => { - const responsePromise = client.app.providers(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/packages/sdk/tests/api-resources/config.test.ts b/packages/sdk/tests/api-resources/config.test.ts deleted file mode 100644 index f85fb1005..000000000 --- a/packages/sdk/tests/api-resources/config.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); - -describe('resource config', () => { - // skipped: tests are disabled for the time being - test.skip('get', async () => { - const responsePromise = client.config.get(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/packages/sdk/tests/api-resources/event.test.ts b/packages/sdk/tests/api-resources/event.test.ts deleted file mode 100644 index 4e228bc86..000000000 --- a/packages/sdk/tests/api-resources/event.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); - -describe('resource event', () => { - // skipped: tests are disabled for the time being - test.skip('list', async () => { - const responsePromise = client.event.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/packages/sdk/tests/api-resources/file.test.ts b/packages/sdk/tests/api-resources/file.test.ts deleted file mode 100644 index 4c5178739..000000000 --- a/packages/sdk/tests/api-resources/file.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); - -describe('resource file', () => { - // skipped: tests are disabled for the time being - test.skip('read: only required params', async () => { - const responsePromise = client.file.read({ path: 'path' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('read: required and optional params', async () => { - const response = await client.file.read({ path: 'path' }); - }); - - // skipped: tests are disabled for the time being - test.skip('status', async () => { - const responsePromise = client.file.status(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/packages/sdk/tests/api-resources/find.test.ts b/packages/sdk/tests/api-resources/find.test.ts deleted file mode 100644 index ce0e7c0ae..000000000 --- a/packages/sdk/tests/api-resources/find.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); - -describe('resource find', () => { - // skipped: tests are disabled for the time being - test.skip('files: only required params', async () => { - const responsePromise = client.find.files({ query: 'query' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('files: required and optional params', async () => { - const response = await client.find.files({ query: 'query' }); - }); - - // skipped: tests are disabled for the time being - test.skip('symbols: only required params', async () => { - const responsePromise = client.find.symbols({ query: 'query' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('symbols: required and optional params', async () => { - const response = await client.find.symbols({ query: 'query' }); - }); - - // skipped: tests are disabled for the time being - test.skip('text: only required params', async () => { - const responsePromise = client.find.text({ pattern: 'pattern' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('text: required and optional params', async () => { - const response = await client.find.text({ pattern: 'pattern' }); - }); -}); diff --git a/packages/sdk/tests/api-resources/session.test.ts b/packages/sdk/tests/api-resources/session.test.ts deleted file mode 100644 index 2acf08251..000000000 --- a/packages/sdk/tests/api-resources/session.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); - -describe('resource session', () => { - // skipped: tests are disabled for the time being - test.skip('create', async () => { - const responsePromise = client.session.create(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('list', async () => { - const responsePromise = client.session.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('delete', async () => { - const responsePromise = client.session.delete('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('abort', async () => { - const responsePromise = client.session.abort('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('chat: only required params', async () => { - const responsePromise = client.session.chat('id', { - modelID: 'modelID', - parts: [{ text: 'text', type: 'text' }], - providerID: 'providerID', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('chat: required and optional params', async () => { - const response = await client.session.chat('id', { - modelID: 'modelID', - parts: [{ text: 'text', type: 'text', id: 'id', synthetic: true, time: { start: 0, end: 0 } }], - providerID: 'providerID', - messageID: 'msg', - mode: 'mode', - system: 'system', - tools: { foo: true }, - }); - }); - - // skipped: tests are disabled for the time being - test.skip('init: only required params', async () => { - const responsePromise = client.session.init('id', { - messageID: 'messageID', - modelID: 'modelID', - providerID: 'providerID', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('init: required and optional params', async () => { - const response = await client.session.init('id', { - messageID: 'messageID', - modelID: 'modelID', - providerID: 'providerID', - }); - }); - - // skipped: tests are disabled for the time being - test.skip('messages', async () => { - const responsePromise = client.session.messages('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('revert: only required params', async () => { - const responsePromise = client.session.revert('id', { messageID: 'msg' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('revert: required and optional params', async () => { - const response = await client.session.revert('id', { messageID: 'msg', partID: 'prt' }); - }); - - // skipped: tests are disabled for the time being - test.skip('share', async () => { - const responsePromise = client.session.share('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('summarize: only required params', async () => { - const responsePromise = client.session.summarize('id', { modelID: 'modelID', providerID: 'providerID' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('summarize: required and optional params', async () => { - const response = await client.session.summarize('id', { modelID: 'modelID', providerID: 'providerID' }); - }); - - // skipped: tests are disabled for the time being - test.skip('unrevert', async () => { - const responsePromise = client.session.unrevert('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('unshare', async () => { - const responsePromise = client.session.unshare('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/packages/sdk/tests/api-resources/tui.test.ts b/packages/sdk/tests/api-resources/tui.test.ts deleted file mode 100644 index 8ac0d4359..000000000 --- a/packages/sdk/tests/api-resources/tui.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import Opencode from '@opencode-ai/sdk'; - -const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' }); - -describe('resource tui', () => { - // skipped: tests are disabled for the time being - test.skip('appendPrompt: only required params', async () => { - const responsePromise = client.tui.appendPrompt({ text: 'text' }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // skipped: tests are disabled for the time being - test.skip('appendPrompt: required and optional params', async () => { - const response = await client.tui.appendPrompt({ text: 'text' }); - }); - - // skipped: tests are disabled for the time being - test.skip('openHelp', async () => { - const responsePromise = client.tui.openHelp(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); -}); diff --git a/packages/sdk/tests/base64.test.ts b/packages/sdk/tests/base64.test.ts deleted file mode 100644 index bf2d17042..000000000 --- a/packages/sdk/tests/base64.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { fromBase64, toBase64 } from '@opencode-ai/sdk/internal/utils/base64'; - -describe.each(['Buffer', 'atob'])('with %s', (mode) => { - let originalBuffer: BufferConstructor; - beforeAll(() => { - if (mode === 'atob') { - originalBuffer = globalThis.Buffer; - // @ts-expect-error Can't assign undefined to BufferConstructor - delete globalThis.Buffer; - } - }); - afterAll(() => { - if (mode === 'atob') { - globalThis.Buffer = originalBuffer; - } - }); - test('toBase64', () => { - const testCases = [ - { - input: 'hello world', - expected: 'aGVsbG8gd29ybGQ=', - }, - { - input: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), - expected: 'aGVsbG8gd29ybGQ=', - }, - { - input: undefined, - expected: '', - }, - { - input: new Uint8Array([ - 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, - 193, 71, - ]), - expected: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', - }, - { - input: '✓', - expected: '4pyT', - }, - { - input: new Uint8Array([226, 156, 147]), - expected: '4pyT', - }, - ]; - - testCases.forEach(({ input, expected }) => { - expect(toBase64(input)).toBe(expected); - }); - }); - - test('fromBase64', () => { - const testCases = [ - { - input: 'aGVsbG8gd29ybGQ=', - expected: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), - }, - { - input: '', - expected: new Uint8Array([]), - }, - { - input: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH', - expected: new Uint8Array([ - 229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123, - 193, 71, - ]), - }, - { - input: '4pyT', - expected: new Uint8Array([226, 156, 147]), - }, - ]; - - testCases.forEach(({ input, expected }) => { - expect(fromBase64(input)).toEqual(expected); - }); - }); -}); diff --git a/packages/sdk/tests/buildHeaders.test.ts b/packages/sdk/tests/buildHeaders.test.ts deleted file mode 100644 index dfa8d4360..000000000 --- a/packages/sdk/tests/buildHeaders.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { inspect } from 'node:util'; -import { buildHeaders, type HeadersLike, type NullableHeaders } from '@opencode-ai/sdk/internal/headers'; - -function inspectNullableHeaders(headers: NullableHeaders) { - return `NullableHeaders {${[ - ...[...headers.values.entries()].map(([name, value]) => ` ${inspect(name)}: ${inspect(value)}`), - ...[...headers.nulls].map((name) => ` ${inspect(name)}: null`), - ].join(', ')} }`; -} - -describe('buildHeaders', () => { - const cases: [HeadersLike[], string][] = [ - [[new Headers({ 'content-type': 'text/plain' })], `NullableHeaders { 'content-type': 'text/plain' }`], - [ - [ - { - 'content-type': 'text/plain', - }, - { - 'Content-Type': undefined, - }, - ], - `NullableHeaders { 'content-type': 'text/plain' }`, - ], - [ - [ - { - 'content-type': 'text/plain', - }, - { - 'Content-Type': null, - }, - ], - `NullableHeaders { 'content-type': null }`, - ], - [ - [ - { - cookie: 'name1=value1', - Cookie: 'name2=value2', - }, - ], - `NullableHeaders { 'cookie': 'name2=value2' }`, - ], - [ - [ - { - cookie: 'name1=value1', - Cookie: undefined, - }, - ], - `NullableHeaders { 'cookie': 'name1=value1' }`, - ], - [ - [ - { - cookie: ['name1=value1', 'name2=value2'], - }, - ], - `NullableHeaders { 'cookie': 'name1=value1; name2=value2' }`, - ], - [ - [ - { - 'x-foo': ['name1=value1', 'name2=value2'], - }, - ], - `NullableHeaders { 'x-foo': 'name1=value1, name2=value2' }`, - ], - [ - [ - [ - ['cookie', 'name1=value1'], - ['cookie', 'name2=value2'], - ['Cookie', 'name3=value3'], - ], - ], - `NullableHeaders { 'cookie': 'name1=value1; name2=value2; name3=value3' }`, - ], - [[undefined], `NullableHeaders { }`], - [[null], `NullableHeaders { }`], - ]; - for (const [input, expected] of cases) { - test(expected, () => { - expect(inspectNullableHeaders(buildHeaders(input))).toEqual(expected); - }); - } -}); diff --git a/packages/sdk/tests/form.test.ts b/packages/sdk/tests/form.test.ts deleted file mode 100644 index e8cc4edb0..000000000 --- a/packages/sdk/tests/form.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { multipartFormRequestOptions, createForm } from '@opencode-ai/sdk/internal/uploads'; -import { toFile } from '@opencode-ai/sdk/core/uploads'; - -describe('form data validation', () => { - test('valid values do not error', async () => { - await multipartFormRequestOptions( - { - body: { - foo: 'foo', - string: 1, - bool: true, - file: await toFile(Buffer.from('some-content')), - blob: new Blob(['Some content'], { type: 'text/plain' }), - }, - }, - fetch, - ); - }); - - test('null', async () => { - await expect( - multipartFormRequestOptions( - { - body: { - null: null, - }, - }, - fetch, - ), - ).rejects.toThrow(TypeError); - }); - - test('undefined is stripped', async () => { - const form = await createForm( - { - foo: undefined, - bar: 'baz', - }, - fetch, - ); - expect(form.has('foo')).toBe(false); - expect(form.get('bar')).toBe('baz'); - }); - - test('nested undefined property is stripped', async () => { - const form = await createForm( - { - bar: { - baz: undefined, - }, - }, - fetch, - ); - expect(Array.from(form.entries())).toEqual([]); - - const form2 = await createForm( - { - bar: { - foo: 'string', - baz: undefined, - }, - }, - fetch, - ); - expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); - }); - - test('nested undefined array item is stripped', async () => { - const form = await createForm( - { - bar: [undefined, undefined], - }, - fetch, - ); - expect(Array.from(form.entries())).toEqual([]); - - const form2 = await createForm( - { - bar: [undefined, 'foo'], - }, - fetch, - ); - expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]); - }); -}); diff --git a/packages/sdk/tests/index.test.ts b/packages/sdk/tests/index.test.ts deleted file mode 100644 index 05b51e074..000000000 --- a/packages/sdk/tests/index.test.ts +++ /dev/null @@ -1,690 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIPromise } from '@opencode-ai/sdk/core/api-promise'; - -import util from 'node:util'; -import Opencode from '@opencode-ai/sdk'; -import { APIUserAbortError } from '@opencode-ai/sdk'; -const defaultFetch = fetch; - -describe('instantiate client', () => { - const env = process.env; - - beforeEach(() => { - jest.resetModules(); - process.env = { ...env }; - }); - - afterEach(() => { - process.env = env; - }); - - describe('defaultHeaders', () => { - const client = new Opencode({ - baseURL: 'http://localhost:5000/', - defaultHeaders: { 'X-My-Default-Header': '2' }, - }); - - test('they are used in the request', async () => { - const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); - expect(req.headers.get('x-my-default-header')).toEqual('2'); - }); - - test('can ignore `undefined` and leave the default', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - headers: { 'X-My-Default-Header': undefined }, - }); - expect(req.headers.get('x-my-default-header')).toEqual('2'); - }); - - test('can be removed with `null`', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - headers: { 'X-My-Default-Header': null }, - }); - expect(req.headers.has('x-my-default-header')).toBe(false); - }); - }); - describe('logging', () => { - const env = process.env; - - beforeEach(() => { - process.env = { ...env }; - process.env['OPENCODE_LOG'] = undefined; - }); - - afterEach(() => { - process.env = env; - }); - - const forceAPIResponseForClient = async (client: Opencode) => { - await new APIPromise( - client, - Promise.resolve({ - response: new Response(), - controller: new AbortController(), - requestLogID: 'log_000000', - retryOfRequestLogID: undefined, - startTime: Date.now(), - options: { - method: 'get', - path: '/', - }, - }), - ); - }; - - test('debug logs when log level is debug', async () => { - const debugMock = jest.fn(); - const logger = { - debug: debugMock, - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - const client = new Opencode({ logger: logger, logLevel: 'debug' }); - - await forceAPIResponseForClient(client); - expect(debugMock).toHaveBeenCalled(); - }); - - test('default logLevel is warn', async () => { - const client = new Opencode({}); - expect(client.logLevel).toBe('warn'); - }); - - test('debug logs are skipped when log level is info', async () => { - const debugMock = jest.fn(); - const logger = { - debug: debugMock, - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - const client = new Opencode({ logger: logger, logLevel: 'info' }); - - await forceAPIResponseForClient(client); - expect(debugMock).not.toHaveBeenCalled(); - }); - - test('debug logs happen with debug env var', async () => { - const debugMock = jest.fn(); - const logger = { - debug: debugMock, - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - process.env['OPENCODE_LOG'] = 'debug'; - const client = new Opencode({ logger: logger }); - expect(client.logLevel).toBe('debug'); - - await forceAPIResponseForClient(client); - expect(debugMock).toHaveBeenCalled(); - }); - - test('warn when env var level is invalid', async () => { - const warnMock = jest.fn(); - const logger = { - debug: jest.fn(), - info: jest.fn(), - warn: warnMock, - error: jest.fn(), - }; - - process.env['OPENCODE_LOG'] = 'not a log level'; - const client = new Opencode({ logger: logger }); - expect(client.logLevel).toBe('warn'); - expect(warnMock).toHaveBeenCalledWith( - 'process.env[\'OPENCODE_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]', - ); - }); - - test('client log level overrides env var', async () => { - const debugMock = jest.fn(); - const logger = { - debug: debugMock, - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - - process.env['OPENCODE_LOG'] = 'debug'; - const client = new Opencode({ logger: logger, logLevel: 'off' }); - - await forceAPIResponseForClient(client); - expect(debugMock).not.toHaveBeenCalled(); - }); - - test('no warning logged for invalid env var level + valid client level', async () => { - const warnMock = jest.fn(); - const logger = { - debug: jest.fn(), - info: jest.fn(), - warn: warnMock, - error: jest.fn(), - }; - - process.env['OPENCODE_LOG'] = 'not a log level'; - const client = new Opencode({ logger: logger, logLevel: 'debug' }); - expect(client.logLevel).toBe('debug'); - expect(warnMock).not.toHaveBeenCalled(); - }); - }); - - describe('defaultQuery', () => { - test('with null query params given', () => { - const client = new Opencode({ baseURL: 'http://localhost:5000/', defaultQuery: { apiVersion: 'foo' } }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); - }); - - test('multiple default query params', () => { - const client = new Opencode({ - baseURL: 'http://localhost:5000/', - defaultQuery: { apiVersion: 'foo', hello: 'world' }, - }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); - }); - - test('overriding with `undefined`', () => { - const client = new Opencode({ baseURL: 'http://localhost:5000/', defaultQuery: { hello: 'world' } }); - expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); - }); - }); - - test('custom fetch', async () => { - const client = new Opencode({ - baseURL: 'http://localhost:5000/', - fetch: (url) => { - return Promise.resolve( - new Response(JSON.stringify({ url, custom: true }), { - headers: { 'Content-Type': 'application/json' }, - }), - ); - }, - }); - - const response = await client.get('/foo'); - expect(response).toEqual({ url: 'http://localhost:5000/foo', custom: true }); - }); - - test('explicit global fetch', async () => { - // make sure the global fetch type is assignable to our Fetch type - const client = new Opencode({ baseURL: 'http://localhost:5000/', fetch: defaultFetch }); - }); - - test('custom signal', async () => { - const client = new Opencode({ - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', - fetch: (...args) => { - return new Promise((resolve, reject) => - setTimeout( - () => - defaultFetch(...args) - .then(resolve) - .catch(reject), - 300, - ), - ); - }, - }); - - const controller = new AbortController(); - setTimeout(() => controller.abort(), 200); - - const spy = jest.spyOn(client, 'request'); - - await expect(client.get('/foo', { signal: controller.signal })).rejects.toThrowError(APIUserAbortError); - expect(spy).toHaveBeenCalledTimes(1); - }); - - test('normalized method', async () => { - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - capturedRequest = init; - return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Opencode({ baseURL: 'http://localhost:5000/', fetch: testFetch }); - - await client.patch('/foo'); - expect(capturedRequest?.method).toEqual('PATCH'); - }); - - describe('baseUrl', () => { - test('trailing slash', () => { - const client = new Opencode({ baseURL: 'http://localhost:5000/custom/path/' }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); - }); - - test('no trailing slash', () => { - const client = new Opencode({ baseURL: 'http://localhost:5000/custom/path' }); - expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); - }); - - afterEach(() => { - process.env['OPENCODE_BASE_URL'] = undefined; - }); - - test('explicit option', () => { - const client = new Opencode({ baseURL: 'https://example.com' }); - expect(client.baseURL).toEqual('https://example.com'); - }); - - test('env variable', () => { - process.env['OPENCODE_BASE_URL'] = 'https://example.com/from_env'; - const client = new Opencode({}); - expect(client.baseURL).toEqual('https://example.com/from_env'); - }); - - test('empty env variable', () => { - process.env['OPENCODE_BASE_URL'] = ''; // empty - const client = new Opencode({}); - expect(client.baseURL).toEqual('http://localhost:54321'); - }); - - test('blank env variable', () => { - process.env['OPENCODE_BASE_URL'] = ' '; // blank - const client = new Opencode({}); - expect(client.baseURL).toEqual('http://localhost:54321'); - }); - - test('in request options', () => { - const client = new Opencode({}); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/option/foo', - ); - }); - - test('in request options overridden by client options', () => { - const client = new Opencode({ baseURL: 'http://localhost:5000/client' }); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/client/foo', - ); - }); - - test('in request options overridden by env variable', () => { - process.env['OPENCODE_BASE_URL'] = 'http://localhost:5000/env'; - const client = new Opencode({}); - expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( - 'http://localhost:5000/env/foo', - ); - }); - }); - - test('maxRetries option is correctly set', () => { - const client = new Opencode({ maxRetries: 4 }); - expect(client.maxRetries).toEqual(4); - - // default - const client2 = new Opencode({}); - expect(client2.maxRetries).toEqual(2); - }); - - describe('withOptions', () => { - test('creates a new client with overridden options', async () => { - const client = new Opencode({ baseURL: 'http://localhost:5000/', maxRetries: 3 }); - - const newClient = client.withOptions({ - maxRetries: 5, - baseURL: 'http://localhost:5001/', - }); - - // Verify the new client has updated options - expect(newClient.maxRetries).toEqual(5); - expect(newClient.baseURL).toEqual('http://localhost:5001/'); - - // Verify the original client is unchanged - expect(client.maxRetries).toEqual(3); - expect(client.baseURL).toEqual('http://localhost:5000/'); - - // Verify it's a different instance - expect(newClient).not.toBe(client); - expect(newClient.constructor).toBe(client.constructor); - }); - - test('inherits options from the parent client', async () => { - const client = new Opencode({ - baseURL: 'http://localhost:5000/', - defaultHeaders: { 'X-Test-Header': 'test-value' }, - defaultQuery: { 'test-param': 'test-value' }, - }); - - const newClient = client.withOptions({ - baseURL: 'http://localhost:5001/', - }); - - // Test inherited options remain the same - expect(newClient.buildURL('/foo', null)).toEqual('http://localhost:5001/foo?test-param=test-value'); - - const { req } = await newClient.buildRequest({ path: '/foo', method: 'get' }); - expect(req.headers.get('x-test-header')).toEqual('test-value'); - }); - - test('respects runtime property changes when creating new client', () => { - const client = new Opencode({ baseURL: 'http://localhost:5000/', timeout: 1000 }); - - // Modify the client properties directly after creation - client.baseURL = 'http://localhost:6000/'; - client.timeout = 2000; - - // Create a new client with withOptions - const newClient = client.withOptions({ - maxRetries: 10, - }); - - // Verify the new client uses the updated properties, not the original ones - expect(newClient.baseURL).toEqual('http://localhost:6000/'); - expect(newClient.timeout).toEqual(2000); - expect(newClient.maxRetries).toEqual(10); - - // Original client should still have its modified properties - expect(client.baseURL).toEqual('http://localhost:6000/'); - expect(client.timeout).toEqual(2000); - expect(client.maxRetries).not.toEqual(10); - - // Verify URL building uses the updated baseURL - expect(newClient.buildURL('/bar', null)).toEqual('http://localhost:6000/bar'); - }); - }); -}); - -describe('request building', () => { - const client = new Opencode({}); - - describe('custom headers', () => { - test('handles undefined', async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: { value: 'hello' }, - headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null }, - }); - expect(req.headers.get('x-foo')).toEqual('bar'); - expect(req.headers.get('x-Foo')).toEqual('bar'); - expect(req.headers.get('X-Foo')).toEqual('bar'); - expect(req.headers.get('x-baz')).toEqual(null); - }); - }); -}); - -describe('default encoder', () => { - const client = new Opencode({}); - - class Serializable { - toJSON() { - return { $type: 'Serializable' }; - } - } - class Collection { - #things: T[]; - constructor(things: T[]) { - this.#things = Array.from(things); - } - toJSON() { - return Array.from(this.#things); - } - [Symbol.iterator]() { - return this.#things[Symbol.iterator]; - } - } - for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) { - test(`serializes ${util.inspect(jsonValue)} as json`, async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: jsonValue, - }); - expect(req.headers).toBeInstanceOf(Headers); - expect(req.headers.get('content-type')).toEqual('application/json'); - expect(req.body).toBe(JSON.stringify(jsonValue)); - }); - } - - const encoder = new TextEncoder(); - const asyncIterable = (async function* () { - yield encoder.encode('a\n'); - yield encoder.encode('b\n'); - yield encoder.encode('c\n'); - })(); - for (const streamValue of [ - [encoder.encode('a\nb\nc\n')][Symbol.iterator](), - new Response('a\nb\nc\n').body, - asyncIterable, - ]) { - test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: streamValue, - }); - expect(req.headers).toBeInstanceOf(Headers); - expect(req.headers.get('content-type')).toEqual(null); - expect(req.body).toBeInstanceOf(ReadableStream); - expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); - }); - } - - test(`can set content-type for ReadableStream`, async () => { - const { req } = await client.buildRequest({ - path: '/foo', - method: 'post', - body: new Response('a\nb\nc\n').body, - headers: { 'Content-Type': 'text/plain' }, - }); - expect(req.headers).toBeInstanceOf(Headers); - expect(req.headers.get('content-type')).toEqual('text/plain'); - expect(req.body).toBeInstanceOf(ReadableStream); - expect(await new Response(req.body).text()).toBe('a\nb\nc\n'); - }); -}); - -describe('retries', () => { - test('retry on timeout', async () => { - let count = 0; - const testFetch = async ( - url: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Promise( - (resolve, reject) => signal?.addEventListener('abort', () => reject(new Error('timed out'))), - ); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Opencode({ timeout: 10, fetch: testFetch }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); - - test('retry count header', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Opencode({ fetch: testFetch, maxRetries: 4 }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - - expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('2'); - expect(count).toEqual(3); - }); - - test('omit retry count header', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new Opencode({ fetch: testFetch, maxRetries: 4 }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - headers: { 'X-Stainless-Retry-Count': null }, - }), - ).toEqual({ a: 1 }); - - expect((capturedRequest!.headers as Headers).has('x-stainless-retry-count')).toBe(false); - }); - - test('omit retry count header by default', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new Opencode({ - fetch: testFetch, - maxRetries: 4, - defaultHeaders: { 'X-Stainless-Retry-Count': null }, - }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - }), - ).toEqual({ a: 1 }); - - expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); - }); - - test('overwrite retry count header', async () => { - let count = 0; - let capturedRequest: RequestInit | undefined; - const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise => { - count++; - if (count <= 2) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - capturedRequest = init; - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - const client = new Opencode({ fetch: testFetch, maxRetries: 4 }); - - expect( - await client.request({ - path: '/foo', - method: 'get', - headers: { 'X-Stainless-Retry-Count': '42' }, - }), - ).toEqual({ a: 1 }); - - expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('42'); - }); - - test('retry on 429 with retry-after', async () => { - let count = 0; - const testFetch = async ( - url: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After': '0.1', - }, - }); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Opencode({ fetch: testFetch }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); - - test('retry on 429 with retry-after-ms', async () => { - let count = 0; - const testFetch = async ( - url: string | URL | Request, - { signal }: RequestInit = {}, - ): Promise => { - if (count++ === 0) { - return new Response(undefined, { - status: 429, - headers: { - 'Retry-After-Ms': '10', - }, - }); - } - return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); - }; - - const client = new Opencode({ fetch: testFetch }); - - expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); - expect(count).toEqual(2); - expect( - await client - .request({ path: '/foo', method: 'get' }) - .asResponse() - .then((r) => r.text()), - ).toEqual(JSON.stringify({ a: 1 })); - expect(count).toEqual(3); - }); -}); diff --git a/packages/sdk/tests/internal/decoders/line.test.ts b/packages/sdk/tests/internal/decoders/line.test.ts deleted file mode 100644 index e9874befb..000000000 --- a/packages/sdk/tests/internal/decoders/line.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { findDoubleNewlineIndex, LineDecoder } from '@opencode-ai/sdk/internal/decoders/line'; - -function decodeChunks(chunks: string[], { flush }: { flush: boolean } = { flush: false }): string[] { - const decoder = new LineDecoder(); - const lines: string[] = []; - for (const chunk of chunks) { - lines.push(...decoder.decode(chunk)); - } - - if (flush) { - lines.push(...decoder.flush()); - } - - return lines; -} - -describe('line decoder', () => { - test('basic', () => { - // baz is not included because the line hasn't ended yet - expect(decodeChunks(['foo', ' bar\nbaz'])).toEqual(['foo bar']); - }); - - test('basic with \\r', () => { - expect(decodeChunks(['foo', ' bar\r\nbaz'])).toEqual(['foo bar']); - expect(decodeChunks(['foo', ' bar\r\nbaz'], { flush: true })).toEqual(['foo bar', 'baz']); - }); - - test('trailing new lines', () => { - expect(decodeChunks(['foo', ' bar', 'baz\n', 'thing\n'])).toEqual(['foo barbaz', 'thing']); - }); - - test('trailing new lines with \\r', () => { - expect(decodeChunks(['foo', ' bar', 'baz\r\n', 'thing\r\n'])).toEqual(['foo barbaz', 'thing']); - }); - - test('escaped new lines', () => { - expect(decodeChunks(['foo', ' bar\\nbaz\n'])).toEqual(['foo bar\\nbaz']); - }); - - test('escaped new lines with \\r', () => { - expect(decodeChunks(['foo', ' bar\\r\\nbaz\n'])).toEqual(['foo bar\\r\\nbaz']); - }); - - test('\\r & \\n split across multiple chunks', () => { - expect(decodeChunks(['foo\r', '\n', 'bar'], { flush: true })).toEqual(['foo', 'bar']); - }); - - test('single \\r', () => { - expect(decodeChunks(['foo\r', 'bar'], { flush: true })).toEqual(['foo', 'bar']); - }); - - test('double \\r', () => { - expect(decodeChunks(['foo\r', 'bar\r'], { flush: true })).toEqual(['foo', 'bar']); - expect(decodeChunks(['foo\r', '\r', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); - // implementation detail that we don't yield the single \r line until a new \r or \n is encountered - expect(decodeChunks(['foo\r', '\r', 'bar'], { flush: false })).toEqual(['foo']); - }); - - test('double \\r then \\r\\n', () => { - expect(decodeChunks(['foo\r', '\r', '\r', '\n', 'bar', '\n'])).toEqual(['foo', '', '', 'bar']); - expect(decodeChunks(['foo\n', '\n', '\n', 'bar', '\n'])).toEqual(['foo', '', '', 'bar']); - }); - - test('double newline', () => { - expect(decodeChunks(['foo\n\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); - expect(decodeChunks(['foo', '\n', '\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); - expect(decodeChunks(['foo\n', '\n', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); - expect(decodeChunks(['foo', '\n', '\n', 'bar'], { flush: true })).toEqual(['foo', '', 'bar']); - }); - - test('multi-byte characters across chunks', () => { - const decoder = new LineDecoder(); - - // bytes taken from the string 'известни' and arbitrarily split - // so that some multi-byte characters span multiple chunks - expect(decoder.decode(new Uint8Array([0xd0]))).toHaveLength(0); - expect(decoder.decode(new Uint8Array([0xb8, 0xd0, 0xb7, 0xd0]))).toHaveLength(0); - expect( - decoder.decode(new Uint8Array([0xb2, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0xd0, 0xbd, 0xd0, 0xb8])), - ).toHaveLength(0); - - const decoded = decoder.decode(new Uint8Array([0xa])); - expect(decoded).toEqual(['известни']); - }); - - test('flushing trailing newlines', () => { - expect(decodeChunks(['foo\n', '\nbar'], { flush: true })).toEqual(['foo', '', 'bar']); - }); - - test('flushing empty buffer', () => { - expect(decodeChunks([], { flush: true })).toEqual([]); - }); -}); - -describe('findDoubleNewlineIndex', () => { - test('finds \\n\\n', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\n\nbar'))).toBe(5); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\n\nbar'))).toBe(2); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\n\n'))).toBe(5); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\n\n'))).toBe(2); - }); - - test('finds \\r\\r', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\rbar'))).toBe(5); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\rbar'))).toBe(2); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\r'))).toBe(5); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\r'))).toBe(2); - }); - - test('finds \\r\\n\\r\\n', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r\nbar'))).toBe(7); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\n\r\nbar'))).toBe(4); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r\n'))).toBe(7); - expect(findDoubleNewlineIndex(new TextEncoder().encode('\r\n\r\n'))).toBe(4); - }); - - test('returns -1 when no double newline found', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\nbar'))).toBe(-1); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\rbar'))).toBe(-1); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\nbar'))).toBe(-1); - expect(findDoubleNewlineIndex(new TextEncoder().encode(''))).toBe(-1); - }); - - test('handles incomplete patterns', () => { - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n\r'))).toBe(-1); - expect(findDoubleNewlineIndex(new TextEncoder().encode('foo\r\n'))).toBe(-1); - }); -}); diff --git a/packages/sdk/tests/path.test.ts b/packages/sdk/tests/path.test.ts deleted file mode 100644 index bece09472..000000000 --- a/packages/sdk/tests/path.test.ts +++ /dev/null @@ -1,462 +0,0 @@ -import { createPathTagFunction, encodeURIPath } from '@opencode-ai/sdk/internal/utils/path'; -import { inspect } from 'node:util'; -import { runInNewContext } from 'node:vm'; - -describe('path template tag function', () => { - test('validates input', () => { - const testParams = ['', '.', '..', 'x', '%2e', '%2E', '%2e%2e', '%2E%2e', '%2e%2E', '%2E%2E']; - const testCases = [ - ['/path_params/', '/a'], - ['/path_params/', '/'], - ['/path_params/', ''], - ['', '/a'], - ['', '/'], - ['', ''], - ['a'], - [''], - ['/path_params/', ':initiate'], - ['/path_params/', '.json'], - ['/path_params/', '?beta=true'], - ['/path_params/', '.?beta=true'], - ['/path_params/', '/', '/download'], - ['/path_params/', '-', '/download'], - ['/path_params/', '', '/download'], - ['/path_params/', '.', '/download'], - ['/path_params/', '..', '/download'], - ['/plain/path'], - ]; - - function paramPermutations(len: number): string[][] { - if (len === 0) return []; - if (len === 1) return testParams.map((e) => [e]); - const rest = paramPermutations(len - 1); - return testParams.flatMap((e) => rest.map((r) => [e, ...r])); - } - - // We need to test how %2E is handled, so we use a custom encoder that does no escaping. - const rawPath = createPathTagFunction((s) => s); - - const emptyObject = {}; - const mathObject = Math; - const numberObject = new Number(); - const stringObject = new String(); - const basicClass = new (class {})(); - const classWithToString = new (class { - toString() { - return 'ok'; - } - })(); - - // Invalid values - expect(() => rawPath`/a/${null}/b`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Null is not a valid path parameter\n' + - '/a/null/b\n' + - ' ^^^^', - ); - expect(() => rawPath`/a/${undefined}/b`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Undefined is not a valid path parameter\n' + - '/a/undefined/b\n' + - ' ^^^^^^^^^', - ); - expect(() => rawPath`/a/${emptyObject}/b`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Object is not a valid path parameter\n' + - '/a/[object Object]/b\n' + - ' ^^^^^^^^^^^^^^^', - ); - expect(() => rawPath`?${mathObject}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Math is not a valid path parameter\n' + - '?[object Math]\n' + - ' ^^^^^^^^^^^^^', - ); - expect(() => rawPath`/${basicClass}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Object is not a valid path parameter\n' + - '/[object Object]\n' + - ' ^^^^^^^^^^^^^^', - ); - expect(() => rawPath`/../${''}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - '/../\n' + - ' ^^', - ); - expect(() => rawPath`/../${{}}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - 'Value of type Object is not a valid path parameter\n' + - '/../[object Object]\n' + - ' ^^ ^^^^^^^^^^^^^^', - ); - - // Valid values - expect(rawPath`/${0}`).toBe('/0'); - expect(rawPath`/${''}`).toBe('/'); - expect(rawPath`/${numberObject}`).toBe('/0'); - expect(rawPath`${stringObject}/`).toBe('/'); - expect(rawPath`/${classWithToString}`).toBe('/ok'); - - // We need to check what happens with cross-realm values, which we might get from - // Jest or other frames in a browser. - - const newRealm = runInNewContext('globalThis'); - expect(newRealm.Object).not.toBe(Object); - - const crossRealmObject = newRealm.Object(); - const crossRealmMathObject = newRealm.Math; - const crossRealmNumber = new newRealm.Number(); - const crossRealmString = new newRealm.String(); - const crossRealmClass = new (class extends newRealm.Object {})(); - const crossRealmClassWithToString = new (class extends newRealm.Object { - toString() { - return 'ok'; - } - })(); - - // Invalid cross-realm values - expect(() => rawPath`/a/${crossRealmObject}/b`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Object is not a valid path parameter\n' + - '/a/[object Object]/b\n' + - ' ^^^^^^^^^^^^^^^', - ); - expect(() => rawPath`?${crossRealmMathObject}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Math is not a valid path parameter\n' + - '?[object Math]\n' + - ' ^^^^^^^^^^^^^', - ); - expect(() => rawPath`/${crossRealmClass}`).toThrow( - 'Path parameters result in path with invalid segments:\n' + - 'Value of type Object is not a valid path parameter\n' + - '/[object Object]\n' + - ' ^^^^^^^^^^^^^^^', - ); - - // Valid cross-realm values - expect(rawPath`/${crossRealmNumber}`).toBe('/0'); - expect(rawPath`${crossRealmString}/`).toBe('/'); - expect(rawPath`/${crossRealmClassWithToString}`).toBe('/ok'); - - const results: { - [pathParts: string]: { - [params: string]: { valid: boolean; result?: string; error?: string }; - }; - } = {}; - - for (const pathParts of testCases) { - const pathResults: Record = {}; - results[JSON.stringify(pathParts)] = pathResults; - for (const params of paramPermutations(pathParts.length - 1)) { - const stringRaw = String.raw({ raw: pathParts }, ...params); - const plainString = String.raw( - { raw: pathParts.map((e) => e.replace(/\./g, 'x')) }, - ...params.map((e) => 'X'.repeat(e.length)), - ); - const normalizedStringRaw = new URL(stringRaw, 'https://example.com').href; - const normalizedPlainString = new URL(plainString, 'https://example.com').href; - const pathResultsKey = JSON.stringify(params); - try { - const result = rawPath(pathParts, ...params); - expect(result).toBe(stringRaw); - // there are no special segments, so the length of the normalized path is - // equal to the length of the normalized plain path. - expect(normalizedStringRaw.length).toBe(normalizedPlainString.length); - pathResults[pathResultsKey] = { - valid: true, - result, - }; - } catch (e) { - const error = String(e); - expect(error).toMatch(/Path parameters result in path with invalid segment/); - // there are special segments, so the length of the normalized path is - // different than the length of the normalized plain path. - expect(normalizedStringRaw.length).not.toBe(normalizedPlainString.length); - pathResults[pathResultsKey] = { - valid: false, - error, - }; - } - } - } - - expect(results).toMatchObject({ - '["/path_params/","/a"]': { - '["x"]': { valid: true, result: '/path_params/x/a' }, - '[""]': { valid: true, result: '/path_params//a' }, - '["%2E%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E%2e/a\n' + - ' ^^^^^^', - }, - '["%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E/a\n' + - ' ^^^', - }, - }, - '["/path_params/","/"]': { - '["x"]': { valid: true, result: '/path_params/x/' }, - '[""]': { valid: true, result: '/path_params//' }, - '["%2e%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2e%2E/\n' + - ' ^^^^^^', - }, - '["%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/%2e/\n' + - ' ^^^', - }, - }, - '["/path_params/",""]': { - '[""]': { valid: true, result: '/path_params/' }, - '["x"]': { valid: true, result: '/path_params/x' }, - '["%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E\n' + - ' ^^^', - }, - '["%2E%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E%2e\n' + - ' ^^^^^^', - }, - }, - '["","/a"]': { - '[""]': { valid: true, result: '/a' }, - '["x"]': { valid: true, result: 'x/a' }, - '["%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E" can\'t be safely passed as a path parameter\n%2E/a\n^^^', - }, - '["%2e%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + - '%2e%2E/a\n' + - '^^^^^^', - }, - }, - '["","/"]': { - '["x"]': { valid: true, result: 'x/' }, - '[""]': { valid: true, result: '/' }, - '["%2E%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + - '%2E%2e/\n' + - '^^^^^^', - }, - '["."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - './\n^', - }, - }, - '["",""]': { - '[""]': { valid: true, result: '' }, - '["x"]': { valid: true, result: 'x' }, - '[".."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - '..\n^^', - }, - '["."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - '.\n^', - }, - }, - '["a"]': {}, - '[""]': {}, - '["/path_params/",":initiate"]': { - '[""]': { valid: true, result: '/path_params/:initiate' }, - '["."]': { valid: true, result: '/path_params/.:initiate' }, - }, - '["/path_params/",".json"]': { - '["x"]': { valid: true, result: '/path_params/x.json' }, - '["."]': { valid: true, result: '/path_params/..json' }, - }, - '["/path_params/","?beta=true"]': { - '["x"]': { valid: true, result: '/path_params/x?beta=true' }, - '[""]': { valid: true, result: '/path_params/?beta=true' }, - '["%2E%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E%2E?beta=true\n' + - ' ^^^^^^', - }, - '["%2e%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2e%2E?beta=true\n' + - ' ^^^^^^', - }, - }, - '["/path_params/",".?beta=true"]': { - '[".."]': { valid: true, result: '/path_params/...?beta=true' }, - '["x"]': { valid: true, result: '/path_params/x.?beta=true' }, - '[""]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - '/path_params/.?beta=true\n' + - ' ^', - }, - '["%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2e." can\'t be safely passed as a path parameter\n' + - '/path_params/%2e.?beta=true\n' + - ' ^^^^', - }, - }, - '["/path_params/","/","/download"]': { - '["",""]': { valid: true, result: '/path_params///download' }, - '["","x"]': { valid: true, result: '/path_params//x/download' }, - '[".","%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - 'Value "%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/./%2e/download\n' + - ' ^ ^^^', - }, - '["%2E%2e","%2e"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E%2e" can\'t be safely passed as a path parameter\n' + - 'Value "%2e" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E%2e/%2e/download\n' + - ' ^^^^^^ ^^^', - }, - }, - '["/path_params/","-","/download"]': { - '["","%2e"]': { valid: true, result: '/path_params/-%2e/download' }, - '["%2E",".."]': { valid: true, result: '/path_params/%2E-../download' }, - }, - '["/path_params/","","/download"]': { - '["%2E%2e","%2e%2E"]': { valid: true, result: '/path_params/%2E%2e%2e%2E/download' }, - '["%2E",".."]': { valid: true, result: '/path_params/%2E../download' }, - '["","%2E"]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E" can\'t be safely passed as a path parameter\n' + - '/path_params/%2E/download\n' + - ' ^^^', - }, - '["%2E","."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "%2E." can\'t be safely passed as a path parameter\n' + - '/path_params/%2E./download\n' + - ' ^^^^', - }, - }, - '["/path_params/",".","/download"]': { - '["%2e%2e",""]': { valid: true, result: '/path_params/%2e%2e./download' }, - '["","%2e%2e"]': { valid: true, result: '/path_params/.%2e%2e/download' }, - '["",""]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value "." can\'t be safely passed as a path parameter\n' + - '/path_params/./download\n' + - ' ^', - }, - '["","."]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - '/path_params/../download\n' + - ' ^^', - }, - }, - '["/path_params/","..","/download"]': { - '["","%2E"]': { valid: true, result: '/path_params/..%2E/download' }, - '["","x"]': { valid: true, result: '/path_params/..x/download' }, - '["",""]': { - valid: false, - error: - 'Error: Path parameters result in path with invalid segments:\n' + - 'Value ".." can\'t be safely passed as a path parameter\n' + - '/path_params/../download\n' + - ' ^^', - }, - }, - }); - }); -}); - -describe('encodeURIPath', () => { - const testCases: string[] = [ - '', - // Every ASCII character - ...Array.from({ length: 0x7f }, (_, i) => String.fromCharCode(i)), - // Unicode BMP codepoint - 'å', - // Unicode supplementary codepoint - '😃', - ]; - - for (const param of testCases) { - test('properly encodes ' + inspect(param), () => { - const encoded = encodeURIPath(param); - const naiveEncoded = encodeURIComponent(param); - // we should never encode more characters than encodeURIComponent - expect(naiveEncoded.length).toBeGreaterThanOrEqual(encoded.length); - expect(decodeURIComponent(encoded)).toBe(param); - }); - } - - test("leaves ':' intact", () => { - expect(encodeURIPath(':')).toBe(':'); - }); - - test("leaves '@' intact", () => { - expect(encodeURIPath('@')).toBe('@'); - }); -}); diff --git a/packages/sdk/tests/streaming.test.ts b/packages/sdk/tests/streaming.test.ts deleted file mode 100644 index ea4bdad01..000000000 --- a/packages/sdk/tests/streaming.test.ts +++ /dev/null @@ -1,219 +0,0 @@ -import assert from 'assert'; -import { _iterSSEMessages } from '@opencode-ai/sdk/core/streaming'; -import { ReadableStreamFrom } from '@opencode-ai/sdk/internal/shims'; - -describe('streaming decoding', () => { - test('basic', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: completion\n'); - yield Buffer.from('data: {"foo":true}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(JSON.parse(event.value.data)).toEqual({ foo: true }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('data without event', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('data: {"foo":true}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toBeNull(); - expect(JSON.parse(event.value.data)).toEqual({ foo: true }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('event without data', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: foo\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('foo'); - expect(event.value.data).toEqual(''); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('multiple events', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: foo\n'); - yield Buffer.from('\n'); - yield Buffer.from('event: ping\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('foo'); - expect(event.value.data).toEqual(''); - - event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('ping'); - expect(event.value.data).toEqual(''); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('multiple events with data', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: foo\n'); - yield Buffer.from('data: {"foo":true}\n'); - yield Buffer.from('\n'); - yield Buffer.from('event: ping\n'); - yield Buffer.from('data: {"bar":false}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('foo'); - expect(JSON.parse(event.value.data)).toEqual({ foo: true }); - - event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('ping'); - expect(JSON.parse(event.value.data)).toEqual({ bar: false }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('multiple data lines with empty line', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: ping\n'); - yield Buffer.from('data: {\n'); - yield Buffer.from('data: "foo":\n'); - yield Buffer.from('data: \n'); - yield Buffer.from('data:\n'); - yield Buffer.from('data: true}\n'); - yield Buffer.from('\n\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('ping'); - expect(JSON.parse(event.value.data)).toEqual({ foo: true }); - expect(event.value.data).toEqual('{\n"foo":\n\n\ntrue}'); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('data json escaped double new line', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: ping\n'); - yield Buffer.from('data: {"foo": "my long\\n\\ncontent"}'); - yield Buffer.from('\n\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('ping'); - expect(JSON.parse(event.value.data)).toEqual({ foo: 'my long\n\ncontent' }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('special new line characters', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('data: {"content": "culpa "}\n'); - yield Buffer.from('\n'); - yield Buffer.from('data: {"content": "'); - yield Buffer.from([0xe2, 0x80, 0xa8]); - yield Buffer.from('"}\n'); - yield Buffer.from('\n'); - yield Buffer.from('data: {"content": "foo"}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(JSON.parse(event.value.data)).toEqual({ content: 'culpa ' }); - - event = await stream.next(); - assert(event.value); - expect(JSON.parse(event.value.data)).toEqual({ content: Buffer.from([0xe2, 0x80, 0xa8]).toString() }); - - event = await stream.next(); - assert(event.value); - expect(JSON.parse(event.value.data)).toEqual({ content: 'foo' }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); - - test('multi-byte characters across chunks', async () => { - async function* body(): AsyncGenerator { - yield Buffer.from('event: completion\n'); - yield Buffer.from('data: {"content": "'); - // bytes taken from the string 'известни' and arbitrarily split - // so that some multi-byte characters span multiple chunks - yield Buffer.from([0xd0]); - yield Buffer.from([0xb8, 0xd0, 0xb7, 0xd0]); - yield Buffer.from([0xb2, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0xd0, 0xbd, 0xd0, 0xb8]); - yield Buffer.from('"}\n'); - yield Buffer.from('\n'); - } - - const stream = _iterSSEMessages(new Response(ReadableStreamFrom(body())), new AbortController())[ - Symbol.asyncIterator - ](); - - let event = await stream.next(); - assert(event.value); - expect(event.value.event).toEqual('completion'); - expect(JSON.parse(event.value.data)).toEqual({ content: 'известни' }); - - event = await stream.next(); - expect(event.done).toBeTruthy(); - }); -}); diff --git a/packages/sdk/tests/stringifyQuery.test.ts b/packages/sdk/tests/stringifyQuery.test.ts deleted file mode 100644 index a028772d4..000000000 --- a/packages/sdk/tests/stringifyQuery.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Opencode } from '@opencode-ai/sdk'; - -const { stringifyQuery } = Opencode.prototype as any; - -describe(stringifyQuery, () => { - for (const [input, expected] of [ - [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], - [{ a: null, b: false, c: undefined }, 'a=&b=false'], - [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], - [ - { 'a/b': 'c/d', 'e=f': 'g&h' }, - `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( - 'e=f', - )}=${encodeURIComponent('g&h')}`, - ], - ]) { - it(`${JSON.stringify(input)} -> ${expected}`, () => { - expect(stringifyQuery(input)).toEqual(expected); - }); - } - - for (const value of [[], {}, new Date()]) { - it(`${JSON.stringify(value)} -> `, () => { - expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); - }); - } -}); diff --git a/packages/sdk/tests/uploads.test.ts b/packages/sdk/tests/uploads.test.ts deleted file mode 100644 index c5e52ee09..000000000 --- a/packages/sdk/tests/uploads.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import fs from 'fs'; -import type { ResponseLike } from '@opencode-ai/sdk/internal/to-file'; -import { toFile } from '@opencode-ai/sdk/core/uploads'; -import { File } from 'node:buffer'; - -class MyClass { - name: string = 'foo'; -} - -function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { - return { - url, - blob: async () => content || new Blob([]), - }; -} - -describe('toFile', () => { - it('throws a helpful error for mismatched types', async () => { - await expect( - // @ts-expect-error intentionally mismatched type - toFile({ foo: 'string' }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unexpected data type: object; constructor: Object; props: ["foo"]"`, - ); - - await expect( - // @ts-expect-error intentionally mismatched type - toFile(new MyClass()), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`, - ); - }); - - it('disallows string at the type-level', async () => { - // @ts-expect-error we intentionally do not type support for `string` - // to help people avoid passing a file path - const file = await toFile('contents'); - expect(file.text()).resolves.toEqual('contents'); - }); - - it('extracts a file name from a Response', async () => { - const response = mockResponse({ url: 'https://example.com/my/audio.mp3' }); - const file = await toFile(response); - expect(file.name).toEqual('audio.mp3'); - }); - - it('extracts a file name from a File', async () => { - const input = new File(['foo'], 'input.jsonl'); - const file = await toFile(input); - expect(file.name).toEqual('input.jsonl'); - }); - - it('extracts a file name from a ReadStream', async () => { - const input = fs.createReadStream(__filename); - const file = await toFile(input); - expect(file.name).toEqual('uploads.test.ts'); - }); - - it('does not copy File objects', async () => { - const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); - const file = await toFile(input); - expect(file).toBe(input); - expect(file.name).toEqual('input.jsonl'); - expect(file.type).toBe('jsonl'); - }); - - it('is assignable to File and Blob', async () => { - const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); - const result = await toFile(input); - const file: File = result; - const blob: Blob = result; - void file, blob; - }); -}); - -describe('missing File error message', () => { - let prevGlobalFile: unknown; - let prevNodeFile: unknown; - beforeEach(() => { - // The file shim captures the global File object when it's first imported. - const buffer = require('node:buffer'); - // @ts-ignore - prevGlobalFile = globalThis.File; - prevNodeFile = buffer.File; - // @ts-ignore - globalThis.File = undefined; - buffer.File = undefined; - }); - afterEach(() => { - // Clean up - // @ts-ignore - globalThis.File = prevGlobalFile; - require('node:buffer').File = prevNodeFile; - }); - - test('is thrown', async () => { - const uploads = await import('@opencode-ai/sdk/core/uploads'); - await expect( - uploads.toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })), - ).rejects.toMatchInlineSnapshot( - `[Error: \`File\` is not defined as a global, which is required for file uploads.]`, - ); - }); -}); diff --git a/packages/sdk/tsc-multi.json b/packages/sdk/tsc-multi.json deleted file mode 100644 index 384ddac5b..000000000 --- a/packages/sdk/tsc-multi.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "targets": [ - { - "extname": ".js", - "module": "commonjs", - "shareHelpers": "internal/tslib.js" - }, - { - "extname": ".mjs", - "module": "esnext", - "shareHelpers": "internal/tslib.mjs" - } - ], - "projects": ["tsconfig.build.json"] -} diff --git a/packages/sdk/tsconfig.build.json b/packages/sdk/tsconfig.build.json deleted file mode 100644 index 2421951bd..000000000 --- a/packages/sdk/tsconfig.build.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["dist/src"], - "exclude": [], - "compilerOptions": { - "rootDir": "./dist/src", - "paths": { - "@opencode-ai/sdk/*": ["dist/src/*"], - "@opencode-ai/sdk": ["dist/src/index.ts"] - }, - "noEmit": false, - "declaration": true, - "declarationMap": true, - "outDir": "dist", - "pretty": true, - "sourceMap": true - } -} diff --git a/packages/sdk/tsconfig.deno.json b/packages/sdk/tsconfig.deno.json deleted file mode 100644 index 849e070db..000000000 --- a/packages/sdk/tsconfig.deno.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["dist-deno"], - "exclude": [], - "compilerOptions": { - "rootDir": "./dist-deno", - "lib": ["es2020", "DOM"], - "noEmit": true, - "declaration": true, - "declarationMap": true, - "outDir": "dist-deno", - "pretty": true, - "sourceMap": true - } -} diff --git a/packages/sdk/tsconfig.dist-src.json b/packages/sdk/tsconfig.dist-src.json deleted file mode 100644 index c550e2996..000000000 --- a/packages/sdk/tsconfig.dist-src.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - // this config is included in the published src directory to prevent TS errors - // from appearing when users go to source, and VSCode opens the source .ts file - // via declaration maps - "include": ["index.ts"], - "compilerOptions": { - "target": "ES2015", - "lib": ["DOM", "DOM.Iterable", "ES2018"], - "moduleResolution": "node" - } -} diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json deleted file mode 100644 index aafeb418f..000000000 --- a/packages/sdk/tsconfig.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "include": ["src", "tests", "examples"], - "exclude": [], - "compilerOptions": { - "target": "es2020", - "lib": ["es2020"], - "module": "commonjs", - "moduleResolution": "node", - "esModuleInterop": true, - "baseUrl": "./", - "paths": { - "@opencode-ai/sdk/*": ["src/*"], - "@opencode-ai/sdk": ["src/index.ts"] - }, - "noEmit": true, - - "resolveJsonModule": true, - - "forceConsistentCasingInFileNames": true, - - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "noImplicitReturns": true, - "alwaysStrict": true, - "exactOptionalPropertyTypes": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "isolatedModules": false, - - "skipLibCheck": true - } -} diff --git a/packages/sdk/yarn.lock b/packages/sdk/yarn.lock deleted file mode 100644 index 58c08d5f1..000000000 --- a/packages/sdk/yarn.lock +++ /dev/null @@ -1,3500 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@andrewbranch/untar.js@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz#ba9494f85eb83017c5c855763969caf1d0adea00" - integrity sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw== - -"@arethetypeswrong/cli@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@arethetypeswrong/cli/-/cli-0.17.0.tgz#f97f10926b3f9f9eb5117550242d2e06c25cadac" - integrity sha512-xSMW7bfzVWpYw5JFgZqBXqr6PdR0/REmn3DkxCES5N0JTcB0CVgbIynJCvKBFmXaPc3hzmmTrb7+yPDRoOSZdA== - dependencies: - "@arethetypeswrong/core" "0.17.0" - chalk "^4.1.2" - cli-table3 "^0.6.3" - commander "^10.0.1" - marked "^9.1.2" - marked-terminal "^7.1.0" - semver "^7.5.4" - -"@arethetypeswrong/core@0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@arethetypeswrong/core/-/core-0.17.0.tgz#abb3b5f425056d37193644c2a2de4aecf866b76b" - integrity sha512-FHyhFizXNetigTVsIhqXKGYLpazPS5YNojEPpZEUcBPt9wVvoEbNIvG+hybuBR+pjlRcbyuqhukHZm1fr+bDgA== - dependencies: - "@andrewbranch/untar.js" "^1.0.3" - cjs-module-lexer "^1.2.3" - fflate "^0.8.2" - lru-cache "^10.4.3" - semver "^7.5.4" - typescript "5.6.1-rc" - validate-npm-package-name "^5.0.0" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" - integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== - dependencies: - "@babel/highlight" "^7.23.4" - chalk "^2.4.2" - -"@babel/compat-data@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" - integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.6.tgz#8be77cd77c55baadcc1eae1c33df90ab6d2151d4" - integrity sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.6" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.23.6" - "@babel/parser" "^7.23.6" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.6" - "@babel/types" "^7.23.6" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.23.6", "@babel/generator@^7.7.2": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" - integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== - dependencies: - "@babel/types" "^7.23.6" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" - integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== - dependencies: - "@babel/compat-data" "^7.23.5" - "@babel/helper-validator-option" "^7.23.5" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-transforms@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" - integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" - integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-option@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" - integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== - -"@babel/helpers@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.6.tgz#d03af2ee5fb34691eec0cda90f5ecbb4d4da145a" - integrity sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA== - dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.6" - "@babel/types" "^7.23.6" - -"@babel/highlight@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" - integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" - integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" - integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz#24f460c85dbbc983cd2b9c4994178bcc01df958f" - integrity sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/template@^7.22.15", "@babel/template@^7.3.3": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" - integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.6" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.6" - "@babel/types" "^7.23.6" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.3": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" - integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== - dependencies: - "@babel/helper-string-parser" "^7.23.4" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== - dependencies: - "@cspotcode/source-map-consumer" "0.8.0" - -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint/config-array@^0.19.0": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" - integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== - dependencies: - "@eslint/object-schema" "^2.1.6" - debug "^4.3.1" - minimatch "^3.1.2" - -"@eslint/core@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" - integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/core@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" - integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/eslintrc@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" - integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^10.0.1" - globals "^14.0.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@9.20.0": - version "9.20.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" - integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== - -"@eslint/object-schema@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" - integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== - -"@eslint/plugin-kit@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" - integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== - dependencies: - "@eslint/core" "^0.10.0" - levn "^0.4.1" - -"@humanfs/core@^0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" - integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== - -"@humanfs/node@^0.16.6": - version "0.16.6" - resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" - integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== - dependencies: - "@humanfs/core" "^0.19.1" - "@humanwhocodes/retry" "^0.3.0" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/retry@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" - integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== - -"@humanwhocodes/retry@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" - integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - dependencies: - "@jest/console" "^29.7.0" - "@jest/reporters" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.7.0" - jest-config "^29.7.0" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-resolve-dependencies "^29.7.0" - jest-runner "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - jest-watcher "^29.7.0" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/create-cache-key-function@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" - integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== - dependencies: - "@jest/types" "^29.6.3" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pkgr/core@^0.2.4": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" - integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sindresorhus/is@^4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" - integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== - -"@sinonjs/commons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@swc/core-darwin-arm64@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.16.tgz#2cd45d709ce76d448d96bf8d0006849541436611" - integrity sha512-UOCcH1GvjRnnM/LWT6VCGpIk0OhHRq6v1U6QXuPt5wVsgXnXQwnf5k3sG5Cm56hQHDvhRPY6HCsHi/p0oek8oQ== - -"@swc/core-darwin-x64@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.4.16.tgz#a5bc7d8b1dd850adb0bb95c6b5c742b92201fd01" - integrity sha512-t3bgqFoYLWvyVtVL6KkFNCINEoOrIlyggT/kJRgi1y0aXSr0oVgcrQ4ezJpdeahZZ4N+Q6vT3ffM30yIunELNA== - -"@swc/core-linux-arm-gnueabihf@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.16.tgz#961744908ee5cbb79bc009dcf58cc8b831111f38" - integrity sha512-DvHuwvEF86YvSd0lwnzVcjOTZ0jcxewIbsN0vc/0fqm9qBdMMjr9ox6VCam1n3yYeRtj4VFgrjeNFksqbUejdQ== - -"@swc/core-linux-arm64-gnu@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.16.tgz#43713be3f26757d82d2745dc25f8b63400e0a3d0" - integrity sha512-9Uu5YlPbyCvbidjKtYEsPpyZlu16roOZ5c2tP1vHfnU9bgf5Tz5q5VovSduNxPHx+ed2iC1b1URODHvDzbbDuQ== - -"@swc/core-linux-arm64-musl@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.16.tgz#394a7d030f3a61902bd3947bb9d70d26d42f3c81" - integrity sha512-/YZq/qB1CHpeoL0eMzyqK5/tYZn/rzKoCYDviFU4uduSUIJsDJQuQA/skdqUzqbheOXKAd4mnJ1hT04RbJ8FPQ== - -"@swc/core-linux-x64-gnu@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.16.tgz#71eb108b784f9d551ee8a35ebcdaed972f567981" - integrity sha512-UUjaW5VTngZYDcA8yQlrFmqs1tLi1TxbKlnaJwoNhel9zRQ0yG1YEVGrzTvv4YApSuIiDK18t+Ip927bwucuVQ== - -"@swc/core-linux-x64-musl@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.16.tgz#10dbaedb4e3dfc7268e3a9a66ad3431471ef035b" - integrity sha512-aFhxPifevDTwEDKPi4eRYWzC0p/WYJeiFkkpNU5Uc7a7M5iMWPAbPFUbHesdlb9Jfqs5c07oyz86u+/HySBNPQ== - -"@swc/core-win32-arm64-msvc@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.16.tgz#80247adff6c245ff32b44d773c1a148858cd655f" - integrity sha512-bTD43MbhIHL2s5QgCwyleaGwl96Gk/scF2TaVKdUe4QlJCDV/YK9h5oIBAp63ckHtE8GHlH4c8dZNBiAXn4Org== - -"@swc/core-win32-ia32-msvc@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.16.tgz#e540afc3ccf3224267b4ddfb408f9d9737984686" - integrity sha512-/lmZeAN/qV5XbK2SEvi8e2RkIg8FQNYiSA8y2/Zb4gTUMKVO5JMLH0BSWMiIKMstKDPDSxMWgwJaQHF8UMyPmQ== - -"@swc/core-win32-x64-msvc@1.4.16": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.16.tgz#f880939fca32c181adfe7e3abd2b6b7857bd3489" - integrity sha512-BPAfFfODWXtUu6SwaTTftDHvcbDyWBSI/oanUeRbQR5vVWkXoQ3cxLTsDluc3H74IqXS5z1Uyoe0vNo2hB1opA== - -"@swc/core@^1.3.102": - version "1.4.16" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.4.16.tgz#d175bae2acfecd53bcbd4293f1fba5ec316634a0" - integrity sha512-Xaf+UBvW6JNuV131uvSNyMXHn+bh6LyKN4tbv7tOUFQpXyz/t9YWRE04emtlUW9Y0qrm/GKFCbY8n3z6BpZbTA== - dependencies: - "@swc/counter" "^0.1.2" - "@swc/types" "^0.1.5" - optionalDependencies: - "@swc/core-darwin-arm64" "1.4.16" - "@swc/core-darwin-x64" "1.4.16" - "@swc/core-linux-arm-gnueabihf" "1.4.16" - "@swc/core-linux-arm64-gnu" "1.4.16" - "@swc/core-linux-arm64-musl" "1.4.16" - "@swc/core-linux-x64-gnu" "1.4.16" - "@swc/core-linux-x64-musl" "1.4.16" - "@swc/core-win32-arm64-msvc" "1.4.16" - "@swc/core-win32-ia32-msvc" "1.4.16" - "@swc/core-win32-x64-msvc" "1.4.16" - -"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" - integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== - -"@swc/jest@^0.2.29": - version "0.2.36" - resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" - integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== - dependencies: - "@jest/create-cache-key-function" "^29.7.0" - "@swc/counter" "^0.1.3" - jsonc-parser "^3.2.0" - -"@swc/types@^0.1.5": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.6.tgz#2f13f748995b247d146de2784d3eb7195410faba" - integrity sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg== - dependencies: - "@swc/counter" "^0.1.3" - -"@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== - -"@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== - -"@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== - -"@tsconfig/node16@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== - -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.8" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" - integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.4" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.4.tgz#ec2c06fed6549df8bc0eb4615b683749a4a92e1b" - integrity sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== - dependencies: - "@babel/types" "^7.20.7" - -"@types/estree@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" - integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== - -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^29.4.0": - version "29.5.11" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" - integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/json-schema@^7.0.15": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/node@*": - version "20.10.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" - integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== - dependencies: - undici-types "~5.26.4" - -"@types/node@^20.17.6": - version "20.17.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.6.tgz#6e4073230c180d3579e8c60141f99efdf5df0081" - integrity sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ== - dependencies: - undici-types "~6.19.2" - -"@types/stack-utils@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^17.0.8": - version "17.0.32" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" - integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== - dependencies: - "@types/yargs-parser" "*" - -"@typescript-eslint/eslint-plugin@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a" - integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/type-utils" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - graphemer "^1.4.0" - ignore "^5.3.1" - natural-compare "^1.4.0" - ts-api-utils "^2.0.1" - -"@typescript-eslint/parser@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b" - integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q== - dependencies: - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/typescript-estree" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b" - integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw== - dependencies: - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - -"@typescript-eslint/type-utils@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c" - integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA== - dependencies: - "@typescript-eslint/typescript-estree" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - debug "^4.3.4" - ts-api-utils "^2.0.1" - -"@typescript-eslint/types@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4" - integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ== - -"@typescript-eslint/typescript-estree@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf" - integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag== - dependencies: - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/visitor-keys" "8.31.1" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.0.1" - -"@typescript-eslint/utils@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14" - integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.31.1" - "@typescript-eslint/types" "8.31.1" - "@typescript-eslint/typescript-estree" "8.31.1" - -"@typescript-eslint/visitor-keys@8.31.1": - version "8.31.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75" - integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw== - dependencies: - "@typescript-eslint/types" "8.31.1" - eslint-visitor-keys "^4.2.0" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^8.14.0: - version "8.14.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" - integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== - -acorn@^8.4.1: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - 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" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-escapes@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" - integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== - dependencies: - environment "^1.0.0" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" - integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - -anymatch@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.22.2: - version "4.22.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" - integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== - dependencies: - caniuse-lite "^1.0.30001565" - electron-to-chromium "^1.4.601" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001565: - version "1.0.30001570" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" - integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - 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" - -chalk@^4.0.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -cjs-module-lexer@^1.0.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" - integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== - -cjs-module-lexer@^1.2.3: - version "1.4.1" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" - integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-highlight@^2.1.11: - version "2.1.11" - resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" - integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== - dependencies: - chalk "^4.0.0" - highlight.js "^10.7.1" - mz "^2.4.0" - parse5 "^5.1.1" - parse5-htmlparser2-tree-adapter "^6.0.0" - yargs "^16.0.0" - -cli-table3@^0.6.3, cli-table3@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" - integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.3, cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^4.3.4, debug@^4.3.7: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== - dependencies: - ms "^2.1.3" - -dedent@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" - integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -electron-to-chromium@^1.4.601: - version "1.4.614" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.614.tgz#2fe789d61fa09cb875569f37c309d0c2701f91c0" - integrity sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojilib@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" - integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== - -environment@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" - integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-plugin-prettier@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz#99b55d7dd70047886b2222fdd853665f180b36af" - integrity sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg== - dependencies: - prettier-linter-helpers "^1.0.0" - synckit "^0.11.7" - -eslint-plugin-unused-imports@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz#62ddc7446ccbf9aa7b6f1f0b00a980423cda2738" - integrity sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ== - -eslint-scope@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" - integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.3.0: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint-visitor-keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" - integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== - -eslint@^9.20.1: - version "9.20.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" - integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.0" - "@eslint/core" "^0.11.0" - "@eslint/eslintrc" "^3.2.0" - "@eslint/js" "9.20.0" - "@eslint/plugin-kit" "^0.2.5" - "@humanfs/node" "^0.16.6" - "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.1" - "@types/estree" "^1.0.6" - "@types/json-schema" "^7.0.15" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.6" - debug "^4.3.2" - escape-string-regexp "^4.0.0" - eslint-scope "^8.2.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" - esquery "^1.5.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^8.0.0" - find-up "^5.0.0" - glob-parent "^6.0.2" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - json-stable-stringify-without-jsonify "^1.0.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - -espree@^10.0.1, espree@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" - integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== - dependencies: - acorn "^8.14.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^29.0.0, expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" - integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== - -fast-glob@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fflate@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" - integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== - -file-entry-cache@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" - integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== - dependencies: - flat-cache "^4.0.0" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" - integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.4" - -flatted@^3.2.9: - version "3.3.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" - integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stdin@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" - integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" - integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== - -graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -hasown@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" - integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== - dependencies: - function-bind "^1.1.2" - -highlight.js@^10.7.1: - version "10.7.3" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" - integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ignore-walk@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" - integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== - dependencies: - minimatch "^5.0.1" - -ignore@^5.2.0, ignore@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-instrument@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" - integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" - integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.7.0" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - pretty-format "^29.7.0" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - create-jest "^29.7.0" - exit "^0.1.2" - import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - yargs "^17.3.1" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.0" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.7.0" - jest-environment-node "^29.7.0" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-runner "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - dependencies: - "@jest/console" "^29.7.0" - "@jest/environment" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.7.0" - jest-environment-node "^29.7.0" - jest-haste-map "^29.7.0" - jest-leak-detector "^29.7.0" - jest-message-util "^29.7.0" - jest-resolve "^29.7.0" - jest-runtime "^29.7.0" - jest-util "^29.7.0" - jest-watcher "^29.7.0" - jest-worker "^29.7.0" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/globals" "^29.7.0" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.7.0" - graceful-fs "^4.2.9" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - natural-compare "^1.4.0" - pretty-format "^29.7.0" - semver "^7.5.3" - -jest-util@^29.0.0, jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.7.0" - string-length "^4.0.1" - -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.4.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonc-parser@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" - integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== - -keyv@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lru-cache@^10.4.3: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@1.x, make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -marked-terminal@^7.1.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-7.2.1.tgz#9c1ae073a245a03c6a13e3eeac6f586f29856068" - integrity sha512-rQ1MoMFXZICWNsKMiiHwP/Z+92PLKskTPXj+e7uwXmuMPkNn7iTqC+IvDekVm1MPeC9wYQeLxeFaOvudRR/XbQ== - dependencies: - ansi-escapes "^7.0.0" - ansi-regex "^6.1.0" - chalk "^5.3.0" - cli-highlight "^2.1.11" - cli-table3 "^0.6.5" - node-emoji "^2.1.3" - supports-hyperlinks "^3.1.0" - -marked@^9.1.2: - version "9.1.6" - resolved "https://registry.yarnpkg.com/marked/-/marked-9.1.6.tgz#5d2a3f8180abfbc5d62e3258a38a1c19c0381695" - integrity sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -mri@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" - integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -mz@^2.4.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -node-emoji@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.1.3.tgz#93cfabb5cc7c3653aa52f29d6ffb7927d8047c06" - integrity sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA== - dependencies: - "@sindresorhus/is" "^4.6.0" - char-regex "^1.0.2" - emojilib "^2.4.0" - skin-tone "^2.0.0" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-bundled@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" - integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== - dependencies: - npm-normalize-package-bin "^2.0.0" - -npm-normalize-package-bin@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" - integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== - -npm-packlist@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" - integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== - dependencies: - glob "^8.0.1" - ignore-walk "^5.0.1" - npm-bundled "^2.0.0" - npm-normalize-package-bin "^2.0.0" - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -object-assign@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== - dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - -p-all@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" - integrity sha512-qUZbvbBFVXm6uJ7U/WDiO0fv6waBMbjlCm4E66oZdRR+egswICarIdHyVSZZHudH8T5SF8x/JG0q0duFzPnlBw== - dependencies: - p-map "^4.0.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse5-htmlparser2-tree-adapter@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" - integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== - dependencies: - parse5 "^6.0.1" - -parse5@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parse5@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pirates@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848" - integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== - -pretty-format@^29.0.0, pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -publint@^0.2.12: - version "0.2.12" - resolved "https://registry.yarnpkg.com/publint/-/publint-0.2.12.tgz#d25cd6bd243d5bdd640344ecdddb3eeafdcc4059" - integrity sha512-YNeUtCVeM4j9nDiTT2OPczmlyzOkIXNtdDZnSuajAxS/nZ6j3t7Vs9SUB4euQNddiltIwu7Tdd3s+hr08fAsMw== - dependencies: - npm-packlist "^5.1.3" - picocolors "^1.1.1" - sade "^1.8.1" - -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -pure-rand@^6.0.0: - version "6.0.4" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" - integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -readable-stream@^3.4.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" - integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== - -resolve@^1.20.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -sade@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" - integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== - dependencies: - mri "^1.1.0" - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.5.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -semver@^7.5.4: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -semver@^7.6.0: - version "7.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" - integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -skin-tone@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" - integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== - dependencies: - unicode-emoji-modifier-base "^1.0.0" - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-to-stream@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/string-to-stream/-/string-to-stream-3.0.1.tgz#480e6fb4d5476d31cb2221f75307a5dcb6638a42" - integrity sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg== - dependencies: - readable-stream "^3.4.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -superstruct@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" - integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac" - integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -synckit@^0.11.7: - version "0.11.8" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457" - integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== - dependencies: - "@pkgr/core" "^0.2.4" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -ts-api-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd" - integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== - -ts-jest@^29.1.0: - version "29.1.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" - integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== - dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "4.x" - make-error "1.x" - semver "^7.5.3" - yargs-parser "^21.0.1" - -ts-node@^10.5.0: - version "10.7.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" - integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== - dependencies: - "@cspotcode/source-map-support" "0.7.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.0" - yn "3.1.1" - -"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz": - version "1.1.8" - resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz#f544b359b8f05e607771ffacc280e58201476b04" - dependencies: - debug "^4.3.7" - fast-glob "^3.3.2" - get-stdin "^8.0.0" - p-all "^3.0.0" - picocolors "^1.1.1" - signal-exit "^3.0.7" - string-to-stream "^3.0.1" - superstruct "^1.0.4" - tslib "^2.8.1" - yargs "^17.7.2" - -tsconfig-paths@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -typescript-eslint@8.31.1: - version "8.31.1" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.31.1.tgz#b77ab1e48ced2daab9225ff94bab54391a4af69b" - integrity sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA== - dependencies: - "@typescript-eslint/eslint-plugin" "8.31.1" - "@typescript-eslint/parser" "8.31.1" - "@typescript-eslint/utils" "8.31.1" - -typescript@5.6.1-rc: - version "5.6.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.1-rc.tgz#d5e4d7d8170174fed607b74cc32aba3d77018e02" - integrity sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ== - -typescript@5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -undici-types@~6.19.2: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" - integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== - -unicode-emoji-modifier-base@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" - integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== - -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -v8-compile-cache-lib@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" - integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== - -v8-to-istanbul@^9.0.1: - version "9.2.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" - integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - -validate-npm-package-name@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" - integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-parser@^21.0.1, yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^16.0.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.3.1, yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -- cgit v1.2.3