fix(opencode): restore v1 account config (#33590)
Co-authored-by: Test <test@opencode.test>
This commit is contained in:
committed by
GitHub
parent
af31e97493
commit
5647ed8ba3
@@ -13,11 +13,12 @@ import { Env } from "../env"
|
|||||||
import { applyEdits, modify } from "jsonc-parser"
|
import { applyEdits, modify } from "jsonc-parser"
|
||||||
import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version"
|
import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||||
import { existsSync } from "fs"
|
import { existsSync } from "fs"
|
||||||
|
import { Account } from "@/account/account"
|
||||||
import { isRecord } from "@/util/record"
|
import { isRecord } from "@/util/record"
|
||||||
import type { ConsoleState } from "@opencode-ai/core/v1/config/console-state"
|
import type { ConsoleState } from "@opencode-ai/core/v1/config/console-state"
|
||||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { Context, Duration, Effect, Exit, Fiber, Layer, Schema } from "effect"
|
import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect"
|
||||||
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
|
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
|
||||||
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
||||||
import { containsPath, type InstanceContext } from "../project/instance-context"
|
import { containsPath, type InstanceContext } from "../project/instance-context"
|
||||||
@@ -176,6 +177,7 @@ export const layer = Layer.effect(
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
const authSvc = yield* Auth.Service
|
const authSvc = yield* Auth.Service
|
||||||
|
const accountSvc = yield* Account.Service
|
||||||
const env = yield* Env.Service
|
const env = yield* Env.Service
|
||||||
const npmSvc = yield* Npm.Service
|
const npmSvc = yield* Npm.Service
|
||||||
const http = yield* HttpClient.HttpClient
|
const http = yield* HttpClient.HttpClient
|
||||||
@@ -314,6 +316,7 @@ export const layer = Layer.effect(
|
|||||||
|
|
||||||
let result: Info = {}
|
let result: Info = {}
|
||||||
const authEnv: Record<string, string> = {}
|
const authEnv: Record<string, string> = {}
|
||||||
|
const consoleManagedProviders = new Set<string>()
|
||||||
let activeOrgName: string | undefined
|
let activeOrgName: string | undefined
|
||||||
|
|
||||||
const pluginScopeForSource = Effect.fnUntraced(function* (source: string) {
|
const pluginScopeForSource = Effect.fnUntraced(function* (source: string) {
|
||||||
@@ -471,6 +474,44 @@ export const layer = Layer.effect(
|
|||||||
yield* Effect.logDebug("loaded custom config from OPENCODE_CONFIG_CONTENT")
|
yield* Effect.logDebug("loaded custom config from OPENCODE_CONFIG_CONTENT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeAccount = Option.getOrUndefined(
|
||||||
|
yield* accountSvc.active().pipe(Effect.catch(() => Effect.succeed(Option.none()))),
|
||||||
|
)
|
||||||
|
if (activeAccount?.active_org_id) {
|
||||||
|
const accountID = activeAccount.id
|
||||||
|
const orgID = activeAccount.active_org_id
|
||||||
|
const url = activeAccount.url
|
||||||
|
yield* Effect.gen(function* () {
|
||||||
|
const [configOpt, tokenOpt] = yield* Effect.all(
|
||||||
|
[accountSvc.config(accountID, orgID), accountSvc.token(accountID)],
|
||||||
|
{ concurrency: 2 },
|
||||||
|
)
|
||||||
|
if (Option.isSome(tokenOpt)) {
|
||||||
|
process.env["OPENCODE_CONSOLE_TOKEN"] = tokenOpt.value
|
||||||
|
yield* env.set("OPENCODE_CONSOLE_TOKEN", tokenOpt.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Option.isSome(configOpt)) {
|
||||||
|
const source = `${url}/api/config`
|
||||||
|
const next = yield* loadConfig(JSON.stringify(configOpt.value), {
|
||||||
|
dir: path.dirname(source),
|
||||||
|
source,
|
||||||
|
})
|
||||||
|
for (const providerID of Object.keys(next.provider ?? {})) {
|
||||||
|
consoleManagedProviders.add(providerID)
|
||||||
|
}
|
||||||
|
yield* merge(source, next, "global")
|
||||||
|
}
|
||||||
|
}).pipe(
|
||||||
|
Effect.withSpan("Config.loadActiveOrgConfig"),
|
||||||
|
Effect.catch((err) =>
|
||||||
|
Effect.logDebug("failed to fetch remote account config", {
|
||||||
|
error: err instanceof Error ? err.message : String(err),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const managedDir = ConfigManaged.managedConfigDir()
|
const managedDir = ConfigManaged.managedConfigDir()
|
||||||
if (existsSync(managedDir)) {
|
if (existsSync(managedDir)) {
|
||||||
for (const file of ["opencode.json", "opencode.jsonc"]) {
|
for (const file of ["opencode.json", "opencode.jsonc"]) {
|
||||||
@@ -546,7 +587,7 @@ export const layer = Layer.effect(
|
|||||||
directories,
|
directories,
|
||||||
deps,
|
deps,
|
||||||
consoleState: {
|
consoleState: {
|
||||||
consoleManagedProviders: [],
|
consoleManagedProviders: Array.from(consoleManagedProviders),
|
||||||
activeOrgName,
|
activeOrgName,
|
||||||
switchableOrgCount: 0,
|
switchableOrgCount: 0,
|
||||||
},
|
},
|
||||||
@@ -635,10 +676,11 @@ export const defaultLayer = layer.pipe(
|
|||||||
Layer.provide(FSUtil.defaultLayer),
|
Layer.provide(FSUtil.defaultLayer),
|
||||||
Layer.provide(Env.defaultLayer),
|
Layer.provide(Env.defaultLayer),
|
||||||
Layer.provide(Auth.defaultLayer),
|
Layer.provide(Auth.defaultLayer),
|
||||||
|
Layer.provide(Account.defaultLayer),
|
||||||
Layer.provide(Npm.defaultLayer),
|
Layer.provide(Npm.defaultLayer),
|
||||||
Layer.provide(FetchHttpClient.layer),
|
Layer.provide(FetchHttpClient.layer),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const node = LayerNode.make(layer, [FSUtil.node, Auth.node, Env.node, Npm.node, httpClient])
|
export const node = LayerNode.make(layer, [FSUtil.node, Auth.node, Account.node, Env.node, Npm.node, httpClient])
|
||||||
|
|
||||||
export * as Config from "./config"
|
export * as Config from "./config"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { test, expect, describe, afterEach, beforeEach, spyOn } from "bun:test"
|
import { test, expect, describe, afterEach, beforeEach, spyOn } from "bun:test"
|
||||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||||
import { Cause, Effect, Exit, Layer } from "effect"
|
import { Cause, Effect, Exit, Layer, Option } from "effect"
|
||||||
import { NamedError } from "@opencode-ai/core/util/error"
|
import { NamedError } from "@opencode-ai/core/util/error"
|
||||||
import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/unstable/http"
|
import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/unstable/http"
|
||||||
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
||||||
@@ -13,6 +13,7 @@ import { InstanceRef } from "../../src/effect/instance-ref"
|
|||||||
import type { InstanceContext } from "../../src/project/instance-context"
|
import type { InstanceContext } from "../../src/project/instance-context"
|
||||||
import { Auth } from "../../src/auth"
|
import { Auth } from "../../src/auth"
|
||||||
import { Account } from "../../src/account/account"
|
import { Account } from "../../src/account/account"
|
||||||
|
import { AccessToken, AccountID, OrgID } from "../../src/account/schema"
|
||||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||||
import { Env } from "../../src/env"
|
import { Env } from "../../src/env"
|
||||||
import {
|
import {
|
||||||
@@ -140,6 +141,7 @@ const clear = (wait = false) => Effect.runPromise(clearEffect(wait))
|
|||||||
// Get managed config directory from environment (set in preload.ts)
|
// Get managed config directory from environment (set in preload.ts)
|
||||||
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR!
|
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR!
|
||||||
const originalTestToken = process.env.TEST_TOKEN
|
const originalTestToken = process.env.TEST_TOKEN
|
||||||
|
const originalConsoleToken = process.env.OPENCODE_CONSOLE_TOKEN
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await clear(true)
|
await clear(true)
|
||||||
@@ -149,6 +151,8 @@ afterEach(async () => {
|
|||||||
await fs.rm(managedConfigDir, { force: true, recursive: true }).catch(() => {})
|
await fs.rm(managedConfigDir, { force: true, recursive: true }).catch(() => {})
|
||||||
if (originalTestToken === undefined) delete process.env.TEST_TOKEN
|
if (originalTestToken === undefined) delete process.env.TEST_TOKEN
|
||||||
else process.env.TEST_TOKEN = originalTestToken
|
else process.env.TEST_TOKEN = originalTestToken
|
||||||
|
if (originalConsoleToken === undefined) delete process.env.OPENCODE_CONSOLE_TOKEN
|
||||||
|
else process.env.OPENCODE_CONSOLE_TOKEN = originalConsoleToken
|
||||||
await clear(true)
|
await clear(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -560,6 +564,49 @@ it.instance("handles file inclusion with replacement tokens", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const accountTokenIt = configIt({
|
||||||
|
account: Layer.mock(Account.Service)({
|
||||||
|
active: () =>
|
||||||
|
Effect.succeed(
|
||||||
|
Option.some({
|
||||||
|
id: AccountID.make("account-1"),
|
||||||
|
email: "user@example.com",
|
||||||
|
url: "https://control.example.com",
|
||||||
|
active_org_id: OrgID.make("org-1"),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
activeOrg: () =>
|
||||||
|
Effect.succeed(
|
||||||
|
Option.some({
|
||||||
|
account: {
|
||||||
|
id: AccountID.make("account-1"),
|
||||||
|
email: "user@example.com",
|
||||||
|
url: "https://control.example.com",
|
||||||
|
active_org_id: OrgID.make("org-1"),
|
||||||
|
},
|
||||||
|
org: {
|
||||||
|
id: OrgID.make("org-1"),
|
||||||
|
name: "Example Org",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
config: () =>
|
||||||
|
Effect.succeed(
|
||||||
|
Option.some({
|
||||||
|
provider: { opencode: { options: { apiKey: "{env:OPENCODE_CONSOLE_TOKEN}" } } },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
token: () => Effect.succeed(Option.some(AccessToken.make("st_test_token"))),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
accountTokenIt.instance("resolves env templates in account config with account token", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const config = yield* Config.use.get()
|
||||||
|
expect(config.provider?.["opencode"]?.options?.apiKey).toBe("st_test_token")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.instance("validates config schema and throws on invalid fields", () =>
|
it.instance("validates config schema and throws on invalid fields", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
|
|||||||
Reference in New Issue
Block a user