Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@tsconfig/bun": "catalog:",
"@types/babel__core": "7.20.5",
"@types/bun": "catalog:",
"@types/semver": "^7.5.8",
"@types/turndown": "5.0.5",
"@types/yargs": "17.0.33",
"@typescript/native-preview": "catalog:",
Expand Down
24 changes: 24 additions & 0 deletions packages/opencode/src/bus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,28 @@ export namespace Bus {
match.splice(index, 1)
}
}

// Stub exports for plugin system compatibility
export interface Interface {
publish: typeof publish
subscribe: typeof subscribe
}
export const Service = {
key: "bus" as const,
access: () => {
throw new Error("Bus.Service requires Effect runtime - not available in this fork")
}
} as const
export const layer = {
key: "bus" as const,
access: () => {
throw new Error("Bus.layer requires Effect runtime - not available in this fork")
}
} as const
export const defaultLayer = {
key: "bus" as const,
access: () => {
throw new Error("Bus.defaultLayer requires Effect runtime - not available in this fork")
}
} as const
}
35 changes: 35 additions & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1500,4 +1500,39 @@ export namespace Config {
export async function directories() {
return state().then((x) => x.directories)
}

// Stub exports for plugin system compatibility
export type PluginOptions = undefined
export type PluginOrigin = { spec: string }
export type PluginSpec = string

export function pluginSpecifier(item: PluginSpec): string {
return item
}

export function pluginOptions(_item: PluginSpec): PluginOptions {
return undefined
}

// Stub for Effect Layer pattern used by plugin system
export const defaultLayer = {
key: "config" as const,
access: () => {
throw new Error("Config.defaultLayer requires Effect runtime - not available in this fork")
},
}

export interface Service {
[key: string]: unknown
}

export const Service: Service = new Proxy({} as Service, {
get(_target, prop) {
if (prop === Symbol.toStringTag) return "Service"
throw new Error(`Service stub: ${String(prop)} not implemented`)
},
getOwnPropertyDescriptor() {
return { configurable: true, enumerable: true }
},
})
}
6 changes: 6 additions & 0 deletions packages/opencode/src/config/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Stub for missing upstream @/config/paths module
export namespace ConfigPaths {
export function fileInDirectory(dir: string, name: "opencode" | "tui"): string[] {
return []
}
}
27 changes: 27 additions & 0 deletions packages/opencode/src/effect/instance-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Stub for missing upstream @/effect/instance-state module
// No external dependencies - pure TypeScript implementation

type State = Record<string, unknown>

const stateMap = new Map<string, State>()
const stateFactories = new Map<string, () => State>()

export const InstanceState = {
make<S extends State>(factory: () => S) {
const key = Math.random().toString(36)
stateFactories.set(key, factory)
return {
[Symbol.iterator]: function* () {
let state = stateMap.get(key)
if (!state) {
state = factory()
stateMap.set(key, state)
}
yield state as S
},
}
},
get<S extends State>(iterable: { [Symbol.iterator]: () => Iterator<S> }): { [Symbol.iterator]: () => Iterator<S> } {
return iterable
},
}
15 changes: 15 additions & 0 deletions packages/opencode/src/effect/run-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Stub for missing upstream @/effect/run-service module
// No external dependencies - pure TypeScript implementation

