mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-28 19:34:43 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 529106dae8 | |||
| a03882ff48 | |||
| 72244e9e09 |
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -85,7 +85,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"bin": {
|
||||
"lildax": "./bin/lildax.cjs",
|
||||
},
|
||||
@@ -110,7 +110,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -146,7 +146,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -173,7 +173,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -195,7 +195,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -219,7 +219,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
@@ -239,7 +239,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -330,7 +330,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"effect": "catalog:",
|
||||
@@ -384,7 +384,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -398,7 +398,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -410,7 +410,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -441,7 +441,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -457,7 +457,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.74",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.74",
|
||||
@@ -476,7 +476,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
"@smithy/util-utf8": "4.2.2",
|
||||
@@ -494,7 +494,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -622,7 +622,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -660,7 +660,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -675,7 +675,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"drizzle-orm": "catalog:",
|
||||
@@ -689,7 +689,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -702,7 +702,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@opencode-ai/stats-core": "workspace:*",
|
||||
@@ -735,7 +735,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -754,7 +754,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -794,7 +794,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
@@ -821,7 +821,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -870,7 +870,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -375,6 +375,12 @@ export function createServerSyncContextInner(_serverSDK?: ServerSDK) {
|
||||
const event = e.details
|
||||
const recent = bootingRoot || Date.now() - bootedAt < 1500
|
||||
|
||||
if (event.type === "provider.models.updated") {
|
||||
void queryClient.invalidateQueries({
|
||||
predicate: (query) => query.queryKey[0] === serverSDK.scope && query.queryKey[2] === "providers",
|
||||
})
|
||||
}
|
||||
|
||||
if (directory === "global") {
|
||||
applyGlobalEvent({
|
||||
event,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -272,10 +272,7 @@ export namespace Billing {
|
||||
},
|
||||
payment_method_options: {
|
||||
card: {
|
||||
setup_future_usage: "off_session",
|
||||
},
|
||||
link: {
|
||||
setup_future_usage: "off_session",
|
||||
setup_future_usage: "on_session",
|
||||
},
|
||||
},
|
||||
//payment_method_data: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"name": "@opencode-ai/llm",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { isImageAttachment } from "@/util/media"
|
||||
|
||||
type ResourceContent = {
|
||||
uri: string
|
||||
mimeType?: string
|
||||
text?: string
|
||||
blob?: string
|
||||
}
|
||||
|
||||
export type Part = SessionV1.TextPartInput | SessionV1.FilePartInput
|
||||
|
||||
export function toParts(contents: readonly ResourceContent[]): Part[] {
|
||||
return contents.flatMap((content) => {
|
||||
if (content.text) return [{ type: "text", text: content.text }]
|
||||
if (!content.blob) return []
|
||||
|
||||
const mime = content.mimeType?.split(";", 1)[0]?.trim().toLowerCase() || "application/octet-stream"
|
||||
if (isImageAttachment(mime)) {
|
||||
return [
|
||||
{
|
||||
type: "file",
|
||||
mime,
|
||||
filename: content.uri,
|
||||
url: `data:${mime};base64,${content.blob}`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
type: "text",
|
||||
text: `[Binary resource: ${mime}, ${Buffer.from(content.blob, "base64").byteLength} bytes]`,
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export * as McpContent from "./content"
|
||||
@@ -28,9 +28,11 @@ import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||
import { ProviderTransform } from "./transform"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { ModelStatus } from "./model-status"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { ProviderError } from "./error"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
|
||||
const OPENAI_HEADER_TIMEOUT_DEFAULT = 10_000
|
||||
|
||||
@@ -1045,6 +1047,13 @@ export const ConfigProvidersResult = Schema.Struct({
|
||||
})
|
||||
export type ConfigProvidersResult = Types.DeepMutable<Schema.Schema.Type<typeof ConfigProvidersResult>>
|
||||
|
||||
export const Event = {
|
||||
ModelsUpdated: EventV2.define({
|
||||
type: "provider.models.updated",
|
||||
schema: { providerID: ProviderV2.ID },
|
||||
}),
|
||||
}
|
||||
|
||||
export function toPublicInfo(provider: Info): Info {
|
||||
return JSON.parse(
|
||||
JSON.stringify(provider, (_, value) => {
|
||||
@@ -1279,6 +1288,7 @@ export const layer = Layer.effect(
|
||||
const plugin = yield* Plugin.Service
|
||||
const modelsDevSvc = yield* ModelsDev.Service
|
||||
const runtimeFlags = yield* RuntimeFlags.Service
|
||||
const events = yield* EventV2Bridge.Service
|
||||
|
||||
const state = yield* InstanceState.make<State>(() =>
|
||||
Effect.gen(function* () {
|
||||
@@ -1334,35 +1344,15 @@ export const layer = Layer.effect(
|
||||
return true
|
||||
}
|
||||
|
||||
for (const hook of plugins) {
|
||||
const p = hook.provider
|
||||
const models = p?.models
|
||||
if (!p || !models) continue
|
||||
|
||||
const providerID = ProviderV2.ID.make(p.id)
|
||||
if (disabled.has(providerID)) continue
|
||||
|
||||
const provider = database[providerID]
|
||||
if (!provider) continue
|
||||
const pluginAuth = yield* auth.get(providerID).pipe(Effect.orDie)
|
||||
|
||||
provider.models = yield* Effect.promise(async () => {
|
||||
const next = await models(toPublicInfo(provider), { auth: pluginAuth })
|
||||
return Object.fromEntries(
|
||||
Object.entries(next).map(([id, model]) => [
|
||||
id,
|
||||
{
|
||||
...model,
|
||||
id: ModelV2.ID.make(id),
|
||||
providerID,
|
||||
},
|
||||
]),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const providerHooks = plugins.flatMap((hook) => {
|
||||
const provider = hook.provider
|
||||
if (!provider?.models) return []
|
||||
const providerID = ProviderV2.ID.make(provider.id)
|
||||
if (!isProviderAllowed(providerID) || !database[providerID]) return []
|
||||
return [{ providerID, models: provider.models }]
|
||||
})
|
||||
// extend database from config
|
||||
for (const [providerID, provider] of configProviders) {
|
||||
function applyConfigProvider(providerID: string, provider: (typeof configProviders)[number][1]) {
|
||||
const existing = database[providerID]
|
||||
const parsed: Info = {
|
||||
id: ProviderV2.ID.make(providerID),
|
||||
@@ -1454,6 +1444,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
database[providerID] = parsed
|
||||
}
|
||||
for (const [providerID, provider] of configProviders) applyConfigProvider(providerID, provider)
|
||||
|
||||
// load env
|
||||
const envs = yield* env.all()
|
||||
@@ -1544,13 +1535,7 @@ export const layer = Layer.effect(
|
||||
})
|
||||
}
|
||||
|
||||
for (const [id, provider] of Object.entries(providers)) {
|
||||
const providerID = ProviderV2.ID.make(id)
|
||||
if (!isProviderAllowed(providerID)) {
|
||||
delete providers[providerID]
|
||||
continue
|
||||
}
|
||||
|
||||
function finalizeProvider(providerID: ProviderV2.ID, provider: Info) {
|
||||
const configProvider = cfg.provider?.[providerID]
|
||||
|
||||
for (const [modelID, model] of Object.entries(provider.models)) {
|
||||
@@ -1586,14 +1571,26 @@ export const layer = Layer.effect(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(provider.models).length === 0) {
|
||||
const refreshProviders = new Map(
|
||||
[...new Set(providerHooks.map((hook) => hook.providerID))].flatMap((providerID) => {
|
||||
const provider = providers[providerID]
|
||||
return provider ? [[providerID, { ...provider, models: {} }] as const] : []
|
||||
}),
|
||||
)
|
||||
|
||||
for (const [id, provider] of Object.entries(providers)) {
|
||||
const providerID = ProviderV2.ID.make(id)
|
||||
if (!isProviderAllowed(providerID)) {
|
||||
delete providers[providerID]
|
||||
continue
|
||||
}
|
||||
finalizeProvider(providerID, provider)
|
||||
if (Object.keys(provider.models).length === 0) delete providers[providerID]
|
||||
}
|
||||
|
||||
return {
|
||||
const result = {
|
||||
models: languages,
|
||||
providers,
|
||||
catalog,
|
||||
@@ -1601,6 +1598,41 @@ export const layer = Layer.effect(
|
||||
modelLoaders,
|
||||
varsLoaders,
|
||||
}
|
||||
|
||||
yield* Effect.forEach(providerHooks, ({ providerID, models }) =>
|
||||
Effect.gen(function* () {
|
||||
const provider = database[providerID]
|
||||
if (!provider) return
|
||||
const pluginAuth = yield* auth.get(providerID).pipe(Effect.orDie)
|
||||
const next = yield* Effect.promise(() => models(toPublicInfo(provider), { auth: pluginAuth }))
|
||||
provider.models = Object.fromEntries(
|
||||
Object.entries(next).map(([id, model]) => [
|
||||
id,
|
||||
{
|
||||
...model,
|
||||
id: ModelV2.ID.make(id),
|
||||
providerID,
|
||||
},
|
||||
]),
|
||||
)
|
||||
const configProvider = configProviders.find(([id]) => id === providerID)
|
||||
if (configProvider) applyConfigProvider(...configProvider)
|
||||
const current = providers[providerID] ?? refreshProviders.get(providerID)
|
||||
if (!current) return
|
||||
const nextProvider = { ...current, models: database[providerID].models }
|
||||
finalizeProvider(providerID, nextProvider)
|
||||
if (Object.keys(nextProvider.models).length === 0) delete providers[providerID]
|
||||
else providers[providerID] = nextProvider
|
||||
for (const key of languages.keys()) {
|
||||
if (key.startsWith(`${providerID}/`)) languages.delete(key)
|
||||
}
|
||||
yield* events.publish(Event.ModelsUpdated, { providerID })
|
||||
}).pipe(
|
||||
Effect.catchCause((cause) => Effect.logWarning("Failed to refresh provider models", { providerID, cause })),
|
||||
),
|
||||
).pipe(Effect.forkScoped)
|
||||
|
||||
return result
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1928,6 +1960,7 @@ export const defaultLayer = Layer.suspend(() =>
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(ModelsDev.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1957,6 +1990,7 @@ export const node = LayerNode.make(layer, [
|
||||
Plugin.node,
|
||||
ModelsDev.node,
|
||||
RuntimeFlags.node,
|
||||
EventV2Bridge.node,
|
||||
])
|
||||
|
||||
export * as Provider from "./provider"
|
||||
|
||||
@@ -220,12 +220,7 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
|
||||
text: part.text,
|
||||
})
|
||||
// text/plain and directory files are converted into text parts, ignore them
|
||||
if (
|
||||
part.type === "file" &&
|
||||
part.mime !== "text/plain" &&
|
||||
part.mime !== "application/x-directory" &&
|
||||
part.source?.type !== "resource"
|
||||
) {
|
||||
if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory") {
|
||||
if (options?.stripMedia && isMedia(part.mime)) {
|
||||
userMessage.parts.push({
|
||||
type: "text",
|
||||
|
||||
@@ -19,7 +19,6 @@ import { Plugin } from "../plugin"
|
||||
import MAX_STEPS from "../session/prompt/max-steps.txt"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { MCP } from "../mcp"
|
||||
import { McpContent } from "@/mcp/content"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
import { ulid } from "ulid"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
@@ -730,14 +729,27 @@ export const layer = Layer.effect(
|
||||
if (Exit.isSuccess(exit)) {
|
||||
const content = exit.value
|
||||
if (!content) throw new Error(`Resource not found: ${clientName}/${uri}`)
|
||||
pieces.push(
|
||||
...McpContent.toParts(content.contents).map((item) => ({
|
||||
...item,
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
...(item.type === "text" ? { synthetic: true } : {}),
|
||||
})),
|
||||
)
|
||||
const items = Array.isArray(content.contents) ? content.contents : [content.contents]
|
||||
for (const c of items) {
|
||||
if ("text" in c && c.text) {
|
||||
pieces.push({
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: c.text,
|
||||
})
|
||||
} else if ("blob" in c && c.blob) {
|
||||
const mime = "mimeType" in c ? c.mimeType : part.mime
|
||||
pieces.push({
|
||||
messageID: info.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: `[Binary content: ${mime}]`,
|
||||
})
|
||||
}
|
||||
}
|
||||
pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID })
|
||||
} else {
|
||||
const error = Cause.squash(exit.cause)
|
||||
@@ -980,7 +992,7 @@ export const layer = Layer.effect(
|
||||
)
|
||||
|
||||
const parts = yield* Effect.forEach(resolvedParts, (part) =>
|
||||
part.type === "file" && part.source?.type !== "resource" && part.mime.startsWith("image/")
|
||||
part.type === "file" && part.mime.startsWith("image/")
|
||||
? image.normalize(part).pipe(
|
||||
Effect.catchIf(
|
||||
(error) => error instanceof Image.ResizerUnavailableError,
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import { MCP } from "@/mcp"
|
||||
import { McpContent } from "@/mcp/content"
|
||||
import { Effect, Schema } from "effect"
|
||||
import * as Tool from "./tool"
|
||||
|
||||
export const ListParameters = Schema.Struct({
|
||||
clientName: Schema.optional(Schema.String).annotate({
|
||||
description: "Only list resources from this MCP server",
|
||||
}),
|
||||
})
|
||||
|
||||
export const ReadParameters = Schema.Struct({
|
||||
clientName: Schema.String.annotate({
|
||||
description: "The MCP server that provides the resource",
|
||||
}),
|
||||
uri: Schema.String.annotate({
|
||||
description: "The resource URI to read",
|
||||
}),
|
||||
})
|
||||
|
||||
export const ListMcpResourcesTool = Tool.define(
|
||||
"list_mcp_resources",
|
||||
Effect.gen(function* () {
|
||||
const mcp = yield* MCP.Service
|
||||
|
||||
return {
|
||||
description:
|
||||
"List resources exposed by connected MCP servers. Resources are read-only context such as files, schemas, documents, images, and application data. Use read_mcp_resource with the returned clientName and uri to retrieve one.",
|
||||
parameters: ListParameters,
|
||||
execute: (params: Schema.Schema.Type<typeof ListParameters>) =>
|
||||
Effect.gen(function* () {
|
||||
const resources = Object.values(yield* mcp.resources())
|
||||
.filter((resource) => !params.clientName || resource.client === params.clientName)
|
||||
.toSorted(
|
||||
(a, b) => a.client.localeCompare(b.client) || a.name.localeCompare(b.name) || a.uri.localeCompare(b.uri),
|
||||
)
|
||||
.map((resource) => ({
|
||||
clientName: resource.client,
|
||||
name: resource.name,
|
||||
uri: resource.uri,
|
||||
...(resource.description ? { description: resource.description } : {}),
|
||||
...(resource.mimeType ? { mimeType: resource.mimeType } : {}),
|
||||
}))
|
||||
|
||||
return {
|
||||
title: "MCP resources",
|
||||
metadata: { count: resources.length },
|
||||
output: resources.length ? JSON.stringify({ resources }, null, 2) : "No MCP resources found.",
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
export const ReadMcpResourceTool = Tool.define(
|
||||
"read_mcp_resource",
|
||||
Effect.gen(function* () {
|
||||
const mcp = yield* MCP.Service
|
||||
|
||||
return {
|
||||
description:
|
||||
"Read a resource from a connected MCP server. Use list_mcp_resources to discover resources when needed. Text is returned directly, images are attached for inspection, and other binary data is described by MIME type and size.",
|
||||
parameters: ReadParameters,
|
||||
execute: (params: Schema.Schema.Type<typeof ReadParameters>, ctx: Tool.Context) =>
|
||||
Effect.gen(function* () {
|
||||
yield* ctx.ask({
|
||||
permission: "read_mcp_resource",
|
||||
patterns: [`${params.clientName}:${params.uri}`],
|
||||
always: [`${params.clientName}:${params.uri}`],
|
||||
metadata: { clientName: params.clientName, uri: params.uri },
|
||||
})
|
||||
|
||||
const result = yield* mcp.readResource(params.clientName, params.uri)
|
||||
if (!result) throw new Error(`Failed to read MCP resource ${params.uri} from ${params.clientName}`)
|
||||
|
||||
const parts = McpContent.toParts(result.contents)
|
||||
const output = parts
|
||||
.filter((part): part is Extract<McpContent.Part, { type: "text" }> => part.type === "text")
|
||||
.map((part) => part.text)
|
||||
.join("\n\n")
|
||||
const attachments = parts.filter(
|
||||
(part): part is Extract<McpContent.Part, { type: "file" }> => part.type === "file",
|
||||
)
|
||||
|
||||
return {
|
||||
title: `${params.clientName}: ${params.uri}`,
|
||||
metadata: { clientName: params.clientName, uri: params.uri },
|
||||
output: output || `Read MCP resource ${params.uri}`,
|
||||
...(attachments.length ? { attachments } : {}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -52,8 +52,6 @@ import { BackgroundJob } from "@/background/job"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { ListMcpResourcesTool, ReadMcpResourceTool } from "./mcp-resource"
|
||||
import { MCP } from "@/mcp"
|
||||
|
||||
export function webSearchEnabled(providerID: ProviderV2.ID, flags = { exa: false, parallel: false }) {
|
||||
return providerID === ProviderV2.ID.opencode || flags.exa || flags.parallel
|
||||
@@ -107,8 +105,6 @@ export const layer = Layer.effect(
|
||||
const greptool = yield* GrepTool
|
||||
const patchtool = yield* ApplyPatchTool
|
||||
const skilltool = yield* SkillTool
|
||||
const listMcpResources = yield* ListMcpResourcesTool
|
||||
const readMcpResource = yield* ReadMcpResourceTool
|
||||
const agent = yield* Agent.Service
|
||||
|
||||
const state = yield* InstanceState.make<State>(
|
||||
@@ -216,8 +212,6 @@ export const layer = Layer.effect(
|
||||
question: Tool.init(question),
|
||||
lsp: Tool.init(lsptool),
|
||||
plan: Tool.init(plan),
|
||||
listMcpResources: Tool.init(listMcpResources),
|
||||
readMcpResource: Tool.init(readMcpResource),
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -237,8 +231,6 @@ export const layer = Layer.effect(
|
||||
tool.search,
|
||||
tool.skill,
|
||||
tool.patch,
|
||||
tool.listMcpResources,
|
||||
tool.readMcpResource,
|
||||
...(flags.experimentalLspTool ? [tool.lsp] : []),
|
||||
...(flags.experimentalPlanMode && flags.client === "cli" ? [tool.plan] : []),
|
||||
],
|
||||
@@ -343,7 +335,6 @@ export const defaultLayer = Layer.suspend(() =>
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(Truncate.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer),
|
||||
)
|
||||
.pipe(Layer.provide(Database.defaultLayer), Layer.provide(RuntimeFlags.defaultLayer)),
|
||||
)
|
||||
@@ -444,7 +435,6 @@ export const node = LayerNode.make(layer.pipe(Layer.provide(Ripgrep.defaultLayer
|
||||
Truncate.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
MCP.node,
|
||||
])
|
||||
|
||||
export * as ToolRegistry from "./registry"
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { McpContent } from "@/mcp/content"
|
||||
|
||||
describe("mcp.content", () => {
|
||||
test("converts text, images, and other binary resources", () => {
|
||||
expect(
|
||||
McpContent.toParts([
|
||||
{ uri: "fixture://guide", mimeType: "text/plain", text: "hello" },
|
||||
{ uri: "fixture://picture", mimeType: "IMAGE/PNG; charset=binary", blob: "aGVsbG8=" },
|
||||
{ uri: "fixture://archive", mimeType: "application/zip", blob: "aGVsbG8=" },
|
||||
]),
|
||||
).toEqual([
|
||||
{ type: "text", text: "hello" },
|
||||
{
|
||||
type: "file",
|
||||
mime: "image/png",
|
||||
filename: "fixture://picture",
|
||||
url: "data:image/png;base64,aGVsbG8=",
|
||||
},
|
||||
{ type: "text", text: "[Binary resource: application/zip, 5 bytes]" },
|
||||
])
|
||||
})
|
||||
|
||||
test("does not treat SVG resources as model images", () => {
|
||||
expect(McpContent.toParts([{ uri: "fixture://vector", mimeType: "image/svg+xml", blob: "PHN2Zy8+" }])).toEqual([
|
||||
{ type: "text", text: "[Binary resource: image/svg+xml, 6 bytes]" },
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,28 @@
|
||||
import { expect } from "bun:test"
|
||||
import { Provider } from "../../src/provider/provider"
|
||||
|
||||
import { Effect } from "effect"
|
||||
import { Deferred, Effect } from "effect"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { GlobalBus, type GlobalEvent } from "@/bus/global"
|
||||
|
||||
const DIGITALOCEAN = ProviderV2.ID.make("digitalocean")
|
||||
const it = testEffect(Provider.defaultLayer)
|
||||
|
||||
const refreshedModels = Effect.fn("DigitalOceanTest.refreshedModels")(function* () {
|
||||
const provider = yield* Provider.Service
|
||||
const refreshed = yield* Deferred.make<void>()
|
||||
const listener = (event: GlobalEvent) => {
|
||||
if (event.payload.type !== "provider.models.updated" || event.payload.properties.providerID !== DIGITALOCEAN) return
|
||||
Deferred.doneUnsafe(refreshed, Effect.void)
|
||||
}
|
||||
GlobalBus.on("event", listener)
|
||||
yield* Effect.addFinalizer(() => Effect.sync(() => GlobalBus.off("event", listener)))
|
||||
yield* provider.list()
|
||||
yield* Deferred.await(refreshed)
|
||||
return (yield* provider.list())[DIGITALOCEAN].models
|
||||
})
|
||||
|
||||
const withEnv = <A, E, R>(values: Record<string, string>, effect: Effect.Effect<A, E, R>) =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.sync(() => {
|
||||
@@ -73,9 +88,7 @@ it.instance(
|
||||
oauth_expires: String(Date.now() + 60 * 60 * 1000),
|
||||
},
|
||||
Effect.gen(function* () {
|
||||
const provider = yield* Provider.Service
|
||||
const providers = yield* provider.list()
|
||||
const models = providers[DIGITALOCEAN].models
|
||||
const models = yield* refreshedModels()
|
||||
expect(models["router:my-router"]).toBeDefined()
|
||||
expect(models["router:my-router"].api.id).toBe("router:my-router")
|
||||
expect(models["router:my-router"].api.url).toBe("https://inference.do-ai.run/v1")
|
||||
@@ -97,9 +110,7 @@ it.instance(
|
||||
oauth_expires: "1",
|
||||
},
|
||||
Effect.gen(function* () {
|
||||
const provider = yield* Provider.Service
|
||||
const providers = yield* provider.list()
|
||||
const models = providers[DIGITALOCEAN].models
|
||||
const models = yield* refreshedModels()
|
||||
expect(models["router:stale-router"]).toBeDefined()
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -20,6 +20,7 @@ import { InstanceLayer } from "@/project/instance-layer"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
|
||||
const originalEnv = new Map<string, string | undefined>()
|
||||
|
||||
@@ -65,6 +66,7 @@ const providerLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(ModelsDev.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer(flags)),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
)
|
||||
|
||||
const list = Provider.use.list()
|
||||
|
||||
@@ -26,6 +26,7 @@ import { TestInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
|
||||
const FIXTURES_DIR = path.join(import.meta.dir, "../fixtures/recordings")
|
||||
|
||||
@@ -270,6 +271,7 @@ function recordedNativeLLMLayer(scenario: RecordedScenario) {
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(ModelsDev.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
)
|
||||
// Only the HTTP client is recorded; RequestExecutor and the opencode LLM stack remain real.
|
||||
const metadata = {
|
||||
|
||||
@@ -319,43 +319,6 @@ describe("session.message-v2.toModelMessage", () => {
|
||||
])
|
||||
})
|
||||
|
||||
test("does not forward MCP resource references as file downloads", async () => {
|
||||
const messageID = "m-user"
|
||||
const input: SessionV1.WithParts[] = [
|
||||
{
|
||||
info: userInfo(messageID),
|
||||
parts: [
|
||||
{
|
||||
...basePart(messageID, "p1"),
|
||||
type: "text",
|
||||
text: "# Resource contents",
|
||||
synthetic: true,
|
||||
},
|
||||
{
|
||||
...basePart(messageID, "p2"),
|
||||
type: "file",
|
||||
mime: "application/json",
|
||||
filename: "status",
|
||||
url: "status://info",
|
||||
source: {
|
||||
type: "resource",
|
||||
clientName: "resource-only-fixture",
|
||||
uri: "status://info",
|
||||
text: { value: "@status", start: 0, end: 7 },
|
||||
},
|
||||
},
|
||||
] as SessionV1.Part[],
|
||||
},
|
||||
]
|
||||
|
||||
expect(await MessageV2.toModelMessages(input, model)).toStrictEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "# Resource contents" }],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test("converts assistant tool completion into tool-call + tool-result messages with attachments", async () => {
|
||||
const userID = "m-user"
|
||||
const assistantID = "m-assistant"
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { MCP } from "@/mcp"
|
||||
import { MessageID, SessionID } from "@/session/schema"
|
||||
import { ListMcpResourcesTool, ReadMcpResourceTool } from "@/tool/mcp-resource"
|
||||
import { Tool } from "@/tool/tool"
|
||||
import { Truncate } from "@/tool/truncate"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const mcp = Layer.mock(MCP.Service, {
|
||||
resources: () =>
|
||||
Effect.succeed({
|
||||
"zeta:guide": {
|
||||
client: "zeta",
|
||||
name: "guide",
|
||||
uri: "fixture://guide",
|
||||
description: "Guide",
|
||||
mimeType: "text/plain",
|
||||
},
|
||||
"alpha:picture": {
|
||||
client: "alpha",
|
||||
name: "picture",
|
||||
uri: "fixture://picture",
|
||||
mimeType: "image/png",
|
||||
},
|
||||
}),
|
||||
readResource: (clientName, uri) =>
|
||||
Effect.succeed(
|
||||
clientName === "alpha" && uri === "fixture://picture"
|
||||
? {
|
||||
contents: [
|
||||
{ uri, mimeType: "text/plain", text: "caption" },
|
||||
{ uri, mimeType: "image/png", blob: "aGVsbG8=" },
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
),
|
||||
})
|
||||
|
||||
const it = testEffect(Layer.mergeAll(mcp, Truncate.defaultLayer, Agent.defaultLayer))
|
||||
|
||||
const baseCtx: Omit<Tool.Context, "ask"> = {
|
||||
sessionID: SessionID.make("ses_test"),
|
||||
messageID: MessageID.make("msg_test"),
|
||||
agent: "build",
|
||||
abort: AbortSignal.any([]),
|
||||
messages: [],
|
||||
metadata: () => Effect.void,
|
||||
}
|
||||
|
||||
describe("tool.mcp-resource", () => {
|
||||
it.instance("lists resources in a deterministic order", () =>
|
||||
Effect.gen(function* () {
|
||||
const info = yield* ListMcpResourcesTool
|
||||
const tool = yield* info.init()
|
||||
const result = yield* tool.execute({}, { ...baseCtx, ask: () => Effect.void })
|
||||
|
||||
expect(JSON.parse(result.output)).toEqual({
|
||||
resources: [
|
||||
{ clientName: "alpha", name: "picture", uri: "fixture://picture", mimeType: "image/png" },
|
||||
{
|
||||
clientName: "zeta",
|
||||
name: "guide",
|
||||
uri: "fixture://guide",
|
||||
description: "Guide",
|
||||
mimeType: "text/plain",
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("returns text and image content from a resource", () =>
|
||||
Effect.gen(function* () {
|
||||
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
|
||||
const info = yield* ReadMcpResourceTool
|
||||
const tool = yield* info.init()
|
||||
const result = yield* tool.execute(
|
||||
{ clientName: "alpha", uri: "fixture://picture" },
|
||||
{
|
||||
...baseCtx,
|
||||
ask: (request) =>
|
||||
Effect.sync(() => {
|
||||
requests.push(request)
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
||||
expect(result.output).toBe("caption")
|
||||
expect(result.attachments).toEqual([
|
||||
{
|
||||
type: "file",
|
||||
mime: "image/png",
|
||||
filename: "fixture://picture",
|
||||
url: "data:image/png;base64,aGVsbG8=",
|
||||
},
|
||||
])
|
||||
expect(requests[0]).toMatchObject({
|
||||
permission: "read_mcp_resource",
|
||||
patterns: ["alpha:fixture://picture"],
|
||||
always: ["alpha:fixture://picture"],
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -72,8 +72,6 @@ describe("tool.registry", () => {
|
||||
const ids = yield* registry.ids()
|
||||
|
||||
expect(ids).not.toContain("task_status")
|
||||
expect(ids).toContain("list_mcp_resources")
|
||||
expect(ids).toContain("read_mcp_resource")
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -77,6 +77,7 @@ export type Event =
|
||||
| EventTuiSessionSelect2
|
||||
| EventMcpToolsChanged
|
||||
| EventMcpBrowserOpenFailed
|
||||
| EventProviderModelsUpdated
|
||||
| EventCommandExecuted
|
||||
| EventProjectDirectoriesUpdated
|
||||
| EventProjectUpdated
|
||||
@@ -1495,6 +1496,13 @@ export type GlobalEvent = {
|
||||
url: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "provider.models.updated"
|
||||
properties: {
|
||||
providerID: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "command.executed"
|
||||
@@ -1875,7 +1883,6 @@ export type McpLocalConfig = {
|
||||
* Command and arguments to run the MCP server
|
||||
*/
|
||||
command: Array<string>
|
||||
cwd?: string
|
||||
environment?: {
|
||||
[key: string]: string
|
||||
}
|
||||
@@ -5139,6 +5146,14 @@ export type EventMcpBrowserOpenFailed = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventProviderModelsUpdated = {
|
||||
id: string
|
||||
type: "provider.models.updated"
|
||||
properties: {
|
||||
providerID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventCommandExecuted = {
|
||||
id: string
|
||||
type: "command.executed"
|
||||
|
||||
@@ -19296,9 +19296,6 @@
|
||||
},
|
||||
"description": "Command and arguments to run the MCP server"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"environment": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -540,7 +540,7 @@ export function Prompt(props: PromptProps) {
|
||||
},
|
||||
{
|
||||
title: "Move session",
|
||||
desc: "Move to another project dir",
|
||||
desc: "Move the session to another project directory",
|
||||
name: "session.move",
|
||||
category: "Session",
|
||||
slashName: "move",
|
||||
|
||||
@@ -159,11 +159,26 @@ export const {
|
||||
.then((x) => (x.data ?? []).toSorted((a, b) => a.id.localeCompare(b.id)))
|
||||
}
|
||||
|
||||
async function refreshProviders(workspace: string | undefined) {
|
||||
const [providers, providerList] = await Promise.all([
|
||||
sdk.client.config.providers({ workspace }, { throwOnError: true }).then((x) => x.data),
|
||||
sdk.client.provider.list({ workspace }, { throwOnError: true }).then((x) => x.data),
|
||||
])
|
||||
batch(() => {
|
||||
setStore("provider", reconcile(providers.providers))
|
||||
setStore("provider_default", reconcile(providers.default))
|
||||
setStore("provider_next", reconcile(providerList))
|
||||
})
|
||||
}
|
||||
|
||||
event.subscribe((event, { workspace }) => {
|
||||
switch (event.type) {
|
||||
case "server.instance.disposed":
|
||||
void bootstrap()
|
||||
break
|
||||
case "provider.models.updated":
|
||||
void refreshProviders(workspace)
|
||||
break
|
||||
case "permission.replied": {
|
||||
const requests = store.permission[event.properties.sessionID]
|
||||
if (!requests) break
|
||||
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
/* biome-ignore-all lint: auto-generated */
|
||||
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.17.4",
|
||||
"version": "1.17.3",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user