Skip to content
Open
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
253 changes: 126 additions & 127 deletions bun.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
pkg-config
openssl
git
playwright-driver.browsers
];
shellHook = ''
export PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}"
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true
'';
};
});

Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"dompurify": "3.3.1",
"drizzle-kit": "1.0.0-beta.12-a5629fb",
"drizzle-orm": "1.0.0-beta.12-a5629fb",
"ai": "5.0.124",
"ai": "6.0.111",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
"fuzzysort": "3.1.0",
Expand Down Expand Up @@ -106,7 +106,6 @@
"@types/node": "catalog:"
},
"patchedDependencies": {
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch"
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch"
}
}
6 changes: 3 additions & 3 deletions packages/console/function/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"@typescript/native-preview": "catalog:"
},
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
"@ai-sdk/openai-compatible": "1.0.1",
"@ai-sdk/anthropic": "3.0.53",
"@ai-sdk/openai": "3.0.39",
"@ai-sdk/openai-compatible": "2.0.30",
"@hono/zod-validator": "catalog:",
"@opencode-ai/console-core": "workspace:*",
"@opencode-ai/console-resource": "workspace:*",
Expand Down
44 changes: 22 additions & 22 deletions packages/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,28 +55,27 @@
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.14.1",
"@ai-sdk/amazon-bedrock": "3.0.82",
"@ai-sdk/anthropic": "2.0.65",
"@ai-sdk/azure": "2.0.91",
"@ai-sdk/cerebras": "1.0.36",
"@ai-sdk/cohere": "2.0.22",
"@ai-sdk/deepinfra": "1.0.36",
"@ai-sdk/gateway": "2.0.30",
"@ai-sdk/google": "2.0.54",
"@ai-sdk/google-vertex": "3.0.106",
"@ai-sdk/groq": "2.0.34",
"@ai-sdk/mistral": "2.0.27",
"@ai-sdk/openai": "2.0.89",
"@ai-sdk/openai-compatible": "1.0.32",
"@ai-sdk/perplexity": "2.0.23",
"@ai-sdk/provider": "2.0.1",
"@ai-sdk/provider-utils": "3.0.21",
"@ai-sdk/togetherai": "1.0.34",
"@ai-sdk/vercel": "1.0.33",
"@ai-sdk/xai": "2.0.51",
"@ai-sdk/amazon-bedrock": "4.0.73",
"@ai-sdk/anthropic": "3.0.54",
"@ai-sdk/azure": "3.0.40",
"@ai-sdk/cerebras": "2.0.37",
"@ai-sdk/cohere": "3.0.23",
"@ai-sdk/deepinfra": "2.0.37",
"@ai-sdk/gateway": "3.0.63",
"@ai-sdk/google": "3.0.37",
"@ai-sdk/google-vertex": "4.0.73",
"@ai-sdk/groq": "3.0.27",
"@ai-sdk/mistral": "3.0.22",
"@ai-sdk/openai": "3.0.39",
"@ai-sdk/openai-compatible": "2.0.33",
"@ai-sdk/perplexity": "3.0.21",
"@ai-sdk/provider": "3.0.8",
"@ai-sdk/provider-utils": "4.0.17",
"@ai-sdk/togetherai": "2.0.37",
"@ai-sdk/vercel": "2.0.35",
"@ai-sdk/xai": "3.0.64",
"@aws-sdk/credential-providers": "3.993.0",
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.6.0",
"@gitlab/opencode-gitlab-auth": "1.3.3",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
Expand All @@ -88,7 +87,7 @@
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.4",
"@openrouter/ai-sdk-provider": "2.2.3",
"@opentui/core": "0.1.86",
"@opentui/solid": "0.1.86",
"@parcel/watcher": "2.5.1",
Expand All @@ -98,7 +97,7 @@
"@standard-schema/spec": "1.0.0",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
"ai-gateway-provider": "2.3.1",
"ai-gateway-provider": "3.1.1",
"bonjour-service": "1.3.0",
"bun-pty": "0.4.8",
"chokidar": "4.0.3",
Expand All @@ -107,6 +106,7 @@
"diff": "catalog:",
"drizzle-orm": "1.0.0-beta.12-a5629fb",
"fuzzysort": "3.1.0",
"gitlab-ai-provider": "5.0.0",
"glob": "13.0.5",
"google-auth-library": "10.5.0",
"gray-matter": "4.0.3",
Expand Down
25 changes: 15 additions & 10 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Config } from "../config/config"
import z from "zod"
import { Provider } from "../provider/provider"
import { generateObject, streamObject, type ModelMessage } from "ai"
import { generateText, streamText, Output, type ModelMessage } from "ai"
import { SystemPrompt } from "../session/system"
import { Instance } from "../project/instance"
import { Truncate } from "../tool/truncation"
Expand Down Expand Up @@ -311,15 +311,17 @@ export namespace Agent {
},
],
model: language,
schema: z.object({
identifier: z.string(),
whenToUse: z.string(),
systemPrompt: z.string(),
output: Output.object({
schema: z.object({
identifier: z.string(),
whenToUse: z.string(),
systemPrompt: z.string(),
}),
}),
} satisfies Parameters<typeof generateObject>[0]
} satisfies Parameters<typeof generateText>[0]

