refactor(flags): route installation client through runtime flags (#27369)
This commit is contained in:
@@ -5,7 +5,6 @@ import { ChildProcess } from "effect/unstable/process"
|
|||||||
import { AppProcess } from "@opencode-ai/core/process"
|
import { AppProcess } from "@opencode-ai/core/process"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { BusEvent } from "@/bus/bus-event"
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||||
import semver from "semver"
|
import semver from "semver"
|
||||||
@@ -50,7 +49,11 @@ export const Info = Schema.Struct({
|
|||||||
}).annotate({ identifier: "InstallationInfo" })
|
}).annotate({ identifier: "InstallationInfo" })
|
||||||
export type Info = Schema.Schema.Type<typeof Info>
|
export type Info = Schema.Schema.Type<typeof Info>
|
||||||
|
|
||||||
export const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}`
|
export function userAgent(client = "cli") {
|
||||||
|
return `opencode/${InstallationChannel}/${InstallationVersion}/${client}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const USER_AGENT = userAgent()
|
||||||
|
|
||||||
export function isPreview() {
|
export function isPreview() {
|
||||||
return InstallationChannel !== "latest"
|
return InstallationChannel !== "latest"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Hash } from "@opencode-ai/core/util/hash"
|
|||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
import { withTransientReadRetry } from "@/util/effect-http-client"
|
import { withTransientReadRetry } from "@/util/effect-http-client"
|
||||||
import { CatalogModelStatus } from "./model-status"
|
import { CatalogModelStatus } from "./model-status"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
const CostTier = Schema.Struct({
|
const CostTier = Schema.Struct({
|
||||||
input: Schema.Finite,
|
input: Schema.Finite,
|
||||||
@@ -109,11 +110,14 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ModelsDev") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/ModelsDev") {}
|
||||||
|
|
||||||
export const layer: Layer.Layer<Service, never, AppFileSystem.Service | HttpClient.HttpClient> = Layer.effect(
|
type Requirements = AppFileSystem.Service | HttpClient.HttpClient | RuntimeFlags.Service
|
||||||
|
|
||||||
|
export const layer: Layer.Layer<Service, never, Requirements> = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
const http = HttpClient.filterStatusOk(withTransientReadRetry(yield* HttpClient.HttpClient))
|
const http = HttpClient.filterStatusOk(withTransientReadRetry(yield* HttpClient.HttpClient))
|
||||||
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
|
||||||
const source = Flag.OPENCODE_MODELS_URL || "https://models.dev"
|
const source = Flag.OPENCODE_MODELS_URL || "https://models.dev"
|
||||||
const filepath = path.join(
|
const filepath = path.join(
|
||||||
@@ -132,7 +136,7 @@ export const layer: Layer.Layer<Service, never, AppFileSystem.Service | HttpClie
|
|||||||
|
|
||||||
const fetchApi = Effect.fn("ModelsDev.fetchApi")(function* () {
|
const fetchApi = Effect.fn("ModelsDev.fetchApi")(function* () {
|
||||||
return yield* HttpClientRequest.get(`${source}/api.json`).pipe(
|
return yield* HttpClientRequest.get(`${source}/api.json`).pipe(
|
||||||
HttpClientRequest.setHeader("User-Agent", Installation.USER_AGENT),
|
HttpClientRequest.setHeader("User-Agent", Installation.userAgent(flags.client)),
|
||||||
http.execute,
|
http.execute,
|
||||||
Effect.flatMap((res) => res.text),
|
Effect.flatMap((res) => res.text),
|
||||||
Effect.timeout("10 seconds"),
|
Effect.timeout("10 seconds"),
|
||||||
@@ -208,6 +212,7 @@ export const layer: Layer.Layer<Service, never, AppFileSystem.Service | HttpClie
|
|||||||
export const defaultLayer: Layer.Layer<Service> = layer.pipe(
|
export const defaultLayer: Layer.Layer<Service> = layer.pipe(
|
||||||
Layer.provide(FetchHttpClient.layer),
|
Layer.provide(FetchHttpClient.layer),
|
||||||
Layer.provide(AppFileSystem.defaultLayer),
|
Layer.provide(AppFileSystem.defaultLayer),
|
||||||
|
Layer.provide(RuntimeFlags.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
export * as ModelsDev from "./models"
|
export * as ModelsDev from "./models"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { ModelsDev } from "../../src/provider/models"
|
|||||||
import { it } from "../lib/effect"
|
import { it } from "../lib/effect"
|
||||||
import { rm, writeFile, utimes, mkdir } from "fs/promises"
|
import { rm, writeFile, utimes, mkdir } from "fs/promises"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
// test/preload.ts pins OPENCODE_MODELS_PATH to a fixture so other tests can
|
// test/preload.ts pins OPENCODE_MODELS_PATH to a fixture so other tests can
|
||||||
// resolve providers without network. These tests need to drive the on-disk
|
// resolve providers without network. These tests need to drive the on-disk
|
||||||
@@ -70,13 +71,16 @@ const fixture2: Record<string, ModelsDev.Provider> = {
|
|||||||
interface MockState {
|
interface MockState {
|
||||||
body: string
|
body: string
|
||||||
status: number
|
status: number
|
||||||
calls: Array<{ url: string }>
|
calls: Array<{ url: string; userAgent: string | null }>
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeMockClient = (state: Ref.Ref<MockState>) =>
|
const makeMockClient = (state: Ref.Ref<MockState>) =>
|
||||||
HttpClient.make((request) =>
|
HttpClient.make((request) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* Ref.update(state, (s) => ({ ...s, calls: [...s.calls, { url: request.url }] }))
|
yield* Ref.update(state, (s) => ({
|
||||||
|
...s,
|
||||||
|
calls: [...s.calls, { url: request.url, userAgent: request.headers["user-agent"] ?? null }],
|
||||||
|
}))
|
||||||
const s = yield* Ref.get(state)
|
const s = yield* Ref.get(state)
|
||||||
return HttpClientResponse.fromWeb(request, new Response(s.body, { status: s.status }))
|
return HttpClientResponse.fromWeb(request, new Response(s.body, { status: s.status }))
|
||||||
}),
|
}),
|
||||||
@@ -89,6 +93,7 @@ const buildLayer = (state: Ref.Ref<MockState>) =>
|
|||||||
Layer.fresh(ModelsDev.layer).pipe(
|
Layer.fresh(ModelsDev.layer).pipe(
|
||||||
Layer.provide(Layer.succeed(HttpClient.HttpClient, makeMockClient(state))),
|
Layer.provide(Layer.succeed(HttpClient.HttpClient, makeMockClient(state))),
|
||||||
Layer.provide(AppFileSystem.defaultLayer),
|
Layer.provide(AppFileSystem.defaultLayer),
|
||||||
|
Layer.provide(RuntimeFlags.layer({ client: "test-client" })),
|
||||||
)
|
)
|
||||||
|
|
||||||
const writeCache = (data: object, mtimeMs?: number) =>
|
const writeCache = (data: object, mtimeMs?: number) =>
|
||||||
@@ -202,6 +207,7 @@ describe("ModelsDev Service", () => {
|
|||||||
const final = yield* Ref.get(state)
|
const final = yield* Ref.get(state)
|
||||||
expect(final.calls.length).toBe(1)
|
expect(final.calls.length).toBe(1)
|
||||||
expect(final.calls[0].url).toContain("/api.json")
|
expect(final.calls[0].url).toContain("/api.json")
|
||||||
|
expect(final.calls[0].userAgent).toContain("/test-client")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user