export const makeRuntime = <I, S>(
_service: new () => S,
_layer: { key: string },
) => {
return {
runPromise: async <R>(fn: (svc: S) => Promise<R>): Promise<R> => {
// This is a simplified stub that doesn't actually run the effect runtime
// Real implementation would use Effect.runPromise
throw new Error("makeRuntime stub called - plugin system needs rework")
},
}
}
2 changes: 2 additions & 0 deletions packages/opencode/src/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export namespace Flag {
export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN")
export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"]
export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"]
export const OPENCODE_PURE = truthy("OPENCODE_PURE")
export const OPENCODE_PLUGIN_META_FILE = process.env["OPENCODE_PLUGIN_META_FILE"]

function number(key: string) {
const value = process.env[key]
Expand Down
6 changes: 6 additions & 0 deletions packages/opencode/src/npm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Stub for missing upstream @/npm module
export namespace Npm {
export async function add(pkg: string): Promise<{ directory: string }> {
throw new Error("Npm.add not implemented - needed for plugin system")
}
}
67 changes: 67 additions & 0 deletions packages/opencode/src/plugin/cloudflare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { Hooks, PluginInput } from "@opencode-ai/plugin"

export async function CloudflareWorkersAuthPlugin(_input: PluginInput): Promise<Hooks> {
const prompts = [
...(!process.env.CLOUDFLARE_ACCOUNT_ID
? [
{
type: "text" as const,
key: "accountId",
message: "Enter your Cloudflare Account ID",
placeholder: "e.g. 1234567890abcdef1234567890abcdef",
},
]
: []),
]

return {
auth: {
provider: "cloudflare-workers-ai",
methods: [
{
type: "api",
label: "API key",
prompts,
},
],
},
}
}

export async function CloudflareAIGatewayAuthPlugin(_input: PluginInput): Promise<Hooks> {
const prompts = [
...(!process.env.CLOUDFLARE_ACCOUNT_ID
? [
{
type: "text" as const,
key: "accountId",
message: "Enter your Cloudflare Account ID",
placeholder: "e.g. 1234567890abcdef1234567890abcdef",
},
]
: []),
...(!process.env.CLOUDFLARE_GATEWAY_ID
? [
{
type: "text" as const,
key: "gatewayId",
message: "Enter your Cloudflare AI Gateway ID",
placeholder: "e.g. my-gateway",
},
]
: []),
]

return {
auth: {
provider: "cloudflare-ai-gateway",
methods: [
{
type: "api",
label: "Gateway API token",
prompts,
},
],
},
}
}
41 changes: 6 additions & 35 deletions packages/opencode/src/plugin/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Installation } from "../installation"
import { Auth, OAUTH_DUMMY_KEY } from "../auth"
import os from "os"
import { ProviderTransform } from "@/provider/transform"
import { setTimeout as sleep } from "node:timers/promises"

const log = Log.create({ service: "plugin.codex" })

Expand Down Expand Up @@ -354,55 +355,25 @@ export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
provider: "openai",
async loader(getAuth, provider) {
const auth = await getAuth()
if (!auth || auth.type !== "oauth") return {}
if (auth.type !== "oauth") return {}

// Filter models to only allowed Codex models for OAuth
const allowedModels = new Set([
"gpt-5.1-codex",
"gpt-5.1-codex-max",
"gpt-5.1-codex-mini",
"gpt-5.2",
"gpt-5.2-codex",
"gpt-5.3-codex",
"gpt-5.1-codex",
"gpt-5.4",
"gpt-5.4-mini",
])
for (const modelId of Object.keys(provider.models)) {
if (modelId.includes("codex")) continue
if (allowedModels.has(modelId)) continue
delete provider.models[modelId]
}

if (!provider.models["gpt-5.3-codex"]) {
const model = {
id: "gpt-5.3-codex",
providerID: "openai",
api: {
id: "gpt-5.3-codex",
url: "https://chatgpt.com/backend-api/codex",
npm: "@ai-sdk/openai",
},
name: "GPT-5.3 Codex",
capabilities: {
temperature: false,
reasoning: true,
attachment: true,
toolcall: true,
input: { text: true, audio: false, image: true, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
limit: { context: 400_000, input: 272_000, output: 128_000 },
status: "active" as const,
options: {},
headers: {},
release_date: "2026-02-05",
variants: {} as Record<string, Record<string, any>>,
family: "gpt-codex",
}
model.variants = ProviderTransform.variants(model)
provider.models["gpt-5.3-codex"] = model
}

// Zero out costs for Codex (included with ChatGPT subscription)
for (const model of Object.values(provider.models)) {
model.cost = {
Expand Down Expand Up @@ -602,7 +573,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
return { type: "failed" as const }
}

await Bun.sleep(interval + OAUTH_POLLING_SAFETY_MARGIN_MS)
await sleep(interval + OAUTH_POLLING_SAFETY_MARGIN_MS)
}
},
}
Expand Down
Loading
Loading