if (defaultModel.providerID === "openai" && (await Auth.get(defaultModel.providerID))?.type === "oauth") {
const result = streamObject({
const result = streamText({
...params,
providerOptions: ProviderTransform.providerOptions(model, {
instructions: SystemPrompt.instructions(),
Expand All @@ -330,10 +332,13 @@ export namespace Agent {
for await (const part of result.fullStream) {
if (part.type === "error") throw part.error
}
return result.object
const output = await result.output
if (!output) throw new Error("Failed to generate agent configuration")
return output
}

const result = await generateObject(params)
return result.object
const result = await generateText(params)
if (!result.output) throw new Error("Failed to generate agent configuration")
return result.output
}
}
54 changes: 35 additions & 19 deletions packages/opencode/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import os from "os"
import fuzzysort from "fuzzysort"
import { Config } from "../config/config"
import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda"
import { NoSuchModelError, type Provider as SDK } from "ai"
import { NoSuchModelError, type Provider as SDK, type LanguageModel } from "ai"
import { Log } from "../util/log"
import { BunProc } from "../bun"
import { Plugin } from "../plugin"
Expand All @@ -27,7 +27,7 @@ import { createVertex } from "@ai-sdk/google-vertex"
import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic"
import { createOpenAI } from "@ai-sdk/openai"
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider"
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/copilot"
import { createXai } from "@ai-sdk/xai"
import { createMistral } from "@ai-sdk/mistral"
Expand All @@ -39,11 +39,12 @@ import { createGateway } from "@ai-sdk/gateway"
import { createTogetherAI } from "@ai-sdk/togetherai"
import { createPerplexity } from "@ai-sdk/perplexity"
import { createVercel } from "@ai-sdk/vercel"
import { createGitLab, VERSION as GITLAB_PROVIDER_VERSION } from "@gitlab/gitlab-ai-provider"
import { createGitLab, VERSION as GITLAB_PROVIDER_VERSION } from "gitlab-ai-provider"
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
import { GoogleAuth } from "google-auth-library"
import { ProviderTransform } from "./transform"
import { Installation } from "../installation"
import { shimLanguageModel, shimProvider } from "./shim"

export namespace Provider {
const log = Log.create({ service: "provider" })
Expand Down Expand Up @@ -84,7 +85,12 @@ export namespace Provider {
})
}

const BUNDLED_PROVIDERS: Record<string, (options: any) => SDK> = {
// Provider factories return varying types: most @ai-sdk/* packages are ProviderV3 while legacy
// third-party packages (e.g. GitLab, Venice) may still return ProviderV2. shimProvider() in
// ./shim.ts wraps v2 providers/models to v3 transparently at load time so all downstream code
// sees a consistent v3 interface. Typed as `any` because not all providers expose the full
// provider interface (e.g. missing embeddingModel).
const BUNDLED_PROVIDERS: Record<string, (options: any) => any> = {
"@ai-sdk/amazon-bedrock": createAmazonBedrock,
"@ai-sdk/anthropic": createAnthropic,
"@ai-sdk/azure": createAzure,
Expand All @@ -104,7 +110,7 @@ export namespace Provider {
"@ai-sdk/togetherai": createTogetherAI,
"@ai-sdk/perplexity": createPerplexity,
"@ai-sdk/vercel": createVercel,
"@gitlab/gitlab-ai-provider": createGitLab,
"gitlab-ai-provider": createGitLab,
// @ts-ignore (TODO: kill this code so we dont have to maintain it)
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
}
Expand Down Expand Up @@ -791,7 +797,7 @@ export namespace Provider {
}

const providers: { [providerID: string]: Info } = {}
const languages = new Map<string, LanguageModelV2>()
const languages = new Map<string, LanguageModel>()
const modelLoaders: {
[providerID: string]: CustomModelLoader
} = {}
Expand Down Expand Up @@ -1138,8 +1144,9 @@ export namespace Provider {
name: model.providerID,
...options,
})
s.sdk.set(key, loaded)
return loaded as SDK
const shimmed = shimProvider(loaded)
s.sdk.set(key, shimmed)
return shimmed as SDK
}

let installedPath: string
Expand All @@ -1150,15 +1157,23 @@ export namespace Provider {
installedPath = model.api.npm
}

const mod = await import(installedPath)

const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
const loaded = fn({
name: model.providerID,
...options,
})
s.sdk.set(key, loaded)
return loaded as SDK
try {
const mod = await import(installedPath)
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
const loaded = fn({
name: model.providerID,
...options,
})
const shimmed = shimProvider(loaded)
s.sdk.set(key, shimmed)
return shimmed as SDK
} catch (e) {
log.warn("failed to load provider - it may not be compatible with AI SDK v6", {
providerID: model.providerID,
pkg: model.api.npm,
})
throw e
}
} catch (e) {
throw new InitError({ providerID: model.providerID }, { cause: e })
}
Expand Down Expand Up @@ -1188,7 +1203,7 @@ export namespace Provider {
return info
}

export async function getLanguage(model: Model): Promise<LanguageModelV2> {
export async function getLanguage(model: Model): Promise<LanguageModel> {
const s = await state()
const key = `${model.providerID}/${model.id}`
if (s.models.has(key)) return s.models.get(key)!
Expand All @@ -1197,9 +1212,10 @@ export namespace Provider {
const sdk = await getSDK(model)

try {
const language = s.modelLoaders[model.providerID]
const raw = s.modelLoaders[model.providerID]
? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options)
: sdk.languageModel(model.api.id)
const language = shimLanguageModel(raw)
s.models.set(key, language)
return language
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {
type LanguageModelV2Prompt,
type SharedV2ProviderMetadata,
type LanguageModelV3Prompt,
type SharedV3ProviderMetadata,
UnsupportedFunctionalityError,
} from "@ai-sdk/provider"
import type { OpenAICompatibleChatPrompt } from "./openai-compatible-api-types"
import { convertToBase64 } from "@ai-sdk/provider-utils"

function getOpenAIMetadata(message: { providerOptions?: SharedV2ProviderMetadata }) {
function getOpenAIMetadata(message: { providerOptions?: SharedV3ProviderMetadata }) {
return message?.providerOptions?.copilot ?? {}
}

export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Prompt): OpenAICompatibleChatPrompt {
export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV3Prompt): OpenAICompatibleChatPrompt {
const messages: OpenAICompatibleChatPrompt = []
for (const { role, content, ...message } of prompt) {
const metadata = getOpenAIMetadata({ ...message })
Expand Down Expand Up @@ -126,10 +126,13 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro
}

case "tool": {
for (const toolResponse of content) {
for (const toolResponseRaw of content) {
// Skip tool approval responses (V3 addition) - only process tool results
if (toolResponseRaw.type !== "tool-result") continue
const toolResponse = toolResponseRaw
const output = toolResponse.output

let contentValue: string
let contentValue = ""
switch (output.type) {
case "text":
case "error-text":
Expand All @@ -140,9 +143,14 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro
case "error-json":
contentValue = JSON.stringify(output.value)
break
default: {
// Unknown tool output type — skip rather than forward an empty string
console.warn(`[copilot] unsupported tool output type: ${(output as any).type} — skipping`)
continue
}
}

const toolResponseMetadata = getOpenAIMetadata(toolResponse)
const toolResponseMetadata = getOpenAIMetadata(toolResponse as any)
messages.push({
role: "tool",
tool_call_id: toolResponse.toolCallId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LanguageModelV2FinishReason } from "@ai-sdk/provider"
type V3FinishReasonUnified = "stop" | "length" | "content-filter" | "tool-calls" | "error" | "other"

export function mapOpenAICompatibleFinishReason(finishReason: string | null | undefined): LanguageModelV2FinishReason {
export function mapOpenAICompatibleFinishReason(finishReason: string | null | undefined): V3FinishReasonUnified {
switch (finishReason) {
case "stop":
return "stop"
Expand All @@ -12,6 +12,6 @@ export function mapOpenAICompatibleFinishReason(finishReason: string | null | un
case "tool_calls":
return "tool-calls"
default:
return "unknown"
return "other"
}
}
Loading