From 2c3bcf3e41c492917632e960b63fc3d5ab1b72c9 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 19 May 2026 11:26:56 +0530 Subject: [PATCH 001/237] feat(httpapi): add v2 public error schemas (#28297) --- .../server/routes/instance/httpapi/errors.ts | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/packages/opencode/src/server/routes/instance/httpapi/errors.ts b/packages/opencode/src/server/routes/instance/httpapi/errors.ts index e5df6f5abfed..004fc7a6e262 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/errors.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/errors.ts @@ -1,5 +1,127 @@ import { Schema } from "effect" +export class InvalidRequestError extends Schema.TaggedErrorClass()( + "InvalidRequestError", + { + message: Schema.String, + kind: Schema.optional(Schema.String), + field: Schema.optional(Schema.String), + }, + { httpApiStatus: 400 }, +) {} + +export class UnauthorizedError extends Schema.TaggedErrorClass()( + "UnauthorizedError", + { message: Schema.String }, + { httpApiStatus: 401 }, +) {} + +export class ForbiddenError extends Schema.TaggedErrorClass()( + "ForbiddenError", + { message: Schema.String }, + { httpApiStatus: 403 }, +) {} + +export class ConflictError extends Schema.TaggedErrorClass()( + "ConflictError", + { + message: Schema.String, + resource: Schema.optional(Schema.String), + }, + { httpApiStatus: 409 }, +) {} + +export class UpstreamError extends Schema.TaggedErrorClass()( + "UpstreamError", + { + message: Schema.String, + service: Schema.optional(Schema.String), + status: Schema.optional(Schema.Number), + }, + { httpApiStatus: 502 }, +) {} + +export class ServiceUnavailableError extends Schema.TaggedErrorClass()( + "ServiceUnavailableError", + { + message: Schema.String, + service: Schema.optional(Schema.String), + }, + { httpApiStatus: 503 }, +) {} + +export class TimeoutError extends Schema.TaggedErrorClass()( + "TimeoutError", + { + message: Schema.String, + operation: Schema.optional(Schema.String), + }, + { httpApiStatus: 504 }, +) {} + +export class UnknownError extends Schema.TaggedErrorClass()( + "UnknownError", + { + message: Schema.String, + ref: Schema.optional(Schema.String), + }, + { httpApiStatus: 500 }, +) {} + +export class ProviderNotFoundError extends Schema.TaggedErrorClass()( + "ProviderNotFoundError", + { + providerID: Schema.String, + message: Schema.String, + }, + { httpApiStatus: 404 }, +) {} + +export class ModelNotFoundError extends Schema.TaggedErrorClass()( + "ModelNotFoundError", + { + providerID: Schema.String, + modelID: Schema.String, + suggestions: Schema.Array(Schema.String), + message: Schema.String, + }, + { httpApiStatus: 404 }, +) {} + +export class SessionNotFoundError extends Schema.TaggedErrorClass()( + "SessionNotFoundError", + { + sessionID: Schema.String, + message: Schema.String, + }, + { httpApiStatus: 404 }, +) {} + +export class MessageNotFoundError extends Schema.TaggedErrorClass()( + "MessageNotFoundError", + { + sessionID: Schema.String, + messageID: Schema.String, + message: Schema.String, + }, + { httpApiStatus: 404 }, +) {} + +export class InvalidCursorError extends Schema.TaggedErrorClass()( + "InvalidCursorError", + { message: Schema.String }, + { httpApiStatus: 400 }, +) {} + +export class SessionBusyError extends Schema.TaggedErrorClass()( + "SessionBusyError", + { + sessionID: Schema.String, + message: Schema.String, + }, + { httpApiStatus: 409 }, +) {} + export class ApiNotFoundError extends Schema.ErrorClass("NotFoundError")( { name: Schema.Literal("NotFoundError"), From dac81cdb6853d357500ffe08d2ae0e0571396f3b Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 19 May 2026 11:42:01 +0530 Subject: [PATCH 002/237] fix(httpapi): preserve v2 openapi errors (#28298) --- .../server/routes/instance/httpapi/public.ts | 44 +++++++--- .../server/httpapi-public-openapi.test.ts | 88 +++++++++++++++++++ 2 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 packages/opencode/test/server/httpapi-public-openapi.test.ts diff --git a/packages/opencode/src/server/routes/instance/httpapi/public.ts b/packages/opencode/src/server/routes/instance/httpapi/public.ts index 12d3791eccb8..5684171837ea 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/public.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/public.ts @@ -99,17 +99,13 @@ function matchLegacyOpenApi(input: Record) { applyLegacySchemaOverrides(spec) normalizeComponentDescriptions(spec) addLegacyErrorSchemas(spec) - delete spec.components?.schemas?.Unauthorized - delete spec.components?.schemas?.EffectHttpApiErrorBadRequest - delete spec.components?.schemas?.EffectHttpApiErrorNotFound - delete spec.components?.schemas?.effect_HttpApiError_BadRequest - delete spec.components?.schemas?.effect_HttpApiError_NotFound delete spec.components?.securitySchemes for (const [path, item] of Object.entries(spec.paths ?? {})) { for (const method of ["get", "post", "put", "delete", "patch"] as const) { const operation = item[method] if (!operation) continue + const isV2Api = isV2ApiPath(path) if (operation.requestBody) { // The legacy OpenAPI surface never marked request bodies as required. // Keep that SDK surface stable while the HttpApi spec is tightened. @@ -146,11 +142,14 @@ function matchLegacyOpenApi(input: Record) { if (content.schema) content.schema = stripOptionalNull(structuredClone(content.schema)) } } - // Auth is still runtime middleware outside the public OpenAPI metadata, so - // the SDK should not expose auth schemes or generated 401 error unions. - delete operation.security - delete operation.responses?.["401"] - normalizeLegacyErrorResponses(operation) + if (!isV2Api) { + // Auth is still runtime middleware outside the legacy public OpenAPI + // metadata, so the legacy SDK should not expose auth schemes or + // generated 401 error unions. + delete operation.security + delete operation.responses?.["401"] + normalizeLegacyErrorResponses(operation) + } normalizeLegacyOperation(operation, path, method) if ((path === "/event" || path === "/global/event") && method === "get") { // HttpApi has no first-class SSE response schema, and these handlers are @@ -171,9 +170,14 @@ function matchLegacyOpenApi(input: Record) { for (const param of operation.parameters ?? []) normalizeParameter(param, route) } } + deleteUnusedLegacyErrorComponents(spec) return input } +function isV2ApiPath(path: string) { + return path === "/api" || path.startsWith("/api/") +} + function addLegacyErrorSchemas(spec: OpenApiSpec) { if (!spec.components?.schemas) return spec.components.schemas.BadRequestError = { @@ -345,6 +349,26 @@ function normalizeLegacyErrorResponses(operation: OpenApiOperation) { } } +function deleteUnusedLegacyErrorComponents(spec: OpenApiSpec) { + for (const name of [ + "Unauthorized", + "EffectHttpApiErrorBadRequest", + "EffectHttpApiErrorNotFound", + "effect_HttpApiError_BadRequest", + "effect_HttpApiError_NotFound", + ]) { + if (referencesComponent(spec.paths, name)) continue + delete spec.components?.schemas?.[name] + } +} + +function referencesComponent(input: unknown, name: string): boolean { + if (Array.isArray(input)) return input.some((item) => referencesComponent(item, name)) + if (!input || typeof input !== "object") return false + if ((input as OpenApiSchema).$ref === `#/components/schemas/${name}`) return true + return Object.values(input).some((value) => referencesComponent(value, name)) +} + function normalizeLegacyOperation(operation: OpenApiOperation, path: string, method: string) { if (path === "/experimental/console/switch" && method === "post") delete operation.responses?.["400"] if (path === "/pty/{ptyID}" && method === "put") delete operation.responses?.["404"] diff --git a/packages/opencode/test/server/httpapi-public-openapi.test.ts b/packages/opencode/test/server/httpapi-public-openapi.test.ts new file mode 100644 index 000000000000..ba415f2abc5b --- /dev/null +++ b/packages/opencode/test/server/httpapi-public-openapi.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, test } from "bun:test" +import { OpenApi } from "effect/unstable/httpapi" +import { PublicApi } from "../../src/server/routes/instance/httpapi/public" + +type Method = "get" | "post" | "put" | "delete" | "patch" +type OpenApiSchema = { readonly $ref?: string } +type OpenApiResponse = { + readonly description?: string + readonly content?: Record +} +type OpenApiOperation = { + readonly responses?: Record + readonly security?: unknown +} +type OpenApiPathItem = Partial> +type OpenApiSpec = { readonly paths: Record } + +const methods = ["get", "post", "put", "delete", "patch"] as const + +const allowedV2BuiltInEndpointErrors = [ + "GET /api/session 400 effect_HttpApiError_BadRequest", + "GET /api/session/{sessionID}/message 400 effect_HttpApiError_BadRequest", +] + +function v2Operations(spec: OpenApiSpec) { + return Object.entries(spec.paths).flatMap(([path, item]) => + path.startsWith("/api/") + ? methods.flatMap((method) => { + const operation = item[method] + return operation ? [{ method, path, operation }] : [] + }) + : [], + ) +} + +function responseRef(response: OpenApiResponse | undefined) { + return response?.content?.["application/json"]?.schema?.$ref +} + +function componentName(ref: string) { + return ref.replace("#/components/schemas/", "") +} + +function isBuiltInEndpointError(name: string) { + return name.startsWith("EffectHttpApiError") || name.startsWith("effect_HttpApiError_") +} + +describe("PublicApi OpenAPI v2 errors", () => { + test("preserves /api auth responses", () => { + const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec + + for (const route of v2Operations(spec)) { + expect(route.operation.responses?.["401"], `${route.method.toUpperCase()} ${route.path}`).toBeDefined() + expect(route.operation.security, `${route.method.toUpperCase()} ${route.path}`).toEqual([]) + } + }) + + test("does not rewrite /api endpoint errors to legacy error components", () => { + const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec + const refs = v2Operations(spec) + .flatMap((route) => + Object.entries(route.operation.responses ?? {}).flatMap(([status, response]) => { + const ref = responseRef(response) + return ref ? [`${route.method.toUpperCase()} ${route.path} ${status} ${componentName(ref)}`] : [] + }), + ) + .filter((entry) => entry.includes("BadRequestError") || entry.includes("NotFoundError")) + + expect(refs).toEqual(["GET /api/provider/{providerID} 404 NotFoundError"]) + }) + + test("new /api endpoint errors cannot use built-in components without an explicit allowlist", () => { + const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec + const builtInEndpointErrors = v2Operations(spec) + .flatMap((route) => + Object.entries(route.operation.responses ?? {}).flatMap(([status, response]) => { + if (status === "401") return [] + const ref = responseRef(response) + if (!ref) return [] + const name = componentName(ref) + return isBuiltInEndpointError(name) ? [`${route.method.toUpperCase()} ${route.path} ${status} ${name}`] : [] + }), + ) + .sort() + + expect(builtInEndpointErrors).toEqual(allowedV2BuiltInEndpointErrors) + }) +}) From 3bd304796b819a49e5d770d70e48b4e90c744ff4 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 19 May 2026 06:13:16 +0000 Subject: [PATCH 003/237] chore: generate --- packages/sdk/js/src/v2/gen/sdk.gen.ts | 18 ++++--- packages/sdk/js/src/v2/gen/types.gen.ts | 66 +++++++++++++++++++++++-- packages/sdk/openapi.json | 55 +++++++++++++++++++-- 3 files changed, 125 insertions(+), 14 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 9aed5ef9c7ef..ddbcd30920bf 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -197,17 +197,23 @@ import type { TuiSelectSessionResponses, TuiShowToastResponses, TuiSubmitPromptResponses, + V2ModelListErrors, V2ModelListResponses, V2ProviderGetErrors, V2ProviderGetResponses, + V2ProviderListErrors, V2ProviderListResponses, + V2SessionCompactErrors, V2SessionCompactResponses, + V2SessionContextErrors, V2SessionContextResponses, V2SessionListErrors, V2SessionListResponses, V2SessionMessagesErrors, V2SessionMessagesResponses, + V2SessionPromptErrors, V2SessionPromptResponses, + V2SessionWaitErrors, V2SessionWaitResponses, VcsApplyErrors, VcsApplyResponses, @@ -4232,7 +4238,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/prompt", ...options, ...params, @@ -4269,7 +4275,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/compact", ...options, ...params, @@ -4301,7 +4307,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/wait", ...options, ...params, @@ -4333,7 +4339,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/api/session/{sessionID}/context", ...options, ...params, @@ -4395,7 +4401,7 @@ export class Model extends HeyApiClient { options?: Options, ) { const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/api/model", ...options, ...params, @@ -4419,7 +4425,7 @@ export class Provider2 extends HeyApiClient { options?: Options, ) { const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/api/provider", ...options, ...params, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 517271ca4055..d0a82a5b0f3a 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -104,6 +104,10 @@ export type WellKnownAuth = { export type Auth = OAuth | ApiAuth | WellKnownAuth +export type EffectHttpApiErrorBadRequest = { + _tag: "BadRequest" +} + export type EventTuiPromptAppend = { id: string type: "tui.prompt.append" @@ -6639,9 +6643,13 @@ export type V2SessionListData = { export type V2SessionListErrors = { /** - * Bad request + * BadRequest */ - 400: BadRequestError + 400: EffectHttpApiErrorBadRequest + /** + * Unauthorized + */ + 401: unknown } export type V2SessionListError = V2SessionListErrors[keyof V2SessionListErrors] @@ -6670,6 +6678,13 @@ export type V2SessionPromptData = { url: "/api/session/{sessionID}/prompt" } +export type V2SessionPromptErrors = { + /** + * Unauthorized + */ + 401: unknown +} + export type V2SessionPromptResponses = { /** * Session.Message @@ -6691,6 +6706,13 @@ export type V2SessionCompactData = { url: "/api/session/{sessionID}/compact" } +export type V2SessionCompactErrors = { + /** + * Unauthorized + */ + 401: unknown +} + export type V2SessionCompactResponses = { /** * @@ -6712,6 +6734,13 @@ export type V2SessionWaitData = { url: "/api/session/{sessionID}/wait" } +export type V2SessionWaitErrors = { + /** + * Unauthorized + */ + 401: unknown +} + export type V2SessionWaitResponses = { /** * @@ -6733,6 +6762,13 @@ export type V2SessionContextData = { url: "/api/session/{sessionID}/context" } +export type V2SessionContextErrors = { + /** + * Unauthorized + */ + 401: unknown +} + export type V2SessionContextResponses = { /** * Success @@ -6762,9 +6798,13 @@ export type V2SessionMessagesData = { export type V2SessionMessagesErrors = { /** - * Bad request + * BadRequest */ - 400: BadRequestError + 400: EffectHttpApiErrorBadRequest + /** + * Unauthorized + */ + 401: unknown } export type V2SessionMessagesError = V2SessionMessagesErrors[keyof V2SessionMessagesErrors] @@ -6790,6 +6830,13 @@ export type V2ModelListData = { url: "/api/model" } +export type V2ModelListErrors = { + /** + * Unauthorized + */ + 401: unknown +} + export type V2ModelListResponses = { /** * Success @@ -6811,6 +6858,13 @@ export type V2ProviderListData = { url: "/api/provider" } +export type V2ProviderListErrors = { + /** + * Unauthorized + */ + 401: unknown +} + export type V2ProviderListResponses = { /** * Success @@ -6835,6 +6889,10 @@ export type V2ProviderGetData = { } export type V2ProviderGetErrors = { + /** + * Unauthorized + */ + 401: unknown /** * NotFoundError */ diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 7a33d1a3fbe4..6a123710e6bb 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -7265,6 +7265,7 @@ "required": false } ], + "security": [], "responses": { "200": { "description": "V2SessionsResponse", @@ -7277,14 +7278,17 @@ } }, "400": { - "description": "Bad request", + "description": "BadRequest", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "$ref": "#/components/schemas/effect_HttpApiError_BadRequest" } } } + }, + "401": { + "description": "Unauthorized" } }, "description": "Retrieve sessions in the requested order. Items keep that order across pages; use cursor.next or cursor.previous to move through the ordered list.", @@ -7328,6 +7332,7 @@ "required": false } ], + "security": [], "responses": { "200": { "description": "Session.Message", @@ -7338,6 +7343,9 @@ } } } + }, + "401": { + "description": "Unauthorized" } }, "description": "Create a v2 session message and queue it for the agent loop.", @@ -7400,9 +7408,13 @@ "required": false } ], + "security": [], "responses": { "204": { "description": "" + }, + "401": { + "description": "Unauthorized" } }, "description": "Compact a v2 session conversation.", @@ -7446,9 +7458,13 @@ "required": false } ], + "security": [], "responses": { "204": { "description": "" + }, + "401": { + "description": "Unauthorized" } }, "description": "Wait for a v2 session agent loop to become idle.", @@ -7492,6 +7508,7 @@ "required": false } ], + "security": [], "responses": { "200": { "description": "Success", @@ -7505,6 +7522,9 @@ } } } + }, + "401": { + "description": "Unauthorized" } }, "description": "Retrieve the active context messages for a v2 session (all messages after the last compaction).", @@ -7574,6 +7594,7 @@ "required": false } ], + "security": [], "responses": { "200": { "description": "V2SessionMessagesResponse", @@ -7586,14 +7607,17 @@ } }, "400": { - "description": "Bad request", + "description": "BadRequest", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BadRequestError" + "$ref": "#/components/schemas/effect_HttpApiError_BadRequest" } } } + }, + "401": { + "description": "Unauthorized" } }, "description": "Retrieve projected v2 messages for a session. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline.", @@ -7631,6 +7655,7 @@ "explode": true } ], + "security": [], "responses": { "200": { "description": "Success", @@ -7644,6 +7669,9 @@ } } } + }, + "401": { + "description": "Unauthorized" } }, "description": "Retrieve available v2 models ordered by release date.", @@ -7681,6 +7709,7 @@ "explode": true } ], + "security": [], "responses": { "200": { "description": "Success", @@ -7694,6 +7723,9 @@ } } } + }, + "401": { + "description": "Unauthorized" } }, "description": "Retrieve active v2 AI providers so clients can show provider availability and configuration.", @@ -7739,6 +7771,7 @@ "explode": true } ], + "security": [], "responses": { "200": { "description": "ProviderV2.Info", @@ -7750,6 +7783,9 @@ } } }, + "401": { + "description": "Unauthorized" + }, "404": { "description": "NotFoundError", "content": { @@ -9419,6 +9455,17 @@ } ] }, + "effect_HttpApiError_BadRequest": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["BadRequest"] + } + }, + "required": ["_tag"], + "additionalProperties": false + }, "Event.tui.prompt.append": { "type": "object", "properties": { From cbd2620c46c90bf6c8401fb91e74fdeeddab7ee3 Mon Sep 17 00:00:00 2001 From: MYMDO <90605529+MYMDO@users.noreply.github.com> Date: Tue, 19 May 2026 09:45:39 +0300 Subject: [PATCH 004/237] feat(i18n): add Ukrainian (uk) locale support (#28061) --- packages/app/src/context/language.tsx | 6 + packages/app/src/i18n/en.ts | 1 + packages/app/src/i18n/parity.test.ts | 3 +- packages/app/src/i18n/uk.ts | 963 ++++++++++++++++++++ packages/console/app/src/i18n/index.ts | 2 + packages/console/app/src/i18n/uk.ts | 785 ++++++++++++++++ packages/console/app/src/lib/language.ts | 7 + packages/desktop/src/renderer/i18n/index.ts | 6 + packages/desktop/src/renderer/i18n/uk.ts | 28 + packages/ui/src/i18n/uk.ts | 167 ++++ packages/web/src/i18n/locales.ts | 3 + screenshot-uk.png | Bin 0 -> 83462 bytes 12 files changed, 1970 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/i18n/uk.ts create mode 100644 packages/console/app/src/i18n/uk.ts create mode 100644 packages/desktop/src/renderer/i18n/uk.ts create mode 100644 packages/ui/src/i18n/uk.ts create mode 100644 screenshot-uk.png diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx index 51dc09cd7d9f..533b805053f1 100644 --- a/packages/app/src/context/language.tsx +++ b/packages/app/src/context/language.tsx @@ -18,6 +18,7 @@ export type Locale = | "ja" | "pl" | "ru" + | "uk" | "ar" | "no" | "br" @@ -45,6 +46,7 @@ const LOCALES: readonly Locale[] = [ "ja", "pl", "ru", + "uk", "bs", "ar", "no", @@ -65,6 +67,7 @@ const INTL: Record = { ja: "ja", pl: "pl", ru: "ru", + uk: "uk", ar: "ar", no: "nb-NO", br: "pt-BR", @@ -85,6 +88,7 @@ const LABEL_KEY: Record = { ja: "language.ja", pl: "language.pl", ru: "language.ru", + uk: "language.uk", ar: "language.ar", no: "language.no", br: "language.br", @@ -110,6 +114,7 @@ const loaders: Record, () => Promise> = { ja: () => merge(import("@/i18n/ja"), import("@opencode-ai/ui/i18n/ja")), pl: () => merge(import("@/i18n/pl"), import("@opencode-ai/ui/i18n/pl")), ru: () => merge(import("@/i18n/ru"), import("@opencode-ai/ui/i18n/ru")), + uk: () => merge(import("@/i18n/uk"), import("@opencode-ai/ui/i18n/uk")), ar: () => merge(import("@/i18n/ar"), import("@opencode-ai/ui/i18n/ar")), no: () => merge(import("@/i18n/no"), import("@opencode-ai/ui/i18n/no")), br: () => merge(import("@/i18n/br"), import("@opencode-ai/ui/i18n/br")), @@ -145,6 +150,7 @@ const localeMatchers: Array<{ locale: Locale; match: (language: string) => boole { locale: "ja", match: (language) => language.startsWith("ja") }, { locale: "pl", match: (language) => language.startsWith("pl") }, { locale: "ru", match: (language) => language.startsWith("ru") }, + { locale: "uk", match: (language) => language.startsWith("uk") }, { locale: "ar", match: (language) => language.startsWith("ar") }, { locale: "no", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 6bb9d3fc447d..2f82d0a78d3a 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -416,6 +416,7 @@ export const dict = { "language.no": "Norsk", "language.br": "Português (Brasil)", "language.bs": "Bosanski", + "language.uk": "Українська", "language.th": "ไทย", "language.tr": "Türkçe", diff --git a/packages/app/src/i18n/parity.test.ts b/packages/app/src/i18n/parity.test.ts index c06a55ab171d..8ad1beb56b0c 100644 --- a/packages/app/src/i18n/parity.test.ts +++ b/packages/app/src/i18n/parity.test.ts @@ -12,12 +12,13 @@ import { dict as ko } from "./ko" import { dict as no } from "./no" import { dict as pl } from "./pl" import { dict as ru } from "./ru" +import { dict as uk } from "./uk" import { dict as th } from "./th" import { dict as zh } from "./zh" import { dict as zht } from "./zht" import { dict as tr } from "./tr" -const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, th, tr, zh, zht] +const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, uk, th, tr, zh, zht] const keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const describe("i18n parity", () => { diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts new file mode 100644 index 000000000000..4ee63748929a --- /dev/null +++ b/packages/app/src/i18n/uk.ts @@ -0,0 +1,963 @@ +export const dict = { + "command.category.suggested": "Рекомендовані", + "command.category.view": "Вигляд", + "command.category.project": "Проєкт", + "command.category.provider": "Провайдер", + "command.category.server": "Сервер", + "command.category.session": "Сесія", + "command.category.theme": "Тема", + "command.category.language": "Мова", + "command.category.file": "Файл", + "command.category.context": "Контекст", + "command.category.terminal": "Термінал", + "command.category.model": "Модель", + "command.category.mcp": "MCP", + "command.category.agent": "Агент", + "command.category.permissions": "Дозволи", + "command.category.workspace": "Робоча область", + "command.category.settings": "Налаштування", + + "theme.scheme.system": "Системна", + "theme.scheme.light": "Світла", + "theme.scheme.dark": "Темна", + + "command.sidebar.toggle": "Перемкнути бічну панель", + "command.project.open": "Відкрити проєкт", + "command.project.previous": "Попередній проєкт", + "command.project.next": "Наступний проєкт", + "command.project.index": "Перемкнути на проєкт {{index}}", + "command.provider.connect": "Підключити провайдера", + "command.server.switch": "Перемкнути сервер", + "command.settings.open": "Відкрити налаштування", + "command.session.previous": "Попередня сесія", + "command.session.next": "Наступна сесія", + "command.session.previous.unseen": "Попередня непрочитана сесія", + "command.session.next.unseen": "Наступна непрочитана сесія", + "command.session.archive": "Архівувати сесію", + + "command.palette": "Палітра команд", + + "command.theme.cycle": "Перемкнути тему", + "command.theme.set": "Використати тему: {{theme}}", + "command.theme.scheme.cycle": "Перемкнути кольорову схему", + "command.theme.scheme.set": "Використати кольорову схему: {{scheme}}", + + "command.language.cycle": "Перемкнути мову", + "command.language.set": "Використати мову: {{language}}", + + "command.session.new": "Нова сесія", + "command.file.open": "Відкрити файл", + "command.tab.close": "Закрити вкладку", + "command.context.addSelection": "Додати виділення до контексту", + "command.context.addSelection.description": "Додати вибрані рядки з поточного файлу", + "command.input.focus": "Фокус на полі введення", + "command.terminal.toggle": "Перемкнути термінал", + "command.fileTree.toggle": "Перемкнути дерево файлів", + "command.review.toggle": "Перемкнути огляд", + "command.terminal.new": "Новий термінал", + "command.terminal.new.description": "Створити нову вкладку термінала", + "command.steps.toggle": "Перемкнути кроки", + "command.steps.toggle.description": "Показати або приховати кроки для поточного повідомлення", + "command.message.previous": "Попереднє повідомлення", + "command.message.previous.description": "Перейти до попереднього повідомлення користувача", + "command.message.next": "Наступне повідомлення", + "command.message.next.description": "Перейти до наступного повідомлення користувача", + "command.model.choose": "Вибрати модель", + "command.model.choose.description": "Вибрати іншу модель", + "command.mcp.toggle": "Перемкнути MCP", + "command.mcp.toggle.description": "Перемкнути MCP", + "command.agent.cycle": "Перемкнути агента", + "command.agent.cycle.description": "Перемкнути на наступного агента", + "command.agent.cycle.reverse": "Перемкнути агента в зворотному напрямку", + "command.agent.cycle.reverse.description": "Перемкнути на попереднього агента", + "command.model.variant.cycle": "Перемкнути рівень мислення", + "command.model.variant.cycle.description": "Перемкнути на наступний рівень зусилля", + "command.prompt.mode.shell": "Команда", + "command.prompt.mode.normal": "Запит", + "command.permissions.autoaccept.enable": "Автоматично приймати дозволи", + "command.permissions.autoaccept.disable": "Зупинити автоматичне прийняття дозволів", + "command.workspace.toggle": "Перемкнути робочі області", + "command.workspace.toggle.description": "Увімкнути або вимкнути декілька робочих областей на бічній панелі", + "command.session.undo": "Скасувати", + "command.session.undo.description": "Скасувати останнє повідомлення", + "command.session.redo": "Повторити", + "command.session.redo.description": "Повторити останнє скасоване повідомлення", + "command.session.compact": "Стиснути сесію", + "command.session.compact.description": "Підсумувати сесію, щоб зменшити розмір контексту", + "command.session.fork": "Відгалузити від повідомлення", + "command.session.fork.description": "Створити нову сесію з попереднього повідомлення", + "command.session.share": "Поділитися сесією", + "command.session.share.description": "Поділитися цією сесією та скопіювати URL у буфер обміну", + "command.session.unshare": "Припинити поширення сесії", + "command.session.unshare.description": "Припинити поширення цієї сесії", + + "palette.search.placeholder": "Пошук файлів, команд і сесій", + "palette.empty": "Результатів не знайдено", + "palette.group.commands": "Команди", + "palette.group.files": "Файли", + + "dialog.provider.search.placeholder": "Пошук провайдерів", + "dialog.provider.empty": "Провайдерів не знайдено", + "dialog.provider.group.popular": "Популярні", + "dialog.provider.group.other": "Інші", + "dialog.provider.tag.recommended": "Рекомендовані", + "dialog.provider.opencode.note": "Відібрані моделі, включаючи Claude, GPT, Gemini та інші", + "dialog.provider.opencode.tagline": "Надійні оптимізовані моделі", + "dialog.provider.opencodeGo.tagline": "Недорога підписка для всіх", + "dialog.provider.anthropic.note": "Прямий доступ до моделей Claude, включаючи Pro та Max", + "dialog.provider.copilot.note": "Моделі AI для допомоги в кодуванні через GitHub Copilot", + "dialog.provider.openai.note": "Моделі GPT для швидких і універсальних завдань AI", + "dialog.provider.google.note": "Моделі Gemini для швидких структурованих відповідей", + "dialog.provider.openrouter.note": "Доступ до всіх підтримуваних моделей від одного провайдера", + "dialog.provider.vercel.note": "Уніфікований доступ до моделей AI з інтелектуальною маршрутизацією", + + "dialog.model.select.title": "Вибрати модель", + "dialog.model.search.placeholder": "Пошук моделей", + "dialog.model.empty": "Немає результатів моделей", + "dialog.model.manage": "Керувати моделями", + "dialog.model.manage.description": "Налаштуйте, які моделі відображатимуться у виборі моделей.", + "dialog.model.manage.provider.toggle": "Перемкнути всі моделі {{provider}}", + + "dialog.model.unpaid.freeModels.title": "Безкоштовні моделі від OpenCode", + "dialog.model.unpaid.addMore.title": "Додати більше моделей від популярних провайдерів", + + "dialog.provider.viewAll": "Показати більше провайдерів", + + "provider.connect.title": "Підключити {{provider}}", + "provider.connect.title.anthropicProMax": "Увійти з Claude Pro/Max", + "provider.connect.selectMethod": "Виберіть спосіб входу для {{provider}}.", + "provider.connect.method.apiKey": "Ключ API", + "provider.connect.status.inProgress": "Авторизація виконується...", + "provider.connect.status.waiting": "Очікування авторизації...", + "provider.connect.status.failed": "Авторизація не вдалася: {{error}}", + "provider.connect.apiKey.description": + "Введіть ключ API {{provider}}, щоб підключити обліковий запис і використовувати моделі {{provider}} у OpenCode.", + "provider.connect.apiKey.label": "Ключ API {{provider}}", + "provider.connect.apiKey.placeholder": "Ключ API", + "provider.connect.apiKey.required": "Ключ API обов'язковий", + "provider.connect.opencodeZen.line1": + "OpenCode Zen надає доступ до відібраного набору надійних оптимізованих моделей для агентів кодування.", + "provider.connect.opencodeZen.line2": + "З одним ключем API ви отримаєте доступ до таких моделей, як Claude, GPT, Gemini, GLM та інших.", + "provider.connect.opencodeZen.visit.prefix": "Відвідайте ", + "provider.connect.opencodeZen.visit.link": "opencode.ai/zen", + "provider.connect.opencodeZen.visit.suffix": ", щоб отримати ключ API.", + "provider.connect.oauth.code.visit.prefix": "Відвідайте ", + "provider.connect.oauth.code.visit.link": "це посилання", + "provider.connect.oauth.code.visit.suffix": + ", щоб отримати код авторизації, підключити обліковий запис і використовувати моделі {{provider}} у OpenCode.", + "provider.connect.oauth.code.label": "Код авторизації {{method}}", + "provider.connect.oauth.code.placeholder": "Код авторизації", + "provider.connect.oauth.code.required": "Код авторизації обов'язковий", + "provider.connect.oauth.code.invalid": "Недійсний код авторизації", + "provider.connect.oauth.auto.visit.prefix": "Відвідайте ", + "provider.connect.oauth.auto.visit.link": "це посилання", + "provider.connect.oauth.auto.visit.suffix": + " і введіть код нижче, щоб підключити обліковий запис і використовувати моделі {{provider}} у OpenCode.", + "provider.connect.oauth.auto.confirmationCode": "Код підтвердження", + "provider.connect.toast.connected.title": "{{provider}} підключено", + "provider.connect.toast.connected.description": "Моделі {{provider}} тепер доступні для використання.", + + "provider.custom.title": "Користувацький провайдер", + "provider.custom.description.prefix": "Налаштуйте провайдера, сумісного з OpenAI. Перегляньте ", + "provider.custom.description.link": "документацію з налаштування провайдера", + "provider.custom.description.suffix": ".", + "provider.custom.field.providerID.label": "ID провайдера", + "provider.custom.field.providerID.placeholder": "myprovider", + "provider.custom.field.providerID.description": "Малі літери, цифри, дефіси або підкреслення", + "provider.custom.field.name.label": "Відображувана назва", + "provider.custom.field.name.placeholder": "Мій AI Провайдер", + "provider.custom.field.baseURL.label": "Базовий URL", + "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", + "provider.custom.field.apiKey.label": "Ключ API", + "provider.custom.field.apiKey.placeholder": "Ключ API", + "provider.custom.field.apiKey.description": "Необов'язково. Залиште порожнім, якщо ви керуєте авторизацією через заголовки.", + "provider.custom.models.label": "Моделі", + "provider.custom.models.id.label": "ID", + "provider.custom.models.id.placeholder": "model-id", + "provider.custom.models.name.label": "Назва", + "provider.custom.models.name.placeholder": "Відображувана назва", + "provider.custom.models.remove": "Видалити модель", + "provider.custom.models.add": "Додати модель", + "provider.custom.headers.label": "Заголовки (необов'язково)", + "provider.custom.headers.key.label": "Заголовок", + "provider.custom.headers.key.placeholder": "Назва-Заголовка", + "provider.custom.headers.value.label": "Значення", + "provider.custom.headers.value.placeholder": "значення", + "provider.custom.headers.remove": "Видалити заголовок", + "provider.custom.headers.add": "Додати заголовок", + "provider.custom.error.providerID.required": "ID провайдера обов'язкове", + "provider.custom.error.providerID.format": "Використовуйте малі літери, цифри, дефіси або підкреслення", + "provider.custom.error.providerID.exists": "ID провайдера вже існує", + "provider.custom.error.name.required": "Відображувана назва обов'язкова", + "provider.custom.error.baseURL.required": "Базовий URL обов'язковий", + "provider.custom.error.baseURL.format": "Має починатися з http:// або https://", + "provider.custom.error.required": "Обов'язково", + "provider.custom.error.duplicate": "Дублікат", + + "provider.disconnect.toast.disconnected.title": "{{provider}} відключено", + "provider.disconnect.toast.disconnected.description": "Моделі {{provider}} більше недоступні.", + + "model.tag.free": "Безкоштовно", + "model.tag.latest": "Остання", + "model.provider.anthropic": "Anthropic", + "model.provider.openai": "OpenAI", + "model.provider.google": "Google", + "model.provider.xai": "xAI", + "model.provider.meta": "Meta", + "model.input.text": "текст", + "model.input.image": "зображення", + "model.input.audio": "аудіо", + "model.input.video": "відео", + "model.input.pdf": "pdf", + "model.tooltip.allows": "Дозволяє: {{inputs}}", + "model.tooltip.reasoning.allowed": "Підтримує мислення", + "model.tooltip.reasoning.none": "Без мислення", + "model.tooltip.context": "Ліміт контексту {{limit}}", + + "common.search.placeholder": "Пошук", + "common.goBack": "Назад", + "common.goForward": "Вперед", + "common.loading": "Завантаження", + "common.loading.ellipsis": "...", + "common.cancel": "Скасувати", + "common.open": "Відкрити", + "common.connect": "Підключити", + "common.disconnect": "Відключити", + "common.continue": "Продовжити", + "common.submit": "Надіслати", + "common.save": "Зберегти", + "common.saving": "Збереження...", + "common.default": "За замовчуванням", + "common.attachment": "вкладення", + + "prompt.placeholder.shell": "Введіть команду термінала... {{example}}", + "prompt.placeholder.normal": "Запитайте що завгодно... \"{{example}}\"", + "prompt.placeholder.simple": "Запитайте що завгодно...", + "prompt.placeholder.summarizeComments": "Підсумувати коментарі…", + "prompt.placeholder.summarizeComment": "Підсумувати коментар…", + "prompt.mode.shell": "Команда", + "prompt.mode.normal": "Запит", + "prompt.mode.shell.exit": "esc для виходу", + "session.child.promptDisabled": "Сесії підагентів не можна надсилати запити.", + "session.child.backToParent": "Назад до основної сесії.", + + "prompt.example.1": "Виправити TODO у коді", + "prompt.example.2": "Який технологічний стек цього проєкту?", + "prompt.example.3": "Виправити зламані тести", + "prompt.example.4": "Пояснити, як працює автентифікація", + "prompt.example.5": "Знайти та виправити вразливості безпеки", + "prompt.example.6": "Додати модульні тести для сервісу користувача", + "prompt.example.7": "Рефакторити цю функцію, щоб зробити її більш читабельною", + "prompt.example.8": "Що означає ця помилка?", + "prompt.example.9": "Допоможіть мені налагодити цю проблему", + "prompt.example.10": "Згенерувати документацію API", + "prompt.example.11": "Оптимізувати запити до бази даних", + "prompt.example.12": "Додати валідацію введення", + "prompt.example.13": "Створити новий компонент для...", + "prompt.example.14": "Як розгорнути цей проєкт?", + "prompt.example.15": "Перевірити мій код на відповідність найкращим практикам", + "prompt.example.16": "Додати обробку помилок до цієї функції", + "prompt.example.17": "Пояснити цей регулярний вираз", + "prompt.example.18": "Конвертувати це в TypeScript", + "prompt.example.19": "Додати логування по всьому коду", + "prompt.example.20": "Які залежності застарілі?", + "prompt.example.21": "Допоможіть написати скрипт міграції", + "prompt.example.22": "Реалізувати кешування для цього ендпоінта", + "prompt.example.23": "Додати посторінкову навігацію до цього списку", + "prompt.example.24": "Створити команду CLI для...", + "prompt.example.25": "Як тут працюють змінні середовища?", + + "prompt.popover.emptyResults": "Немає відповідних результатів", + "prompt.popover.emptyCommands": "Немає відповідних команд", + "prompt.dropzone.label": "Перетягніть сюди зображення, PDF або текстові файли", + "prompt.dropzone.file.label": "Перетягніть, щоб @згадати файл", + "prompt.slash.badge.custom": "користувацький", + "prompt.slash.badge.skill": "навичка", + "prompt.slash.badge.mcp": "mcp", + "prompt.context.active": "активний", + "prompt.context.includeActiveFile": "Включити активний файл", + "prompt.context.removeActiveFile": "Видалити активний файл з контексту", + "prompt.context.removeFile": "Видалити файл з контексту", + "prompt.action.attachFile": "Додати файли", + "prompt.attachment.remove": "Видалити вкладення", + "prompt.action.send": "Надіслати", + "prompt.action.stop": "Зупинити", + + "prompt.toast.pasteUnsupported.title": "Непідтримуване вкладення", + "prompt.toast.pasteUnsupported.description": "Сюди можна прикріплювати лише зображення, PDF або текстові файли.", + "prompt.toast.modelAgentRequired.title": "Виберіть агента та модель", + "prompt.toast.modelAgentRequired.description": "Виберіть агента та модель перед надсиланням запиту.", + "prompt.toast.worktreeCreateFailed.title": "Не вдалося створити робоче дерево", + "prompt.toast.sessionCreateFailed.title": "Не вдалося створити сесію", + "prompt.toast.shellSendFailed.title": "Не вдалося надіслати команду термінала", + "prompt.toast.commandSendFailed.title": "Не вдалося надіслати команду", + "prompt.toast.promptSendFailed.title": "Не вдалося надіслати запит", + "prompt.toast.promptSendFailed.description": "Не вдалося отримати сесію", + + "dialog.mcp.title": "MCP", + "dialog.mcp.description": "{{enabled}} з {{total}} увімкнено", + "dialog.mcp.empty": "MCP не налаштовано", + + "dialog.lsp.empty": "LSP автоматично виявлені за типами файлів", + "dialog.plugins.empty": "Плагіни налаштовані в opencode.json", + + "mcp.status.connected": "підключено", + "mcp.status.failed": "помилка", + "mcp.status.needs_auth": "потрібна авторизація", + "mcp.status.disabled": "вимкнено", + "mcp.auth.clickToAuthenticate": "Натисніть для автентифікації", + + "dialog.fork.empty": "Немає повідомлень для відгалуження", + + "dialog.directory.search.placeholder": "Пошук папок", + "dialog.directory.empty": "Папок не знайдено", + + "app.server.unreachable": "Не вдалося досягти {{server}}", + "app.server.retrying": "Автоматичне повторення...", + "app.server.otherServers": "Інші сервери", + + "dialog.server.title": "Сервери", + "dialog.server.description": "Перемкніть сервер OpenCode, до якого підключається ця програма.", + "dialog.server.search.placeholder": "Пошук серверів", + "dialog.server.empty": "Ще немає серверів", + "dialog.server.add.title": "Додати сервер", + "dialog.server.add.url": "Адреса сервера", + "dialog.server.add.placeholder": "http://localhost:4096", + "dialog.server.add.error": "Не вдалося підключитися до сервера", + "dialog.server.add.checking": "Перевірка...", + "dialog.server.add.button": "Додати сервер", + "dialog.server.add.name": "Назва сервера (необов'язково)", + "dialog.server.add.namePlaceholder": "Localhost", + "dialog.server.add.username": "Ім'я користувача (необов'язково)", + "dialog.server.add.usernamePlaceholder": "ім'я користувача", + "dialog.server.add.password": "Пароль (необов'язково)", + "dialog.server.add.passwordPlaceholder": "пароль", + "dialog.server.edit.title": "Редагувати сервер", + "dialog.server.default.title": "Сервер за замовчуванням", + "dialog.server.default.description": + "Підключатися до цього сервера під час запуску програми замість запуску локального сервера. Потребує перезапуску.", + "dialog.server.default.none": "Сервер не вибрано", + "dialog.server.default.set": "Встановити поточний сервер як сервер за замовчуванням", + "dialog.server.default.clear": "Очистити", + "dialog.server.action.remove": "Видалити сервер", + + "dialog.server.menu.edit": "Редагувати", + "dialog.server.menu.default": "Встановити за замовчуванням", + "dialog.server.menu.defaultRemove": "Видалити за замовчуванням", + "dialog.server.menu.delete": "Видалити", + "dialog.server.current": "Поточний сервер", + "dialog.server.status.default": "За замовчуванням", + "server.row.noUsername": "без імені користувача", + + "dialog.project.edit.title": "Редагувати проєкт", + "dialog.project.edit.name": "Назва", + "dialog.project.edit.icon": "Іконка", + "dialog.project.edit.icon.alt": "Іконка проєкту", + "dialog.project.edit.icon.hint": "Натисніть або перетягніть зображення", + "dialog.project.edit.icon.recommended": "Рекомендовано: 128x128px", + "dialog.project.edit.color": "Колір", + "dialog.project.edit.color.select": "Вибрати колір {{color}}", + "dialog.project.edit.worktree.startup": "Скрипт запуску робочої області", + "dialog.project.edit.worktree.startup.description": "Виконується після створення нової робочої області (worktree).", + "dialog.project.edit.worktree.startup.placeholder": "напр. bun install", + + "dialog.releaseNotes.action.getStarted": "Розпочати", + "dialog.releaseNotes.action.next": "Далі", + "dialog.releaseNotes.action.hideFuture": "Не показувати це в майбутньому", + "dialog.releaseNotes.media.alt": "Попередній перегляд релізу", + + "context.breakdown.title": "Розподіл контексту", + "context.breakdown.note": "Приблизний розподіл вхідних токенів. \"Інше\" включає визначення інструментів і накладні витрати.", + "context.breakdown.system": "Система", + "context.breakdown.user": "Користувач", + "context.breakdown.assistant": "Асистент", + "context.breakdown.tool": "Виклики інструментів", + "context.breakdown.other": "Інше", + + "context.systemPrompt.title": "Системний запит", + "context.rawMessages.title": "Сировинні повідомлення", + + "context.stats.session": "Сесія", + "context.stats.messages": "Повідомлення", + "context.stats.provider": "Провайдер", + "context.stats.model": "Модель", + "context.stats.limit": "Ліміт контексту", + "context.stats.totalTokens": "Всього токенів", + "context.stats.usage": "Використання", + "context.stats.inputTokens": "Вхідні токени", + "context.stats.outputTokens": "Вихідні токени", + "context.stats.reasoningTokens": "Токени мислення", + "context.stats.cacheTokens": "Токени кешу (читання/запис)", + "context.stats.userMessages": "Повідомлення користувача", + "context.stats.assistantMessages": "Повідомлення асистента", + "context.stats.totalCost": "Загальна вартість", + "context.stats.sessionCreated": "Сесію створено", + "context.stats.lastActivity": "Остання активність", + + "context.usage.tokens": "Токени", + "context.usage.usage": "Використання", + "context.usage.cost": "Вартість", + "context.usage.clickToView": "Натисніть, щоб переглянути контекст", + "context.usage.view": "Переглянути використання контексту", + + "language.en": "English", + "language.zh": "简体中文", + "language.zht": "繁體中文", + "language.ko": "한국어", + "language.de": "Deutsch", + "language.es": "Español", + "language.fr": "Français", + "language.da": "Dansk", + "language.ja": "日本語", + "language.pl": "Polski", + "language.ru": "Русский", + "language.ar": "العربية", + "language.no": "Norsk", + "language.br": "Português (Brasil)", + "language.bs": "Bosanski", + "language.uk": "Українська", + "language.th": "ไทย", + "language.tr": "Türkçe", + + "toast.language.title": "Мова", + "toast.language.description": "Перемкнено на {{language}}", + + "toast.theme.title": "Тему змінено", + "toast.scheme.title": "Кольорова схема", + + "toast.workspace.enabled.title": "Робочі області увімкнено", + "toast.workspace.enabled.description": "Кілька робочих дерев тепер відображаються на бічній панелі", + "toast.workspace.disabled.title": "Робочі області вимкнено", + "toast.workspace.disabled.description": "Тільки головне робоче дерево відображається на бічній панелі", + + "toast.permissions.autoaccept.on.title": "Автоматичне прийняття дозволів", + "toast.permissions.autoaccept.on.description": "Запити дозволів будуть автоматично схвалюватися", + "toast.permissions.autoaccept.off.title": "Автоматичне прийняття дозволів зупинено", + "toast.permissions.autoaccept.off.description": "Запити дозволів вимагатимуть схвалення", + + "toast.model.none.title": "Модель не вибрано", + "toast.model.none.description": "Підключіть провайдера, щоб підсумувати цю сесію", + + "toast.file.loadFailed.title": "Не вдалося завантажити файл", + "toast.file.listFailed.title": "Не вдалося отримати список файлів", + + "toast.context.noLineSelection.title": "Не вибрано рядків", + "toast.context.noLineSelection.description": "Спочатку виберіть діапазон рядків у вкладці файлу.", + + "toast.session.share.copyFailed.title": "Не вдалося скопіювати URL у буфер обміну", + "toast.session.share.success.title": "Сесію опубліковано", + "toast.session.share.success.description": "Посилання скопійовано в буфер обміну!", + "toast.session.share.failed.title": "Не вдалося опублікувати сесію", + "toast.session.share.failed.description": "Під час публікації сесії сталася помилка", + + "toast.session.unshare.success.title": "Поширення сесії припинено", + "toast.session.unshare.success.description": "Поширення сесії успішно припинено!", + "toast.session.unshare.failed.title": "Не вдалося припинити поширення сесії", + "toast.session.unshare.failed.description": "Під час припинення поширення сесії сталася помилка", + + "toast.session.listFailed.title": "Не вдалося завантажити сесії для {{project}}", + "toast.project.reloadFailed.title": "Не вдалося перезавантажити {{project}}", + + "toast.update.title": "Доступне оновлення", + "toast.update.description": "Нова версія OpenCode ({{version}}) тепер доступна для встановлення.", + "toast.update.action.installRestart": "Встановити та перезапустити", + "toast.update.action.notYet": "Не зараз", + + "error.page.title": "Щось пішло не так", + "error.page.description": "Під час завантаження програми сталася помилка.", + "error.page.details.label": "Деталі помилки", + "error.page.action.restart": "Перезапустити", + "error.page.action.report": "Повідомити про помилку", + "error.page.action.reported": "Помилку повідомлено", + "error.page.action.checking": "Перевірка...", + "error.page.action.checkUpdates": "Перевірити оновлення", + "error.page.action.updateTo": "Оновити до {{version}}", + "error.page.circular": "[Циклічне]", + "error.page.report.prefix": "Будь ласка, повідомте про цю помилку команді OpenCode", + "error.page.report.discord": "на Discord", + "error.page.version": "Версія: {{version}}", + + "error.dev.rootNotFound": + "Кореневий елемент не знайдено. Ви забули додати його до index.html? Або, можливо, атрибут id було написано з помилкою?", + + "error.globalSync.connectFailed": "Не вдалося підключитися до сервера. Чи працює сервер за адресою `{{url}}`?", + "error.globalSDK.noServerAvailable": "Сервер недоступний", + "error.globalSDK.serverNotAvailable": "Сервер недоступний", + "error.childStore.persistedCacheCreateFailed": "Не вдалося створити постійний кеш", + "error.childStore.persistedProjectMetadataCreateFailed": "Не вдалося створити постійні метадані проєкту", + "error.childStore.persistedProjectIconCreateFailed": "Не вдалося створити постійну іконку проєкту", + "error.childStore.storeCreateFailed": "Не вдалося створити сховище", + "directory.error.invalidUrl": "Недійсний каталог у URL.", + + "error.chain.unknown": "Невідома помилка", + "error.server.invalidConfiguration": "Недійсна конфігурація", + "error.chain.causedBy": "Причина:", + "error.chain.apiError": "Помилка API", + "error.chain.status": "Статус: {{status}}", + "error.chain.retryable": "Повторювано: {{retryable}}", + "error.chain.responseBody": "Тіло відповіді:\n{{body}}", + "error.chain.didYouMean": "Можливо, ви мали на увазі: {{suggestions}}", + "error.chain.modelNotFound": "Модель не знайдено: {{provider}}/{{model}}", + "error.chain.checkConfig": "Перевірте назви провайдерів/моделей у конфігурації (opencode.json)", + "error.chain.mcpFailed": "Сервер MCP \"{{name}}\" не працює. Зверніть увагу, OpenCode ще не підтримує автентифікацію MCP.", + "error.chain.providerAuthFailed": "Автентифікація провайдера не вдалася ({{provider}}): {{message}}", + "error.chain.providerInitFailed": + "Не вдалося ініціалізувати провайдера \"{{provider}}\". Перевірте облікові дані та конфігурацію.", + "error.chain.configJsonInvalid": "Файл конфігурації {{path}} не є дійсним JSON(C)", + "error.chain.configJsonInvalidWithMessage": "Файл конфігурації {{path}} не є дійсним JSON(C): {{message}}", + "error.chain.configDirectoryTypo": + "Каталог \"{{dir}}\" у {{path}} недійсний. Перейменуйте каталог на \"{{suggestion}}\" або видаліть його. Це поширена помилка.", + "error.chain.configFrontmatterError": "Не вдалося розібрати frontmatter у {{path}}:\n{{message}}", + "error.chain.configInvalid": "Файл конфігурації {{path}} недійсний", + "error.chain.configInvalidWithMessage": "Файл конфігурації {{path}} недійсний: {{message}}", + + "notification.permission.title": "Потрібен дозвіл", + "notification.permission.description": "{{sessionTitle}} у {{projectName}} потребує дозволу", + "notification.question.title": "Запитання", + "notification.question.description": "{{sessionTitle}} у {{projectName}} має запитання", + "notification.action.goToSession": "Перейти до сесії", + + "notification.session.responseReady.title": "Відповідь готова", + "notification.session.error.title": "Помилка сесії", + "notification.session.error.fallbackDescription": "Сталася помилка", + + "home.recentProjects": "Нещодавні проєкти", + "home.empty.title": "Немає нещодавніх проєктів", + "home.empty.description": "Почніть, відкривши локальний проєкт", + + "session.tab.session": "Сесія", + "session.tab.review": "Огляд", + "session.tab.context": "Контекст", + "session.panel.reviewAndFiles": "Огляд і файли", + "session.review.filesChanged": "Змінено файлів: {{count}}", + "session.review.change.one": "Зміна", + "session.review.change.other": "Зміни", + "session.review.loadingChanges": "Завантаження змін...", + "session.review.empty": "У цій сесії ще немає змін", + "session.review.noVcs": "Систему контролю версій Git не виявлено, зміни не відображаються", + "session.review.noVcs.createGit.title": "Створити Git-репозиторій", + "session.review.noVcs.createGit.description": "Відстежуйте, переглядайте та скасовуйте зміни в цьому проєкті", + "session.review.noVcs.createGit.actionLoading": "Створення Git-репозиторію...", + "session.review.noVcs.createGit.action": "Створити Git-репозиторій", + "session.review.noSnapshot": "Відстеження знімків вимкнено в конфігурації, тому зміни сесії недоступні", + "session.review.noChanges": "Немає змін", + "session.review.noUncommittedChanges": "Ще немає незафіксованих змін", + "session.review.noBranchChanges": "Ще немає змін у гілці", + + "session.files.selectToOpen": "Виберіть файл для відкриття", + "session.files.all": "Усі файли", + "session.files.empty": "Немає файлів", + "session.files.binaryContent": "Бінарний файл (вміст не може бути відображено)", + + "session.messages.renderEarlier": "Відобразити раніші повідомлення", + "session.messages.loadingEarlier": "Завантаження раніших повідомлень...", + "session.messages.loadEarlier": "Завантажити раніші повідомлення", + "session.messages.loading": "Завантаження повідомлень...", + "session.messages.jumpToLatest": "Перейти до останніх", + + "session.context.addToContext": "Додати {{selection}} до контексту", + "session.todo.title": "Завдання", + "session.todo.collapse": "Згорнути", + "session.todo.expand": "Розгорнути", + "session.todo.progress": "Виконано {{done}} з {{total}} завдань", + "session.question.progress": "{{current}} з {{total}} запитань", + "session.followupDock.summary.one": "{{count}} повідомлення в черзі", + "session.followupDock.summary.other": "{{count}} повідомлень у черзі", + "session.followupDock.sendNow": "Надіслати зараз", + "session.followupDock.edit": "Редагувати", + "session.followupDock.collapse": "Згорнути повідомлення в черзі", + "session.followupDock.expand": "Розгорнути повідомлення в черзі", + "session.revertDock.summary.one": "{{count}} скасоване повідомлення", + "session.revertDock.summary.other": "{{count}} скасованих повідомлень", + "session.revertDock.collapse": "Згорнути скасовані повідомлення", + "session.revertDock.expand": "Розгорнути скасовані повідомлення", + "session.revertDock.restore": "Відновити повідомлення", + + "session.new.title": "Створити що завгодно", + "session.new.worktree.main": "Основна гілка", + "session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})", + "session.new.worktree.create": "Створити нове робоче дерево", + "session.new.lastModified": "Востаннє змінено", + + "session.header.search.placeholder": "Пошук {{project}}", + "session.header.searchFiles": "Пошук файлів", + "session.header.openIn": "Відкрити в", + "session.header.open.action": "Відкрити {{app}}", + "session.header.open.ariaLabel": "Відкрити в {{app}}", + "session.header.open.menu": "Параметри відкриття", + "session.header.open.copyPath": "Копіювати шлях", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Провідник файлів", + "session.header.open.fileManager": "Файловий менеджер", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Термінал", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + + "status.popover.trigger": "Статус", + "status.popover.ariaLabel": "Конфігурації серверів", + "status.popover.tab.servers": "Сервери", + "status.popover.tab.mcp": "MCP", + "status.popover.tab.lsp": "LSP", + "status.popover.tab.plugins": "Плагіни", + "status.popover.action.manageServers": "Керувати серверами", + + "session.share.popover.title": "Опублікувати в інтернеті", + "session.share.popover.description.shared": + "Ця сесія є публічною в інтернеті. Вона доступна будь-кому за посиланням.", + "session.share.popover.description.unshared": + "Опублікуйте сесію публічно в інтернеті. Вона буде доступна будь-кому за посиланням.", + "session.share.action.share": "Поділитися", + "session.share.action.publish": "Опублікувати", + "session.share.action.publishing": "Публікація...", + "session.share.action.unpublish": "Скасувати публікацію", + "session.share.action.unpublishing": "Скасування публікації...", + "session.share.action.view": "Переглянути", + "session.share.copy.copied": "Скопійовано", + "session.share.copy.copyLink": "Копіювати посилання", + + "lsp.tooltip.none": "Немає серверів LSP", + "lsp.label.connected": "{{count}} LSP", + + "prompt.loading": "Завантаження запиту...", + "terminal.loading": "Завантаження термінала...", + "terminal.title": "Термінал", + "terminal.title.numbered": "Термінал {{number}}", + "terminal.close": "Закрити термінал", + "terminal.connectionLost.title": "З'єднання втрачено", + "terminal.connectionLost.abnormalClose": "WebSocket закрито аномально: {{code}}", + "terminal.connectionLost.description": + "З'єднання з терміналом було перервано. Це може статися під час перезапуску сервера.", + + "common.closeTab": "Закрити вкладку", + "common.dismiss": "Відхилити", + "common.moreCountSuffix": " (ще {{count}})", + "common.requestFailed": "Запит не виконано", + "common.moreOptions": "Більше опцій", + "common.learnMore": "Дізнатися більше", + "common.rename": "Перейменувати", + "common.reset": "Скинути", + "common.archive": "Архівувати", + "common.delete": "Видалити", + "common.close": "Закрити", + "common.edit": "Редагувати", + "common.loadMore": "Завантажити більше", + "common.key.esc": "ESC", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Пробіл", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "невідомо", + + "common.time.justNow": "Щойно", + "common.time.minutesAgo.short": "{{count}} хв тому", + "common.time.hoursAgo.short": "{{count}} год тому", + "common.time.daysAgo.short": "{{count}} дн тому", + + "sidebar.menu.toggle": "Перемкнути меню", + "sidebar.nav.projectsAndSessions": "Проєкти та сесії", + "sidebar.settings": "Налаштування", + "sidebar.help": "Довідка", + "sidebar.workspaces.enable": "Увімкнути робочі області", + "sidebar.workspaces.disable": "Вимкнути робочі області", + "sidebar.gettingStarted.title": "Початок роботи", + "sidebar.gettingStarted.line1": "OpenCode містить безкоштовні моделі, тому ви можете почати негайно.", + "sidebar.gettingStarted.line2": "Підключіть будь-якого провайдера, щоб використовувати моделі, включаючи Claude, GPT, Gemini тощо.", + "sidebar.project.recentSessions": "Нещодавні сесії", + "sidebar.project.viewAllSessions": "Переглянути всі сесії", + "sidebar.project.clearNotifications": "Очистити сповіщення", + "sidebar.empty.title": "Немає відкритих проєктів", + "sidebar.empty.description": "Відкрийте проєкт, щоб почати", + + "debugBar.ariaLabel": "Діагностика продуктивності розробки", + "debugBar.na": "н/д", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Останній завершений перехід маршруту, що торкається сторінки сесії, виміряний від запуску маршрутизатора до першого відображення після стабілізації.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Поточна кількість кадрів за секунду за останні 5 секунд.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Найгірший час кадру за останні 5 секунд.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Кадри понад 32 мс за останні 5 секунд.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Заблокований час і кількість довгих завдань за останні 5 секунд. Макс. завдання: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Найгірша спостережувана затримка введення за останні 5 секунд.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Приблизна тривалість взаємодії за останні 5 секунд. Це схоже на INP, а не на офіційний Web Vitals INP.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Сукупний зсув макета за весь час роботи програми.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Використана купа JS проти ліміту купи. Тільки Chromium.", + "debugBar.mem.tip": "Використана купа JS проти ліміту купи. {{used}} з {{limit}}.", + + "app.name.desktop": "OpenCode Desktop", + + "settings.section.desktop": "Робочий стіл", + "settings.section.server": "Сервер", + "settings.tab.general": "Загальні", + "settings.tab.shortcuts": "Скорочення", + "settings.desktop.section.wsl": "WSL", + "settings.desktop.wsl.title": "Інтеграція WSL", + "settings.desktop.wsl.description": "Запускати сервер OpenCode всередині WSL на Windows.", + + "settings.general.section.appearance": "Зовнішній вигляд", + "settings.general.section.advanced": "Додатково", + "settings.general.section.notifications": "Системні сповіщення", + "settings.general.section.updates": "Оновлення", + "settings.general.section.sounds": "Звукові ефекти", + "settings.general.section.feed": "Стрічка", + "settings.general.section.display": "Дисплей", + + "settings.general.row.language.title": "Мова", + "settings.general.row.language.description": "Змінити мову інтерфейсу OpenCode", + "settings.general.row.shell.title": "Командна оболонка термінала", + "settings.general.row.shell.description": + "Виберіть оболонку для термінала. Сумісні оболонки також використовуються для викликів інструментів агента.", + "settings.general.row.shell.autoDefault": "Авто (за замовчуванням)", + "settings.general.row.shell.terminalOnly": "тільки термінал", + "settings.general.row.appearance.title": "Зовнішній вигляд", + "settings.general.row.appearance.description": "Налаштуйте вигляд OpenCode на вашому пристрої", + "settings.general.row.colorScheme.title": "Кольорова схема", + "settings.general.row.colorScheme.description": "Виберіть, чи OpenCode використовує системну, світлу або темну тему", + "settings.general.row.theme.title": "Тема", + "settings.general.row.theme.description": "Налаштуйте тему OpenCode.", + "settings.general.row.font.title": "Шрифт коду", + "settings.general.row.font.description": "Налаштуйте шрифт, який використовується в блоках коду", + "settings.general.row.terminalFont.title": "Шрифт термінала", + "settings.general.row.terminalFont.description": "Налаштуйте шрифт, який використовується в терміналі", + "settings.general.row.uiFont.title": "Шрифт інтерфейсу", + "settings.general.row.uiFont.description": "Налаштуйте шрифт, який використовується в інтерфейсі", + "settings.general.row.followup.title": "Поведінка продовження", + "settings.general.row.followup.description": "Виберіть, чи продовження виконується негайно, чи чекає в черзі", + "settings.general.row.followup.option.queue": "Черга", + "settings.general.row.followup.option.steer": "Керування", + "settings.general.row.showFileTree.title": "Дерево файлів", + "settings.general.row.showFileTree.description": "Показувати перемикач і панель дерева файлів у сесіях на робочому столі", + "settings.general.row.showNavigation.title": "Елементи навігації", + "settings.general.row.showNavigation.description": "Показувати кнопки назад і вперед у заголовку робочого столу", + "settings.general.row.showSearch.title": "Палітра команд", + "settings.general.row.showSearch.description": "Показувати кнопку пошуку та палітри команд у заголовку робочого столу", + "settings.general.row.showTerminal.title": "Термінал", + "settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу", + "settings.general.row.showStatus.title": "Статус сервера", + "settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку робочого столу", + "settings.general.row.reasoningSummaries.title": "Показувати підсумки мислення", + "settings.general.row.reasoningSummaries.description": "Відображати підсумки мислення моделі на часовій шкалі", + "settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки", + "settings.general.row.shellToolPartsExpanded.description": + "Показувати частини інструменту оболонки розгорнутими за замовчуванням на часовій шкалі", + "settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування", + "settings.general.row.editToolPartsExpanded.description": + "Показувати частини інструментів редагування, запису та патчів розгорнутими за замовчуванням на часовій шкалі", + "settings.general.row.showSessionProgressBar.title": "Показувати індикатор прогресу сесії", + "settings.general.row.showSessionProgressBar.description": + "Відображати анімований індикатор прогресу вгорі сесії, коли агент працює", + + "settings.general.row.wayland.title": "Використовувати нативний Wayland", + "settings.general.row.wayland.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.", + "settings.general.row.wayland.tooltip": + "На Linux з моніторами з різною частотою оновлення нативний Wayland може бути більш стабільним.", + + "settings.general.row.releaseNotes.title": "Нотатки до релізу", + "settings.general.row.releaseNotes.description": "Показувати спливаючі вікна \"Що нового\" після оновлень", + + "settings.updates.row.startup.title": "Перевіряти оновлення під час запуску", + "settings.updates.row.startup.description": "Автоматично перевіряти наявність оновлень під час запуску OpenCode", + "settings.updates.row.check.title": "Перевірити оновлення", + "settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні", + "settings.updates.action.checkNow": "Перевірити зараз", + "settings.updates.action.checking": "Перевірка...", + "settings.updates.toast.latest.title": "У вас актуальна версія", + "settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.", + "sound.option.none": "Немає", + "sound.option.alert01": "Alert 01", + "sound.option.alert02": "Alert 02", + "sound.option.alert03": "Alert 03", + "sound.option.alert04": "Alert 04", + "sound.option.alert05": "Alert 05", + "sound.option.alert06": "Alert 06", + "sound.option.alert07": "Alert 07", + "sound.option.alert08": "Alert 08", + "sound.option.alert09": "Alert 09", + "sound.option.alert10": "Alert 10", + "sound.option.bipbop01": "Bip-bop 01", + "sound.option.bipbop02": "Bip-bop 02", + "sound.option.bipbop03": "Bip-bop 03", + "sound.option.bipbop04": "Bip-bop 04", + "sound.option.bipbop05": "Bip-bop 05", + "sound.option.bipbop06": "Bip-bop 06", + "sound.option.bipbop07": "Bip-bop 07", + "sound.option.bipbop08": "Bip-bop 08", + "sound.option.bipbop09": "Bip-bop 09", + "sound.option.bipbop10": "Bip-bop 10", + "sound.option.staplebops01": "Staplebops 01", + "sound.option.staplebops02": "Staplebops 02", + "sound.option.staplebops03": "Staplebops 03", + "sound.option.staplebops04": "Staplebops 04", + "sound.option.staplebops05": "Staplebops 05", + "sound.option.staplebops06": "Staplebops 06", + "sound.option.staplebops07": "Staplebops 07", + "sound.option.nope01": "Nope 01", + "sound.option.nope02": "Nope 02", + "sound.option.nope03": "Nope 03", + "sound.option.nope04": "Nope 04", + "sound.option.nope05": "Nope 05", + "sound.option.nope06": "Nope 06", + "sound.option.nope07": "Nope 07", + "sound.option.nope08": "Nope 08", + "sound.option.nope09": "Nope 09", + "sound.option.nope10": "Nope 10", + "sound.option.nope11": "Nope 11", + "sound.option.nope12": "Nope 12", + "sound.option.yup01": "Yup 01", + "sound.option.yup02": "Yup 02", + "sound.option.yup03": "Yup 03", + "sound.option.yup04": "Yup 04", + "sound.option.yup05": "Yup 05", + "sound.option.yup06": "Yup 06", + + "settings.general.notifications.agent.title": "Агент", + "settings.general.notifications.agent.description": + "Показувати системне сповіщення, коли агент завершує роботу або потребує уваги", + "settings.general.notifications.permissions.title": "Дозволи", + "settings.general.notifications.permissions.description": "Показувати системне сповіщення, коли потрібен дозвіл", + "settings.general.notifications.errors.title": "Помилки", + "settings.general.notifications.errors.description": "Показувати системне сповіщення, коли виникає помилка", + + "settings.general.sounds.agent.title": "Агент", + "settings.general.sounds.agent.description": "Відтворювати звук, коли агент завершує роботу або потребує уваги", + "settings.general.sounds.permissions.title": "Дозволи", + "settings.general.sounds.permissions.description": "Відтворювати звук, коли потрібен дозвіл", + "settings.general.sounds.errors.title": "Помилки", + "settings.general.sounds.errors.description": "Відтворювати звук, коли виникає помилка", + + "settings.shortcuts.title": "Скорочення клавіш", + "settings.shortcuts.reset.button": "Скинути до стандартних", + "settings.shortcuts.reset.toast.title": "Скорочення скинуто", + "settings.shortcuts.reset.toast.description": "Скорочення клавіш були скинуті до стандартних.", + "settings.shortcuts.conflict.title": "Скорочення вже використовується", + "settings.shortcuts.conflict.description": "{{keybind}} вже призначено для {{titles}}.", + "settings.shortcuts.unassigned": "Не призначено", + "settings.shortcuts.pressKeys": "Натисніть клавіші", + "settings.shortcuts.search.placeholder": "Пошук скорочень", + "settings.shortcuts.search.empty": "Скорочень не знайдено", + + "settings.shortcuts.group.general": "Загальні", + "settings.shortcuts.group.session": "Сесія", + "settings.shortcuts.group.navigation": "Навігація", + "settings.shortcuts.group.modelAndAgent": "Модель та агент", + "settings.shortcuts.group.terminal": "Термінал", + "settings.shortcuts.group.prompt": "Запит", + + "settings.providers.title": "Провайдери", + "settings.providers.description": "Налаштування провайдерів будуть доступні тут.", + "settings.providers.section.connected": "Підключені провайдери", + "settings.providers.connected.empty": "Немає підключених провайдерів", + "settings.providers.connected.environmentDescription": "Підключено зі змінних середовища", + "settings.providers.section.popular": "Популярні провайдери", + "settings.providers.custom.description": "Додайте провайдера, сумісного з OpenAI, за базовим URL.", + "settings.providers.tag.environment": "Середовище", + "settings.providers.tag.config": "Конфігурація", + "settings.providers.tag.custom": "Користувацький", + "settings.providers.tag.other": "Інше", + "settings.models.title": "Моделі", + "settings.models.description": "Налаштування моделей будуть доступні тут.", + "settings.agents.title": "Агенти", + "settings.agents.description": "Налаштування агентів будуть доступні тут.", + "settings.commands.title": "Команди", + "settings.commands.description": "Налаштування команд будуть доступні тут.", + "settings.mcp.title": "MCP", + "settings.mcp.description": "Налаштування MCP будуть доступні тут.", + + "settings.permissions.title": "Дозволи", + "settings.permissions.description": "Керуйте тим, які інструменти сервер може використовувати за замовчуванням.", + "settings.permissions.section.tools": "Інструменти", + "settings.permissions.toast.updateFailed.title": "Не вдалося оновити дозволи", + + "settings.permissions.action.allow": "Дозволити", + "settings.permissions.action.ask": "Запитувати", + "settings.permissions.action.deny": "Заборонити", + + "settings.permissions.tool.read.title": "Читання", + "settings.permissions.tool.read.description": "Читання файлу (відповідає шляху файлу)", + "settings.permissions.tool.edit.title": "Редагування", + "settings.permissions.tool.edit.description": "Зміна файлів, включаючи редагування, запис і патчі", + "settings.permissions.tool.glob.title": "Glob", + "settings.permissions.tool.glob.description": "Зіставлення файлів за допомогою glob-шаблонів", + "settings.permissions.tool.grep.title": "Grep", + "settings.permissions.tool.grep.description": "Пошук вмісту файлів за допомогою регулярних виразів", + "settings.permissions.tool.list.title": "Список", + "settings.permissions.tool.list.description": "Список файлів у каталозі", + "settings.permissions.tool.bash.title": "Bash", + "settings.permissions.tool.bash.description": "Запуск команд оболонки", + "settings.permissions.tool.task.title": "Завдання", + "settings.permissions.tool.task.description": "Запуск підагентів", + "settings.permissions.tool.skill.title": "Навичка", + "settings.permissions.tool.skill.description": "Завантаження навички за назвою", + "settings.permissions.tool.lsp.title": "LSP", + "settings.permissions.tool.lsp.description": "Виконання запитів мовного сервера", + "settings.permissions.tool.todowrite.title": "Todo Write", + "settings.permissions.tool.todowrite.description": "Оновлення списку завдань", + "settings.permissions.tool.webfetch.title": "Web Fetch", + "settings.permissions.tool.webfetch.description": "Отримання вмісту з URL", + "settings.permissions.tool.websearch.title": "Web Search", + "settings.permissions.tool.websearch.description": "Пошук в інтернеті", + "settings.permissions.tool.external_directory.title": "Зовнішній каталог", + "settings.permissions.tool.external_directory.description": "Доступ до файлів за межами каталогу проєкту", + "settings.permissions.tool.doom_loop.title": "Цикл приреченості", + "settings.permissions.tool.doom_loop.description": "Виявлення повторюваних викликів інструментів з однаковими вхідними даними", + + "session.delete.failed.title": "Не вдалося видалити сесію", + "session.delete.title": "Видалити сесію", + "session.delete.confirm": 'Видалити сесію "{{name}}"?', + "session.delete.button": "Видалити сесію", + + "workspace.new": "Нова робоча область", + "workspace.type.local": "локальна", + "workspace.type.sandbox": "пісочниця", + "workspace.create.failed.title": "Не вдалося створити робочу область", + "workspace.delete.failed.title": "Не вдалося видалити робочу область", + "workspace.resetting.title": "Скидання робочої області", + "workspace.resetting.description": "Це може зайняти хвилину.", + "workspace.reset.failed.title": "Не вдалося скинути робочу область", + "workspace.reset.success.title": "Робочу область скинуто", + "workspace.reset.success.description": "Робоча область тепер відповідає гілці за замовчуванням.", + "workspace.error.stillPreparing": "Робоча область все ще готується", + "workspace.status.checking": "Перевірка незлитих змін...", + "workspace.status.error": "Не вдалося перевірити статус git.", + "workspace.status.clean": "Незлитих змін не виявлено.", + "workspace.status.dirty": "Виявлено незлиті зміни в цій робочій області.", + "workspace.delete.title": "Видалити робочу область", + "workspace.delete.confirm": 'Видалити робочу область "{{name}}"?', + "workspace.delete.button": "Видалити робочу область", + "workspace.reset.title": "Скинути робочу область", + "workspace.reset.confirm": 'Скинути робочу область "{{name}}"?', + "workspace.reset.button": "Скинути робочу область", + "workspace.reset.archived.none": "Жодна активна сесія не буде заархівована.", + "workspace.reset.archived.one": "1 сесію буде заархівовано.", + "workspace.reset.archived.many": "{{count}} сесій буде заархівовано.", + "workspace.reset.note": "Це скине робочу область, щоб вона відповідала гілці за замовчуванням.", +} diff --git a/packages/console/app/src/i18n/index.ts b/packages/console/app/src/i18n/index.ts index a49fbe375880..a855ccfa3efe 100644 --- a/packages/console/app/src/i18n/index.ts +++ b/packages/console/app/src/i18n/index.ts @@ -11,6 +11,7 @@ import { dict as da } from "~/i18n/da" import { dict as ja } from "~/i18n/ja" import { dict as pl } from "~/i18n/pl" import { dict as ru } from "~/i18n/ru" +import { dict as uk } from "~/i18n/uk" import { dict as ar } from "~/i18n/ar" import { dict as no } from "~/i18n/no" import { dict as br } from "~/i18n/br" @@ -35,6 +36,7 @@ export function i18n(locale: Locale): Dict { if (locale === "ja") return { ...base, ...ja } if (locale === "pl") return { ...base, ...pl } if (locale === "ru") return { ...base, ...ru } + if (locale === "uk") return { ...base, ...uk } if (locale === "ar") return { ...base, ...ar } if (locale === "no") return { ...base, ...no } if (locale === "br") return { ...base, ...br } diff --git a/packages/console/app/src/i18n/uk.ts b/packages/console/app/src/i18n/uk.ts new file mode 100644 index 000000000000..442acb9a6b7b --- /dev/null +++ b/packages/console/app/src/i18n/uk.ts @@ -0,0 +1,785 @@ +import { dict as en } from "./en" + +export const dict = { + ...en, + "nav.github": "GitHub", + "nav.docs": "Документація", + "nav.changelog": "Журнал змін", + "nav.discord": "Discord", + "nav.x": "X", + "nav.enterprise": "Enterprise", + "nav.zen": "Zen", + "nav.login": "Увійти", + "nav.free": "Завантажити", + "nav.home": "Головна", + "nav.openMenu": "Відкрити меню", + "nav.getStartedFree": "Почати безкоштовно", + "nav.logoAlt": "OpenCode", + + "nav.context.copyLogo": "Копіювати логотип як SVG", + "nav.context.copyWordmark": "Копіювати знак як SVG", + "nav.context.brandAssets": "Бренд-матеріали", + + "footer.github": "GitHub", + "footer.docs": "Документація", + "footer.changelog": "Журнал змін", + "footer.discord": "Discord", + "footer.x": "X", + + "legal.brand": "Бренд", + "legal.privacy": "Конфіденційність", + "legal.terms": "Умови", + + "email.title": "Дізнайтеся першими про нові продукти", + "email.subtitle": "Приєднуйтесь до списку очікування для раннього доступу.", + "email.placeholder": "Електронна адреса", + "email.subscribe": "Підписатися", + "email.success": "Майже готово! Перевірте пошту та підтвердьте адресу", + + "notFound.title": "Не знайдено | opencode", + "notFound.heading": "404 — Сторінку не знайдено", + "notFound.home": "Головна", + "notFound.docs": "Документація", + "notFound.github": "GitHub", + "notFound.discord": "Discord", + "notFound.logoLightAlt": "світлий логотип opencode", + "notFound.logoDarkAlt": "темний логотип opencode", + + "user.logout": "Вийти", + + "auth.callback.error.codeMissing": "Не знайдено код авторизації.", + + "workspace.select": "Виберіть робочий простір", + "workspace.createNew": "+ Створити новий робочий простір", + "workspace.modal.title": "Створити новий робочий простір", + "workspace.modal.placeholder": "Введіть назву робочого простору", + + "common.cancel": "Скасувати", + "common.creating": "Створення...", + "common.create": "Створити", + "common.contactUs": "Зв'яжіться з нами", + + "common.videoUnsupported": "Ваш браузер не підтримує відео.", + "common.figure": "Рис. {{n}}.", + "common.faq": "FAQ", + "common.learnMore": "Дізнатися більше", + + "error.invalidPlan": "Недійсний план", + "error.workspaceRequired": "ID робочого простору обов'язкове", + "error.alreadySubscribed": "Цей робочий простір уже має підписку", + "error.limitRequired": "Ліміт обов'язковий.", + "error.monthlyLimitInvalid": "Встановіть дійсний місячний ліміт.", + "error.workspaceNameRequired": "Назва робочого простору обов'язкова.", + "error.nameTooLong": "Назва має містити не більше 255 символів.", + "error.emailRequired": "Електронна адреса обов'язкова", + "error.roleRequired": "Роль обов'язкова", + "error.idRequired": "ID обов'язкове", + "error.nameRequired": "Назва обов'язкова", + "error.providerRequired": "Провайдер обов'язковий", + "error.apiKeyRequired": "Ключ API обов'язковий", + "error.modelRequired": "Модель обов'язкова", + "error.reloadAmountMin": "Сума поповнення має бути щонайменше ${{amount}}", + "error.reloadTriggerMin": "Поріг балансу має бути щонайменше ${{amount}}", + + "app.meta.description": "OpenCode — відкритий агент для програмування.", + + "home.title": "OpenCode | Відкритий AI-агент для кодування", + + "temp.title": "opencode | AI-агент для кодування, створений для термінала", + "temp.hero.title": "AI-агент для кодування, створений для термінала", + "temp.zen": "opencode zen", + "temp.getStarted": "Почати", + "temp.feature.native.title": "Рідний TUI", + "temp.feature.native.body": "Чуйний, рідний інтерфейс термінала з темами", + "temp.feature.zen.beforeLink": "A", + "temp.feature.zen.link": "добірка моделей", + "temp.feature.zen.afterLink": "від opencode", + "temp.feature.models.beforeLink": "Підтримує 75+ LLM-провайдерів через", + "temp.feature.models.afterLink": ", включаючи локальні моделі", + "temp.screenshot.caption": "OpenCode TUI з темою tokyonight", + "temp.screenshot.alt": "OpenCode TUI з темою tokyonight", + "temp.logoLightAlt": "світлий логотип opencode", + "temp.logoDarkAlt": "темний логотип opencode", + + "home.banner.badge": "Нове", + "home.banner.text": "Десктопний застосунок доступний у бета-версії", + "home.banner.platforms": "на macOS, Windows та Linux", + "home.banner.downloadNow": "Завантажити зараз", + "home.banner.downloadBetaNow": "Завантажити бета-версію десктопного застосунку", + + "home.hero.title": "Відкритий AI-агент для кодування", + "home.hero.subtitle.a": "Безкоштовні моделі включено або підключіть будь-яку модель від будь-якого провайдера,", + "home.hero.subtitle.b": "включно з Claude, GPT, Gemini та іншими.", + + "home.install.ariaLabel": "Параметри встановлення", + + "home.what.title": "Що таке OpenCode?", + "home.what.body": "OpenCode — це відкритий агент, який допомагає писати код у терміналі, IDE або на десктопі.", + "home.what.lsp.title": "LSP увімкнено", + "home.what.lsp.body": "Автоматично завантажує потрібні LSP для LLM", + "home.what.multiSession.title": "Багатосесійність", + "home.what.multiSession.body": "Запускайте кількох агентів паралельно в одному проекті", + "home.what.shareLinks.title": "Посилання для обміну", + "home.what.shareLinks.body": "Діліться посиланням на будь-яку сесію для обговорення або налагодження", + "home.what.copilot.title": "GitHub Copilot", + "home.what.copilot.body": "Увійдіть через GitHub, щоб використовувати свій обліковий запис Copilot", + "home.what.chatgptPlus.title": "ChatGPT Plus/Pro", + "home.what.chatgptPlus.body": "Увійдіть через OpenAI, щоб використовувати ChatGPT Plus або Pro", + "home.what.anyModel.title": "Будь-яка модель", + "home.what.anyModel.body": "75+ LLM-провайдерів через Models.dev, включаючи локальні моделі", + "home.what.anyEditor.title": "Будь-який редактор", + "home.what.anyEditor.body": "Доступний як термінальний інтерфейс, десктопний застосунок та розширення IDE", + "home.what.readDocs": "Читати документацію", + + "home.growth.title": "Відкритий AI-агент для кодування", + "home.growth.body": + "З понад {{stars}} зірками на GitHub, {{contributors}} учасниками та понад {{commits}} комітами, OpenCode використовують понад {{monthlyUsers}} розробників щомісяця.", + "home.growth.githubStars": "Зірки GitHub", + "home.growth.contributors": "Учасники", + "home.growth.monthlyDevs": "Розробників на місяць", + + "home.privacy.title": "Створено для конфіденційності", + "home.privacy.body": + "OpenCode не зберігає ваш код або контекстні дані, тому може працювати в середовищах з чутливими даними.", + "home.privacy.learnMore": "Дізнатися більше про", + "home.privacy.link": "конфіденційність", + + "home.faq.q1": "Що таке OpenCode?", + "home.faq.a1": + "OpenCode — це відкритий агент, який допомагає писати та запускати код з будь-якою AI-моделлю. Доступний як термінальний інтерфейс, десктопний застосунок або розширення IDE.", + "home.faq.q2": "Як почати користуватися OpenCode?", + "home.faq.a2.before": "Найпростіший спосіб почати — прочитати", + "home.faq.a2.link": "вступ", + "home.faq.q3": "Чи потрібні додаткові AI-підписки для використання OpenCode?", + "home.faq.a3.p1": + "Не обов'язково, OpenCode має набір безкоштовних моделей, які можна використовувати без реєстрації.", + "home.faq.a3.p2.beforeZen": "Крім цього, ви можете використовувати будь-які популярні моделі, створивши обліковий запис", + "home.faq.a3.p2.afterZen": ".", + "home.faq.a3.p3": + "Хоча ми рекомендуємо Zen, OpenCode також працює з усіма популярними провайдерами, такими як OpenAI, Anthropic, xAI тощо.", + "home.faq.a3.p4.beforeLocal": "Ви навіть можете підключити свої", + "home.faq.a3.p4.localLink": "локальні моделі", + "home.faq.q4": "Чи можу я використовувати свої наявні AI-підписки з OpenCode?", + "home.faq.a4.p1": + "Так, OpenCode підтримує підписки всіх основних провайдерів. Ви можете використовувати Claude Pro/Max, ChatGPT Plus/Pro або GitHub Copilot.", + "home.faq.q5": "Чи можна використовувати OpenCode лише в терміналі?", + "home.faq.a5.beforeDesktop": "Вже ні! OpenCode тепер доступний як застосунок для", + "home.faq.a5.desktop": "десктопа", + "home.faq.a5.and": "та", + "home.faq.a5.web": "вебу", + "home.faq.q6": "Скільки коштує OpenCode?", + "home.faq.a6": + "OpenCode є 100% безкоштовним. Він також має набір безкоштовних моделей. Додаткові витрати можливі, якщо ви підключите іншого провайдера.", + "home.faq.q7": "А як щодо даних та конфіденційності?", + "home.faq.a7.p1": "Ваші дані зберігаються лише тоді, коли ви використовуєте безкоштовні моделі або створюєте посилання для обміну.", + "home.faq.a7.p2.beforeModels": "Дізнайтеся більше про", + "home.faq.a7.p2.modelsLink": "наші моделі", + "home.faq.a7.p2.and": "та", + "home.faq.a7.p2.shareLink": "сторінки обміну", + "home.faq.q8": "Чи є OpenCode відкритим?", + "home.faq.a8.p1": "Так, OpenCode повністю відкритий. Вихідний код доступний публічно на", + "home.faq.a8.p2": "під ліцензією", + "home.faq.a8.mitLicense": "MIT License", + "home.faq.a8.p3": + ", тобто кожен може використовувати, змінювати або сприяти його розвитку. Будь-хто зі спільноти може створювати issues, надсилати pull request'и та розширювати функціональність.", + + "home.zenCta.title": "Отримайте доступ до надійних оптимізованих моделей для агентів кодування", + "home.zenCta.body": + "Zen дає доступ до добірки AI-моделей, які OpenCode протестував спеціально для агентів кодування. Не турбуйтеся про нестабільну якість — використовуйте перевірені моделі.", + "home.zenCta.link": "Дізнатися про Zen", + + "zen.title": "OpenCode Zen | Добірка надійних оптимізованих моделей для агентів кодування", + "zen.hero.title": "Надійні оптимізовані моделі для агентів кодування", + "zen.hero.body": + "Zen дає доступ до добірки AI-моделей, які OpenCode протестував спеціально для агентів кодування. Не турбуйтеся про нестабільну якість — використовуйте перевірені моделі.", + + "zen.faq.q1": "Що таке OpenCode Zen?", + "zen.faq.a1": + "Zen — це добірка AI-моделей, протестованих для агентів кодування, створена командою OpenCode.", + "zen.faq.q2": "Чому Zen точніший?", + "zen.faq.a2": + "Zen надає лише моделі, спеціально протестовані для агентів кодування. Ви ж не використовуєте масло ніж для стейка — не використовуйте погані моделі для кодування.", + "zen.faq.q3": "Чи Zen дешевший?", + "zen.faq.a3": + "Zen не є прибутковим. Zen передає вам вартість від провайдерів моделей. Чим вище використання Zen, тим кращі ціни OpenCode може узгодити та передати вам.", + "zen.faq.q4": "Скільки коштує Zen?", + "zen.faq.a4.p1.beforePricing": "Zen", + "zen.faq.a4.p1.pricingLink": "стягує плату за запит", + "zen.faq.a4.p1.afterPricing": "без націнок — ви платите рівно стільки, скільки стягує провайдер моделі.", + "zen.faq.a4.p2.beforeAccount": "Загальна вартість залежить від використання. Ви можете встановити місячні ліміти в", + "zen.faq.a4.p2.accountLink": "обліковому записі", + "zen.faq.a4.p3": + "Щоб покрити витрати, OpenCode додає лише невелику комісію за обробку платежу в розмірі $1.23 за кожне поповнення балансу $20.", + "zen.faq.q5": "А як щодо даних та конфіденційності?", + "zen.faq.a5.beforeExceptions": + "Усі моделі Zen розміщені в США. Провайдери дотримуються політики нульового зберігання та не використовують ваші дані для навчання моделей, за", + "zen.faq.a5.exceptionsLink": "такими винятками", + "zen.faq.q6": "Чи можна встановити ліміти витрат?", + "zen.faq.a6": "Так, ви можете встановити місячні ліміти витрат в обліковому записі.", + "zen.faq.q7": "Чи можна скасувати?", + "zen.faq.a7": "Так, ви можете вимкнути оплату в будь-який час і використовувати залишок.", + "zen.faq.q8": "Чи можна використовувати Zen з іншими агентами кодування?", + "zen.faq.a8": + "Хоча Zen чудово працює з OpenCode, ви можете використовувати Zen з будь-яким агентом. Дотримуйтесь інструкцій з налаштування у вашому агенті.", + + "zen.cta.start": "Почати з Zen", + "zen.pricing.title": "Додати $20 балансу Pay as you go", + "zen.pricing.fee": "(+$1.23 комісія за обробку карти)", + "zen.pricing.body": "Використовуйте з будь-яким агентом. Встановлюйте місячні ліміти. Скасуйте в будь-який час.", + "zen.problem.title": "Яку проблему вирішує Zen?", + "zen.problem.body": + "Доступно багато моделей, але лише деякі добре працюють з агентами кодування. Більшість провайдерів налаштовують їх по-різному з різними результатами.", + "zen.problem.subtitle": "Ми вирішуємо це для всіх, а не лише для користувачів OpenCode.", + "zen.problem.item1": "Тестування вибраних моделей та консультації з їхніми командами", + "zen.problem.item2": "Співпраця з провайдерами для забезпечення правильної доставки", + "zen.problem.item3": "Бенчмаркінг усіх комбінацій моделей та провайдерів, які ми рекомендуємо", + "zen.how.title": "Як працює Zen", + "zen.how.body": "Хоча ми пропонуємо використовувати Zen з OpenCode, ви можете використовувати Zen з будь-яким агентом.", + "zen.how.step1.title": "Зареєструйтеся та додайте $20 балансу", + "zen.how.step1.beforeLink": "дотримуйтесь", + "zen.how.step1.link": "інструкцій з налаштування", + "zen.how.step2.title": "Використовуйте Zen із прозорими цінами", + "zen.how.step2.link": "платіть за запит", + "zen.how.step2.afterLink": "без націнок", + "zen.how.step3.title": "Автоматичне поповнення", + "zen.how.step3.body": "коли баланс досягає $5, ми автоматично додаємо $20", + "zen.privacy.title": "Ваша конфіденційність важлива для нас", + "zen.privacy.beforeExceptions": + "Усі моделі Zen розміщені в США. Провайдери дотримуються політики нульового зберігання та не використовують ваші дані для навчання моделей, за", + "zen.privacy.exceptionsLink": "такими винятками", + + "go.title": "OpenCode Go | Недорогі моделі кодування для всіх", + "go.meta.description": + "Go починається від $5 за перший місяць, потім $10/місяць, з generous 5-годинними лімітами запитів для GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro та DeepSeek V4 Flash.", + "go.hero.title": "Недорогі моделі кодування для всіх", + "go.hero.body": + "Go надає агентне програмування програмістам у всьому світі, пропонуючи щедрі ліміти та надійний доступ до найкращих моделей з відкритим кодом.", + + "go.cta.start": "Підписатися на Go", + "go.cta.template": "{{text}} {{price}}", + "go.cta.text": "Підписатися на Go", + "go.cta.price": "$10/місяць", + "go.cta.promo": "$5 перший місяць", + "go.pricing.body": "Використовуйте з будь-яким агентом. $5 перший місяць, потім $10/місяць. Поповнюйте за потреби. Скасуйте в будь-який час.", + "go.graph.free": "Безкоштовно", + "go.graph.freePill": "Big Pickle та безкоштовні моделі", + "go.graph.go": "Go", + "go.graph.label": "Запитів за 5 годин", + "go.graph.usageLimits": "Ліміти використання", + "go.graph.tick": "{{n}}x", + "go.graph.aria": "Запитів за 5 год: {{free}} vs {{go}}", + + "go.testimonials.brand.zen": "Zen", + "go.testimonials.brand.go": "Go", + "go.testimonials.handle": "@OpenCode", + "go.testimonials.dax.name": "Dax Raad", + "go.testimonials.dax.title": "ex-CEO, Terminal Products", + "go.testimonials.dax.quoteAfter": "змінило моє життя, це справді очевидний вибір.", + "go.testimonials.jay.name": "Jay V", + "go.testimonials.jay.title": "ex-Founder, SEED, PM, Melt, Pop, Dapt, Cadmus, and ViewPoint", + "go.testimonials.jay.quoteBefore": "4 з 5 людей у нашій команді люблять використовувати", + "go.testimonials.jay.quoteAfter": ".", + "go.testimonials.adam.name": "Adam Elmore", + "go.testimonials.adam.title": "ex-Hero, AWS", + "go.testimonials.adam.quoteBefore": "Я не можу достатньо рекомендувати", + "go.testimonials.adam.quoteAfter": ". Серйозно, це дійсно добре.", + "go.testimonials.david.name": "David Hill", + "go.testimonials.david.title": "ex-Head of Design, Laravel", + "go.testimonials.david.quoteBefore": "Завдяки", + "go.testimonials.david.quoteAfter": "я знаю, що всі моделі протестовані та ідеальні для агентів кодування.", + "go.testimonials.frank.name": "Frank Wang", + "go.testimonials.frank.title": "ex-Intern, Nvidia (4 times)", + "go.testimonials.frank.quote": "Хотів би я досі бути в Nvidia.", + "go.problem.title": "Яку проблему вирішує Go?", + "go.problem.body": + "Ми зосереджені на тому, щоб зробити досвід OpenCode доступним для якомога більшої кількості людей. OpenCode Go — це недорога підписка: $5 за перший місяць, потім $10/місяць. Вона надає щедрі ліміти та надійний доступ до найкращих моделей з відкритим кодом.", + "go.problem.subtitle": " ", + "go.problem.item1": "Недорога підписка", + "go.problem.item2": "Щедрі ліміти та надійний доступ", + "go.problem.item3": "Створено для якомога більшої кількості програмістів", + "go.problem.item4": + "Включає GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro та DeepSeek V4 Flash", + "go.how.title": "Як працює Go", + "go.how.body": "Go починається від $5 за перший місяць, потім $10/місяць. Використовуйте з OpenCode або будь-яким агентом.", + "go.how.step1.title": "Створіть обліковий запис", + "go.how.step1.beforeLink": "дотримуйтесь", + "go.how.step1.link": "інструкцій з налаштування", + "go.how.step2.title": "Підпишіться на Go", + "go.how.step2.link": "$5 перший місяць", + "go.how.step2.afterLink": "потім $10/місяць із щедрими лімітами", + "go.how.step3.title": "Почніть кодувати", + "go.how.step3.body": "з надійним доступом до моделей з відкритим кодом", + "go.privacy.title": "Ваша конфіденційність важлива для нас", + "go.privacy.body": + "План розроблений переважно для міжнародних користувачів, з моделями, розміщеними в США, ЄС та Сінгапурі для стабільного глобального доступу.", + "go.privacy.contactAfter": "якщо у вас є запитання.", + "go.privacy.beforeExceptions": + "Моделі Go розміщені в США. Провайдери дотримуються політики нульового зберігання та не використовують ваші дані для навчання моделей, за", + "go.privacy.exceptionsLink": "такими винятками", + "go.faq.q1": "Що таке OpenCode Go?", + "go.faq.a1": + "Go — це недорога підписка, яка надає надійний доступ до найкращих моделей з відкритим кодом для агентного кодування.", + "go.faq.q2": "Які моделі включає Go?", + "go.faq.a2": "Go включає моделі, перелічені нижче, із щедрими лімітами та надійним доступом.", + "go.faq.q3": "Чи Go те саме, що Zen?", + "go.faq.a3": + "Ні. Zen — це плата за використання, тоді як Go починається від $5 за перший місяць, потім $10/місяць, із щедрими лімітами та надійним доступом до моделей з відкритим кодом.", + "go.faq.q4": "Скільки коштує Go?", + "go.faq.a4.p1.beforePricing": "Go коштує", + "go.faq.a4.p1.pricingLink": "$5 за перший місяць", + "go.faq.a4.p1.afterPricing": "потім $10/місяць із щедрими лімітами.", + "go.faq.a4.p2.beforeAccount": "Ви можете керувати підпискою в", + "go.faq.a4.p2.accountLink": "обліковому записі", + "go.faq.a4.p3": "Скасуйте в будь-який час.", + "go.faq.q5": "А як щодо даних та конфіденційності?", + "go.faq.a5.body": + "План розроблений переважно для міжнародних користувачів, з моделями в США, ЄС та Сінгапурі. Провайдери дотримуються політики нульового зберігання.", + "go.faq.a5.beforeExceptions": + "Моделі Go розміщені в США. Провайдери дотримуються політики нульового зберігання та не використовують ваші дані для навчання моделей, за", + "go.faq.a5.exceptionsLink": "такими винятками", + "go.faq.q6": "Чи можна поповнити баланс?", + "go.faq.a6": "Якщо вам потрібно більше використання, ви можете поповнити баланс в обліковому записі.", + "go.faq.q7": "Чи можна скасувати?", + "go.faq.a7": "Так, ви можете скасувати в будь-який час.", + "go.faq.q8": "Чи можна використовувати Go з іншими агентами кодування?", + "go.faq.a8": "Так, ви можете використовувати Go з будь-яким агентом.", + + "go.faq.q9": "Яка різниця між безкоштовними моделями та Go?", + "go.faq.a9": + "Безкоштовні моделі включають Big Pickle та акційні моделі з лімітом 200 запитів/день. Go включає GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro та DeepSeek V4 Flash із вищими лімітами.", + + "zen.api.error.rateLimitExceeded": "Перевищено ліміт запитів. Спробуйте пізніше.", + "zen.api.error.modelNotSupported": "Модель {{model}} не підтримується", + "zen.api.error.modelFormatNotSupported": "Модель {{model}} не підтримується для формату {{format}}", + "zen.api.error.noProviderAvailable": "Немає доступного провайдера", + "zen.api.error.providerNotSupported": "Провайдер {{provider}} не підтримується", + "zen.api.error.missingApiKey": "Відсутній ключ API.", + "zen.api.error.invalidApiKey": "Недійсний ключ API.", + "zen.api.error.subscriptionQuotaExceeded": "Перевищено квоту підписки. Повторіть через {{retryIn}}.", + "zen.api.error.goSubscriptionRollingLimitExceeded": + "Досягнуто 5-годинного ліміту використання. Скидається через {{retryIn}}. Щоб продовжити, увімкніть використання з доступного балансу: {{consoleGoUrl}}", + "zen.api.error.goSubscriptionWeeklyLimitExceeded": + "Досягнуто тижневого ліміту використання. Скидається через {{retryIn}}. Щоб продовжити, увімкніть використання з доступного балансу: {{consoleGoUrl}}", + "zen.api.error.goSubscriptionMonthlyLimitExceeded": + "Досягнуто місячного ліміту використання. Скидається через {{retryIn}}. Щоб продовжити, увімкніть використання з доступного балансу: {{consoleGoUrl}}", + "zen.api.error.noPaymentMethod": "Немає способу оплати. Додайте метод оплати: {{billingUrl}}", + "zen.api.error.insufficientBalance": "Недостатньо коштів. Керуйте оплатою: {{billingUrl}}", + "zen.api.error.workspaceMonthlyLimitReached": + "Ваш робочий простір досяг місячного ліміту витрат ${{amount}}. Керуйте лімітами: {{billingUrl}}", + "zen.api.error.userMonthlyLimitReached": + "Ви досягли місячного ліміту витрат ${{amount}}. Керуйте лімітами: {{membersUrl}}", + "zen.api.error.modelDisabled": "Модель вимкнено", + "zen.api.error.trialEnded": + "Безкоштовна акція для {{model}} закінчилася. Ви можете продовжити використання, підписавшись на OpenCode Go — {{link}}", + + "black.meta.title": "OpenCode Black | Доступ до найкращих моделей кодування", + "black.meta.description": "Отримайте доступ до Claude, GPT, Gemini та інших із планами підписки OpenCode Black.", + "black.hero.title": "Доступ до найкращих моделей кодування", + "black.hero.subtitle": "Включаючи Claude, GPT, Gemini та інші", + "black.title": "OpenCode Black | Ціни", + "black.paused": "Реєстрація в план Black тимчасово призупинена.", + "black.plan.icon20": "План Black 20", + "black.plan.icon100": "План Black 100", + "black.plan.icon200": "План Black 200", + "black.plan.multiplier100": "5x більше використання ніж Black 20", + "black.plan.multiplier200": "20x більше використання ніж Black 20", + "black.price.perMonth": "на місяць", + "black.price.perPersonBilledMonthly": "за особу з щомісячною оплатою", + "black.terms.1": "Ваша підписка не розпочнеться негайно", + "black.terms.2": "Вас додадуть до списку очікування та активують незабаром", + "black.terms.3": "Картку буде списано лише після активації підписки", + "black.terms.4": "Діють ліміти використання, інтенсивне автоматичне використання може швидше вичерпати ліміти", + "black.terms.5": "Підписки призначені для фізичних осіб, зверніться в Enterprise для команд", + "black.terms.6": "Ліміти можуть бути змінені, а плани можуть бути припинені в майбутньому", + "black.terms.7": "Скасуйте підписку в будь-який час", + "black.action.continue": "Продовжити", + "black.finePrint.beforeTerms": "Зазначені ціни не включають податки", + "black.finePrint.terms": "Умови надання послуг", + "black.workspace.title": "OpenCode Black | Виберіть робочий простір", + "black.workspace.selectPlan": "Виберіть робочий простір для цього плану", + "black.workspace.name": "Робочий простір {{n}}", + "black.subscribe.title": "Підписатися на OpenCode Black", + "black.subscribe.paymentMethod": "Спосіб оплати", + "black.subscribe.loadingPaymentForm": "Завантаження форми оплати...", + "black.subscribe.selectWorkspaceToContinue": "Виберіть робочий простір для продовження", + "black.subscribe.failurePrefix": "Ой!", + "black.subscribe.error.generic": "Сталася помилка", + "black.subscribe.error.invalidPlan": "Недійсний план", + "black.subscribe.error.workspaceRequired": "ID робочого простору обов'язкове", + "black.subscribe.error.alreadySubscribed": "Цей робочий простір уже має підписку", + "black.subscribe.processing": "Обробка...", + "black.subscribe.submit": "Підписатися ${{plan}}", + "black.subscribe.form.chargeNotice": "Платіж буде списано лише після активації підписки", + "black.subscribe.success.title": "Ви в списку очікування OpenCode Black", + "black.subscribe.success.subscriptionPlan": "План підписки", + "black.subscribe.success.planName": "OpenCode Black {{plan}}", + "black.subscribe.success.amount": "Сума", + "black.subscribe.success.amountValue": "${{plan}} на місяць", + "black.subscribe.success.paymentMethod": "Спосіб оплати", + "black.subscribe.success.dateJoined": "Дата приєднання", + "black.subscribe.success.chargeNotice": "Вашу картку буде списано після активації підписки", + + "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", + "workspace.nav.usage": "Використання", + "workspace.nav.apiKeys": "Ключі API", + "workspace.nav.members": "Учасники", + "workspace.nav.billing": "Оплата", + "workspace.nav.settings": "Налаштування", + + "workspace.home.banner.beforeLink": "Надійні оптимізовані моделі для агентів кодування.", + "workspace.lite.banner.beforeLink": "Недорогі моделі кодування для всіх.", + "workspace.home.billing.loading": "Завантаження...", + "workspace.home.billing.enable": "Увімкнути оплату", + "workspace.home.billing.currentBalance": "Поточний баланс", + + "workspace.newUser.feature.tested.title": "Протестовані та перевірені моделі", + "workspace.newUser.feature.tested.body": + "Ми протестували моделі спеціально для агентів кодування, щоб забезпечити найкращу продуктивність.", + "workspace.newUser.feature.quality.title": "Найвища якість", + "workspace.newUser.feature.quality.body": + "Доступ до моделей, налаштованих для оптимальної продуктивності — без зниження якості.", + "workspace.newUser.feature.lockin.title": "Без блокування (Lock-in)", + "workspace.newUser.feature.lockin.body": + "Використовуйте Zen з будь-яким агентом і продовжуйте користуватися іншими провайдерами.", + "workspace.newUser.copyApiKey": "Копіювати ключ API", + "workspace.newUser.copyKey": "Копіювати ключ", + "workspace.newUser.copied": "Скопійовано!", + "workspace.newUser.step.enableBilling": "Увімкнути оплату", + "workspace.newUser.step.login.before": "Запустіть", + "workspace.newUser.step.login.after": "і виберіть opencode", + "workspace.newUser.step.pasteKey": "Вставте ключ API", + "workspace.newUser.step.models.before": "Запустіть opencode і виконайте", + "workspace.newUser.step.models.after": "щоб вибрати модель", + + "workspace.models.title": "Моделі", + "workspace.models.subtitle.beforeLink": "Керуйте доступом учасників до моделей.", + "workspace.models.table.model": "Модель", + "workspace.models.table.enabled": "Увімкнено", + + "workspace.providers.title": "Принесіть власний ключ (BYOK)", + "workspace.providers.subtitle": "Налаштуйте власні ключі API від AI-провайдерів.", + "workspace.providers.placeholder": "Введіть ключ API {{provider}} ({{prefix}}...)", + "workspace.providers.configure": "Налаштувати", + "workspace.providers.edit": "Редагувати", + "workspace.providers.delete": "Видалити", + "workspace.providers.saving": "Збереження...", + "workspace.providers.save": "Зберегти", + "workspace.providers.table.provider": "Провайдер", + "workspace.providers.table.apiKey": "Ключ API", + + "workspace.usage.title": "Історія використання", + "workspace.usage.subtitle": "Останнє використання API та витрати.", + "workspace.usage.empty": "Зробіть перший API-запит, щоб почати.", + "workspace.usage.table.date": "Дата", + "workspace.usage.table.model": "Модель", + "workspace.usage.table.input": "Вхід", + "workspace.usage.table.output": "Вихід", + "workspace.usage.table.cost": "Вартість", + "workspace.usage.table.session": "Сесія", + "workspace.usage.breakdown.input": "Вхід", + "workspace.usage.breakdown.cacheRead": "Читання кешу", + "workspace.usage.breakdown.cacheWrite": "Запис кешу", + "workspace.usage.breakdown.output": "Вихід", + "workspace.usage.breakdown.reasoning": "Міркування", + "workspace.usage.subscription": "Black (${{amount}})", + "workspace.usage.lite": "Go (${{amount}})", + "workspace.usage.byok": "BYOK (${{amount}})", + + "workspace.cost.title": "Вартість", + "workspace.cost.subtitle": "Витрати в розрізі моделей.", + "workspace.cost.allModels": "Усі моделі", + "workspace.cost.allKeys": "Усі ключі", + "workspace.cost.deletedSuffix": "(видалено)", + "workspace.cost.empty": "Немає даних про використання за вибраний період.", + "workspace.cost.subscriptionShort": "підп", + + "workspace.keys.title": "Ключі API", + "workspace.keys.subtitle": "Керуйте ключами API для доступу до сервісів opencode.", + "workspace.keys.create": "Створити ключ API", + "workspace.keys.placeholder": "Введіть назву ключа", + "workspace.keys.empty": "Створіть ключ API шлюзу opencode", + "workspace.keys.table.name": "Назва", + "workspace.keys.table.key": "Ключ", + "workspace.keys.table.createdBy": "Створено", + "workspace.keys.table.lastUsed": "Останнє використання", + "workspace.keys.copyApiKey": "Копіювати ключ API", + "workspace.keys.delete": "Видалити", + + "workspace.members.title": "Учасники", + "workspace.members.subtitle": "Керуйте учасниками робочого простору та їхніми дозволами.", + "workspace.members.invite": "Запросити учасника", + "workspace.members.inviting": "Запрошення...", + "workspace.members.beta.beforeLink": "Робочі простори безкоштовні для команд під час бета-версії.", + "workspace.members.form.invitee": "Запрошений", + "workspace.members.form.emailPlaceholder": "Введіть email", + "workspace.members.form.role": "Роль", + "workspace.members.form.monthlyLimit": "Місячний ліміт витрат", + "workspace.members.noLimit": "Без ліміту", + "workspace.members.noLimitLowercase": "без ліміту", + "workspace.members.invited": "запрошено", + "workspace.members.edit": "Редагувати", + "workspace.members.delete": "Видалити", + "workspace.members.saving": "Збереження...", + "workspace.members.save": "Зберегти", + "workspace.members.table.email": "Email", + "workspace.members.table.role": "Роль", + "workspace.members.table.monthLimit": "Ліміт на місяць", + "workspace.members.role.admin": "Адміністратор", + "workspace.members.role.adminDescription": "Може керувати моделями, учасниками та оплатою", + "workspace.members.role.member": "Учасник", + "workspace.members.role.memberDescription": "Може створювати ключі API лише для себе", + + "workspace.settings.title": "Налаштування", + "workspace.settings.subtitle": "Оновіть назву робочого простору та налаштування.", + "workspace.settings.workspaceName": "Назва робочого простору", + "workspace.settings.defaultName": "Стандартна", + "workspace.settings.updating": "Оновлення...", + "workspace.settings.save": "Зберегти", + "workspace.settings.edit": "Редагувати", + + "workspace.billing.title": "Оплата", + "workspace.billing.subtitle.beforeLink": "Керуйте способами оплати.", + "workspace.billing.contactUs": "Зв'яжіться з нами", + "workspace.billing.subtitle.afterLink": "якщо у вас є запитання.", + "workspace.billing.currentBalance": "Поточний баланс", + "workspace.billing.add": "Додати $", + "workspace.billing.enterAmount": "Введіть суму", + "workspace.billing.loading": "Завантаження...", + "workspace.billing.addAction": "Додати", + "workspace.billing.addBalance": "Поповнити баланс", + "workspace.billing.alipay": "Alipay", + "workspace.billing.wechat": "WeChat Pay", + "workspace.billing.linkedToStripe": "Підключено до Stripe", + "workspace.billing.manage": "Керувати", + "workspace.billing.enable": "Увімкнути оплату", + + "workspace.monthlyLimit.title": "Місячний ліміт", + "workspace.monthlyLimit.subtitle": "Встановіть місячний ліміт використання для облікового запису.", + "workspace.monthlyLimit.placeholder": "50", + "workspace.monthlyLimit.setting": "Встановлення...", + "workspace.monthlyLimit.set": "Встановити", + "workspace.monthlyLimit.edit": "Редагувати ліміт", + "workspace.monthlyLimit.noLimit": "Ліміт використання не встановлено.", + "workspace.monthlyLimit.currentUsage.beforeMonth": "Поточне використання за", + "workspace.monthlyLimit.currentUsage.beforeAmount": "становить $", + + "workspace.redeem.title": "Активувати купон", + "workspace.redeem.subtitle": "Активуйте код купона для отримання коштів або бонусів.", + "workspace.redeem.placeholder": "Введіть код купона", + "workspace.redeem.redeem": "Активувати", + "workspace.redeem.redeeming": "Активація...", + "workspace.redeem.success": "Купон успішно активовано.", + + "workspace.reload.title": "Автоматичне поповнення", + "workspace.reload.disabled.before": "Автоматичне поповнення", + "workspace.reload.disabled.state": "вимкнено", + "workspace.reload.disabled.after": "Увімкніть для автоматичного поповнення при низькому балансі.", + "workspace.reload.enabled.before": "Автоматичне поповнення", + "workspace.reload.enabled.state": "увімкнено", + "workspace.reload.enabled.middle": "Ми поповнимо", + "workspace.reload.processingFee": "комісія за обробку", + "workspace.reload.enabled.after": "коли баланс досягне", + "workspace.reload.edit": "Редагувати", + "workspace.reload.enable": "Увімкнути", + "workspace.reload.enableAutoReload": "Увімкнути автоматичне поповнення", + "workspace.reload.reloadAmount": "Поповнити на $", + "workspace.reload.whenBalanceReaches": "Коли баланс досягне $", + "workspace.reload.saving": "Збереження...", + "workspace.reload.save": "Зберегти", + "workspace.reload.failedAt": "Поповнення не вдалося о", + "workspace.reload.reason": "Причина:", + "workspace.reload.updatePaymentMethod": "Оновіть спосіб оплати та спробуйте ще раз.", + "workspace.reload.retrying": "Повтор...", + "workspace.reload.retry": "Повторити", + "workspace.reload.error.paymentFailed": "Платіж не вдався.", + + "workspace.payments.title": "Історія платежів", + "workspace.payments.subtitle": "Останні платіжні транзакції.", + "workspace.payments.table.date": "Дата", + "workspace.payments.table.paymentId": "ID платежу", + "workspace.payments.table.amount": "Сума", + "workspace.payments.table.receipt": "Квитанція", + "workspace.payments.type.credit": "кредит", + "workspace.payments.type.subscription": "підписка", + "workspace.payments.view": "Переглянути", + + "workspace.black.loading": "Завантаження...", + "workspace.black.time.day": "день", + "workspace.black.time.days": "дні", + "workspace.black.time.hour": "година", + "workspace.black.time.hours": "годин(и)", + "workspace.black.time.minute": "хвилина", + "workspace.black.time.minutes": "хвилин(и)", + "workspace.black.time.fewSeconds": "кілька секунд", + "workspace.black.subscription.title": "Підписка", + "workspace.black.subscription.message": "Ви підписані на OpenCode Black за ${{plan}} на місяць.", + "workspace.black.subscription.manage": "Керувати підпискою", + "workspace.black.subscription.rollingUsage": "Використання (5 год)", + "workspace.black.subscription.weeklyUsage": "Тижневе використання", + "workspace.black.subscription.resetsIn": "Скидається через", + "workspace.black.subscription.useBalance": "Використовуйте доступний баланс після досягнення лімітів", + "workspace.black.waitlist.title": "Список очікування", + "workspace.black.waitlist.joined": "Ви в списку очікування на план OpenCode Black за ${{plan}} на місяць.", + "workspace.black.waitlist.ready": "Ми готові зареєструвати вас на план OpenCode Black за ${{plan}} на місяць.", + "workspace.black.waitlist.leave": "Залишити список очікування", + "workspace.black.waitlist.leaving": "Вихід...", + "workspace.black.waitlist.left": "Вишли", + "workspace.black.waitlist.enroll": "Зареєструватися", + "workspace.black.waitlist.enrolling": "Реєстрація...", + "workspace.black.waitlist.enrolled": "Зареєстровано", + "workspace.black.waitlist.enrollNote": + "Після натискання «Зареєструватися» підписка почнеться негайно, а картку буде списано.", + + "workspace.lite.loading": "Завантаження...", + "workspace.lite.time.day": "день", + "workspace.lite.time.days": "дні", + "workspace.lite.time.hour": "година", + "workspace.lite.time.hours": "годин(и)", + "workspace.lite.time.minute": "хвилина", + "workspace.lite.time.minutes": "хвилин(и)", + "workspace.lite.time.fewSeconds": "кілька секунд", + "workspace.lite.subscription.message": "Ви підписані на OpenCode Go.", + "workspace.lite.subscription.manage": "Керувати підпискою", + "workspace.lite.subscription.rollingUsage": "Ковзне використання", + "workspace.lite.subscription.weeklyUsage": "Тижневе використання", + "workspace.lite.subscription.monthlyUsage": "Місячне використання", + "workspace.lite.subscription.resetsIn": "Скидається через", + "workspace.lite.subscription.useBalance": "Використовуйте доступний баланс після досягнення лімітів", + "workspace.lite.subscription.selectProvider": + 'Виберіть "OpenCode Go" як провайдера в конфігурації opencode.', + "workspace.lite.black.message": + "Ви вже підписані на OpenCode Black або в списку очікування. Спочатку скасуйте підписку, якщо хочете перейти на Go.", + "workspace.lite.other.message": + "Інший учасник цього робочого простору вже підписаний на OpenCode Go.", + "workspace.lite.promo.description": + "OpenCode Go починається від {{price}}, потім $10/місяць, із щедрими лімітами.", + "workspace.lite.promo.price": "$5 за перший місяць", + "workspace.lite.promo.modelsTitle": "Що включено", + "workspace.lite.promo.footer": + "План призначений для міжнародних користувачів. Ціни можуть змінюватися.", + "workspace.lite.promo.subscribe": "Підписатися на Go", + "workspace.lite.promo.subscribing": "Перенаправлення...", + "workspace.lite.promo.otherMethods": "Інші способи оплати", + "workspace.lite.promo.selectMethod": "Виберіть спосіб оплати", + + "download.title": "OpenCode | Завантажити", + "download.meta.description": "Завантажте OpenCode для macOS, Windows та Linux", + "download.hero.title": "Завантажити OpenCode", + "download.hero.subtitle": "Доступно в бета-версії для macOS, Windows та Linux", + "download.hero.button": "Завантажити для {{os}}", + "download.section.terminal": "Термінал OpenCode", + "download.section.desktop": "Десктоп OpenCode (Бета)", + "download.section.extensions": "Розширення OpenCode", + "download.section.integrations": "Інтеграції OpenCode", + "download.action.download": "Завантажити", + "download.action.install": "Встановити", + + "download.platform.macosAppleSilicon": "macOS (Apple Silicon)", + "download.platform.macosIntel": "macOS (Intel)", + "download.platform.windowsX64": "Windows (x64)", + "download.platform.linuxDeb": "Linux (.deb)", + "download.platform.linuxRpm": "Linux (.rpm)", + + "download.faq.a3.beforeLocal": + "Не обов'язково, але ймовірно. Вам знадобиться AI-підписка, якщо ви хочете підключити платного провайдера, хоча ви можете працювати з", + "download.faq.a3.localLink": "локальними моделями", + "download.faq.a3.afterLocal.beforeZen": "безкоштовно. Хоча ми рекомендуємо", + "download.faq.a3.afterZen": + ", OpenCode працює з усіма популярними провайдерами, такими як OpenAI, Anthropic, xAI тощо.", + + "download.faq.a5.p1": "OpenCode є 100% безкоштовним.", + "download.faq.a5.p2.beforeZen": + "Будь-які додаткові витрати будуть з вашої підписки у провайдера моделі. Ми рекомендуємо", + "download.faq.a5.p2.afterZen": ".", + + "download.faq.a6.p1": "Ваші дані зберігаються лише при створенні посилань для обміну в OpenCode.", + "download.faq.a6.p2.beforeShare": "Дізнайтеся більше про", + "download.faq.a6.shareLink": "сторінки обміну", + + "enterprise.title": "OpenCode | Enterprise-рішення для вашої організації", + "enterprise.meta.description": "Зв'яжіться з OpenCode для Enterprise-рішень", + "enterprise.hero.title": "Ваш код належить вам", + "enterprise.hero.body1": + "OpenCode працює безпечно всередині вашої організації без зберігання даних, ліцензійних обмежень. Почніть пробний період із командою, потім розгорніть через SSO та внутрішній AI-шлюз.", + "enterprise.hero.body2": "Дайте знати, чим ми можемо допомогти.", + "enterprise.form.name.label": "Повне ім'я", + "enterprise.form.name.placeholder": "Джеф Безос", + "enterprise.form.role.label": "Посада", + "enterprise.form.role.placeholder": "Голова правління", + "enterprise.form.company.label": "Компанія", + "enterprise.form.company.placeholder": "Acme Inc", + "enterprise.form.email.label": "Робоча електронна адреса", + "enterprise.form.email.placeholder": "jeff@amazon.com", + "enterprise.form.phone.label": "Номер телефону", + "enterprise.form.phone.placeholder": "+1 234 567 8900", + "enterprise.form.message.label": "Яку проблему ви намагаєтеся вирішити?", + "enterprise.form.message.placeholder": "Нам потрібна допомога з...", + "enterprise.form.send": "Надіслати", + "enterprise.form.sending": "Надсилання...", + "enterprise.form.success": "Повідомлення надіслано, ми зв'яжемося найближчим часом.", + "enterprise.form.success.submitted": "Форму успішно надіслано.", + "enterprise.form.error.allFieldsRequired": "Усі поля обов'язкові.", + "enterprise.form.error.invalidEmailFormat": "Недійсний формат email.", + "enterprise.form.error.internalServer": "Внутрішня помилка сервера.", + "enterprise.faq.title": "FAQ", + "enterprise.faq.q1": "Що таке OpenCode Enterprise?", + "enterprise.faq.a1": + "OpenCode Enterprise для організацій, які хочуть гарантувати, що код і дані ніколи не залишають їхню інфраструктуру.", + "enterprise.faq.q2": "Як почати з OpenCode Enterprise?", + "enterprise.faq.a2": + "Почніть із внутрішнього тестування з командою. OpenCode за замовчуванням не зберігає код. Потім зв'яжіться з нами для обговорення цін.", + "enterprise.faq.q3": "Як працює ціноутворення enterprise?", + "enterprise.faq.a3": + "Ми пропонуємо ціну за робоче місце. Якщо у вас власний LLM-шлюз, ми не стягуємо плату за токени.", + "enterprise.faq.q4": "Чи безпечні мої дані з OpenCode Enterprise?", + "enterprise.faq.a4": + "Так. OpenCode не зберігає ваш код або контекст. Вся обробка відбувається локально або через прямі API-виклики.", + + "brand.title": "OpenCode | Бренд", + "brand.meta.description": "Рекомендації щодо бренду OpenCode", + "brand.heading": "Рекомендації щодо бренду", + "brand.subtitle": "Ресурси та матеріали для роботи з брендом OpenCode.", + "brand.downloadAll": "Завантажити всі матеріали", + + "changelog.title": "OpenCode | Журнал змін", + "changelog.meta.description": "Нотатки про випуски та журнал змін OpenCode", + "changelog.hero.title": "Журнал змін", + "changelog.hero.subtitle": "Нові оновлення та покращення OpenCode", + "changelog.empty": "Записів у журналі змін не знайдено.", + "changelog.viewJson": "Переглянути JSON", + + "bench.list.title": "Бенчмарк", + "bench.list.heading": "Бенчмарки", + "bench.list.table.agent": "Агент", + "bench.list.table.model": "Модель", + "bench.list.table.score": "Результат", + "bench.submission.error.allFieldsRequired": "Усі поля обов'язкові.", + + "bench.detail.title": "Бенчмарк — {{task}}", + "bench.detail.notFound": "Завдання не знайдено", + "bench.detail.na": "Н/Д", + "bench.detail.labels.agent": "Агент", + "bench.detail.labels.model": "Модель", + "bench.detail.labels.task": "Завдання", + "bench.detail.labels.repo": "Репозиторій", + "bench.detail.labels.from": "Від", + "bench.detail.labels.to": "До", + "bench.detail.labels.prompt": "Prompt", + "bench.detail.labels.commit": "Коміт", + "bench.detail.labels.averageDuration": "Середня тривалість", + "bench.detail.labels.averageScore": "Середній результат", + "bench.detail.labels.averageCost": "Середня вартість", + "bench.detail.labels.summary": "Підсумок", + "bench.detail.labels.runs": "Запуски", + "bench.detail.labels.score": "Результат", + "bench.detail.labels.base": "База", + "bench.detail.labels.penalty": "Штраф", + "bench.detail.labels.weight": "вага", + "bench.detail.table.run": "Запуск", + "bench.detail.table.score": "Результат (База — Штраф)", + "bench.detail.table.cost": "Вартість", + "bench.detail.table.duration": "Тривалість", + "bench.detail.run.title": "Запуск {{n}}", + "bench.detail.rawJson": "Сирий JSON", +} diff --git a/packages/console/app/src/lib/language.ts b/packages/console/app/src/lib/language.ts index 5e80179e4774..a196f663db4c 100644 --- a/packages/console/app/src/lib/language.ts +++ b/packages/console/app/src/lib/language.ts @@ -11,6 +11,7 @@ export const LOCALES = [ "ja", "pl", "ru", + "uk", "ar", "no", "br", @@ -41,6 +42,7 @@ const LABEL = { ja: "日本語", pl: "Polski", ru: "Русский", + uk: "Українська", ar: "العربية", no: "Norsk", br: "Português (Brasil)", @@ -61,6 +63,7 @@ const TAG = { ja: "ja", pl: "pl", ru: "ru", + uk: "uk", ar: "ar", no: "no", br: "pt-BR", @@ -81,6 +84,7 @@ const DOCS = { ja: "ja", pl: "pl", ru: "ru", + uk: "uk", ar: "ar", no: "nb", br: "pt-br", @@ -104,6 +108,7 @@ const DOCS_SEGMENT = new Set([ "ru", "th", "tr", + "uk", "zh-cn", "zh-tw", ]) @@ -124,6 +129,7 @@ const DOCS_LOCALE = { ru: "ru", th: "th", tr: "tr", + uk: "uk", "zh-cn": "zh", "zh-tw": "zht", } as const satisfies Record @@ -239,6 +245,7 @@ function match(input: string): Locale | null { if (value.startsWith("ja")) return "ja" if (value.startsWith("pl")) return "pl" if (value.startsWith("ru")) return "ru" + if (value.startsWith("uk")) return "uk" if (value.startsWith("ar")) return "ar" if (value.startsWith("tr")) return "tr" if (value.startsWith("th")) return "th" diff --git a/packages/desktop/src/renderer/i18n/index.ts b/packages/desktop/src/renderer/i18n/index.ts index be87f94f9159..a88fed708027 100644 --- a/packages/desktop/src/renderer/i18n/index.ts +++ b/packages/desktop/src/renderer/i18n/index.ts @@ -11,6 +11,7 @@ import { dict as desktopDa } from "./da" import { dict as desktopJa } from "./ja" import { dict as desktopPl } from "./pl" import { dict as desktopRu } from "./ru" +import { dict as desktopUk } from "./uk" import { dict as desktopAr } from "./ar" import { dict as desktopNo } from "./no" import { dict as desktopBr } from "./br" @@ -27,6 +28,7 @@ import { dict as appDa } from "../../../../app/src/i18n/da" import { dict as appJa } from "../../../../app/src/i18n/ja" import { dict as appPl } from "../../../../app/src/i18n/pl" import { dict as appRu } from "../../../../app/src/i18n/ru" +import { dict as appUk } from "../../../../app/src/i18n/uk" import { dict as appAr } from "../../../../app/src/i18n/ar" import { dict as appNo } from "../../../../app/src/i18n/no" import { dict as appBr } from "../../../../app/src/i18n/br" @@ -44,6 +46,7 @@ export type Locale = | "ja" | "pl" | "ru" + | "uk" | "ar" | "no" | "br" @@ -64,6 +67,7 @@ const LOCALES: readonly Locale[] = [ "ja", "pl", "ru", + "uk", "bs", "ar", "no", @@ -89,6 +93,7 @@ function detectLocale(): Locale { if (language.toLowerCase().startsWith("ja")) return "ja" if (language.toLowerCase().startsWith("pl")) return "pl" if (language.toLowerCase().startsWith("ru")) return "ru" + if (language.toLowerCase().startsWith("uk")) return "uk" if (language.toLowerCase().startsWith("ar")) return "ar" if ( language.toLowerCase().startsWith("no") || @@ -148,6 +153,7 @@ function build(locale: Locale): Dictionary { if (locale === "ja") return { ...base, ...i18n.flatten(appJa), ...i18n.flatten(desktopJa) } if (locale === "pl") return { ...base, ...i18n.flatten(appPl), ...i18n.flatten(desktopPl) } if (locale === "ru") return { ...base, ...i18n.flatten(appRu), ...i18n.flatten(desktopRu) } + if (locale === "uk") return { ...base, ...i18n.flatten(appUk), ...i18n.flatten(desktopUk) } if (locale === "ar") return { ...base, ...i18n.flatten(appAr), ...i18n.flatten(desktopAr) } if (locale === "no") return { ...base, ...i18n.flatten(appNo), ...i18n.flatten(desktopNo) } if (locale === "br") return { ...base, ...i18n.flatten(appBr), ...i18n.flatten(desktopBr) } diff --git a/packages/desktop/src/renderer/i18n/uk.ts b/packages/desktop/src/renderer/i18n/uk.ts new file mode 100644 index 000000000000..f9a6212b228b --- /dev/null +++ b/packages/desktop/src/renderer/i18n/uk.ts @@ -0,0 +1,28 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Перевірити оновлення...", + "desktop.menu.installCli": "Встановити CLI...", + "desktop.menu.reloadWebview": "Перезавантажити Webview", + "desktop.menu.restart": "Перезапустити", + + "desktop.dialog.chooseFolder": "Виберіть теку", + "desktop.dialog.chooseFile": "Виберіть файл", + "desktop.dialog.saveFile": "Зберегти файл", + + "desktop.updater.checkFailed.title": "Не вдалося перевірити оновлення", + "desktop.updater.checkFailed.message": "Не вдалося перевірити наявність оновлень", + "desktop.updater.none.title": "Немає доступних оновлень", + "desktop.updater.none.message": "Ви вже використовуєте найновішу версію OpenCode", + "desktop.updater.downloadFailed.title": "Помилка оновлення", + "desktop.updater.downloadFailed.message": "Не вдалося завантажити оновлення", + "desktop.updater.downloaded.title": "Оновлення завантажено", + "desktop.updater.downloaded.prompt": + "Версію {{version}} OpenCode завантажено. Бажаєте встановити її та перезапустити?", + "desktop.updater.installFailed.title": "Помилка оновлення", + "desktop.updater.installFailed.message": "Не вдалося встановити оновлення", + + "desktop.cli.installed.title": "CLI встановлено", + "desktop.cli.installed.message": + "CLI встановлено до {{path}}\n\nПерезапустіть термінал, щоб використовувати команду 'opencode'.", + "desktop.cli.failed.title": "Не вдалося встановити", + "desktop.cli.failed.message": "Не вдалося встановити CLI: {{error}}", +} diff --git a/packages/ui/src/i18n/uk.ts b/packages/ui/src/i18n/uk.ts new file mode 100644 index 000000000000..8bfb3e4404dc --- /dev/null +++ b/packages/ui/src/i18n/uk.ts @@ -0,0 +1,167 @@ +export const dict: Record = { + "ui.sessionReview.title": "Зміни сесії", + "ui.sessionReview.title.git": "Зміни Git", + "ui.sessionReview.title.branch": "Зміни гілки", + "ui.sessionReview.title.lastTurn": "Зміни останнього кроку", + "ui.sessionReview.diffStyle.unified": "Об'єднаний", + "ui.sessionReview.diffStyle.split": "Розділений", + "ui.sessionReview.expandAll": "Розгорнути все", + "ui.sessionReview.collapseAll": "Згорнути все", + "ui.sessionReview.change.added": "Додано", + "ui.sessionReview.change.removed": "Видалено", + "ui.sessionReview.change.modified": "Змінено", + "ui.sessionReview.image.loading": "Завантаження...", + "ui.sessionReview.image.placeholder": "Зображення", + "ui.sessionReview.largeDiff.title": "Завеликий diff для відображення", + "ui.sessionReview.largeDiff.meta": "Ліміт: {{limit}} змінених рядків. Поточно: {{current}} змінених рядків.", + "ui.sessionReview.largeDiff.renderAnyway": "Все одно відобразити", + "ui.sessionReview.openFile": "Відкрити файл", + "ui.sessionReview.selection.line": "рядок {{line}}", + "ui.sessionReview.selection.lines": "рядки {{start}}-{{end}}", + + "ui.fileMedia.kind.image": "зображення", + "ui.fileMedia.kind.audio": "аудіо", + "ui.fileMedia.state.removed": "{{kind}} видалено", + "ui.fileMedia.state.loading": "Завантаження {{kind}}...", + "ui.fileMedia.state.error": "Не вдалося завантажити {{kind}}", + "ui.fileMedia.state.unavailable": "{{kind}} недоступне", + "ui.fileMedia.binary.title": "Бінарний файл", + "ui.fileMedia.binary.description.path": "Неможливо відобразити {{path}}, оскільки це бінарний файл.", + "ui.fileMedia.binary.description.default": "Неможливо відобразити цей файл, оскільки він бінарний.", + + "ui.lineComment.label.prefix": "Коментар до ", + "ui.lineComment.label.suffix": "", + "ui.lineComment.editorLabel.prefix": "Коментування: ", + "ui.lineComment.editorLabel.suffix": "", + "ui.lineComment.placeholder": "Додати коментар", + "ui.lineComment.submit": "Коментувати", + + "ui.sessionTurn.steps.show": "Показати кроки", + "ui.sessionTurn.steps.hide": "Приховати кроки", + "ui.sessionTurn.summary.response": "Відповідь", + "ui.sessionTurn.diff.showMore": "Показати більше змін ({{count}})", + "ui.sessionTurn.diffs.changed": "Змінено", + "ui.sessionTurn.diffs.showAll": "Показати всі", + "ui.sessionTurn.diffs.showLess": "Показати менше", + "ui.sessionTurn.diffs.more": "+{{count}} інших файлів", + + "ui.sessionTurn.retry.retrying": "повтор", + "ui.sessionTurn.retry.inSeconds": "за {{seconds}}с", + "ui.sessionTurn.retry.attempt": "спроба №{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} — спроба №{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini зараз перевантажений", + "ui.sessionTurn.error.freeUsageExceeded": "Перевищено ліміт безкоштовного використання", + "ui.sessionTurn.error.addCredits": "Додати кредити", + + "ui.sessionTurn.status.delegating": "Делегування роботи", + "ui.sessionTurn.status.planning": "Планування наступних кроків", + "ui.sessionTurn.status.gatheringContext": "Дослідження", + "ui.sessionTurn.status.gatheredContext": "Досліджено", + "ui.sessionTurn.status.searchingCodebase": "Пошук у кодовій базі", + "ui.sessionTurn.status.searchingWeb": "Пошук в інтернеті", + "ui.sessionTurn.status.makingEdits": "Внесення змін", + "ui.sessionTurn.status.runningCommands": "Виконання команд", + "ui.sessionTurn.status.thinking": "Міркування", + "ui.sessionTurn.status.thinkingWithTopic": "Міркування — {{topic}}", + "ui.sessionTurn.status.gatheringThoughts": "Збирання думок", + "ui.sessionTurn.status.consideringNextSteps": "Розгляд наступних кроків", + + "ui.messagePart.diagnostic.error": "Помилка", + "ui.messagePart.title.edit": "Редагувати", + "ui.messagePart.title.write": "Написати", + "ui.messagePart.option.typeOwnAnswer": "Введіть власну відповідь", + "ui.messagePart.review.title": "Перевірте свої відповіді", + "ui.messagePart.questions.dismissed": "Питання відхилено", + "ui.messagePart.compaction": "Сесію стиснуто", + "ui.messagePart.context.read.one": "{{count}} читання", + "ui.messagePart.context.read.other": "{{count}} читань", + "ui.messagePart.context.search.one": "{{count}} пошук", + "ui.messagePart.context.search.other": "{{count}} пошуків", + "ui.messagePart.context.list.one": "{{count}} список", + "ui.messagePart.context.list.other": "{{count}} списків", + + "ui.list.loading": "Завантаження", + "ui.list.empty": "Немає результатів", + "ui.list.clearFilter": "Очистити фільтр", + "ui.list.emptyWithFilter.prefix": "Немає результатів для", + "ui.list.emptyWithFilter.suffix": "", + + "ui.fileSearch.placeholder": "Знайти", + "ui.fileSearch.previousMatch": "Попередній збіг", + "ui.fileSearch.nextMatch": "Наступний збіг", + "ui.fileSearch.close": "Закрити пошук", + + "ui.messageNav.newMessage": "Нове повідомлення", + + "ui.textField.copyToClipboard": "Копіювати в буфер обміну", + "ui.textField.copyLink": "Копіювати посилання", + "ui.textField.copied": "Скопійовано", + + "ui.imagePreview.alt": "Попередній перегляд зображення", + "ui.scrollView.ariaLabel": "контент для прокручування", + + "ui.tool.read": "Читання", + "ui.tool.loaded": "Завантажено", + "ui.tool.list": "Список", + "ui.tool.glob": "Glob", + "ui.tool.grep": "Grep", + "ui.tool.task": "Завдання", + "ui.tool.webfetch": "Веб-отримання", + "ui.tool.websearch": "Веб-пошук", + "ui.tool.shell": "Оболонка", + "ui.tool.patch": "Патч", + "ui.tool.todos": "Завдання", + "ui.tool.todos.read": "Читати завдання", + "ui.tool.questions": "Питання", + "ui.tool.agent": "Агент {{type}}", + "ui.tool.agent.default": "Агент", + "ui.tool.skill": "Навичка", + + "ui.basicTool.called": "Викликано `{{tool}}`", + "ui.toolErrorCard.failed": "Помилка", + "ui.toolErrorCard.copyError": "Копіювати помилку", + + "ui.common.file.one": "файл", + "ui.common.file.other": "файлів", + "ui.common.question.one": "питання", + "ui.common.question.other": "питань", + + "ui.common.add": "Додати", + "ui.common.back": "Назад", + "ui.common.cancel": "Скасувати", + "ui.common.confirm": "Підтвердити", + "ui.common.dismiss": "Відхилити", + "ui.common.close": "Закрити", + "ui.common.next": "Далі", + "ui.common.submit": "Надіслати", + + "ui.permission.deny": "Заборонити", + "ui.permission.allowAlways": "Дозволяти завжди", + "ui.permission.allowOnce": "Дозволити один раз", + + "ui.message.expand": "Розгорнути повідомлення", + "ui.message.collapse": "Згорнути повідомлення", + "ui.message.copy": "Копіювати", + "ui.message.copyMessage": "Копіювати повідомлення", + "ui.message.forkMessage": "Відгалузити в нову сесію", + "ui.message.revertMessage": "Скинути до цього моменту", + "ui.message.copyResponse": "Копіювати відповідь", + "ui.message.copied": "Скопійовано", + "ui.message.duration.seconds": "{{count}}с", + "ui.message.duration.minutesSeconds": "{{minutes}}хв {{seconds}}с", + "ui.message.interrupted": "Перервано", + "ui.message.queued": "У черзі", + "ui.message.attachment.alt": "вкладення", + + "ui.patch.action.deleted": "Видалено", + "ui.patch.action.created": "Створено", + "ui.patch.action.moved": "Переміщено", + "ui.patch.action.patched": "Пропатчено", + + "ui.question.subtitle.answered": "{{count}} відповідей", + "ui.question.answer.none": "(немає відповіді)", + "ui.question.review.notAnswered": "(не відповіли)", + "ui.question.multiHint": "Виберіть усі відповідні варіанти", + "ui.question.singleHint": "Виберіть одну відповідь", + "ui.question.custom.placeholder": "Введіть свою відповідь...", +} diff --git a/packages/web/src/i18n/locales.ts b/packages/web/src/i18n/locales.ts index 67e36dfe1489..d1b3e74d1f07 100644 --- a/packages/web/src/i18n/locales.ts +++ b/packages/web/src/i18n/locales.ts @@ -14,6 +14,7 @@ export const docsLocale = [ "ru", "th", "tr", + "uk", "zh-cn", "zh-tw", ] as const @@ -46,6 +47,7 @@ export const localeAlias = { ru: "ru", th: "th", tr: "tr", + uk: "uk", zh: "zh-cn", "zh-cn": "zh-cn", zht: "zh-tw", @@ -63,6 +65,7 @@ const starts = [ ["ja", "ja"], ["pl", "pl"], ["ru", "ru"], + ["uk", "uk"], ["ar", "ar"], ["th", "th"], ["tr", "tr"], diff --git a/screenshot-uk.png b/screenshot-uk.png new file mode 100644 index 0000000000000000000000000000000000000000..b7c7b4573e4959414fc89ddb628294a564528319 GIT binary patch literal 83462 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�)@cgt51 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 Date: Tue, 19 May 2026 06:47:00 +0000 Subject: [PATCH 005/237] chore: generate --- packages/app/src/i18n/uk.ts | 29 +++++++++++++++++----------- packages/console/app/src/i18n/uk.ts | 30 ++++++++++++++--------------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts index 4ee63748929a..cf886f6a024e 100644 --- a/packages/app/src/i18n/uk.ts +++ b/packages/app/src/i18n/uk.ts @@ -171,7 +171,8 @@ export const dict = { "provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1", "provider.custom.field.apiKey.label": "Ключ API", "provider.custom.field.apiKey.placeholder": "Ключ API", - "provider.custom.field.apiKey.description": "Необов'язково. Залиште порожнім, якщо ви керуєте авторизацією через заголовки.", + "provider.custom.field.apiKey.description": + "Необов'язково. Залиште порожнім, якщо ви керуєте авторизацією через заголовки.", "provider.custom.models.label": "Моделі", "provider.custom.models.id.label": "ID", "provider.custom.models.id.placeholder": "model-id", @@ -232,7 +233,7 @@ export const dict = { "common.attachment": "вкладення", "prompt.placeholder.shell": "Введіть команду термінала... {{example}}", - "prompt.placeholder.normal": "Запитайте що завгодно... \"{{example}}\"", + "prompt.placeholder.normal": 'Запитайте що завгодно... "{{example}}"', "prompt.placeholder.simple": "Запитайте що завгодно...", "prompt.placeholder.summarizeComments": "Підсумувати коментарі…", "prompt.placeholder.summarizeComment": "Підсумувати коментар…", @@ -368,7 +369,8 @@ export const dict = { "dialog.releaseNotes.media.alt": "Попередній перегляд релізу", "context.breakdown.title": "Розподіл контексту", - "context.breakdown.note": "Приблизний розподіл вхідних токенів. \"Інше\" включає визначення інструментів і накладні витрати.", + "context.breakdown.note": + 'Приблизний розподіл вхідних токенів. "Інше" включає визначення інструментів і накладні витрати.', "context.breakdown.system": "Система", "context.breakdown.user": "Користувач", "context.breakdown.assistant": "Асистент", @@ -500,14 +502,15 @@ export const dict = { "error.chain.didYouMean": "Можливо, ви мали на увазі: {{suggestions}}", "error.chain.modelNotFound": "Модель не знайдено: {{provider}}/{{model}}", "error.chain.checkConfig": "Перевірте назви провайдерів/моделей у конфігурації (opencode.json)", - "error.chain.mcpFailed": "Сервер MCP \"{{name}}\" не працює. Зверніть увагу, OpenCode ще не підтримує автентифікацію MCP.", + "error.chain.mcpFailed": + 'Сервер MCP "{{name}}" не працює. Зверніть увагу, OpenCode ще не підтримує автентифікацію MCP.', "error.chain.providerAuthFailed": "Автентифікація провайдера не вдалася ({{provider}}): {{message}}", "error.chain.providerInitFailed": - "Не вдалося ініціалізувати провайдера \"{{provider}}\". Перевірте облікові дані та конфігурацію.", + 'Не вдалося ініціалізувати провайдера "{{provider}}". Перевірте облікові дані та конфігурацію.', "error.chain.configJsonInvalid": "Файл конфігурації {{path}} не є дійсним JSON(C)", "error.chain.configJsonInvalidWithMessage": "Файл конфігурації {{path}} не є дійсним JSON(C): {{message}}", "error.chain.configDirectoryTypo": - "Каталог \"{{dir}}\" у {{path}} недійсний. Перейменуйте каталог на \"{{suggestion}}\" або видаліть його. Це поширена помилка.", + 'Каталог "{{dir}}" у {{path}} недійсний. Перейменуйте каталог на "{{suggestion}}" або видаліть його. Це поширена помилка.', "error.chain.configFrontmatterError": "Не вдалося розібрати frontmatter у {{path}}:\n{{message}}", "error.chain.configInvalid": "Файл конфігурації {{path}} недійсний", "error.chain.configInvalidWithMessage": "Файл конфігурації {{path}} недійсний: {{message}}", @@ -682,7 +685,8 @@ export const dict = { "sidebar.workspaces.disable": "Вимкнути робочі області", "sidebar.gettingStarted.title": "Початок роботи", "sidebar.gettingStarted.line1": "OpenCode містить безкоштовні моделі, тому ви можете почати негайно.", - "sidebar.gettingStarted.line2": "Підключіть будь-якого провайдера, щоб використовувати моделі, включаючи Claude, GPT, Gemini тощо.", + "sidebar.gettingStarted.line2": + "Підключіть будь-якого провайдера, щоб використовувати моделі, включаючи Claude, GPT, Gemini тощо.", "sidebar.project.recentSessions": "Нещодавні сесії", "sidebar.project.viewAllSessions": "Переглянути всі сесії", "sidebar.project.clearNotifications": "Очистити сповіщення", @@ -755,11 +759,13 @@ export const dict = { "settings.general.row.followup.option.queue": "Черга", "settings.general.row.followup.option.steer": "Керування", "settings.general.row.showFileTree.title": "Дерево файлів", - "settings.general.row.showFileTree.description": "Показувати перемикач і панель дерева файлів у сесіях на робочому столі", + "settings.general.row.showFileTree.description": + "Показувати перемикач і панель дерева файлів у сесіях на робочому столі", "settings.general.row.showNavigation.title": "Елементи навігації", "settings.general.row.showNavigation.description": "Показувати кнопки назад і вперед у заголовку робочого столу", "settings.general.row.showSearch.title": "Палітра команд", - "settings.general.row.showSearch.description": "Показувати кнопку пошуку та палітри команд у заголовку робочого столу", + "settings.general.row.showSearch.description": + "Показувати кнопку пошуку та палітри команд у заголовку робочого столу", "settings.general.row.showTerminal.title": "Термінал", "settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу", "settings.general.row.showStatus.title": "Статус сервера", @@ -782,7 +788,7 @@ export const dict = { "На Linux з моніторами з різною частотою оновлення нативний Wayland може бути більш стабільним.", "settings.general.row.releaseNotes.title": "Нотатки до релізу", - "settings.general.row.releaseNotes.description": "Показувати спливаючі вікна \"Що нового\" після оновлень", + "settings.general.row.releaseNotes.description": 'Показувати спливаючі вікна "Що нового" після оновлень', "settings.updates.row.startup.title": "Перевіряти оновлення під час запуску", "settings.updates.row.startup.description": "Автоматично перевіряти наявність оновлень під час запуску OpenCode", @@ -928,7 +934,8 @@ export const dict = { "settings.permissions.tool.external_directory.title": "Зовнішній каталог", "settings.permissions.tool.external_directory.description": "Доступ до файлів за межами каталогу проєкту", "settings.permissions.tool.doom_loop.title": "Цикл приреченості", - "settings.permissions.tool.doom_loop.description": "Виявлення повторюваних викликів інструментів з однаковими вхідними даними", + "settings.permissions.tool.doom_loop.description": + "Виявлення повторюваних викликів інструментів з однаковими вхідними даними", "session.delete.failed.title": "Не вдалося видалити сесію", "session.delete.title": "Видалити сесію", diff --git a/packages/console/app/src/i18n/uk.ts b/packages/console/app/src/i18n/uk.ts index 442acb9a6b7b..4c0b0d645ef6 100644 --- a/packages/console/app/src/i18n/uk.ts +++ b/packages/console/app/src/i18n/uk.ts @@ -153,7 +153,8 @@ export const dict = { "home.faq.q3": "Чи потрібні додаткові AI-підписки для використання OpenCode?", "home.faq.a3.p1": "Не обов'язково, OpenCode має набір безкоштовних моделей, які можна використовувати без реєстрації.", - "home.faq.a3.p2.beforeZen": "Крім цього, ви можете використовувати будь-які популярні моделі, створивши обліковий запис", + "home.faq.a3.p2.beforeZen": + "Крім цього, ви можете використовувати будь-які популярні моделі, створивши обліковий запис", "home.faq.a3.p2.afterZen": ".", "home.faq.a3.p3": "Хоча ми рекомендуємо Zen, OpenCode також працює з усіма популярними провайдерами, такими як OpenAI, Anthropic, xAI тощо.", @@ -171,7 +172,8 @@ export const dict = { "home.faq.a6": "OpenCode є 100% безкоштовним. Він також має набір безкоштовних моделей. Додаткові витрати можливі, якщо ви підключите іншого провайдера.", "home.faq.q7": "А як щодо даних та конфіденційності?", - "home.faq.a7.p1": "Ваші дані зберігаються лише тоді, коли ви використовуєте безкоштовні моделі або створюєте посилання для обміну.", + "home.faq.a7.p1": + "Ваші дані зберігаються лише тоді, коли ви використовуєте безкоштовні моделі або створюєте посилання для обміну.", "home.faq.a7.p2.beforeModels": "Дізнайтеся більше про", "home.faq.a7.p2.modelsLink": "наші моделі", "home.faq.a7.p2.and": "та", @@ -194,8 +196,7 @@ export const dict = { "Zen дає доступ до добірки AI-моделей, які OpenCode протестував спеціально для агентів кодування. Не турбуйтеся про нестабільну якість — використовуйте перевірені моделі.", "zen.faq.q1": "Що таке OpenCode Zen?", - "zen.faq.a1": - "Zen — це добірка AI-моделей, протестованих для агентів кодування, створена командою OpenCode.", + "zen.faq.a1": "Zen — це добірка AI-моделей, протестованих для агентів кодування, створена командою OpenCode.", "zen.faq.q2": "Чому Zen точніший?", "zen.faq.a2": "Zen надає лише моделі, спеціально протестовані для агентів кодування. Ви ж не використовуєте масло ніж для стейка — не використовуйте погані моделі для кодування.", @@ -234,7 +235,8 @@ export const dict = { "zen.problem.item2": "Співпраця з провайдерами для забезпечення правильної доставки", "zen.problem.item3": "Бенчмаркінг усіх комбінацій моделей та провайдерів, які ми рекомендуємо", "zen.how.title": "Як працює Zen", - "zen.how.body": "Хоча ми пропонуємо використовувати Zen з OpenCode, ви можете використовувати Zen з будь-яким агентом.", + "zen.how.body": + "Хоча ми пропонуємо використовувати Zen з OpenCode, ви можете використовувати Zen з будь-яким агентом.", "zen.how.step1.title": "Зареєструйтеся та додайте $20 балансу", "zen.how.step1.beforeLink": "дотримуйтесь", "zen.how.step1.link": "інструкцій з налаштування", @@ -260,7 +262,8 @@ export const dict = { "go.cta.text": "Підписатися на Go", "go.cta.price": "$10/місяць", "go.cta.promo": "$5 перший місяць", - "go.pricing.body": "Використовуйте з будь-яким агентом. $5 перший місяць, потім $10/місяць. Поповнюйте за потреби. Скасуйте в будь-який час.", + "go.pricing.body": + "Використовуйте з будь-яким агентом. $5 перший місяць, потім $10/місяць. Поповнюйте за потреби. Скасуйте в будь-який час.", "go.graph.free": "Безкоштовно", "go.graph.freePill": "Big Pickle та безкоштовні моделі", "go.graph.go": "Go", @@ -300,7 +303,8 @@ export const dict = { "go.problem.item4": "Включає GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro та DeepSeek V4 Flash", "go.how.title": "Як працює Go", - "go.how.body": "Go починається від $5 за перший місяць, потім $10/місяць. Використовуйте з OpenCode або будь-яким агентом.", + "go.how.body": + "Go починається від $5 за перший місяць, потім $10/місяць. Використовуйте з OpenCode або будь-яким агентом.", "go.how.step1.title": "Створіть обліковий запис", "go.how.step1.beforeLink": "дотримуйтесь", "go.how.step1.link": "інструкцій з налаштування", @@ -646,18 +650,14 @@ export const dict = { "workspace.lite.subscription.monthlyUsage": "Місячне використання", "workspace.lite.subscription.resetsIn": "Скидається через", "workspace.lite.subscription.useBalance": "Використовуйте доступний баланс після досягнення лімітів", - "workspace.lite.subscription.selectProvider": - 'Виберіть "OpenCode Go" як провайдера в конфігурації opencode.', + "workspace.lite.subscription.selectProvider": 'Виберіть "OpenCode Go" як провайдера в конфігурації opencode.', "workspace.lite.black.message": "Ви вже підписані на OpenCode Black або в списку очікування. Спочатку скасуйте підписку, якщо хочете перейти на Go.", - "workspace.lite.other.message": - "Інший учасник цього робочого простору вже підписаний на OpenCode Go.", - "workspace.lite.promo.description": - "OpenCode Go починається від {{price}}, потім $10/місяць, із щедрими лімітами.", + "workspace.lite.other.message": "Інший учасник цього робочого простору вже підписаний на OpenCode Go.", + "workspace.lite.promo.description": "OpenCode Go починається від {{price}}, потім $10/місяць, із щедрими лімітами.", "workspace.lite.promo.price": "$5 за перший місяць", "workspace.lite.promo.modelsTitle": "Що включено", - "workspace.lite.promo.footer": - "План призначений для міжнародних користувачів. Ціни можуть змінюватися.", + "workspace.lite.promo.footer": "План призначений для міжнародних користувачів. Ціни можуть змінюватися.", "workspace.lite.promo.subscribe": "Підписатися на Go", "workspace.lite.promo.subscribing": "Перенаправлення...", "workspace.lite.promo.otherMethods": "Інші способи оплати", From 2339aacbf2878e4c8247a3f00f0f7ffa5f132e8b Mon Sep 17 00:00:00 2001 From: Dustin Deus Date: Tue, 19 May 2026 10:49:10 +0200 Subject: [PATCH 006/237] chore(team): add starptech to the team members list (#28320) --- .github/TEAM_MEMBERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/TEAM_MEMBERS b/.github/TEAM_MEMBERS index a662c7c0630a..f892eb95dbc4 100644 --- a/.github/TEAM_MEMBERS +++ b/.github/TEAM_MEMBERS @@ -14,3 +14,4 @@ rekram1-node thdxr simonklee vimtor +starptech From 9790a61f96771ca9fcb7b8b8d336aa8c150bb52d Mon Sep 17 00:00:00 2001 From: Dustin Deus Date: Tue, 19 May 2026 12:08:40 +0200 Subject: [PATCH 007/237] chore(docs): remove beta zen go (#28317) Co-authored-by: starptech --- packages/web/src/content/docs/ar/go.mdx | 4 ---- packages/web/src/content/docs/ar/zen.mdx | 4 ---- packages/web/src/content/docs/bs/go.mdx | 4 ---- packages/web/src/content/docs/bs/zen.mdx | 4 ---- packages/web/src/content/docs/da/go.mdx | 4 ---- packages/web/src/content/docs/da/zen.mdx | 4 ---- packages/web/src/content/docs/de/go.mdx | 4 ---- packages/web/src/content/docs/de/zen.mdx | 4 ---- packages/web/src/content/docs/es/go.mdx | 4 ---- packages/web/src/content/docs/es/zen.mdx | 4 ---- packages/web/src/content/docs/fr/go.mdx | 4 ---- packages/web/src/content/docs/fr/zen.mdx | 4 ---- packages/web/src/content/docs/go.mdx | 4 ---- packages/web/src/content/docs/it/go.mdx | 4 ---- packages/web/src/content/docs/it/zen.mdx | 4 ---- packages/web/src/content/docs/ja/go.mdx | 4 ---- packages/web/src/content/docs/ja/zen.mdx | 4 ---- packages/web/src/content/docs/ko/go.mdx | 4 ---- packages/web/src/content/docs/ko/zen.mdx | 4 ---- packages/web/src/content/docs/nb/go.mdx | 4 ---- packages/web/src/content/docs/nb/zen.mdx | 4 ---- packages/web/src/content/docs/pl/go.mdx | 4 ---- packages/web/src/content/docs/pl/zen.mdx | 4 ---- packages/web/src/content/docs/pt-br/go.mdx | 4 ---- packages/web/src/content/docs/pt-br/zen.mdx | 4 ---- packages/web/src/content/docs/ru/go.mdx | 4 ---- packages/web/src/content/docs/ru/zen.mdx | 4 ---- packages/web/src/content/docs/th/go.mdx | 4 ---- packages/web/src/content/docs/th/zen.mdx | 4 ---- packages/web/src/content/docs/tr/go.mdx | 4 ---- packages/web/src/content/docs/tr/zen.mdx | 4 ---- packages/web/src/content/docs/zen.mdx | 4 ---- packages/web/src/content/docs/zh-cn/go.mdx | 4 ---- packages/web/src/content/docs/zh-cn/zen.mdx | 4 ---- packages/web/src/content/docs/zh-tw/go.mdx | 4 ---- packages/web/src/content/docs/zh-tw/zen.mdx | 4 ---- 36 files changed, 144 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 81f885335c26..47e2a916fae2 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go هو اشتراك منخفض التكلفة — **$5 للشهر الأول**، ثم **$10/شهريًا** — يمنحك وصولًا موثوقًا إلى نماذج البرمجة المفتوحة الشائعة. -:::note -OpenCode Go حاليًا في المرحلة التجريبية. -::: - يعمل Go مثل أي مزود آخر في OpenCode. تشترك في OpenCode Go وتحصل على مفتاح API الخاص بك. وهو **اختياري تمامًا**، ولا تحتاج إلى استخدامه لاستخدام OpenCode. صُمّم أساسًا للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة لضمان وصول عالمي مستقر. diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index ced1f9c4de05..149425a77757 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen هي قائمة بالنماذج التي اختبرها فريق OpenCode وتحقّق منها. -:::note -OpenCode Zen متاح حاليا بنسخة تجريبية. -::: - يعمل Zen مثل أي مزوّد آخر في OpenCode. تسجّل الدخول إلى OpenCode Zen وتحصل على مفتاح API. استخدامه **اختياري بالكامل** ولا تحتاج إليه لاستخدام OpenCode. diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index d2df6aaad86c..274a05713631 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go je povoljna pretplata — **$5 za vaš prvi mjesec**, a zatim **$10/mjesečno** — koja vam pruža pouzdan pristup popularnim otvorenim modelima za programiranje. -:::note -OpenCode Go je trenutno u beta fazi. -::: - Go radi kao bilo koji drugi provajder u OpenCode-u. Pretplatite se na OpenCode Go i dobijete svoj API ključ. On je **potpuno opcionalan** i ne morate ga koristiti da biste koristili OpenCode. diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index af708ca8f844..623ae996e15a 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen je lista testiranih i provjerenih modela koje pruža OpenCode tim. -:::note -OpenCode Zen je trenutno u beta fazi. -::: - Zen radi kao i svaki drugi provajder u OpenCode. Prijavite se na OpenCode Zen i preuzmete svoj API ključ. To je **potpuno opcionalno** i ne morate ga koristiti da biste koristili OpenCode. diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 6891e6d5791a..80e363c29d6a 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go er et lavprisabonnement — **$5 for din første måned**, derefter **$10/måned** — der giver dig pålidelig adgang til populære åbne kodningsmodeller. -:::note -OpenCode Go er i øjeblikket i beta. -::: - Go fungerer som enhver anden udbyder i OpenCode. Du abonnerer på OpenCode Go og får din API-nøgle. Det er **helt valgfrit**, og du behøver ikke at bruge det for at bruge OpenCode. diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index e70b767c6cde..360f51ddd877 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen er en liste over testede og verificerede modeller leveret af OpenCode-teamet. -:::note -OpenCode Zen er i øjeblikket i beta. -::: - Zen fungerer som enhver anden udbyder i OpenCode. Du logger ind på OpenCode Zen og får din API-nøgle. Det er **helt valgfrit**, og du behøver ikke bruge det for at bruge OpenCode. diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 917ea340efcd..f148f5f3b8b7 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go ist ein kostengünstiges Abonnement — **5 $ für deinen ersten Monat**, danach **10 $/Monat** —, das dir zuverlässigen Zugriff auf beliebte offene Coding-Modelle bietet. -:::note -OpenCode Go befindet sich derzeit in der Beta-Phase. -::: - Go funktioniert wie jeder andere Provider in OpenCode. Du abonnierst OpenCode Go und erhältst deinen API-Key. Es ist **völlig optional** und du musst es nicht nutzen, um OpenCode zu verwenden. diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index e3d532ae6bc3..bf0937cdf81a 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen ist eine Liste von getesteten und verifizierten Modellen, die vom OpenCode-Team bereitgestellt wird. -:::note -OpenCode Zen befindet sich derzeit in der Beta. -::: - Zen funktioniert wie jeder andere Provider in OpenCode. Du meldest dich bei OpenCode Zen an und erhältst deinen API-Key. Es ist **vollständig optional** und du musst es nicht verwenden, um OpenCode zu nutzen. --- diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 0be23b3fa4ff..b97643170bbb 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go es una suscripción de bajo costo — **$5 por tu primer mes**, luego **$10/mes** — que te brinda acceso confiable a modelos abiertos de programación populares. -:::note -OpenCode Go está actualmente en beta. -::: - Go funciona como cualquier otro proveedor en OpenCode. Te suscribes a OpenCode Go y obtienes tu API key. Es **completamente opcional** y no necesitas usarlo para usar OpenCode. diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 94638d43a521..1dae815e7940 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen es una lista de modelos probados y verificados proporcionada por el equipo de OpenCode. -:::note -OpenCode Zen está actualmente en beta. -::: - Zen funciona como cualquier otro proveedor en OpenCode. Inicias sesión en OpenCode Zen y obtienes tu API key. Es **completamente opcional** y no necesitas usarlo para usar OpenCode. diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 3dd9c25f32e2..d43ce13fc8ee 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go est un abonnement à bas coût — **5 $ pour votre premier mois**, puis **10 $/mois** — qui vous donne un accès fiable aux modèles de codage ouverts populaires. -:::note -OpenCode Go est actuellement en version bêta. -::: - Go fonctionne comme n'importe quel autre fournisseur dans OpenCode. Vous vous abonnez à OpenCode Go et obtenez votre clé d'API. C'est **totalement facultatif** et vous n'avez pas besoin de l'utiliser pour utiliser OpenCode. Il est conçu principalement pour les utilisateurs internationaux, avec des modèles hébergés aux États-Unis, dans l'UE et à Singapour pour un accès mondial stable. diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index cb13ea6a90cc..ddab58803732 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen est une liste de modèles testés et vérifiés fournie par l'équipe OpenCode. -:::note -OpenCode Zen est actuellement en version bêta. -::: - Zen fonctionne comme n'importe quel autre fournisseur dans OpenCode. Vous vous connectez à OpenCode Zen et obtenez votre clé API. C'est **entièrement facultatif** et vous n'avez pas besoin de l'utiliser pour utiliser OpenCode. --- diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 237d1c4b8416..b97f21219695 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go is a low cost subscription — **$5 for your first month**, then **$10/month** — that gives you reliable access to popular open coding models. -:::note -OpenCode Go is currently in beta. -::: - Go works like any other provider in OpenCode. You subscribe to OpenCode Go and get your API key. It's **completely optional** and you don't need to use it to use OpenCode. diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index df4f6dd1caa1..2c7aecd49647 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go è un abbonamento a basso costo — **5 $ per il primo mese**, poi **10 $/mese** — che ti offre un accesso affidabile ai popolari modelli di programmazione aperti. -:::note -OpenCode Go è attualmente in beta. -::: - Go funziona come qualsiasi altro provider in OpenCode. Ti abboni a OpenCode Go e ottieni la tua chiave API. È **completamente facoltativo** e non hai bisogno di usarlo per utilizzare OpenCode. diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 7cf2988d4917..378c62c9e652 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen è un elenco di modelli testati e verificati forniti dal team di OpenCode. -:::note -OpenCode Zen è attualmente in beta. -::: - Zen funziona come qualsiasi altro provider in OpenCode. Accedi a OpenCode Zen e ottieni la tua chiave API. È **completamente opzionale** e non ti serve usarlo per usare OpenCode. diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 0cb294754fd2..9ba3476966d7 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Goは低価格のサブスクリプションで、**初月は5ドル**、その後は**月額10ドル**で、人気のオープンなコーディングモデルに安定してアクセスできます。 -:::note -OpenCode Goは現在ベータ版です。 -::: - GoはOpenCodeの他のプロバイダーと同様に機能します。OpenCode GoをサブスクライブしてAPIキーを取得します。これは**完全に任意**であり、OpenCodeを使用するために必須ではありません。 主に海外ユーザー向けに設計されており、世界中で安定してアクセスできるよう、モデルは米国、EU、シンガポールでホストされています。 diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index a15f291c062f..0f05672e1ee2 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen は、OpenCode チームが提供する、テスト済みかつ検証済みのモデルの一覧です。 -:::note -OpenCode Zen は現在ベータ版です。 -::: - Zen は OpenCode のほかのプロバイダーと同じように動作します。OpenCode Zen にログインして API キーを取得できます。これは **完全にオプション** であり、OpenCode を使うために必須ではありません。 --- diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index d0a3b9d0d146..53feaf669e30 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go는 인기 있는 오픈 코딩 모델에 안정적으로 액세스할 수 있게 해주는 저비용 구독 서비스입니다. **첫 달은 $5**, 이후에는 **월 $10**입니다. -:::note -OpenCode Go는 현재 베타입니다. -::: - Go는 OpenCode의 다른 제공자와 똑같이 작동합니다. OpenCode Go를 구독하고 API 키를 발급받으면 됩니다. 이는 **완전히 선택 사항**이며, OpenCode를 사용하기 위해 꼭 필요하지는 않습니다. 주로 해외 사용자를 위해 설계되었으며, 안정적인 전 세계 액세스를 위해 모델은 미국, EU, 싱가포르에 호스팅됩니다. diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 3a6b1ddc4d2e..a6411aded27a 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen은 OpenCode 팀이 제공하는, 테스트와 검증을 거친 모델 목록입니다. -:::note -OpenCode Zen은 현재 beta입니다. -::: - Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. OpenCode Zen에 로그인해 API 키를 받으면 됩니다. 이는 **완전히 선택 사항**이며, OpenCode를 사용하기 위해 반드시 사용할 필요는 없습니다. --- diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index e19b6ccce1cd..2f37bdb3f183 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go er et lavkostnadsabonnement — **$5 for din første måned**, deretter **$10/måned** — som gir deg pålitelig tilgang til populære åpne kodemodeller. -:::note -OpenCode Go er for øyeblikket i beta. -::: - Go fungerer som enhver annen leverandør i OpenCode. Du abonnerer på OpenCode Go og får din API-nøkkel. Det er **helt valgfritt**, og du trenger ikke å bruke det for å bruke OpenCode. diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index d3434b5cbb57..ce26d6bd5f81 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen er en liste over testede og verifiserte modeller levert av OpenCode-teamet. -:::note -OpenCode Zen er for øyeblikket i beta. -::: - Zen fungerer som enhver annen leverandør i OpenCode. Du logger inn på OpenCode Zen og får API-nøkkelen din. Det er **helt valgfritt**, og du trenger ikke bruke det for å bruke OpenCode. diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index 00f76a103f3d..c801b7e40dd8 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go to niskokosztowa subskrypcja — **5 $ za pierwszy miesiąc**, a następnie **10 $/miesiąc** — która zapewnia niezawodny dostęp do popularnych otwartych modeli do kodowania. -:::note -OpenCode Go jest obecnie w fazie beta. -::: - Go działa jak każdy inny dostawca w OpenCode. Subskrybujesz OpenCode Go i otrzymujesz swój klucz API. Jest to **całkowicie opcjonalne** i nie musisz z tego korzystać, aby używać OpenCode. diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 1984bf69c723..54696740760f 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen to lista przetestowanych i zweryfikowanych modeli udostępniana przez zespół OpenCode. -:::note -OpenCode Zen jest obecnie w wersji beta. -::: - Zen działa jak każdy inny dostawca w OpenCode. Logujesz się do OpenCode Zen i otrzymujesz swój klucz API. To **całkowicie opcjonalne** i nie musisz z tego korzystać, aby używać OpenCode. diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 44c5092a00e0..91b36a859ce6 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` O OpenCode Go é uma assinatura de baixo custo — **US$ 5 no seu primeiro mês**, depois **US$ 10/mês** — que oferece acesso confiável a modelos abertos de programação populares. -:::note -O OpenCode Go está atualmente em beta. -::: - O Go funciona como qualquer outro provedor no OpenCode. Você assina o OpenCode Go e obtém a sua chave de API. Ele é **totalmente opcional** e você não precisa usá-lo para usar o OpenCode. diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 712d3741fb04..784cbd3c7cad 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` O OpenCode Zen é uma lista de modelos testados e verificados fornecidos pela equipe do OpenCode. -:::note -O OpenCode Zen está atualmente em beta. -::: - O Zen funciona como qualquer outro provedor no OpenCode. Você faz login no OpenCode Zen e recebe sua chave de API. É **completamente opcional** e você não precisa usá-lo para usar o OpenCode. --- diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index 66e929c5f411..bef140567cfd 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go — это недорогая подписка (**$5 за первый месяц**, далее **$10 в месяц**), которая предоставляет надежный доступ к популярным открытым моделям для программирования. -:::note -OpenCode Go в настоящее время находится в бета-тестировании. -::: - Go работает так же, как и любой другой провайдер в OpenCode. Вы оформляете подписку на OpenCode Go и получаете свой API-ключ. Использование Go **абсолютно необязательно**, и вам не нужно использовать его, чтобы пользоваться OpenCode. diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 0f18811f7008..ffcf7fd6846d 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen — это список протестированных и проверенных моделей, предоставленный командой OpenCode. -:::note -OpenCode Zen сейчас находится в бета-версии. -::: - Zen работает как любой другой провайдер в OpenCode. Вы входите в OpenCode Zen и получаете свой ключ API. Это **полностью необязательно**, и вам не нужно использовать его, чтобы пользоваться OpenCode. diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 1fa0f8cc2a22..f88e484c8f0f 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go คือการสมัครสมาชิกในราคาประหยัด — **$5 สำหรับเดือนแรก** จากนั้น **$10/เดือน** — ซึ่งให้คุณเข้าถึงโมเดลโอเพนซอร์สยอดนิยมสำหรับการเขียนโค้ดได้อย่างเสถียร -:::note -OpenCode Go ขณะนี้อยู่ในช่วงเบต้า -::: - Go ทำงานเหมือนกับผู้ให้บริการ (provider) รายอื่นๆ ใน OpenCode คุณสามารถสมัครสมาชิก OpenCode Go และรับ API key ของคุณ บริการนี้เป็น**ทางเลือกเพิ่มเติม** และคุณไม่จำเป็นต้องใช้มันเพื่อใช้งาน OpenCode บริการนี้ออกแบบมาเพื่อผู้ใช้ในระดับสากลเป็นหลัก โดยมีโมเดลโฮสต์อยู่ในสหรัฐอเมริกา สหภาพยุโรป และสิงคโปร์ เพื่อการเข้าถึงทั่วโลกที่เสถียร diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 89e089bb4293..4b124409b49a 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen คือรายการโมเดลที่ผ่านการทดสอบและยืนยันแล้วโดยทีม OpenCode -:::note -OpenCode Zen อยู่ในช่วงเบต้าในขณะนี้ -::: - Zen ทำงานเหมือน provider อื่น ๆ ใน OpenCode คุณล็อกอินเข้า OpenCode Zen แล้วรับ API key ของคุณได้เลย มันเป็น **ทางเลือกทั้งหมด** และคุณไม่จำเป็นต้องใช้มันเพื่อใช้งาน OpenCode diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 367be5a750bf..2ff7c427e172 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go, popüler açık kodlama modellerine güvenilir erişim sağlayan düşük maliyetli bir aboneliktir — **ilk ayınız için 5$**, sonrasında **aylık 10$**. -:::note -OpenCode Go şu anda beta olarak mevcut. -::: - Go, OpenCode'daki diğer sağlayıcılar gibi çalışır. OpenCode Go'ya abone olur ve API anahtarınızı alırsınız. Bu **tamamen isteğe bağlıdır** ve OpenCode'u kullanmak için bunu kullanmanıza gerek yoktur. Dünya çapında istikrarlı erişim için ABD, AB ve Singapur'da barındırılan modellerle temel olarak uluslararası kullanıcılar için tasarlanmıştır. diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 23616dc17295..8ce0e90259f9 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen, OpenCode ekibi tarafından test edilip doğrulanmış modellerin bir listesidir. -:::note -OpenCode Zen şu anda beta olarak mevcut. -::: - Zen, OpenCode'daki diğer sağlayıcılar gibi çalışır. OpenCode Zen'de oturum açıp API anahtarınızı alırsınız. Bu **tamamen isteğe bağlıdır** ve OpenCode kullanmak için buna ihtiyacınız yoktur. --- diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index de5355c74ae3..544241669e47 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen is a list of tested and verified models provided by the OpenCode team. -:::note -OpenCode Zen is currently in beta. -::: - Zen works like any other provider in OpenCode. You login to OpenCode Zen and get your API key. It's **completely optional** and you don't need to use it to use OpenCode. diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 17934ee2a04a..2880771edb2f 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go 是一项低成本的订阅服务 —— **首月 5 美元**,之后 **每月 10 美元** —— 让你能够稳定地访问流行的开源编程模型。 -:::note -OpenCode Go 目前处于 beta 测试阶段。 -::: - Go 的工作方式与 OpenCode 中的任何其他提供商(provider)一样。订阅 OpenCode Go 后你将获得 API 密钥。它是 **完全可选** 的,并非使用 OpenCode 所必需的条件。 它主要为国际用户设计,模型托管在美国、欧盟和新加坡,以确保稳定的全球访问。 diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index c6d7f1bbd6c7..14f5a65730fe 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen 是由 OpenCode 团队提供的一组经过测试和验证的模型。 -:::note -OpenCode Zen 目前处于测试版阶段。 -::: - Zen 的工作方式与 OpenCode 中的任何其他提供商相同。你登录 OpenCode Zen 并获取 API 密钥。它是**完全可选的**,即使不用它,你也可以照常使用 OpenCode。 --- diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index c4589716f2a4..714225f03949 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Go 是一項低成本的訂閱服務——**首月 $5 美元**,之後**每月 $10 美元**——讓您能穩定使用受歡迎的開源寫程式模型。 -:::note -OpenCode Go 目前處於 beta 測試階段。 -::: - Go 的運作方式與 OpenCode 中的任何其他供應商相同。您訂閱 OpenCode Go 並取得您的 API key。這是**完全可選的**,您不需要使用它也能使用 OpenCode。 它主要為國際使用者設計,模型託管於美國、歐盟和新加坡,以提供全球穩定的存取。 diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 7e3b7bfd5450..5e6d58c6e05a 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -9,10 +9,6 @@ export const email = `mailto:${config.email}` OpenCode Zen 是由 OpenCode 團隊提供、經過測試與驗證的模型清單。 -:::note -OpenCode Zen 目前仍處於 beta。 -::: - Zen 的運作方式和 OpenCode 中的其他供應商一樣。你登入 OpenCode Zen 並取得 你的 API 金鑰。它是 **完全可選的**,你不需要使用它也能使用 OpenCode。 From ee16f08ffa35d564cf9a7149a69c43e8a6817b3f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 19 May 2026 13:52:00 +0200 Subject: [PATCH 008/237] Fix legacy pgup/pgdown TUI keybind aliases (#28275) --- packages/opencode/src/cli/cmd/tui/keymap.tsx | 2 + .../opencode/test/cli/tui/keymap.test.tsx | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 packages/opencode/test/cli/tui/keymap.test.tsx diff --git a/packages/opencode/src/cli/cmd/tui/keymap.tsx b/packages/opencode/src/cli/cmd/tui/keymap.tsx index 289bb901d6d1..41a7b08612dc 100644 --- a/packages/opencode/src/cli/cmd/tui/keymap.tsx +++ b/packages/opencode/src/cli/cmd/tui/keymap.tsx @@ -29,6 +29,8 @@ export type OpenTuiKeymap = ReturnType const KEY_ALIASES = { enter: "return", esc: "escape", + pgdown: "pagedown", + pgup: "pageup", } as const function expandKeyAliases(input: string) { diff --git a/packages/opencode/test/cli/tui/keymap.test.tsx b/packages/opencode/test/cli/tui/keymap.test.tsx new file mode 100644 index 000000000000..82cd72d6c874 --- /dev/null +++ b/packages/opencode/test/cli/tui/keymap.test.tsx @@ -0,0 +1,54 @@ +/** @jsxImportSource @opentui/solid */ +import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui" +import { testRender, useRenderer } from "@opentui/solid" +import { expect, test } from "bun:test" +import { onCleanup } from "solid-js" +import { createTuiResolvedConfig } from "../../fixture/tui-runtime" +import { OpencodeKeymapProvider, registerOpencodeKeymap } from "@/cli/cmd/tui/keymap" + +test("legacy page key aliases compile as page keys", async () => { + const sequences: Record = {} + + function Harness() { + const renderer = useRenderer() + const keymap = createDefaultOpenTuiKeymap(renderer) + const config = createTuiResolvedConfig({ + keybinds: { + messages_page_up: "pgup", + messages_page_down: "pgdown", + }, + }) + const offKeymap = registerOpencodeKeymap(keymap, renderer, config) + const offLayer = keymap.registerLayer({ + bindings: config.keybinds.gather("session", ["session.page.up", "session.page.down"]), + }) + const bindings = keymap.getCommandBindings({ + visibility: "registered", + commands: ["session.page.up", "session.page.down"], + }) + sequences.up = + bindings.get("session.page.up")?.map((binding) => binding.sequence.map((part) => part.stroke.name)) ?? [] + sequences.down = + bindings.get("session.page.down")?.map((binding) => binding.sequence.map((part) => part.stroke.name)) ?? [] + onCleanup(() => { + offLayer() + offKeymap() + }) + + return ( + + + + ) + } + + const app = await testRender(() => ) + try { + expect(sequences).toEqual({ + up: [["pageup"]], + down: [["pagedown"]], + }) + } finally { + app.renderer.destroy() + } +}) From cb15b3ad8432f1e9b244846aaacbf8d202bf4cbe Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 08:18:22 -0400 Subject: [PATCH 009/237] test(cli): subprocess integration tests for opencode acp (#28265) --- .../opencode/test/cli/acp/acp-process.test.ts | 70 ++++++++++ packages/opencode/test/lib/cli-process.ts | 120 +++++++++++++++++- 2 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 packages/opencode/test/cli/acp/acp-process.test.ts diff --git a/packages/opencode/test/cli/acp/acp-process.test.ts b/packages/opencode/test/cli/acp/acp-process.test.ts new file mode 100644 index 000000000000..a3c244c6c8ce --- /dev/null +++ b/packages/opencode/test/cli/acp/acp-process.test.ts @@ -0,0 +1,70 @@ +// Subprocess integration tests for `opencode acp`. ACP is a JSON-RPC +// protocol spoken over stdin/stdout (not HTTP) — see src/acp/README.md. +// This is the only test tier that exercises the full pipe of bun startup → +// server boot → ACP agent init → stdio framing → graceful shutdown. +import { describe, expect } from "bun:test" +import { Duration, Effect } from "effect" +import { cliIt } from "../../lib/cli-process" + +describe("opencode acp (subprocess)", () => { + // Smoke test: send the `initialize` request from src/acp/README.md and + // assert the response advertises the same protocol version and a non-empty + // capabilities block. If this fails, every other ACP test will too — start + // debugging here. + cliIt.live( + "responds to initialize with protocolVersion 1 and capabilities", + ({ opencode }) => + Effect.gen(function* () { + const acp = yield* opencode.acp() + + yield* acp.send({ + jsonrpc: "2.0", + id: 1, + method: "initialize", + params: { protocolVersion: 1 }, + }) + + // Tight deadline — the response should arrive within a few seconds + // once startup completes. A hang means the agent never finished init, + // which is a real regression and not a tuning issue. + const response = (yield* acp.receive.pipe(Effect.timeout(Duration.seconds(10)))) as { + jsonrpc: string + id: number + result?: { protocolVersion: number; agentCapabilities: Record } + error?: unknown + } + + expect(response.jsonrpc).toBe("2.0") + expect(response.id).toBe(1) + expect(response.error).toBeUndefined() + expect(response.result?.protocolVersion).toBe(1) + expect(response.result?.agentCapabilities).toBeDefined() + }), + 60_000, + ) + + // Lock in the scope-close kill path. ACP's clean shutdown is "EOF on stdin" + // — if a future refactor breaks the stdin-end branch in the handler, the + // process would only exit on SIGTERM fallback (2s in the harness). This + // test passing within the inner-scope assertion proves the EOF path works. + cliIt.live( + "exits cleanly when stdin is closed (scope close)", + ({ opencode }) => + Effect.gen(function* () { + const exitedPromise = yield* Effect.scoped( + Effect.gen(function* () { + const acp = yield* opencode.acp() + // Capture the Promise — scope-close fires the finalizer which + // ends stdin, and ACP should exit gracefully. + return acp.exited + }), + ) + + const code = yield* Effect.promise(() => exitedPromise) + // Bun returns a number for normal exit. Anything goes for SIGTERM, + // but we still require resolution within the test timeout. + expect(typeof code === "number" || code === null).toBe(true) + }), + 60_000, + ) +}) diff --git a/packages/opencode/test/lib/cli-process.ts b/packages/opencode/test/lib/cli-process.ts index ac03eddbf534..8481214a82f7 100644 --- a/packages/opencode/test/lib/cli-process.ts +++ b/packages/opencode/test/lib/cli-process.ts @@ -18,7 +18,7 @@ // without changing the fixture. Long-lived commands like `serve` will need a // different return shape — see the TODO at the bottom of OpencodeCli. import type { TestOptions } from "bun:test" -import { Deferred, Duration, Effect, Layer, Scope, Stream } from "effect" +import { Deferred, Duration, Effect, Layer, Queue, Scope, Stream } from "effect" import { FetchHttpClient, HttpClient } from "effect/unstable/http" import path from "node:path" import fs from "node:fs/promises" @@ -98,6 +98,28 @@ export type ServeHandle = { readonly exited: Promise } +// `opencode acp` speaks newline-delimited JSON-RPC over stdin/stdout. It is +// long-lived and exits cleanly when stdin is closed. The handle exposes the +// duplex stream as send/receive rather than raw pipes so tests don't have to +// reimplement framing on every call site. +export type AcpOpts = SpawnOpts & { + readonly cwd?: string + readonly extraArgs?: string[] +} + +export type AcpHandle = { + // Writes a single JSON-RPC message to the child's stdin as one ndjson line. + readonly send: (msg: object) => Effect.Effect + // Resolves with the next parsed JSON-RPC line from the child's stdout. + // Lines are buffered in a queue so multiple receives in a row won't drop + // anything. Pair with `Effect.timeout` if a test wants a deadline. + readonly receive: Effect.Effect + // Closes stdin. ACP exits cleanly on stdin EOF; the scope finalizer also + // calls this, so tests only need it when asserting exit behavior. + readonly close: () => void + readonly exited: Promise +} + export type OpencodeCli = { // High-level: run a single prompt against the test model. Short-lived. readonly run: (message: string, opts?: RunOpts) => Effect.Effect @@ -105,6 +127,9 @@ export type OpencodeCli = { // returned handle is killed when the caller's Scope closes. Fails if the // listening line doesn't appear within `readyTimeoutMs`. readonly serve: (opts?: ServeOpts) => Effect.Effect + // Spawn `opencode acp` and return a duplex JSON-RPC handle. Long-lived: + // the subprocess exits on stdin close, which the scope finalizer triggers. + readonly acp: (opts?: AcpOpts) => Effect.Effect // Escape hatch: any CLI invocation with full control over argv. Used to test // commands that don't yet have a typed builder. readonly spawn: (args: string[], opts?: SpawnOpts) => Effect.Effect @@ -260,7 +285,98 @@ export function withCliFixture( } satisfies ServeHandle }) - const opencode: OpencodeCli = { run, serve, spawn, expectExit, parseJsonEvents } + const acp = Effect.fn("opencode.acp")(function* (opts?: AcpOpts) { + const argv = ["acp"] + if (opts?.cwd) argv.push("--cwd", opts.cwd) + if (opts?.extraArgs) argv.push(...opts.extraArgs) + + // Acquire the subprocess. Release ends stdin (clean shutdown — ACP exits + // on stdin EOF) and falls back to SIGTERM if it doesn't exit promptly. + // Either way we await proc.exited so the test scope doesn't leak. + const proc = yield* Effect.acquireRelease( + Effect.sync(() => + Bun.spawn(["bun", "run", "--conditions=browser", cliEntry, ...argv], { + cwd: opts?.cwd ?? home, + env: { ...process.env, ...env, ...opts?.env }, + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + }), + ), + (p) => + // Graceful shutdown: close stdin (ACP exits on EOF), give it a + // window to exit, then SIGTERM. The Effect.timeoutOrElse expresses + // exactly that race without raw setTimeout or Promise.race. + Effect.gen(function* () { + yield* Effect.sync(() => p.stdin.end()) + yield* Effect.promise(() => p.exited).pipe( + Effect.timeoutOrElse({ + duration: Duration.seconds(2), + orElse: () => + Effect.sync(() => { + p.kill() + }), + }), + ) + yield* Effect.promise(() => p.exited) + }).pipe(Effect.ignore), + ) + + const stderrChunks: string[] = [] + yield* Effect.forkScoped( + Stream.fromReadableStream({ + evaluate: () => proc.stderr, + onError: () => new Error("stderr stream error"), + }).pipe( + Stream.decodeText(), + Stream.runForEach((chunk) => Effect.sync(() => stderrChunks.push(chunk))), + Effect.ignore, + ), + ) + + // Each ndjson line becomes one queue entry. JSON.parse failures are + // surfaced as the raw string so a malformed protocol message doesn't + // silently wedge the test in `receive`. + const responses = yield* Queue.unbounded() + yield* Effect.forkScoped( + Stream.fromReadableStream({ + evaluate: () => proc.stdout, + onError: () => new Error("stdout stream error"), + }).pipe( + Stream.decodeText(), + Stream.splitLines, + Stream.runForEach((line) => { + if (line.length === 0) return Effect.void + let parsed: unknown + try { + parsed = JSON.parse(line) + } catch { + parsed = { _rawLine: line } + } + return Queue.offer(responses, parsed) + }), + Effect.ignore, + ), + ) + + return { + send: (msg: object) => + Effect.sync(() => { + proc.stdin.write(JSON.stringify(msg) + "\n") + }), + receive: Queue.take(responses), + close: () => { + try { + proc.stdin.end() + } catch { + // already closed + } + }, + exited: proc.exited as Promise, + } satisfies AcpHandle + }) + + const opencode: OpencodeCli = { run, serve, acp, spawn, expectExit, parseJsonEvents } return yield* fn({ llm, home, opencode }) // FetchHttpClient is provided so test bodies can `yield* HttpClient.HttpClient` From 6618e2bce2c19eccaf14104399df6cac90520135 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 08:20:27 -0400 Subject: [PATCH 010/237] feat(native-llm): route Anthropic API-key models through native runtime (#28271) --- .../src/session/llm/native-runtime.ts | 11 +- .../session/native-anthropic-tool-loop.json | 53 +++ .../session/native-openai-tool-call.json | 31 -- .../session/native-zen-tool-call.json | 31 -- .../session/native-zen-tool-loop.json | 54 +++ .../test/session/llm-native-recorded.test.ts | 428 +++++++++--------- .../opencode/test/session/llm-native.test.ts | 32 +- 7 files changed, 349 insertions(+), 291 deletions(-) create mode 100644 packages/opencode/test/fixtures/recordings/session/native-anthropic-tool-loop.json delete mode 100644 packages/opencode/test/fixtures/recordings/session/native-openai-tool-call.json delete mode 100644 packages/opencode/test/fixtures/recordings/session/native-zen-tool-call.json create mode 100644 packages/opencode/test/fixtures/recordings/session/native-zen-tool-loop.json diff --git a/packages/opencode/src/session/llm/native-runtime.ts b/packages/opencode/src/session/llm/native-runtime.ts index f3dcd0c8692b..22b152a9b368 100644 --- a/packages/opencode/src/session/llm/native-runtime.ts +++ b/packages/opencode/src/session/llm/native-runtime.ts @@ -37,13 +37,16 @@ type StreamInput = { } export function status(input: Pick): RuntimeStatus { - if (input.model.providerID !== "openai" && !input.model.providerID.startsWith("opencode")) - return { type: "unsupported", reason: "provider is not openai or opencode" } - if (input.model.api.npm !== "@ai-sdk/openai") return { type: "unsupported", reason: "provider package is not OpenAI" } + const providerID = input.model.providerID + if (providerID !== "openai" && providerID !== "anthropic" && !providerID.startsWith("opencode")) + return { type: "unsupported", reason: "provider is not openai, opencode, or anthropic" } + const npm = input.model.api.npm + if (npm !== "@ai-sdk/openai" && npm !== "@ai-sdk/anthropic") + return { type: "unsupported", reason: "provider package is not OpenAI or Anthropic" } if (input.auth?.type === "oauth") return { type: "unsupported", reason: "OAuth auth is not supported" } const apiKey = typeof input.provider.options.apiKey === "string" ? input.provider.options.apiKey : input.provider.key - if (!apiKey) return { type: "unsupported", reason: "OpenAI API key is not configured" } + if (!apiKey) return { type: "unsupported", reason: "API key is not configured" } return { type: "supported", diff --git a/packages/opencode/test/fixtures/recordings/session/native-anthropic-tool-loop.json b/packages/opencode/test/fixtures/recordings/session/native-anthropic-tool-loop.json new file mode 100644 index 000000000000..cd099a68ee8f --- /dev/null +++ b/packages/opencode/test/fixtures/recordings/session/native-anthropic-tool-loop.json @@ -0,0 +1,53 @@ +{ + "version": 1, + "metadata": { + "name": "session/native-anthropic-tool-loop", + "recordedAt": "2026-05-19T01:40:12.788Z", + "provider": "anthropic", + "protocol": "anthropic-messages", + "route": "anthropic-messages", + "tags": [ + "opencode", + "native", + "tool-loop" + ] + }, + "interactions": [ + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.anthropic.com/v1/messages", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"claude-haiku-4-5-20251001\",\"system\":[{\"type\":\"text\",\"text\":\"Answer using tools when appropriate.\\nUse the get_weather tool exactly once to look up Paris, then reply with exactly: Paris is sunny.\",\"cache_control\":{\"type\":\"ephemeral\"}}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is the weather in Paris?\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get the current weather for a city.\",\"input_schema\":{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"cache_control\":{\"type\":\"ephemeral\"}}],\"stream\":true,\"max_tokens\":32000,\"temperature\":0}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_01KSRzhxWxF38x5yYVYvktbc\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":622,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":54,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01A8pEqifk2HVQfq1ZDNP6iY\",\"name\":\"get_weather\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"city\\\": \\\"P\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"aris\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":622,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":54} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n" + } + }, + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://api.anthropic.com/v1/messages", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"claude-haiku-4-5-20251001\",\"system\":[{\"type\":\"text\",\"text\":\"Answer using tools when appropriate.\\nUse the get_weather tool exactly once to look up Paris, then reply with exactly: Paris is sunny.\",\"cache_control\":{\"type\":\"ephemeral\"}}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is the weather in Paris?\",\"cache_control\":{\"type\":\"ephemeral\"}}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"tool_use\",\"id\":\"toolu_01A8pEqifk2HVQfq1ZDNP6iY\",\"name\":\"get_weather\",\"input\":{\"city\":{}}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"toolu_01A8pEqifk2HVQfq1ZDNP6iY\",\"content\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get the current weather for a city.\",\"input_schema\":{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"cache_control\":{\"type\":\"ephemeral\"}}],\"stream\":true,\"max_tokens\":32000,\"temperature\":0}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_01UyghbuSVecMVozDny14vCD\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":697,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Paris\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" is sunny.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":697,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":7} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n" + } + } + ] +} diff --git a/packages/opencode/test/fixtures/recordings/session/native-openai-tool-call.json b/packages/opencode/test/fixtures/recordings/session/native-openai-tool-call.json deleted file mode 100644 index b6670d58aa99..000000000000 --- a/packages/opencode/test/fixtures/recordings/session/native-openai-tool-call.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "version": 1, - "metadata": { - "name": "session/native-openai-tool-call", - "recordedAt": "2026-05-13T00:27:15.166Z", - "provider": "openai", - "protocol": "openai-responses", - "route": "openai-responses", - "tags": ["opencode", "native", "tool-call"] - }, - "interactions": [ - { - "transport": "http", - "request": { - "method": "POST", - "url": "https://api.openai.com/v1/responses", - "headers": { - "content-type": "application/json" - }, - "body": "{\"model\":\"gpt-4.1-mini\",\"input\":[{\"role\":\"system\",\"content\":\"Call tools exactly as instructed.\\nYou must call the lookup tool exactly once with query weather. Do not answer in text.\"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Use lookup.\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"lookup\",\"description\":\"Lookup data.\",\"parameters\":{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"],\"additionalProperties\":false}}],\"tool_choice\":\"required\",\"store\":false,\"prompt_cache_key\":\"session-recorded-native-tool\",\"temperature\":0,\"stream\":true}" - }, - "response": { - "status": 200, - "headers": { - "content-type": "text/event-stream; charset=utf-8" - }, - "body": "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_09354e0087427e4e016a03c56273e481a1842a9d4d6e5c3434\",\"object\":\"response\",\"created_at\":1778632034,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-native-tool\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":\"Lookup data.\",\"name\":\"lookup\",\"parameters\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}\n\nevent: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_09354e0087427e4e016a03c56273e481a1842a9d4d6e5c3434\",\"object\":\"response\",\"created_at\":1778632034,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-native-tool\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":\"Lookup data.\",\"name\":\"lookup\",\"parameters\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_0bqJ0EdThTwv5g1VILLkf9bo\",\"name\":\"lookup\"},\"output_index\":0,\"sequence_number\":2}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"obfuscation\":\"sDHc7xGP1uQu4v\",\"output_index\":0,\"sequence_number\":3}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"query\",\"item_id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"obfuscation\":\"wGG9bOcTCVa\",\"output_index\":0,\"sequence_number\":4}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"obfuscation\":\"i3uIOqQeUw5x4\",\"output_index\":0,\"sequence_number\":5}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"weather\",\"item_id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"obfuscation\":\"Y6emvEwAT\",\"output_index\":0,\"sequence_number\":6}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"}\",\"item_id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"obfuscation\":\"e5oTX3Ry6hrVEC\",\"output_index\":0,\"sequence_number\":7}\n\nevent: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"query\\\":\\\"weather\\\"}\",\"item_id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"output_index\":0,\"sequence_number\":8}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"query\\\":\\\"weather\\\"}\",\"call_id\":\"call_0bqJ0EdThTwv5g1VILLkf9bo\",\"name\":\"lookup\"},\"output_index\":0,\"sequence_number\":9}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_09354e0087427e4e016a03c56273e481a1842a9d4d6e5c3434\",\"object\":\"response\",\"created_at\":1778632034,\"status\":\"completed\",\"background\":false,\"completed_at\":1778632034,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4.1-mini-2025-04-14\",\"moderation\":null,\"output\":[{\"id\":\"fc_09354e0087427e4e016a03c562e5e881a1af75085c0cd7b52f\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"query\\\":\\\"weather\\\"}\",\"call_id\":\"call_0bqJ0EdThTwv5g1VILLkf9bo\",\"name\":\"lookup\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-native-tool\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":false,\"temperature\":0.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":\"Lookup data.\",\"name\":\"lookup\",\"parameters\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":72,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":6,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":78},\"user\":null,\"metadata\":{}},\"sequence_number\":10}\n\n" - } - } - ] -} diff --git a/packages/opencode/test/fixtures/recordings/session/native-zen-tool-call.json b/packages/opencode/test/fixtures/recordings/session/native-zen-tool-call.json deleted file mode 100644 index a7951cad5d71..000000000000 --- a/packages/opencode/test/fixtures/recordings/session/native-zen-tool-call.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "version": 1, - "metadata": { - "name": "session/native-zen-tool-call", - "recordedAt": "2026-05-13T02:31:23.884Z", - "provider": "opencode", - "protocol": "openai-responses", - "route": "openai-responses", - "tags": ["opencode", "zen", "native", "tool-call"] - }, - "interactions": [ - { - "transport": "http", - "request": { - "method": "POST", - "url": "https://console.opencode.ai/proxy/connections/{connection}/v1/responses", - "headers": { - "content-type": "application/json" - }, - "body": "{\"model\":\"gpt-5.2-codex\",\"input\":[{\"role\":\"system\",\"content\":\"Call tools exactly as instructed.\\nYou must call the lookup tool exactly once with query weather. Do not answer in text.\"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Use lookup.\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"lookup\",\"description\":\"Lookup data.\",\"parameters\":{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"],\"additionalProperties\":false}}],\"tool_choice\":\"required\",\"store\":false,\"prompt_cache_key\":\"session-recorded-native-zen-tool\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":32000,\"stream\":true}" - }, - "response": { - "status": 200, - "headers": { - "content-type": "text/event-stream; charset=utf-8" - }, - "body": "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_04ca34b8d77b281a016a03e27aa97c819ba8b0b5fca73ab4be\",\"object\":\"response\",\"created_at\":1778639482,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-native-zen-tool\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":\"Lookup data.\",\"name\":\"lookup\",\"parameters\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}\n\nevent: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_04ca34b8d77b281a016a03e27aa97c819ba8b0b5fca73ab4be\",\"object\":\"response\",\"created_at\":1778639482,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-native-zen-tool\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":\"Lookup data.\",\"name\":\"lookup\",\"parameters\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"rs_04ca34b8d77b281a016a03e27b0698819b856a269e323c764c\",\"type\":\"reasoning\",\"summary\":[]},\"output_index\":0,\"sequence_number\":2}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"rs_04ca34b8d77b281a016a03e27b0698819b856a269e323c764c\",\"type\":\"reasoning\",\"summary\":[]},\"output_index\":0,\"sequence_number\":3}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_4A3XM5Y1Nr1TtrbAaO61NyBa\",\"name\":\"lookup\"},\"output_index\":1,\"sequence_number\":4}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"obfuscation\":\"ZIWPTYcHCo2Crg\",\"output_index\":1,\"sequence_number\":5}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"query\",\"item_id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"obfuscation\":\"TZYnEWuRnuY\",\"output_index\":1,\"sequence_number\":6}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"obfuscation\":\"mR4nrEBFjAaQp\",\"output_index\":1,\"sequence_number\":7}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"weather\",\"item_id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"obfuscation\":\"JjG0yWAbO\",\"output_index\":1,\"sequence_number\":8}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"}\",\"item_id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"obfuscation\":\"vzmP5bsEBES4nV\",\"output_index\":1,\"sequence_number\":9}\n\nevent: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"query\\\":\\\"weather\\\"}\",\"item_id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"output_index\":1,\"sequence_number\":10}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"query\\\":\\\"weather\\\"}\",\"call_id\":\"call_4A3XM5Y1Nr1TtrbAaO61NyBa\",\"name\":\"lookup\"},\"output_index\":1,\"sequence_number\":11}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_04ca34b8d77b281a016a03e27aa97c819ba8b0b5fca73ab4be\",\"object\":\"response\",\"created_at\":1778639482,\"status\":\"completed\",\"background\":false,\"completed_at\":1778639483,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[{\"id\":\"rs_04ca34b8d77b281a016a03e27b0698819b856a269e323c764c\",\"type\":\"reasoning\",\"summary\":[]},{\"id\":\"fc_04ca34b8d77b281a016a03e27bb13c819bbd320fe32a98884a\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"query\\\":\\\"weather\\\"}\",\"call_id\":\"call_4A3XM5Y1Nr1TtrbAaO61NyBa\",\"name\":\"lookup\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-native-zen-tool\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"default\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":\"Lookup data.\",\"name\":\"lookup\",\"parameters\":{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":69,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":37,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":106},\"user\":null,\"metadata\":{}},\"sequence_number\":12}\n\nevent: ping\ndata: {\"type\":\"ping\",\"cost\":\"0\"}\n\n" - } - } - ] -} diff --git a/packages/opencode/test/fixtures/recordings/session/native-zen-tool-loop.json b/packages/opencode/test/fixtures/recordings/session/native-zen-tool-loop.json new file mode 100644 index 000000000000..bc065b2eaff0 --- /dev/null +++ b/packages/opencode/test/fixtures/recordings/session/native-zen-tool-loop.json @@ -0,0 +1,54 @@ +{ + "version": 1, + "metadata": { + "name": "session/native-zen-tool-loop", + "recordedAt": "2026-05-19T01:40:10.784Z", + "provider": "opencode", + "protocol": "openai-responses", + "route": "openai-responses", + "tags": [ + "opencode", + "zen", + "native", + "tool-loop" + ] + }, + "interactions": [ + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://console.opencode.ai/proxy/connections/{connection}/v1/responses", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"gpt-5.2-codex\",\"input\":[{\"role\":\"system\",\"content\":\"Answer using tools when appropriate.\\nUse the get_weather tool exactly once to look up Paris, then reply with exactly: Paris is sunny.\"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is the weather in Paris?\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get the current weather for a city.\",\"parameters\":{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"store\":false,\"prompt_cache_key\":\"session-recorded-opencode-loop\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":32000,\"stream\":true}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_0d90585f58f2d35f016a0bbf7742148195bfe42212304e4be6\",\"object\":\"response\",\"created_at\":1779154807,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-opencode-loop\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get the current weather for a city.\",\"name\":\"get_weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}\n\nevent: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_0d90585f58f2d35f016a0bbf7742148195bfe42212304e4be6\",\"object\":\"response\",\"created_at\":1779154807,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-opencode-loop\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get the current weather for a city.\",\"name\":\"get_weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"rs_0d90585f58f2d35f016a0bbf77ac888195b425de10685dc743\",\"type\":\"reasoning\",\"summary\":[]},\"output_index\":0,\"sequence_number\":2}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"rs_0d90585f58f2d35f016a0bbf77ac888195b425de10685dc743\",\"type\":\"reasoning\",\"summary\":[]},\"output_index\":0,\"sequence_number\":3}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_DfI0RwTrlaizfnQ9zkJC8rks\",\"name\":\"get_weather\"},\"output_index\":1,\"sequence_number\":4}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"{\\\"\",\"item_id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"obfuscation\":\"r6BY9MSxl1dMen\",\"output_index\":1,\"sequence_number\":5}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"city\",\"item_id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"obfuscation\":\"8mGMLd9Nbn3q\",\"output_index\":1,\"sequence_number\":6}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\":\\\"\",\"item_id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"obfuscation\":\"JyDVbFfriZuoj\",\"output_index\":1,\"sequence_number\":7}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"Paris\",\"item_id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"obfuscation\":\"FhppDWJYCJr\",\"output_index\":1,\"sequence_number\":8}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"delta\":\"\\\"}\",\"item_id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"obfuscation\":\"EIGPio8iUTvuCQ\",\"output_index\":1,\"sequence_number\":9}\n\nevent: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\",\"item_id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"output_index\":1,\"sequence_number\":10}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\",\"call_id\":\"call_DfI0RwTrlaizfnQ9zkJC8rks\",\"name\":\"get_weather\"},\"output_index\":1,\"sequence_number\":11}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_0d90585f58f2d35f016a0bbf7742148195bfe42212304e4be6\",\"object\":\"response\",\"created_at\":1779154807,\"status\":\"completed\",\"background\":false,\"completed_at\":1779154808,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[{\"id\":\"rs_0d90585f58f2d35f016a0bbf77ac888195b425de10685dc743\",\"type\":\"reasoning\",\"summary\":[]},{\"id\":\"fc_0d90585f58f2d35f016a0bbf7828548195be9a48e16e07947d\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\",\"call_id\":\"call_DfI0RwTrlaizfnQ9zkJC8rks\",\"name\":\"get_weather\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-opencode-loop\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"default\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get the current weather for a city.\",\"name\":\"get_weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":82,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":36,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":118},\"user\":null,\"metadata\":{}},\"sequence_number\":12}\n\nevent: ping\ndata: {\"type\":\"ping\",\"cost\":\"0\"}\n\n" + } + }, + { + "transport": "http", + "request": { + "method": "POST", + "url": "https://console.opencode.ai/proxy/connections/{connection}/v1/responses", + "headers": { + "content-type": "application/json" + }, + "body": "{\"model\":\"gpt-5.2-codex\",\"input\":[{\"role\":\"system\",\"content\":\"Answer using tools when appropriate.\\nUse the get_weather tool exactly once to look up Paris, then reply with exactly: Paris is sunny.\"},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is the weather in Paris?\"}]},{\"type\":\"function_call\",\"call_id\":\"call_DfI0RwTrlaizfnQ9zkJC8rks\",\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":{}}\"},{\"type\":\"function_call_output\",\"call_id\":\"call_DfI0RwTrlaizfnQ9zkJC8rks\",\"output\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}],\"tools\":[{\"type\":\"function\",\"name\":\"get_weather\",\"description\":\"Get the current weather for a city.\",\"parameters\":{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"store\":false,\"prompt_cache_key\":\"session-recorded-opencode-loop\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"auto\"},\"max_output_tokens\":32000,\"stream\":true}" + }, + "response": { + "status": 200, + "headers": { + "content-type": "text/event-stream; charset=utf-8" + }, + "body": "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_0d6b7c1a77edda29016a0bbf79f7b88194bc2a195b421fed3d\",\"object\":\"response\",\"created_at\":1779154810,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-opencode-loop\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get the current weather for a city.\",\"name\":\"get_weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":0}\n\nevent: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"response\":{\"id\":\"resp_0d6b7c1a77edda29016a0bbf79f7b88194bc2a195b421fed3d\",\"object\":\"response\",\"created_at\":1779154810,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-opencode-loop\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get the current weather for a city.\",\"name\":\"get_weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}},\"sequence_number\":1}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":2}\n\nevent: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"content_index\":0,\"item_id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"\"},\"sequence_number\":3}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\"Paris\",\"item_id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"logprobs\":[],\"obfuscation\":\"UjaMdEpsbTl\",\"output_index\":0,\"sequence_number\":4}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" is\",\"item_id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"logprobs\":[],\"obfuscation\":\"duJ9WCI0BevPi\",\"output_index\":0,\"sequence_number\":5}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\" sunny\",\"item_id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"logprobs\":[],\"obfuscation\":\"uD2rktvf81\",\"output_index\":0,\"sequence_number\":6}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\":\".\",\"item_id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"logprobs\":[],\"obfuscation\":\"d54HCWvaBdX0LvC\",\"output_index\":0,\"sequence_number\":7}\n\nevent: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"logprobs\":[],\"output_index\":0,\"sequence_number\":8,\"text\":\"Paris is sunny.\"}\n\nevent: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Paris is sunny.\"},\"sequence_number\":9}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Paris is sunny.\"}],\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\":10}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_0d6b7c1a77edda29016a0bbf79f7b88194bc2a195b421fed3d\",\"object\":\"response\",\"created_at\":1779154810,\"status\":\"completed\",\"background\":false,\"completed_at\":1779154810,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":32000,\"max_tool_calls\":null,\"model\":\"gpt-5.2-codex\",\"moderation\":null,\"output\":[{\"id\":\"msg_0d6b7c1a77edda29016a0bbf7a6a748194b3a46dcc8c72142c\",\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"text\":\"Paris is sunny.\"}],\"role\":\"assistant\"}],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\":\"session-recorded-opencode-loop\",\"prompt_cache_retention\":\"in_memory\",\"reasoning\":{\"effort\":\"medium\",\"summary\":\"detailed\"},\"safety_identifier\":\"wrk_redacted\",\"service_tier\":\"default\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Get the current weather for a city.\",\"name\":\"get_weather\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":118,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":8,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":126},\"user\":null,\"metadata\":{}},\"sequence_number\":11}\n\nevent: ping\ndata: {\"type\":\"ping\",\"cost\":\"0\"}\n\n" + } + } + ] +} diff --git a/packages/opencode/test/session/llm-native-recorded.test.ts b/packages/opencode/test/session/llm-native-recorded.test.ts index e5d6a31a5941..02c146d27009 100644 --- a/packages/opencode/test/session/llm-native-recorded.test.ts +++ b/packages/opencode/test/session/llm-native-recorded.test.ts @@ -1,7 +1,7 @@ import { NodeFileSystem } from "@effect/platform-node" import { HttpRecorder, Redactor } from "@opencode-ai/http-recorder" import { describe, expect } from "bun:test" -import { tool } from "ai" +import { tool, type ModelMessage, type JSONValue } from "ai" import { Effect, Layer, Stream } from "effect" import { FetchHttpClient } from "effect/unstable/http" import path from "node:path" @@ -12,6 +12,7 @@ import { Plugin } from "@/plugin" import { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "@/provider/schema" import { Filesystem } from "@/util/filesystem" +import { LLMEvent, LLMResponse } from "@opencode-ai/llm" import { LLMClient, RequestExecutor } from "@opencode-ai/llm/route" import { RuntimeFlags } from "@/effect/runtime-flags" import type { Agent } from "../../src/agent/agent" @@ -22,22 +23,105 @@ import type { ModelsDev } from "@opencode-ai/core/models-dev" import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const OPENAI_CASSETTE = "session/native-openai-tool-call" -const ZEN_CASSETTE = "session/native-zen-tool-call" const FIXTURES_DIR = path.join(import.meta.dir, "../fixtures/recordings") -const OPENAI_API_KEY = process.env.OPENCODE_RECORD_OPENAI_API_KEY ?? process.env.OPENAI_API_KEY -const CONSOLE_TOKEN = process.env.OPENCODE_RECORD_CONSOLE_TOKEN -const ZEN_ORG_ID = process.env.OPENCODE_RECORD_ZEN_ORG_ID -const ZEN_API_URL = - process.env.OPENCODE_RECORD_ZEN_API_URL ?? "https://console.opencode.ai/proxy/connections/fixture/v1" + +const zenURL = (connection: string) => `https://console.opencode.ai/proxy/connections/${connection}/v1` + +type ProviderSpec = { + readonly providerID: ProviderID + readonly modelID: string + readonly cassette: string + readonly protocol: string + readonly tags: ReadonlyArray + readonly canRecord: boolean + readonly config: (model: ModelsDev.Provider["models"][string]) => Partial +} + +const cloneModel = (model: ModelsDev.Provider["models"][string]) => + structuredClone(model) as NonNullable[string]["models"]>[string] + +const PROVIDERS = { + openai: { + providerID: ProviderID.openai, + modelID: "gpt-4.1-mini", + cassette: "session/native-openai-tool-loop", + protocol: "openai-responses", + tags: ["opencode", "native", "tool-loop"], + canRecord: Boolean(process.env.OPENCODE_RECORD_OPENAI_API_KEY ?? process.env.OPENAI_API_KEY), + config: (model) => ({ + enabled_providers: ["openai"], + provider: { + openai: { + name: "OpenAI", + env: ["OPENAI_API_KEY"], + npm: "@ai-sdk/openai", + api: "https://api.openai.com/v1", + models: { [model.id]: cloneModel(model) }, + options: { + apiKey: process.env.OPENCODE_RECORD_OPENAI_API_KEY ?? process.env.OPENAI_API_KEY ?? "fixture-openai-key", + baseURL: "https://api.openai.com/v1", + }, + }, + }, + }), + }, + opencode: { + providerID: ProviderID.opencode, + modelID: "gpt-5.2-codex", + cassette: "session/native-zen-tool-loop", + protocol: "openai-responses", + tags: ["opencode", "zen", "native", "tool-loop"], + canRecord: Boolean(process.env.OPENCODE_RECORD_CONSOLE_TOKEN && process.env.OPENCODE_RECORD_ZEN_ORG_ID), + config: (model) => ({ + enabled_providers: ["opencode"], + provider: { + opencode: { + name: "OpenCode Zen", + env: ["OPENCODE_CONSOLE_TOKEN"], + npm: "@ai-sdk/openai-compatible", + // The connection slug is account-specific; the cassette redactor + // normalizes it to {connection} for replay. Set during recording. + api: zenURL(process.env.OPENCODE_RECORD_ZEN_CONNECTION ?? "fixture"), + models: { [model.id]: cloneModel(model) }, + options: { + apiKey: process.env.OPENCODE_RECORD_CONSOLE_TOKEN ?? "fixture-console-token", + headers: { "x-org-id": process.env.OPENCODE_RECORD_ZEN_ORG_ID ?? "fixture-org" }, + }, + }, + }, + }), + }, + anthropic: { + providerID: ProviderID.anthropic, + modelID: "claude-haiku-4-5-20251001", + cassette: "session/native-anthropic-tool-loop", + protocol: "anthropic-messages", + tags: ["opencode", "native", "tool-loop"], + canRecord: Boolean(process.env.OPENCODE_RECORD_ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY), + config: (model) => ({ + enabled_providers: ["anthropic"], + provider: { + anthropic: { + name: "Anthropic", + env: ["ANTHROPIC_API_KEY"], + npm: "@ai-sdk/anthropic", + api: "https://api.anthropic.com/v1", + models: { [model.id]: cloneModel(model) }, + options: { + apiKey: + process.env.OPENCODE_RECORD_ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? "fixture-anthropic-key", + baseURL: "https://api.anthropic.com/v1", + }, + }, + }, + }), + }, +} satisfies Record const shouldRecord = process.env.RECORD === "true" -const canRunOpenAI = shouldRecord - ? Boolean(OPENAI_API_KEY) - : HttpRecorder.hasCassetteSync(OPENAI_CASSETTE, { directory: FIXTURES_DIR }) -const canRunZen = shouldRecord - ? Boolean(CONSOLE_TOKEN && ZEN_ORG_ID) - : HttpRecorder.hasCassetteSync(ZEN_CASSETTE, { directory: FIXTURES_DIR }) + +const canRun = (spec: ProviderSpec) => + shouldRecord ? spec.canRecord : HttpRecorder.hasCassetteSync(spec.cassette, { directory: FIXTURES_DIR }) async function loadFixture(providerID: string, modelID: string) { const data = await Filesystem.readJson>( @@ -50,234 +134,140 @@ async function loadFixture(providerID: string, modelID: string) { return model } -const openAIConfig = (model: ModelsDev.Provider["models"][string]): Partial => ({ - enabled_providers: ["openai"], - provider: { - openai: { - name: "OpenAI", - env: ["OPENAI_API_KEY"], - npm: "@ai-sdk/openai", - api: "https://api.openai.com/v1", - models: { - [model.id]: JSON.parse(JSON.stringify(model)) as NonNullable< - NonNullable[string]["models"] - >[string], - }, - options: { - apiKey: OPENAI_API_KEY ?? "fixture-openai-key", - baseURL: "https://api.openai.com/v1", - }, - }, - }, -}) - -const zenConfig = (model: ModelsDev.Provider["models"][string]): Partial => ({ - enabled_providers: ["opencode"], - provider: { - opencode: { - name: "OpenCode Zen", - env: ["OPENCODE_CONSOLE_TOKEN"], - npm: "@ai-sdk/openai-compatible", - api: ZEN_API_URL, - models: { - [model.id]: JSON.parse(JSON.stringify(model)) as NonNullable< - NonNullable[string]["models"] - >[string], - }, - options: { - apiKey: CONSOLE_TOKEN ?? "fixture-console-token", - headers: { - "x-org-id": ZEN_ORG_ID ?? "fixture-org", - }, - }, - }, - }, -}) - -function recordedNativeLLMLayer(cassette: string, metadata: Record) { - const cassetteService = HttpRecorder.Cassette.fileSystem({ directory: FIXTURES_DIR }).pipe( - Layer.provide(NodeFileSystem.layer), - ) +function recordedNativeLLMLayer(spec: ProviderSpec) { // Only the HTTP client is recorded; RequestExecutor and the opencode LLM stack remain real. - const recorder = HttpRecorder.recordingLayer(cassette, { - mode: shouldRecord ? "record" : "replay", - metadata, - redactor: Redactor.compose( - Redactor.defaults({ - url: { - transform: (url) => url.replace(/\/proxy\/connections\/[^/]+\/v1/, "/proxy/connections/{connection}/v1"), - }, - }), - { - response: (snapshot) => ({ ...snapshot, body: snapshot.body.replace(/wrk_[A-Z0-9]+/g, "wrk_redacted") }), - }, + const recordedClient = LLMClient.layer.pipe( + Layer.provide(RequestExecutor.layer), + Layer.provide( + HttpRecorder.recordingLayer(spec.cassette, { + mode: shouldRecord ? "record" : "replay", + metadata: { provider: spec.providerID, protocol: spec.protocol, route: spec.protocol, tags: spec.tags }, + redactor: Redactor.compose( + Redactor.defaults({ + url: { + transform: (url) => url.replace(/\/proxy\/connections\/[^/]+\/v1/, "/proxy/connections/{connection}/v1"), + }, + }), + { + response: (snapshot) => ({ ...snapshot, body: snapshot.body.replace(/wrk_[A-Z0-9]+/g, "wrk_redacted") }), + }, + ), + }).pipe(Layer.provide(FetchHttpClient.layer)), ), - }).pipe(Layer.provide(FetchHttpClient.layer)) - const executor = RequestExecutor.layer.pipe(Layer.provide(recorder)) - const client = LLMClient.layer.pipe(Layer.provide(executor)) - - const providerLayer = Provider.defaultLayer.pipe( - Layer.provide(Auth.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(Plugin.defaultLayer), - ) - const llmLayer = LLM.layer.pipe( - Layer.provide(Auth.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(Provider.defaultLayer), - Layer.provide(Plugin.defaultLayer), - Layer.provide(client), - Layer.provide(cassetteService), - Layer.provide(RuntimeFlags.layer({ experimentalNativeLlm: true })), ) - return Layer.mergeAll(providerLayer, llmLayer) + return Layer.mergeAll( + Provider.defaultLayer.pipe( + Layer.provide(Auth.defaultLayer), + Layer.provide(Config.defaultLayer), + Layer.provide(Plugin.defaultLayer), + ), + LLM.layer.pipe( + Layer.provide(Auth.defaultLayer), + Layer.provide(Config.defaultLayer), + Layer.provide(Provider.defaultLayer), + Layer.provide(Plugin.defaultLayer), + Layer.provide(recordedClient), + Layer.provide(HttpRecorder.Cassette.fileSystem({ directory: FIXTURES_DIR }).pipe(Layer.provide(NodeFileSystem.layer))), + Layer.provide(RuntimeFlags.layer({ experimentalNativeLlm: true })), + ), + ) } -const openAIIt = testEffect( - recordedNativeLLMLayer(OPENAI_CASSETTE, { - provider: "openai", - protocol: "openai-responses", - route: "openai-responses", - tags: ["opencode", "native", "tool-call"], - }), -) -const zenIt = testEffect( - recordedNativeLLMLayer(ZEN_CASSETTE, { - provider: "opencode", - protocol: "openai-responses", - route: "openai-responses", - tags: ["opencode", "zen", "native", "tool-call"], - }), -) -const recordedOpenAIInstance = canRunOpenAI ? openAIIt.instance : openAIIt.instance.skip -const recordedZenInstance = canRunZen ? zenIt.instance : zenIt.instance.skip - -const writeConfig = ( - directory: string, - model: ModelsDev.Provider["models"][string], - config: (model: ModelsDev.Provider["models"][string]) => Partial = openAIConfig, -) => +const writeConfig = (directory: string, spec: ProviderSpec, model: ModelsDev.Provider["models"][string]) => Effect.promise(() => Bun.write( path.join(directory, "opencode.json"), - JSON.stringify({ $schema: "https://opencode.ai/config.json", ...config(model) }), + JSON.stringify({ $schema: "https://opencode.ai/config.json", ...spec.config(model) }), ), ) -const getModel = (providerID: ProviderID, modelID: ModelID) => - Effect.gen(function* () { - const provider = yield* Provider.Service - return yield* provider.getModel(providerID, modelID) - }) - const collect = (input: LLM.StreamInput) => Effect.gen(function* () { const llm = yield* LLM.Service return Array.from(yield* llm.stream(input).pipe(Stream.runCollect)) }) -describe("session.llm native recorded", () => { - recordedOpenAIInstance("uses real RequestExecutor with HTTP recorder for native OpenAI tools", () => - Effect.gen(function* () { - const test = yield* TestInstance - const model = yield* Effect.promise(() => loadFixture("openai", "gpt-4.1-mini")) - yield* writeConfig(test.directory, model) +const WEATHER_RESULT = { temperature: 22, condition: "sunny" } as const +const WEATHER_SYSTEM = + "Use the get_weather tool exactly once to look up Paris, then reply with exactly: Paris is sunny." +const WEATHER_USER = "What is the weather in Paris?" - const sessionID = SessionID.make("session-recorded-native-tool") - const agent = { - name: "test", - mode: "primary", - prompt: "Call tools exactly as instructed.", - options: {}, - permission: [{ permission: "*", pattern: "*", action: "allow" }], - temperature: 0, - } satisfies Agent.Info - const resolved = yield* getModel(ProviderID.openai, ModelID.make(model.id)) - let executed: unknown - - const events = yield* collect({ - user: { - id: MessageID.make("msg_user-recorded-native-tool"), - sessionID, - role: "user", - time: { created: 0 }, - agent: agent.name, - model: { providerID: ProviderID.make("openai"), modelID: ModelID.make(model.id) }, - } satisfies MessageV2.User, - sessionID, - model: resolved, - agent, - system: ["You must call the lookup tool exactly once with query weather. Do not answer in text."], - messages: [{ role: "user", content: "Use lookup." }], - toolChoice: "required", - tools: { - lookup: tool({ - description: "Lookup data.", - inputSchema: z.object({ query: z.string() }), - execute: async (args, options) => { - executed = { args, toolCallId: options.toolCallId } - return { output: "looked up" } - }, - }), - }, - }) +const weatherTool = tool({ + description: "Get the current weather for a city.", + inputSchema: z.object({ city: z.string() }), + execute: async () => WEATHER_RESULT, +}) - expect(events.filter((event) => event.type === "step-finish")).toHaveLength(1) - expect(events.filter((event) => event.type === "finish")).toHaveLength(1) - expect(events.some((event) => event.type === "tool-result")).toBe(true) - expect(executed).toMatchObject({ args: { query: "weather" }, toolCallId: expect.any(String) }) - }), - ) +const toolRoundtrip = ( + call: { readonly id: string; readonly name: string; readonly input: unknown }, + result: JSONValue, +): ModelMessage[] => [ + { role: "assistant", content: [{ type: "tool-call", toolCallId: call.id, toolName: call.name, input: call.input }] }, + { + role: "tool", + content: [{ type: "tool-result", toolCallId: call.id, toolName: call.name, output: { type: "json", value: result } }], + }, +] - recordedZenInstance("uses console-managed Zen config with native OpenAI-compatible tools", () => - Effect.gen(function* () { - const test = yield* TestInstance - const model = yield* Effect.promise(() => loadFixture("opencode", "gpt-5.2-codex")) - yield* writeConfig(test.directory, model, zenConfig) +const driveToolLoop = (spec: ProviderSpec) => + Effect.gen(function* () { + const test = yield* TestInstance + const model = yield* Effect.promise(() => loadFixture(spec.providerID, spec.modelID)) + yield* writeConfig(test.directory, spec, model) - const sessionID = SessionID.make("session-recorded-native-zen-tool") - const agent = { - name: "test", - mode: "primary", - prompt: "Call tools exactly as instructed.", - options: {}, - permission: [{ permission: "*", pattern: "*", action: "allow" }], - } satisfies Agent.Info - const resolved = yield* getModel(ProviderID.opencode, ModelID.make(model.id)) - let executed: unknown + const sessionID = SessionID.make(`session-recorded-${spec.providerID}-loop`) + const modelID = ModelID.make(model.id) + const agent = { + name: "test", + mode: "primary", + prompt: "Answer using tools when appropriate.", + options: {}, + permission: [{ permission: "*", pattern: "*", action: "allow" }], + temperature: 0, + } satisfies Agent.Info + const provider = yield* Provider.Service + const resolved = yield* provider.getModel(spec.providerID, modelID) - const events = yield* collect({ - user: { - id: MessageID.make("msg_user-recorded-native-zen-tool"), - sessionID, - role: "user", - time: { created: 0 }, - agent: agent.name, - model: { providerID: ProviderID.opencode, modelID: ModelID.make(model.id) }, - } satisfies MessageV2.User, + const userMessage = { role: "user", content: WEATHER_USER } satisfies ModelMessage + const base = { + user: { + id: MessageID.make(`msg_user-recorded-${spec.providerID}-loop`), sessionID, - model: resolved, - agent, - system: ["You must call the lookup tool exactly once with query weather. Do not answer in text."], - messages: [{ role: "user", content: "Use lookup." }], - toolChoice: "required", - tools: { - lookup: tool({ - description: "Lookup data.", - inputSchema: z.object({ query: z.string() }), - execute: async (args, options) => { - executed = { args, toolCallId: options.toolCallId } - return { output: "looked up" } - }, - }), - }, - }) + role: "user", + time: { created: 0 }, + agent: agent.name, + model: { providerID: spec.providerID, modelID }, + } satisfies MessageV2.User, + sessionID, + model: resolved, + agent, + system: [WEATHER_SYSTEM], + tools: { get_weather: weatherTool }, + } - expect(events.filter((event) => event.type === "step-finish")).toHaveLength(1) - expect(events.filter((event) => event.type === "finish")).toHaveLength(1) - expect(events.some((event) => event.type === "tool-result")).toBe(true) - expect(executed).toMatchObject({ args: { query: "weather" }, toolCallId: expect.any(String) }) - }), - ) + const turn1 = yield* collect({ ...base, messages: [userMessage] }) + const toolCall = turn1.find(LLMEvent.is.toolCall) + expect(toolCall).toBeDefined() + expect(turn1.find(LLMEvent.is.toolResult)).toBeDefined() + expect(toolCall!.name).toBe("get_weather") + expect(toolCall!.input).toMatchObject({ city: expect.stringMatching(/Paris/i) }) + expect(turn1.filter(LLMEvent.is.stepFinish)).toHaveLength(1) + + const turn2 = yield* collect({ + ...base, + messages: [userMessage, ...toolRoundtrip(toolCall!, WEATHER_RESULT)], + }) + + expect(LLMResponse.text({ events: turn2 })).toMatch(/Paris is sunny/i) + expect(turn2.filter(LLMEvent.is.finish)).toHaveLength(1) + expect(turn2.filter(LLMEvent.is.toolCall)).toHaveLength(0) + }) + +describe("session.llm native recorded", () => { + for (const [name, spec] of Object.entries(PROVIDERS)) { + const it = testEffect(recordedNativeLLMLayer(spec)) + const instance = canRun(spec) ? it.instance : it.instance.skip + instance(`${name}: drives a tool loop to a final text answer`, () => driveToolLoop(spec)) + } }) diff --git a/packages/opencode/test/session/llm-native.test.ts b/packages/opencode/test/session/llm-native.test.ts index 7dd0c2e18a92..ecdcc2a57dec 100644 --- a/packages/opencode/test/session/llm-native.test.ts +++ b/packages/opencode/test/session/llm-native.test.ts @@ -262,11 +262,11 @@ describe("session.llm-native.request", () => { }) expect( LLMNativeRuntime.status({ - model: { ...baseModel, providerID: ProviderID.make("anthropic") }, - provider: { ...providerInfo, id: ProviderID.make("anthropic") }, + model: { ...baseModel, providerID: ProviderID.make("google") }, + provider: { ...providerInfo, id: ProviderID.make("google") }, auth: undefined, }), - ).toEqual({ type: "unsupported", reason: "provider is not openai or opencode" }) + ).toEqual({ type: "unsupported", reason: "provider is not openai, opencode, or anthropic" }) expect( LLMNativeRuntime.status({ model: baseModel, @@ -277,11 +277,11 @@ describe("session.llm-native.request", () => { expect( LLMNativeRuntime.status({ - model: { ...baseModel, api: { ...baseModel.api, npm: "@ai-sdk/anthropic" } }, + model: { ...baseModel, api: { ...baseModel.api, npm: "@ai-sdk/google" } }, provider: providerInfo, auth: undefined, }), - ).toEqual({ type: "unsupported", reason: "provider package is not OpenAI" }) + ).toEqual({ type: "unsupported", reason: "provider package is not OpenAI or Anthropic" }) expect( LLMNativeRuntime.status({ @@ -289,7 +289,27 @@ describe("session.llm-native.request", () => { provider: { ...providerInfo, options: {} }, auth: undefined, }), - ).toEqual({ type: "unsupported", reason: "OpenAI API key is not configured" }) + ).toEqual({ type: "unsupported", reason: "API key is not configured" }) + }) + + test("enables native runtime for Anthropic API-key models", () => { + expect( + LLMNativeRuntime.status({ + model: { + ...baseModel, + providerID: ProviderID.make("anthropic"), + api: { ...baseModel.api, npm: "@ai-sdk/anthropic", url: "https://api.anthropic.com/v1" }, + }, + provider: { + ...providerInfo, + id: ProviderID.make("anthropic"), + name: "Anthropic", + env: ["ANTHROPIC_API_KEY"], + options: { apiKey: "test-anthropic-key" }, + }, + auth: undefined, + }), + ).toMatchObject({ type: "supported", apiKey: "test-anthropic-key" }) }) test("prefers console provider api key over stored opencode auth", () => { From a2ee437a0e39caf7557ff48f5466dee5240a3900 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 19 May 2026 12:21:56 +0000 Subject: [PATCH 011/237] chore: generate --- .../recordings/session/native-anthropic-tool-loop.json | 6 +----- .../fixtures/recordings/session/native-zen-tool-loop.json | 7 +------ .../opencode/test/session/llm-native-recorded.test.ts | 8 ++++++-- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/opencode/test/fixtures/recordings/session/native-anthropic-tool-loop.json b/packages/opencode/test/fixtures/recordings/session/native-anthropic-tool-loop.json index cd099a68ee8f..ea88bccc1deb 100644 --- a/packages/opencode/test/fixtures/recordings/session/native-anthropic-tool-loop.json +++ b/packages/opencode/test/fixtures/recordings/session/native-anthropic-tool-loop.json @@ -6,11 +6,7 @@ "provider": "anthropic", "protocol": "anthropic-messages", "route": "anthropic-messages", - "tags": [ - "opencode", - "native", - "tool-loop" - ] + "tags": ["opencode", "native", "tool-loop"] }, "interactions": [ { diff --git a/packages/opencode/test/fixtures/recordings/session/native-zen-tool-loop.json b/packages/opencode/test/fixtures/recordings/session/native-zen-tool-loop.json index bc065b2eaff0..d9bf9e4505f4 100644 --- a/packages/opencode/test/fixtures/recordings/session/native-zen-tool-loop.json +++ b/packages/opencode/test/fixtures/recordings/session/native-zen-tool-loop.json @@ -6,12 +6,7 @@ "provider": "opencode", "protocol": "openai-responses", "route": "openai-responses", - "tags": [ - "opencode", - "zen", - "native", - "tool-loop" - ] + "tags": ["opencode", "zen", "native", "tool-loop"] }, "interactions": [ { diff --git a/packages/opencode/test/session/llm-native-recorded.test.ts b/packages/opencode/test/session/llm-native-recorded.test.ts index 02c146d27009..6732a3a1a3b8 100644 --- a/packages/opencode/test/session/llm-native-recorded.test.ts +++ b/packages/opencode/test/session/llm-native-recorded.test.ts @@ -168,7 +168,9 @@ function recordedNativeLLMLayer(spec: ProviderSpec) { Layer.provide(Provider.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(recordedClient), - Layer.provide(HttpRecorder.Cassette.fileSystem({ directory: FIXTURES_DIR }).pipe(Layer.provide(NodeFileSystem.layer))), + Layer.provide( + HttpRecorder.Cassette.fileSystem({ directory: FIXTURES_DIR }).pipe(Layer.provide(NodeFileSystem.layer)), + ), Layer.provide(RuntimeFlags.layer({ experimentalNativeLlm: true })), ), ) @@ -206,7 +208,9 @@ const toolRoundtrip = ( { role: "assistant", content: [{ type: "tool-call", toolCallId: call.id, toolName: call.name, input: call.input }] }, { role: "tool", - content: [{ type: "tool-result", toolCallId: call.id, toolName: call.name, output: { type: "json", value: result } }], + content: [ + { type: "tool-result", toolCallId: call.id, toolName: call.name, output: { type: "json", value: result } }, + ], }, ] From 922b2e10eb8172f891f981d4cd9588c9e7590b26 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 19 May 2026 20:22:50 +0800 Subject: [PATCH 012/237] refactor(app): remove Accessor wrapping from timeline row renders (#28334) --- .../src/pages/session/message-timeline.tsx | 108 +++++++----------- 1 file changed, 41 insertions(+), 67 deletions(-) diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index c1186b1d103b..1df861cfc383 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -1,19 +1,4 @@ -import { - createEffect, - createMemo, - createSignal, - For, - Index, - Match, - Switch, - on, - onCleanup, - Show, - mapArray, - untrack, - type Accessor, - type JSX, -} from "solid-js" +import { createEffect, createMemo, createSignal, For, Index, on, onCleanup, Show, mapArray, type JSX } from "solid-js" import { createStore, produce } from "solid-js/store" import { Dynamic } from "solid-js/web" import { useNavigate } from "@solidjs/router" @@ -995,26 +980,26 @@ export function MessageTimeline(props: { const getMsgPart = (messageID: string, partID: string) => getMsgParts(messageID).find((part) => part.id === partID) - const renderAssistantPartGroup = (row: Accessor) => { - if (untrack(row).group.type === "context") { + const renderAssistantPartGroup = (row: TimelineRowMap["AssistantPart"]) => { + if (row.group.type === "context") { const parts = createMemo(() => { - const group = row().group + const group = row.group if (group.type !== "context") return emptyTools return group.refs .map((ref) => getMsgPart(ref.messageID, ref.partID)) .filter((part): part is ToolPart => part?.type === "tool") }) - return + return } const message = createMemo(() => { - const group = row().group + const group = row.group if (group.type !== "part") return return messageByID().get(group.ref.messageID) }) const part = createMemo(() => { - const group = row().group + const group = row.group if (group.type !== "part") return return getMsgPart(group.ref.messageID, group.ref.partID) }) @@ -1027,8 +1012,8 @@ export function MessageTimeline(props: { ; children: JSX.Element }) { + function TimelineRowFrame(input: { row: FramedTimelineRow; children: JSX.Element }) { const anchor = () => { - const row = input.row() + const row = input.row return row._tag === "CommentStrip" || (row._tag === "UserMessage" && row.anchor) } const previousUserMessage = () => { - const row = input.row() + const row = input.row return (row._tag === "CommentStrip" || row._tag === "UserMessage") && row.previousUserMessage } const previousAssistantPart = () => { - const row = input.row() + const row = input.row return row._tag === "AssistantPart" && row.previousAssistantPart } return (
) => { - switch (row()._tag) { + const renderTimelineRow = (row: TimelineRow.TimelineRow) => { + switch (row._tag) { case "CommentStrip": { - const commentStripRow = row as Accessor> const comments = createMemo(() => - getMsgParts(commentStripRow().userMessageID).flatMap((part) => MessageComment.fromPart(part) ?? []), + getMsgParts(row.userMessageID).flatMap((part) => MessageComment.fromPart(part) ?? []), ) return ( - +
@@ -1118,22 +1102,17 @@ export function MessageTimeline(props: { ) } case "UserMessage": { - const userMessageRow = row as Accessor> const message = createMemo(() => { - const m = messageByID().get(userMessageRow().userMessageID) + const m = messageByID().get(row.userMessageID) if (m?.role === "user") return m }) return ( - + {(message) => (
- +
)} @@ -1142,14 +1121,13 @@ export function MessageTimeline(props: { ) } case "TurnDivider": { - const turnDividerRow = row as Accessor> return ( - +
@@ -1158,27 +1136,22 @@ export function MessageTimeline(props: { ) } case "AssistantPart": { - const assistantPartRow = row as Accessor> return ( - +
-
- {renderAssistantPartGroup(assistantPartRow)} +
+ {renderAssistantPartGroup(row)}
) } case "Thinking": { - const thinkingRow = row as Accessor> return ( - +
@@ -1186,32 +1159,29 @@ export function MessageTimeline(props: { ) } case "Retry": { - const retryRow = row as Accessor> return ( - +
- +
) } case "DiffSummary": { - const diffSummaryRow = row as Accessor> return ( - +
- +
) } case "Error": { - const errorRow = row as Accessor> return ( - +
- {errorRow().text} + {row.text}
@@ -1223,7 +1193,11 @@ export function MessageTimeline(props: { } function TimelineRowView(props: { rowKey: string }) { - return {(item) => renderTimelineRow(item)} + return ( + + {(item) => renderTimelineRow(item)} + + ) } return ( From 2932a7a35dc1af306177b7a53324c0cac978d2c5 Mon Sep 17 00:00:00 2001 From: Shawn Date: Tue, 19 May 2026 20:31:36 +0800 Subject: [PATCH 013/237] fix(app): invalidate provider queries after config update to show custom providers immediately (#28313) --- packages/app/src/context/global-sync.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 594f94fb6218..96c4526da65c 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -422,7 +422,13 @@ function createGlobalSync() { const updateConfigMutation = useMutation(() => ({ mutationFn: (config: Config) => globalSDK.client.global.config.update({ config }), - onSuccess: () => bootstrap.refetch(), + onSuccess: () => { + bootstrap.refetch() + // Invalidate all provider queries so newly configured custom providers + // appear immediately in the available provider list across all directories. + queryClient.invalidateQueries({ queryKey: [null, "providers"] }) + queryClient.invalidateQueries({ predicate: (query) => query.queryKey[1] === "providers" }) + }, })) return { From 18b9cec50d6b058cf991e6c29d643a7717d07196 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 10:58:12 -0400 Subject: [PATCH 014/237] test(cli): help-text snapshots for every CLI command (#28267) --- .../__snapshots__/help-snapshots.test.ts.snap | 623 ++++++++++++++++++ .../test/cli/help/help-snapshots.test.ts | 139 ++++ packages/opencode/test/lib/cli-process.ts | 83 ++- 3 files changed, 803 insertions(+), 42 deletions(-) create mode 100644 packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap create mode 100644 packages/opencode/test/cli/help/help-snapshots.test.ts diff --git a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap new file mode 100644 index 000000000000..22ad59aaa29d --- /dev/null +++ b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap @@ -0,0 +1,623 @@ +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode acp --help 1`] = ` +"opencode acp + +start ACP (Agent Client Protocol) server + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --port port to listen on [number] [default: 0] + --hostname hostname to listen on [string] [default: "127.0.0.1"] + --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) + [boolean] [default: false] + --mdns-domain custom domain name for mDNS service (default: opencode.local) + [string] [default: "opencode.local"] + --cors additional domains to allow for CORS [array] [default: []] + --cwd working directory [string] [default: ""]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode mcp --help 1`] = ` +"opencode mcp + +manage MCP (Model Context Protocol) servers + +Commands: + opencode mcp add add an MCP server + opencode mcp list list MCP servers and their status [aliases: ls] + opencode mcp auth [name] authenticate with an OAuth-enabled MCP server + opencode mcp logout [name] remove OAuth credentials for an MCP server + opencode mcp debug debug OAuth connection for an MCP server + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode attach --help 1`] = ` +"opencode attach + +attach to a running opencode server + +Positionals: + url http://localhost:4096 [string] [required] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --dir directory to run in [string] + -c, --continue continue the last session [boolean] + -s, --session session id to continue [string] + --fork fork the session when continuing (use with --continue or --session) [boolean] + -p, --password basic auth password (defaults to OPENCODE_SERVER_PASSWORD) [string] + -u, --username basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode')[string]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode run --help 1`] = ` +"opencode run [message..] + +run opencode with a message + +Positionals: + message message to send [array] [default: []] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --command the command to run, use message for args [string] + -c, --continue continue the last session [boolean] + -s, --session session id to continue [string] + --fork fork the session before continuing (requires --continue or + --session) [boolean] + --share share the session [boolean] + -m, --model model to use in the format of provider/model [string] + --agent agent to use [string] + --format format: default (formatted) or json (raw JSON events) + [string] [choices: "default", "json"] [default: "default"] + -f, --file file(s) to attach to message [array] + --title title for the session (uses truncated prompt if no value + provided) [string] + --attach attach to a running opencode server (e.g., + http://localhost:4096) [string] + -p, --password basic auth password (defaults to OPENCODE_SERVER_PASSWORD) + [string] + -u, --username basic auth username (defaults to OPENCODE_SERVER_USERNAME or + 'opencode') [string] + --dir directory to run in, path on remote server if attaching + [string] + --port port for the local server (defaults to random port if no value + provided) [number] + --variant model variant (provider-specific reasoning effort, e.g., high, + max, minimal) [string] + --thinking show thinking blocks [boolean] + --replay replay visible session history on interactive resume + [boolean] [default: false] + --replay-limit cap visible interactive replay to the newest N messages + [number] + -i, --interactive run in direct interactive split-footer mode + [boolean] [default: false] + --dangerously-skip-permissions auto-approve permissions that are not explicitly denied + (dangerous!) [boolean] [default: false] + --demo enable direct interactive demo slash commands; pass one as the + message to run it immediately [boolean] [default: false]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode debug --help 1`] = ` +"opencode debug + +debugging and troubleshooting tools + +Commands: + opencode debug config show resolved configuration + opencode debug lsp LSP debugging utilities + opencode debug rg ripgrep debugging utilities + opencode debug file file system debugging utilities + opencode debug scrap list all known projects + opencode debug skill list all available skills + opencode debug snapshot snapshot debugging utilities + opencode debug startup print startup timing + opencode debug agent show agent configuration details + opencode debug v2 debug v2 catalog and built-in plugins + opencode debug info show debug information + opencode debug paths show global paths (data, config, cache, state) + opencode debug wait wait indefinitely (for debugging) + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode providers --help 1`] = ` +"opencode providers + +manage AI providers and credentials + +Commands: + opencode providers list list providers and credentials [aliases: ls] + opencode providers login [url] log in to a provider + opencode providers logout log out from a configured provider + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode agent --help 1`] = ` +"opencode agent + +manage agents + +Commands: + opencode agent create create a new agent + opencode agent list list all available agents + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode upgrade --help 1`] = ` +"opencode upgrade [target] + +upgrade opencode to the latest or a specific version + +Positionals: + target version to upgrade to, for ex '0.1.48' or 'v0.1.48' [string] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + -m, --method installation method to use + [string] [choices: "curl", "npm", "pnpm", "bun", "brew", "choco", "scoop"]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode uninstall --help 1`] = ` +"opencode uninstall + +uninstall opencode and remove all related files + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + -c, --keep-config keep configuration files [boolean] [default: false] + -d, --keep-data keep session data and snapshots [boolean] [default: false] + --dry-run show what would be removed without removing [boolean] [default: false] + -f, --force skip confirmation prompts [boolean] [default: false]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode serve --help 1`] = ` +"opencode serve + +starts a headless opencode server + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --port port to listen on [number] [default: 0] + --hostname hostname to listen on [string] [default: "127.0.0.1"] + --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) + [boolean] [default: false] + --mdns-domain custom domain name for mDNS service (default: opencode.local) + [string] [default: "opencode.local"] + --cors additional domains to allow for CORS [array] [default: []]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode web --help 1`] = ` +"opencode web + +start opencode server and open web interface + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --port port to listen on [number] [default: 0] + --hostname hostname to listen on [string] [default: "127.0.0.1"] + --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) + [boolean] [default: false] + --mdns-domain custom domain name for mDNS service (default: opencode.local) + [string] [default: "opencode.local"] + --cors additional domains to allow for CORS [array] [default: []]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode models --help 1`] = ` +"opencode models [provider] + +list all available models + +Positionals: + provider provider ID to filter models by [string] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --verbose use more verbose model output (includes metadata like costs) [boolean] + --refresh refresh the models cache from models.dev [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode stats --help 1`] = ` +"opencode stats + +show token usage and cost statistics + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --days show stats for the last N days (default: all time) [number] + --tools number of tools to show (default: all) [number] + --models show model statistics (default: hidden). Pass a number to show top N, otherwise + shows all + --project filter by project (default: all projects, empty string: current project)[string]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode export --help 1`] = ` +"opencode export [sessionID] + +export session data as JSON + +Positionals: + sessionID session id to export [string] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --sanitize redact sensitive transcript and file data [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode import --help 1`] = ` +"opencode import + +import session data from JSON file or URL + +Positionals: + file path to JSON file or share URL [string] [required] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode github --help 1`] = ` +"opencode github + +manage GitHub agent + +Commands: + opencode github install install the GitHub agent + opencode github run run the GitHub agent + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode pr --help 1`] = ` +"opencode pr + +fetch and checkout a GitHub PR branch, then run opencode + +Positionals: + number PR number to checkout [number] [required] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode session --help 1`] = ` +"opencode session + +manage sessions + +Commands: + opencode session list list sessions + opencode session delete delete a session + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode plugin --help 1`] = ` +"opencode plugin + +install plugin and update config + +Positionals: + module npm module name [string] [required] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + -g, --global install in global config [boolean] [default: false] + -f, --force replace existing plugin version [boolean] [default: false]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode db --help 1`] = ` +"opencode db + +database tools + +Commands: + opencode db [query] open an interactive sqlite3 shell or run a query [default] + opencode db path print the database path + opencode db migrate migrate JSON data to SQLite (merges with existing data) + +Positionals: + query SQL query to execute [string] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --format Output format [string] [choices: "json", "tsv"] [default: "tsv"]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode mcp list --help 1`] = ` +"opencode mcp list + +list MCP servers and their status + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode mcp add --help 1`] = ` +"opencode mcp add + +add an MCP server + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode mcp auth --help 1`] = ` +"opencode mcp auth [name] + +authenticate with an OAuth-enabled MCP server + +Commands: + opencode mcp auth list list OAuth-capable MCP servers and their auth status [aliases: ls] + +Positionals: + name name of the MCP server [string] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode mcp logout --help 1`] = ` +"opencode mcp logout [name] + +remove OAuth credentials for an MCP server + +Positionals: + name name of the MCP server [string] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode providers list --help 1`] = ` +"opencode providers list + +list providers and credentials + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode providers login --help 1`] = ` +"opencode providers login [url] + +log in to a provider + +Positionals: + url opencode auth provider [string] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + -p, --provider provider id or name to log in to (skips provider selection) [string] + -m, --method login method label (skips method selection) [string]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode providers logout --help 1`] = ` +"opencode providers logout + +log out from a configured provider + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode agent create --help 1`] = ` +"opencode agent create + +create a new agent + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --path directory path to generate the agent file [string] + --description what the agent should do [string] + --mode agent mode [string] [choices: "all", "primary", "subagent"] + --permissions, --tools comma-separated list of permissions to allow (default: all). + Available: "bash, read, edit, glob, grep, webfetch, task, todowrite, + websearch, lsp, skill" [string] + -m, --model model to use in the format of provider/model [string]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode agent list --help 1`] = ` +"opencode agent list + +list all available agents + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode session list --help 1`] = ` +"opencode session list + +list sessions + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + -n, --max-count limit to N most recent sessions [number] + --format output format [string] [choices: "table", "json"] [default: "table"]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode session delete --help 1`] = ` +"opencode session delete + +delete a session + +Positionals: + sessionID session ID to delete [string] [required] + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode github install --help 1`] = ` +"opencode github install + +install the GitHub agent + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode github run --help 1`] = ` +"opencode github run + +run the GitHub agent + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean] + --event GitHub mock event to run the agent for [string] + --token GitHub personal access token (github_pat_********) [string]" +`; + +exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode db path --help 1`] = ` +"opencode db path + +print the database path + +Options: + -h, --help show help [boolean] + -v, --version show version number [boolean] + --print-logs print logs to stderr [boolean] + --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] + --pure run without external plugins [boolean]" +`; diff --git a/packages/opencode/test/cli/help/help-snapshots.test.ts b/packages/opencode/test/cli/help/help-snapshots.test.ts new file mode 100644 index 000000000000..e9b1bb6413ca --- /dev/null +++ b/packages/opencode/test/cli/help/help-snapshots.test.ts @@ -0,0 +1,139 @@ +// Help-text snapshots for every CLI command + key subcommand. Catches +// accidental flag removals, renames, and reordering in a single sweep — +// any change to the user-visible CLI surface shows up here as a diff. +// +// This is the broad coverage layer that makes the future Effect CLI +// migration (yargs → effect-smol/cli) safe to attempt: if a refactor +// preserves the surface, the snapshots stay green; if it doesn't, the +// diff tells you exactly which command(s) changed. +// +// Snapshots are taken at COLUMNS=120 so wrapping is stable across +// terminal sizes. The default opencode tui command is excluded — +// `opencode --help` includes an ASCII banner that pulls in the install +// version (changes per release), so we'd snapshot a moving target. +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import fs from "node:fs" +import os from "node:os" +import { cliIt } from "../../lib/cli-process" + +// Strips dynamic content that varies per run so snapshots are stable. +// Currently only the tmpdir prefix bleeds in (via `--cwd` defaults that +// resolve to `process.cwd()`). Add new patterns here as they surface. +// +// On macOS `os.tmpdir()` returns `/var/folders/...` but `process.cwd()` +// inside the child returns the realpath `/private/var/folders/...` — so +// we strip both forms. +const TMP = os.tmpdir() +const REAL_TMP = fs.realpathSync(TMP) +function normalize(text: string): string { + return ( + text + // Windows emits CRLF on stderr; collapse first so the rest of the + // pipeline doesn't need separate Windows-vs-POSIX branches. + .replaceAll("\r\n", "\n") + .replaceAll(REAL_TMP, "") + .replaceAll(TMP, "") + // The harness writes the random home dir at `/oc-cli-XXX` on + // POSIX, `\oc-cli-XXX` on Windows. Strip either form. + .replace(/[/\\]oc-cli-[a-z0-9]+/g, "") + // yargs wraps the `[string] [default: "..."]` clause based on the + // pre-normalized default's character length, so different random home + // path widths produce different leading-whitespace counts (or even + // line-wraps onto a fresh line on Windows). `\s+` matches both forms. + .replace(/\s+\[string\] \[default: ""\]/g, ' [string] [default: ""]') + ) +} + +// Top-level commands. Order matches what `opencode --help` prints today; +// keep it in that order so the snapshot file reads as a table of contents. +// `completion` is intentionally excluded — it's a yargs built-in that emits +// top-level help on `--help` and exits 1; not a real opencode command. +const TOP_LEVEL = [ + "acp", + "mcp", + "attach", + "run", + "debug", + "providers", // aliased to `auth` + "agent", + "upgrade", + "uninstall", + "serve", + "web", + "models", + "stats", + "export", + "import", + "github", + "pr", + "session", + "plugin", + "db", +] as const + +// Subcommands worth pinning. Not exhaustive — the goal is one snapshot per +// distinct argv shape, not every leaf. Add new entries when a subcommand +// gains user-visible flags that we want to lock in. +const SUBCOMMANDS = [ + ["mcp", "list"], + ["mcp", "add"], + ["mcp", "auth"], + ["mcp", "logout"], + ["providers", "list"], + ["providers", "login"], + ["providers", "logout"], + ["agent", "create"], + ["agent", "list"], + ["session", "list"], + ["session", "delete"], + ["github", "install"], + ["github", "run"], + ["db", "path"], +] as const + +// Fixed wrap width so a developer's terminal doesn't affect snapshots. +// yargs honors COLUMNS; CI runners typically default to 80 which produces +// different wraps from a 200-col local terminal. +const SNAPSHOT_ENV = { COLUMNS: "120" } + +describe("opencode CLI help-text snapshots", () => { + // Single test, parallel spawns. Each command's help fires under + // `concurrency: 8` — wall-clock stays under ~10s even for ~35 commands, + // versus ~1 minute if we serialized. + cliIt.live( + "every documented command emits stable help text", + ({ opencode }) => + Effect.gen(function* () { + const argvs: Array = [...TOP_LEVEL.map((c) => [c] as const), ...SUBCOMMANDS] + + // Spawn in parallel, then assert in argv order so snapshot output is + // deterministic and per-command failures don't abort the rest of + // the sweep. `Effect.partition` is the canonical "run all, separate + // failures from successes" primitive — no mutable accumulator needed. + const [failures, results] = yield* Effect.partition( + argvs, + (argv) => + Effect.gen(function* () { + const result = yield* opencode.spawn([...argv, "--help"], { env: SNAPSHOT_ENV }) + if (result.exitCode !== 0) { + return yield* Effect.fail(`opencode ${argv.join(" ")}: exit ${result.exitCode}`) + } + return { argv, result } + }), + { concurrency: 8 }, + ) + + for (const { argv, result } of results) { + // yargs writes --help to stderr, not stdout. Snapshotting stderr + // means our test catches the help body; stdout for these commands + // is expected to be empty. + expect(normalize(result.stderr)).toMatchSnapshot(`opencode ${argv.join(" ")} --help`) + } + if (failures.length > 0) { + throw new Error(`Help text failed for:\n ${failures.join("\n ")}`) + } + }), + 180_000, + ) +}) diff --git a/packages/opencode/test/lib/cli-process.ts b/packages/opencode/test/lib/cli-process.ts index 8481214a82f7..1f11bb4e794b 100644 --- a/packages/opencode/test/lib/cli-process.ts +++ b/packages/opencode/test/lib/cli-process.ts @@ -33,6 +33,30 @@ const cliEntry = path.join(opencodeRoot, "src/index.ts") export const testModelID = "test/test-model" +// Wrap a Bun subprocess pipe (or any ReadableStream) as a Stream. +// Centralizes the `evaluate` + `onError` boilerplate and tags errors with the +// stream name so a stderr/stdout failure is greppable in logs. +function fromBunStream(name: string, get: () => ReadableStream) { + return Stream.fromReadableStream({ + evaluate: get, + onError: (cause) => new Error(`${name} stream error: ${String(cause)}`), + }) +} + +// Long-lived processes (serve, acp) all want the same stderr drain: read every +// chunk, push to a tail buffer, swallow stream errors (the child closing the +// pipe is normal). `log: true` surfaces a real protocol error to logs so a +// regression doesn't silently disappear. +function forkStderrDrain(stream: ReadableStream, into: string[]) { + return Effect.forkScoped( + fromBunStream("stderr", () => stream).pipe( + Stream.decodeText(), + Stream.runForEach((chunk) => Effect.sync(() => into.push(chunk))), + Effect.ignore({ log: true }), + ), + ) +} + function isolatedEnv(home: string, configJson: string): Record { return { OPENCODE_TEST_HOME: home, @@ -225,20 +249,10 @@ export function withCliFixture( }).pipe(Effect.ignore), ) - // Drain stderr in a scope-bound fork. Without this the OS pipe buffer - // eventually fills and the child blocks on its next log call. Kept as a - // tail buffer so timeout failures can include context. + // Tail buffer so timeout failures can include stderr context. The fork + // also keeps the OS pipe buffer from filling and wedging the child. const stderrChunks: string[] = [] - yield* Effect.forkScoped( - Stream.fromReadableStream({ - evaluate: () => proc.stderr, - onError: () => new Error("stderr stream error"), - }).pipe( - Stream.decodeText(), - Stream.runForEach((chunk) => Effect.sync(() => stderrChunks.push(chunk))), - Effect.ignore, - ), - ) + yield* forkStderrDrain(proc.stderr, stderrChunks) // Watch stdout line-by-line for the listening sentinel. Format // (see src/cli/cmd/serve.ts): @@ -246,17 +260,14 @@ export function withCliFixture( const readyRe = /listening on (http:\/\/([^\s:]+):(\d+))/ const readyDeferred = yield* Deferred.make<{ url: string; hostname: string; port: number }>() yield* Effect.forkScoped( - Stream.fromReadableStream({ - evaluate: () => proc.stdout, - onError: () => new Error("stdout stream error"), - }).pipe( + fromBunStream("stdout", () => proc.stdout).pipe( Stream.decodeText(), Stream.splitLines, Stream.runForEach((line) => { const m = line.match(readyRe) return m ? Deferred.succeed(readyDeferred, { url: m[1], hostname: m[2], port: Number(m[3]) }) : Effect.void }), - Effect.ignore, + Effect.ignore({ log: true }), ), ) @@ -323,26 +334,14 @@ export function withCliFixture( ) const stderrChunks: string[] = [] - yield* Effect.forkScoped( - Stream.fromReadableStream({ - evaluate: () => proc.stderr, - onError: () => new Error("stderr stream error"), - }).pipe( - Stream.decodeText(), - Stream.runForEach((chunk) => Effect.sync(() => stderrChunks.push(chunk))), - Effect.ignore, - ), - ) + yield* forkStderrDrain(proc.stderr, stderrChunks) // Each ndjson line becomes one queue entry. JSON.parse failures are // surfaced as the raw string so a malformed protocol message doesn't // silently wedge the test in `receive`. const responses = yield* Queue.unbounded() yield* Effect.forkScoped( - Stream.fromReadableStream({ - evaluate: () => proc.stdout, - onError: () => new Error("stdout stream error"), - }).pipe( + fromBunStream("stdout", () => proc.stdout).pipe( Stream.decodeText(), Stream.splitLines, Stream.runForEach((line) => { @@ -355,23 +354,23 @@ export function withCliFixture( } return Queue.offer(responses, parsed) }), - Effect.ignore, + Effect.ignore({ log: true }), ), ) return { + // `proc.stdin.write` returns `number | Promise`. The promise + // form is the backpressure signal — if we don't await it, rapid + // successive sends can interleave under pipe-buffer-full conditions + // and corrupt the ndjson framing. send: (msg: object) => - Effect.sync(() => { - proc.stdin.write(JSON.stringify(msg) + "\n") + Effect.promise(async () => { + const ret = proc.stdin.write(JSON.stringify(msg) + "\n") + if (typeof ret !== "number") await ret }), receive: Queue.take(responses), - close: () => { - try { - proc.stdin.end() - } catch { - // already closed - } - }, + // proc.stdin.end() is idempotent in Bun; no try/catch needed. + close: () => proc.stdin.end(), exited: proc.exited as Promise, } satisfies AcpHandle }) From 8dd6448c90d6f21b77e4454b57acf7bd152bd557 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 19 May 2026 17:07:26 +0200 Subject: [PATCH 015/237] use keymap state for layer visibility (#26246) --- packages/opencode/specs/tui-plugins.md | 63 +++++++ packages/opencode/src/cli/cmd/tui/app.tsx | 43 ++--- .../cli/cmd/tui/component/command-palette.tsx | 80 +++++++++ .../cmd/tui/component/prompt/autocomplete.tsx | 17 +- .../cli/cmd/tui/component/prompt/index.tsx | 12 +- .../cli/cmd/tui/context/command-palette.tsx | 163 ------------------ packages/opencode/src/cli/cmd/tui/keymap.tsx | 117 ++++++++++++- .../opencode/src/cli/cmd/tui/plugin/api.tsx | 8 + .../src/cli/cmd/tui/plugin/runtime.ts | 12 ++ .../src/cli/cmd/tui/routes/session/index.tsx | 10 +- .../cli/cmd/tui/routes/session/permission.tsx | 9 +- .../cli/cmd/tui/routes/session/question.tsx | 9 +- .../tui/routes/session/subagent-footer.tsx | 11 +- .../opencode/src/cli/cmd/tui/ui/dialog.tsx | 11 +- .../test/cli/tui/plugin-toggle.test.ts | 64 +++++++ packages/opencode/test/fixture/tui-plugin.ts | 5 + packages/plugin/src/tui.ts | 6 + 17 files changed, 418 insertions(+), 222 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/component/command-palette.tsx delete mode 100644 packages/opencode/src/cli/cmd/tui/context/command-palette.tsx diff --git a/packages/opencode/specs/tui-plugins.md b/packages/opencode/specs/tui-plugins.md index e9ece6c0a22b..06f7757d5137 100644 --- a/packages/opencode/specs/tui-plugins.md +++ b/packages/opencode/specs/tui-plugins.md @@ -230,6 +230,7 @@ Top-level API groups exposed to `tui(api, options, meta)`: - `api.attention.notify(input)` - `api.keys.formatSequence(parts)`, `formatBindings(bindings)` - `api.keymap` +- `api.mode.current()`, `api.mode.push(mode)` - `api.route.register(routes)` / `api.route.navigate(name, params?)` / `api.route.current` - `api.ui.Dialog`, `DialogAlert`, `DialogConfirm`, `DialogPrompt`, `DialogSelect`, `Slot`, `Prompt`, `ui.toast`, `ui.dialog` - `api.tuiConfig` @@ -255,6 +256,68 @@ Top-level API groups exposed to `tui(api, options, meta)`: - Disposers returned by `api.keymap` registrations and `acquireResource(...)` are automatically cleaned up when the plugin deactivates. You do not need to add those disposers to `api.lifecycle.onDispose(...)` yourself. - Built-in which-key shortcuts are resolved from flat `keybinds` command ids such as `which_key_toggle`, not plugin options. +#### Mode-aware layers + +OpenCode registers a `mode` layer field on the host keymap. Plugins can use it to keep bindings active only in the relevant UI state. + +Built-in modes: + +- `base`: normal app, route, and prompt interaction. +- `modal`: host dialog stack is open, including dialogs rendered through `api.ui.dialog` and `api.ui.Dialog*` components. +- `autocomplete`: host prompt autocomplete is open. +- `api.mode.current()` returns the active top mode, or `base` when no pushed mode is active. + +Example: register a command and shortcut that are active only in normal app mode: + +```tsx +api.keymap.registerLayer({ + mode: "base", + commands: [ + { + name: "demo.open", + title: "Demo", + category: "Plugin", + namespace: "palette", + run() { + api.route.navigate("demo") + }, + }, + ], + bindings: [{ key: "ctrl+shift+m", cmd: "demo.open", desc: "Open demo" }], +}) +``` + +Layers without `mode` are not mode-gated and can remain active while dialogs or autocomplete are open. Use that only for intentionally global commands or low-level keymap extensions. + +Plugins that own a full-screen route or modal-like UI can temporarily push a plugin-specific mode with `api.mode.push(...)`. Use a plugin-scoped mode name. The returned disposer pops that specific stack entry and is idempotent, so popping an older mode while a newer mode is on top leaves the newer mode active. + +```tsx +import { onCleanup } from "solid-js" + +api.route.register([ + { + name: "demo", + render: () => { + const popMode = api.mode.push("acme.demo") + onCleanup(popMode) + + return ( + + demo + + ) + }, + }, +]) + +api.keymap.registerLayer({ + mode: "acme.demo", + bindings: [{ key: "escape", cmd: () => api.route.navigate("home"), desc: "Close demo" }], +}) +``` + +Mode pushes are automatically tracked by the plugin runtime. If a plugin is disabled, fails during activation, or the TUI shuts down before the plugin calls the disposer, OpenCode pops the plugin's pushed modes during plugin cleanup. Calling the disposer yourself is still recommended for component lifetimes; cleanup remains idempotent. + ### Keys - `api.keys` exposes host-formatted shortcut display helpers for plugin UI. diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 19a15a26ecea..e326a39b59fc 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -66,8 +66,15 @@ import { createTuiApi } from "@/cli/cmd/tui/plugin/api" import type { RouteMap } from "@/cli/cmd/tui/plugin/api" import { createTuiAttention } from "@/cli/cmd/tui/attention" import { FormatError, FormatUnknownError } from "@/cli/error" -import { CommandPaletteProvider, useCommandPalette } from "./context/command-palette" -import { OpencodeKeymapProvider, registerOpencodeKeymap, useBindings, useOpencodeKeymap } from "./keymap" +import { CommandPaletteDialog } from "./component/command-palette" +import { + COMMAND_PALETTE_COMMAND, + OPENCODE_BASE_MODE, + OpencodeKeymapProvider, + registerOpencodeKeymap, + useBindings, + useOpencodeKeymap, +} from "./keymap" import type { EventSource } from "./context/sdk" import { DialogVariant } from "./component/dialog-variant" @@ -227,17 +234,15 @@ export function tui(input: { - - - - - - - - - - - + + + + + + + + + @@ -267,7 +272,6 @@ function App(props: { onSnapshot?: () => Promise }) { const dialog = useDialog() const local = useLocal() const kv = useKV() - const command = useCommandPalette() const keymap = useOpencodeKeymap() const event = useEvent() const sdk = useSDK() @@ -446,12 +450,12 @@ function App(props: { onSnapshot?: () => Promise }) { const appCommands = createMemo(() => [ { - name: "command.palette.show", + name: COMMAND_PALETTE_COMMAND, title: "Show command palette", category: "System", hidden: true, run: () => { - command.show() + dialog.replace(() => ) }, }, { @@ -801,14 +805,13 @@ function App(props: { onSnapshot?: () => Promise }) { })) useBindings(() => ({ - enabled: command.matcher, + mode: OPENCODE_BASE_MODE, bindings: tuiConfig.keybinds.gather("app", appBindingCommands), })) useBindings(() => ({ + mode: OPENCODE_BASE_MODE, enabled: () => { - const ok = command.matcher.get() - if (!ok) return false const current = promptRef.current if (!current?.focused) return true return current.current.input === "" @@ -817,7 +820,7 @@ function App(props: { onSnapshot?: () => Promise }) { })) event.on(TuiEvent.CommandExecute.type, (evt) => { - command.run(evt.properties.command) + keymap.dispatchCommand(evt.properties.command) }) event.on(TuiEvent.ToastShow.type, (evt) => { diff --git a/packages/opencode/src/cli/cmd/tui/component/command-palette.tsx b/packages/opencode/src/cli/cmd/tui/component/command-palette.tsx new file mode 100644 index 000000000000..c54e67d4f271 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/command-palette.tsx @@ -0,0 +1,80 @@ +import { createMemo } from "solid-js" +import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select" +import { type DialogContext } from "@tui/ui/dialog" +import { + COMMAND_PALETTE_COMMAND, + formatKeyBindings, + type OpenTuiKeymap, + useKeymapSelector, + useOpencodeKeymap, +} from "../keymap" +import { useTuiConfig } from "../context/tui-config" + +type PaletteCommandEntry = ReturnType[number] + +function isVisiblePaletteCommand(command: PaletteCommandEntry["command"]) { + return command.hidden !== true && command.name !== COMMAND_PALETTE_COMMAND +} + +function isSuggestedPaletteCommand(entry: PaletteCommandEntry) { + const suggested = entry.command.suggested + if (typeof suggested === "boolean") return suggested + if (typeof suggested === "function") return suggested() === true + return false +} + +export function CommandPaletteDialog() { + const config = useTuiConfig() + const keymap = useOpencodeKeymap() + const entries = useKeymapSelector((keymap: OpenTuiKeymap) => { + const query = { + namespace: "palette", + } + const reachable = keymap + .getCommandEntries({ + ...query, + visibility: "reachable", + filter: isVisiblePaletteCommand, + }) + const registeredBindings = keymap.getCommandBindings({ + visibility: "registered", + commands: reachable.map((entry) => entry.command.name), + }) + + return reachable.map((entry) => ({ + ...entry, + bindings: registeredBindings.get(entry.command.name) ?? entry.bindings, + })) + }) + const options = createMemo(() => + entries().map((entry) => ({ + title: typeof entry.command.title === "string" ? entry.command.title : entry.command.name, + description: typeof entry.command.desc === "string" ? entry.command.desc : undefined, + category: typeof entry.command.category === "string" ? entry.command.category : undefined, + footer: formatKeyBindings(entry.bindings, config), + value: entry.command.name, + suggested: isSuggestedPaletteCommand(entry), + onSelect: (dialog: DialogContext) => { + dialog.clear() + keymap.dispatchCommand(entry.command.name) + }, + })), + ) + + let ref: DialogSelectRef + const list = () => { + if (ref?.filter) return options() + return [ + ...options() + .filter((option) => option.suggested) + .map((option) => ({ + ...option, + value: `suggested:${option.value}`, + category: "Suggested", + })), + ...options(), + ] + } + + return (ref = value)} title="Commands" options={list()} /> +} diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index db4d650d5226..2bda73cff76d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -13,12 +13,11 @@ import { getScrollAcceleration } from "../../util/scroll" import { useTuiConfig } from "../../context/tui-config" import { useTheme, selectedForeground } from "@tui/context/theme" import { SplitBorder } from "@tui/component/border" -import { useCommandPalette } from "../../context/command-palette" import { useTerminalDimensions } from "@opentui/solid" import { Locale } from "@/util/locale" import type { PromptInfo } from "./history" import { useFrecency } from "./frecency" -import { useBindings } from "../../keymap" +import { useBindings, useCommandSlashes, useOpencodeModeStack } from "../../keymap" import { Reference } from "@/reference/reference" import { ConfigReference } from "@/config/reference" import { displayCharAt, mentionTriggerIndex } from "@/cli/cmd/prompt-display" @@ -87,7 +86,8 @@ export function Autocomplete(props: { const sdk = useSDK() const sync = useSync() const project = useProject() - const command = useCommandPalette() + const slashes = useCommandSlashes() + const modeStack = useOpencodeModeStack() const { theme } = useTheme() const dimensions = useTerminalDimensions() const frecency = useFrecency() @@ -101,6 +101,12 @@ export function Autocomplete(props: { const [positionTick, setPositionTick] = createSignal(0) + createEffect(() => { + if (!store.visible) return + const popMode = modeStack.push("autocomplete") + onCleanup(popMode) + }) + createEffect(() => { if (store.visible) { let lastPos = { x: 0, y: 0, width: 0 } @@ -367,7 +373,6 @@ export function Autocomplete(props: { const { filename, part } = createFilePart(item, lineRange) const index = store.visible === "@" ? store.index : props.input().cursorOffset - command.suspend(false) setStore("visible", false) setStore("index", index) insertPart(filename, part) @@ -539,7 +544,7 @@ export function Autocomplete(props: { ) const commands = createMemo((): AutocompleteOption[] => { - const results: AutocompleteOption[] = [...command.slashes()] + const results: AutocompleteOption[] = [...slashes()] for (const serverCommand of sync.data.command) { if (serverCommand.source === "skill") continue @@ -730,7 +735,6 @@ export function Autocomplete(props: { })) function show(mode: "@" | "/") { - command.suspend(true) setStore({ visible: mode, index: props.input().cursorOffset, @@ -747,7 +751,6 @@ export function Autocomplete(props: { draft.input = props.input().plainText }) } - command.suspend(false) setStore("visible", false) } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index eb93a75ddc27..f6724c758252 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -59,8 +59,13 @@ import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable" import { useArgs } from "@tui/context/args" import { Flag } from "@opencode-ai/core/flag/flag" import { type WorkspaceStatus } from "../workspace-label" -import { useCommandPalette } from "../../context/command-palette" -import { useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap" +import { + OPENCODE_BASE_MODE, + useBindings, + useCommandShortcut, + useLeaderActive, + useOpencodeKeymap, +} from "../../keymap" import { useTuiConfig } from "../../context/tui-config" export type PromptProps = { @@ -152,7 +157,6 @@ export function Prompt(props: PromptProps) { const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" }) const history = usePromptHistory() const stash = usePromptStash() - const command = useCommandPalette() const keymap = useOpencodeKeymap() const agentShortcut = useCommandShortcut("agent.cycle") const paletteShortcut = useCommandShortcut("command.palette.show") @@ -629,7 +633,7 @@ export function Prompt(props: PromptProps) { })) useBindings(() => ({ - enabled: command.matcher, + mode: OPENCODE_BASE_MODE, bindings: tuiConfig.keybinds.gather("prompt.palette", [ "prompt.submit", "prompt.editor", diff --git a/packages/opencode/src/cli/cmd/tui/context/command-palette.tsx b/packages/opencode/src/cli/cmd/tui/context/command-palette.tsx deleted file mode 100644 index 07cca99074c9..000000000000 --- a/packages/opencode/src/cli/cmd/tui/context/command-palette.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { createContext, createMemo, createSignal, useContext, type Accessor, type ParentProps } from "solid-js" -import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select" -import { useDialog, type DialogContext } from "@tui/ui/dialog" -import { - formatKeyBindings, - reactiveMatcherFromSignal, - type OpenTuiKeymap, - useKeymapSelector, - useOpencodeKeymap, -} from "../keymap" -import { useTuiConfig } from "./tui-config" - -type SlashEntry = { - display: string - description?: string - aliases?: string[] - onSelect: () => void -} - -type CommandPaletteContext = { - run(command: string): void - show(): void - slashes: Accessor - suspend(enabled: boolean): void - readonly suspended: boolean - matcher: ReturnType -} - -const COMMAND_PALETTE_DIALOG = "command.palette.show" -const ctx = createContext() -type PaletteCommandEntry = ReturnType[number] - -function isVisiblePaletteCommand(entry: PaletteCommandEntry) { - return entry.command.hidden !== true && entry.command.name !== COMMAND_PALETTE_DIALOG -} - -function isSuggestedPaletteCommand(entry: PaletteCommandEntry) { - const suggested = entry.command.suggested - if (typeof suggested === "boolean") return suggested - if (typeof suggested === "function") return suggested() === true - return false -} - -export function CommandPaletteProvider(props: ParentProps) { - const dialog = useDialog() - const keymap = useOpencodeKeymap() - const [suspendCount, setSuspendCount] = createSignal(0) - const entries = useKeymapSelector((keymap: OpenTuiKeymap) => - keymap - .getCommandEntries({ - visibility: "reachable", - namespace: "palette", - }) - .filter(isVisiblePaletteCommand), - ) - - const run = (command: string) => { - keymap.dispatchCommand(command) - } - - const slashes = createMemo(() => - entries().flatMap((entry) => { - const slashName = entry.command.slashName - if (typeof slashName !== "string" || !slashName) return [] - const slashAliases = entry.command.slashAliases - return { - display: `/${slashName}`, - description: - typeof entry.command.desc === "string" - ? entry.command.desc - : typeof entry.command.title === "string" - ? entry.command.title - : undefined, - aliases: Array.isArray(slashAliases) - ? slashAliases.filter((alias): alias is string => typeof alias === "string").map((alias) => `/${alias}`) - : undefined, - onSelect: () => run(entry.command.name), - } - }), - ) - - const value: CommandPaletteContext = { - run, - show() { - dialog.replace(() => ) - }, - slashes, - suspend(enabled: boolean) { - setSuspendCount((count) => Math.max(0, count + (enabled ? 1 : -1))) - }, - get suspended() { - return suspendCount() > 0 || dialog.stack.length > 0 - }, - matcher: reactiveMatcherFromSignal(() => suspendCount() === 0 && dialog.stack.length === 0), - } - - return {props.children} -} - -export function useCommandPalette() { - const value = useContext(ctx) - if (!value) throw new Error("CommandPalette context must be used within a CommandPaletteProvider") - return value -} - -function CommandPaletteDialog(props: { run(command: string): void }) { - const config = useTuiConfig() - const entries = useKeymapSelector((keymap: OpenTuiKeymap) => { - const query = { - namespace: "palette", - } - const reachable = keymap - .getCommandEntries({ - ...query, - visibility: "reachable", - }) - .filter(isVisiblePaletteCommand) - const registeredBindings = keymap.getCommandBindings({ - visibility: "registered", - commands: reachable.map((entry) => entry.command.name), - }) - - return reachable.map((entry) => ({ - ...entry, - bindings: registeredBindings.get(entry.command.name) ?? entry.bindings, - })) - }) - const options = createMemo(() => - entries().map((entry) => ({ - title: typeof entry.command.title === "string" ? entry.command.title : entry.command.name, - description: typeof entry.command.desc === "string" ? entry.command.desc : undefined, - category: typeof entry.command.category === "string" ? entry.command.category : undefined, - footer: formatKeyBindings(entry.bindings, config), - value: entry.command.name, - suggested: isSuggestedPaletteCommand(entry), - onSelect: (dialog: DialogContext) => { - dialog.clear() - props.run(entry.command.name) - }, - })), - ) - - let ref: DialogSelectRef - const list = () => { - if (ref?.filter) return options() - return [ - ...options() - .filter((option) => option.suggested) - .map((option) => ({ - ...option, - value: `suggested:${option.value}`, - category: "Suggested", - })), - ...options(), - ] - } - - return (ref = value)} title="Commands" options={list()} /> -} - -export function useCommandSlashes(): Accessor { - return useCommandPalette().slashes -} diff --git a/packages/opencode/src/cli/cmd/tui/keymap.tsx b/packages/opencode/src/cli/cmd/tui/keymap.tsx index 41a7b08612dc..6da518a0ba50 100644 --- a/packages/opencode/src/cli/cmd/tui/keymap.tsx +++ b/packages/opencode/src/cli/cmd/tui/keymap.tsx @@ -7,24 +7,100 @@ import { } from "@opentui/keymap/extras" import { KeymapProvider, - reactiveMatcherFromSignal, useKeymap, useKeymapSelector, useBindings, } from "@opentui/keymap/solid" -import type { Accessor } from "solid-js" +import { createMemo, type Accessor } from "solid-js" import type { TuiConfig } from "./config/tui" import { useTuiConfig } from "./context/tui-config" import { TuiKeybind } from "./config/keybind" export const LEADER_TOKEN = "leader" +export const OPENCODE_BASE_MODE = "base" +export const COMMAND_PALETTE_COMMAND = "command.palette.show" + +const OPENCODE_MODE_KEY = "opencode.mode" export const OpencodeKeymapProvider = KeymapProvider export const useOpencodeKeymap = useKeymap -export { reactiveMatcherFromSignal, useBindings, useKeymapSelector } +export { useBindings, useKeymapSelector } export type OpenTuiKeymap = ReturnType +type OpencodeModeStack = ReturnType +type CommandSlashEntry = { + display: string + description?: string + aliases?: string[] + onSelect: () => void +} +type Command = ReturnType[number] + +const modeStacks = new WeakMap() + +function isVisiblePaletteCommand(command: Command) { + return command.hidden !== true && command.name !== COMMAND_PALETTE_COMMAND +} + +export function createOpencodeModeStack(keymap: OpenTuiKeymap) { + keymap.setData(OPENCODE_MODE_KEY, OPENCODE_BASE_MODE) + + const offFields = keymap.registerLayerFields({ + mode(value, ctx) { + ctx.require(OPENCODE_MODE_KEY, value) + }, + }) + + const stack: { id: symbol; mode: string }[] = [] + let disposed = false + + const update = () => { + keymap.setData(OPENCODE_MODE_KEY, stack.at(-1)?.mode ?? OPENCODE_BASE_MODE) + } + + const stackApi = { + current() { + return stack.at(-1)?.mode ?? OPENCODE_BASE_MODE + }, + push(mode: string) { + if (disposed) return () => {} + const id = Symbol(mode) + let active = true + stack.push({ id, mode }) + update() + + return () => { + if (!active) return + active = false + const index = stack.findIndex((item) => item.id === id) + if (index !== -1) stack.splice(index, 1) + update() + } + }, + dispose() { + if (disposed) return + disposed = true + stack.length = 0 + offFields() + keymap.setData(OPENCODE_MODE_KEY, undefined) + modeStacks.delete(keymap) + }, + } + + modeStacks.set(keymap, stackApi) + return stackApi +} + +export function useOpencodeModeStack() { + return getOpencodeModeStack(useOpencodeKeymap()) +} + +export function getOpencodeModeStack(keymap: OpenTuiKeymap) { + const value = modeStacks.get(keymap) + if (!value) throw new Error("Opencode mode stack is not registered for this keymap") + return value +} const KEY_ALIASES = { enter: "return", @@ -127,6 +203,7 @@ export function registerOpencodeKeymap( renderer: CliRenderer, config: Pick, ) { + const modeStack = createOpencodeModeStack(keymap) const offCommaBindings = addons.registerCommaBindings(keymap) const offAliasExpander = registerKeyAliases(keymap) const offBaseLayout = addons.registerBaseLayoutFallback(keymap) @@ -150,6 +227,7 @@ export function registerOpencodeKeymap( offAliasExpander() offBaseLayout() offCommaBindings() + modeStack.dispose() } } @@ -166,3 +244,36 @@ export function useCommandShortcut(command: string): Accessor { export function useLeaderActive(): Accessor { return useKeymapSelector((keymap: OpenTuiKeymap) => keymap.getPendingSequence()[0]?.tokenName === LEADER_TOKEN) } + +export function useCommandSlashes(): Accessor { + const keymap = useOpencodeKeymap() + const entries = useKeymapSelector((keymap: OpenTuiKeymap) => + keymap + .getCommandEntries({ + visibility: "reachable", + namespace: "palette", + filter: isVisiblePaletteCommand, + }) + ) + + return createMemo(() => + entries().flatMap((entry) => { + const slashName = entry.command.slashName + if (typeof slashName !== "string" || !slashName) return [] + const slashAliases = entry.command.slashAliases + return { + display: `/${slashName}`, + description: + typeof entry.command.desc === "string" + ? entry.command.desc + : typeof entry.command.title === "string" + ? entry.command.title + : undefined, + aliases: Array.isArray(slashAliases) + ? slashAliases.filter((alias): alias is string => typeof alias === "string").map((alias) => `/${alias}`) + : undefined, + onSelect: () => keymap.dispatchCommand(entry.command.name), + } + }), + ) +} diff --git a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx index 05bfa31d1415..a704286835aa 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx +++ b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx @@ -219,6 +219,14 @@ export function createTuiApi(input: Input): TuiPluginApi { }, }, keymap: input.keymap, + mode: { + current() { + return Keymap.getOpencodeModeStack(input.keymap).current() + }, + push(mode) { + return Keymap.getOpencodeModeStack(input.keymap).push(mode) + }, + }, route: { register(list) { return routeRegister(input.routes, list, input.bump) diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index 62f04c9707b2..515e6175631f 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -192,6 +192,17 @@ function createScopedAttention( } } +function createScopedMode(mode: TuiPluginApi["mode"], scope: PluginScope): TuiPluginApi["mode"] { + return { + current() { + return mode.current() + }, + push(value) { + return scope.track(mode.push(value)) + }, + } +} + type CleanupResult = { type: "ok" } | { type: "error"; error: unknown } | { type: "timeout" } function runCleanup(fn: () => unknown, ms: number): Promise { @@ -616,6 +627,7 @@ function pluginApi(runtime: RuntimeState, plugin: PluginEntry, scope: PluginScop command: createCommandShim(keymap, api.ui.dialog, api.tuiConfig.keybinds), keys: api.keys, keymap, + mode: createScopedMode(api.mode, scope), route, ui: api.ui, tuiConfig: api.tuiConfig, diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index e8e29a40c960..3f2e902bdf89 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -89,8 +89,7 @@ import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime" import { DialogRetryAction } from "../../component/dialog-retry-action" import { SessionRetry } from "@/session/retry" import { getRevertDiffFiles } from "../../util/revert-diff" -import { useCommandPalette } from "../../context/command-palette" -import { useBindings, useCommandShortcut } from "../../keymap" +import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut, useOpencodeKeymap } from "../../keymap" import { PathFormatterProvider, usePathFormatter } from "../../context/path-format" addDefaultParsers(parsers.parsers) @@ -311,7 +310,7 @@ export function Session() { seeded = true r.set(route.prompt) } - const command = useCommandPalette() + const keymap = useOpencodeKeymap() const dialog = useDialog() const renderer = useRenderer() @@ -1056,7 +1055,7 @@ export function Session() { })) useBindings(() => ({ - enabled: command.matcher, + mode: OPENCODE_BASE_MODE, bindings: tuiConfig.keybinds.gather("session", sessionBindingCommands), })) @@ -1133,7 +1132,6 @@ export function Session() { {(function () { - const command = useCommandPalette() const redoShortcut = useCommandShortcut("session.redo") const [hover, setHover] = createSignal(false) const dialog = useDialog() @@ -1145,7 +1143,7 @@ export function Session() { "Are you sure you want to restore the reverted messages?", ) if (confirmed) { - command.run("session.redo") + keymap.dispatchCommand("session.redo") } } diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index 5b40c3c318fa..ac8c45a6ee65 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -13,10 +13,9 @@ import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import { Locale } from "@/util/locale" import { ShellID } from "@/tool/shell/id" import { webSearchProviderLabel } from "@/tool/websearch" -import { useDialog } from "../../ui/dialog" import { getScrollAcceleration } from "../../util/scroll" import { useTuiConfig } from "../../context/tui-config" -import { useBindings, useCommandShortcut } from "../../keymap" +import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut } from "../../keymap" import { usePathFormatter } from "../../context/path-format" type PermissionStage = "permission" | "always" | "reject" @@ -448,9 +447,8 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: ( const tuiConfig = useTuiConfig() const dimensions = useTerminalDimensions() const narrow = createMemo(() => dimensions().width < 80) - const dialog = useDialog() useBindings(() => ({ - enabled: dialog.stack.length === 0, + mode: OPENCODE_BASE_MODE, commands: [ { name: "app.exit", @@ -542,11 +540,10 @@ function Prompt>(props: { expanded: false, }) const narrow = createMemo(() => dimensions().width < 80) - const dialog = useDialog() const fullscreenHint = useCommandShortcut("permission.prompt.fullscreen") useBindings(() => ({ - enabled: dialog.stack.length === 0, + mode: OPENCODE_BASE_MODE, commands: [ { name: "app.exit", diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx index 46fc220bdc63..ba38e34eae5c 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx @@ -6,9 +6,8 @@ import { selectedForeground, tint, useTheme } from "../../context/theme" import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2" import { useSDK } from "../../context/sdk" import { SplitBorder } from "../../component/border" -import { useDialog } from "../../ui/dialog" import { useTuiConfig } from "../../context/tui-config" -import { useBindings } from "../../keymap" +import { OPENCODE_BASE_MODE, useBindings } from "../../keymap" export function QuestionPrompt(props: { request: QuestionRequest }) { const sdk = useSDK() @@ -120,9 +119,8 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { pick(opt.label) } - const dialog = useDialog() - useBindings(() => ({ + mode: OPENCODE_BASE_MODE, enabled: store.editing && !confirm(), commands: [ { @@ -203,7 +201,8 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { const max = Math.min(total, 9) return { - enabled: dialog.stack.length === 0 && !store.editing, + mode: OPENCODE_BASE_MODE, + enabled: !store.editing, commands: [ { name: "app.exit", diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx index f4a458b63dfe..e1be4286f12b 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/subagent-footer.tsx @@ -6,8 +6,7 @@ import { SplitBorder } from "@tui/component/border" import type { AssistantMessage } from "@opencode-ai/sdk/v2" import { Locale } from "@/util/locale" import { useTerminalDimensions } from "@opentui/solid" -import { useCommandPalette } from "../../context/command-palette" -import { useCommandShortcut } from "../../keymap" +import { useCommandShortcut, useOpencodeKeymap } from "../../keymap" export function SubagentFooter() { const route = useRouteData("session") @@ -56,7 +55,7 @@ export function SubagentFooter() { }) const { theme } = useTheme() - const command = useCommandPalette() + const keymap = useOpencodeKeymap() const parentShortcut = useCommandShortcut("session.parent") const previousShortcut = useCommandShortcut("session.child.previous") const nextShortcut = useCommandShortcut("session.child.next") @@ -98,7 +97,7 @@ export function SubagentFooter() { setHover("parent")} onMouseOut={() => setHover(null)} - onMouseUp={() => command.run("session.parent")} + onMouseUp={() => keymap.dispatchCommand("session.parent")} backgroundColor={hover() === "parent" ? theme.backgroundElement : theme.backgroundPanel} > @@ -108,7 +107,7 @@ export function SubagentFooter() { setHover("prev")} onMouseOut={() => setHover(null)} - onMouseUp={() => command.run("session.child.previous")} + onMouseUp={() => keymap.dispatchCommand("session.child.previous")} backgroundColor={hover() === "prev" ? theme.backgroundElement : theme.backgroundPanel} > @@ -118,7 +117,7 @@ export function SubagentFooter() { setHover("next")} onMouseOut={() => setHover(null)} - onMouseUp={() => command.run("session.child.next")} + onMouseUp={() => keymap.dispatchCommand("session.child.next")} backgroundColor={hover() === "next" ? theme.backgroundElement : theme.backgroundPanel} > diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx index 3c74ded5fd31..73d61c3ba66f 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx @@ -1,12 +1,12 @@ import { useRenderer, useTerminalDimensions } from "@opentui/solid" -import { batch, createContext, Show, useContext, type JSX, type ParentProps } from "solid-js" +import { batch, createContext, createEffect, onCleanup, Show, useContext, type JSX, type ParentProps } from "solid-js" import { useTheme } from "@tui/context/theme" import { MouseButton, Renderable, RGBA } from "@opentui/core" import { createStore } from "solid-js/store" import { useToast } from "./toast" import { Flag } from "@opencode-ai/core/flag/flag" import * as Selection from "@tui/util/selection" -import { useBindings } from "../keymap" +import { useBindings, useOpencodeModeStack } from "../keymap" export function Dialog( props: ParentProps<{ @@ -73,6 +73,13 @@ function init() { }) const renderer = useRenderer() + const modeStack = useOpencodeModeStack() + + createEffect(() => { + if (store.stack.length === 0) return + const popMode = modeStack.push("modal") + onCleanup(popMode) + }) let focus: Renderable | null function refocus() { diff --git a/packages/opencode/test/cli/tui/plugin-toggle.test.ts b/packages/opencode/test/cli/tui/plugin-toggle.test.ts index a3ee744bff53..902f0265c773 100644 --- a/packages/opencode/test/cli/tui/plugin-toggle.test.ts +++ b/packages/opencode/test/cli/tui/plugin-toggle.test.ts @@ -91,6 +91,70 @@ test("toggles plugin runtime state by exported id", async () => { } }) +test("deactivating plugin pops pushed mode", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const file = path.join(dir, "mode-plugin.ts") + const spec = pathToFileURL(file).href + + await Bun.write( + file, + `export default { + id: "demo.mode", + tui: async (api) => { + api.mode.push("demo.mode") + }, +} +`, + ) + + return { spec } + }, + }) + + const stack: { id: symbol; mode: string }[] = [] + let popCount = 0 + const api = createTuiPluginApi({ + mode: { + current: () => stack.at(-1)?.mode ?? "base", + push(mode) { + const id = Symbol(mode) + let active = true + stack.push({ id, mode }) + return () => { + if (!active) return + active = false + popCount += 1 + const index = stack.findIndex((item) => item.id === id) + if (index !== -1) stack.splice(index, 1) + } + }, + }, + }) + const config = createTuiResolvedConfig({ + plugin: [tmp.extra.spec], + plugin_origins: [{ spec: tmp.extra.spec, scope: "local", source: path.join(tmp.path, "tui.json") }], + }) + const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() + const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) + + try { + await TuiPluginRuntime.init({ api, config }) + + expect(api.mode.current()).toBe("demo.mode") + expect(popCount).toBe(0) + + await expect(TuiPluginRuntime.deactivatePlugin("demo.mode")).resolves.toBe(true) + + expect(api.mode.current()).toBe("base") + expect(popCount).toBe(1) + } finally { + await TuiPluginRuntime.dispose() + cwd.mockRestore() + wait.mockRestore() + } +}) + test("kv plugin_enabled overrides tui config on startup", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/opencode/test/fixture/tui-plugin.ts b/packages/opencode/test/fixture/tui-plugin.ts index f95025494559..ef18801571f0 100644 --- a/packages/opencode/test/fixture/tui-plugin.ts +++ b/packages/opencode/test/fixture/tui-plugin.ts @@ -89,6 +89,7 @@ type Opts = { renderer?: HostPluginApi["renderer"] attention?: AttentionOpts event?: HostPluginApi["event"] + mode?: HostPluginApi["mode"] count?: Count keymap?: HostPluginApi["keymap"] tuiConfig?: Partial @@ -237,6 +238,10 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi { }, }, keymap, + mode: opts.mode ?? { + current: () => "base", + push: () => () => {}, + }, route: { register: () => { if (count) count.route_add += 1 diff --git a/packages/plugin/src/tui.ts b/packages/plugin/src/tui.ts index 354e44abcd08..d78c607f8071 100644 --- a/packages/plugin/src/tui.ts +++ b/packages/plugin/src/tui.ts @@ -78,6 +78,11 @@ export type TuiKeys = { export type TuiKeymap = Keymap +export type TuiModeApi = { + current: () => string + push: (mode: string) => () => void +} + /** * Legacy `api.command` shape kept so v1 plugins can initialize. Remove in v2. * @@ -589,6 +594,7 @@ export type TuiPluginApi = { command?: TuiCommandApi keys: TuiKeys keymap: TuiKeymap + mode: TuiModeApi route: { register: (routes: TuiRouteDefinition[]) => () => void navigate: (name: string, params?: Record) => void From c79a9634d37dd5e7a0d7aedba45b150d04e25d0c Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 11:07:28 -0400 Subject: [PATCH 016/237] fix(tool): tolerate plugin tool defs with missing args (#28357) --- packages/opencode/src/tool/registry.ts | 7 +- packages/opencode/test/tool/registry.test.ts | 92 ++++++++++++++++++-- 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 2547daee6eba..6ef6d39a65a5 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -145,9 +145,12 @@ export const layer: Layer.Layer< function fromPlugin(id: string, def: ToolDefinition): Tool.Def { // Plugin tools still expose Zod args publicly; keep that compatibility // boxed at the registry boundary and give the LLM the original JSON Schema. - const entries = Object.entries(def.args) + // Normalize missing args to `{}` once — pre-1.14.49 the code was + // `z.object(def.args)` and Zod silently tolerated undefined (#27451, #27630). + const args = def.args ?? {} + const entries = Object.entries(args) const allZod = entries.every((entry) => isZodType(entry[1])) - const zodParams = allZod ? z.object(def.args) : undefined + const zodParams = allZod ? z.object(args) : undefined const jsonSchema = zodParams ? zodJsonSchema(zodParams) : legacyJsonSchema(entries) const parameters = zodParams ? Schema.declare((u): u is unknown => zodParams.safeParse(u).success) diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 261c4693720a..321ab12444cc 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -40,11 +40,16 @@ const configLayer = TestConfig.layer({ directories: () => InstanceState.directory.pipe(Effect.map((dir) => [path.join(dir, ".opencode")])), }) -const registryLayer = (flags: Partial = {}) => +type RegistryLayerOptions = { + flags?: Partial + plugin?: Layer.Layer +} + +const registryLayer = (opts: RegistryLayerOptions = {}) => ToolRegistry.layer .pipe( Layer.provide(configLayer), - Layer.provide(Plugin.defaultLayer), + Layer.provide(opts.plugin ?? Plugin.defaultLayer), Layer.provide(Question.defaultLayer), Layer.provide(Todo.defaultLayer), Layer.provide(Skill.defaultLayer), @@ -64,12 +69,38 @@ const registryLayer = (flags: Partial = {}) => Layer.provide(Ripgrep.defaultLayer), Layer.provide(Truncate.defaultLayer), ) - .pipe(Layer.provide(RuntimeFlags.layer(flags))) + .pipe(Layer.provide(RuntimeFlags.layer(opts.flags ?? {}))) + +// Fake Plugin.Service that returns a single plugin whose `tool` map contains +// one definition with `args: undefined`. Used to exercise the plugin entry +// point of `fromPlugin` for the #27451 / #27630 regression. +const brokenPluginLayer = Layer.succeed( + Plugin.Service, + Plugin.Service.of({ + init: () => Effect.void, + trigger: ((_name: unknown, _input: unknown, output: unknown) => Effect.succeed(output)) as Plugin.Interface["trigger"], + list: () => + Effect.succeed([ + { + tool: { + broken_plugin_tool: { + description: "plugin tool with missing args", + args: undefined as unknown as Record, + execute: async () => "ok", + }, + }, + }, + ]), + }), +) const it = testEffect(Layer.mergeAll(registryLayer(), node, Agent.defaultLayer)) -const scout = testEffect(Layer.mergeAll(registryLayer({ experimentalScout: true }), node, Agent.defaultLayer)) +const scout = testEffect(Layer.mergeAll(registryLayer({ flags: { experimentalScout: true } }), node, Agent.defaultLayer)) const background = testEffect( - Layer.mergeAll(registryLayer({ experimentalBackgroundSubagents: true }), node, Agent.defaultLayer), + Layer.mergeAll(registryLayer({ flags: { experimentalBackgroundSubagents: true } }), node, Agent.defaultLayer), +) +const withBrokenPlugin = testEffect( + Layer.mergeAll(registryLayer({ plugin: brokenPluginLayer }), node, Agent.defaultLayer), ) afterEach(async () => { @@ -186,6 +217,57 @@ describe("tool.registry", () => { }), ) + // Regression for #27451 / #27630: a custom tool that omits `args` must not + // crash registry initialization with + // `Object.entries requires that input parameter not be null or undefined`. + // Pre-1.14.49 the code path was `z.object(def.args)`, and `z.object(undefined)` + // silently produced an empty schema — so the tool registered as no-args. + // Preserve that tolerance. + it.instance("tolerates a custom tool exporting null/undefined args (no-args fallback)", () => + Effect.gen(function* () { + const test = yield* TestInstance + const tool = path.join(test.directory, ".opencode", "tool") + yield* Effect.promise(() => fs.mkdir(tool, { recursive: true })) + yield* Effect.promise(() => + Bun.write( + path.join(tool, "noargs.ts"), + [ + "export default {", + " description: 'tool with no args',", + " args: undefined,", + " execute: async () => 'ok',", + "}", + "", + ].join("\n"), + ), + ) + + const registry = yield* ToolRegistry.Service + const ids = yield* registry.ids() + // Built-in tools must still load — a single malformed custom tool must + // not poison the whole registry. + expect(ids).toContain("read") + const loaded = (yield* registry.all()).find((t) => t.id === "noargs") + if (!loaded) throw new Error("noargs tool was not loaded") + expect(loaded.jsonSchema).toMatchObject({ type: "object", properties: {} }) + }), + ) + + // Same regression, plugin entry point. The original reports (#27451, #27630) + // came in through `plugin.list()` — `oh-my-opencode` was registering a tool + // with `args: undefined` and crashing every message submit. The file-scan + // and plugin-list loops both funnel through `fromPlugin`, but covering both + // entry points means a future refactor that splits them won't silently lose + // protection. + withBrokenPlugin.instance("tolerates a plugin tool registered with null/undefined args", () => + Effect.gen(function* () { + const registry = yield* ToolRegistry.Service + const ids = yield* registry.ids() + expect(ids).toContain("read") + expect(ids).toContain("broken_plugin_tool") + }), + ) + it.instance("loads tools from .opencode/tools (plural)", () => Effect.gen(function* () { const test = yield* TestInstance From 55baa16fbc740d943423b8e145d1d791c184a6c4 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 11:09:49 -0400 Subject: [PATCH 017/237] test(lib): extract snapshot normalizer utility for cross-OS stability (#28356) --- .../test/cli/help/help-snapshots.test.ts | 42 ++++------- packages/opencode/test/lib/snapshot.ts | 73 +++++++++++++++++++ 2 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 packages/opencode/test/lib/snapshot.ts diff --git a/packages/opencode/test/cli/help/help-snapshots.test.ts b/packages/opencode/test/cli/help/help-snapshots.test.ts index e9b1bb6413ca..94ea803b2655 100644 --- a/packages/opencode/test/cli/help/help-snapshots.test.ts +++ b/packages/opencode/test/cli/help/help-snapshots.test.ts @@ -13,36 +13,26 @@ // version (changes per release), so we'd snapshot a moving target. import { describe, expect } from "bun:test" import { Effect } from "effect" -import fs from "node:fs" -import os from "node:os" import { cliIt } from "../../lib/cli-process" +import { normalizeForSnapshot, PATH_SEP } from "../../lib/snapshot" -// Strips dynamic content that varies per run so snapshots are stable. -// Currently only the tmpdir prefix bleeds in (via `--cwd` defaults that -// resolve to `process.cwd()`). Add new patterns here as they surface. +// Composes `normalizeForSnapshot` (CRLF + tmpdir) with two help-specific +// rules: // -// On macOS `os.tmpdir()` returns `/var/folders/...` but `process.cwd()` -// inside the child returns the realpath `/private/var/folders/...` — so -// we strip both forms. -const TMP = os.tmpdir() -const REAL_TMP = fs.realpathSync(TMP) +// 1. The harness's `oc-cli-XXX` subdir under TMPDIR collapses to ``. +// `PATH_SEP` matches `/` and `\\` so the rule works on POSIX + Windows. +// +// 2. yargs wraps the `[string] [default: "..."]` clause based on the +// pre-normalized default's character length, so different random home +// path widths produce different leading-whitespace counts (or even +// line-wraps onto a fresh line on Windows). `\s+` matches both forms. function normalize(text: string): string { - return ( - text - // Windows emits CRLF on stderr; collapse first so the rest of the - // pipeline doesn't need separate Windows-vs-POSIX branches. - .replaceAll("\r\n", "\n") - .replaceAll(REAL_TMP, "") - .replaceAll(TMP, "") - // The harness writes the random home dir at `/oc-cli-XXX` on - // POSIX, `\oc-cli-XXX` on Windows. Strip either form. - .replace(/[/\\]oc-cli-[a-z0-9]+/g, "") - // yargs wraps the `[string] [default: "..."]` clause based on the - // pre-normalized default's character length, so different random home - // path widths produce different leading-whitespace counts (or even - // line-wraps onto a fresh line on Windows). `\s+` matches both forms. - .replace(/\s+\[string\] \[default: ""\]/g, ' [string] [default: ""]') - ) + return normalizeForSnapshot(text, { + pathReplacements: [ + [new RegExp(`${PATH_SEP}oc-cli-[a-z0-9]+`, "g"), ""], + [/\s+\[string\] \[default: ""\]/g, ' [string] [default: ""]'], + ], + }) } // Top-level commands. Order matches what `opencode --help` prints today; diff --git a/packages/opencode/test/lib/snapshot.ts b/packages/opencode/test/lib/snapshot.ts new file mode 100644 index 000000000000..64fa31eb8e74 --- /dev/null +++ b/packages/opencode/test/lib/snapshot.ts @@ -0,0 +1,73 @@ +// Shared normalization helpers for cross-OS-stable snapshot tests. +// +// Every snapshot test that captures subprocess output, file paths, or other +// OS-flavored strings hits the same two issues: +// 1. Bun emits CRLF line endings on Windows stderr; LF elsewhere. +// 2. Path separators differ (\ on Windows, / on POSIX), and macOS's +// /var/folders symlink resolves to /private/var/folders. +// +// These helpers exist so each test doesn't reinvent the same regexes. +// +// Use individually for fine-grained control, or compose them via +// `normalizeForSnapshot` for the common "snapshot subprocess output" path. +import fs from "node:fs" +import os from "node:os" + +const TMP = os.tmpdir() +const REAL_TMP = fs.realpathSync(TMP) + +/** + * Collapses CRLF to LF. Bun's subprocess pipes emit native line endings — + * snapshots captured on macOS/Linux contain LF, so a Windows run without + * this step always diffs. + */ +export function stripCrlf(text: string): string { + return text.replaceAll("\r\n", "\n") +} + +/** + * Converts Windows-style `\` separators to POSIX `/` so paths render + * identically across OSes. Use for path strings you want stable in a + * snapshot, not for filesystem operations. + */ +export function toPosixPath(p: string): string { + return p.replaceAll("\\", "/") +} + +/** + * Strips both the OS-level `os.tmpdir()` and its realpath form (macOS + * `/var/folders` → `/private/var/folders`) from text, replacing each + * occurrence with `marker` (default ``). + */ +export function withTmpdirStripped(text: string, marker = ""): string { + return text.replaceAll(REAL_TMP, marker).replaceAll(TMP, marker) +} + +/** + * Separator-agnostic match class for path-style strings. Use inside a + * larger regex when you want to match both `/` (POSIX) and `\` (Windows) + * boundaries — e.g. `${PATH_SEP}oc-cli-[a-z0-9]+`. + */ +export const PATH_SEP = "[/\\\\]" + +/** + * One-shot normalization for the common case: strip CRLF, strip tmpdir, + * then apply any caller-supplied path regex substitutions. Does NOT + * blanket-replace `\` with `/` — that would mangle non-path backslash + * content (regex literals in help text, etc.). Use `toPosixPath` or + * `PATH_SEP` in your own regex when you need separator agnosticism. + */ +export function normalizeForSnapshot( + text: string, + options?: { + readonly tmpdirMarker?: string + readonly pathReplacements?: ReadonlyArray + }, +): string { + let out = stripCrlf(text) + out = withTmpdirStripped(out, options?.tmpdirMarker) + for (const [pattern, replacement] of options?.pathReplacements ?? []) { + out = out.replace(pattern, replacement) + } + return out +} From eec0843ce42298080569ca31a6455bc3f699d213 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 19 May 2026 15:11:40 +0000 Subject: [PATCH 018/237] chore: generate --- .../cli/cmd/tui/component/command-palette.tsx | 11 +++++------ .../src/cli/cmd/tui/component/prompt/index.tsx | 8 +------- packages/opencode/src/cli/cmd/tui/keymap.tsx | 18 ++++++------------ packages/opencode/test/tool/registry.test.ts | 7 +++++-- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/command-palette.tsx b/packages/opencode/src/cli/cmd/tui/component/command-palette.tsx index c54e67d4f271..0d6deeedcdea 100644 --- a/packages/opencode/src/cli/cmd/tui/component/command-palette.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/command-palette.tsx @@ -30,12 +30,11 @@ export function CommandPaletteDialog() { const query = { namespace: "palette", } - const reachable = keymap - .getCommandEntries({ - ...query, - visibility: "reachable", - filter: isVisiblePaletteCommand, - }) + const reachable = keymap.getCommandEntries({ + ...query, + visibility: "reachable", + filter: isVisiblePaletteCommand, + }) const registeredBindings = keymap.getCommandBindings({ visibility: "registered", commands: reachable.map((entry) => entry.command.name), diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index f6724c758252..e4907dcc27d5 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -59,13 +59,7 @@ import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable" import { useArgs } from "@tui/context/args" import { Flag } from "@opencode-ai/core/flag/flag" import { type WorkspaceStatus } from "../workspace-label" -import { - OPENCODE_BASE_MODE, - useBindings, - useCommandShortcut, - useLeaderActive, - useOpencodeKeymap, -} from "../../keymap" +import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap" import { useTuiConfig } from "../../context/tui-config" export type PromptProps = { diff --git a/packages/opencode/src/cli/cmd/tui/keymap.tsx b/packages/opencode/src/cli/cmd/tui/keymap.tsx index 6da518a0ba50..d8489fd2f73e 100644 --- a/packages/opencode/src/cli/cmd/tui/keymap.tsx +++ b/packages/opencode/src/cli/cmd/tui/keymap.tsx @@ -5,12 +5,7 @@ import { formatCommandBindings as formatCommandBindingsExtra, formatKeySequence as formatKeySequenceExtra, } from "@opentui/keymap/extras" -import { - KeymapProvider, - useKeymap, - useKeymapSelector, - useBindings, -} from "@opentui/keymap/solid" +import { KeymapProvider, useKeymap, useKeymapSelector, useBindings } from "@opentui/keymap/solid" import { createMemo, type Accessor } from "solid-js" import type { TuiConfig } from "./config/tui" import { useTuiConfig } from "./context/tui-config" @@ -248,12 +243,11 @@ export function useLeaderActive(): Accessor { export function useCommandSlashes(): Accessor { const keymap = useOpencodeKeymap() const entries = useKeymapSelector((keymap: OpenTuiKeymap) => - keymap - .getCommandEntries({ - visibility: "reachable", - namespace: "palette", - filter: isVisiblePaletteCommand, - }) + keymap.getCommandEntries({ + visibility: "reachable", + namespace: "palette", + filter: isVisiblePaletteCommand, + }), ) return createMemo(() => diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 321ab12444cc..d3549e66f340 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -78,7 +78,8 @@ const brokenPluginLayer = Layer.succeed( Plugin.Service, Plugin.Service.of({ init: () => Effect.void, - trigger: ((_name: unknown, _input: unknown, output: unknown) => Effect.succeed(output)) as Plugin.Interface["trigger"], + trigger: ((_name: unknown, _input: unknown, output: unknown) => + Effect.succeed(output)) as Plugin.Interface["trigger"], list: () => Effect.succeed([ { @@ -95,7 +96,9 @@ const brokenPluginLayer = Layer.succeed( ) const it = testEffect(Layer.mergeAll(registryLayer(), node, Agent.defaultLayer)) -const scout = testEffect(Layer.mergeAll(registryLayer({ flags: { experimentalScout: true } }), node, Agent.defaultLayer)) +const scout = testEffect( + Layer.mergeAll(registryLayer({ flags: { experimentalScout: true } }), node, Agent.defaultLayer), +) const background = testEffect( Layer.mergeAll(registryLayer({ flags: { experimentalBackgroundSubagents: true } }), node, Agent.defaultLayer), ) From b67f5d741f8939703825742a4ad7a7ba2114f839 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 11:24:23 -0400 Subject: [PATCH 019/237] test(opencode/run): skip Windows-only scrollback replay failure (#28261) --- .../test/cli/run/scrollback.surface.test.ts | 100 +++++++++++++----- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/packages/opencode/test/cli/run/scrollback.surface.test.ts b/packages/opencode/test/cli/run/scrollback.surface.test.ts index c61de69f8c85..8b5a49d987b9 100644 --- a/packages/opencode/test/cli/run/scrollback.surface.test.ts +++ b/packages/opencode/test/cli/run/scrollback.surface.test.ts @@ -403,38 +403,82 @@ test("inserts spacers for new visible groups", async () => { } }) -test("renders replayed user, reasoning, and assistant output after completion", async () => { - const out = await setup() +// TODO(windows): Re-enable on Windows once the streaming CodeRenderable +// flush race is fixed. The reasoning commit is delivered as a `` +// renderable with `filetype="markdown"`, `streaming=true`, and +// `drawUnstyledText=false`. On Windows the first paragraph of the reasoning +// body (here `_Thinking:_ **Plan**`) is dropped from the committed rows — +// the failing assertion shows only `Say hello.` survives, while Linux +// (where `useThread` is forced off in `@opentui/core/testing`) and macOS +// both pass. +// +// Investigation summary (see PR description for the link to this work): +// 1. `reasoning("Thinking: ...", "progress")` enters `entry.body.ts` +// `reasoningBody`, which becomes a `code` body with filetype="markdown". +// 2. `RunScrollbackStream.writeStreaming` sets `renderable.content = ...` +// while `streaming=true`. `CodeRenderable.set content` short-circuits +// (does NOT call `textBuffer.setText`) when streaming, drawUnstyledText +// is false, and a filetype is set — it relies on the next +// `startHighlight()` cycle to populate the buffer. +// 3. `ScrollbackSurface.settle()` renders the surface, kicks the +// highlight via `renderSelf` → `startHighlight`, waits on +// `highlightingDone`, and re-renders. With `MockTreeSitterClient` +// returning `{highlights: []}`, the final branch (`else +// this.textBuffer.setText(content)`) populates the buffer and +// `_shouldRenderTextBuffer = true`. +// 4. `flushActive` then commits rows `[0, surface.height - 1)` during +// streaming. On Windows the committed rows are blank for the first +// paragraph — suggesting the height/text-buffer state is observed +// before/after the highlight resolution in a way that drops rows on +// that platform. +// +// The Linux pass path takes `useThread = false` (see +// `@opentui/core/testing.js` line ~540) which serializes the FFI render +// thread. macOS passes despite `useThread = true`, so the divergence is +// likely either Bun's microtask scheduling on Windows or a Zig-side +// threading interaction during the second `renderSurface()` pass in +// `settleSurface`. A real fix probably belongs in opentui (either force +// `useThread=false` for testing on Windows, or eagerly call +// `textBuffer.setText` in `CodeRenderable.set content` when streaming +// updates a non-empty body). +// +// Skipping on win32 unblocks unrelated PRs; the assertion is still +// exercised on Linux and macOS in CI. +test.skipIf(process.platform === "win32")( + "renders replayed user, reasoning, and assistant output after completion", + async () => { + const out = await setup() - try { - const lines: string[] = [] - const take = () => { - const commits = claim(out.renderer) - try { - lines.push(...commits.flatMap((commit) => renderRows(commit).flatMap((row) => row.split("\n")))) - } finally { - destroy(commits) + try { + const lines: string[] = [] + const take = () => { + const commits = claim(out.renderer) + try { + lines.push(...commits.flatMap((commit) => renderRows(commit).flatMap((row) => row.split("\n")))) + } finally { + destroy(commits) + } } - } - await out.scrollback.append(user("Hello you")) - take() - await out.scrollback.append(reasoning("Thinking: **Plan**\n\nSay hello.", "progress")) - await out.scrollback.complete() - take() - await out.scrollback.append(assistant("Hello.", "progress")) - await out.scrollback.complete() - take() + await out.scrollback.append(user("Hello you")) + take() + await out.scrollback.append(reasoning("Thinking: **Plan**\n\nSay hello.", "progress")) + await out.scrollback.complete() + take() + await out.scrollback.append(assistant("Hello.", "progress")) + await out.scrollback.complete() + take() - const output = lines.join("\n") - expect(output).toContain("› Hello you") - expect(output).toContain("Thinking:") - expect(output).toContain("Plan") - expect(output).toContain("Hello.") - } finally { - out.scrollback.destroy() - } -}) + const output = lines.join("\n") + expect(output).toContain("› Hello you") + expect(output).toContain("Thinking:") + expect(output).toContain("Plan") + expect(output).toContain("Hello.") + } finally { + out.scrollback.destroy() + } + }, +) test("coalesces same-line tool progress into one snapshot", async () => { const out = await setup() From 7051796c38abbd1f84942af381b60f997b3b83be Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 11:35:48 -0400 Subject: [PATCH 020/237] test(cli): tier-A read-only command smoke tests (#28274) --- .../test/cli/smokes/read-only.test.ts | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 packages/opencode/test/cli/smokes/read-only.test.ts diff --git a/packages/opencode/test/cli/smokes/read-only.test.ts b/packages/opencode/test/cli/smokes/read-only.test.ts new file mode 100644 index 000000000000..bf80a27876ee --- /dev/null +++ b/packages/opencode/test/cli/smokes/read-only.test.ts @@ -0,0 +1,115 @@ +// Tier-A smoke tests for read-only commands. Each test asserts only that the +// command exits 0 and produces *some* output in the isolated harness env. +// +// These are not behavioral tests — they're the cheapest possible signal that +// the dependency-layer wiring (config load, DB init, server boot, provider +// resolution) doesn't crash for the broad class of "no inputs, no side +// effects" commands. A regression in any shared layer (an Effect.fail that +// propagates out of a service constructor, a renamed env var, a broken DB +// migration) will fail one or more of these tests. +// +// If a future change should make one of these commands intentionally fail in +// an empty env, update the assertion + add a note explaining the new contract. +// +// Speed: each test pays ~1.5s for bun startup. 7 tests serialize within this +// file. See script/prebuild-test-cli.ts for an opt-in pre-built binary that +// cuts per-spawn cost when this suite gets bigger. +import { describe, expect } from "bun:test" +import { Effect } from "effect" +import { cliIt } from "../../lib/cli-process" + +describe("opencode read-only commands (smoke)", () => { + // `mcp list` reads MCP server config and pings each one. With the empty + // OPENCODE_CONFIG_CONTENT={} we provide, no servers should be configured + // and the command should report that cleanly. + cliIt.live( + "mcp list: exits 0", + ({ opencode }) => + Effect.gen(function* () { + const r = yield* opencode.spawn(["mcp", "list"]) + opencode.expectExit(r, 0, "mcp list") + }), + 60_000, + ) + + // `providers list` enumerates credentials + env-resolved providers. + // (Not config-injected ones — those don't appear here by design.) The + // Credentials header always renders; the Environment header only renders + // when at least one provider env var is set, which the isolation harness + // deliberately doesn't guarantee. Assert the always-present marker so the + // test passes on a clean CI runner without env-var leakage. + cliIt.live( + "providers list: exits 0 and prints the credentials section", + ({ opencode }) => + Effect.gen(function* () { + const r = yield* opencode.spawn(["providers", "list"]) + opencode.expectExit(r, 0, "providers list") + expect(r.stdout).toContain("Credentials") + }), + 60_000, + ) + + // `models` lists models from configured providers. Our test/test-model + // should appear because it's wired into the test provider config. + cliIt.live( + "models: exits 0 and lists the test model", + ({ opencode }) => + Effect.gen(function* () { + const r = yield* opencode.spawn(["models"]) + opencode.expectExit(r, 0, "models") + expect(r.stdout).toContain("test/test-model") + }), + 60_000, + ) + + // `agent list` walks the agent config. Empty config means no agents + // configured; the command should still exit 0 with a "no agents" line or + // similar. We don't pin the message — just exit cleanly. + cliIt.live( + "agent list: exits 0", + ({ opencode }) => + Effect.gen(function* () { + const r = yield* opencode.spawn(["agent", "list"]) + opencode.expectExit(r, 0, "agent list") + }), + 60_000, + ) + + // `session list` reads the session DB. Fresh OPENCODE_TEST_HOME means + // empty DB. Exit 0 with no sessions. + cliIt.live( + "session list: exits 0", + ({ opencode }) => + Effect.gen(function* () { + const r = yield* opencode.spawn(["session", "list"]) + opencode.expectExit(r, 0, "session list") + }), + 60_000, + ) + + // `stats` aggregates token usage from the session DB. Empty DB → all zeros. + cliIt.live( + "stats: exits 0", + ({ opencode }) => + Effect.gen(function* () { + const r = yield* opencode.spawn(["stats"]) + opencode.expectExit(r, 0, "stats") + }), + 60_000, + ) + + // `db path` prints the DB file location. Under harness isolation the DB + // resolves to SQLite's `:memory:` (no on-disk pollution between tests); + // in production it'd be a path under OPENCODE_TEST_HOME / XDG_DATA_HOME. + // Accept either form — both prove the resolver ran without crashing. + cliIt.live( + "db path: exits 0 and prints a path or :memory:", + ({ opencode }) => + Effect.gen(function* () { + const r = yield* opencode.spawn(["db", "path"]) + opencode.expectExit(r, 0, "db path") + expect(r.stdout.trim()).toMatch(/^(:memory:|[/\\].+\.(db|sqlite|sqlite3))$/i) + }), + 60_000, + ) +}) From 512e34af83dca3b27f4abdc353bb1b427ad56403 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 11:45:13 -0400 Subject: [PATCH 021/237] Migrate MCP config tests to instance fixtures (#28338) --- packages/opencode/test/config/config.test.ts | 264 +++++++++---------- perf/test-suite.md | 2 + 2 files changed, 120 insertions(+), 146 deletions(-) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index f3c72c7584c6..ccc63d807d19 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1446,159 +1446,131 @@ test("config parser preserves permission order while rejecting unknown top-level // MCP config merging tests -test("project config can override MCP server enabled status", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - // Simulates a base config (like from remote .well-known) with disabled MCP - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - mcp: { - jira: { - type: "remote", - url: "https://jira.example.com/mcp", - enabled: false, - }, - wiki: { - type: "remote", - url: "https://wiki.example.com/mcp", - enabled: false, - }, - }, - }), - ) - // Project config enables just jira - await Filesystem.write( - path.join(dir, "opencode.jsonc"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - mcp: { - jira: { - type: "remote", - url: "https://jira.example.com/mcp", - enabled: true, - }, +it.instance("project config can override MCP server enabled status", () => + Effect.gen(function* () { + const test = yield* TestInstance + // Simulates a base config (like from remote .well-known) with disabled MCP. + yield* writeConfigEffect(test.directory, { + $schema: "https://opencode.ai/config.json", + mcp: { + jira: { + type: "remote", + url: "https://jira.example.com/mcp", + enabled: false, + }, + wiki: { + type: "remote", + url: "https://wiki.example.com/mcp", + enabled: false, + }, + }, + }) + // Project config enables just jira. + yield* writeConfigEffect( + test.directory, + { + $schema: "https://opencode.ai/config.json", + mcp: { + jira: { + type: "remote", + url: "https://jira.example.com/mcp", + enabled: true, }, - }), - ) - }, - }) - await withTestInstance({ - directory: tmp.path, - fn: async (ctx) => { - const config = await load(ctx) - // jira should be enabled (overridden by project config) - expect(config.mcp?.jira).toEqual({ - type: "remote", - url: "https://jira.example.com/mcp", - enabled: true, - }) - // wiki should still be disabled (not overridden) - expect(config.mcp?.wiki).toEqual({ - type: "remote", - url: "https://wiki.example.com/mcp", - enabled: false, - }) - }, - }) -}) + }, + }, + "opencode.jsonc", + ) -test("MCP config deep merges preserving base config properties", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - // Base config with full MCP definition - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - mcp: { - myserver: { - type: "remote", - url: "https://myserver.example.com/mcp", - enabled: false, - headers: { - "X-Custom-Header": "value", - }, - }, + const config = yield* Config.Service.use((svc) => svc.get()) + expect(config.mcp?.jira).toEqual({ + type: "remote", + url: "https://jira.example.com/mcp", + enabled: true, + }) + expect(config.mcp?.wiki).toEqual({ + type: "remote", + url: "https://wiki.example.com/mcp", + enabled: false, + }) + }), +) + +it.instance("MCP config deep merges preserving base config properties", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeConfigEffect(test.directory, { + $schema: "https://opencode.ai/config.json", + mcp: { + myserver: { + type: "remote", + url: "https://myserver.example.com/mcp", + enabled: false, + headers: { + "X-Custom-Header": "value", }, - }), - ) - // Override just enables it, should preserve other properties - await Filesystem.write( - path.join(dir, "opencode.jsonc"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - mcp: { - myserver: { - type: "remote", - url: "https://myserver.example.com/mcp", - enabled: true, - }, + }, + }, + }) + yield* writeConfigEffect( + test.directory, + { + $schema: "https://opencode.ai/config.json", + mcp: { + myserver: { + type: "remote", + url: "https://myserver.example.com/mcp", + enabled: true, }, - }), - ) - }, - }) - await withTestInstance({ - directory: tmp.path, - fn: async (ctx) => { - const config = await load(ctx) - expect(config.mcp?.myserver).toEqual({ - type: "remote", - url: "https://myserver.example.com/mcp", - enabled: true, - headers: { - "X-Custom-Header": "value", }, - }) - }, - }) -}) + }, + "opencode.jsonc", + ) -test("local .opencode config can override MCP from project config", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - // Project config with disabled MCP - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - mcp: { - docs: { - type: "remote", - url: "https://docs.example.com/mcp", - enabled: false, - }, - }, - }), - ) - // Local .opencode directory config enables it - const opencodeDir = path.join(dir, ".opencode") - await fs.mkdir(opencodeDir, { recursive: true }) - await Filesystem.write( - path.join(opencodeDir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - mcp: { - docs: { - type: "remote", - url: "https://docs.example.com/mcp", - enabled: true, - }, + const config = yield* Config.Service.use((svc) => svc.get()) + expect(config.mcp?.myserver).toEqual({ + type: "remote", + url: "https://myserver.example.com/mcp", + enabled: true, + headers: { + "X-Custom-Header": "value", + }, + }) + }), +) + +it.instance("local .opencode config can override MCP from project config", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeConfigEffect(test.directory, { + $schema: "https://opencode.ai/config.json", + mcp: { + docs: { + type: "remote", + url: "https://docs.example.com/mcp", + enabled: false, + }, + }, + }) + yield* mkdirEffect(path.join(test.directory, ".opencode")) + yield* writeConfigEffect( + path.join(test.directory, ".opencode"), + { + $schema: "https://opencode.ai/config.json", + mcp: { + docs: { + type: "remote", + url: "https://docs.example.com/mcp", + enabled: true, }, - }), - ) - }, - }) - await withTestInstance({ - directory: tmp.path, - fn: async (ctx) => { - const config = await load(ctx) - expect(config.mcp?.docs?.enabled).toBe(true) - }, - }) -}) + }, + }, + "opencode.json", + ) + + const config = yield* Config.Service.use((svc) => svc.get()) + expect(config.mcp?.docs?.enabled).toBe(true) + }), +) test("project config overrides remote well-known config", async () => { const originalFetch = globalThis.fetch diff --git a/perf/test-suite.md b/perf/test-suite.md index 2516bdc40902..b37b868bbd3a 100644 --- a/perf/test-suite.md +++ b/perf/test-suite.md @@ -80,6 +80,7 @@ Repeated setup work, long sleeps/timeouts, serial integration tests, filesystem/ | Remaining simple config load cases can use Effect-aware instance fixtures | Migrated default config load and legacy TUI-key cases to `it.instance` | 7.78s | 6.39s | keep | Single baseline before edit; after median from three sequential reruns (5.76, 6.39, 6.53). Keep as cleanup with cautious timing. | | Managed settings config cases can use Effect-aware instance fixtures | Migrated managed override and missing-managed-file cases to `it.instance` | 2.40s | 1.76s | keep | Single baseline before edit; after median from three sequential reruns (1.75, 1.76, 1.80). | | Local plugin and subagent config fixtures can use Effect-aware instance fixtures | Migrated scoped npm plugin and custom subagent markdown cases to `it.instance` | 2.37s | 1.67s | keep | Single baseline before edit; after median from three sequential reruns (1.66, 1.67, 1.67). | +| MCP merge config cases can use Effect-aware instance fixtures | Migrated three MCP merge/override cases to `it.instance` | 1.98s | 1.95s | keep | Neutral timing within noise; removes manual `tmpdir` + `withTestInstance` setup from isolated filesystem-only config cases. | ## Profiling Results @@ -135,3 +136,4 @@ Full-suite sanity checks: | Socket reset retry test can shorten its idle-timeout path | Reduced Bun server idle timeout and tried forced server close | 16.46s | failed | discard | Shorter idle timeout changed the error shape; forced close hung. Keep the real socket reset. | | `tool/webfetch` can avoid per-test instance setup | Switched local HTTP tests from `it.instance` to `it.live` | 1.219s | failed | discard | Tool execution reads instance-local agent state, so the temp instance is required. | | LSP client interop tests can shorten coarse request-handling sleeps | Reduced fixed post-notification waits from 100ms to 10ms | 4.270s | 4.740s | discard | First run improved to 3.870s but verification was slower than baseline; not a clear win. | +| Config content env cases can use Effect-aware instance fixtures | Migrated two `OPENCODE_CONFIG_CONTENT` token substitution cases to `it.instance` | 1.95s | 2.06s | discard | Passing but not neutral-or-better in focused reruns; keep existing explicit env cleanup. | From b32f0715029cc1502cef326aa8a8f6a517220d3a Mon Sep 17 00:00:00 2001 From: Victor Navarro Date: Tue, 19 May 2026 17:51:39 +0200 Subject: [PATCH 022/237] feat(go): referral support (#28345) Co-authored-by: Jack --- .../console/app/src/component/go-referral.css | 354 ++ .../console/app/src/component/go-referral.tsx | 313 ++ packages/console/app/src/component/modal.css | 67 + packages/console/app/src/component/modal.tsx | 38 +- packages/console/app/src/i18n/ar.ts | 30 + packages/console/app/src/i18n/br.ts | 30 + packages/console/app/src/i18n/da.ts | 30 + packages/console/app/src/i18n/de.ts | 30 + packages/console/app/src/i18n/en.ts | 30 + packages/console/app/src/i18n/es.ts | 30 + packages/console/app/src/i18n/fr.ts | 30 + packages/console/app/src/i18n/it.ts | 30 + packages/console/app/src/i18n/ja.ts | 30 + packages/console/app/src/i18n/ko.ts | 30 + packages/console/app/src/i18n/no.ts | 30 + packages/console/app/src/i18n/pl.ts | 30 + packages/console/app/src/i18n/ru.ts | 30 + packages/console/app/src/i18n/th.ts | 30 + packages/console/app/src/i18n/tr.ts | 30 + packages/console/app/src/i18n/zh.ts | 30 + packages/console/app/src/i18n/zht.ts | 30 + .../console/app/src/lib/format-reset-time.ts | 47 + .../console/app/src/lib/referral-invite.ts | 28 + packages/console/app/src/middleware.ts | 16 +- .../app/src/routes/auth/[...callback].ts | 12 +- packages/console/app/src/routes/black.css | 119 +- .../app/src/routes/black/subscribe/[plan].tsx | 9 +- packages/console/app/src/routes/go/index.tsx | 11 +- .../console/app/src/routes/stripe/webhook.ts | 9 +- .../app/src/routes/workspace-picker.css | 4 + .../app/src/routes/workspace-picker.tsx | 59 +- packages/console/app/src/routes/workspace.css | 1 + .../workspace/[id]/billing/black-section.tsx | 19 +- .../src/routes/workspace/[id]/go/index.tsx | 12 + .../workspace/[id]/go/lite-section.module.css | 3 + .../routes/workspace/[id]/go/lite-section.tsx | 129 +- .../migration.sql | 47 + .../snapshot.json | 3292 +++++++++++++++++ .../20260516082200_long_spirit/migration.sql | 20 + .../20260516082200_long_spirit/snapshot.json | 3124 ++++++++++++++++ .../migration.sql | 4 + .../snapshot.json | 3090 ++++++++++++++++ .../migration.sql | 3 + .../snapshot.json | 3075 +++++++++++++++ .../migration.sql | 2 + .../snapshot.json | 3075 +++++++++++++++ .../migration.sql | 2 + .../snapshot.json | 3075 +++++++++++++++ packages/console/core/src/billing.ts | 20 + packages/console/core/src/identifier.ts | 1 + packages/console/core/src/referral.ts | 386 ++ .../console/core/src/schema/referral.sql.ts | 28 + .../console/core/src/schema/workspace.sql.ts | 6 +- packages/console/function/src/auth.ts | 5 +- 54 files changed, 20809 insertions(+), 206 deletions(-) create mode 100644 packages/console/app/src/component/go-referral.css create mode 100644 packages/console/app/src/component/go-referral.tsx create mode 100644 packages/console/app/src/lib/format-reset-time.ts create mode 100644 packages/console/app/src/lib/referral-invite.ts create mode 100644 packages/console/core/migrations/20260513173355_groovy_jane_foster/migration.sql create mode 100644 packages/console/core/migrations/20260513173355_groovy_jane_foster/snapshot.json create mode 100644 packages/console/core/migrations/20260516082200_long_spirit/migration.sql create mode 100644 packages/console/core/migrations/20260516082200_long_spirit/snapshot.json create mode 100644 packages/console/core/migrations/20260516110447_classy_wilson_fisk/migration.sql create mode 100644 packages/console/core/migrations/20260516110447_classy_wilson_fisk/snapshot.json create mode 100644 packages/console/core/migrations/20260516112143_cynical_dexter_bennett/migration.sql create mode 100644 packages/console/core/migrations/20260516112143_cynical_dexter_bennett/snapshot.json create mode 100644 packages/console/core/migrations/20260516222132_referral_code_length/migration.sql create mode 100644 packages/console/core/migrations/20260516222132_referral_code_length/snapshot.json create mode 100644 packages/console/core/migrations/20260518181630_secret_strong_guy/migration.sql create mode 100644 packages/console/core/migrations/20260518181630_secret_strong_guy/snapshot.json create mode 100644 packages/console/core/src/referral.ts create mode 100644 packages/console/core/src/schema/referral.sql.ts diff --git a/packages/console/app/src/component/go-referral.css b/packages/console/app/src/component/go-referral.css new file mode 100644 index 000000000000..c9050e6cf972 --- /dev/null +++ b/packages/console/app/src/component/go-referral.css @@ -0,0 +1,354 @@ +[data-component="go-credit-confirm"] { + display: flex; + flex-direction: column; + gap: var(--space-4); + min-width: min(34rem, calc(100vw - var(--space-8))); + + p { + margin: 0; + color: var(--color-text-secondary); + font-size: var(--font-size-sm); + line-height: 1.6; + } + + [data-slot="usage-preview"] { + display: flex; + flex-direction: column; + gap: var(--space-5); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg-surface); + } + + [data-slot="usage-preview-item"] { + display: flex; + flex-direction: column; + gap: var(--space-2); + } + + [data-slot="usage-preview-header"] { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--space-3); + } + + [data-slot="usage-preview-label"] { + color: var(--color-text); + font-size: var(--font-size-sm); + font-weight: 500; + } + + [data-slot="usage-preview-value"] { + display: inline-flex; + align-items: center; + gap: var(--space-1); + color: var(--color-text-muted); + font-family: var(--font-mono); + font-size: var(--font-size-xs); + white-space: nowrap; + } + + [data-slot="usage-preview-after-value"] { + color: var(--color-accent); + font-weight: 600; + } + + [data-slot="usage-preview-progress"] { + position: relative; + height: 8px; + overflow: hidden; + border-radius: var(--border-radius-sm); + background-color: var(--color-bg); + } + + [data-slot="usage-preview-before"], + [data-slot="usage-preview-after"] { + position: absolute; + top: 0; + bottom: 0; + left: 0; + border-radius: var(--border-radius-sm); + } + + [data-slot="usage-preview-before"] { + background-color: var(--color-border); + } + + [data-slot="usage-preview-after"] { + background-color: var(--color-accent); + transition: width 0.35s ease; + } + + [data-slot="usage-preview-reset"] { + color: var(--color-text-muted); + font-size: var(--font-size-xs); + } + + [data-slot="modal-actions"] { + display: flex; + justify-content: flex-end; + gap: var(--space-3); + } +} + +[data-slot="invite-link-box"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + + > div { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-4); + border: 2px solid var(--color-accent); + border-radius: var(--border-radius-sm); + + @media (max-width: 40rem) { + align-items: stretch; + flex-direction: column; + } + } + + code { + flex: 1; + min-width: 0; + padding: var(--space-3); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg); + color: var(--color-text); + font-family: var(--font-mono); + font-size: var(--font-size-sm); + line-height: 1.4; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + @media (max-width: 40rem) { + padding: var(--space-2-5); + font-size: var(--font-size-xs); + } + } + + button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + min-width: 130px; + white-space: nowrap; + + @media (max-width: 40rem) { + min-width: 96px; + padding: var(--space-2-5) var(--space-3); + font-size: var(--font-size-xs); + } + } +} + +[data-slot="instructions"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + + ol { + display: flex; + flex-direction: column; + gap: var(--space-2); + margin: 0; + padding-left: 0; + color: var(--color-text-secondary); + font-size: var(--font-size-md); + list-style-position: inside; + line-height: 1.5; + } +} + +[data-component="go-referral-section"] { + [data-component="go-referral-overview"] { + display: flex; + flex-direction: column; + gap: var(--space-8); + padding: var(--space-6); + border: 1px dashed var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg-surface); + + @media (max-width: 30rem) { + gap: var(--space-8); + padding: var(--space-4); + } + } + + [data-slot="rewards-title"] { + display: flex; + flex-direction: column; + gap: var(--space-1); + padding-top: var(--space-2); + + h2 { + margin: 0; + color: var(--color-text); + font-size: var(--font-size-md); + font-weight: 600; + line-height: 1.2; + letter-spacing: -0.03125rem; + } + + p { + margin: 0; + color: var(--color-text-muted); + font-size: var(--font-size-sm); + line-height: 1.5; + } + } + + [data-slot="referral-stats"] { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-6); + + @media (max-width: 30rem) { + grid-template-columns: 1fr; + gap: var(--space-4); + } + + > div { + display: flex; + flex-direction: column; + gap: var(--space-2); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + } + + span { + color: var(--color-text-muted); + font-size: var(--font-size-sm); + font-weight: 600; + text-transform: uppercase; + letter-spacing: -0.025rem; + } + + strong { + color: var(--color-text); + font-size: var(--font-size-md); + font-weight: 600; + } + } + + [data-slot="referrals-table"] { + overflow-x: auto; + } + + [data-component="empty-state"] { + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg-surface); + color: var(--color-text-muted); + font-size: var(--font-size-sm); + } + + [data-slot="referrals-table-element"] { + width: 100%; + border-collapse: collapse; + font-size: var(--font-size-sm); + + thead { + border-bottom: 1px solid var(--color-border); + } + + th { + padding: var(--space-3) var(--space-4); + text-align: left; + font-weight: normal; + color: var(--color-text-muted); + text-transform: uppercase; + + &:nth-child(1) { + width: 120px; + } + + &:nth-child(3) { + width: 180px; + } + + &:nth-child(4) { + width: 140px; + } + } + + td { + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--color-border-muted); + color: var(--color-text-muted); + font-family: var(--font-mono); + + &[data-slot="referral-amount"] { + color: var(--color-text); + font-weight: 500; + } + + &[data-slot="referral-source"] { + color: var(--color-text-secondary); + font-family: var(--font-sans); + + span { + display: block; + } + + [data-slot="referral-email"] { + margin-top: var(--space-1); + color: var(--color-text-muted); + line-height: 1.4; + white-space: normal; + } + } + + &[data-slot="referral-action"] { + text-align: right; + font-family: var(--font-sans); + white-space: nowrap; + + button { + min-width: 96px; + } + } + } + + tbody tr { + &[data-status="applied"] { + td:not([data-slot="referral-action"]) { + opacity: 0.68; + } + } + + &[data-status="pending"] { + td[data-slot="referral-amount"], + td[data-slot="referral-date"] { + color: var(--color-text-muted); + } + + td[data-slot="referral-source"] { + color: var(--color-text); + } + } + + &:last-child td { + border-bottom: none; + } + } + + @media (max-width: 40rem) { + th, + td { + padding: var(--space-2) var(--space-3); + font-size: var(--font-size-xs); + } + } + } +} diff --git a/packages/console/app/src/component/go-referral.tsx b/packages/console/app/src/component/go-referral.tsx new file mode 100644 index 000000000000..6a186974eaab --- /dev/null +++ b/packages/console/app/src/component/go-referral.tsx @@ -0,0 +1,313 @@ +import { action, createAsync, json, query, useAction, useSubmission } from "@solidjs/router" +import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js" +import { getRequestEvent } from "solid-js/web" +import { Referral } from "@opencode-ai/console-core/referral.js" +import { withActor } from "~/context/auth.withActor" +import { Modal } from "~/component/modal" +import { IconCheck, IconCopy } from "~/component/icon" +import { useI18n } from "~/context/i18n" +import { useLanguage } from "~/context/language" +import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time" +import { queryLiteSubscription } from "~/routes/workspace/[id]/go/lite-section" +import "./go-referral.css" + +type GoReferralSummary = Awaited> +type GoReferralReward = GoReferralSummary["rewards"][number] +type GoReferralUsagePreview = NonNullable>> +type GoReferralUsagePreviewItem = GoReferralUsagePreview["rollingUsage"] + +const emptyUsagePreview = { + rollingUsage: { beforePercent: 0, afterPercent: 0, resetInSec: 0 }, + weeklyUsage: { beforePercent: 0, afterPercent: 0, resetInSec: 0 }, + monthlyUsage: { beforePercent: 0, afterPercent: 0, resetInSec: 0 }, +} satisfies GoReferralUsagePreview + +export const queryGoReferral = query(async (workspaceID: string) => { + "use server" + return withActor(() => Referral.summary(), workspaceID) +}, "go.referral.get") + +export const queryGoReferralUsagePreview = query(async (workspaceID: string, referralID?: string) => { + "use server" + if (!referralID) return null + return withActor(() => Referral.usagePreview({ referralID }), workspaceID) +}, "go.referral.usagePreview") + +export const applyGoReferralReward = action(async (workspaceID: string, referralID: string) => { + "use server" + return json( + await withActor(() => Referral.applyReward({ referralID }), workspaceID), + { revalidate: [queryGoReferral.key, queryGoReferralUsagePreview.key, queryLiteSubscription.key] }, + ) +}, "go.referral.reward.apply") + +function currentUsagePreview(usage: { resetInSec: number; usagePercent: number }) { + return { + beforePercent: usage.usagePercent, + afterPercent: usage.usagePercent, + resetInSec: usage.resetInSec, + } +} + +function formatCurrency(amount: number) { + if (amount % 100 === 0) return `$${amount / 100}` + return `$${(amount / 100).toFixed(2)}` +} + +function formatDate(value: string | Date, locale: string) { + return new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", year: "numeric" }).format(new Date(value)) +} + +function rewardTitleKey(reward: GoReferralReward) { + if (reward.status === "pending" && reward.source === "invitee") + return "workspace.referral.reward.source.pendingInvitee" as const + if (reward.status === "pending") return "workspace.referral.reward.source.pendingInviter" as const + if (reward.status === "applied") return "workspace.referral.reward.source.applied" as const + return "workspace.referral.reward.source.available" as const +} + +function rewardPendingStatusKey(source: GoReferralReward["source"]) { + if (source === "invitee") return "workspace.referral.reward.status.pendingInvitee" as const + return "workspace.referral.reward.status.pendingInviter" as const +} + +function CopyInviteLink(props: { summary: GoReferralSummary }) { + const i18n = useI18n() + const [copied, setCopied] = createSignal(false) + const event = getRequestEvent() + const origin = event + ? new URL(event.request.url).origin + : typeof window === "object" + ? window.location.origin + : undefined + const inviteUrl = createMemo(() => { + const path = `/go?ref=${props.summary.referralCode}` + if (!origin) return path + return new URL(path, origin).toString() + }) + + async function copy() { + if (typeof navigator !== "object") return + await navigator.clipboard.writeText(inviteUrl()) + setCopied(true) + window.setTimeout(() => setCopied(false), 1600) + } + + return ( +
+
+ {inviteUrl()} + +
+
+ ) +} + +export function GoReferralSection(props: { workspaceID: string; summary: GoReferralSummary }) { + const i18n = useI18n() + const language = useLanguage() + const apply = useAction(applyGoReferralReward) + const submission = useSubmission(applyGoReferralReward) + const [selected, setSelected] = createSignal() + const [preview, setPreview] = createSignal() + const lite = createAsync(() => queryLiteSubscription(props.workspaceID)) + const displayPreview = createMemo(() => { + const loaded = preview() + if (loaded) return loaded + const current = lite() + if (!current) return emptyUsagePreview + return { + rollingUsage: currentUsagePreview(current.rollingUsage), + weeklyUsage: currentUsagePreview(current.weeklyUsage), + monthlyUsage: currentUsagePreview(current.monthlyUsage), + } satisfies GoReferralUsagePreview + }) + createEffect(() => { + const reward = selected() + if (!reward) { + setPreview(undefined) + return + } + + const request = { cancelled: false } + setPreview(undefined) + queryGoReferralUsagePreview(props.workspaceID, reward.id).then((result) => { + if (request.cancelled) return + setPreview(result) + }) + onCleanup(() => { + request.cancelled = true + }) + }) + + async function onApply() { + const reward = selected() + if (!reward) return + await apply(props.workspaceID, reward.id) + setSelected(undefined) + } + + return ( +
+
+

{i18n.t("workspace.referral.overview.title")}

+

+ {i18n.t("workspace.referral.overview.subtitle", { + reward: formatCurrency(props.summary.rewardAmount), + })} +

+
+
+
+
+ {i18n.t("workspace.referral.stats.invites")} + {props.summary.inviteCount} +
+
+ {i18n.t("workspace.referral.stats.earned")} + {formatCurrency(props.summary.totalEarned)} +
+
+ {i18n.t("workspace.referral.stats.applied")} + {formatCurrency(props.summary.totalApplied)} +
+
+ +
+
    +
  1. {i18n.t("workspace.referral.instructions.share")}
  2. +
  3. {i18n.t("workspace.referral.instructions.subscribe")}
  4. +
  5. {i18n.t("workspace.referral.instructions.claim")}
  6. +
+
+
+ 0} + fallback={
{i18n.t("workspace.referral.rewards.empty")}
} + > +
+ + + + + + + + + + + + {(reward) => { + const applied = reward.status === "applied" + const pending = reward.status === "pending" + const earnedAt = () => formatDate(reward.timeCreated, language.tag(language.locale())) + return ( + + + + + + + ) + }} + + +
{i18n.t("workspace.referral.table.reward")}{i18n.t("workspace.referral.table.referral")}{i18n.t("workspace.referral.table.date")}
{formatCurrency(reward.amount)} + {i18n.t(rewardTitleKey(reward))} + {reward.email} + + {earnedAt()} + + +
+
+
+ setSelected(undefined)} title={i18n.t("workspace.referral.apply.confirmTitle")}> +
+

+ {i18n.t("workspace.referral.apply.confirmBody", { + amount: formatCurrency(selected()?.amount ?? 0), + })} +

+ +
+ + +
+
+
+
+ ) +} + +function GoReferralUsagePreview(props: { preview: GoReferralUsagePreview }) { + const i18n = useI18n() + + return ( +
+ + + +
+ ) +} + +function GoReferralUsagePreviewRow(props: { label: string; usage: GoReferralUsagePreviewItem }) { + const i18n = useI18n() + + return ( +
+
+ {props.label} + + {props.usage.beforePercent}% + + {props.usage.afterPercent}% + +
+
+
+
+
+ + {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(props.usage.resetInSec, i18n, liteResetTimeKeys)} + +
+ ) +} diff --git a/packages/console/app/src/component/modal.css b/packages/console/app/src/component/modal.css index e71fd1a192e9..ab7f222b021e 100644 --- a/packages/console/app/src/component/modal.css +++ b/packages/console/app/src/component/modal.css @@ -55,6 +55,61 @@ @media (prefers-color-scheme: dark) { box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); } + + button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--space-3) var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg); + color: var(--color-text); + font-size: var(--font-size-sm); + font-family: var(--font-sans); + font-weight: 500; + line-height: 1; + cursor: pointer; + transition: all 0.15s ease; + + &:hover:not(:disabled) { + background-color: var(--color-surface-hover); + border-color: var(--color-accent); + } + + &:active:not(:disabled) { + transform: translateY(1px); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + + &[data-color="primary"] { + background-color: var(--color-primary); + border-color: var(--color-primary); + color: var(--color-primary-text); + + &:hover:not(:disabled) { + background-color: var(--color-primary-hover); + border-color: var(--color-primary-hover); + } + } + + &[data-color="ghost"] { + background-color: transparent; + border-color: transparent; + color: var(--color-text-muted); + + &:hover:not(:disabled) { + background-color: var(--color-surface-hover); + border-color: var(--color-border); + color: var(--color-text); + } + } + } } [data-slot="title"] { @@ -64,4 +119,16 @@ color: var(--color-text); text-align: center; } + + [data-slot="content"][data-variant="black"] { + background-color: #000; + border-color: rgba(255, 255, 255, 0.17); + color: rgba(255, 255, 255, 0.92); + font-family: var(--font-mono); + + [data-slot="title"] { + color: rgba(255, 255, 255, 0.92); + font-family: var(--font-mono); + } + } } diff --git a/packages/console/app/src/component/modal.tsx b/packages/console/app/src/component/modal.tsx index d6dc8a3de53e..8cac8c4ce79a 100644 --- a/packages/console/app/src/component/modal.tsx +++ b/packages/console/app/src/component/modal.tsx @@ -1,3 +1,4 @@ +import { Dialog as Kobalte } from "@kobalte/core/dialog" import { JSX, Show } from "solid-js" import "./modal.css" @@ -5,20 +6,41 @@ interface ModalProps { open: boolean onClose: () => void title?: string + variant?: "black" children: JSX.Element } export function Modal(props: ModalProps) { return ( -
-
e.stopPropagation()}> - -

{props.title}

-
- {props.children} -
-
+ { + if (!open) props.onClose() + }} + > + + + e.stopPropagation()} + onOpenAutoFocus={(e) => { + e.preventDefault() + const target = e.currentTarget as HTMLElement | null + target?.focus({ preventScroll: true }) + }} + > + + {props.title} + + {props.children} + + + +
) } diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index f413b5572f8c..291d732c222f 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -660,6 +660,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "طرق دفع أخرى", "workspace.lite.promo.selectMethod": "اختر طريقة الدفع", + "workspace.referral.copyLink": "نسخ الرابط", + "workspace.referral.copied": "تم النسخ", + "workspace.referral.overview.title": "ادعُ أصدقاءك إلى Go", + "workspace.referral.overview.subtitle": "احصل على رصيد Go بقيمة $5 عند اشتراك صديق. وسيحصل هو أيضًا على $5.", + "workspace.referral.stats.invites": "الدعوات", + "workspace.referral.stats.earned": "المُكتسب", + "workspace.referral.stats.applied": "المُطبَّق", + "workspace.referral.instructions.share": "شارك رابط دعوتك", + "workspace.referral.instructions.subscribe": "يشترك صديقك في Go", + "workspace.referral.instructions.claim": "طبِّق رصيد $5 الخاص بك أدناه", + "workspace.referral.rewards.title": "مكافآت الإحالة", + "workspace.referral.rewards.subtitle": "تم تطبيق {{applied}} / {{total}} من المكافآت.", + "workspace.referral.rewards.empty": "لا توجد مكافآت إحالة بعد.", + "workspace.referral.table.reward": "المكافأة", + "workspace.referral.table.referral": "الوصف", + "workspace.referral.table.date": "التاريخ", + "workspace.referral.reward.source.pendingInviter": "بانتظار اشتراكه", + "workspace.referral.reward.source.pendingInvitee": "اشترك لإلغاء قفل المكافأة", + "workspace.referral.reward.source.available": "المكافأة جاهزة للتطبيق", + "workspace.referral.reward.source.applied": "تم تطبيق المكافأة", + "workspace.referral.reward.status.applied": "مُطبَّق", + "workspace.referral.reward.status.pendingInviter": "تطبيق", + "workspace.referral.reward.status.pendingInvitee": "تطبيق", + "workspace.referral.apply.noGo": "الاشتراك في Go", + "workspace.referral.apply.preview": "معاينة", + "workspace.referral.apply.action": "تطبيق", + "workspace.referral.apply.confirmTitle": "تطبيق مكافأة Go", + "workspace.referral.apply.confirmBody": "طبِّق {{amount}} لتقليل عدّادات استخدام Go الحالية في مساحة العمل هذه.", + "workspace.referral.apply.confirmAction": "تطبيق", + "download.title": "OpenCode | تنزيل", "download.meta.description": "نزّل OpenCode لـ macOS، Windows، وLinux", "download.hero.title": "تنزيل OpenCode", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 8466acc5fd70..4cd1ef27473c 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -670,6 +670,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Outros métodos de pagamento", "workspace.lite.promo.selectMethod": "Selecionar método de pagamento", + "workspace.referral.copyLink": "Copiar link", + "workspace.referral.copied": "Copiado", + "workspace.referral.overview.title": "Convide amigos para o Go", + "workspace.referral.overview.subtitle": "Ganhe $5 em crédito Go quando um amigo assinar. Ele também ganha $5.", + "workspace.referral.stats.invites": "Convites", + "workspace.referral.stats.earned": "Ganhos", + "workspace.referral.stats.applied": "Aplicados", + "workspace.referral.instructions.share": "Compartilhe seu link de convite", + "workspace.referral.instructions.subscribe": "Seu amigo assina o Go", + "workspace.referral.instructions.claim": "Aplique seu crédito de $5 abaixo", + "workspace.referral.rewards.title": "Recompensas de indicação", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} recompensas aplicadas.", + "workspace.referral.rewards.empty": "Ainda não há recompensas de indicação.", + "workspace.referral.table.reward": "Recompensa", + "workspace.referral.table.referral": "Descrição", + "workspace.referral.table.date": "Data", + "workspace.referral.reward.source.pendingInviter": "Aguardando ele assinar", + "workspace.referral.reward.source.pendingInvitee": "Assine para desbloquear a recompensa", + "workspace.referral.reward.source.available": "Recompensa pronta para usar", + "workspace.referral.reward.source.applied": "Recompensa aplicada", + "workspace.referral.reward.status.applied": "Aplicada", + "workspace.referral.reward.status.pendingInviter": "Aplicar", + "workspace.referral.reward.status.pendingInvitee": "Aplicar", + "workspace.referral.apply.noGo": "Assinar Go", + "workspace.referral.apply.preview": "Visualizar", + "workspace.referral.apply.action": "Aplicar", + "workspace.referral.apply.confirmTitle": "Aplicar recompensa Go", + "workspace.referral.apply.confirmBody": "Aplique {{amount}} para reduzir os contadores atuais de uso do Go deste workspace.", + "workspace.referral.apply.confirmAction": "Aplicar", + "download.title": "OpenCode | Baixar", "download.meta.description": "Baixe o OpenCode para macOS, Windows e Linux", "download.hero.title": "Baixar OpenCode", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 9338e3add5f9..2e27ddac33b5 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -666,6 +666,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Andre betalingsmetoder", "workspace.lite.promo.selectMethod": "Vælg betalingsmetode", + "workspace.referral.copyLink": "Kopiér link", + "workspace.referral.copied": "Kopieret", + "workspace.referral.overview.title": "Inviter venner til Go", + "workspace.referral.overview.subtitle": "Få $5 i Go-kredit, når en ven abonnerer. De får også $5.", + "workspace.referral.stats.invites": "Invitationer", + "workspace.referral.stats.earned": "Optjent", + "workspace.referral.stats.applied": "Brugt", + "workspace.referral.instructions.share": "Del dit invitationslink", + "workspace.referral.instructions.subscribe": "Din ven abonnerer på Go", + "workspace.referral.instructions.claim": "Brug din $5-kredit nedenfor", + "workspace.referral.rewards.title": "Henvisningsbelønninger", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} belønninger brugt.", + "workspace.referral.rewards.empty": "Ingen henvisningsbelønninger endnu.", + "workspace.referral.table.reward": "Belønning", + "workspace.referral.table.referral": "Beskrivelse", + "workspace.referral.table.date": "Dato", + "workspace.referral.reward.source.pendingInviter": "Venter på, at de abonnerer", + "workspace.referral.reward.source.pendingInvitee": "Abonner for at låse belønningen op", + "workspace.referral.reward.source.available": "Belønning klar til brug", + "workspace.referral.reward.source.applied": "Belønning brugt", + "workspace.referral.reward.status.applied": "Brugt", + "workspace.referral.reward.status.pendingInviter": "Brug", + "workspace.referral.reward.status.pendingInvitee": "Brug", + "workspace.referral.apply.noGo": "Abonner på Go", + "workspace.referral.apply.preview": "Forhåndsvis", + "workspace.referral.apply.action": "Brug", + "workspace.referral.apply.confirmTitle": "Brug Go-belønning", + "workspace.referral.apply.confirmBody": "Brug {{amount}} til at reducere dette workspaces nuværende Go-forbrugstællere.", + "workspace.referral.apply.confirmAction": "Brug", + "download.title": "OpenCode | Download", "download.meta.description": "Download OpenCode til macOS, Windows og Linux", "download.hero.title": "Download OpenCode", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 7a2d3e91b4bd..6f6986e74569 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -669,6 +669,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Andere Zahlungsmethoden", "workspace.lite.promo.selectMethod": "Zahlungsmethode auswählen", + "workspace.referral.copyLink": "Link kopieren", + "workspace.referral.copied": "Kopiert", + "workspace.referral.overview.title": "Freunde zu Go einladen", + "workspace.referral.overview.subtitle": "Erhalte $5 Go-Guthaben, wenn ein Freund abonniert. Er bekommt ebenfalls $5.", + "workspace.referral.stats.invites": "Einladungen", + "workspace.referral.stats.earned": "Verdient", + "workspace.referral.stats.applied": "Eingelöst", + "workspace.referral.instructions.share": "Teile deinen Einladungslink", + "workspace.referral.instructions.subscribe": "Dein Freund abonniert Go", + "workspace.referral.instructions.claim": "Löse unten dein $5-Guthaben ein", + "workspace.referral.rewards.title": "Empfehlungsbelohnungen", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} Belohnungen eingelöst.", + "workspace.referral.rewards.empty": "Noch keine Empfehlungsbelohnungen.", + "workspace.referral.table.reward": "Belohnung", + "workspace.referral.table.referral": "Beschreibung", + "workspace.referral.table.date": "Datum", + "workspace.referral.reward.source.pendingInviter": "Warten auf das Abo des Freundes", + "workspace.referral.reward.source.pendingInvitee": "Abonnieren, um Belohnung freizuschalten", + "workspace.referral.reward.source.available": "Belohnung kann eingelöst werden", + "workspace.referral.reward.source.applied": "Belohnung eingelöst", + "workspace.referral.reward.status.applied": "Eingelöst", + "workspace.referral.reward.status.pendingInviter": "Einlösen", + "workspace.referral.reward.status.pendingInvitee": "Einlösen", + "workspace.referral.apply.noGo": "Go abonnieren", + "workspace.referral.apply.preview": "Vorschau", + "workspace.referral.apply.action": "Einlösen", + "workspace.referral.apply.confirmTitle": "Go-Belohnung einlösen", + "workspace.referral.apply.confirmBody": "Löse {{amount}} ein, um die aktuellen Go-Nutzungszähler dieses Workspace zu reduzieren.", + "workspace.referral.apply.confirmAction": "Einlösen", + "download.title": "OpenCode | Download", "download.meta.description": "Lade OpenCode für macOS, Windows und Linux herunter", "download.hero.title": "OpenCode herunterladen", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index b7ef397be6dd..d8a81848a4ce 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -662,6 +662,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Other payment methods", "workspace.lite.promo.selectMethod": "Select payment method", + "workspace.referral.copyLink": "Copy Link", + "workspace.referral.copied": "Copied", + "workspace.referral.overview.title": "Invite friends to Go", + "workspace.referral.overview.subtitle": "Earn $5 in Go credit when a friend subscribes. They’ll get $5 too.", + "workspace.referral.stats.invites": "Invites", + "workspace.referral.stats.earned": "Earned", + "workspace.referral.stats.applied": "Applied", + "workspace.referral.instructions.share": "Share your invite link", + "workspace.referral.instructions.subscribe": "Your friend subscribes to Go", + "workspace.referral.instructions.claim": "Apply your $5 credit below", + "workspace.referral.rewards.title": "Referral rewards", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} rewards applied.", + "workspace.referral.rewards.empty": "No referral rewards yet.", + "workspace.referral.table.reward": "Reward", + "workspace.referral.table.referral": "Description", + "workspace.referral.table.date": "Date", + "workspace.referral.reward.source.pendingInviter": "Waiting for them to subscribe", + "workspace.referral.reward.source.pendingInvitee": "Subscribe to unlock reward", + "workspace.referral.reward.source.available": "Reward ready to apply", + "workspace.referral.reward.source.applied": "Reward applied", + "workspace.referral.reward.status.applied": "Applied", + "workspace.referral.reward.status.pendingInviter": "Apply", + "workspace.referral.reward.status.pendingInvitee": "Apply", + "workspace.referral.apply.noGo": "Subscribe to Go", + "workspace.referral.apply.preview": "Preview", + "workspace.referral.apply.action": "Apply", + "workspace.referral.apply.confirmTitle": "Apply Go reward", + "workspace.referral.apply.confirmBody": "Apply {{amount}} to reduce this workspace's current Go usage counters.", + "workspace.referral.apply.confirmAction": "Apply", + "download.title": "OpenCode | Download", "download.meta.description": "Download OpenCode for macOS, Windows, and Linux", "download.hero.title": "Download OpenCode", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index f6347d3b522d..071d3a2c76ec 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -670,6 +670,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Otros métodos de pago", "workspace.lite.promo.selectMethod": "Seleccionar método de pago", + "workspace.referral.copyLink": "Copiar enlace", + "workspace.referral.copied": "Copiado", + "workspace.referral.overview.title": "Invita amigos a Go", + "workspace.referral.overview.subtitle": "Gana $5 de crédito Go cuando un amigo se suscriba. Él también recibirá $5.", + "workspace.referral.stats.invites": "Invitaciones", + "workspace.referral.stats.earned": "Ganado", + "workspace.referral.stats.applied": "Aplicado", + "workspace.referral.instructions.share": "Comparte tu enlace de invitación", + "workspace.referral.instructions.subscribe": "Tu amigo se suscribe a Go", + "workspace.referral.instructions.claim": "Aplica tu crédito de $5 abajo", + "workspace.referral.rewards.title": "Recompensas por referidos", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} recompensas aplicadas.", + "workspace.referral.rewards.empty": "Aún no hay recompensas por referidos.", + "workspace.referral.table.reward": "Recompensa", + "workspace.referral.table.referral": "Descripción", + "workspace.referral.table.date": "Fecha", + "workspace.referral.reward.source.pendingInviter": "Esperando a que se suscriba", + "workspace.referral.reward.source.pendingInvitee": "Suscríbete para desbloquear la recompensa", + "workspace.referral.reward.source.available": "Recompensa lista para aplicar", + "workspace.referral.reward.source.applied": "Recompensa aplicada", + "workspace.referral.reward.status.applied": "Aplicada", + "workspace.referral.reward.status.pendingInviter": "Aplicar", + "workspace.referral.reward.status.pendingInvitee": "Aplicar", + "workspace.referral.apply.noGo": "Suscribirse a Go", + "workspace.referral.apply.preview": "Vista previa", + "workspace.referral.apply.action": "Aplicar", + "workspace.referral.apply.confirmTitle": "Aplicar recompensa de Go", + "workspace.referral.apply.confirmBody": "Aplica {{amount}} para reducir los contadores actuales de uso de Go de este workspace.", + "workspace.referral.apply.confirmAction": "Aplicar", + "download.title": "OpenCode | Descargar", "download.meta.description": "Descarga OpenCode para macOS, Windows y Linux", "download.hero.title": "Descargar OpenCode", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 5d1cd0fab73d..7ae31968fa59 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -676,6 +676,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Autres méthodes de paiement", "workspace.lite.promo.selectMethod": "Sélectionner la méthode de paiement", + "workspace.referral.copyLink": "Copier le lien", + "workspace.referral.copied": "Copié", + "workspace.referral.overview.title": "Inviter des amis sur Go", + "workspace.referral.overview.subtitle": "Gagnez $5 de crédit Go lorsqu'un ami s'abonne. Il recevra également $5.", + "workspace.referral.stats.invites": "Invitations", + "workspace.referral.stats.earned": "Gagné", + "workspace.referral.stats.applied": "Utilisé", + "workspace.referral.instructions.share": "Partagez votre lien d'invitation", + "workspace.referral.instructions.subscribe": "Votre ami s'abonne à Go", + "workspace.referral.instructions.claim": "Utilisez votre crédit de $5 ci-dessous", + "workspace.referral.rewards.title": "Récompenses de parrainage", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} récompenses utilisées.", + "workspace.referral.rewards.empty": "Aucune récompense de parrainage pour l'instant.", + "workspace.referral.table.reward": "Récompense", + "workspace.referral.table.referral": "Description", + "workspace.referral.table.date": "Date", + "workspace.referral.reward.source.pendingInviter": "En attente de son abonnement", + "workspace.referral.reward.source.pendingInvitee": "Abonnez-vous pour débloquer la récompense", + "workspace.referral.reward.source.available": "Récompense prête à utiliser", + "workspace.referral.reward.source.applied": "Récompense utilisée", + "workspace.referral.reward.status.applied": "Utilisée", + "workspace.referral.reward.status.pendingInviter": "Utiliser", + "workspace.referral.reward.status.pendingInvitee": "Utiliser", + "workspace.referral.apply.noGo": "S'abonner à Go", + "workspace.referral.apply.preview": "Aperçu", + "workspace.referral.apply.action": "Utiliser", + "workspace.referral.apply.confirmTitle": "Utiliser la récompense Go", + "workspace.referral.apply.confirmBody": "Utilisez {{amount}} pour réduire les compteurs d'utilisation Go actuels de ce workspace.", + "workspace.referral.apply.confirmAction": "Utiliser", + "download.title": "OpenCode | Téléchargement", "download.meta.description": "Téléchargez OpenCode pour macOS, Windows et Linux", "download.hero.title": "Télécharger OpenCode", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 07da9434eb6c..a2d300bede33 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -668,6 +668,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Altri metodi di pagamento", "workspace.lite.promo.selectMethod": "Seleziona metodo di pagamento", + "workspace.referral.copyLink": "Copia link", + "workspace.referral.copied": "Copiato", + "workspace.referral.overview.title": "Invita amici su Go", + "workspace.referral.overview.subtitle": "Guadagna $5 di credito Go quando un amico si abbona. Anche lui riceverà $5.", + "workspace.referral.stats.invites": "Inviti", + "workspace.referral.stats.earned": "Guadagnato", + "workspace.referral.stats.applied": "Utilizzato", + "workspace.referral.instructions.share": "Condividi il tuo link di invito", + "workspace.referral.instructions.subscribe": "Il tuo amico si abbona a Go", + "workspace.referral.instructions.claim": "Usa il tuo credito di $5 qui sotto", + "workspace.referral.rewards.title": "Premi referral", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} premi utilizzati.", + "workspace.referral.rewards.empty": "Nessun premio referral ancora.", + "workspace.referral.table.reward": "Premio", + "workspace.referral.table.referral": "Descrizione", + "workspace.referral.table.date": "Data", + "workspace.referral.reward.source.pendingInviter": "In attesa che si abboni", + "workspace.referral.reward.source.pendingInvitee": "Abbonati per sbloccare il premio", + "workspace.referral.reward.source.available": "Premio pronto da utilizzare", + "workspace.referral.reward.source.applied": "Premio utilizzato", + "workspace.referral.reward.status.applied": "Utilizzato", + "workspace.referral.reward.status.pendingInviter": "Utilizza", + "workspace.referral.reward.status.pendingInvitee": "Utilizza", + "workspace.referral.apply.noGo": "Abbonati a Go", + "workspace.referral.apply.preview": "Anteprima", + "workspace.referral.apply.action": "Utilizza", + "workspace.referral.apply.confirmTitle": "Utilizza premio Go", + "workspace.referral.apply.confirmBody": "Utilizza {{amount}} per ridurre i contatori di utilizzo Go attuali di questo workspace.", + "workspace.referral.apply.confirmAction": "Utilizza", + "download.title": "OpenCode | Download", "download.meta.description": "Scarica OpenCode per macOS, Windows e Linux", "download.hero.title": "Scarica OpenCode", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index 975728fe7e58..003682ec53cb 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -668,6 +668,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "その他の支払い方法", "workspace.lite.promo.selectMethod": "支払い方法を選択", + "workspace.referral.copyLink": "リンクをコピー", + "workspace.referral.copied": "コピーしました", + "workspace.referral.overview.title": "友達を Go に招待", + "workspace.referral.overview.subtitle": "友達がサブスクライブすると $5 分の Go クレジットを獲得。友達にも $5 が付与されます。", + "workspace.referral.stats.invites": "招待", + "workspace.referral.stats.earned": "獲得", + "workspace.referral.stats.applied": "適用済み", + "workspace.referral.instructions.share": "招待リンクをシェア", + "workspace.referral.instructions.subscribe": "友達が Go にサブスクライブ", + "workspace.referral.instructions.claim": "下の $5 クレジットを適用", + "workspace.referral.rewards.title": "リファラル特典", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} 件の特典を適用済み。", + "workspace.referral.rewards.empty": "リファラル特典はまだありません。", + "workspace.referral.table.reward": "特典", + "workspace.referral.table.referral": "説明", + "workspace.referral.table.date": "日付", + "workspace.referral.reward.source.pendingInviter": "友達のサブスクライブ待ち", + "workspace.referral.reward.source.pendingInvitee": "サブスクライブして特典をアンロック", + "workspace.referral.reward.source.available": "特典は適用可能です", + "workspace.referral.reward.source.applied": "特典を適用済み", + "workspace.referral.reward.status.applied": "適用済み", + "workspace.referral.reward.status.pendingInviter": "適用", + "workspace.referral.reward.status.pendingInvitee": "適用", + "workspace.referral.apply.noGo": "Goを購読する", + "workspace.referral.apply.preview": "プレビュー", + "workspace.referral.apply.action": "適用", + "workspace.referral.apply.confirmTitle": "Go 特典を適用", + "workspace.referral.apply.confirmBody": "{{amount}} を適用して、このワークスペースの現在の Go 使用カウンターを減らします。", + "workspace.referral.apply.confirmAction": "適用", + "download.title": "OpenCode | ダウンロード", "download.meta.description": "OpenCode を macOS、Windows、Linux 向けにダウンロード", "download.hero.title": "OpenCode をダウンロード", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index 293c3eb7d990..cf7f0e7898cc 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -660,6 +660,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "기타 결제 수단", "workspace.lite.promo.selectMethod": "결제 수단 선택", + "workspace.referral.copyLink": "링크 복사", + "workspace.referral.copied": "복사됨", + "workspace.referral.overview.title": "친구를 Go에 초대", + "workspace.referral.overview.subtitle": "친구가 구독하면 $5의 Go 크레딧을 받으세요. 친구도 $5를 받습니다.", + "workspace.referral.stats.invites": "초대", + "workspace.referral.stats.earned": "획득", + "workspace.referral.stats.applied": "사용", + "workspace.referral.instructions.share": "초대 링크 공유", + "workspace.referral.instructions.subscribe": "친구가 Go를 구독", + "workspace.referral.instructions.claim": "아래에서 $5 크레딧 사용", + "workspace.referral.rewards.title": "추천 보상", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}}개 보상 사용됨.", + "workspace.referral.rewards.empty": "아직 추천 보상이 없습니다.", + "workspace.referral.table.reward": "보상", + "workspace.referral.table.referral": "설명", + "workspace.referral.table.date": "날짜", + "workspace.referral.reward.source.pendingInviter": "친구의 구독을 기다리는 중", + "workspace.referral.reward.source.pendingInvitee": "구독하여 보상 잠금 해제", + "workspace.referral.reward.source.available": "보상 사용 가능", + "workspace.referral.reward.source.applied": "보상 사용됨", + "workspace.referral.reward.status.applied": "사용됨", + "workspace.referral.reward.status.pendingInviter": "사용", + "workspace.referral.reward.status.pendingInvitee": "사용", + "workspace.referral.apply.noGo": "Go 구독하기", + "workspace.referral.apply.preview": "미리 보기", + "workspace.referral.apply.action": "사용", + "workspace.referral.apply.confirmTitle": "Go 보상 사용", + "workspace.referral.apply.confirmBody": "{{amount}}를 사용하여 이 워크스페이스의 현재 Go 사용 카운터를 줄입니다.", + "workspace.referral.apply.confirmAction": "사용", + "download.title": "OpenCode | 다운로드", "download.meta.description": "macOS, Windows, Linux용 OpenCode 다운로드", "download.hero.title": "OpenCode 다운로드", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 27b5522e3261..e80c3ff19ec4 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -667,6 +667,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Andre betalingsmetoder", "workspace.lite.promo.selectMethod": "Velg betalingsmetode", + "workspace.referral.copyLink": "Kopier lenke", + "workspace.referral.copied": "Kopiert", + "workspace.referral.overview.title": "Inviter venner til Go", + "workspace.referral.overview.subtitle": "Få $5 i Go-kreditt når en venn abonnerer. De får også $5.", + "workspace.referral.stats.invites": "Invitasjoner", + "workspace.referral.stats.earned": "Opptjent", + "workspace.referral.stats.applied": "Brukt", + "workspace.referral.instructions.share": "Del invitasjonslenken din", + "workspace.referral.instructions.subscribe": "Vennen din abonnerer på Go", + "workspace.referral.instructions.claim": "Bruk $5-kreditten din nedenfor", + "workspace.referral.rewards.title": "Henvisningsbelønninger", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} belønninger brukt.", + "workspace.referral.rewards.empty": "Ingen henvisningsbelønninger ennå.", + "workspace.referral.table.reward": "Belønning", + "workspace.referral.table.referral": "Beskrivelse", + "workspace.referral.table.date": "Dato", + "workspace.referral.reward.source.pendingInviter": "Venter på at de abonnerer", + "workspace.referral.reward.source.pendingInvitee": "Abonner for å låse opp belønningen", + "workspace.referral.reward.source.available": "Belønning klar til bruk", + "workspace.referral.reward.source.applied": "Belønning brukt", + "workspace.referral.reward.status.applied": "Brukt", + "workspace.referral.reward.status.pendingInviter": "Bruk", + "workspace.referral.reward.status.pendingInvitee": "Bruk", + "workspace.referral.apply.noGo": "Abonner på Go", + "workspace.referral.apply.preview": "Forhåndsvis", + "workspace.referral.apply.action": "Bruk", + "workspace.referral.apply.confirmTitle": "Bruk Go-belønning", + "workspace.referral.apply.confirmBody": "Bruk {{amount}} for å redusere dette workspacets nåværende Go-forbrukstellere.", + "workspace.referral.apply.confirmAction": "Bruk", + "download.title": "OpenCode | Last ned", "download.meta.description": "Last ned OpenCode for macOS, Windows og Linux", "download.hero.title": "Last ned OpenCode", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 7f8c84915658..8ec6c519ea1f 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -668,6 +668,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Inne metody płatności", "workspace.lite.promo.selectMethod": "Wybierz metodę płatności", + "workspace.referral.copyLink": "Kopiuj link", + "workspace.referral.copied": "Skopiowano", + "workspace.referral.overview.title": "Zaproś znajomych do Go", + "workspace.referral.overview.subtitle": "Zdobądź $5 kredytu Go, gdy znajomy się zasubskrybuje. On też dostanie $5.", + "workspace.referral.stats.invites": "Zaproszenia", + "workspace.referral.stats.earned": "Zdobyte", + "workspace.referral.stats.applied": "Wykorzystane", + "workspace.referral.instructions.share": "Udostępnij swój link z zaproszeniem", + "workspace.referral.instructions.subscribe": "Twój znajomy subskrybuje Go", + "workspace.referral.instructions.claim": "Wykorzystaj swój kredyt $5 poniżej", + "workspace.referral.rewards.title": "Nagrody za polecenia", + "workspace.referral.rewards.subtitle": "Wykorzystano {{applied}} / {{total}} nagród.", + "workspace.referral.rewards.empty": "Brak nagród za polecenia.", + "workspace.referral.table.reward": "Nagroda", + "workspace.referral.table.referral": "Opis", + "workspace.referral.table.date": "Data", + "workspace.referral.reward.source.pendingInviter": "Oczekiwanie na jego subskrypcję", + "workspace.referral.reward.source.pendingInvitee": "Subskrybuj, aby odblokować nagrodę", + "workspace.referral.reward.source.available": "Nagroda gotowa do wykorzystania", + "workspace.referral.reward.source.applied": "Nagroda wykorzystana", + "workspace.referral.reward.status.applied": "Wykorzystana", + "workspace.referral.reward.status.pendingInviter": "Wykorzystaj", + "workspace.referral.reward.status.pendingInvitee": "Wykorzystaj", + "workspace.referral.apply.noGo": "Subskrybuj Go", + "workspace.referral.apply.preview": "Podgląd", + "workspace.referral.apply.action": "Wykorzystaj", + "workspace.referral.apply.confirmTitle": "Wykorzystaj nagrodę Go", + "workspace.referral.apply.confirmBody": "Wykorzystaj {{amount}}, aby zmniejszyć aktualne liczniki użycia Go w tym workspace.", + "workspace.referral.apply.confirmAction": "Wykorzystaj", + "download.title": "OpenCode | Pobierz", "download.meta.description": "Pobierz OpenCode na macOS, Windows i Linux", "download.hero.title": "Pobierz OpenCode", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 4ac54c2ac0f6..9f0a108c04fa 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -674,6 +674,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Другие способы оплаты", "workspace.lite.promo.selectMethod": "Выберите способ оплаты", + "workspace.referral.copyLink": "Копировать ссылку", + "workspace.referral.copied": "Скопировано", + "workspace.referral.overview.title": "Пригласите друзей в Go", + "workspace.referral.overview.subtitle": "Получите $5 кредита Go, когда друг оформит подписку. Он тоже получит $5.", + "workspace.referral.stats.invites": "Приглашения", + "workspace.referral.stats.earned": "Заработано", + "workspace.referral.stats.applied": "Использовано", + "workspace.referral.instructions.share": "Поделитесь своей ссылкой-приглашением", + "workspace.referral.instructions.subscribe": "Ваш друг оформляет подписку на Go", + "workspace.referral.instructions.claim": "Используйте свой кредит $5 ниже", + "workspace.referral.rewards.title": "Реферальные награды", + "workspace.referral.rewards.subtitle": "Использовано {{applied}} / {{total}} наград.", + "workspace.referral.rewards.empty": "Реферальных наград пока нет.", + "workspace.referral.table.reward": "Награда", + "workspace.referral.table.referral": "Описание", + "workspace.referral.table.date": "Дата", + "workspace.referral.reward.source.pendingInviter": "Ожидание его подписки", + "workspace.referral.reward.source.pendingInvitee": "Подпишитесь, чтобы разблокировать награду", + "workspace.referral.reward.source.available": "Награда готова к применению", + "workspace.referral.reward.source.applied": "Награда использована", + "workspace.referral.reward.status.applied": "Использована", + "workspace.referral.reward.status.pendingInviter": "Применить", + "workspace.referral.reward.status.pendingInvitee": "Применить", + "workspace.referral.apply.noGo": "Подписаться на Go", + "workspace.referral.apply.preview": "Предпросмотр", + "workspace.referral.apply.action": "Применить", + "workspace.referral.apply.confirmTitle": "Применить награду Go", + "workspace.referral.apply.confirmBody": "Используйте {{amount}}, чтобы уменьшить текущие счётчики использования Go этого workspace.", + "workspace.referral.apply.confirmAction": "Применить", + "download.title": "OpenCode | Скачать", "download.meta.description": "Скачать OpenCode для macOS, Windows и Linux", "download.hero.title": "Скачать OpenCode", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index 280b9d9fa8c3..4f06b931d8ac 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -663,6 +663,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "วิธีการชำระเงินอื่นๆ", "workspace.lite.promo.selectMethod": "เลือกวิธีการชำระเงิน", + "workspace.referral.copyLink": "คัดลอกลิงก์", + "workspace.referral.copied": "คัดลอกแล้ว", + "workspace.referral.overview.title": "ชวนเพื่อนมาใช้ Go", + "workspace.referral.overview.subtitle": "รับเครดิต Go $5 เมื่อเพื่อนสมัครสมาชิก เพื่อนก็จะได้รับ $5 เช่นกัน", + "workspace.referral.stats.invites": "คำเชิญ", + "workspace.referral.stats.earned": "ได้รับ", + "workspace.referral.stats.applied": "ใช้แล้ว", + "workspace.referral.instructions.share": "แชร์ลิงก์เชิญของคุณ", + "workspace.referral.instructions.subscribe": "เพื่อนของคุณสมัครสมาชิก Go", + "workspace.referral.instructions.claim": "ใช้เครดิต $5 ของคุณด้านล่าง", + "workspace.referral.rewards.title": "รางวัลการแนะนำ", + "workspace.referral.rewards.subtitle": "ใช้แล้ว {{applied}} / {{total}} รางวัล", + "workspace.referral.rewards.empty": "ยังไม่มีรางวัลการแนะนำ", + "workspace.referral.table.reward": "รางวัล", + "workspace.referral.table.referral": "คำอธิบาย", + "workspace.referral.table.date": "วันที่", + "workspace.referral.reward.source.pendingInviter": "รอเพื่อนสมัครสมาชิก", + "workspace.referral.reward.source.pendingInvitee": "สมัครสมาชิกเพื่อปลดล็อกรางวัล", + "workspace.referral.reward.source.available": "รางวัลพร้อมใช้งาน", + "workspace.referral.reward.source.applied": "ใช้รางวัลแล้ว", + "workspace.referral.reward.status.applied": "ใช้แล้ว", + "workspace.referral.reward.status.pendingInviter": "ใช้", + "workspace.referral.reward.status.pendingInvitee": "ใช้", + "workspace.referral.apply.noGo": "สมัครสมาชิก Go", + "workspace.referral.apply.preview": "ดูตัวอย่าง", + "workspace.referral.apply.action": "ใช้", + "workspace.referral.apply.confirmTitle": "ใช้รางวัล Go", + "workspace.referral.apply.confirmBody": "ใช้ {{amount}} เพื่อลดตัวนับการใช้งาน Go ปัจจุบันของ workspace นี้", + "workspace.referral.apply.confirmAction": "ใช้", + "download.title": "OpenCode | ดาวน์โหลด", "download.meta.description": "ดาวน์โหลด OpenCode สำหรับ macOS, Windows และ Linux", "download.hero.title": "ดาวน์โหลด OpenCode", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index a8f449dc471b..f6cf2d2b6a4c 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -670,6 +670,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "Diğer ödeme yöntemleri", "workspace.lite.promo.selectMethod": "Ödeme yöntemini seçin", + "workspace.referral.copyLink": "Bağlantıyı Kopyala", + "workspace.referral.copied": "Kopyalandı", + "workspace.referral.overview.title": "Arkadaşlarını Go'ya davet et", + "workspace.referral.overview.subtitle": "Bir arkadaşın abone olduğunda $5 Go kredisi kazan. O da $5 alacak.", + "workspace.referral.stats.invites": "Davetler", + "workspace.referral.stats.earned": "Kazanılan", + "workspace.referral.stats.applied": "Kullanılan", + "workspace.referral.instructions.share": "Davet bağlantını paylaş", + "workspace.referral.instructions.subscribe": "Arkadaşın Go'ya abone olur", + "workspace.referral.instructions.claim": "Aşağıdaki $5 kredini kullan", + "workspace.referral.rewards.title": "Davet ödülleri", + "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} ödül kullanıldı.", + "workspace.referral.rewards.empty": "Henüz davet ödülü yok.", + "workspace.referral.table.reward": "Ödül", + "workspace.referral.table.referral": "Açıklama", + "workspace.referral.table.date": "Tarih", + "workspace.referral.reward.source.pendingInviter": "Abone olması bekleniyor", + "workspace.referral.reward.source.pendingInvitee": "Ödülün kilidini açmak için abone ol", + "workspace.referral.reward.source.available": "Ödül kullanıma hazır", + "workspace.referral.reward.source.applied": "Ödül kullanıldı", + "workspace.referral.reward.status.applied": "Kullanıldı", + "workspace.referral.reward.status.pendingInviter": "Kullan", + "workspace.referral.reward.status.pendingInvitee": "Kullan", + "workspace.referral.apply.noGo": "Go'ya Abone Ol", + "workspace.referral.apply.preview": "Önizleme", + "workspace.referral.apply.action": "Kullan", + "workspace.referral.apply.confirmTitle": "Go ödülünü kullan", + "workspace.referral.apply.confirmBody": "Bu workspace'in mevcut Go kullanım sayaçlarını azaltmak için {{amount}} kullan.", + "workspace.referral.apply.confirmAction": "Kullan", + "download.title": "OpenCode | İndir", "download.meta.description": "OpenCode'u macOS, Windows ve Linux için indirin", "download.hero.title": "OpenCode'u İndir", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index ced0060ca02f..2d8d22618a29 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -643,6 +643,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "其他付款方式", "workspace.lite.promo.selectMethod": "选择付款方式", + "workspace.referral.copyLink": "复制链接", + "workspace.referral.copied": "已复制", + "workspace.referral.overview.title": "邀请好友使用 Go", + "workspace.referral.overview.subtitle": "好友订阅后,您可获得 $5 Go 抵用金,对方也可获得 $5。", + "workspace.referral.stats.invites": "邀请", + "workspace.referral.stats.earned": "已获得", + "workspace.referral.stats.applied": "已使用", + "workspace.referral.instructions.share": "分享您的邀请链接。", + "workspace.referral.instructions.subscribe": "好友订阅 Go。", + "workspace.referral.instructions.claim": "在下方使用您的 $5 抵用金。", + "workspace.referral.rewards.title": "邀请奖励", + "workspace.referral.rewards.subtitle": "已使用 {{applied}} / {{total}} 个奖励。", + "workspace.referral.rewards.empty": "暂无邀请奖励。", + "workspace.referral.table.reward": "奖励", + "workspace.referral.table.referral": "描述", + "workspace.referral.table.date": "日期", + "workspace.referral.reward.source.pendingInviter": "等待对方订阅", + "workspace.referral.reward.source.pendingInvitee": "订阅即可解锁奖励", + "workspace.referral.reward.source.available": "奖励可使用", + "workspace.referral.reward.source.applied": "奖励已使用", + "workspace.referral.reward.status.applied": "已使用", + "workspace.referral.reward.status.pendingInviter": "等待订阅", + "workspace.referral.reward.status.pendingInvitee": "订阅后解锁", + "workspace.referral.apply.noGo": "订阅 Go", + "workspace.referral.apply.preview": "预览", + "workspace.referral.apply.action": "使用", + "workspace.referral.apply.confirmTitle": "使用 Go 奖励", + "workspace.referral.apply.confirmBody": "使用 {{amount}} 抵扣当前工作区的 Go 用量计数。", + "workspace.referral.apply.confirmAction": "使用", + "download.title": "OpenCode | 下载", "download.meta.description": "下载适用于 macOS, Windows, 和 Linux 的 OpenCode", "download.hero.title": "下载 OpenCode", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index e3e374a329ca..54376d73c7a3 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -643,6 +643,36 @@ export const dict = { "workspace.lite.promo.otherMethods": "其他付款方式", "workspace.lite.promo.selectMethod": "選擇付款方式", + "workspace.referral.copyLink": "複製連結", + "workspace.referral.copied": "已複製", + "workspace.referral.overview.title": "邀請朋友使用 Go", + "workspace.referral.overview.subtitle": "朋友訂閱後,您可獲得 $5 Go 抵用金,對方也可獲得 $5。", + "workspace.referral.stats.invites": "邀請", + "workspace.referral.stats.earned": "已獲得", + "workspace.referral.stats.applied": "已使用", + "workspace.referral.instructions.share": "分享您的邀請連結。", + "workspace.referral.instructions.subscribe": "朋友訂閱 Go。", + "workspace.referral.instructions.claim": "在下方使用您的 $5 抵用金。", + "workspace.referral.rewards.title": "邀請獎勵", + "workspace.referral.rewards.subtitle": "已使用 {{applied}} / {{total}} 個獎勵。", + "workspace.referral.rewards.empty": "暫無邀請獎勵。", + "workspace.referral.table.reward": "獎勵", + "workspace.referral.table.referral": "描述", + "workspace.referral.table.date": "日期", + "workspace.referral.reward.source.pendingInviter": "等待對方訂閱", + "workspace.referral.reward.source.pendingInvitee": "訂閱即可解鎖獎勵", + "workspace.referral.reward.source.available": "獎勵可使用", + "workspace.referral.reward.source.applied": "獎勵已使用", + "workspace.referral.reward.status.applied": "已使用", + "workspace.referral.reward.status.pendingInviter": "等待訂閱", + "workspace.referral.reward.status.pendingInvitee": "訂閱後解鎖", + "workspace.referral.apply.noGo": "訂閱 Go", + "workspace.referral.apply.preview": "預覽", + "workspace.referral.apply.action": "使用", + "workspace.referral.apply.confirmTitle": "使用 Go 獎勵", + "workspace.referral.apply.confirmBody": "使用 {{amount}} 抵扣目前工作區的 Go 用量計數。", + "workspace.referral.apply.confirmAction": "使用", + "download.title": "OpenCode | 下載", "download.meta.description": "下載適用於 macOS、Windows 與 Linux 的 OpenCode", "download.hero.title": "下載 OpenCode", diff --git a/packages/console/app/src/lib/format-reset-time.ts b/packages/console/app/src/lib/format-reset-time.ts new file mode 100644 index 000000000000..2462a87a3f4b --- /dev/null +++ b/packages/console/app/src/lib/format-reset-time.ts @@ -0,0 +1,47 @@ +import type { Key } from "~/i18n" +import type { useI18n } from "~/context/i18n" + +type ResetTimeKeys = { + day: Key + days: Key + hour: Key + hours: Key + minute: Key + minutes: Key + fewSeconds: Key +} + +export const liteResetTimeKeys = { + day: "workspace.lite.time.day", + days: "workspace.lite.time.days", + hour: "workspace.lite.time.hour", + hours: "workspace.lite.time.hours", + minute: "workspace.lite.time.minute", + minutes: "workspace.lite.time.minutes", + fewSeconds: "workspace.lite.time.fewSeconds", +} satisfies ResetTimeKeys + +export const blackResetTimeKeys = { + day: "workspace.black.time.day", + days: "workspace.black.time.days", + hour: "workspace.black.time.hour", + hours: "workspace.black.time.hours", + minute: "workspace.black.time.minute", + minutes: "workspace.black.time.minutes", + fewSeconds: "workspace.black.time.fewSeconds", +} satisfies ResetTimeKeys + +export function formatResetTime(seconds: number, i18n: ReturnType, keys: ResetTimeKeys) { + const days = Math.floor(seconds / 86400) + if (days >= 1) { + const hours = Math.floor((seconds % 86400) / 3600) + return `${days} ${days === 1 ? i18n.t(keys.day) : i18n.t(keys.days)} ${hours} ${hours === 1 ? i18n.t(keys.hour) : i18n.t(keys.hours)}` + } + + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + if (hours >= 1) + return `${hours} ${hours === 1 ? i18n.t(keys.hour) : i18n.t(keys.hours)} ${minutes} ${minutes === 1 ? i18n.t(keys.minute) : i18n.t(keys.minutes)}` + if (minutes === 0) return i18n.t(keys.fewSeconds) + return `${minutes} ${minutes === 1 ? i18n.t(keys.minute) : i18n.t(keys.minutes)}` +} diff --git a/packages/console/app/src/lib/referral-invite.ts b/packages/console/app/src/lib/referral-invite.ts new file mode 100644 index 000000000000..b17a0fc3c5df --- /dev/null +++ b/packages/console/app/src/lib/referral-invite.ts @@ -0,0 +1,28 @@ +import { Referral } from "@opencode-ai/console-core/referral.js" + +const REFERRAL_COOKIE = "oc_referral" +const REFERRAL_MAX_AGE = 60 * 60 * 24 * 30 + +export function normalizeReferralCode(code?: string | null) { + return Referral.normalizeCode(code) +} + +export function referralCookie(code: string) { + return `${REFERRAL_COOKIE}=${encodeURIComponent(code)}; Path=/; Max-Age=${REFERRAL_MAX_AGE}; SameSite=Lax; HttpOnly` +} + +export function clearReferralCookie() { + return `${REFERRAL_COOKIE}=; Path=/; Max-Age=0; SameSite=Lax; HttpOnly` +} + +export function referralCodeFromCookieHeader(header: string | null) { + if (!header) return undefined + + return normalizeReferralCode( + header + .split(";") + .map((x) => x.trim()) + .find((x) => x.startsWith(`${REFERRAL_COOKIE}=`)) + ?.slice(`${REFERRAL_COOKIE}=`.length), + ) +} diff --git a/packages/console/app/src/middleware.ts b/packages/console/app/src/middleware.ts index 750b0d9674bd..ad5aa09e2ab9 100644 --- a/packages/console/app/src/middleware.ts +++ b/packages/console/app/src/middleware.ts @@ -1,16 +1,20 @@ import { createMiddleware } from "@solidjs/start/middleware" import { LOCALE_HEADER, cookie, fromPathname, strip } from "~/lib/language" +import { normalizeReferralCode, referralCookie } from "~/lib/referral-invite" export default createMiddleware({ onRequest(event) { const url = new URL(event.request.url) const locale = fromPathname(url.pathname) - if (!locale) return + if (locale) { + url.pathname = strip(url.pathname) + const request = new Request(url, event.request) + request.headers.set(LOCALE_HEADER, locale) + event.request = request + event.response.headers.append("set-cookie", cookie(locale)) + } - url.pathname = strip(url.pathname) - const request = new Request(url, event.request) - request.headers.set(LOCALE_HEADER, locale) - event.request = request - event.response.headers.append("set-cookie", cookie(locale)) + const referralCode = normalizeReferralCode(url.searchParams.get("ref")) + if (referralCode) event.response.headers.append("set-cookie", referralCookie(referralCode)) }, }) diff --git a/packages/console/app/src/routes/auth/[...callback].ts b/packages/console/app/src/routes/auth/[...callback].ts index 00bb89406fc8..b1b942ee72a0 100644 --- a/packages/console/app/src/routes/auth/[...callback].ts +++ b/packages/console/app/src/routes/auth/[...callback].ts @@ -1,9 +1,11 @@ import { redirect } from "@solidjs/router" import type { APIEvent } from "@solidjs/start/server" +import { Referral } from "@opencode-ai/console-core/referral.js" import { AuthClient } from "~/context/auth" import { useAuthSession } from "~/context/auth" import { i18n } from "~/i18n" import { localeFromRequest, route } from "~/lib/language" +import { clearReferralCookie, referralCodeFromCookieHeader } from "~/lib/referral-invite" export async function GET(input: APIEvent) { const url = new URL(input.request.url) @@ -17,6 +19,7 @@ export async function GET(input: APIEvent) { if (result.err) throw new Error(result.err.message) const decoded = AuthClient.decode(result.tokens.access, {} as any) if (decoded.err) throw new Error(decoded.err.message) + const referralCode = referralCodeFromCookieHeader(input.request.headers.get("cookie")) const session = await useAuthSession() const id = decoded.subject.properties.accountID await session.update((value) => { @@ -32,8 +35,15 @@ export async function GET(input: APIEvent) { current: id, } }) + if (decoded.subject.properties.newAccount && referralCode) { + await Referral.createFromAccount({ accountID: id, referralCode }).catch((error) => { + console.error("Referral create failed", error) + }) + } const next = url.pathname === "/auth/callback" ? "/auth" : url.pathname.replace("/auth/callback", "") - return redirect(route(locale, next)) + const response = redirect(route(locale, next)) + if (referralCode) response.headers.append("set-cookie", clearReferralCookie()) + return response } catch (e: any) { return new Response( JSON.stringify({ diff --git a/packages/console/app/src/routes/black.css b/packages/console/app/src/routes/black.css index 4031a78fc33e..fa78eee5608c 100644 --- a/packages/console/app/src/routes/black.css +++ b/packages/console/app/src/routes/black.css @@ -701,64 +701,6 @@ } } - [data-slot="workspace-picker"] { - [data-slot="workspace-list"] { - width: 100%; - padding: 0; - margin: 0; - list-style: none; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; - align-self: stretch; - outline: none; - overflow-y: auto; - max-height: 240px; - scrollbar-width: none; - - &::-webkit-scrollbar { - display: none; - } - - [data-slot="workspace-item"] { - width: 100%; - display: flex; - padding: 8px 12px; - align-items: center; - gap: 8px; - align-self: stretch; - cursor: pointer; - - [data-slot="selected-icon"] { - visibility: hidden; - color: rgba(255, 255, 255, 0.39); - font-family: "IBM Plex Mono", monospace; - font-size: 16px; - font-style: normal; - font-weight: 400; - line-height: 160%; - } - - span:last-child { - color: rgba(255, 255, 255, 0.92); - font-size: 16px; - font-style: normal; - font-weight: 400; - line-height: 160%; - } - - &:hover, - &[data-active="true"] { - background: #161616; - - [data-slot="selected-icon"] { - visibility: visible; - } - } - } - } - } } } @@ -839,3 +781,64 @@ } } } + +[data-component="black-workspace-picker-modal"] { + font-family: var(--font-mono); + + [data-slot="workspace-list"] { + width: 100%; + padding: 0; + margin: 0; + list-style: none; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + align-self: stretch; + outline: none; + overflow-y: auto; + max-height: 240px; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } + + [data-slot="workspace-item"] { + width: 100%; + display: flex; + padding: 8px 12px; + align-items: center; + gap: 8px; + align-self: stretch; + cursor: pointer; + + [data-slot="selected-icon"] { + visibility: hidden; + color: rgba(255, 255, 255, 0.39); + font-family: "IBM Plex Mono", monospace; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 160%; + } + + span:last-child { + color: rgba(255, 255, 255, 0.92); + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 160%; + } + + &:hover, + &[data-active="true"] { + background: #161616; + + [data-slot="selected-icon"] { + visibility: visible; + } + } + } +} diff --git a/packages/console/app/src/routes/black/subscribe/[plan].tsx b/packages/console/app/src/routes/black/subscribe/[plan].tsx index 52e6408761ff..c29c5fac80a9 100644 --- a/packages/console/app/src/routes/black/subscribe/[plan].tsx +++ b/packages/console/app/src/routes/black/subscribe/[plan].tsx @@ -444,8 +444,13 @@ export default function BlackSubscribe() {
{/* Workspace picker modal */} - {}} title={i18n.t("black.workspace.selectPlan")}> -
+ {}} + title={i18n.t("black.workspace.selectPlan")} + variant="black" + > +
    checkLoggedIn()) - const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/go` : "/auth")) + const referralCode = createMemo(() => new URLSearchParams(location.search).get("ref") ?? undefined) + const subscribeUrl = createMemo(() => { + const code = referralCode() + const referral = code ? `?ref=${encodeURIComponent(code)}` : "" + if (workspaceID()) return `/workspace/${workspaceID()}/go${referral}` + return `/auth${referral}` + }) const i18n = useI18n() const language = useLanguage() return ( diff --git a/packages/console/app/src/routes/stripe/webhook.ts b/packages/console/app/src/routes/stripe/webhook.ts index 58522cacc46f..dfff3bd809b4 100644 --- a/packages/console/app/src/routes/stripe/webhook.ts +++ b/packages/console/app/src/routes/stripe/webhook.ts @@ -9,7 +9,7 @@ import { Actor } from "@opencode-ai/console-core/actor.js" import { Resource } from "@opencode-ai/console-resource" import { LiteData } from "@opencode-ai/console-core/lite.js" import { BlackData } from "@opencode-ai/console-core/black.js" -import { User } from "@opencode-ai/console-core/user.js" +import { Referral } from "@opencode-ai/console-core/referral.js" export async function POST(input: APIEvent) { const body = await Billing.stripe().webhooks.constructEventAsync( @@ -174,6 +174,13 @@ export async function POST(input: APIEvent) { } } }) + + await Referral.completeFromLiteSubscription({ + workspaceID, + userID, + }).catch((error) => { + console.error("Referral sync failed", error) + }) }) } } diff --git a/packages/console/app/src/routes/workspace-picker.css b/packages/console/app/src/routes/workspace-picker.css index dd0aafaf5b23..df7552fb759d 100644 --- a/packages/console/app/src/routes/workspace-picker.css +++ b/packages/console/app/src/routes/workspace-picker.css @@ -34,6 +34,10 @@ background-color: var(--color-bg-surface); } } +} + +[data-component="workspace-create-modal"] { + width: 100%; [data-slot="create-form"] { width: 100%; diff --git a/packages/console/app/src/routes/workspace-picker.tsx b/packages/console/app/src/routes/workspace-picker.tsx index 8778abefd153..83f5582ebe09 100644 --- a/packages/console/app/src/routes/workspace-picker.tsx +++ b/packages/console/app/src/routes/workspace-picker.tsx @@ -1,6 +1,5 @@ import { query, useParams, action, createAsync, redirect, useSubmission } from "@solidjs/router" -import { For, createEffect } from "solid-js" -import { createStore } from "solid-js/store" +import { For, createEffect, createSignal } from "solid-js" import { withActor } from "~/context/auth.withActor" import { Actor } from "@opencode-ai/console-core/actor.js" import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js" @@ -51,9 +50,7 @@ export function WorkspacePicker() { const i18n = useI18n() const workspaces = createAsync(() => getWorkspaces()) const submission = useSubmission(createWorkspace) - const [store, setStore] = createStore({ - showForm: false, - }) + const [showForm, setShowForm] = createSignal(false) let inputRef: HTMLInputElement | undefined const currentWorkspace = () => { @@ -61,12 +58,8 @@ export function WorkspacePicker() { return ws ? ws.name : i18n.t("workspace.select") } - const handleWorkspaceNew = () => { - setStore("showForm", true) - } - createEffect(() => { - if (store.showForm && inputRef) { + if (showForm() && inputRef) { setTimeout(() => inputRef?.focus(), 0) } }) @@ -79,7 +72,7 @@ export function WorkspacePicker() { // Reset signals when workspace ID changes createEffect(() => { params.id - setStore("showForm", false) + setShowForm(false) }) return ( @@ -92,32 +85,34 @@ export function WorkspacePicker() { )} - - setStore("showForm", false)} title={i18n.t("workspace.modal.title")}> -
    -
    - -
    - - + setShowForm(false)} title={i18n.t("workspace.modal.title")}> +
    + +
    + +
    + + +
    -
    - + +
    ) diff --git a/packages/console/app/src/routes/workspace.css b/packages/console/app/src/routes/workspace.css index ddb13ab743ff..7bd7e1e579c4 100644 --- a/packages/console/app/src/routes/workspace.css +++ b/packages/console/app/src/routes/workspace.css @@ -25,6 +25,7 @@ &:disabled { opacity: 0.5; + cursor: not-allowed; transform: none; } diff --git a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx index 5b4389466e82..ee3c1fc8307f 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx @@ -13,6 +13,7 @@ import styles from "./black-section.module.css" import waitlistStyles from "./black-waitlist-section.module.css" import { useI18n } from "~/context/i18n" import { formError } from "~/lib/form-error" +import { blackResetTimeKeys, formatResetTime } from "~/lib/format-reset-time" const querySubscription = query(async (workspaceID: string) => { "use server" @@ -52,20 +53,6 @@ const querySubscription = query(async (workspaceID: string) => { }, workspaceID) }, "subscription.get") -function formatResetTime(seconds: number, i18n: ReturnType) { - const days = Math.floor(seconds / 86400) - if (days >= 1) { - const hours = Math.floor((seconds % 86400) / 3600) - return `${days} ${days === 1 ? i18n.t("workspace.black.time.day") : i18n.t("workspace.black.time.days")} ${hours} ${hours === 1 ? i18n.t("workspace.black.time.hour") : i18n.t("workspace.black.time.hours")}` - } - const hours = Math.floor(seconds / 3600) - const minutes = Math.floor((seconds % 3600) / 60) - if (hours >= 1) - return `${hours} ${hours === 1 ? i18n.t("workspace.black.time.hour") : i18n.t("workspace.black.time.hours")} ${minutes} ${minutes === 1 ? i18n.t("workspace.black.time.minute") : i18n.t("workspace.black.time.minutes")}` - if (minutes === 0) return i18n.t("workspace.black.time.fewSeconds") - return `${minutes} ${minutes === 1 ? i18n.t("workspace.black.time.minute") : i18n.t("workspace.black.time.minutes")}` -} - const cancelWaitlist = action(async (workspaceID: string) => { "use server" return json( @@ -209,7 +196,7 @@ export function BlackSection() {
{i18n.t("workspace.black.subscription.resetsIn")}{" "} - {formatResetTime(sub().rollingUsage.resetInSec, i18n)} + {formatResetTime(sub().rollingUsage.resetInSec, i18n, blackResetTimeKeys)}
@@ -222,7 +209,7 @@ export function BlackSection() {
{i18n.t("workspace.black.subscription.resetsIn")}{" "} - {formatResetTime(sub().weeklyUsage.resetInSec, i18n)} + {formatResetTime(sub().weeklyUsage.resetInSec, i18n, blackResetTimeKeys)}
diff --git a/packages/console/app/src/routes/workspace/[id]/go/index.tsx b/packages/console/app/src/routes/workspace/[id]/go/index.tsx index fb89e3c7025a..bf4c394137be 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/index.tsx @@ -1,11 +1,16 @@ +import { createAsync, useParams } from "@solidjs/router" +import { Show } from "solid-js" import { IconGo } from "~/component/icon" +import { GoReferralSection, queryGoReferral } from "~/component/go-referral" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { LiteSection } from "./lite-section" export default function () { + const params = useParams() const i18n = useI18n() const language = useLanguage() + const referral = createAsync(() => queryGoReferral(params.id!)) return (
@@ -24,6 +29,13 @@ export default function () {
+ {i18n.t("workspace.lite.loading")}}> + {(summary) => ( + 0}> + + + )} +
) diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css index 05daf43b7a97..0904f8b4aa33 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css @@ -212,6 +212,9 @@ gap: 4px; } +} + +.paymentMethodModal { [data-slot="modal-actions"] { display: flex; gap: var(--space-3); diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index eba52b0e1794..04d45ce25811 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -14,10 +14,11 @@ import styles from "./lite-section.module.css" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { formError } from "~/lib/form-error" +import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time" import { IconAlipay, IconUpi } from "~/component/icon" -const queryLiteSubscription = query(async (workspaceID: string) => { +export const queryLiteSubscription = query(async (workspaceID: string) => { "use server" return withActor(async () => { const row = await Database.use((tx) => @@ -67,20 +68,6 @@ const queryLiteSubscription = query(async (workspaceID: string) => { }, workspaceID) }, "lite.subscription.get") -function formatResetTime(seconds: number, i18n: ReturnType) { - const days = Math.floor(seconds / 86400) - if (days >= 1) { - const hours = Math.floor((seconds % 86400) / 3600) - return `${days} ${days === 1 ? i18n.t("workspace.lite.time.day") : i18n.t("workspace.lite.time.days")} ${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")}` - } - const hours = Math.floor(seconds / 3600) - const minutes = Math.floor((seconds % 3600) / 60) - if (hours >= 1) - return `${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")} ${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}` - if (minutes === 0) return i18n.t("workspace.lite.time.fewSeconds") - return `${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}` -} - const createLiteCheckoutUrl = action( async (workspaceID: string, successUrl: string, cancelUrl: string, method?: "alipay" | "upi") => { "use server" @@ -140,6 +127,25 @@ const setLiteUseBalance = action(async (form: FormData) => { ) }, "setLiteUseBalance") +function LiteUsageItem(props: { label: string; usage: { usagePercent: number; resetInSec: number } }) { + const i18n = useI18n() + + return ( +
+
+ {props.label} + {props.usage.usagePercent}% +
+
+
+
+ + {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(props.usage.resetInSec, i18n, liteResetTimeKeys)} + +
+ ) +} + export function LiteSection() { const params = useParams() const i18n = useI18n() @@ -207,44 +213,9 @@ export function LiteSection() { .
-
-
- {i18n.t("workspace.lite.subscription.rollingUsage")} - {sub().rollingUsage.usagePercent}% -
-
-
-
- - {i18n.t("workspace.lite.subscription.resetsIn")}{" "} - {formatResetTime(sub().rollingUsage.resetInSec, i18n)} - -
-
-
- {i18n.t("workspace.lite.subscription.weeklyUsage")} - {sub().weeklyUsage.usagePercent}% -
-
-
-
- - {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(sub().weeklyUsage.resetInSec, i18n)} - -
-
-
- {i18n.t("workspace.lite.subscription.monthlyUsage")} - {sub().monthlyUsage.usagePercent}% -
-
-
-
- - {i18n.t("workspace.lite.subscription.resetsIn")}{" "} - {formatResetTime(sub().monthlyUsage.resetInSec, i18n)} - -
+ + +

{i18n.t("workspace.lite.subscription.useBalance")}

@@ -330,31 +301,33 @@ export function LiteSection() { onClose={() => setStore("showModal", false)} title={i18n.t("workspace.lite.promo.selectMethod")} > -
- - +
+
+ + +
diff --git a/packages/console/core/migrations/20260513173355_groovy_jane_foster/migration.sql b/packages/console/core/migrations/20260513173355_groovy_jane_foster/migration.sql new file mode 100644 index 000000000000..76f85ee168d5 --- /dev/null +++ b/packages/console/core/migrations/20260513173355_groovy_jane_foster/migration.sql @@ -0,0 +1,47 @@ +CREATE TABLE `referral_code` ( + `id` varchar(30) NOT NULL, + `workspace_id` varchar(30) NOT NULL, + `time_created` timestamp(3) NOT NULL DEFAULT (now()), + `time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `time_deleted` timestamp(3), + `code` varchar(10) NOT NULL, + CONSTRAINT PRIMARY KEY(`workspace_id`,`id`), + CONSTRAINT `referral_code_workspace_id` UNIQUE INDEX(`workspace_id`), + CONSTRAINT `referral_code_code` UNIQUE INDEX(`code`) +); +--> statement-breakpoint +CREATE TABLE `referral_reward` ( + `id` varchar(30) NOT NULL, + `workspace_id` varchar(30) NOT NULL, + `time_created` timestamp(3) NOT NULL DEFAULT (now()), + `time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `time_deleted` timestamp(3), + `referral_id` varchar(30) NOT NULL, + `source` enum('inviter','invitee') NOT NULL, + `amount` bigint NOT NULL, + `applied_by_user_id` varchar(30), + `time_applied` timestamp(3), + CONSTRAINT PRIMARY KEY(`workspace_id`,`id`), + CONSTRAINT `referral_reward_referral_source` UNIQUE INDEX(`referral_id`,`source`) +); +--> statement-breakpoint +CREATE TABLE `referral` ( + `id` varchar(30) NOT NULL, + `workspace_id` varchar(30) NOT NULL, + `time_created` timestamp(3) NOT NULL DEFAULT (now()), + `time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + `time_deleted` timestamp(3), + `inviter_workspace_id` varchar(30) NOT NULL, + `invitee_account_id` varchar(30) NOT NULL, + `invitee_user_id` varchar(30) NOT NULL, + `referral_code_id` varchar(30) NOT NULL, + `stripe_customer_id` varchar(255) NOT NULL, + `stripe_subscription_id` varchar(255) NOT NULL, + CONSTRAINT PRIMARY KEY(`workspace_id`,`id`), + CONSTRAINT `referral_invitee_account_id` UNIQUE INDEX(`invitee_account_id`), + CONSTRAINT `referral_stripe_subscription_id` UNIQUE INDEX(`stripe_subscription_id`) +); +--> statement-breakpoint +CREATE INDEX `referral_reward_workspace_time` ON `referral_reward` (`workspace_id`,`time_created`);--> statement-breakpoint +CREATE INDEX `referral_inviter_workspace_id` ON `referral` (`inviter_workspace_id`);--> statement-breakpoint +CREATE INDEX `referral_code_id` ON `referral` (`referral_code_id`); diff --git a/packages/console/core/migrations/20260513173355_groovy_jane_foster/snapshot.json b/packages/console/core/migrations/20260513173355_groovy_jane_foster/snapshot.json new file mode 100644 index 000000000000..4ab0b08f7e30 --- /dev/null +++ b/packages/console/core/migrations/20260513173355_groovy_jane_foster/snapshot.json @@ -0,0 +1,3292 @@ +{ + "version": "6", + "dialect": "mysql", + "id": "91a9afa8-a5b1-4fa4-b71d-b3ae866744c9", + "prevIds": [ + "c742e0f2-5d89-4216-b843-059d00680f13" + ], + "ddl": [ + { + "name": "account", + "entityType": "tables" + }, + { + "name": "auth", + "entityType": "tables" + }, + { + "name": "benchmark", + "entityType": "tables" + }, + { + "name": "billing", + "entityType": "tables" + }, + { + "name": "coupon", + "entityType": "tables" + }, + { + "name": "lite", + "entityType": "tables" + }, + { + "name": "payment", + "entityType": "tables" + }, + { + "name": "subscription", + "entityType": "tables" + }, + { + "name": "usage", + "entityType": "tables" + }, + { + "name": "ip_rate_limit", + "entityType": "tables" + }, + { + "name": "ip", + "entityType": "tables" + }, + { + "name": "key_rate_limit", + "entityType": "tables" + }, + { + "name": "model_tpm_rate_limit", + "entityType": "tables" + }, + { + "name": "model_tps_rate_limit", + "entityType": "tables" + }, + { + "name": "key", + "entityType": "tables" + }, + { + "name": "model", + "entityType": "tables" + }, + { + "name": "provider", + "entityType": "tables" + }, + { + "name": "referral_code", + "entityType": "tables" + }, + { + "name": "referral_reward", + "entityType": "tables" + }, + { + "name": "referral", + "entityType": "tables" + }, + { + "name": "user", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "account" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "auth" + }, + { + "type": "enum('email','github','google')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subject", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "agent", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "mediumtext", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "result", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(32)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_type", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(4)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_last4", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "balance", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "boolean", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_trigger", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_amount", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_locked_till", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "enum('20','100','200')", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_plan", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_booked", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_selected", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite_subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "enum('BUILDATHON','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_redeemed", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "weekly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_weekly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invoice_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_refunded", + "entityType": "columns", + "table": "payment" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "fixed_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_fixed_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "input_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "output_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reasoning_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_read_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_5m_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_1h_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cost", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(10)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "ip" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "usage", + "entityType": "columns", + "table": "ip" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(40)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "qualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "unqualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_used", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "provider" + }, + { + "type": "text", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "credentials", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral_code" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral_code" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral_code" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral_code" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral_code" + }, + { + "type": "varchar(10)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "code", + "entityType": "columns", + "table": "referral_code" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "enum('inviter','invitee')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "source", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "applied_by_user_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_applied", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "inviter_workspace_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invitee_account_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invitee_user_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_code_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "stripe_customer_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "stripe_subscription_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_seen", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "color", + "entityType": "columns", + "table": "user" + }, + { + "type": "enum('admin','member')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "role", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "user" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "workspace" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "auth", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "benchmark", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "billing", + "entityType": "pks" + }, + { + "columns": [ + "email", + "type" + ], + "name": "PRIMARY", + "table": "coupon", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "lite", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "payment", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "subscription", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "usage", + "entityType": "pks" + }, + { + "columns": [ + "ip", + "interval" + ], + "name": "PRIMARY", + "table": "ip_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "ip" + ], + "name": "PRIMARY", + "table": "ip", + "entityType": "pks" + }, + { + "columns": [ + "key", + "interval" + ], + "name": "PRIMARY", + "table": "key_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tpm_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tps_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "key", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "model", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "provider", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "referral_code", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "referral_reward", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "referral", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "provider", + "isExpression": false + }, + { + "value": "subject", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "provider", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "account_id", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "time_created", + "entityType": "indexes", + "table": "benchmark" + }, + { + "columns": [ + { + "value": "customer_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_customer_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "subscription_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_subscription_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "lite" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "subscription" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "usage_time_created", + "entityType": "indexes", + "table": "usage" + }, + { + "columns": [ + { + "value": "key", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_key", + "entityType": "indexes", + "table": "key" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "model", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "model_workspace_model", + "entityType": "indexes", + "table": "model" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "provider", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_provider", + "entityType": "indexes", + "table": "provider" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_code_workspace_id", + "entityType": "indexes", + "table": "referral_code" + }, + { + "columns": [ + { + "value": "code", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_code_code", + "entityType": "indexes", + "table": "referral_code" + }, + { + "columns": [ + { + "value": "referral_id", + "isExpression": false + }, + { + "value": "source", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_reward_referral_source", + "entityType": "indexes", + "table": "referral_reward" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_reward_workspace_time", + "entityType": "indexes", + "table": "referral_reward" + }, + { + "columns": [ + { + "value": "invitee_account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_invitee_account_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "stripe_subscription_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_stripe_subscription_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "inviter_workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_inviter_workspace_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "referral_code_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_code_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "email", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "email", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "slug", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "slug", + "entityType": "indexes", + "table": "workspace" + } + ], + "renames": [] +} diff --git a/packages/console/core/migrations/20260516082200_long_spirit/migration.sql b/packages/console/core/migrations/20260516082200_long_spirit/migration.sql new file mode 100644 index 000000000000..471552c8b0ec --- /dev/null +++ b/packages/console/core/migrations/20260516082200_long_spirit/migration.sql @@ -0,0 +1,20 @@ +DROP TABLE `referral_code`;--> statement-breakpoint +DROP INDEX `referral_reward_referral_source` ON `referral_reward`;--> statement-breakpoint +DROP INDEX `referral_stripe_subscription_id` ON `referral`;--> statement-breakpoint +DROP INDEX `referral_inviter_workspace_id` ON `referral`;--> statement-breakpoint +DROP INDEX `referral_code_id` ON `referral`;--> statement-breakpoint +ALTER TABLE `referral_reward` DROP PRIMARY KEY;--> statement-breakpoint +ALTER TABLE `referral` DROP PRIMARY KEY;--> statement-breakpoint +ALTER TABLE `referral_reward` MODIFY COLUMN `workspace_id` varchar(30);--> statement-breakpoint +ALTER TABLE `workspace` ADD `referral_code` varchar(16);--> statement-breakpoint +ALTER TABLE `referral_reward` ADD PRIMARY KEY (`id`);--> statement-breakpoint +ALTER TABLE `referral` ADD PRIMARY KEY (`id`);--> statement-breakpoint +CREATE INDEX `referral_workspace_id` ON `referral` (`workspace_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `workspace_referral_code` ON `workspace` (`referral_code`);--> statement-breakpoint +ALTER TABLE `referral_reward` DROP COLUMN `source`;--> statement-breakpoint +ALTER TABLE `referral_reward` DROP COLUMN `applied_by_user_id`;--> statement-breakpoint +ALTER TABLE `referral` DROP COLUMN `inviter_workspace_id`;--> statement-breakpoint +ALTER TABLE `referral` DROP COLUMN `invitee_user_id`;--> statement-breakpoint +ALTER TABLE `referral` DROP COLUMN `referral_code_id`;--> statement-breakpoint +ALTER TABLE `referral` DROP COLUMN `stripe_customer_id`;--> statement-breakpoint +ALTER TABLE `referral` DROP COLUMN `stripe_subscription_id`; \ No newline at end of file diff --git a/packages/console/core/migrations/20260516082200_long_spirit/snapshot.json b/packages/console/core/migrations/20260516082200_long_spirit/snapshot.json new file mode 100644 index 000000000000..0bf2a4b2f7bd --- /dev/null +++ b/packages/console/core/migrations/20260516082200_long_spirit/snapshot.json @@ -0,0 +1,3124 @@ +{ + "version": "6", + "dialect": "mysql", + "id": "e93db4f6-6e8b-43f4-b883-59d2fd6ede53", + "prevIds": [ + "1f04bd59-35b0-493b-9d55-cfa08207ba8e", + "91a9afa8-a5b1-4fa4-b71d-b3ae866744c9" + ], + "ddl": [ + { + "name": "account", + "entityType": "tables" + }, + { + "name": "auth", + "entityType": "tables" + }, + { + "name": "benchmark", + "entityType": "tables" + }, + { + "name": "billing", + "entityType": "tables" + }, + { + "name": "coupon", + "entityType": "tables" + }, + { + "name": "lite", + "entityType": "tables" + }, + { + "name": "payment", + "entityType": "tables" + }, + { + "name": "subscription", + "entityType": "tables" + }, + { + "name": "usage", + "entityType": "tables" + }, + { + "name": "ip_rate_limit", + "entityType": "tables" + }, + { + "name": "ip", + "entityType": "tables" + }, + { + "name": "key_rate_limit", + "entityType": "tables" + }, + { + "name": "model_sticky_provider", + "entityType": "tables" + }, + { + "name": "model_tpm_rate_limit", + "entityType": "tables" + }, + { + "name": "model_tps_rate_limit", + "entityType": "tables" + }, + { + "name": "key", + "entityType": "tables" + }, + { + "name": "model", + "entityType": "tables" + }, + { + "name": "provider", + "entityType": "tables" + }, + { + "name": "referral_reward", + "entityType": "tables" + }, + { + "name": "referral", + "entityType": "tables" + }, + { + "name": "user", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "account" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "auth" + }, + { + "type": "enum('email','github','google')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subject", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "agent", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "mediumtext", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "result", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(32)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_type", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(4)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_last4", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "balance", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "boolean", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_trigger", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_amount", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_locked_till", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "enum('20','100','200')", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_plan", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_booked", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_selected", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite_subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "enum('BUILDATHON','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_redeemed", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "weekly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_weekly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invoice_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_refunded", + "entityType": "columns", + "table": "payment" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "fixed_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_fixed_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "input_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "output_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reasoning_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_read_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_5m_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_1h_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cost", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(10)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "ip" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "usage", + "entityType": "columns", + "table": "ip" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(40)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider_id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "qualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "unqualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_used", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "provider" + }, + { + "type": "text", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "credentials", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_applied", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invitee_account_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_seen", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "color", + "entityType": "columns", + "table": "user" + }, + { + "type": "enum('admin','member')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "role", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "user" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(16)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_code", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "workspace" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "auth", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "benchmark", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "billing", + "entityType": "pks" + }, + { + "columns": [ + "email", + "type" + ], + "name": "PRIMARY", + "table": "coupon", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "lite", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "payment", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "subscription", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "usage", + "entityType": "pks" + }, + { + "columns": [ + "ip", + "interval" + ], + "name": "PRIMARY", + "table": "ip_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "ip" + ], + "name": "PRIMARY", + "table": "ip", + "entityType": "pks" + }, + { + "columns": [ + "key", + "interval" + ], + "name": "PRIMARY", + "table": "key_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "model_sticky_provider", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tpm_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tps_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "key", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "model", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "provider", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "referral_reward", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "referral", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "provider", + "isExpression": false + }, + { + "value": "subject", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "provider", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "account_id", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "time_created", + "entityType": "indexes", + "table": "benchmark" + }, + { + "columns": [ + { + "value": "customer_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_customer_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "subscription_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_subscription_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "lite" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "subscription" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "usage_time_created", + "entityType": "indexes", + "table": "usage" + }, + { + "columns": [ + { + "value": "key", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_key", + "entityType": "indexes", + "table": "key" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "model", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "model_workspace_model", + "entityType": "indexes", + "table": "model" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "provider", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_provider", + "entityType": "indexes", + "table": "provider" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_reward_workspace_time", + "entityType": "indexes", + "table": "referral_reward" + }, + { + "columns": [ + { + "value": "invitee_account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_invitee_account_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_workspace_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "email", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "email", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "slug", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "slug", + "entityType": "indexes", + "table": "workspace" + }, + { + "columns": [ + { + "value": "referral_code", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_referral_code", + "entityType": "indexes", + "table": "workspace" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/console/core/migrations/20260516110447_classy_wilson_fisk/migration.sql b/packages/console/core/migrations/20260516110447_classy_wilson_fisk/migration.sql new file mode 100644 index 000000000000..ab3a10784745 --- /dev/null +++ b/packages/console/core/migrations/20260516110447_classy_wilson_fisk/migration.sql @@ -0,0 +1,4 @@ +DROP INDEX `referral_reward_workspace_time` ON `referral_reward`;--> statement-breakpoint +ALTER TABLE `referral_reward` DROP PRIMARY KEY;--> statement-breakpoint +ALTER TABLE `referral_reward` ADD PRIMARY KEY (`workspace_id`,`referral_id`);--> statement-breakpoint +ALTER TABLE `referral_reward` DROP COLUMN `id`; \ No newline at end of file diff --git a/packages/console/core/migrations/20260516110447_classy_wilson_fisk/snapshot.json b/packages/console/core/migrations/20260516110447_classy_wilson_fisk/snapshot.json new file mode 100644 index 000000000000..5e0d53517374 --- /dev/null +++ b/packages/console/core/migrations/20260516110447_classy_wilson_fisk/snapshot.json @@ -0,0 +1,3090 @@ +{ + "version": "6", + "dialect": "mysql", + "id": "77de1eaf-69f6-433f-b244-cda75319450a", + "prevIds": [ + "e93db4f6-6e8b-43f4-b883-59d2fd6ede53" + ], + "ddl": [ + { + "name": "account", + "entityType": "tables" + }, + { + "name": "auth", + "entityType": "tables" + }, + { + "name": "benchmark", + "entityType": "tables" + }, + { + "name": "billing", + "entityType": "tables" + }, + { + "name": "coupon", + "entityType": "tables" + }, + { + "name": "lite", + "entityType": "tables" + }, + { + "name": "payment", + "entityType": "tables" + }, + { + "name": "subscription", + "entityType": "tables" + }, + { + "name": "usage", + "entityType": "tables" + }, + { + "name": "ip_rate_limit", + "entityType": "tables" + }, + { + "name": "ip", + "entityType": "tables" + }, + { + "name": "key_rate_limit", + "entityType": "tables" + }, + { + "name": "model_sticky_provider", + "entityType": "tables" + }, + { + "name": "model_tpm_rate_limit", + "entityType": "tables" + }, + { + "name": "model_tps_rate_limit", + "entityType": "tables" + }, + { + "name": "key", + "entityType": "tables" + }, + { + "name": "model", + "entityType": "tables" + }, + { + "name": "provider", + "entityType": "tables" + }, + { + "name": "referral_reward", + "entityType": "tables" + }, + { + "name": "referral", + "entityType": "tables" + }, + { + "name": "user", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "account" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "auth" + }, + { + "type": "enum('email','github','google')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subject", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "agent", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "mediumtext", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "result", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(32)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_type", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(4)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_last4", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "balance", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "boolean", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_trigger", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_amount", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_locked_till", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "enum('20','100','200')", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_plan", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_booked", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_selected", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite_subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "enum('BUILDATHON','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_redeemed", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "weekly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_weekly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invoice_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_refunded", + "entityType": "columns", + "table": "payment" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "fixed_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_fixed_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "input_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "output_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reasoning_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_read_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_5m_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_1h_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cost", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(10)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "ip" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "usage", + "entityType": "columns", + "table": "ip" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(40)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider_id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "qualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "unqualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_used", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "provider" + }, + { + "type": "text", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "credentials", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_applied", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invitee_account_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_seen", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "color", + "entityType": "columns", + "table": "user" + }, + { + "type": "enum('admin','member')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "role", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "user" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(16)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_code", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "workspace" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "auth", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "benchmark", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "billing", + "entityType": "pks" + }, + { + "columns": [ + "email", + "type" + ], + "name": "PRIMARY", + "table": "coupon", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "lite", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "payment", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "subscription", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "usage", + "entityType": "pks" + }, + { + "columns": [ + "ip", + "interval" + ], + "name": "PRIMARY", + "table": "ip_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "ip" + ], + "name": "PRIMARY", + "table": "ip", + "entityType": "pks" + }, + { + "columns": [ + "key", + "interval" + ], + "name": "PRIMARY", + "table": "key_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "model_sticky_provider", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tpm_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tps_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "key", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "model", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "provider", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "referral_id" + ], + "name": "PRIMARY", + "table": "referral_reward", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "referral", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "provider", + "isExpression": false + }, + { + "value": "subject", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "provider", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "account_id", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "time_created", + "entityType": "indexes", + "table": "benchmark" + }, + { + "columns": [ + { + "value": "customer_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_customer_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "subscription_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_subscription_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "lite" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "subscription" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "usage_time_created", + "entityType": "indexes", + "table": "usage" + }, + { + "columns": [ + { + "value": "key", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_key", + "entityType": "indexes", + "table": "key" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "model", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "model_workspace_model", + "entityType": "indexes", + "table": "model" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "provider", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_provider", + "entityType": "indexes", + "table": "provider" + }, + { + "columns": [ + { + "value": "invitee_account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_invitee_account_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_workspace_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "email", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "email", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "slug", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "slug", + "entityType": "indexes", + "table": "workspace" + }, + { + "columns": [ + { + "value": "referral_code", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_referral_code", + "entityType": "indexes", + "table": "workspace" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/migration.sql b/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/migration.sql new file mode 100644 index 000000000000..c2ac686762b8 --- /dev/null +++ b/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/migration.sql @@ -0,0 +1,3 @@ +DROP INDEX `referral_workspace_id` ON `referral`;--> statement-breakpoint +ALTER TABLE `referral` DROP PRIMARY KEY;--> statement-breakpoint +ALTER TABLE `referral` ADD PRIMARY KEY (`workspace_id`,`id`); \ No newline at end of file diff --git a/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/snapshot.json b/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/snapshot.json new file mode 100644 index 000000000000..73c4209c2f4e --- /dev/null +++ b/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/snapshot.json @@ -0,0 +1,3075 @@ +{ + "version": "6", + "dialect": "mysql", + "id": "9809797a-e6f6-418c-8532-196553b315b8", + "prevIds": [ + "77de1eaf-69f6-433f-b244-cda75319450a" + ], + "ddl": [ + { + "name": "account", + "entityType": "tables" + }, + { + "name": "auth", + "entityType": "tables" + }, + { + "name": "benchmark", + "entityType": "tables" + }, + { + "name": "billing", + "entityType": "tables" + }, + { + "name": "coupon", + "entityType": "tables" + }, + { + "name": "lite", + "entityType": "tables" + }, + { + "name": "payment", + "entityType": "tables" + }, + { + "name": "subscription", + "entityType": "tables" + }, + { + "name": "usage", + "entityType": "tables" + }, + { + "name": "ip_rate_limit", + "entityType": "tables" + }, + { + "name": "ip", + "entityType": "tables" + }, + { + "name": "key_rate_limit", + "entityType": "tables" + }, + { + "name": "model_sticky_provider", + "entityType": "tables" + }, + { + "name": "model_tpm_rate_limit", + "entityType": "tables" + }, + { + "name": "model_tps_rate_limit", + "entityType": "tables" + }, + { + "name": "key", + "entityType": "tables" + }, + { + "name": "model", + "entityType": "tables" + }, + { + "name": "provider", + "entityType": "tables" + }, + { + "name": "referral_reward", + "entityType": "tables" + }, + { + "name": "referral", + "entityType": "tables" + }, + { + "name": "user", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "account" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "auth" + }, + { + "type": "enum('email','github','google')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subject", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "agent", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "mediumtext", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "result", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(32)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_type", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(4)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_last4", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "balance", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "boolean", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_trigger", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_amount", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_locked_till", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "enum('20','100','200')", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_plan", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_booked", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_selected", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite_subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "enum('BUILDATHON','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_redeemed", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "weekly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_weekly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invoice_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_refunded", + "entityType": "columns", + "table": "payment" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "fixed_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_fixed_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "input_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "output_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reasoning_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_read_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_5m_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_1h_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cost", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(10)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "ip" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "usage", + "entityType": "columns", + "table": "ip" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(40)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider_id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "qualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "unqualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_used", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "provider" + }, + { + "type": "text", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "credentials", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_applied", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invitee_account_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_seen", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "color", + "entityType": "columns", + "table": "user" + }, + { + "type": "enum('admin','member')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "role", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "user" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(16)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_code", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "workspace" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "auth", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "benchmark", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "billing", + "entityType": "pks" + }, + { + "columns": [ + "email", + "type" + ], + "name": "PRIMARY", + "table": "coupon", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "lite", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "payment", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "subscription", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "usage", + "entityType": "pks" + }, + { + "columns": [ + "ip", + "interval" + ], + "name": "PRIMARY", + "table": "ip_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "ip" + ], + "name": "PRIMARY", + "table": "ip", + "entityType": "pks" + }, + { + "columns": [ + "key", + "interval" + ], + "name": "PRIMARY", + "table": "key_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "model_sticky_provider", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tpm_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tps_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "key", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "model", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "provider", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "referral_id" + ], + "name": "PRIMARY", + "table": "referral_reward", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "referral", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "provider", + "isExpression": false + }, + { + "value": "subject", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "provider", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "account_id", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "time_created", + "entityType": "indexes", + "table": "benchmark" + }, + { + "columns": [ + { + "value": "customer_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_customer_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "subscription_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_subscription_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "lite" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "subscription" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "usage_time_created", + "entityType": "indexes", + "table": "usage" + }, + { + "columns": [ + { + "value": "key", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_key", + "entityType": "indexes", + "table": "key" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "model", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "model_workspace_model", + "entityType": "indexes", + "table": "model" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "provider", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_provider", + "entityType": "indexes", + "table": "provider" + }, + { + "columns": [ + { + "value": "invitee_account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_invitee_account_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "email", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "email", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "slug", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "slug", + "entityType": "indexes", + "table": "workspace" + }, + { + "columns": [ + { + "value": "referral_code", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_referral_code", + "entityType": "indexes", + "table": "workspace" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/console/core/migrations/20260516222132_referral_code_length/migration.sql b/packages/console/core/migrations/20260516222132_referral_code_length/migration.sql new file mode 100644 index 000000000000..943f1f4a7b68 --- /dev/null +++ b/packages/console/core/migrations/20260516222132_referral_code_length/migration.sql @@ -0,0 +1,2 @@ +UPDATE `workspace` SET `referral_code` = NULL WHERE CHAR_LENGTH(`referral_code`) > 10;--> statement-breakpoint +ALTER TABLE `workspace` MODIFY COLUMN `referral_code` varchar(10); diff --git a/packages/console/core/migrations/20260516222132_referral_code_length/snapshot.json b/packages/console/core/migrations/20260516222132_referral_code_length/snapshot.json new file mode 100644 index 000000000000..9841c032b923 --- /dev/null +++ b/packages/console/core/migrations/20260516222132_referral_code_length/snapshot.json @@ -0,0 +1,3075 @@ +{ + "version": "6", + "dialect": "mysql", + "id": "e49321c9-2b03-4e2c-b2f0-76fbb88088ea", + "prevIds": [ + "9809797a-e6f6-418c-8532-196553b315b8" + ], + "ddl": [ + { + "name": "account", + "entityType": "tables" + }, + { + "name": "auth", + "entityType": "tables" + }, + { + "name": "benchmark", + "entityType": "tables" + }, + { + "name": "billing", + "entityType": "tables" + }, + { + "name": "coupon", + "entityType": "tables" + }, + { + "name": "lite", + "entityType": "tables" + }, + { + "name": "payment", + "entityType": "tables" + }, + { + "name": "subscription", + "entityType": "tables" + }, + { + "name": "usage", + "entityType": "tables" + }, + { + "name": "ip_rate_limit", + "entityType": "tables" + }, + { + "name": "ip", + "entityType": "tables" + }, + { + "name": "key_rate_limit", + "entityType": "tables" + }, + { + "name": "model_sticky_provider", + "entityType": "tables" + }, + { + "name": "model_tpm_rate_limit", + "entityType": "tables" + }, + { + "name": "model_tps_rate_limit", + "entityType": "tables" + }, + { + "name": "key", + "entityType": "tables" + }, + { + "name": "model", + "entityType": "tables" + }, + { + "name": "provider", + "entityType": "tables" + }, + { + "name": "referral_reward", + "entityType": "tables" + }, + { + "name": "referral", + "entityType": "tables" + }, + { + "name": "user", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "account" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "auth" + }, + { + "type": "enum('email','github','google')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subject", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "agent", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "mediumtext", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "result", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(32)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_type", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(4)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_last4", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "balance", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "boolean", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_trigger", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_amount", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_locked_till", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "enum('20','100','200')", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_plan", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_booked", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_selected", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite_subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "enum('BUILDATHON','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_redeemed", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "weekly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_weekly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invoice_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_refunded", + "entityType": "columns", + "table": "payment" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "fixed_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_fixed_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "input_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "output_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reasoning_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_read_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_5m_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_1h_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cost", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(10)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "ip" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "usage", + "entityType": "columns", + "table": "ip" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(40)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider_id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "qualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "unqualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_used", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "provider" + }, + { + "type": "text", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "credentials", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_applied", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invitee_account_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_seen", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "color", + "entityType": "columns", + "table": "user" + }, + { + "type": "enum('admin','member')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "role", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "user" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(10)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_code", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "workspace" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "auth", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "benchmark", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "billing", + "entityType": "pks" + }, + { + "columns": [ + "email", + "type" + ], + "name": "PRIMARY", + "table": "coupon", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "lite", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "payment", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "subscription", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "usage", + "entityType": "pks" + }, + { + "columns": [ + "ip", + "interval" + ], + "name": "PRIMARY", + "table": "ip_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "ip" + ], + "name": "PRIMARY", + "table": "ip", + "entityType": "pks" + }, + { + "columns": [ + "key", + "interval" + ], + "name": "PRIMARY", + "table": "key_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "model_sticky_provider", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tpm_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tps_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "key", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "model", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "provider", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "referral_id" + ], + "name": "PRIMARY", + "table": "referral_reward", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "referral", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "provider", + "isExpression": false + }, + { + "value": "subject", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "provider", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "account_id", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "time_created", + "entityType": "indexes", + "table": "benchmark" + }, + { + "columns": [ + { + "value": "customer_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_customer_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "subscription_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_subscription_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "lite" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "subscription" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "usage_time_created", + "entityType": "indexes", + "table": "usage" + }, + { + "columns": [ + { + "value": "key", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_key", + "entityType": "indexes", + "table": "key" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "model", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "model_workspace_model", + "entityType": "indexes", + "table": "model" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "provider", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_provider", + "entityType": "indexes", + "table": "provider" + }, + { + "columns": [ + { + "value": "invitee_account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_invitee_account_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "email", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "email", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "slug", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "slug", + "entityType": "indexes", + "table": "workspace" + }, + { + "columns": [ + { + "value": "referral_code", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_referral_code", + "entityType": "indexes", + "table": "workspace" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/console/core/migrations/20260518181630_secret_strong_guy/migration.sql b/packages/console/core/migrations/20260518181630_secret_strong_guy/migration.sql new file mode 100644 index 000000000000..27b1bc9fb3a5 --- /dev/null +++ b/packages/console/core/migrations/20260518181630_secret_strong_guy/migration.sql @@ -0,0 +1,2 @@ +DROP INDEX `workspace_referral_code` ON `workspace`;--> statement-breakpoint +CREATE UNIQUE INDEX `referral_code` ON `workspace` (`referral_code`); \ No newline at end of file diff --git a/packages/console/core/migrations/20260518181630_secret_strong_guy/snapshot.json b/packages/console/core/migrations/20260518181630_secret_strong_guy/snapshot.json new file mode 100644 index 000000000000..5b8e94a7ff24 --- /dev/null +++ b/packages/console/core/migrations/20260518181630_secret_strong_guy/snapshot.json @@ -0,0 +1,3075 @@ +{ + "version": "6", + "dialect": "mysql", + "id": "9a7123cb-5c2c-47f6-b22f-8fd6a02dcb03", + "prevIds": [ + "e49321c9-2b03-4e2c-b2f0-76fbb88088ea" + ], + "ddl": [ + { + "name": "account", + "entityType": "tables" + }, + { + "name": "auth", + "entityType": "tables" + }, + { + "name": "benchmark", + "entityType": "tables" + }, + { + "name": "billing", + "entityType": "tables" + }, + { + "name": "coupon", + "entityType": "tables" + }, + { + "name": "lite", + "entityType": "tables" + }, + { + "name": "payment", + "entityType": "tables" + }, + { + "name": "subscription", + "entityType": "tables" + }, + { + "name": "usage", + "entityType": "tables" + }, + { + "name": "ip_rate_limit", + "entityType": "tables" + }, + { + "name": "ip", + "entityType": "tables" + }, + { + "name": "key_rate_limit", + "entityType": "tables" + }, + { + "name": "model_sticky_provider", + "entityType": "tables" + }, + { + "name": "model_tpm_rate_limit", + "entityType": "tables" + }, + { + "name": "model_tps_rate_limit", + "entityType": "tables" + }, + { + "name": "key", + "entityType": "tables" + }, + { + "name": "model", + "entityType": "tables" + }, + { + "name": "provider", + "entityType": "tables" + }, + { + "name": "referral_reward", + "entityType": "tables" + }, + { + "name": "referral", + "entityType": "tables" + }, + { + "name": "user", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "account" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "auth" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "auth" + }, + { + "type": "enum('email','github','google')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subject", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "auth" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "agent", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "mediumtext", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "result", + "entityType": "columns", + "table": "benchmark" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(32)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_type", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(4)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_method_last4", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "balance", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "billing" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "billing" + }, + { + "type": "boolean", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_trigger", + "entityType": "columns", + "table": "billing" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_amount", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_error", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_reload_locked_till", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "enum('20','100','200')", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "subscription_plan", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_booked", + "entityType": "columns", + "table": "billing" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_subscription_selected", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(28)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite_subscription_id", + "entityType": "columns", + "table": "billing" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "lite", + "entityType": "columns", + "table": "billing" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "enum('BUILDATHON','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_redeemed", + "entityType": "columns", + "table": "coupon" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "weekly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_weekly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_updated", + "entityType": "columns", + "table": "lite" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "customer_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invoice_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "payment_id", + "entityType": "columns", + "table": "payment" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "payment" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_refunded", + "entityType": "columns", + "table": "payment" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "payment" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "rolling_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "fixed_usage", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_rolling_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_fixed_updated", + "entityType": "columns", + "table": "subscription" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "usage" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "input_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "output_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "reasoning_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_read_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_5m_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cache_write_1h_tokens", + "entityType": "columns", + "table": "usage" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "cost", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "usage" + }, + { + "type": "json", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "enrichment", + "entityType": "columns", + "table": "usage" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(10)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "ip_rate_limit" + }, + { + "type": "varchar(45)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "ip", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "ip" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "ip" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "usage", + "entityType": "columns", + "table": "ip" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(40)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "key_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider_id", + "entityType": "columns", + "table": "model_sticky_provider" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "count", + "entityType": "columns", + "table": "model_tpm_rate_limit" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "interval", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "qualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "int", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "unqualify", + "entityType": "columns", + "table": "model_tps_rate_limit" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "key", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "key" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_used", + "entityType": "columns", + "table": "key" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "model" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "model" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "provider" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(64)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "provider", + "entityType": "columns", + "table": "provider" + }, + { + "type": "text", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "credentials", + "entityType": "columns", + "table": "provider" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_id", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "bigint", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "amount", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_applied", + "entityType": "columns", + "table": "referral_reward" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "referral" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "invitee_account_id", + "entityType": "columns", + "table": "referral" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "account_id", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_seen", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "color", + "entityType": "columns", + "table": "user" + }, + { + "type": "enum('admin','member')", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "role", + "entityType": "columns", + "table": "user" + }, + { + "type": "int", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_limit", + "entityType": "columns", + "table": "user" + }, + { + "type": "bigint", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "monthly_usage", + "entityType": "columns", + "table": "user" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_monthly_usage_updated", + "entityType": "columns", + "table": "user" + }, + { + "type": "varchar(30)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(10)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "referral_code", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "varchar(255)", + "notNull": true, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(now())", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": true, + "autoIncrement": false, + "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))", + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "timestamp(3)", + "notNull": false, + "autoIncrement": false, + "default": null, + "onUpdateNow": false, + "onUpdateNowFsp": null, + "charSet": null, + "collation": null, + "generated": null, + "name": "time_deleted", + "entityType": "columns", + "table": "workspace" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "auth", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "benchmark", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "billing", + "entityType": "pks" + }, + { + "columns": [ + "email", + "type" + ], + "name": "PRIMARY", + "table": "coupon", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "lite", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "payment", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "subscription", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "usage", + "entityType": "pks" + }, + { + "columns": [ + "ip", + "interval" + ], + "name": "PRIMARY", + "table": "ip_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "ip" + ], + "name": "PRIMARY", + "table": "ip", + "entityType": "pks" + }, + { + "columns": [ + "key", + "interval" + ], + "name": "PRIMARY", + "table": "key_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "model_sticky_provider", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tpm_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "id", + "interval" + ], + "name": "PRIMARY", + "table": "model_tps_rate_limit", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "key", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "model", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "provider", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "referral_id" + ], + "name": "PRIMARY", + "table": "referral_reward", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "referral", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id", + "id" + ], + "name": "PRIMARY", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "name": "PRIMARY", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "provider", + "isExpression": false + }, + { + "value": "subject", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "provider", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "account_id", + "entityType": "indexes", + "table": "auth" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "time_created", + "entityType": "indexes", + "table": "benchmark" + }, + { + "columns": [ + { + "value": "customer_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_customer_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "subscription_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_subscription_id", + "entityType": "indexes", + "table": "billing" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "lite" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "user_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_user_id", + "entityType": "indexes", + "table": "subscription" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "usage_time_created", + "entityType": "indexes", + "table": "usage" + }, + { + "columns": [ + { + "value": "key", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_key", + "entityType": "indexes", + "table": "key" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "model", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "model_workspace_model", + "entityType": "indexes", + "table": "model" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "provider", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "workspace_provider", + "entityType": "indexes", + "table": "provider" + }, + { + "columns": [ + { + "value": "invitee_account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_invitee_account_id", + "entityType": "indexes", + "table": "referral" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + }, + { + "value": "email", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "user_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "account_id", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_account_id", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "email", + "isExpression": false + } + ], + "isUnique": false, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "global_email", + "entityType": "indexes", + "table": "user" + }, + { + "columns": [ + { + "value": "slug", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "slug", + "entityType": "indexes", + "table": "workspace" + }, + { + "columns": [ + { + "value": "referral_code", + "isExpression": false + } + ], + "isUnique": true, + "using": null, + "algorithm": null, + "lock": null, + "nameExplicit": true, + "name": "referral_code", + "entityType": "indexes", + "table": "workspace" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index f782968ed74a..82307658d777 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -155,6 +155,26 @@ export namespace Billing { return amountInMicroCents } + export const subtractLiteUsage = async (workspaceID: string, amountInMicroCents: number) => { + await Database.transaction(async (tx) => { + const lite = await tx + .select({ id: LiteTable.id }) + .from(LiteTable) + .where(and(eq(LiteTable.workspaceID, workspaceID), isNull(LiteTable.timeDeleted))) + .then((rows) => rows[0]) + if (!lite) throw new Error("Subscribe to Go before applying referral rewards") + + await tx + .update(LiteTable) + .set({ + monthlyUsage: sql`GREATEST(0, COALESCE(${LiteTable.monthlyUsage}, 0) - ${amountInMicroCents})`, + weeklyUsage: sql`GREATEST(0, COALESCE(${LiteTable.weeklyUsage}, 0) - ${amountInMicroCents})`, + rollingUsage: sql`GREATEST(0, COALESCE(${LiteTable.rollingUsage}, 0) - ${amountInMicroCents})`, + }) + .where(and(eq(LiteTable.workspaceID, workspaceID), isNull(LiteTable.timeDeleted))) + }) + } + export const redeemCoupon = async (email: string, type: (typeof CouponType)[number]) => { // validate coupon type await (async () => { diff --git a/packages/console/core/src/identifier.ts b/packages/console/core/src/identifier.ts index 8aa324ba07f9..8fcd68148f8c 100644 --- a/packages/console/core/src/identifier.ts +++ b/packages/console/core/src/identifier.ts @@ -12,6 +12,7 @@ export namespace Identifier { model: "mod", payment: "pay", provider: "prv", + referral: "ref", subscription: "sub", usage: "usg", user: "usr", diff --git a/packages/console/core/src/referral.ts b/packages/console/core/src/referral.ts new file mode 100644 index 000000000000..7150a21bdca7 --- /dev/null +++ b/packages/console/core/src/referral.ts @@ -0,0 +1,386 @@ +import { z } from "zod" +import { and, desc, eq, isNull, sql, Database } from "./drizzle" +import { Actor } from "./actor" +import { Identifier } from "./identifier" +import { LiteTable } from "./schema/billing.sql" +import { ReferralRewardTable, ReferralTable } from "./schema/referral.sql" +import { AuthTable } from "./schema/auth.sql" +import { UserTable } from "./schema/user.sql" +import { WorkspaceTable } from "./schema/workspace.sql" +import { centsToMicroCents, microCentsToCents } from "./util/price" +import { fn } from "./util/fn" +import { Billing } from "./billing" +import { LiteData } from "./lite" +import { Subscription } from "./subscription" +import { ulid } from "ulid" + +export namespace Referral { + export const REWARD_AMOUNT = centsToMicroCents(500) + export const CODE_LENGTH = 10 + + export function normalizeCode(code?: string | null) { + return code?.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, CODE_LENGTH) + } + + function generateCode() { + return ulid().slice(-CODE_LENGTH) + } + + async function ensureCode(workspaceID = Actor.workspace()) { + return Database.transaction(async (tx) => { + const existing = await tx + .select({ code: WorkspaceTable.referralCode }) + .from(WorkspaceTable) + .where(and(eq(WorkspaceTable.id, workspaceID), isNull(WorkspaceTable.timeDeleted))) + .then((rows) => rows[0]) + if (!existing) throw new Error("Workspace not found") + if (existing.code) return { code: existing.code } + + for (const _ of Array.from({ length: 5 })) { + await tx + .update(WorkspaceTable) + .set({ referralCode: generateCode() }) + .where( + and(eq(WorkspaceTable.id, workspaceID), isNull(WorkspaceTable.referralCode), isNull(WorkspaceTable.timeDeleted)), + ) + + const created = await tx + .select({ code: WorkspaceTable.referralCode }) + .from(WorkspaceTable) + .where(and(eq(WorkspaceTable.id, workspaceID), isNull(WorkspaceTable.timeDeleted))) + .then((rows) => rows[0]) + if (created?.code) return { code: created.code } + } + + throw new Error("Failed to generate referral code") + }) + } + + export const summary = fn(z.void(), async () => { + const workspaceID = Actor.workspace() + const accountID = Actor.account() + const code = await ensureCode(workspaceID) + const rows = await Database.use(async (tx) => { + const [rewards, invites, inviteeReferrals, inviteeRewards, lite] = await Promise.all([ + tx + .select({ + referralID: ReferralRewardTable.referralID, + workspaceID: ReferralRewardTable.workspaceID, + referralWorkspaceID: ReferralTable.workspaceID, + inviteeEmail: AuthTable.subject, + amount: ReferralRewardTable.amount, + timeCreated: ReferralRewardTable.timeCreated, + timeApplied: ReferralRewardTable.timeApplied, + }) + .from(ReferralRewardTable) + .innerJoin(ReferralTable, eq(ReferralTable.id, ReferralRewardTable.referralID)) + .innerJoin(AuthTable, and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email"))) + .where( + and( + eq(ReferralRewardTable.workspaceID, workspaceID), + isNull(ReferralRewardTable.timeDeleted), + isNull(ReferralTable.timeDeleted), + ), + ) + .orderBy(desc(ReferralRewardTable.timeCreated)), + tx + .select({ id: ReferralTable.id, inviteeEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) + .from(ReferralTable) + .innerJoin(AuthTable, and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email"))) + .where(and(eq(ReferralTable.workspaceID, workspaceID), isNull(ReferralTable.timeDeleted))), + tx + .select({ id: ReferralTable.id, inviteeEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) + .from(ReferralTable) + .innerJoin(AuthTable, and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email"))) + .where(and(eq(ReferralTable.inviteeAccountID, accountID), isNull(ReferralTable.timeDeleted))), + tx + .select({ referralID: ReferralRewardTable.referralID }) + .from(ReferralRewardTable) + .innerJoin(ReferralTable, eq(ReferralTable.id, ReferralRewardTable.referralID)) + .where( + and( + eq(ReferralTable.inviteeAccountID, accountID), + isNull(ReferralRewardTable.timeDeleted), + isNull(ReferralTable.timeDeleted), + ), + ), + tx + .select({ id: LiteTable.id }) + .from(LiteTable) + .where(and(eq(LiteTable.workspaceID, workspaceID), isNull(LiteTable.timeDeleted))) + .then((result) => result[0]), + ]) + + return { inviteeReferrals, inviteeRewards, invites, lite, rewards } + }) + + const rewardReferralIDs = new Set(rows.rewards.map((reward) => reward.referralID)) + const inviteeRewardReferralIDs = new Set(rows.inviteeRewards.map((reward) => reward.referralID)) + const rewards = rows.rewards.map((reward) => ({ + id: reward.referralID, + source: reward.workspaceID === reward.referralWorkspaceID ? ("inviter" as const) : ("invitee" as const), + status: reward.timeApplied ? ("applied" as const) : ("available" as const), + email: reward.inviteeEmail, + amount: microCentsToCents(reward.amount), + timeCreated: reward.timeCreated, + timeApplied: reward.timeApplied, + })) + const pending = [ + ...rows.invites + .filter((referral) => !rewardReferralIDs.has(referral.id)) + .map((referral) => ({ + id: `${referral.id}:inviter`, + source: "inviter" as const, + status: "pending" as const, + email: referral.inviteeEmail, + amount: microCentsToCents(REWARD_AMOUNT), + timeCreated: referral.timeCreated, + timeApplied: null, + })), + ...rows.inviteeReferrals + .filter((referral) => !inviteeRewardReferralIDs.has(referral.id)) + .map((referral) => ({ + id: `${referral.id}:invitee`, + source: "invitee" as const, + status: "pending" as const, + email: referral.inviteeEmail, + amount: microCentsToCents(REWARD_AMOUNT), + timeCreated: referral.timeCreated, + timeApplied: null, + })), + ] + const allRewards = [...pending, ...rewards].sort( + (a, b) => new Date(b.timeCreated).getTime() - new Date(a.timeCreated).getTime(), + ) + return { + referralCode: code.code, + inviteCount: allRewards.length, + hasActiveGo: !!rows.lite, + rewardAmount: microCentsToCents(REWARD_AMOUNT), + totalEarned: rewards.reduce((total, reward) => total + reward.amount, 0), + totalApplied: rewards + .filter((reward) => reward.timeApplied) + .reduce((total, reward) => total + reward.amount, 0), + rewards: allRewards, + } + }) + + export const applyReward = fn(z.object({ referralID: z.string() }), async (input) => { + const workspaceID = Actor.workspace() + + return Database.transaction(async (tx) => { + const reward = await tx + .select({ amount: ReferralRewardTable.amount, timeApplied: ReferralRewardTable.timeApplied }) + .from(ReferralRewardTable) + .where( + and( + eq(ReferralRewardTable.workspaceID, workspaceID), + eq(ReferralRewardTable.referralID, input.referralID), + isNull(ReferralRewardTable.timeDeleted), + ), + ) + .then((rows) => rows[0]) + if (!reward) throw new Error("Referral reward not found") + if (reward.timeApplied) throw new Error("Referral reward already applied") + + const update = await tx + .update(ReferralRewardTable) + .set({ + timeApplied: sql`now()`, + }) + .where( + and( + eq(ReferralRewardTable.workspaceID, workspaceID), + eq(ReferralRewardTable.referralID, input.referralID), + isNull(ReferralRewardTable.timeApplied), + isNull(ReferralRewardTable.timeDeleted), + ), + ) + if (update.rowsAffected === 0) throw new Error("Referral reward already applied") + + await Billing.subtractLiteUsage(workspaceID, reward.amount) + + return { amount: microCentsToCents(reward.amount) } + }) + }) + + export const usagePreview = fn(z.object({ referralID: z.string() }), async (input) => { + const row = await Database.use((tx) => + tx + .select({ + rewardAmount: ReferralRewardTable.amount, + rollingUsage: LiteTable.rollingUsage, + weeklyUsage: LiteTable.weeklyUsage, + monthlyUsage: LiteTable.monthlyUsage, + timeRollingUpdated: LiteTable.timeRollingUpdated, + timeWeeklyUpdated: LiteTable.timeWeeklyUpdated, + timeMonthlyUpdated: LiteTable.timeMonthlyUpdated, + timeCreated: LiteTable.timeCreated, + }) + .from(ReferralRewardTable) + .innerJoin(LiteTable, eq(LiteTable.workspaceID, ReferralRewardTable.workspaceID)) + .where( + and( + eq(ReferralRewardTable.workspaceID, Actor.workspace()), + eq(ReferralRewardTable.referralID, input.referralID), + isNull(ReferralRewardTable.timeApplied), + isNull(ReferralRewardTable.timeDeleted), + isNull(LiteTable.timeDeleted), + ), + ) + .then((rows) => rows[0]), + ) + if (!row) return null + + const limits = LiteData.getLimits() + return { + rollingUsage: usagePreviewItem( + Subscription.analyzeRollingUsage({ + limit: limits.rollingLimit, + window: limits.rollingWindow, + usage: row.rollingUsage ?? 0, + timeUpdated: row.timeRollingUpdated ?? new Date(), + }), + Subscription.analyzeRollingUsage({ + limit: limits.rollingLimit, + window: limits.rollingWindow, + usage: Math.max(0, (row.rollingUsage ?? 0) - row.rewardAmount), + timeUpdated: row.timeRollingUpdated ?? new Date(), + }), + ), + weeklyUsage: usagePreviewItem( + Subscription.analyzeWeeklyUsage({ + limit: limits.weeklyLimit, + usage: row.weeklyUsage ?? 0, + timeUpdated: row.timeWeeklyUpdated ?? new Date(), + }), + Subscription.analyzeWeeklyUsage({ + limit: limits.weeklyLimit, + usage: Math.max(0, (row.weeklyUsage ?? 0) - row.rewardAmount), + timeUpdated: row.timeWeeklyUpdated ?? new Date(), + }), + ), + monthlyUsage: usagePreviewItem( + Subscription.analyzeMonthlyUsage({ + limit: limits.monthlyLimit, + usage: row.monthlyUsage ?? 0, + timeUpdated: row.timeMonthlyUpdated ?? new Date(), + timeSubscribed: row.timeCreated, + }), + Subscription.analyzeMonthlyUsage({ + limit: limits.monthlyLimit, + usage: Math.max(0, (row.monthlyUsage ?? 0) - row.rewardAmount), + timeUpdated: row.timeMonthlyUpdated ?? new Date(), + timeSubscribed: row.timeCreated, + }), + ), + } + }) + + export async function createFromAccount(input: { + accountID: string + referralCode?: string + }) { + const referralCode = normalizeCode(input.referralCode) + if (!referralCode) return + + return Database.transaction(async (tx) => { + const code = await tx + .select({ workspaceID: WorkspaceTable.id }) + .from(WorkspaceTable) + .where(and(eq(WorkspaceTable.referralCode, referralCode), isNull(WorkspaceTable.timeDeleted))) + .then((rows) => rows[0]) + if (!code) throw new Error("Referral code invalid") + + const existingReferral = await tx + .select({ id: ReferralTable.id }) + .from(ReferralTable) + .where(and(eq(ReferralTable.inviteeAccountID, input.accountID), isNull(ReferralTable.timeDeleted))) + .then((rows) => rows[0]) + if (existingReferral) throw new Error("Referral already redeemed") + + const selfReferral = await tx + .select({ id: UserTable.id }) + .from(UserTable) + .where( + and( + eq(UserTable.workspaceID, code.workspaceID), + eq(UserTable.accountID, input.accountID), + isNull(UserTable.timeDeleted), + ), + ) + .then((rows) => rows[0]) + if (selfReferral) throw new Error("Self-referral is not allowed") + + const referralID = Identifier.create("referral") + await tx + .insert(ReferralTable) + .ignore() + .values({ + workspaceID: code.workspaceID, + id: referralID, + inviteeAccountID: input.accountID, + }) + + const referral = await tx + .select({ id: ReferralTable.id, workspaceID: ReferralTable.workspaceID }) + .from(ReferralTable) + .where(and(eq(ReferralTable.inviteeAccountID, input.accountID), isNull(ReferralTable.timeDeleted))) + .then((rows) => rows[0]) + if (!referral) throw new Error("Referral not created") + if (referral.id !== referralID) throw new Error("Referral already redeemed") + }) + } + + export async function completeFromLiteSubscription(input: { + workspaceID: string + userID: string + }) { + return Database.transaction(async (tx) => { + const invitee = await tx + .select({ accountID: UserTable.accountID }) + .from(UserTable) + .where( + and(eq(UserTable.workspaceID, input.workspaceID), eq(UserTable.id, input.userID), isNull(UserTable.timeDeleted)), + ) + .then((rows) => rows[0]) + if (!invitee?.accountID) throw new Error("Referral invitee account missing") + + const referral = await tx + .select({ id: ReferralTable.id, workspaceID: ReferralTable.workspaceID }) + .from(ReferralTable) + .where(and(eq(ReferralTable.inviteeAccountID, invitee.accountID), isNull(ReferralTable.timeDeleted))) + .then((rows) => rows[0]) + if (!referral) throw new Error("Referral not found") + + const result = await tx + .insert(ReferralRewardTable) + .ignore() + .values([ + { + workspaceID: referral.workspaceID, + referralID: referral.id, + amount: REWARD_AMOUNT, + }, + { + workspaceID: input.workspaceID, + referralID: referral.id, + amount: REWARD_AMOUNT, + }, + ]) + + if (result.rowsAffected === 0) throw new Error("Referral already completed") + }) + } + + function usagePreviewItem( + before: { usagePercent: number; resetInSec: number }, + after: { usagePercent: number; resetInSec: number }, + ) { + return { + beforePercent: before.usagePercent, + afterPercent: after.usagePercent, + resetInSec: after.resetInSec, + } + } +} diff --git a/packages/console/core/src/schema/referral.sql.ts b/packages/console/core/src/schema/referral.sql.ts new file mode 100644 index 000000000000..63d01a2c7d78 --- /dev/null +++ b/packages/console/core/src/schema/referral.sql.ts @@ -0,0 +1,28 @@ +import { bigint, mysqlTable, primaryKey, uniqueIndex } from "drizzle-orm/mysql-core" +import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types" +import { workspaceIndexes } from "./workspace.sql" + +export const ReferralTable = mysqlTable( + "referral", + { + ...workspaceColumns, + ...timestamps, + inviteeAccountID: ulid("invitee_account_id").notNull(), + }, + (table) => [ + ...workspaceIndexes(table), + uniqueIndex("referral_invitee_account_id").on(table.inviteeAccountID), + ], +) + +export const ReferralRewardTable = mysqlTable( + "referral_reward", + { + workspaceID: ulid("workspace_id").notNull(), + referralID: ulid("referral_id").notNull(), + ...timestamps, + amount: bigint("amount", { mode: "number" }).notNull(), + timeApplied: utc("time_applied"), + }, + (table) => [primaryKey({ columns: [table.workspaceID, table.referralID] })], +) diff --git a/packages/console/core/src/schema/workspace.sql.ts b/packages/console/core/src/schema/workspace.sql.ts index 269b62a2a27f..7fe7e0c2a2e3 100644 --- a/packages/console/core/src/schema/workspace.sql.ts +++ b/packages/console/core/src/schema/workspace.sql.ts @@ -6,10 +6,14 @@ export const WorkspaceTable = mysqlTable( { id: ulid("id").notNull().primaryKey(), slug: varchar("slug", { length: 255 }), + referralCode: varchar("referral_code", { length: 10 }), name: varchar("name", { length: 255 }).notNull(), ...timestamps, }, - (table) => [uniqueIndex("slug").on(table.slug)], + (table) => [ + uniqueIndex("slug").on(table.slug), + uniqueIndex("referral_code").on(table.referralCode), + ], ) export function workspaceIndexes(table: any) { diff --git a/packages/console/function/src/auth.ts b/packages/console/function/src/auth.ts index c26ab215b32b..6d56b9670605 100644 --- a/packages/console/function/src/auth.ts +++ b/packages/console/function/src/auth.ts @@ -26,6 +26,7 @@ export const subjects = createSubjects({ account: z.object({ accountID: z.string(), email: z.string(), + newAccount: z.boolean().optional(), }), user: z.object({ userID: z.string(), @@ -142,6 +143,7 @@ export default { } // Get account + let newAccount = false const accountID = await (async () => { const matches = await Database.use(async (tx) => tx @@ -166,6 +168,7 @@ export default { if (!accountID) { console.log("creating account for", email) accountID = await Account.create({}) + newAccount = true } await Database.use(async (tx) => @@ -215,7 +218,7 @@ export default { await Workspace.create({ name: "Default" }) } }) - return ctx.subject("account", accountID, { accountID, email }) + return ctx.subject("account", accountID, { accountID, email, newAccount }) }, }).fetch(request, env, ctx) return result From e4eb98b9911d0cd6bd3378d51b974b14828c8543 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 19 May 2026 15:52:51 +0000 Subject: [PATCH 023/237] chore: generate --- .../console/app/src/component/go-referral.tsx | 16 ++- packages/console/app/src/i18n/br.ts | 3 +- packages/console/app/src/i18n/da.ts | 3 +- packages/console/app/src/i18n/de.ts | 3 +- packages/console/app/src/i18n/es.ts | 3 +- packages/console/app/src/i18n/fr.ts | 3 +- packages/console/app/src/i18n/it.ts | 3 +- packages/console/app/src/i18n/ja.ts | 6 +- packages/console/app/src/i18n/no.ts | 3 +- packages/console/app/src/i18n/pl.ts | 3 +- packages/console/app/src/i18n/ru.ts | 3 +- packages/console/app/src/i18n/tr.ts | 3 +- packages/console/app/src/routes/black.css | 1 - .../workspace/[id]/go/lite-section.module.css | 1 - .../routes/workspace/[id]/go/lite-section.tsx | 3 +- .../snapshot.json | 109 ++++------------- .../20260516082200_long_spirit/snapshot.json | 109 ++++------------- .../snapshot.json | 109 ++++------------- .../snapshot.json | 110 ++++-------------- .../snapshot.json | 110 ++++-------------- .../snapshot.json | 110 ++++-------------- packages/console/core/src/referral.ts | 59 ++++++---- .../console/core/src/schema/referral.sql.ts | 5 +- .../console/core/src/schema/workspace.sql.ts | 5 +- 24 files changed, 215 insertions(+), 568 deletions(-) diff --git a/packages/console/app/src/component/go-referral.tsx b/packages/console/app/src/component/go-referral.tsx index 6a186974eaab..e1f4174c74d9 100644 --- a/packages/console/app/src/component/go-referral.tsx +++ b/packages/console/app/src/component/go-referral.tsx @@ -35,10 +35,9 @@ export const queryGoReferralUsagePreview = query(async (workspaceID: string, ref export const applyGoReferralReward = action(async (workspaceID: string, referralID: string) => { "use server" - return json( - await withActor(() => Referral.applyReward({ referralID }), workspaceID), - { revalidate: [queryGoReferral.key, queryGoReferralUsagePreview.key, queryLiteSubscription.key] }, - ) + return json(await withActor(() => Referral.applyReward({ referralID }), workspaceID), { + revalidate: [queryGoReferral.key, queryGoReferralUsagePreview.key, queryLiteSubscription.key], + }) }, "go.referral.reward.apply") function currentUsagePreview(usage: { resetInSec: number; usagePercent: number }) { @@ -245,7 +244,11 @@ export function GoReferralSection(props: { workspaceID: string; summary: GoRefer
- setSelected(undefined)} title={i18n.t("workspace.referral.apply.confirmTitle")}> + setSelected(undefined)} + title={i18n.t("workspace.referral.apply.confirmTitle")} + >

{i18n.t("workspace.referral.apply.confirmBody", { @@ -306,7 +309,8 @@ function GoReferralUsagePreviewRow(props: { label: string; usage: GoReferralUsag

- {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(props.usage.resetInSec, i18n, liteResetTimeKeys)} + {i18n.t("workspace.lite.subscription.resetsIn")}{" "} + {formatResetTime(props.usage.resetInSec, i18n, liteResetTimeKeys)}
) diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 4cd1ef27473c..9e0024898a00 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -697,7 +697,8 @@ export const dict = { "workspace.referral.apply.preview": "Visualizar", "workspace.referral.apply.action": "Aplicar", "workspace.referral.apply.confirmTitle": "Aplicar recompensa Go", - "workspace.referral.apply.confirmBody": "Aplique {{amount}} para reduzir os contadores atuais de uso do Go deste workspace.", + "workspace.referral.apply.confirmBody": + "Aplique {{amount}} para reduzir os contadores atuais de uso do Go deste workspace.", "workspace.referral.apply.confirmAction": "Aplicar", "download.title": "OpenCode | Baixar", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 2e27ddac33b5..13c1a8a6cb98 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -693,7 +693,8 @@ export const dict = { "workspace.referral.apply.preview": "Forhåndsvis", "workspace.referral.apply.action": "Brug", "workspace.referral.apply.confirmTitle": "Brug Go-belønning", - "workspace.referral.apply.confirmBody": "Brug {{amount}} til at reducere dette workspaces nuværende Go-forbrugstællere.", + "workspace.referral.apply.confirmBody": + "Brug {{amount}} til at reducere dette workspaces nuværende Go-forbrugstællere.", "workspace.referral.apply.confirmAction": "Brug", "download.title": "OpenCode | Download", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 6f6986e74569..9debb9f95956 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -696,7 +696,8 @@ export const dict = { "workspace.referral.apply.preview": "Vorschau", "workspace.referral.apply.action": "Einlösen", "workspace.referral.apply.confirmTitle": "Go-Belohnung einlösen", - "workspace.referral.apply.confirmBody": "Löse {{amount}} ein, um die aktuellen Go-Nutzungszähler dieses Workspace zu reduzieren.", + "workspace.referral.apply.confirmBody": + "Löse {{amount}} ein, um die aktuellen Go-Nutzungszähler dieses Workspace zu reduzieren.", "workspace.referral.apply.confirmAction": "Einlösen", "download.title": "OpenCode | Download", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 071d3a2c76ec..9b00cc644300 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -697,7 +697,8 @@ export const dict = { "workspace.referral.apply.preview": "Vista previa", "workspace.referral.apply.action": "Aplicar", "workspace.referral.apply.confirmTitle": "Aplicar recompensa de Go", - "workspace.referral.apply.confirmBody": "Aplica {{amount}} para reducir los contadores actuales de uso de Go de este workspace.", + "workspace.referral.apply.confirmBody": + "Aplica {{amount}} para reducir los contadores actuales de uso de Go de este workspace.", "workspace.referral.apply.confirmAction": "Aplicar", "download.title": "OpenCode | Descargar", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 7ae31968fa59..0bd79399b325 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -703,7 +703,8 @@ export const dict = { "workspace.referral.apply.preview": "Aperçu", "workspace.referral.apply.action": "Utiliser", "workspace.referral.apply.confirmTitle": "Utiliser la récompense Go", - "workspace.referral.apply.confirmBody": "Utilisez {{amount}} pour réduire les compteurs d'utilisation Go actuels de ce workspace.", + "workspace.referral.apply.confirmBody": + "Utilisez {{amount}} pour réduire les compteurs d'utilisation Go actuels de ce workspace.", "workspace.referral.apply.confirmAction": "Utiliser", "download.title": "OpenCode | Téléchargement", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index a2d300bede33..ab62763843c7 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -695,7 +695,8 @@ export const dict = { "workspace.referral.apply.preview": "Anteprima", "workspace.referral.apply.action": "Utilizza", "workspace.referral.apply.confirmTitle": "Utilizza premio Go", - "workspace.referral.apply.confirmBody": "Utilizza {{amount}} per ridurre i contatori di utilizzo Go attuali di questo workspace.", + "workspace.referral.apply.confirmBody": + "Utilizza {{amount}} per ridurre i contatori di utilizzo Go attuali di questo workspace.", "workspace.referral.apply.confirmAction": "Utilizza", "download.title": "OpenCode | Download", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index 003682ec53cb..a6c4158c6480 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -671,7 +671,8 @@ export const dict = { "workspace.referral.copyLink": "リンクをコピー", "workspace.referral.copied": "コピーしました", "workspace.referral.overview.title": "友達を Go に招待", - "workspace.referral.overview.subtitle": "友達がサブスクライブすると $5 分の Go クレジットを獲得。友達にも $5 が付与されます。", + "workspace.referral.overview.subtitle": + "友達がサブスクライブすると $5 分の Go クレジットを獲得。友達にも $5 が付与されます。", "workspace.referral.stats.invites": "招待", "workspace.referral.stats.earned": "獲得", "workspace.referral.stats.applied": "適用済み", @@ -695,7 +696,8 @@ export const dict = { "workspace.referral.apply.preview": "プレビュー", "workspace.referral.apply.action": "適用", "workspace.referral.apply.confirmTitle": "Go 特典を適用", - "workspace.referral.apply.confirmBody": "{{amount}} を適用して、このワークスペースの現在の Go 使用カウンターを減らします。", + "workspace.referral.apply.confirmBody": + "{{amount}} を適用して、このワークスペースの現在の Go 使用カウンターを減らします。", "workspace.referral.apply.confirmAction": "適用", "download.title": "OpenCode | ダウンロード", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index e80c3ff19ec4..019db79ba039 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -694,7 +694,8 @@ export const dict = { "workspace.referral.apply.preview": "Forhåndsvis", "workspace.referral.apply.action": "Bruk", "workspace.referral.apply.confirmTitle": "Bruk Go-belønning", - "workspace.referral.apply.confirmBody": "Bruk {{amount}} for å redusere dette workspacets nåværende Go-forbrukstellere.", + "workspace.referral.apply.confirmBody": + "Bruk {{amount}} for å redusere dette workspacets nåværende Go-forbrukstellere.", "workspace.referral.apply.confirmAction": "Bruk", "download.title": "OpenCode | Last ned", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 8ec6c519ea1f..5f123158dd9d 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -695,7 +695,8 @@ export const dict = { "workspace.referral.apply.preview": "Podgląd", "workspace.referral.apply.action": "Wykorzystaj", "workspace.referral.apply.confirmTitle": "Wykorzystaj nagrodę Go", - "workspace.referral.apply.confirmBody": "Wykorzystaj {{amount}}, aby zmniejszyć aktualne liczniki użycia Go w tym workspace.", + "workspace.referral.apply.confirmBody": + "Wykorzystaj {{amount}}, aby zmniejszyć aktualne liczniki użycia Go w tym workspace.", "workspace.referral.apply.confirmAction": "Wykorzystaj", "download.title": "OpenCode | Pobierz", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 9f0a108c04fa..40e75727f9a9 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -701,7 +701,8 @@ export const dict = { "workspace.referral.apply.preview": "Предпросмотр", "workspace.referral.apply.action": "Применить", "workspace.referral.apply.confirmTitle": "Применить награду Go", - "workspace.referral.apply.confirmBody": "Используйте {{amount}}, чтобы уменьшить текущие счётчики использования Go этого workspace.", + "workspace.referral.apply.confirmBody": + "Используйте {{amount}}, чтобы уменьшить текущие счётчики использования Go этого workspace.", "workspace.referral.apply.confirmAction": "Применить", "download.title": "OpenCode | Скачать", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index f6cf2d2b6a4c..c38a5b2fd638 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -697,7 +697,8 @@ export const dict = { "workspace.referral.apply.preview": "Önizleme", "workspace.referral.apply.action": "Kullan", "workspace.referral.apply.confirmTitle": "Go ödülünü kullan", - "workspace.referral.apply.confirmBody": "Bu workspace'in mevcut Go kullanım sayaçlarını azaltmak için {{amount}} kullan.", + "workspace.referral.apply.confirmBody": + "Bu workspace'in mevcut Go kullanım sayaçlarını azaltmak için {{amount}} kullan.", "workspace.referral.apply.confirmAction": "Kullan", "download.title": "OpenCode | İndir", diff --git a/packages/console/app/src/routes/black.css b/packages/console/app/src/routes/black.css index fa78eee5608c..648a79c33fe2 100644 --- a/packages/console/app/src/routes/black.css +++ b/packages/console/app/src/routes/black.css @@ -700,7 +700,6 @@ text-decoration: underline; } } - } } diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css index 0904f8b4aa33..5e598eeaf6ad 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css @@ -211,7 +211,6 @@ align-items: center; gap: 4px; } - } .paymentMethodModal { diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index 04d45ce25811..a3ce2bf9c996 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -140,7 +140,8 @@ function LiteUsageItem(props: { label: string; usage: { usagePercent: number; re
- {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(props.usage.resetInSec, i18n, liteResetTimeKeys)} + {i18n.t("workspace.lite.subscription.resetsIn")}{" "} + {formatResetTime(props.usage.resetInSec, i18n, liteResetTimeKeys)}
) diff --git a/packages/console/core/migrations/20260513173355_groovy_jane_foster/snapshot.json b/packages/console/core/migrations/20260513173355_groovy_jane_foster/snapshot.json index 4ab0b08f7e30..3fb58d1fb163 100644 --- a/packages/console/core/migrations/20260513173355_groovy_jane_foster/snapshot.json +++ b/packages/console/core/migrations/20260513173355_groovy_jane_foster/snapshot.json @@ -2,9 +2,7 @@ "version": "6", "dialect": "mysql", "id": "91a9afa8-a5b1-4fa4-b71d-b3ae866744c9", - "prevIds": [ - "c742e0f2-5d89-4216-b843-059d00680f13" - ], + "prevIds": ["c742e0f2-5d89-4216-b843-059d00680f13"], "ddl": [ { "name": "account", @@ -2671,194 +2669,133 @@ "table": "workspace" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "auth", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "benchmark", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "billing", "entityType": "pks" }, { - "columns": [ - "email", - "type" - ], + "columns": ["email", "type"], "name": "PRIMARY", "table": "coupon", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "lite", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "payment", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "subscription", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "usage", "entityType": "pks" }, { - "columns": [ - "ip", - "interval" - ], + "columns": ["ip", "interval"], "name": "PRIMARY", "table": "ip_rate_limit", "entityType": "pks" }, { - "columns": [ - "ip" - ], + "columns": ["ip"], "name": "PRIMARY", "table": "ip", "entityType": "pks" }, { - "columns": [ - "key", - "interval" - ], + "columns": ["key", "interval"], "name": "PRIMARY", "table": "key_rate_limit", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tpm_rate_limit", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tps_rate_limit", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "key", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "model", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "provider", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "referral_code", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "referral_reward", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "referral", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "user", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "workspace", "entityType": "pks" diff --git a/packages/console/core/migrations/20260516082200_long_spirit/snapshot.json b/packages/console/core/migrations/20260516082200_long_spirit/snapshot.json index 0bf2a4b2f7bd..2bdb0af0e789 100644 --- a/packages/console/core/migrations/20260516082200_long_spirit/snapshot.json +++ b/packages/console/core/migrations/20260516082200_long_spirit/snapshot.json @@ -2,10 +2,7 @@ "version": "6", "dialect": "mysql", "id": "e93db4f6-6e8b-43f4-b883-59d2fd6ede53", - "prevIds": [ - "1f04bd59-35b0-493b-9d55-cfa08207ba8e", - "91a9afa8-a5b1-4fa4-b71d-b3ae866744c9" - ], + "prevIds": ["1f04bd59-35b0-493b-9d55-cfa08207ba8e", "91a9afa8-a5b1-4fa4-b71d-b3ae866744c9"], "ddl": [ { "name": "account", @@ -2574,191 +2571,133 @@ "table": "workspace" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "auth", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "benchmark", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "billing", "entityType": "pks" }, { - "columns": [ - "email", - "type" - ], + "columns": ["email", "type"], "name": "PRIMARY", "table": "coupon", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "lite", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "payment", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "subscription", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "usage", "entityType": "pks" }, { - "columns": [ - "ip", - "interval" - ], + "columns": ["ip", "interval"], "name": "PRIMARY", "table": "ip_rate_limit", "entityType": "pks" }, { - "columns": [ - "ip" - ], + "columns": ["ip"], "name": "PRIMARY", "table": "ip", "entityType": "pks" }, { - "columns": [ - "key", - "interval" - ], + "columns": ["key", "interval"], "name": "PRIMARY", "table": "key_rate_limit", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "model_sticky_provider", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tpm_rate_limit", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tps_rate_limit", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "key", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "model", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "provider", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "user", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "referral_reward", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "referral", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "workspace", "entityType": "pks" @@ -3121,4 +3060,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/20260516110447_classy_wilson_fisk/snapshot.json b/packages/console/core/migrations/20260516110447_classy_wilson_fisk/snapshot.json index 5e0d53517374..f22931251840 100644 --- a/packages/console/core/migrations/20260516110447_classy_wilson_fisk/snapshot.json +++ b/packages/console/core/migrations/20260516110447_classy_wilson_fisk/snapshot.json @@ -2,9 +2,7 @@ "version": "6", "dialect": "mysql", "id": "77de1eaf-69f6-433f-b244-cda75319450a", - "prevIds": [ - "e93db4f6-6e8b-43f4-b883-59d2fd6ede53" - ], + "prevIds": ["e93db4f6-6e8b-43f4-b883-59d2fd6ede53"], "ddl": [ { "name": "account", @@ -2559,192 +2557,133 @@ "table": "workspace" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "auth", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "benchmark", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "billing", "entityType": "pks" }, { - "columns": [ - "email", - "type" - ], + "columns": ["email", "type"], "name": "PRIMARY", "table": "coupon", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "lite", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "payment", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "subscription", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "usage", "entityType": "pks" }, { - "columns": [ - "ip", - "interval" - ], + "columns": ["ip", "interval"], "name": "PRIMARY", "table": "ip_rate_limit", "entityType": "pks" }, { - "columns": [ - "ip" - ], + "columns": ["ip"], "name": "PRIMARY", "table": "ip", "entityType": "pks" }, { - "columns": [ - "key", - "interval" - ], + "columns": ["key", "interval"], "name": "PRIMARY", "table": "key_rate_limit", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "model_sticky_provider", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tpm_rate_limit", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tps_rate_limit", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "key", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "model", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "provider", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "referral_id" - ], + "columns": ["workspace_id", "referral_id"], "name": "PRIMARY", "table": "referral_reward", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "user", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "referral", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "workspace", "entityType": "pks" @@ -3087,4 +3026,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/snapshot.json b/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/snapshot.json index 73c4209c2f4e..c38a24ee8b59 100644 --- a/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/snapshot.json +++ b/packages/console/core/migrations/20260516112143_cynical_dexter_bennett/snapshot.json @@ -2,9 +2,7 @@ "version": "6", "dialect": "mysql", "id": "9809797a-e6f6-418c-8532-196553b315b8", - "prevIds": [ - "77de1eaf-69f6-433f-b244-cda75319450a" - ], + "prevIds": ["77de1eaf-69f6-433f-b244-cda75319450a"], "ddl": [ { "name": "account", @@ -2559,193 +2557,133 @@ "table": "workspace" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "auth", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "benchmark", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "billing", "entityType": "pks" }, { - "columns": [ - "email", - "type" - ], + "columns": ["email", "type"], "name": "PRIMARY", "table": "coupon", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "lite", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "payment", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "subscription", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "usage", "entityType": "pks" }, { - "columns": [ - "ip", - "interval" - ], + "columns": ["ip", "interval"], "name": "PRIMARY", "table": "ip_rate_limit", "entityType": "pks" }, { - "columns": [ - "ip" - ], + "columns": ["ip"], "name": "PRIMARY", "table": "ip", "entityType": "pks" }, { - "columns": [ - "key", - "interval" - ], + "columns": ["key", "interval"], "name": "PRIMARY", "table": "key_rate_limit", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "model_sticky_provider", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tpm_rate_limit", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tps_rate_limit", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "key", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "model", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "provider", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "referral_id" - ], + "columns": ["workspace_id", "referral_id"], "name": "PRIMARY", "table": "referral_reward", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "referral", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "user", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "workspace", "entityType": "pks" @@ -3072,4 +3010,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/20260516222132_referral_code_length/snapshot.json b/packages/console/core/migrations/20260516222132_referral_code_length/snapshot.json index 9841c032b923..b7265ebefa45 100644 --- a/packages/console/core/migrations/20260516222132_referral_code_length/snapshot.json +++ b/packages/console/core/migrations/20260516222132_referral_code_length/snapshot.json @@ -2,9 +2,7 @@ "version": "6", "dialect": "mysql", "id": "e49321c9-2b03-4e2c-b2f0-76fbb88088ea", - "prevIds": [ - "9809797a-e6f6-418c-8532-196553b315b8" - ], + "prevIds": ["9809797a-e6f6-418c-8532-196553b315b8"], "ddl": [ { "name": "account", @@ -2559,193 +2557,133 @@ "table": "workspace" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "auth", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "benchmark", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "billing", "entityType": "pks" }, { - "columns": [ - "email", - "type" - ], + "columns": ["email", "type"], "name": "PRIMARY", "table": "coupon", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "lite", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "payment", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "subscription", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "usage", "entityType": "pks" }, { - "columns": [ - "ip", - "interval" - ], + "columns": ["ip", "interval"], "name": "PRIMARY", "table": "ip_rate_limit", "entityType": "pks" }, { - "columns": [ - "ip" - ], + "columns": ["ip"], "name": "PRIMARY", "table": "ip", "entityType": "pks" }, { - "columns": [ - "key", - "interval" - ], + "columns": ["key", "interval"], "name": "PRIMARY", "table": "key_rate_limit", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "model_sticky_provider", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tpm_rate_limit", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tps_rate_limit", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "key", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "model", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "provider", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "referral_id" - ], + "columns": ["workspace_id", "referral_id"], "name": "PRIMARY", "table": "referral_reward", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "referral", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "user", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "workspace", "entityType": "pks" @@ -3072,4 +3010,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/console/core/migrations/20260518181630_secret_strong_guy/snapshot.json b/packages/console/core/migrations/20260518181630_secret_strong_guy/snapshot.json index 5b8e94a7ff24..c7625a714e6b 100644 --- a/packages/console/core/migrations/20260518181630_secret_strong_guy/snapshot.json +++ b/packages/console/core/migrations/20260518181630_secret_strong_guy/snapshot.json @@ -2,9 +2,7 @@ "version": "6", "dialect": "mysql", "id": "9a7123cb-5c2c-47f6-b22f-8fd6a02dcb03", - "prevIds": [ - "e49321c9-2b03-4e2c-b2f0-76fbb88088ea" - ], + "prevIds": ["e49321c9-2b03-4e2c-b2f0-76fbb88088ea"], "ddl": [ { "name": "account", @@ -2559,193 +2557,133 @@ "table": "workspace" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "auth", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "benchmark", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "billing", "entityType": "pks" }, { - "columns": [ - "email", - "type" - ], + "columns": ["email", "type"], "name": "PRIMARY", "table": "coupon", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "lite", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "payment", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "subscription", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "usage", "entityType": "pks" }, { - "columns": [ - "ip", - "interval" - ], + "columns": ["ip", "interval"], "name": "PRIMARY", "table": "ip_rate_limit", "entityType": "pks" }, { - "columns": [ - "ip" - ], + "columns": ["ip"], "name": "PRIMARY", "table": "ip", "entityType": "pks" }, { - "columns": [ - "key", - "interval" - ], + "columns": ["key", "interval"], "name": "PRIMARY", "table": "key_rate_limit", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "model_sticky_provider", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tpm_rate_limit", "entityType": "pks" }, { - "columns": [ - "id", - "interval" - ], + "columns": ["id", "interval"], "name": "PRIMARY", "table": "model_tps_rate_limit", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "key", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "model", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "provider", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "referral_id" - ], + "columns": ["workspace_id", "referral_id"], "name": "PRIMARY", "table": "referral_reward", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "referral", "entityType": "pks" }, { - "columns": [ - "workspace_id", - "id" - ], + "columns": ["workspace_id", "id"], "name": "PRIMARY", "table": "user", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "name": "PRIMARY", "table": "workspace", "entityType": "pks" @@ -3072,4 +3010,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/console/core/src/referral.ts b/packages/console/core/src/referral.ts index 7150a21bdca7..d9d88bf10494 100644 --- a/packages/console/core/src/referral.ts +++ b/packages/console/core/src/referral.ts @@ -19,7 +19,10 @@ export namespace Referral { export const CODE_LENGTH = 10 export function normalizeCode(code?: string | null) { - return code?.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, CODE_LENGTH) + return code + ?.toUpperCase() + .replace(/[^A-Z0-9]/g, "") + .slice(0, CODE_LENGTH) } function generateCode() { @@ -41,7 +44,11 @@ export namespace Referral { .update(WorkspaceTable) .set({ referralCode: generateCode() }) .where( - and(eq(WorkspaceTable.id, workspaceID), isNull(WorkspaceTable.referralCode), isNull(WorkspaceTable.timeDeleted)), + and( + eq(WorkspaceTable.id, workspaceID), + isNull(WorkspaceTable.referralCode), + isNull(WorkspaceTable.timeDeleted), + ), ) const created = await tx @@ -74,7 +81,10 @@ export namespace Referral { }) .from(ReferralRewardTable) .innerJoin(ReferralTable, eq(ReferralTable.id, ReferralRewardTable.referralID)) - .innerJoin(AuthTable, and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email"))) + .innerJoin( + AuthTable, + and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email")), + ) .where( and( eq(ReferralRewardTable.workspaceID, workspaceID), @@ -86,12 +96,18 @@ export namespace Referral { tx .select({ id: ReferralTable.id, inviteeEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) .from(ReferralTable) - .innerJoin(AuthTable, and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email"))) + .innerJoin( + AuthTable, + and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email")), + ) .where(and(eq(ReferralTable.workspaceID, workspaceID), isNull(ReferralTable.timeDeleted))), tx .select({ id: ReferralTable.id, inviteeEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) .from(ReferralTable) - .innerJoin(AuthTable, and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email"))) + .innerJoin( + AuthTable, + and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email")), + ) .where(and(eq(ReferralTable.inviteeAccountID, accountID), isNull(ReferralTable.timeDeleted))), tx .select({ referralID: ReferralRewardTable.referralID }) @@ -158,9 +174,7 @@ export namespace Referral { hasActiveGo: !!rows.lite, rewardAmount: microCentsToCents(REWARD_AMOUNT), totalEarned: rewards.reduce((total, reward) => total + reward.amount, 0), - totalApplied: rewards - .filter((reward) => reward.timeApplied) - .reduce((total, reward) => total + reward.amount, 0), + totalApplied: rewards.filter((reward) => reward.timeApplied).reduce((total, reward) => total + reward.amount, 0), rewards: allRewards, } }) @@ -277,10 +291,7 @@ export namespace Referral { } }) - export async function createFromAccount(input: { - accountID: string - referralCode?: string - }) { + export async function createFromAccount(input: { accountID: string; referralCode?: string }) { const referralCode = normalizeCode(input.referralCode) if (!referralCode) return @@ -313,14 +324,11 @@ export namespace Referral { if (selfReferral) throw new Error("Self-referral is not allowed") const referralID = Identifier.create("referral") - await tx - .insert(ReferralTable) - .ignore() - .values({ - workspaceID: code.workspaceID, - id: referralID, - inviteeAccountID: input.accountID, - }) + await tx.insert(ReferralTable).ignore().values({ + workspaceID: code.workspaceID, + id: referralID, + inviteeAccountID: input.accountID, + }) const referral = await tx .select({ id: ReferralTable.id, workspaceID: ReferralTable.workspaceID }) @@ -332,16 +340,17 @@ export namespace Referral { }) } - export async function completeFromLiteSubscription(input: { - workspaceID: string - userID: string - }) { + export async function completeFromLiteSubscription(input: { workspaceID: string; userID: string }) { return Database.transaction(async (tx) => { const invitee = await tx .select({ accountID: UserTable.accountID }) .from(UserTable) .where( - and(eq(UserTable.workspaceID, input.workspaceID), eq(UserTable.id, input.userID), isNull(UserTable.timeDeleted)), + and( + eq(UserTable.workspaceID, input.workspaceID), + eq(UserTable.id, input.userID), + isNull(UserTable.timeDeleted), + ), ) .then((rows) => rows[0]) if (!invitee?.accountID) throw new Error("Referral invitee account missing") diff --git a/packages/console/core/src/schema/referral.sql.ts b/packages/console/core/src/schema/referral.sql.ts index 63d01a2c7d78..8a8325441156 100644 --- a/packages/console/core/src/schema/referral.sql.ts +++ b/packages/console/core/src/schema/referral.sql.ts @@ -9,10 +9,7 @@ export const ReferralTable = mysqlTable( ...timestamps, inviteeAccountID: ulid("invitee_account_id").notNull(), }, - (table) => [ - ...workspaceIndexes(table), - uniqueIndex("referral_invitee_account_id").on(table.inviteeAccountID), - ], + (table) => [...workspaceIndexes(table), uniqueIndex("referral_invitee_account_id").on(table.inviteeAccountID)], ) export const ReferralRewardTable = mysqlTable( diff --git a/packages/console/core/src/schema/workspace.sql.ts b/packages/console/core/src/schema/workspace.sql.ts index 7fe7e0c2a2e3..fc9b0dd5f666 100644 --- a/packages/console/core/src/schema/workspace.sql.ts +++ b/packages/console/core/src/schema/workspace.sql.ts @@ -10,10 +10,7 @@ export const WorkspaceTable = mysqlTable( name: varchar("name", { length: 255 }).notNull(), ...timestamps, }, - (table) => [ - uniqueIndex("slug").on(table.slug), - uniqueIndex("referral_code").on(table.referralCode), - ], + (table) => [uniqueIndex("slug").on(table.slug), uniqueIndex("referral_code").on(table.referralCode)], ) export function workspaceIndexes(table: any) { From c449d3dc7403a1b2144dc1bb9dc27800885f77f4 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 19 May 2026 12:06:07 -0400 Subject: [PATCH 024/237] Migrate remaining legacy tools config tests (#28363) --- packages/opencode/test/config/config.test.ts | 90 +++++++------------- perf/test-suite.md | 1 + 2 files changed, 31 insertions(+), 60 deletions(-) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index ccc63d807d19..9a88fcf4b9ed 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1204,67 +1204,37 @@ test("keeps plugin origins aligned with merged plugin list", async () => { // Legacy tools migration tests -test("migrates legacy tools config to permissions - allow", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - agent: { - test: { - tools: { - bash: true, - read: true, - }, - }, - }, - }), - ) - }, - }) - await withTestInstance({ - directory: tmp.path, - fn: async (ctx) => { - const config = await load(ctx) - expect(config.agent?.["test"]?.permission).toEqual({ - bash: "allow", - read: "allow", - }) - }, - }) -}) +it.instance("migrates legacy tools config to permissions - allow", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeConfigEffect(test.directory, { + $schema: "https://opencode.ai/config.json", + agent: { test: { tools: { bash: true, read: true } } }, + }) -test("migrates legacy tools config to permissions - deny", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - agent: { - test: { - tools: { - bash: false, - webfetch: false, - }, - }, - }, - }), - ) - }, - }) - await withTestInstance({ - directory: tmp.path, - fn: async (ctx) => { - const config = await load(ctx) - expect(config.agent?.["test"]?.permission).toEqual({ - bash: "deny", - webfetch: "deny", - }) - }, - }) -}) + const config = yield* Config.Service.use((svc) => svc.get()) + expect(config.agent?.["test"]?.permission).toEqual({ + bash: "allow", + read: "allow", + }) + }), +) + +it.instance("migrates legacy tools config to permissions - deny", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeConfigEffect(test.directory, { + $schema: "https://opencode.ai/config.json", + agent: { test: { tools: { bash: false, webfetch: false } } }, + }) + + const config = yield* Config.Service.use((svc) => svc.get()) + expect(config.agent?.["test"]?.permission).toEqual({ + bash: "deny", + webfetch: "deny", + }) + }), +) it.instance("migrates legacy write tool to edit permission", () => Effect.gen(function* () { diff --git a/perf/test-suite.md b/perf/test-suite.md index b37b868bbd3a..77f7e7359b19 100644 --- a/perf/test-suite.md +++ b/perf/test-suite.md @@ -81,6 +81,7 @@ Repeated setup work, long sleeps/timeouts, serial integration tests, filesystem/ | Managed settings config cases can use Effect-aware instance fixtures | Migrated managed override and missing-managed-file cases to `it.instance` | 2.40s | 1.76s | keep | Single baseline before edit; after median from three sequential reruns (1.75, 1.76, 1.80). | | Local plugin and subagent config fixtures can use Effect-aware instance fixtures | Migrated scoped npm plugin and custom subagent markdown cases to `it.instance` | 2.37s | 1.67s | keep | Single baseline before edit; after median from three sequential reruns (1.66, 1.67, 1.67). | | MCP merge config cases can use Effect-aware instance fixtures | Migrated three MCP merge/override cases to `it.instance` | 1.98s | 1.95s | keep | Neutral timing within noise; removes manual `tmpdir` + `withTestInstance` setup from isolated filesystem-only config cases. | +| Remaining legacy tools config cases can use Effect-aware instance fixtures | Migrated allow/deny legacy `tools` permission cases to `it.instance` | 2.65s | 1.90s | keep | Single baseline before edit; after median from three sequential reruns (2.58, 1.90, 1.90). | ## Profiling Results From b20b569b0e3c5c10b071c8130377a7294f1dd733 Mon Sep 17 00:00:00 2001 From: Victor Navarro Date: Tue, 19 May 2026 20:26:38 +0200 Subject: [PATCH 025/237] chore(go): referral improvements (#28368) --- .../console/app/src/component/go-referral.css | 69 +------ .../console/app/src/component/go-referral.tsx | 181 ++++++++---------- packages/console/app/src/i18n/ar.ts | 33 ++-- packages/console/app/src/i18n/br.ts | 34 ++-- packages/console/app/src/i18n/da.ts | 34 ++-- packages/console/app/src/i18n/de.ts | 33 ++-- packages/console/app/src/i18n/en.ts | 33 ++-- packages/console/app/src/i18n/es.ts | 34 ++-- packages/console/app/src/i18n/fr.ts | 34 ++-- packages/console/app/src/i18n/it.ts | 34 ++-- packages/console/app/src/i18n/ja.ts | 35 ++-- packages/console/app/src/i18n/ko.ts | 33 ++-- packages/console/app/src/i18n/no.ts | 34 ++-- packages/console/app/src/i18n/pl.ts | 34 ++-- packages/console/app/src/i18n/ru.ts | 34 ++-- packages/console/app/src/i18n/th.ts | 33 ++-- packages/console/app/src/i18n/tr.ts | 34 ++-- packages/console/app/src/i18n/zh.ts | 33 ++-- packages/console/app/src/i18n/zht.ts | 33 ++-- packages/console/app/src/routes/go/index.tsx | 11 +- .../src/routes/workspace/[id]/go/index.tsx | 11 +- .../routes/workspace/[id]/go/lite-section.tsx | 11 +- packages/console/core/src/referral.ts | 82 ++++---- packages/console/core/sst-env.d.ts | 1 + packages/console/function/sst-env.d.ts | 1 + packages/console/resource/sst-env.d.ts | 1 + packages/enterprise/sst-env.d.ts | 1 + packages/function/sst-env.d.ts | 1 + sst-env.d.ts | 4 + 29 files changed, 456 insertions(+), 490 deletions(-) diff --git a/packages/console/app/src/component/go-referral.css b/packages/console/app/src/component/go-referral.css index c9050e6cf972..aa61612d4b93 100644 --- a/packages/console/app/src/component/go-referral.css +++ b/packages/console/app/src/component/go-referral.css @@ -183,61 +183,8 @@ } } - [data-slot="rewards-title"] { - display: flex; - flex-direction: column; - gap: var(--space-1); - padding-top: var(--space-2); - - h2 { - margin: 0; - color: var(--color-text); - font-size: var(--font-size-md); - font-weight: 600; - line-height: 1.2; - letter-spacing: -0.03125rem; - } - - p { - margin: 0; - color: var(--color-text-muted); - font-size: var(--font-size-sm); - line-height: 1.5; - } - } - - [data-slot="referral-stats"] { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: var(--space-6); - - @media (max-width: 30rem) { - grid-template-columns: 1fr; - gap: var(--space-4); - } - - > div { - display: flex; - flex-direction: column; - gap: var(--space-2); - padding: var(--space-4); - border: 1px solid var(--color-border); - border-radius: var(--border-radius-sm); - } - - span { - color: var(--color-text-muted); - font-size: var(--font-size-sm); - font-weight: 600; - text-transform: uppercase; - letter-spacing: -0.025rem; - } - - strong { - color: var(--color-text); - font-size: var(--font-size-md); - font-weight: 600; - } + [data-component="go-referral-overview"] + [data-slot="section-title"] { + margin-top: var(--space-4); } [data-slot="referrals-table"] { @@ -296,17 +243,7 @@ &[data-slot="referral-source"] { color: var(--color-text-secondary); font-family: var(--font-sans); - - span { - display: block; - } - - [data-slot="referral-email"] { - margin-top: var(--space-1); - color: var(--color-text-muted); - line-height: 1.4; - white-space: normal; - } + white-space: nowrap; } &[data-slot="referral-action"] { diff --git a/packages/console/app/src/component/go-referral.tsx b/packages/console/app/src/component/go-referral.tsx index e1f4174c74d9..325111cadc4e 100644 --- a/packages/console/app/src/component/go-referral.tsx +++ b/packages/console/app/src/component/go-referral.tsx @@ -1,4 +1,4 @@ -import { action, createAsync, json, query, useAction, useSubmission } from "@solidjs/router" +import { action, json, query, useAction, useSubmission } from "@solidjs/router" import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js" import { getRequestEvent } from "solid-js/web" import { Referral } from "@opencode-ai/console-core/referral.js" @@ -13,6 +13,7 @@ import "./go-referral.css" type GoReferralSummary = Awaited> type GoReferralReward = GoReferralSummary["rewards"][number] +type GoLiteSubscription = Awaited> type GoReferralUsagePreview = NonNullable>> type GoReferralUsagePreviewItem = GoReferralUsagePreview["rollingUsage"] @@ -57,17 +58,15 @@ function formatDate(value: string | Date, locale: string) { return new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", year: "numeric" }).format(new Date(value)) } -function rewardTitleKey(reward: GoReferralReward) { - if (reward.status === "pending" && reward.source === "invitee") - return "workspace.referral.reward.source.pendingInvitee" as const - if (reward.status === "pending") return "workspace.referral.reward.source.pendingInviter" as const - if (reward.status === "applied") return "workspace.referral.reward.source.applied" as const - return "workspace.referral.reward.source.available" as const +function rewardDescriptionKey(source: GoReferralReward["source"]) { + if (source === "invitee") return "workspace.referral.reward.description.invitee" as const + return "workspace.referral.reward.description.inviter" as const } -function rewardPendingStatusKey(source: GoReferralReward["source"]) { - if (source === "invitee") return "workspace.referral.reward.status.pendingInvitee" as const - return "workspace.referral.reward.status.pendingInviter" as const +function rewardActionKey(reward: GoReferralReward, hasActiveGo: boolean) { + if (reward.status === "applied") return "workspace.referral.reward.action.applied" as const + if (reward.status === "pending" || !hasActiveGo) return "workspace.referral.reward.action.subscribeUnlock" as const + return "workspace.referral.reward.action.view" as const } function CopyInviteLink(props: { summary: GoReferralSummary }) { @@ -113,18 +112,21 @@ function CopyInviteLink(props: { summary: GoReferralSummary }) { ) } -export function GoReferralSection(props: { workspaceID: string; summary: GoReferralSummary }) { +export function GoReferralSection(props: { + workspaceID: string + summary: GoReferralSummary + lite: GoLiteSubscription | undefined +}) { const i18n = useI18n() const language = useLanguage() const apply = useAction(applyGoReferralReward) const submission = useSubmission(applyGoReferralReward) const [selected, setSelected] = createSignal() const [preview, setPreview] = createSignal() - const lite = createAsync(() => queryLiteSubscription(props.workspaceID)) const displayPreview = createMemo(() => { const loaded = preview() if (loaded) return loaded - const current = lite() + const current = props.lite if (!current) return emptyUsagePreview return { rollingUsage: currentUsagePreview(current.rollingUsage), @@ -158,92 +160,73 @@ export function GoReferralSection(props: { workspaceID: string; summary: GoRefer } return ( -
-
-

{i18n.t("workspace.referral.overview.title")}

-

- {i18n.t("workspace.referral.overview.subtitle", { - reward: formatCurrency(props.summary.rewardAmount), - })} -

-
-
-
-
- {i18n.t("workspace.referral.stats.invites")} - {props.summary.inviteCount} -
-
- {i18n.t("workspace.referral.stats.earned")} - {formatCurrency(props.summary.totalEarned)} -
-
- {i18n.t("workspace.referral.stats.applied")} - {formatCurrency(props.summary.totalApplied)} -
-
- -
-
    -
  1. {i18n.t("workspace.referral.instructions.share")}
  2. -
  3. {i18n.t("workspace.referral.instructions.subscribe")}
  4. -
  5. {i18n.t("workspace.referral.instructions.claim")}
  6. -
-
-
- 0} - fallback={
{i18n.t("workspace.referral.rewards.empty")}
} - > -
- - - - - - - - - - - - {(reward) => { - const applied = reward.status === "applied" - const pending = reward.status === "pending" - const earnedAt = () => formatDate(reward.timeCreated, language.tag(language.locale())) - return ( - - - - - - - ) - }} - - -
{i18n.t("workspace.referral.table.reward")}{i18n.t("workspace.referral.table.referral")}{i18n.t("workspace.referral.table.date")}
{formatCurrency(reward.amount)} - {i18n.t(rewardTitleKey(reward))} - {reward.email} - - {earnedAt()} - - -
-
+ <> + +
+ +
+

{i18n.t("workspace.referral.overview.title")}

+

{i18n.t("workspace.referral.overview.subtitle")}

+
+
+ +
+
    +
  1. {i18n.t("workspace.referral.instructions.share")}
  2. +
  3. {i18n.t("workspace.referral.instructions.subscribe")}
  4. +
  5. {i18n.t("workspace.referral.instructions.claim")}
  6. +
+
+
+
+ +
+

{i18n.t("workspace.referral.rewards.title")}

+

{i18n.t("workspace.referral.rewards.description")}

+
+
+ + + + + + + + + + + + {(reward) => { + const earnedAt = () => formatDate(reward.timeCreated, language.tag(language.locale())) + return ( + + + + + + + ) + }} + + +
{i18n.t("workspace.referral.table.reward")}{i18n.t("workspace.referral.table.referral")}{i18n.t("workspace.referral.table.date")}
{formatCurrency(reward.amount)} + {i18n.t(rewardDescriptionKey(reward.source), { email: reward.email ?? "" })} + + {earnedAt()} + + +
+
+
+
+ setSelected(undefined)} @@ -266,7 +249,7 @@ export function GoReferralSection(props: { workspaceID: string; summary: GoRefer
- + ) } diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 291d732c222f..bf624e04552c 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -662,32 +662,35 @@ export const dict = { "workspace.referral.copyLink": "نسخ الرابط", "workspace.referral.copied": "تم النسخ", - "workspace.referral.overview.title": "ادعُ أصدقاءك إلى Go", - "workspace.referral.overview.subtitle": "احصل على رصيد Go بقيمة $5 عند اشتراك صديق. وسيحصل هو أيضًا على $5.", - "workspace.referral.stats.invites": "الدعوات", - "workspace.referral.stats.earned": "المُكتسب", - "workspace.referral.stats.applied": "المُطبَّق", - "workspace.referral.instructions.share": "شارك رابط دعوتك", - "workspace.referral.instructions.subscribe": "يشترك صديقك في Go", - "workspace.referral.instructions.claim": "طبِّق رصيد $5 الخاص بك أدناه", + "workspace.referral.overview.title": "ادعُ أصدقاءك", + "workspace.referral.overview.subtitle": "احصل على $5 عند اشتراك صديق. وسيحصل هو أيضًا على $5.", + "workspace.referral.instructions.share": "شارك رابط الإحالة الخاص بك", + "workspace.referral.instructions.subscribe": "ينضم صديقك ويشترك في Go", + "workspace.referral.instructions.claim": "تحصلان كلاكما على رصيد استخدام بقيمة $5 لتطبيقه على حدود استخدام Go", "workspace.referral.rewards.title": "مكافآت الإحالة", + "workspace.referral.rewards.description": "طبّق أرصدة الإحالة المتاحة على استخدامك لـ Go.", "workspace.referral.rewards.subtitle": "تم تطبيق {{applied}} / {{total}} من المكافآت.", "workspace.referral.rewards.empty": "لا توجد مكافآت إحالة بعد.", "workspace.referral.table.reward": "المكافأة", "workspace.referral.table.referral": "الوصف", "workspace.referral.table.date": "التاريخ", + "workspace.referral.reward.description.inviter": "تمت دعوة {{email}}", + "workspace.referral.reward.description.invitee": "تمت دعوتك بواسطة {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "اشترك لإلغاء القفل", + "workspace.referral.reward.action.view": "عرض المكافأة", + "workspace.referral.reward.action.applied": "تم تطبيق المكافأة", "workspace.referral.reward.source.pendingInviter": "بانتظار اشتراكه", "workspace.referral.reward.source.pendingInvitee": "اشترك لإلغاء قفل المكافأة", "workspace.referral.reward.source.available": "المكافأة جاهزة للتطبيق", "workspace.referral.reward.source.applied": "تم تطبيق المكافأة", - "workspace.referral.reward.status.applied": "مُطبَّق", - "workspace.referral.reward.status.pendingInviter": "تطبيق", - "workspace.referral.reward.status.pendingInvitee": "تطبيق", - "workspace.referral.apply.noGo": "الاشتراك في Go", - "workspace.referral.apply.preview": "معاينة", + "workspace.referral.reward.status.applied": "تم تطبيق المكافأة", + "workspace.referral.reward.status.pendingInviter": "اشترك لإلغاء القفل", + "workspace.referral.reward.status.pendingInvitee": "اشترك لإلغاء القفل", + "workspace.referral.apply.noGo": "اشترك لإلغاء القفل", + "workspace.referral.apply.preview": "عرض المكافأة", "workspace.referral.apply.action": "تطبيق", - "workspace.referral.apply.confirmTitle": "تطبيق مكافأة Go", - "workspace.referral.apply.confirmBody": "طبِّق {{amount}} لتقليل عدّادات استخدام Go الحالية في مساحة العمل هذه.", + "workspace.referral.apply.confirmTitle": "تطبيق المكافأة", + "workspace.referral.apply.confirmBody": "طبِّق {{amount}} لتقليل الاستخدام الحالي في مساحة العمل هذه.", "workspace.referral.apply.confirmAction": "تطبيق", "download.title": "OpenCode | تنزيل", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 9e0024898a00..409a5138a452 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -672,33 +672,35 @@ export const dict = { "workspace.referral.copyLink": "Copiar link", "workspace.referral.copied": "Copiado", - "workspace.referral.overview.title": "Convide amigos para o Go", - "workspace.referral.overview.subtitle": "Ganhe $5 em crédito Go quando um amigo assinar. Ele também ganha $5.", - "workspace.referral.stats.invites": "Convites", - "workspace.referral.stats.earned": "Ganhos", - "workspace.referral.stats.applied": "Aplicados", - "workspace.referral.instructions.share": "Compartilhe seu link de convite", - "workspace.referral.instructions.subscribe": "Seu amigo assina o Go", - "workspace.referral.instructions.claim": "Aplique seu crédito de $5 abaixo", + "workspace.referral.overview.title": "Convide amigos", + "workspace.referral.overview.subtitle": "Ganhe $5 quando um amigo assinar. Ele também ganha $5.", + "workspace.referral.instructions.share": "Compartilhe seu link de indicação", + "workspace.referral.instructions.subscribe": "Seu amigo entra e assina o Go", + "workspace.referral.instructions.claim": "Vocês dois ganham um crédito de uso de $5 para aplicar aos seus limites de uso do Go", "workspace.referral.rewards.title": "Recompensas de indicação", + "workspace.referral.rewards.description": "Aplique os créditos de indicação disponíveis no seu uso do Go.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} recompensas aplicadas.", "workspace.referral.rewards.empty": "Ainda não há recompensas de indicação.", "workspace.referral.table.reward": "Recompensa", "workspace.referral.table.referral": "Descrição", "workspace.referral.table.date": "Data", + "workspace.referral.reward.description.inviter": "Convidou {{email}}", + "workspace.referral.reward.description.invitee": "Convidado por {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Assine para desbloquear", + "workspace.referral.reward.action.view": "Ver recompensa", + "workspace.referral.reward.action.applied": "Recompensa aplicada", "workspace.referral.reward.source.pendingInviter": "Aguardando ele assinar", "workspace.referral.reward.source.pendingInvitee": "Assine para desbloquear a recompensa", "workspace.referral.reward.source.available": "Recompensa pronta para usar", "workspace.referral.reward.source.applied": "Recompensa aplicada", - "workspace.referral.reward.status.applied": "Aplicada", - "workspace.referral.reward.status.pendingInviter": "Aplicar", - "workspace.referral.reward.status.pendingInvitee": "Aplicar", - "workspace.referral.apply.noGo": "Assinar Go", - "workspace.referral.apply.preview": "Visualizar", + "workspace.referral.reward.status.applied": "Recompensa aplicada", + "workspace.referral.reward.status.pendingInviter": "Assine para desbloquear", + "workspace.referral.reward.status.pendingInvitee": "Assine para desbloquear", + "workspace.referral.apply.noGo": "Assine para desbloquear", + "workspace.referral.apply.preview": "Ver recompensa", "workspace.referral.apply.action": "Aplicar", - "workspace.referral.apply.confirmTitle": "Aplicar recompensa Go", - "workspace.referral.apply.confirmBody": - "Aplique {{amount}} para reduzir os contadores atuais de uso do Go deste workspace.", + "workspace.referral.apply.confirmTitle": "Aplicar recompensa", + "workspace.referral.apply.confirmBody": "Aplique {{amount}} para reduzir o uso atual deste workspace.", "workspace.referral.apply.confirmAction": "Aplicar", "download.title": "OpenCode | Baixar", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 13c1a8a6cb98..ae3940fa704e 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -668,33 +668,35 @@ export const dict = { "workspace.referral.copyLink": "Kopiér link", "workspace.referral.copied": "Kopieret", - "workspace.referral.overview.title": "Inviter venner til Go", - "workspace.referral.overview.subtitle": "Få $5 i Go-kredit, når en ven abonnerer. De får også $5.", - "workspace.referral.stats.invites": "Invitationer", - "workspace.referral.stats.earned": "Optjent", - "workspace.referral.stats.applied": "Brugt", - "workspace.referral.instructions.share": "Del dit invitationslink", - "workspace.referral.instructions.subscribe": "Din ven abonnerer på Go", - "workspace.referral.instructions.claim": "Brug din $5-kredit nedenfor", + "workspace.referral.overview.title": "Inviter venner", + "workspace.referral.overview.subtitle": "Få $5, når en ven abonnerer. De får også $5.", + "workspace.referral.instructions.share": "Del dit henvisningslink", + "workspace.referral.instructions.subscribe": "Din ven tilmelder sig og abonnerer på Go", + "workspace.referral.instructions.claim": "I får begge $5 i forbrugskredit til at bruge på jeres Go-forbrugsgrænser", "workspace.referral.rewards.title": "Henvisningsbelønninger", + "workspace.referral.rewards.description": "Brug tilgængelige henvisningskreditter på dit Go-forbrug.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} belønninger brugt.", "workspace.referral.rewards.empty": "Ingen henvisningsbelønninger endnu.", "workspace.referral.table.reward": "Belønning", "workspace.referral.table.referral": "Beskrivelse", "workspace.referral.table.date": "Dato", + "workspace.referral.reward.description.inviter": "Inviterede {{email}}", + "workspace.referral.reward.description.invitee": "Inviteret af {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Abonner for at låse op", + "workspace.referral.reward.action.view": "Vis belønning", + "workspace.referral.reward.action.applied": "Belønning brugt", "workspace.referral.reward.source.pendingInviter": "Venter på, at de abonnerer", "workspace.referral.reward.source.pendingInvitee": "Abonner for at låse belønningen op", "workspace.referral.reward.source.available": "Belønning klar til brug", "workspace.referral.reward.source.applied": "Belønning brugt", - "workspace.referral.reward.status.applied": "Brugt", - "workspace.referral.reward.status.pendingInviter": "Brug", - "workspace.referral.reward.status.pendingInvitee": "Brug", - "workspace.referral.apply.noGo": "Abonner på Go", - "workspace.referral.apply.preview": "Forhåndsvis", + "workspace.referral.reward.status.applied": "Belønning brugt", + "workspace.referral.reward.status.pendingInviter": "Abonner for at låse op", + "workspace.referral.reward.status.pendingInvitee": "Abonner for at låse op", + "workspace.referral.apply.noGo": "Abonner for at låse op", + "workspace.referral.apply.preview": "Vis belønning", "workspace.referral.apply.action": "Brug", - "workspace.referral.apply.confirmTitle": "Brug Go-belønning", - "workspace.referral.apply.confirmBody": - "Brug {{amount}} til at reducere dette workspaces nuværende Go-forbrugstællere.", + "workspace.referral.apply.confirmTitle": "Brug belønning", + "workspace.referral.apply.confirmBody": "Brug {{amount}} til at reducere dette workspaces nuværende forbrug.", "workspace.referral.apply.confirmAction": "Brug", "download.title": "OpenCode | Download", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 9debb9f95956..56a66249f2c1 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -671,33 +671,36 @@ export const dict = { "workspace.referral.copyLink": "Link kopieren", "workspace.referral.copied": "Kopiert", - "workspace.referral.overview.title": "Freunde zu Go einladen", - "workspace.referral.overview.subtitle": "Erhalte $5 Go-Guthaben, wenn ein Freund abonniert. Er bekommt ebenfalls $5.", - "workspace.referral.stats.invites": "Einladungen", - "workspace.referral.stats.earned": "Verdient", - "workspace.referral.stats.applied": "Eingelöst", - "workspace.referral.instructions.share": "Teile deinen Einladungslink", - "workspace.referral.instructions.subscribe": "Dein Freund abonniert Go", - "workspace.referral.instructions.claim": "Löse unten dein $5-Guthaben ein", + "workspace.referral.overview.title": "Freunde einladen", + "workspace.referral.overview.subtitle": "Erhalte $5, wenn ein Freund abonniert. Er bekommt ebenfalls $5.", + "workspace.referral.instructions.share": "Teile deinen Empfehlungslink", + "workspace.referral.instructions.subscribe": "Dein Freund tritt bei und abonniert Go", + "workspace.referral.instructions.claim": "Ihr erhaltet beide ein Nutzungsguthaben von $5, das ihr auf eure Go-Nutzungslimits anrechnen könnt", "workspace.referral.rewards.title": "Empfehlungsbelohnungen", + "workspace.referral.rewards.description": "Verfügbare Empfehlungsguthaben auf deine Go-Nutzung anwenden.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} Belohnungen eingelöst.", "workspace.referral.rewards.empty": "Noch keine Empfehlungsbelohnungen.", "workspace.referral.table.reward": "Belohnung", "workspace.referral.table.referral": "Beschreibung", "workspace.referral.table.date": "Datum", + "workspace.referral.reward.description.inviter": "{{email}} eingeladen", + "workspace.referral.reward.description.invitee": "Eingeladen von {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Abonnieren zum Freischalten", + "workspace.referral.reward.action.view": "Belohnung ansehen", + "workspace.referral.reward.action.applied": "Belohnung eingelöst", "workspace.referral.reward.source.pendingInviter": "Warten auf das Abo des Freundes", "workspace.referral.reward.source.pendingInvitee": "Abonnieren, um Belohnung freizuschalten", "workspace.referral.reward.source.available": "Belohnung kann eingelöst werden", "workspace.referral.reward.source.applied": "Belohnung eingelöst", - "workspace.referral.reward.status.applied": "Eingelöst", - "workspace.referral.reward.status.pendingInviter": "Einlösen", - "workspace.referral.reward.status.pendingInvitee": "Einlösen", - "workspace.referral.apply.noGo": "Go abonnieren", - "workspace.referral.apply.preview": "Vorschau", + "workspace.referral.reward.status.applied": "Belohnung eingelöst", + "workspace.referral.reward.status.pendingInviter": "Abonnieren zum Freischalten", + "workspace.referral.reward.status.pendingInvitee": "Abonnieren zum Freischalten", + "workspace.referral.apply.noGo": "Abonnieren zum Freischalten", + "workspace.referral.apply.preview": "Belohnung ansehen", "workspace.referral.apply.action": "Einlösen", - "workspace.referral.apply.confirmTitle": "Go-Belohnung einlösen", + "workspace.referral.apply.confirmTitle": "Belohnung einlösen", "workspace.referral.apply.confirmBody": - "Löse {{amount}} ein, um die aktuellen Go-Nutzungszähler dieses Workspace zu reduzieren.", + "Löse {{amount}} ein, um die aktuelle Nutzung dieses Workspace zu reduzieren.", "workspace.referral.apply.confirmAction": "Einlösen", "download.title": "OpenCode | Download", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index d8a81848a4ce..d98e392118fd 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -664,32 +664,35 @@ export const dict = { "workspace.referral.copyLink": "Copy Link", "workspace.referral.copied": "Copied", - "workspace.referral.overview.title": "Invite friends to Go", - "workspace.referral.overview.subtitle": "Earn $5 in Go credit when a friend subscribes. They’ll get $5 too.", - "workspace.referral.stats.invites": "Invites", - "workspace.referral.stats.earned": "Earned", - "workspace.referral.stats.applied": "Applied", - "workspace.referral.instructions.share": "Share your invite link", - "workspace.referral.instructions.subscribe": "Your friend subscribes to Go", - "workspace.referral.instructions.claim": "Apply your $5 credit below", + "workspace.referral.overview.title": "Invite friends", + "workspace.referral.overview.subtitle": "Earn $5 when a friend subscribes. They’ll get $5 too.", + "workspace.referral.instructions.share": "Share your referral link", + "workspace.referral.instructions.subscribe": "Your friend joins and subscribes to Go", + "workspace.referral.instructions.claim": "You both get a $5 usage credit to apply toward your Go usage limits", "workspace.referral.rewards.title": "Referral rewards", + "workspace.referral.rewards.description": "Apply available referral credits toward your Go usage.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} rewards applied.", "workspace.referral.rewards.empty": "No referral rewards yet.", "workspace.referral.table.reward": "Reward", "workspace.referral.table.referral": "Description", "workspace.referral.table.date": "Date", + "workspace.referral.reward.description.inviter": "Invited {{email}}", + "workspace.referral.reward.description.invitee": "Invited by {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Subscribe to unlock", + "workspace.referral.reward.action.view": "View Reward", + "workspace.referral.reward.action.applied": "Reward Applied", "workspace.referral.reward.source.pendingInviter": "Waiting for them to subscribe", "workspace.referral.reward.source.pendingInvitee": "Subscribe to unlock reward", "workspace.referral.reward.source.available": "Reward ready to apply", "workspace.referral.reward.source.applied": "Reward applied", - "workspace.referral.reward.status.applied": "Applied", - "workspace.referral.reward.status.pendingInviter": "Apply", - "workspace.referral.reward.status.pendingInvitee": "Apply", - "workspace.referral.apply.noGo": "Subscribe to Go", - "workspace.referral.apply.preview": "Preview", + "workspace.referral.reward.status.applied": "Reward Applied", + "workspace.referral.reward.status.pendingInviter": "Subscribe to unlock", + "workspace.referral.reward.status.pendingInvitee": "Subscribe to unlock", + "workspace.referral.apply.noGo": "Subscribe to unlock", + "workspace.referral.apply.preview": "View Reward", "workspace.referral.apply.action": "Apply", - "workspace.referral.apply.confirmTitle": "Apply Go reward", - "workspace.referral.apply.confirmBody": "Apply {{amount}} to reduce this workspace's current Go usage counters.", + "workspace.referral.apply.confirmTitle": "Apply reward", + "workspace.referral.apply.confirmBody": "Apply {{amount}} to reduce this workspace's current usage.", "workspace.referral.apply.confirmAction": "Apply", "download.title": "OpenCode | Download", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 9b00cc644300..78d228b6969a 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -672,33 +672,35 @@ export const dict = { "workspace.referral.copyLink": "Copiar enlace", "workspace.referral.copied": "Copiado", - "workspace.referral.overview.title": "Invita amigos a Go", - "workspace.referral.overview.subtitle": "Gana $5 de crédito Go cuando un amigo se suscriba. Él también recibirá $5.", - "workspace.referral.stats.invites": "Invitaciones", - "workspace.referral.stats.earned": "Ganado", - "workspace.referral.stats.applied": "Aplicado", - "workspace.referral.instructions.share": "Comparte tu enlace de invitación", - "workspace.referral.instructions.subscribe": "Tu amigo se suscribe a Go", - "workspace.referral.instructions.claim": "Aplica tu crédito de $5 abajo", + "workspace.referral.overview.title": "Invita amigos", + "workspace.referral.overview.subtitle": "Gana $5 cuando un amigo se suscriba. Él también recibirá $5.", + "workspace.referral.instructions.share": "Comparte tu enlace de referido", + "workspace.referral.instructions.subscribe": "Tu amigo se une y se suscribe a Go", + "workspace.referral.instructions.claim": "Ambos reciben un crédito de uso de $5 para aplicar a sus límites de uso de Go", "workspace.referral.rewards.title": "Recompensas por referidos", + "workspace.referral.rewards.description": "Aplica los créditos por referidos disponibles a tu uso de Go.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} recompensas aplicadas.", "workspace.referral.rewards.empty": "Aún no hay recompensas por referidos.", "workspace.referral.table.reward": "Recompensa", "workspace.referral.table.referral": "Descripción", "workspace.referral.table.date": "Fecha", + "workspace.referral.reward.description.inviter": "Invitaste a {{email}}", + "workspace.referral.reward.description.invitee": "Invitado por {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Suscríbete para desbloquear", + "workspace.referral.reward.action.view": "Ver recompensa", + "workspace.referral.reward.action.applied": "Recompensa aplicada", "workspace.referral.reward.source.pendingInviter": "Esperando a que se suscriba", "workspace.referral.reward.source.pendingInvitee": "Suscríbete para desbloquear la recompensa", "workspace.referral.reward.source.available": "Recompensa lista para aplicar", "workspace.referral.reward.source.applied": "Recompensa aplicada", - "workspace.referral.reward.status.applied": "Aplicada", - "workspace.referral.reward.status.pendingInviter": "Aplicar", - "workspace.referral.reward.status.pendingInvitee": "Aplicar", - "workspace.referral.apply.noGo": "Suscribirse a Go", - "workspace.referral.apply.preview": "Vista previa", + "workspace.referral.reward.status.applied": "Recompensa aplicada", + "workspace.referral.reward.status.pendingInviter": "Suscríbete para desbloquear", + "workspace.referral.reward.status.pendingInvitee": "Suscríbete para desbloquear", + "workspace.referral.apply.noGo": "Suscríbete para desbloquear", + "workspace.referral.apply.preview": "Ver recompensa", "workspace.referral.apply.action": "Aplicar", - "workspace.referral.apply.confirmTitle": "Aplicar recompensa de Go", - "workspace.referral.apply.confirmBody": - "Aplica {{amount}} para reducir los contadores actuales de uso de Go de este workspace.", + "workspace.referral.apply.confirmTitle": "Aplicar recompensa", + "workspace.referral.apply.confirmBody": "Aplica {{amount}} para reducir el uso actual de este workspace.", "workspace.referral.apply.confirmAction": "Aplicar", "download.title": "OpenCode | Descargar", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 0bd79399b325..03dc2b93a7f7 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -678,33 +678,35 @@ export const dict = { "workspace.referral.copyLink": "Copier le lien", "workspace.referral.copied": "Copié", - "workspace.referral.overview.title": "Inviter des amis sur Go", - "workspace.referral.overview.subtitle": "Gagnez $5 de crédit Go lorsqu'un ami s'abonne. Il recevra également $5.", - "workspace.referral.stats.invites": "Invitations", - "workspace.referral.stats.earned": "Gagné", - "workspace.referral.stats.applied": "Utilisé", - "workspace.referral.instructions.share": "Partagez votre lien d'invitation", - "workspace.referral.instructions.subscribe": "Votre ami s'abonne à Go", - "workspace.referral.instructions.claim": "Utilisez votre crédit de $5 ci-dessous", + "workspace.referral.overview.title": "Inviter des amis", + "workspace.referral.overview.subtitle": "Gagnez $5 lorsqu'un ami s'abonne. Il recevra également $5.", + "workspace.referral.instructions.share": "Partagez votre lien de parrainage", + "workspace.referral.instructions.subscribe": "Votre ami rejoint et s'abonne à Go", + "workspace.referral.instructions.claim": "Vous recevez tous les deux un crédit d'utilisation de $5 à appliquer à vos limites d'utilisation Go", "workspace.referral.rewards.title": "Récompenses de parrainage", + "workspace.referral.rewards.description": "Utilisez les crédits de parrainage disponibles pour votre utilisation de Go.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} récompenses utilisées.", "workspace.referral.rewards.empty": "Aucune récompense de parrainage pour l'instant.", "workspace.referral.table.reward": "Récompense", "workspace.referral.table.referral": "Description", "workspace.referral.table.date": "Date", + "workspace.referral.reward.description.inviter": "Vous avez invité {{email}}", + "workspace.referral.reward.description.invitee": "Invité par {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Abonnez-vous pour débloquer", + "workspace.referral.reward.action.view": "Voir la récompense", + "workspace.referral.reward.action.applied": "Récompense utilisée", "workspace.referral.reward.source.pendingInviter": "En attente de son abonnement", "workspace.referral.reward.source.pendingInvitee": "Abonnez-vous pour débloquer la récompense", "workspace.referral.reward.source.available": "Récompense prête à utiliser", "workspace.referral.reward.source.applied": "Récompense utilisée", - "workspace.referral.reward.status.applied": "Utilisée", - "workspace.referral.reward.status.pendingInviter": "Utiliser", - "workspace.referral.reward.status.pendingInvitee": "Utiliser", - "workspace.referral.apply.noGo": "S'abonner à Go", - "workspace.referral.apply.preview": "Aperçu", + "workspace.referral.reward.status.applied": "Récompense utilisée", + "workspace.referral.reward.status.pendingInviter": "Abonnez-vous pour débloquer", + "workspace.referral.reward.status.pendingInvitee": "Abonnez-vous pour débloquer", + "workspace.referral.apply.noGo": "Abonnez-vous pour débloquer", + "workspace.referral.apply.preview": "Voir la récompense", "workspace.referral.apply.action": "Utiliser", - "workspace.referral.apply.confirmTitle": "Utiliser la récompense Go", - "workspace.referral.apply.confirmBody": - "Utilisez {{amount}} pour réduire les compteurs d'utilisation Go actuels de ce workspace.", + "workspace.referral.apply.confirmTitle": "Utiliser la récompense", + "workspace.referral.apply.confirmBody": "Utilisez {{amount}} pour réduire l'utilisation actuelle de ce workspace.", "workspace.referral.apply.confirmAction": "Utiliser", "download.title": "OpenCode | Téléchargement", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index ab62763843c7..0b6c6929175a 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -670,33 +670,35 @@ export const dict = { "workspace.referral.copyLink": "Copia link", "workspace.referral.copied": "Copiato", - "workspace.referral.overview.title": "Invita amici su Go", - "workspace.referral.overview.subtitle": "Guadagna $5 di credito Go quando un amico si abbona. Anche lui riceverà $5.", - "workspace.referral.stats.invites": "Inviti", - "workspace.referral.stats.earned": "Guadagnato", - "workspace.referral.stats.applied": "Utilizzato", - "workspace.referral.instructions.share": "Condividi il tuo link di invito", - "workspace.referral.instructions.subscribe": "Il tuo amico si abbona a Go", - "workspace.referral.instructions.claim": "Usa il tuo credito di $5 qui sotto", + "workspace.referral.overview.title": "Invita amici", + "workspace.referral.overview.subtitle": "Guadagna $5 quando un amico si abbona. Anche lui riceverà $5.", + "workspace.referral.instructions.share": "Condividi il tuo link di referral", + "workspace.referral.instructions.subscribe": "Il tuo amico si iscrive e si abbona a Go", + "workspace.referral.instructions.claim": "Entrambi ricevete un credito di utilizzo di $5 da applicare ai vostri limiti di utilizzo Go", "workspace.referral.rewards.title": "Premi referral", + "workspace.referral.rewards.description": "Applica i crediti referral disponibili al tuo utilizzo di Go.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} premi utilizzati.", "workspace.referral.rewards.empty": "Nessun premio referral ancora.", "workspace.referral.table.reward": "Premio", "workspace.referral.table.referral": "Descrizione", "workspace.referral.table.date": "Data", + "workspace.referral.reward.description.inviter": "Hai invitato {{email}}", + "workspace.referral.reward.description.invitee": "Invitato da {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Abbonati per sbloccare", + "workspace.referral.reward.action.view": "Vedi premio", + "workspace.referral.reward.action.applied": "Premio utilizzato", "workspace.referral.reward.source.pendingInviter": "In attesa che si abboni", "workspace.referral.reward.source.pendingInvitee": "Abbonati per sbloccare il premio", "workspace.referral.reward.source.available": "Premio pronto da utilizzare", "workspace.referral.reward.source.applied": "Premio utilizzato", - "workspace.referral.reward.status.applied": "Utilizzato", - "workspace.referral.reward.status.pendingInviter": "Utilizza", - "workspace.referral.reward.status.pendingInvitee": "Utilizza", - "workspace.referral.apply.noGo": "Abbonati a Go", - "workspace.referral.apply.preview": "Anteprima", + "workspace.referral.reward.status.applied": "Premio utilizzato", + "workspace.referral.reward.status.pendingInviter": "Abbonati per sbloccare", + "workspace.referral.reward.status.pendingInvitee": "Abbonati per sbloccare", + "workspace.referral.apply.noGo": "Abbonati per sbloccare", + "workspace.referral.apply.preview": "Vedi premio", "workspace.referral.apply.action": "Utilizza", - "workspace.referral.apply.confirmTitle": "Utilizza premio Go", - "workspace.referral.apply.confirmBody": - "Utilizza {{amount}} per ridurre i contatori di utilizzo Go attuali di questo workspace.", + "workspace.referral.apply.confirmTitle": "Utilizza premio", + "workspace.referral.apply.confirmBody": "Utilizza {{amount}} per ridurre l'utilizzo attuale di questo workspace.", "workspace.referral.apply.confirmAction": "Utilizza", "download.title": "OpenCode | Download", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index a6c4158c6480..18cf077a2d2e 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -670,34 +670,35 @@ export const dict = { "workspace.referral.copyLink": "リンクをコピー", "workspace.referral.copied": "コピーしました", - "workspace.referral.overview.title": "友達を Go に招待", - "workspace.referral.overview.subtitle": - "友達がサブスクライブすると $5 分の Go クレジットを獲得。友達にも $5 が付与されます。", - "workspace.referral.stats.invites": "招待", - "workspace.referral.stats.earned": "獲得", - "workspace.referral.stats.applied": "適用済み", - "workspace.referral.instructions.share": "招待リンクをシェア", - "workspace.referral.instructions.subscribe": "友達が Go にサブスクライブ", - "workspace.referral.instructions.claim": "下の $5 クレジットを適用", + "workspace.referral.overview.title": "友達を招待", + "workspace.referral.overview.subtitle": "友達がサブスクライブすると $5 を獲得。友達にも $5 が付与されます。", + "workspace.referral.instructions.share": "リファラルリンクをシェア", + "workspace.referral.instructions.subscribe": "友達が参加して Go にサブスクライブ", + "workspace.referral.instructions.claim": "二人とも $5 の利用クレジットを獲得し、Go の利用上限に充当できます", "workspace.referral.rewards.title": "リファラル特典", + "workspace.referral.rewards.description": "利用可能なリファラルクレジットを Go の利用に適用します。", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} 件の特典を適用済み。", "workspace.referral.rewards.empty": "リファラル特典はまだありません。", "workspace.referral.table.reward": "特典", "workspace.referral.table.referral": "説明", "workspace.referral.table.date": "日付", + "workspace.referral.reward.description.inviter": "{{email}} を招待しました", + "workspace.referral.reward.description.invitee": "{{email}} に招待されました", + "workspace.referral.reward.action.subscribeUnlock": "サブスクライブしてアンロック", + "workspace.referral.reward.action.view": "特典を表示", + "workspace.referral.reward.action.applied": "特典を適用済み", "workspace.referral.reward.source.pendingInviter": "友達のサブスクライブ待ち", "workspace.referral.reward.source.pendingInvitee": "サブスクライブして特典をアンロック", "workspace.referral.reward.source.available": "特典は適用可能です", "workspace.referral.reward.source.applied": "特典を適用済み", - "workspace.referral.reward.status.applied": "適用済み", - "workspace.referral.reward.status.pendingInviter": "適用", - "workspace.referral.reward.status.pendingInvitee": "適用", - "workspace.referral.apply.noGo": "Goを購読する", - "workspace.referral.apply.preview": "プレビュー", + "workspace.referral.reward.status.applied": "特典を適用済み", + "workspace.referral.reward.status.pendingInviter": "サブスクライブしてアンロック", + "workspace.referral.reward.status.pendingInvitee": "サブスクライブしてアンロック", + "workspace.referral.apply.noGo": "サブスクライブしてアンロック", + "workspace.referral.apply.preview": "特典を表示", "workspace.referral.apply.action": "適用", - "workspace.referral.apply.confirmTitle": "Go 特典を適用", - "workspace.referral.apply.confirmBody": - "{{amount}} を適用して、このワークスペースの現在の Go 使用カウンターを減らします。", + "workspace.referral.apply.confirmTitle": "特典を適用", + "workspace.referral.apply.confirmBody": "{{amount}} を適用して、このワークスペースの現在の使用量を減らします。", "workspace.referral.apply.confirmAction": "適用", "download.title": "OpenCode | ダウンロード", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index cf7f0e7898cc..3572d605cf99 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -662,32 +662,35 @@ export const dict = { "workspace.referral.copyLink": "링크 복사", "workspace.referral.copied": "복사됨", - "workspace.referral.overview.title": "친구를 Go에 초대", - "workspace.referral.overview.subtitle": "친구가 구독하면 $5의 Go 크레딧을 받으세요. 친구도 $5를 받습니다.", - "workspace.referral.stats.invites": "초대", - "workspace.referral.stats.earned": "획득", - "workspace.referral.stats.applied": "사용", - "workspace.referral.instructions.share": "초대 링크 공유", - "workspace.referral.instructions.subscribe": "친구가 Go를 구독", - "workspace.referral.instructions.claim": "아래에서 $5 크레딧 사용", + "workspace.referral.overview.title": "친구 초대", + "workspace.referral.overview.subtitle": "친구가 구독하면 $5를 받으세요. 친구도 $5를 받습니다.", + "workspace.referral.instructions.share": "추천 링크 공유", + "workspace.referral.instructions.subscribe": "친구가 가입하고 Go를 구독", + "workspace.referral.instructions.claim": "두 분 모두 $5 사용 크레딧을 받아 Go 사용 한도에 적용할 수 있습니다", "workspace.referral.rewards.title": "추천 보상", + "workspace.referral.rewards.description": "사용 가능한 추천 크레딧을 Go 사용량에 적용합니다.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}}개 보상 사용됨.", "workspace.referral.rewards.empty": "아직 추천 보상이 없습니다.", "workspace.referral.table.reward": "보상", "workspace.referral.table.referral": "설명", "workspace.referral.table.date": "날짜", + "workspace.referral.reward.description.inviter": "{{email}} 초대됨", + "workspace.referral.reward.description.invitee": "{{email}}님이 초대", + "workspace.referral.reward.action.subscribeUnlock": "구독하여 잠금 해제", + "workspace.referral.reward.action.view": "보상 보기", + "workspace.referral.reward.action.applied": "보상 사용됨", "workspace.referral.reward.source.pendingInviter": "친구의 구독을 기다리는 중", "workspace.referral.reward.source.pendingInvitee": "구독하여 보상 잠금 해제", "workspace.referral.reward.source.available": "보상 사용 가능", "workspace.referral.reward.source.applied": "보상 사용됨", - "workspace.referral.reward.status.applied": "사용됨", - "workspace.referral.reward.status.pendingInviter": "사용", - "workspace.referral.reward.status.pendingInvitee": "사용", - "workspace.referral.apply.noGo": "Go 구독하기", - "workspace.referral.apply.preview": "미리 보기", + "workspace.referral.reward.status.applied": "보상 사용됨", + "workspace.referral.reward.status.pendingInviter": "구독하여 잠금 해제", + "workspace.referral.reward.status.pendingInvitee": "구독하여 잠금 해제", + "workspace.referral.apply.noGo": "구독하여 잠금 해제", + "workspace.referral.apply.preview": "보상 보기", "workspace.referral.apply.action": "사용", - "workspace.referral.apply.confirmTitle": "Go 보상 사용", - "workspace.referral.apply.confirmBody": "{{amount}}를 사용하여 이 워크스페이스의 현재 Go 사용 카운터를 줄입니다.", + "workspace.referral.apply.confirmTitle": "보상 사용", + "workspace.referral.apply.confirmBody": "{{amount}}를 사용하여 이 워크스페이스의 현재 사용량을 줄입니다.", "workspace.referral.apply.confirmAction": "사용", "download.title": "OpenCode | 다운로드", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 019db79ba039..ea25f98eac97 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -669,33 +669,35 @@ export const dict = { "workspace.referral.copyLink": "Kopier lenke", "workspace.referral.copied": "Kopiert", - "workspace.referral.overview.title": "Inviter venner til Go", - "workspace.referral.overview.subtitle": "Få $5 i Go-kreditt når en venn abonnerer. De får også $5.", - "workspace.referral.stats.invites": "Invitasjoner", - "workspace.referral.stats.earned": "Opptjent", - "workspace.referral.stats.applied": "Brukt", - "workspace.referral.instructions.share": "Del invitasjonslenken din", - "workspace.referral.instructions.subscribe": "Vennen din abonnerer på Go", - "workspace.referral.instructions.claim": "Bruk $5-kreditten din nedenfor", + "workspace.referral.overview.title": "Inviter venner", + "workspace.referral.overview.subtitle": "Få $5 når en venn abonnerer. De får også $5.", + "workspace.referral.instructions.share": "Del henvisningslenken din", + "workspace.referral.instructions.subscribe": "Vennen din blir med og abonnerer på Go", + "workspace.referral.instructions.claim": "Dere får begge $5 i brukskreditt å bruke på Go-bruksgrensene deres", "workspace.referral.rewards.title": "Henvisningsbelønninger", + "workspace.referral.rewards.description": "Bruk tilgjengelige henvisningskreditter på Go-bruken din.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} belønninger brukt.", "workspace.referral.rewards.empty": "Ingen henvisningsbelønninger ennå.", "workspace.referral.table.reward": "Belønning", "workspace.referral.table.referral": "Beskrivelse", "workspace.referral.table.date": "Dato", + "workspace.referral.reward.description.inviter": "Inviterte {{email}}", + "workspace.referral.reward.description.invitee": "Invitert av {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Abonner for å låse opp", + "workspace.referral.reward.action.view": "Vis belønning", + "workspace.referral.reward.action.applied": "Belønning brukt", "workspace.referral.reward.source.pendingInviter": "Venter på at de abonnerer", "workspace.referral.reward.source.pendingInvitee": "Abonner for å låse opp belønningen", "workspace.referral.reward.source.available": "Belønning klar til bruk", "workspace.referral.reward.source.applied": "Belønning brukt", - "workspace.referral.reward.status.applied": "Brukt", - "workspace.referral.reward.status.pendingInviter": "Bruk", - "workspace.referral.reward.status.pendingInvitee": "Bruk", - "workspace.referral.apply.noGo": "Abonner på Go", - "workspace.referral.apply.preview": "Forhåndsvis", + "workspace.referral.reward.status.applied": "Belønning brukt", + "workspace.referral.reward.status.pendingInviter": "Abonner for å låse opp", + "workspace.referral.reward.status.pendingInvitee": "Abonner for å låse opp", + "workspace.referral.apply.noGo": "Abonner for å låse opp", + "workspace.referral.apply.preview": "Vis belønning", "workspace.referral.apply.action": "Bruk", - "workspace.referral.apply.confirmTitle": "Bruk Go-belønning", - "workspace.referral.apply.confirmBody": - "Bruk {{amount}} for å redusere dette workspacets nåværende Go-forbrukstellere.", + "workspace.referral.apply.confirmTitle": "Bruk belønning", + "workspace.referral.apply.confirmBody": "Bruk {{amount}} for å redusere dette workspacets nåværende forbruk.", "workspace.referral.apply.confirmAction": "Bruk", "download.title": "OpenCode | Last ned", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 5f123158dd9d..4a222b13b135 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -670,33 +670,35 @@ export const dict = { "workspace.referral.copyLink": "Kopiuj link", "workspace.referral.copied": "Skopiowano", - "workspace.referral.overview.title": "Zaproś znajomych do Go", - "workspace.referral.overview.subtitle": "Zdobądź $5 kredytu Go, gdy znajomy się zasubskrybuje. On też dostanie $5.", - "workspace.referral.stats.invites": "Zaproszenia", - "workspace.referral.stats.earned": "Zdobyte", - "workspace.referral.stats.applied": "Wykorzystane", - "workspace.referral.instructions.share": "Udostępnij swój link z zaproszeniem", - "workspace.referral.instructions.subscribe": "Twój znajomy subskrybuje Go", - "workspace.referral.instructions.claim": "Wykorzystaj swój kredyt $5 poniżej", + "workspace.referral.overview.title": "Zaproś znajomych", + "workspace.referral.overview.subtitle": "Zdobądź $5, gdy znajomy się zasubskrybuje. On też dostanie $5.", + "workspace.referral.instructions.share": "Udostępnij swój link polecający", + "workspace.referral.instructions.subscribe": "Twój znajomy dołącza i subskrybuje Go", + "workspace.referral.instructions.claim": "Oboje otrzymujecie kredyt $5 do wykorzystania na limity użycia Go", "workspace.referral.rewards.title": "Nagrody za polecenia", + "workspace.referral.rewards.description": "Wykorzystaj dostępne środki za polecenia na swoje użycie Go.", "workspace.referral.rewards.subtitle": "Wykorzystano {{applied}} / {{total}} nagród.", "workspace.referral.rewards.empty": "Brak nagród za polecenia.", "workspace.referral.table.reward": "Nagroda", "workspace.referral.table.referral": "Opis", "workspace.referral.table.date": "Data", + "workspace.referral.reward.description.inviter": "Zaproszono {{email}}", + "workspace.referral.reward.description.invitee": "Zaproszony przez {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Subskrybuj, aby odblokować", + "workspace.referral.reward.action.view": "Zobacz nagrodę", + "workspace.referral.reward.action.applied": "Nagroda wykorzystana", "workspace.referral.reward.source.pendingInviter": "Oczekiwanie na jego subskrypcję", "workspace.referral.reward.source.pendingInvitee": "Subskrybuj, aby odblokować nagrodę", "workspace.referral.reward.source.available": "Nagroda gotowa do wykorzystania", "workspace.referral.reward.source.applied": "Nagroda wykorzystana", - "workspace.referral.reward.status.applied": "Wykorzystana", - "workspace.referral.reward.status.pendingInviter": "Wykorzystaj", - "workspace.referral.reward.status.pendingInvitee": "Wykorzystaj", - "workspace.referral.apply.noGo": "Subskrybuj Go", - "workspace.referral.apply.preview": "Podgląd", + "workspace.referral.reward.status.applied": "Nagroda wykorzystana", + "workspace.referral.reward.status.pendingInviter": "Subskrybuj, aby odblokować", + "workspace.referral.reward.status.pendingInvitee": "Subskrybuj, aby odblokować", + "workspace.referral.apply.noGo": "Subskrybuj, aby odblokować", + "workspace.referral.apply.preview": "Zobacz nagrodę", "workspace.referral.apply.action": "Wykorzystaj", - "workspace.referral.apply.confirmTitle": "Wykorzystaj nagrodę Go", - "workspace.referral.apply.confirmBody": - "Wykorzystaj {{amount}}, aby zmniejszyć aktualne liczniki użycia Go w tym workspace.", + "workspace.referral.apply.confirmTitle": "Wykorzystaj nagrodę", + "workspace.referral.apply.confirmBody": "Wykorzystaj {{amount}}, aby zmniejszyć aktualne użycie w tym workspace.", "workspace.referral.apply.confirmAction": "Wykorzystaj", "download.title": "OpenCode | Pobierz", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 40e75727f9a9..7b838f46c60e 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -676,33 +676,35 @@ export const dict = { "workspace.referral.copyLink": "Копировать ссылку", "workspace.referral.copied": "Скопировано", - "workspace.referral.overview.title": "Пригласите друзей в Go", - "workspace.referral.overview.subtitle": "Получите $5 кредита Go, когда друг оформит подписку. Он тоже получит $5.", - "workspace.referral.stats.invites": "Приглашения", - "workspace.referral.stats.earned": "Заработано", - "workspace.referral.stats.applied": "Использовано", - "workspace.referral.instructions.share": "Поделитесь своей ссылкой-приглашением", - "workspace.referral.instructions.subscribe": "Ваш друг оформляет подписку на Go", - "workspace.referral.instructions.claim": "Используйте свой кредит $5 ниже", + "workspace.referral.overview.title": "Пригласите друзей", + "workspace.referral.overview.subtitle": "Получите $5, когда друг оформит подписку. Он тоже получит $5.", + "workspace.referral.instructions.share": "Поделитесь своей реферальной ссылкой", + "workspace.referral.instructions.subscribe": "Ваш друг присоединяется и оформляет подписку на Go", + "workspace.referral.instructions.claim": "Вы оба получаете кредит на использование $5, который можно применить к лимитам использования Go", "workspace.referral.rewards.title": "Реферальные награды", + "workspace.referral.rewards.description": "Используйте доступные реферальные кредиты для оплаты использования Go.", "workspace.referral.rewards.subtitle": "Использовано {{applied}} / {{total}} наград.", "workspace.referral.rewards.empty": "Реферальных наград пока нет.", "workspace.referral.table.reward": "Награда", "workspace.referral.table.referral": "Описание", "workspace.referral.table.date": "Дата", + "workspace.referral.reward.description.inviter": "Приглашён {{email}}", + "workspace.referral.reward.description.invitee": "Приглашены пользователем {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "Оформите подписку для разблокировки", + "workspace.referral.reward.action.view": "Посмотреть награду", + "workspace.referral.reward.action.applied": "Награда использована", "workspace.referral.reward.source.pendingInviter": "Ожидание его подписки", "workspace.referral.reward.source.pendingInvitee": "Подпишитесь, чтобы разблокировать награду", "workspace.referral.reward.source.available": "Награда готова к применению", "workspace.referral.reward.source.applied": "Награда использована", - "workspace.referral.reward.status.applied": "Использована", - "workspace.referral.reward.status.pendingInviter": "Применить", - "workspace.referral.reward.status.pendingInvitee": "Применить", - "workspace.referral.apply.noGo": "Подписаться на Go", - "workspace.referral.apply.preview": "Предпросмотр", + "workspace.referral.reward.status.applied": "Награда использована", + "workspace.referral.reward.status.pendingInviter": "Оформите подписку для разблокировки", + "workspace.referral.reward.status.pendingInvitee": "Оформите подписку для разблокировки", + "workspace.referral.apply.noGo": "Оформите подписку для разблокировки", + "workspace.referral.apply.preview": "Посмотреть награду", "workspace.referral.apply.action": "Применить", - "workspace.referral.apply.confirmTitle": "Применить награду Go", - "workspace.referral.apply.confirmBody": - "Используйте {{amount}}, чтобы уменьшить текущие счётчики использования Go этого workspace.", + "workspace.referral.apply.confirmTitle": "Применить награду", + "workspace.referral.apply.confirmBody": "Используйте {{amount}}, чтобы уменьшить текущее использование этого workspace.", "workspace.referral.apply.confirmAction": "Применить", "download.title": "OpenCode | Скачать", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index 4f06b931d8ac..bae5c860d568 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -665,32 +665,35 @@ export const dict = { "workspace.referral.copyLink": "คัดลอกลิงก์", "workspace.referral.copied": "คัดลอกแล้ว", - "workspace.referral.overview.title": "ชวนเพื่อนมาใช้ Go", - "workspace.referral.overview.subtitle": "รับเครดิต Go $5 เมื่อเพื่อนสมัครสมาชิก เพื่อนก็จะได้รับ $5 เช่นกัน", - "workspace.referral.stats.invites": "คำเชิญ", - "workspace.referral.stats.earned": "ได้รับ", - "workspace.referral.stats.applied": "ใช้แล้ว", - "workspace.referral.instructions.share": "แชร์ลิงก์เชิญของคุณ", - "workspace.referral.instructions.subscribe": "เพื่อนของคุณสมัครสมาชิก Go", - "workspace.referral.instructions.claim": "ใช้เครดิต $5 ของคุณด้านล่าง", + "workspace.referral.overview.title": "ชวนเพื่อน", + "workspace.referral.overview.subtitle": "รับ $5 เมื่อเพื่อนสมัครสมาชิก เพื่อนก็จะได้รับ $5 เช่นกัน", + "workspace.referral.instructions.share": "แชร์ลิงก์แนะนำของคุณ", + "workspace.referral.instructions.subscribe": "เพื่อนของคุณเข้าร่วมและสมัครสมาชิก Go", + "workspace.referral.instructions.claim": "คุณทั้งคู่จะได้รับเครดิตการใช้งาน $5 เพื่อใช้กับขีดจำกัดการใช้งาน Go", "workspace.referral.rewards.title": "รางวัลการแนะนำ", + "workspace.referral.rewards.description": "ใช้เครดิตการแนะนำที่มีอยู่กับการใช้งาน Go ของคุณ", "workspace.referral.rewards.subtitle": "ใช้แล้ว {{applied}} / {{total}} รางวัล", "workspace.referral.rewards.empty": "ยังไม่มีรางวัลการแนะนำ", "workspace.referral.table.reward": "รางวัล", "workspace.referral.table.referral": "คำอธิบาย", "workspace.referral.table.date": "วันที่", + "workspace.referral.reward.description.inviter": "เชิญ {{email}}", + "workspace.referral.reward.description.invitee": "ได้รับเชิญจาก {{email}}", + "workspace.referral.reward.action.subscribeUnlock": "สมัครสมาชิกเพื่อปลดล็อก", + "workspace.referral.reward.action.view": "ดูรางวัล", + "workspace.referral.reward.action.applied": "ใช้รางวัลแล้ว", "workspace.referral.reward.source.pendingInviter": "รอเพื่อนสมัครสมาชิก", "workspace.referral.reward.source.pendingInvitee": "สมัครสมาชิกเพื่อปลดล็อกรางวัล", "workspace.referral.reward.source.available": "รางวัลพร้อมใช้งาน", "workspace.referral.reward.source.applied": "ใช้รางวัลแล้ว", - "workspace.referral.reward.status.applied": "ใช้แล้ว", - "workspace.referral.reward.status.pendingInviter": "ใช้", - "workspace.referral.reward.status.pendingInvitee": "ใช้", - "workspace.referral.apply.noGo": "สมัครสมาชิก Go", - "workspace.referral.apply.preview": "ดูตัวอย่าง", + "workspace.referral.reward.status.applied": "ใช้รางวัลแล้ว", + "workspace.referral.reward.status.pendingInviter": "สมัครสมาชิกเพื่อปลดล็อก", + "workspace.referral.reward.status.pendingInvitee": "สมัครสมาชิกเพื่อปลดล็อก", + "workspace.referral.apply.noGo": "สมัครสมาชิกเพื่อปลดล็อก", + "workspace.referral.apply.preview": "ดูรางวัล", "workspace.referral.apply.action": "ใช้", - "workspace.referral.apply.confirmTitle": "ใช้รางวัล Go", - "workspace.referral.apply.confirmBody": "ใช้ {{amount}} เพื่อลดตัวนับการใช้งาน Go ปัจจุบันของ workspace นี้", + "workspace.referral.apply.confirmTitle": "ใช้รางวัล", + "workspace.referral.apply.confirmBody": "ใช้ {{amount}} เพื่อลดการใช้งานปัจจุบันของ workspace นี้", "workspace.referral.apply.confirmAction": "ใช้", "download.title": "OpenCode | ดาวน์โหลด", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index c38a5b2fd638..5d85f76e020b 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -672,33 +672,35 @@ export const dict = { "workspace.referral.copyLink": "Bağlantıyı Kopyala", "workspace.referral.copied": "Kopyalandı", - "workspace.referral.overview.title": "Arkadaşlarını Go'ya davet et", - "workspace.referral.overview.subtitle": "Bir arkadaşın abone olduğunda $5 Go kredisi kazan. O da $5 alacak.", - "workspace.referral.stats.invites": "Davetler", - "workspace.referral.stats.earned": "Kazanılan", - "workspace.referral.stats.applied": "Kullanılan", - "workspace.referral.instructions.share": "Davet bağlantını paylaş", - "workspace.referral.instructions.subscribe": "Arkadaşın Go'ya abone olur", - "workspace.referral.instructions.claim": "Aşağıdaki $5 kredini kullan", + "workspace.referral.overview.title": "Arkadaşlarını davet et", + "workspace.referral.overview.subtitle": "Bir arkadaşın abone olduğunda $5 kazan. O da $5 alacak.", + "workspace.referral.instructions.share": "Referans bağlantını paylaş", + "workspace.referral.instructions.subscribe": "Arkadaşın katılır ve Go'ya abone olur", + "workspace.referral.instructions.claim": "İkiniz de Go kullanım limitlerinize uygulamak için $5 kullanım kredisi alırsınız", "workspace.referral.rewards.title": "Davet ödülleri", + "workspace.referral.rewards.description": "Mevcut davet kredilerini Go kullanımınıza uygulayın.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} ödül kullanıldı.", "workspace.referral.rewards.empty": "Henüz davet ödülü yok.", "workspace.referral.table.reward": "Ödül", "workspace.referral.table.referral": "Açıklama", "workspace.referral.table.date": "Tarih", + "workspace.referral.reward.description.inviter": "{{email}} davet edildi", + "workspace.referral.reward.description.invitee": "{{email}} tarafından davet edildi", + "workspace.referral.reward.action.subscribeUnlock": "Kilidi açmak için abone ol", + "workspace.referral.reward.action.view": "Ödülü Görüntüle", + "workspace.referral.reward.action.applied": "Ödül Kullanıldı", "workspace.referral.reward.source.pendingInviter": "Abone olması bekleniyor", "workspace.referral.reward.source.pendingInvitee": "Ödülün kilidini açmak için abone ol", "workspace.referral.reward.source.available": "Ödül kullanıma hazır", "workspace.referral.reward.source.applied": "Ödül kullanıldı", - "workspace.referral.reward.status.applied": "Kullanıldı", - "workspace.referral.reward.status.pendingInviter": "Kullan", - "workspace.referral.reward.status.pendingInvitee": "Kullan", - "workspace.referral.apply.noGo": "Go'ya Abone Ol", - "workspace.referral.apply.preview": "Önizleme", + "workspace.referral.reward.status.applied": "Ödül Kullanıldı", + "workspace.referral.reward.status.pendingInviter": "Kilidi açmak için abone ol", + "workspace.referral.reward.status.pendingInvitee": "Kilidi açmak için abone ol", + "workspace.referral.apply.noGo": "Kilidi açmak için abone ol", + "workspace.referral.apply.preview": "Ödülü Görüntüle", "workspace.referral.apply.action": "Kullan", - "workspace.referral.apply.confirmTitle": "Go ödülünü kullan", - "workspace.referral.apply.confirmBody": - "Bu workspace'in mevcut Go kullanım sayaçlarını azaltmak için {{amount}} kullan.", + "workspace.referral.apply.confirmTitle": "Ödülü kullan", + "workspace.referral.apply.confirmBody": "Bu workspace'in mevcut kullanımını azaltmak için {{amount}} kullan.", "workspace.referral.apply.confirmAction": "Kullan", "download.title": "OpenCode | İndir", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 2d8d22618a29..ec134ccae16b 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -645,32 +645,35 @@ export const dict = { "workspace.referral.copyLink": "复制链接", "workspace.referral.copied": "已复制", - "workspace.referral.overview.title": "邀请好友使用 Go", - "workspace.referral.overview.subtitle": "好友订阅后,您可获得 $5 Go 抵用金,对方也可获得 $5。", - "workspace.referral.stats.invites": "邀请", - "workspace.referral.stats.earned": "已获得", - "workspace.referral.stats.applied": "已使用", - "workspace.referral.instructions.share": "分享您的邀请链接。", - "workspace.referral.instructions.subscribe": "好友订阅 Go。", - "workspace.referral.instructions.claim": "在下方使用您的 $5 抵用金。", + "workspace.referral.overview.title": "邀请好友", + "workspace.referral.overview.subtitle": "好友订阅后,您可获得 $5,对方也可获得 $5。", + "workspace.referral.instructions.share": "分享您的推荐链接。", + "workspace.referral.instructions.subscribe": "好友加入并订阅 Go。", + "workspace.referral.instructions.claim": "你们都将获得 $5 使用额度,可用于您的 Go 使用限额。", "workspace.referral.rewards.title": "邀请奖励", + "workspace.referral.rewards.description": "将可用的邀请积分应用到您的 Go 用量。", "workspace.referral.rewards.subtitle": "已使用 {{applied}} / {{total}} 个奖励。", "workspace.referral.rewards.empty": "暂无邀请奖励。", "workspace.referral.table.reward": "奖励", "workspace.referral.table.referral": "描述", "workspace.referral.table.date": "日期", + "workspace.referral.reward.description.inviter": "已邀请 {{email}}", + "workspace.referral.reward.description.invitee": "由 {{email}} 邀请", + "workspace.referral.reward.action.subscribeUnlock": "订阅以解锁", + "workspace.referral.reward.action.view": "查看奖励", + "workspace.referral.reward.action.applied": "奖励已使用", "workspace.referral.reward.source.pendingInviter": "等待对方订阅", "workspace.referral.reward.source.pendingInvitee": "订阅即可解锁奖励", "workspace.referral.reward.source.available": "奖励可使用", "workspace.referral.reward.source.applied": "奖励已使用", - "workspace.referral.reward.status.applied": "已使用", - "workspace.referral.reward.status.pendingInviter": "等待订阅", - "workspace.referral.reward.status.pendingInvitee": "订阅后解锁", - "workspace.referral.apply.noGo": "订阅 Go", - "workspace.referral.apply.preview": "预览", + "workspace.referral.reward.status.applied": "奖励已使用", + "workspace.referral.reward.status.pendingInviter": "订阅以解锁", + "workspace.referral.reward.status.pendingInvitee": "订阅以解锁", + "workspace.referral.apply.noGo": "订阅以解锁", + "workspace.referral.apply.preview": "查看奖励", "workspace.referral.apply.action": "使用", - "workspace.referral.apply.confirmTitle": "使用 Go 奖励", - "workspace.referral.apply.confirmBody": "使用 {{amount}} 抵扣当前工作区的 Go 用量计数。", + "workspace.referral.apply.confirmTitle": "使用奖励", + "workspace.referral.apply.confirmBody": "使用 {{amount}} 抵扣当前工作区的用量。", "workspace.referral.apply.confirmAction": "使用", "download.title": "OpenCode | 下载", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 54376d73c7a3..cfb57d359fd2 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -645,32 +645,35 @@ export const dict = { "workspace.referral.copyLink": "複製連結", "workspace.referral.copied": "已複製", - "workspace.referral.overview.title": "邀請朋友使用 Go", - "workspace.referral.overview.subtitle": "朋友訂閱後,您可獲得 $5 Go 抵用金,對方也可獲得 $5。", - "workspace.referral.stats.invites": "邀請", - "workspace.referral.stats.earned": "已獲得", - "workspace.referral.stats.applied": "已使用", - "workspace.referral.instructions.share": "分享您的邀請連結。", - "workspace.referral.instructions.subscribe": "朋友訂閱 Go。", - "workspace.referral.instructions.claim": "在下方使用您的 $5 抵用金。", + "workspace.referral.overview.title": "邀請朋友", + "workspace.referral.overview.subtitle": "朋友訂閱後,您可獲得 $5,對方也可獲得 $5。", + "workspace.referral.instructions.share": "分享您的推薦連結。", + "workspace.referral.instructions.subscribe": "朋友加入並訂閱 Go。", + "workspace.referral.instructions.claim": "你們都將獲得 $5 使用額度,可用於您的 Go 使用限額。", "workspace.referral.rewards.title": "邀請獎勵", + "workspace.referral.rewards.description": "將可用的邀請點數套用至您的 Go 使用量。", "workspace.referral.rewards.subtitle": "已使用 {{applied}} / {{total}} 個獎勵。", "workspace.referral.rewards.empty": "暫無邀請獎勵。", "workspace.referral.table.reward": "獎勵", "workspace.referral.table.referral": "描述", "workspace.referral.table.date": "日期", + "workspace.referral.reward.description.inviter": "已邀請 {{email}}", + "workspace.referral.reward.description.invitee": "由 {{email}} 邀請", + "workspace.referral.reward.action.subscribeUnlock": "訂閱以解鎖", + "workspace.referral.reward.action.view": "查看獎勵", + "workspace.referral.reward.action.applied": "獎勵已使用", "workspace.referral.reward.source.pendingInviter": "等待對方訂閱", "workspace.referral.reward.source.pendingInvitee": "訂閱即可解鎖獎勵", "workspace.referral.reward.source.available": "獎勵可使用", "workspace.referral.reward.source.applied": "獎勵已使用", - "workspace.referral.reward.status.applied": "已使用", - "workspace.referral.reward.status.pendingInviter": "等待訂閱", - "workspace.referral.reward.status.pendingInvitee": "訂閱後解鎖", - "workspace.referral.apply.noGo": "訂閱 Go", - "workspace.referral.apply.preview": "預覽", + "workspace.referral.reward.status.applied": "獎勵已使用", + "workspace.referral.reward.status.pendingInviter": "訂閱以解鎖", + "workspace.referral.reward.status.pendingInvitee": "訂閱以解鎖", + "workspace.referral.apply.noGo": "訂閱以解鎖", + "workspace.referral.apply.preview": "查看獎勵", "workspace.referral.apply.action": "使用", - "workspace.referral.apply.confirmTitle": "使用 Go 獎勵", - "workspace.referral.apply.confirmBody": "使用 {{amount}} 抵扣目前工作區的 Go 用量計數。", + "workspace.referral.apply.confirmTitle": "使用獎勵", + "workspace.referral.apply.confirmBody": "使用 {{amount}} 抵扣目前工作區的用量。", "workspace.referral.apply.confirmAction": "使用", "download.title": "OpenCode | 下載", diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index 373a2b405311..71102c7227c1 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -1,5 +1,5 @@ import "./index.css" -import { createAsync, query, useLocation } from "@solidjs/router" +import { createAsync, query } from "@solidjs/router" import { Title, Meta } from "@solidjs/meta" import { For, createMemo, createSignal, onCleanup, onMount } from "solid-js" //import { HttpHeader } from "@solidjs/start" @@ -224,15 +224,8 @@ function LimitsGraph(props: { href: string }) { } export default function Home() { - const location = useLocation() const workspaceID = createAsync(() => checkLoggedIn()) - const referralCode = createMemo(() => new URLSearchParams(location.search).get("ref") ?? undefined) - const subscribeUrl = createMemo(() => { - const code = referralCode() - const referral = code ? `?ref=${encodeURIComponent(code)}` : "" - if (workspaceID()) return `/workspace/${workspaceID()}/go${referral}` - return `/auth${referral}` - }) + const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/go` : "/auth")) const i18n = useI18n() const language = useLanguage() return ( diff --git a/packages/console/app/src/routes/workspace/[id]/go/index.tsx b/packages/console/app/src/routes/workspace/[id]/go/index.tsx index bf4c394137be..c73aae7daf8d 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/index.tsx @@ -4,13 +4,14 @@ import { IconGo } from "~/component/icon" import { GoReferralSection, queryGoReferral } from "~/component/go-referral" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" -import { LiteSection } from "./lite-section" +import { LiteSection, queryLiteSubscription } from "./lite-section" export default function () { const params = useParams() const i18n = useI18n() const language = useLanguage() const referral = createAsync(() => queryGoReferral(params.id!)) + const lite = createAsync(() => queryLiteSubscription(params.id!)) return (
@@ -28,13 +29,9 @@ export default function () {
- + {i18n.t("workspace.lite.loading")}}> - {(summary) => ( - 0}> - - - )} + {(summary) => }
diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index a3ce2bf9c996..2e065e938848 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -68,6 +68,8 @@ export const queryLiteSubscription = query(async (workspaceID: string) => { }, workspaceID) }, "lite.subscription.get") +type LiteSubscription = Awaited> + const createLiteCheckoutUrl = action( async (workspaceID: string, successUrl: string, cancelUrl: string, method?: "alipay" | "upi") => { "use server" @@ -147,13 +149,12 @@ function LiteUsageItem(props: { label: string; usage: { usagePercent: number; re ) } -export function LiteSection() { +export function LiteSection(props: { lite: LiteSubscription | undefined }) { const params = useParams() const i18n = useI18n() const language = useLanguage() const billingInfo = createAsync(() => queryBillingInfo(params.id!)) const isBlack = createMemo(() => billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked) - const lite = createAsync(() => queryLiteSubscription(params.id!)) const sessionAction = useAction(createSessionUrl) const sessionSubmission = useSubmission(createSessionUrl) const checkoutAction = useAction(createLiteCheckoutUrl) @@ -193,7 +194,7 @@ export function LiteSection() {

{i18n.t("workspace.lite.black.message")}

- + {(sub) => (
@@ -235,12 +236,12 @@ export function LiteSection() {
)}
- +

{i18n.t("workspace.lite.other.message")}

- +

{ - const [rewards, invites, inviteeReferrals, inviteeRewards, lite] = await Promise.all([ + const [rewards, invites, inviteeReferral, inviteeRewards] = await Promise.all([ tx .select({ referralID: ReferralRewardTable.referralID, @@ -91,8 +91,7 @@ export namespace Referral { isNull(ReferralRewardTable.timeDeleted), isNull(ReferralTable.timeDeleted), ), - ) - .orderBy(desc(ReferralRewardTable.timeCreated)), + ), tx .select({ id: ReferralTable.id, inviteeEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) .from(ReferralTable) @@ -102,13 +101,23 @@ export namespace Referral { ) .where(and(eq(ReferralTable.workspaceID, workspaceID), isNull(ReferralTable.timeDeleted))), tx - .select({ id: ReferralTable.id, inviteeEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) + .select({ id: ReferralTable.id, inviterEmail: AuthTable.subject, timeCreated: ReferralTable.timeCreated }) .from(ReferralTable) - .innerJoin( + .leftJoin( + UserTable, + and( + eq(UserTable.workspaceID, ReferralTable.workspaceID), + eq(UserTable.role, "admin"), + isNull(UserTable.timeDeleted), + ), + ) + .leftJoin( AuthTable, - and(eq(AuthTable.accountID, ReferralTable.inviteeAccountID), eq(AuthTable.provider, "email")), + and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")), ) - .where(and(eq(ReferralTable.inviteeAccountID, accountID), isNull(ReferralTable.timeDeleted))), + .where(and(eq(ReferralTable.inviteeAccountID, accountID), isNull(ReferralTable.timeDeleted))) + .orderBy(asc(UserTable.timeCreated)) + .then((rows) => rows.find((row) => row.inviterEmail) ?? rows[0]), tx .select({ referralID: ReferralRewardTable.referralID }) .from(ReferralRewardTable) @@ -120,27 +129,25 @@ export namespace Referral { isNull(ReferralTable.timeDeleted), ), ), - tx - .select({ id: LiteTable.id }) - .from(LiteTable) - .where(and(eq(LiteTable.workspaceID, workspaceID), isNull(LiteTable.timeDeleted))) - .then((result) => result[0]), ]) - return { inviteeReferrals, inviteeRewards, invites, lite, rewards } + return { inviteeReferral, inviteeRewards, invites, rewards } }) const rewardReferralIDs = new Set(rows.rewards.map((reward) => reward.referralID)) const inviteeRewardReferralIDs = new Set(rows.inviteeRewards.map((reward) => reward.referralID)) - const rewards = rows.rewards.map((reward) => ({ - id: reward.referralID, - source: reward.workspaceID === reward.referralWorkspaceID ? ("inviter" as const) : ("invitee" as const), - status: reward.timeApplied ? ("applied" as const) : ("available" as const), - email: reward.inviteeEmail, - amount: microCentsToCents(reward.amount), - timeCreated: reward.timeCreated, - timeApplied: reward.timeApplied, - })) + const rewards = rows.rewards.map((reward) => { + const source = reward.workspaceID === reward.referralWorkspaceID ? ("inviter" as const) : ("invitee" as const) + return { + id: reward.referralID, + source, + status: reward.timeApplied ? ("applied" as const) : ("available" as const), + email: source === "invitee" ? (rows.inviteeReferral?.inviterEmail ?? null) : reward.inviteeEmail, + amount: microCentsToCents(reward.amount), + timeCreated: reward.timeCreated, + timeApplied: reward.timeApplied, + } + }) const pending = [ ...rows.invites .filter((referral) => !rewardReferralIDs.has(referral.id)) @@ -153,28 +160,27 @@ export namespace Referral { timeCreated: referral.timeCreated, timeApplied: null, })), - ...rows.inviteeReferrals - .filter((referral) => !inviteeRewardReferralIDs.has(referral.id)) - .map((referral) => ({ - id: `${referral.id}:invitee`, - source: "invitee" as const, - status: "pending" as const, - email: referral.inviteeEmail, - amount: microCentsToCents(REWARD_AMOUNT), - timeCreated: referral.timeCreated, - timeApplied: null, - })), + ...(rows.inviteeReferral && !inviteeRewardReferralIDs.has(rows.inviteeReferral.id) + ? [ + { + id: `${rows.inviteeReferral.id}:invitee`, + source: "invitee" as const, + status: "pending" as const, + email: rows.inviteeReferral.inviterEmail, + amount: microCentsToCents(REWARD_AMOUNT), + timeCreated: rows.inviteeReferral.timeCreated, + timeApplied: null, + }, + ] + : []), ] const allRewards = [...pending, ...rewards].sort( (a, b) => new Date(b.timeCreated).getTime() - new Date(a.timeCreated).getTime(), ) return { referralCode: code.code, - inviteCount: allRewards.length, - hasActiveGo: !!rows.lite, + hasReferral: allRewards.length > 0, rewardAmount: microCentsToCents(REWARD_AMOUNT), - totalEarned: rewards.reduce((total, reward) => total + reward.amount, 0), - totalApplied: rewards.filter((reward) => reward.timeApplied).reduce((total, reward) => total + reward.amount, 0), rewards: allRewards, } }) diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index 5f1d5d56e1d5..088db5be2c7e 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -296,6 +296,7 @@ declare module "sst" { "AuthStorage": cloudflare.KVNamespace "Bucket": cloudflare.R2Bucket "EnterpriseStorage": cloudflare.R2Bucket + "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "Stat": cloudflare.Service "ZenData": cloudflare.R2Bucket diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts index 5f1d5d56e1d5..088db5be2c7e 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -296,6 +296,7 @@ declare module "sst" { "AuthStorage": cloudflare.KVNamespace "Bucket": cloudflare.R2Bucket "EnterpriseStorage": cloudflare.R2Bucket + "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "Stat": cloudflare.Service "ZenData": cloudflare.R2Bucket diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index 5f1d5d56e1d5..088db5be2c7e 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -296,6 +296,7 @@ declare module "sst" { "AuthStorage": cloudflare.KVNamespace "Bucket": cloudflare.R2Bucket "EnterpriseStorage": cloudflare.R2Bucket + "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "Stat": cloudflare.Service "ZenData": cloudflare.R2Bucket diff --git a/packages/enterprise/sst-env.d.ts b/packages/enterprise/sst-env.d.ts index 5f1d5d56e1d5..088db5be2c7e 100644 --- a/packages/enterprise/sst-env.d.ts +++ b/packages/enterprise/sst-env.d.ts @@ -296,6 +296,7 @@ declare module "sst" { "AuthStorage": cloudflare.KVNamespace "Bucket": cloudflare.R2Bucket "EnterpriseStorage": cloudflare.R2Bucket + "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "Stat": cloudflare.Service "ZenData": cloudflare.R2Bucket diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index 5f1d5d56e1d5..088db5be2c7e 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -296,6 +296,7 @@ declare module "sst" { "AuthStorage": cloudflare.KVNamespace "Bucket": cloudflare.R2Bucket "EnterpriseStorage": cloudflare.R2Bucket + "GatewayKv": cloudflare.KVNamespace "LogProcessor": cloudflare.Service "Stat": cloudflare.Service "ZenData": cloudflare.R2Bucket diff --git a/sst-env.d.ts b/sst-env.d.ts index 25319d9c4688..b75b4bd6e635 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -106,6 +106,10 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "GatewayKv": { + "namespaceId": string + "type": "sst.cloudflare.Kv" + } "HONEYCOMB_API_KEY": { "type": "sst.sst.Secret" "value": string From a99337fbe15b4ea6ccd0f1d34c586c722a6a22a0 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 19 May 2026 18:28:20 +0000 Subject: [PATCH 026/237] chore: generate --- packages/console/app/src/i18n/br.ts | 3 ++- packages/console/app/src/i18n/de.ts | 3 ++- packages/console/app/src/i18n/es.ts | 3 ++- packages/console/app/src/i18n/fr.ts | 6 ++++-- packages/console/app/src/i18n/it.ts | 3 ++- packages/console/app/src/i18n/ru.ts | 6 ++++-- packages/console/app/src/i18n/tr.ts | 3 ++- packages/console/core/src/referral.ts | 5 +---- 8 files changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 409a5138a452..8f902cb63f60 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -676,7 +676,8 @@ export const dict = { "workspace.referral.overview.subtitle": "Ganhe $5 quando um amigo assinar. Ele também ganha $5.", "workspace.referral.instructions.share": "Compartilhe seu link de indicação", "workspace.referral.instructions.subscribe": "Seu amigo entra e assina o Go", - "workspace.referral.instructions.claim": "Vocês dois ganham um crédito de uso de $5 para aplicar aos seus limites de uso do Go", + "workspace.referral.instructions.claim": + "Vocês dois ganham um crédito de uso de $5 para aplicar aos seus limites de uso do Go", "workspace.referral.rewards.title": "Recompensas de indicação", "workspace.referral.rewards.description": "Aplique os créditos de indicação disponíveis no seu uso do Go.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} recompensas aplicadas.", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 56a66249f2c1..8cbbbe3ef764 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -675,7 +675,8 @@ export const dict = { "workspace.referral.overview.subtitle": "Erhalte $5, wenn ein Freund abonniert. Er bekommt ebenfalls $5.", "workspace.referral.instructions.share": "Teile deinen Empfehlungslink", "workspace.referral.instructions.subscribe": "Dein Freund tritt bei und abonniert Go", - "workspace.referral.instructions.claim": "Ihr erhaltet beide ein Nutzungsguthaben von $5, das ihr auf eure Go-Nutzungslimits anrechnen könnt", + "workspace.referral.instructions.claim": + "Ihr erhaltet beide ein Nutzungsguthaben von $5, das ihr auf eure Go-Nutzungslimits anrechnen könnt", "workspace.referral.rewards.title": "Empfehlungsbelohnungen", "workspace.referral.rewards.description": "Verfügbare Empfehlungsguthaben auf deine Go-Nutzung anwenden.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} Belohnungen eingelöst.", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 78d228b6969a..0e9561b220b3 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -676,7 +676,8 @@ export const dict = { "workspace.referral.overview.subtitle": "Gana $5 cuando un amigo se suscriba. Él también recibirá $5.", "workspace.referral.instructions.share": "Comparte tu enlace de referido", "workspace.referral.instructions.subscribe": "Tu amigo se une y se suscribe a Go", - "workspace.referral.instructions.claim": "Ambos reciben un crédito de uso de $5 para aplicar a sus límites de uso de Go", + "workspace.referral.instructions.claim": + "Ambos reciben un crédito de uso de $5 para aplicar a sus límites de uso de Go", "workspace.referral.rewards.title": "Recompensas por referidos", "workspace.referral.rewards.description": "Aplica los créditos por referidos disponibles a tu uso de Go.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} recompensas aplicadas.", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 03dc2b93a7f7..9aed850eb3a1 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -682,9 +682,11 @@ export const dict = { "workspace.referral.overview.subtitle": "Gagnez $5 lorsqu'un ami s'abonne. Il recevra également $5.", "workspace.referral.instructions.share": "Partagez votre lien de parrainage", "workspace.referral.instructions.subscribe": "Votre ami rejoint et s'abonne à Go", - "workspace.referral.instructions.claim": "Vous recevez tous les deux un crédit d'utilisation de $5 à appliquer à vos limites d'utilisation Go", + "workspace.referral.instructions.claim": + "Vous recevez tous les deux un crédit d'utilisation de $5 à appliquer à vos limites d'utilisation Go", "workspace.referral.rewards.title": "Récompenses de parrainage", - "workspace.referral.rewards.description": "Utilisez les crédits de parrainage disponibles pour votre utilisation de Go.", + "workspace.referral.rewards.description": + "Utilisez les crédits de parrainage disponibles pour votre utilisation de Go.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} récompenses utilisées.", "workspace.referral.rewards.empty": "Aucune récompense de parrainage pour l'instant.", "workspace.referral.table.reward": "Récompense", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 0b6c6929175a..d1b0c390b39a 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -674,7 +674,8 @@ export const dict = { "workspace.referral.overview.subtitle": "Guadagna $5 quando un amico si abbona. Anche lui riceverà $5.", "workspace.referral.instructions.share": "Condividi il tuo link di referral", "workspace.referral.instructions.subscribe": "Il tuo amico si iscrive e si abbona a Go", - "workspace.referral.instructions.claim": "Entrambi ricevete un credito di utilizzo di $5 da applicare ai vostri limiti di utilizzo Go", + "workspace.referral.instructions.claim": + "Entrambi ricevete un credito di utilizzo di $5 da applicare ai vostri limiti di utilizzo Go", "workspace.referral.rewards.title": "Premi referral", "workspace.referral.rewards.description": "Applica i crediti referral disponibili al tuo utilizzo di Go.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} premi utilizzati.", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 7b838f46c60e..d2e284935993 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -680,7 +680,8 @@ export const dict = { "workspace.referral.overview.subtitle": "Получите $5, когда друг оформит подписку. Он тоже получит $5.", "workspace.referral.instructions.share": "Поделитесь своей реферальной ссылкой", "workspace.referral.instructions.subscribe": "Ваш друг присоединяется и оформляет подписку на Go", - "workspace.referral.instructions.claim": "Вы оба получаете кредит на использование $5, который можно применить к лимитам использования Go", + "workspace.referral.instructions.claim": + "Вы оба получаете кредит на использование $5, который можно применить к лимитам использования Go", "workspace.referral.rewards.title": "Реферальные награды", "workspace.referral.rewards.description": "Используйте доступные реферальные кредиты для оплаты использования Go.", "workspace.referral.rewards.subtitle": "Использовано {{applied}} / {{total}} наград.", @@ -704,7 +705,8 @@ export const dict = { "workspace.referral.apply.preview": "Посмотреть награду", "workspace.referral.apply.action": "Применить", "workspace.referral.apply.confirmTitle": "Применить награду", - "workspace.referral.apply.confirmBody": "Используйте {{amount}}, чтобы уменьшить текущее использование этого workspace.", + "workspace.referral.apply.confirmBody": + "Используйте {{amount}}, чтобы уменьшить текущее использование этого workspace.", "workspace.referral.apply.confirmAction": "Применить", "download.title": "OpenCode | Скачать", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 5d85f76e020b..bf3144c73b94 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -676,7 +676,8 @@ export const dict = { "workspace.referral.overview.subtitle": "Bir arkadaşın abone olduğunda $5 kazan. O da $5 alacak.", "workspace.referral.instructions.share": "Referans bağlantını paylaş", "workspace.referral.instructions.subscribe": "Arkadaşın katılır ve Go'ya abone olur", - "workspace.referral.instructions.claim": "İkiniz de Go kullanım limitlerinize uygulamak için $5 kullanım kredisi alırsınız", + "workspace.referral.instructions.claim": + "İkiniz de Go kullanım limitlerinize uygulamak için $5 kullanım kredisi alırsınız", "workspace.referral.rewards.title": "Davet ödülleri", "workspace.referral.rewards.description": "Mevcut davet kredilerini Go kullanımınıza uygulayın.", "workspace.referral.rewards.subtitle": "{{applied}} / {{total}} ödül kullanıldı.", diff --git a/packages/console/core/src/referral.ts b/packages/console/core/src/referral.ts index 440366ab254e..4c29c76776f1 100644 --- a/packages/console/core/src/referral.ts +++ b/packages/console/core/src/referral.ts @@ -111,10 +111,7 @@ export namespace Referral { isNull(UserTable.timeDeleted), ), ) - .leftJoin( - AuthTable, - and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")), - ) + .leftJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email"))) .where(and(eq(ReferralTable.inviteeAccountID, accountID), isNull(ReferralTable.timeDeleted))) .orderBy(asc(UserTable.timeCreated)) .then((rows) => rows.find((row) => row.inviterEmail) ?? rows[0]), From 64d67f213434ff808e85b557d694b7dd9c52dc06 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 19 May 2026 14:29:53 -0400 Subject: [PATCH 027/237] sync --- packages/console/app/src/routes/zen/util/handler.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 3af36ad77a6c..6d11a94730f3 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -170,6 +170,10 @@ export async function handler( if (v === "$ip") return [[k, ip]] if (v === "$workspace") return authInfo?.workspaceID ? [[k, authInfo?.workspaceID]] : [] if (v === "$session") return sessionId ? [[k, sessionId]] : [] + if (v === "$user") { + const user = sessionId ?? authInfo?.workspaceID ?? ip + return user ? [[k, user]] : [] + } if (v.startsWith("$header.")) { const headerValue = input.request.headers.get(v.slice(8)) return headerValue ? [[k, headerValue]] : [] From ea27114eb9c7890a35bcfffdb2e6eda74c55c41d Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 19 May 2026 15:01:27 -0400 Subject: [PATCH 028/237] go: update referral invite ui style --- packages/console/app/src/component/go-referral.css | 10 +++++----- packages/console/app/src/component/go-referral.tsx | 2 +- packages/console/core/sst-env.d.ts | 1 - packages/console/function/sst-env.d.ts | 1 - packages/console/resource/sst-env.d.ts | 1 - packages/enterprise/sst-env.d.ts | 1 - packages/function/sst-env.d.ts | 1 - sst-env.d.ts | 4 ---- 8 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/console/app/src/component/go-referral.css b/packages/console/app/src/component/go-referral.css index aa61612d4b93..2f3b4d5e7b81 100644 --- a/packages/console/app/src/component/go-referral.css +++ b/packages/console/app/src/component/go-referral.css @@ -98,12 +98,10 @@ flex-direction: column; gap: var(--space-3); - > div { + >div { display: flex; align-items: center; gap: var(--space-3); - padding: var(--space-4); - border: 2px solid var(--color-accent); border-radius: var(--border-radius-sm); @media (max-width: 40rem) { @@ -183,7 +181,7 @@ } } - [data-component="go-referral-overview"] + [data-slot="section-title"] { + [data-component="go-referral-overview"]+[data-slot="section-title"] { margin-top: var(--space-4); } @@ -265,6 +263,7 @@ } &[data-status="pending"] { + td[data-slot="referral-amount"], td[data-slot="referral-date"] { color: var(--color-text-muted); @@ -281,6 +280,7 @@ } @media (max-width: 40rem) { + th, td { padding: var(--space-2) var(--space-3); @@ -288,4 +288,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/console/app/src/component/go-referral.tsx b/packages/console/app/src/component/go-referral.tsx index 325111cadc4e..fd4d5c8b1a20 100644 --- a/packages/console/app/src/component/go-referral.tsx +++ b/packages/console/app/src/component/go-referral.tsx @@ -95,7 +95,7 @@ function CopyInviteLink(props: { summary: GoReferralSummary }) {

{inviteUrl()} - + +
+ {/**/} +
+ ) }} - > - - - - -
-
- -
- - -
- -
-
-
- + +
+
- - -