mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-17 12:56:41 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67223eaa53 | |||
| f9ba23ab62 | |||
| b258a55a6c | |||
| 2725eed992 | |||
| a9c115c220 | |||
| 917a36abc3 |
@@ -255,7 +255,7 @@
|
||||
"@ai-sdk/provider-utils": "4.0.23",
|
||||
"@ai-sdk/togetherai": "2.0.41",
|
||||
"@ai-sdk/vercel": "2.0.39",
|
||||
"@ai-sdk/xai": "3.0.82",
|
||||
"@ai-sdk/xai": "3.0.92",
|
||||
"@aws-sdk/credential-providers": "3.993.0",
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -482,7 +482,7 @@
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@ai-sdk/togetherai": "2.0.41",
|
||||
"@ai-sdk/vercel": "2.0.39",
|
||||
"@ai-sdk/xai": "3.0.82",
|
||||
"@ai-sdk/xai": "3.0.92",
|
||||
"@aws-sdk/credential-providers": "3.993.0",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
@@ -837,9 +837,9 @@
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
||||
"@ai-sdk/xai@3.0.92": "patches/@ai-sdk%2Fxai@3.0.92.patch",
|
||||
"virtua@0.49.1": "patches/virtua@0.49.1.patch",
|
||||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
||||
"@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch",
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch",
|
||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||
@@ -976,7 +976,7 @@
|
||||
|
||||
"@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="],
|
||||
|
||||
"@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="],
|
||||
"@ai-sdk/xai": ["@ai-sdk/xai@3.0.92", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yqRMdEVfSkZCXs5mqeKfYOuQElo93igwz/4zWbYkhZWAW7tDh+5uqRef6MtQVYF/bTC1EsdbeO9DtsOtAZMOLA=="],
|
||||
|
||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||
|
||||
@@ -5300,7 +5300,11 @@
|
||||
|
||||
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
"@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="],
|
||||
|
||||
"@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
|
||||
"@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||
|
||||
"@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
@@ -6246,6 +6250,10 @@
|
||||
|
||||
"@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@ai-sdk/xai/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
|
||||
|
||||
"@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
||||
"virtua@0.49.1": "patches/virtua@0.49.1.patch",
|
||||
"@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch",
|
||||
"@ai-sdk/xai@3.0.92": "patches/@ai-sdk%2Fxai@3.0.92.patch",
|
||||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
import { fixture, pageMessages } from "../smoke/session-timeline.fixture"
|
||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||
|
||||
test("shows loaded sessions before the directory path request resolves", async ({ page }) => {
|
||||
await mockOpenCodeServer(page, {
|
||||
sessions: fixture.sessions,
|
||||
provider: fixture.provider,
|
||||
directory: fixture.directory,
|
||||
project: fixture.project,
|
||||
pageMessages,
|
||||
})
|
||||
|
||||
let releasePath!: () => void
|
||||
const pathBlocked = new Promise<void>((resolve) => {
|
||||
releasePath = resolve
|
||||
})
|
||||
await page.route("**/path?*", async (route) => {
|
||||
if (!new URL(route.request().url()).searchParams.has("directory")) return route.fallback()
|
||||
await pathBlocked
|
||||
return route.fallback()
|
||||
})
|
||||
|
||||
await page.addInitScript((directory) => {
|
||||
localStorage.setItem(
|
||||
"opencode.global.dat:server",
|
||||
JSON.stringify({
|
||||
projects: { local: [{ worktree: directory, expanded: true }] },
|
||||
lastProject: { local: directory },
|
||||
}),
|
||||
)
|
||||
}, fixture.directory)
|
||||
|
||||
await page.goto("/")
|
||||
try {
|
||||
await expect(page.getByText(fixture.expected.sourceTitle).first()).toBeVisible({ timeout: 5_000 })
|
||||
} finally {
|
||||
releasePath()
|
||||
}
|
||||
})
|
||||
@@ -52,7 +52,7 @@ beforeAll(async () => {
|
||||
useQueries: (options: () => { queries: Array<{ enabled?: boolean }> }) => {
|
||||
queryGroups.push(options)
|
||||
return [
|
||||
{ isLoading: false, data: { state: "", config: "", worktree: "", directory: "", home: "" } },
|
||||
{ isLoading: true, data: undefined },
|
||||
{ isLoading: false, data: {} },
|
||||
{ isLoading: false, data: [] },
|
||||
{ isLoading: false, data: provider },
|
||||
@@ -128,6 +128,35 @@ describe("createChildStoreManager", () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("provides the requested directory while the path query is pending", () => {
|
||||
let manager: ReturnType<typeof createChildStoreManager> | undefined
|
||||
|
||||
const dispose = createOwner((owner) => {
|
||||
manager = createChildStoreManager({
|
||||
owner,
|
||||
isBooting: () => false,
|
||||
isLoadingSessions: () => false,
|
||||
onBootstrap() {},
|
||||
onMcp() {},
|
||||
onDispose() {},
|
||||
translate: (key) => key,
|
||||
queryOptions: queryOptionsApi,
|
||||
global: { provider },
|
||||
})
|
||||
})
|
||||
|
||||
try {
|
||||
if (!manager) throw new Error("manager required")
|
||||
|
||||
const [store] = manager.child("/project", { bootstrap: false })
|
||||
|
||||
expect(store.path.directory).toBe("/project")
|
||||
expect(store.path.worktree).toBe("")
|
||||
} finally {
|
||||
dispose()
|
||||
}
|
||||
})
|
||||
|
||||
test("enables MCP only when requested for the directory", () => {
|
||||
let manager: ReturnType<typeof createChildStoreManager> | undefined
|
||||
const offset = queryGroups.length
|
||||
|
||||
@@ -204,9 +204,8 @@ export function createChildStoreManager(input: {
|
||||
},
|
||||
config: {},
|
||||
get path() {
|
||||
if (pathQuery.isLoading || !pathQuery.data)
|
||||
return { state: "", config: "", worktree: "", directory: "", home: "" }
|
||||
return pathQuery.data
|
||||
if (pathQuery.data) return pathQuery.data
|
||||
return { state: "", config: "", worktree: "", directory, home: "" }
|
||||
},
|
||||
status: "loading" as const,
|
||||
agent: [],
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `session` ADD `metadata` text;
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE `session` ADD `metadata` text;
|
||||
@@ -54,7 +54,7 @@
|
||||
"@ai-sdk/provider-utils": "4.0.23",
|
||||
"@ai-sdk/togetherai": "2.0.41",
|
||||
"@ai-sdk/vercel": "2.0.39",
|
||||
"@ai-sdk/xai": "3.0.82",
|
||||
"@ai-sdk/xai": "3.0.92",
|
||||
"@aws-sdk/credential-providers": "3.993.0",
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
"@effect/platform-node": "catalog:",
|
||||
|
||||
@@ -22,6 +22,6 @@ export const migrations = (
|
||||
import("./migration/20260507164347_add_workspace_time"),
|
||||
import("./migration/20260510033149_session_usage"),
|
||||
import("./migration/20260511000411_data_migration_state"),
|
||||
import("./migration/20260530232709_lovely_romulus"),
|
||||
import("./migration/20260511173437_session-metadata"),
|
||||
])
|
||||
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Effect } from "effect"
|
||||
import type { DatabaseMigration } from "../migration"
|
||||
|
||||
export default {
|
||||
id: "20260511173437_session-metadata",
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
// This column briefly shipped again under 20260530232709_lovely_romulus.
|
||||
if (
|
||||
(yield* tx.all<{ name: string }>(`PRAGMA table_info(\`session\`)`)).some((column) => column.name === "metadata")
|
||||
)
|
||||
return
|
||||
yield* tx.run(`ALTER TABLE \`session\` ADD \`metadata\` text;`)
|
||||
})
|
||||
},
|
||||
} satisfies DatabaseMigration.Migration
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Effect } from "effect"
|
||||
import type { DatabaseMigration } from "../migration"
|
||||
|
||||
export default {
|
||||
id: "20260530232709_lovely_romulus",
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
yield* tx.run(`ALTER TABLE \`session\` ADD \`metadata\` text;`)
|
||||
})
|
||||
},
|
||||
} satisfies DatabaseMigration.Migration
|
||||
@@ -7,6 +7,7 @@ import { Effect } from "effect"
|
||||
import { sql } from "drizzle-orm"
|
||||
import { DatabaseMigration } from "@opencode-ai/core/database/migration"
|
||||
import sessionUsageMigration from "@opencode-ai/core/database/migration/20260510033149_session_usage"
|
||||
import sessionMetadataMigration from "@opencode-ai/core/database/migration/20260511173437_session-metadata"
|
||||
import type { SqlClient as SqlClientService } from "effect/unstable/sql/SqlClient"
|
||||
|
||||
const run = <A, E>(effect: Effect.Effect<A, E, SqlClientService>) =>
|
||||
@@ -89,6 +90,44 @@ describe("DatabaseMigration", () => {
|
||||
)
|
||||
})
|
||||
|
||||
test("does not replay a migrated session metadata column", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY, metadata text)`)
|
||||
yield* db.run(
|
||||
sql`CREATE TABLE __drizzle_migrations (id INTEGER PRIMARY KEY, hash text NOT NULL, created_at numeric, name text, applied_at TEXT)`,
|
||||
)
|
||||
yield* db.run(sql`
|
||||
INSERT INTO __drizzle_migrations (hash, created_at, name, applied_at)
|
||||
VALUES ('hash', 1, '20260511173437_session-metadata', ${new Date().toISOString()})
|
||||
`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [sessionMetadataMigration])
|
||||
|
||||
expect(yield* db.all(sql`SELECT id FROM migration`)).toEqual([{ id: "20260511173437_session-metadata" }])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("accepts the temporary replacement session metadata migration id", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY, metadata text)`)
|
||||
yield* db.run(sql`CREATE TABLE migration (id TEXT PRIMARY KEY, time_completed INTEGER NOT NULL)`)
|
||||
yield* db.run(sql`INSERT INTO migration (id, time_completed) VALUES ('20260530232709_lovely_romulus', 1)`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [sessionMetadataMigration])
|
||||
|
||||
expect(yield* db.all(sql`SELECT id FROM migration ORDER BY id`)).toEqual([
|
||||
{ id: "20260511173437_session-metadata" },
|
||||
{ id: "20260530232709_lovely_romulus" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("skips drizzle import when migration table already has state", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@ai-sdk/togetherai": "2.0.41",
|
||||
"@ai-sdk/vercel": "2.0.39",
|
||||
"@ai-sdk/xai": "3.0.82",
|
||||
"@ai-sdk/xai": "3.0.92",
|
||||
"@aws-sdk/credential-providers": "3.993.0",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
|
||||
@@ -3,13 +3,13 @@ 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 * as Log from "@opencode-ai/core/util/log"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { Hash } from "@opencode-ai/core/util/hash"
|
||||
import { Plugin } from "../plugin"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { type LanguageModelV3 } from "@ai-sdk/provider"
|
||||
import * as ModelsDev from "@opencode-ai/core/models-dev"
|
||||
import { ModelsDev } from "@opencode-ai/core/models-dev"
|
||||
import { Auth } from "../auth"
|
||||
import { Env } from "../env"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
@@ -24,7 +24,7 @@ import { EffectPromise } from "@/effect/promise"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { isRecord } from "@/util/record"
|
||||
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||
import * as ProviderTransform from "./transform"
|
||||
import { ProviderTransform } from "./transform"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelStatus } from "./model-status"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
@@ -32,11 +32,6 @@ import { ProviderError } from "./error"
|
||||
|
||||
const log = Log.create({ service: "provider" })
|
||||
const OPENAI_HEADER_TIMEOUT_DEFAULT = 10_000
|
||||
function shouldUseCopilotResponsesApi(modelID: string): boolean {
|
||||
const match = /^gpt-(\d+)/.exec(modelID)
|
||||
if (!match) return false
|
||||
return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
|
||||
}
|
||||
|
||||
function wrapSSE(res: Response, ms: number, ctl: AbortController) {
|
||||
if (typeof ms !== "number" || ms <= 0) return res
|
||||
@@ -152,10 +147,6 @@ type CustomDep = {
|
||||
get: (key: string) => Effect.Effect<string | undefined>
|
||||
}
|
||||
|
||||
function useLanguageModel(sdk: any) {
|
||||
return sdk.responses === undefined && sdk.chat === undefined
|
||||
}
|
||||
|
||||
function selectAzureLanguageModel(sdk: any, modelID: string, useChat: boolean) {
|
||||
if (useChat && sdk.chat) return sdk.chat(modelID)
|
||||
if (sdk.responses) return sdk.responses(modelID)
|
||||
@@ -218,8 +209,10 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
Effect.succeed({
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||
if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
|
||||
return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID)
|
||||
if (sdk.responses === undefined && sdk.chat === undefined) return sdk.languageModel(modelID)
|
||||
const match = /^gpt-(\d+)/.exec(modelID)
|
||||
if (match && Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")) return sdk.responses(modelID)
|
||||
return sdk.chat(modelID)
|
||||
},
|
||||
options: {},
|
||||
}),
|
||||
@@ -582,11 +575,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
const instanceUrl = (yield* dep.get("GITLAB_INSTANCE_URL")) || "https://gitlab.com"
|
||||
|
||||
const auth = yield* dep.auth(input.id)
|
||||
const apiKey = yield* Effect.sync(() => {
|
||||
if (auth?.type === "oauth") return auth.access
|
||||
if (auth?.type === "api") return auth.key
|
||||
return undefined
|
||||
})
|
||||
const apiKey = auth?.type === "oauth" ? auth.access : auth?.type === "api" ? auth.key : undefined
|
||||
const token = apiKey ?? (yield* dep.get("GITLAB_TOKEN"))
|
||||
|
||||
const providerConfig = (yield* dep.config()).provider?.["gitlab"]
|
||||
@@ -734,12 +723,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
},
|
||||
}
|
||||
|
||||
const apiKey = yield* Effect.gen(function* () {
|
||||
const envToken = env["CLOUDFLARE_API_KEY"]
|
||||
if (envToken) return envToken
|
||||
if (auth?.type === "api") return auth.key
|
||||
return undefined
|
||||
})
|
||||
const apiKey = env["CLOUDFLARE_API_KEY"] || (auth?.type === "api" ? auth.key : undefined)
|
||||
|
||||
return {
|
||||
autoload: !!apiKey,
|
||||
@@ -785,12 +769,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
}
|
||||
|
||||
// Get API token from env or auth - required for authenticated gateways
|
||||
const apiToken = yield* Effect.gen(function* () {
|
||||
const envToken = env["CLOUDFLARE_API_TOKEN"] || env["CF_AIG_TOKEN"]
|
||||
if (envToken) return envToken
|
||||
if (auth?.type === "api") return auth.key
|
||||
return undefined
|
||||
})
|
||||
const apiToken =
|
||||
env["CLOUDFLARE_API_TOKEN"] || env["CF_AIG_TOKEN"] || (auth?.type === "api" ? auth.key : undefined)
|
||||
|
||||
if (!apiToken) {
|
||||
throw new Error(
|
||||
@@ -1171,18 +1151,15 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
||||
}
|
||||
}
|
||||
|
||||
function suggestionModelIDs(provider: Info | undefined, enableExperimentalModels: boolean) {
|
||||
if (!provider) return []
|
||||
return Object.keys(provider.models).filter((id) => {
|
||||
const model = provider.models[id]
|
||||
if (model.status === "deprecated") return false
|
||||
if (model.status === "alpha" && !enableExperimentalModels) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
function modelSuggestions(provider: Info | undefined, modelID: ProviderV2.ModelID, enableExperimentalModels: boolean) {
|
||||
const available = suggestionModelIDs(provider, enableExperimentalModels)
|
||||
const available = provider
|
||||
? Object.keys(provider.models).filter((id) => {
|
||||
const model = provider.models[id]
|
||||
if (model.status === "deprecated") return false
|
||||
if (model.status === "alpha" && !enableExperimentalModels) return false
|
||||
return true
|
||||
})
|
||||
: []
|
||||
const fuzzy = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 }).map((m) => m.target)
|
||||
if (fuzzy.length) return fuzzy
|
||||
const query = modelID
|
||||
@@ -1681,15 +1658,15 @@ export const layer = Layer.effect(
|
||||
return loaded as SDK
|
||||
}
|
||||
|
||||
let installedPath: string
|
||||
if (!model.api.npm.startsWith("file://")) {
|
||||
const installedPath = await (async () => {
|
||||
if (model.api.npm.startsWith("file://")) {
|
||||
log.info("loading local provider", { pkg: model.api.npm })
|
||||
return model.api.npm
|
||||
}
|
||||
const item = await Npm.add(model.api.npm)
|
||||
if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
|
||||
installedPath = item.entrypoint
|
||||
} else {
|
||||
log.info("loading local provider", { pkg: model.api.npm })
|
||||
installedPath = model.api.npm
|
||||
}
|
||||
return item.entrypoint
|
||||
})()
|
||||
|
||||
// `installedPath` is a local entry path or an existing `file://` URL. Normalize
|
||||
// only path inputs so Node on Windows accepts the dynamic import.
|
||||
@@ -1788,7 +1765,7 @@ export const layer = Layer.effect(
|
||||
const provider = s.providers[providerID]
|
||||
if (!provider) return undefined
|
||||
|
||||
let priority = [
|
||||
const defaultPriority = [
|
||||
"claude-haiku-4-5",
|
||||
"claude-haiku-4.5",
|
||||
"3-5-haiku",
|
||||
@@ -1797,12 +1774,11 @@ export const layer = Layer.effect(
|
||||
"gemini-2.5-flash",
|
||||
"gpt-5-nano",
|
||||
]
|
||||
if (providerID.startsWith("opencode")) {
|
||||
priority = ["gpt-5-nano"]
|
||||
}
|
||||
if (providerID.startsWith("github-copilot")) {
|
||||
priority = ["gpt-5-mini", "claude-haiku-4.5", ...priority]
|
||||
}
|
||||
const priority = providerID.startsWith("opencode")
|
||||
? ["gpt-5-nano"]
|
||||
: providerID.startsWith("github-copilot")
|
||||
? ["gpt-5-mini", "claude-haiku-4.5", ...defaultPriority]
|
||||
: defaultPriority
|
||||
for (const item of priority) {
|
||||
if (providerID === ProviderV2.ID.amazonBedrock) {
|
||||
const crossRegionPrefixes = ["global.", "us.", "eu."]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import * as Session from "./session"
|
||||
import { Session } from "./session"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { Token } from "@/util/token"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { SessionProcessor } from "./processor"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { Plugin } from "@/plugin"
|
||||
|
||||
@@ -12,12 +12,6 @@ import { Global } from "@opencode-ai/core/global"
|
||||
import type { MessageV2 } from "./message-v2"
|
||||
import type { MessageID } from "./schema"
|
||||
|
||||
const files = (disableClaudeCodePrompt: boolean) => [
|
||||
"AGENTS.md",
|
||||
...(disableClaudeCodePrompt ? [] : ["CLAUDE.md"]),
|
||||
"CONTEXT.md", // deprecated
|
||||
]
|
||||
|
||||
function extract(messages: SessionLegacy.WithParts[]) {
|
||||
const paths = new Set<string>()
|
||||
for (const msg of messages) {
|
||||
@@ -65,7 +59,11 @@ export const layer: Layer.Layer<
|
||||
path.join(global.config, "AGENTS.md"),
|
||||
...(!flags.disableClaudeCodePrompt ? [path.join(global.home, ".claude", "CLAUDE.md")] : []),
|
||||
]
|
||||
const instructionFiles = files(flags.disableClaudeCodePrompt)
|
||||
const instructionFiles = [
|
||||
"AGENTS.md",
|
||||
...(!flags.disableClaudeCodePrompt ? ["CLAUDE.md"] : []),
|
||||
"CONTEXT.md", // deprecated
|
||||
]
|
||||
|
||||
const state = yield* InstanceState.make(
|
||||
Effect.fn("Instruction.state")(() =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { streamText, wrapLanguageModel, type ModelMessage, type Tool } from "ai"
|
||||
|
||||
@@ -30,7 +30,7 @@ import { inArray } from "drizzle-orm"
|
||||
import { lt } from "drizzle-orm"
|
||||
import { or } from "drizzle-orm"
|
||||
import { MessageTable, PartTable, SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import * as ProviderError from "@/provider/error"
|
||||
import { ProviderError } from "@/provider/error"
|
||||
import { iife } from "@/util/iife"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { isMedia } from "@/util/media"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Config } from "@/config/config"
|
||||
import { Permission } from "@/permission"
|
||||
import { Plugin } from "@/plugin"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import * as Session from "./session"
|
||||
import { Session } from "./session"
|
||||
import { LLM } from "./llm"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { isOverflow } from "./overflow"
|
||||
@@ -19,7 +19,7 @@ import { SessionSummary } from "./summary"
|
||||
import type { Provider } from "@/provider/provider"
|
||||
import { Question } from "@/question"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { isRecord } from "@/util/record"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
@@ -301,8 +301,6 @@ export const layer = Layer.effect(
|
||||
}
|
||||
}
|
||||
|
||||
const toolInput = (value: unknown): Record<string, any> => (isRecord(value) ? value : { value })
|
||||
|
||||
const handleEvent = Effect.fnUntraced(function* (value: StreamEvent) {
|
||||
switch (value.type) {
|
||||
case "reasoning-start":
|
||||
@@ -380,7 +378,7 @@ export const layer = Layer.effect(
|
||||
throw new Error(`Tool call not allowed while generating summary: ${value.name}`)
|
||||
}
|
||||
const toolCall = yield* ensureToolCall(value)
|
||||
const input = toolInput(value.input)
|
||||
const input = isRecord(value.input) ? value.input : { value: value.input }
|
||||
if (!toolCall.call.inputEnded) {
|
||||
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
|
||||
if (flags.experimentalEventSystem) {
|
||||
|
||||
@@ -3,9 +3,9 @@ import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import os from "os"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { SessionRevert } from "./revert"
|
||||
import * as Session from "./session"
|
||||
import { Session } from "./session"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { Provider } from "@/provider/provider"
|
||||
|
||||
@@ -148,10 +148,6 @@ export const layer = Layer.effect(
|
||||
const parts: Types.DeepMutable<PromptInput["parts"]> = [{ type: "text", text: template }]
|
||||
const files = ConfigMarkdown.files(template)
|
||||
const seen = new Set<string>()
|
||||
const mentionSource = (match: RegExpMatchArray) => {
|
||||
const start = match.index ?? 0
|
||||
return { value: match[0], start, end: start + match[0].length }
|
||||
}
|
||||
yield* Effect.forEach(
|
||||
files,
|
||||
Effect.fnUntraced(function* (match) {
|
||||
@@ -164,7 +160,8 @@ export const layer = Layer.effect(
|
||||
const alias = slash === -1 ? name : name.slice(0, slash)
|
||||
const reference = yield* references.get(alias)
|
||||
if (reference) {
|
||||
const source = mentionSource(match)
|
||||
const start = match.index ?? 0
|
||||
const source = { value: match[0], start, end: start + match[0].length }
|
||||
if (reference.kind === "invalid") {
|
||||
parts.push(
|
||||
referenceTextPart({ reference, source, target: slash === -1 ? undefined : name.slice(slash + 1) }),
|
||||
|
||||
@@ -7,7 +7,7 @@ import { InstanceState } from "@/effect/instance-state"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { PartID } from "./schema"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import * as Session from "./session"
|
||||
import { Session } from "./session"
|
||||
import PROMPT_PLAN from "./prompt/plan.txt"
|
||||
import BUILD_SWITCH from "./prompt/build-switch.txt"
|
||||
import PLAN_MODE from "./prompt/plan-mode.txt"
|
||||
|
||||
@@ -3,8 +3,8 @@ import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Snapshot } from "../snapshot"
|
||||
import { Storage } from "@/storage/storage"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import * as Session from "./session"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { Session } from "./session"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { SessionRunState } from "./run-state"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { Runner } from "@/effect/runner"
|
||||
import { BackgroundJob } from "@/background/job"
|
||||
import { Effect, Latch, Layer, Scope, Context } from "effect"
|
||||
import * as Session from "./session"
|
||||
import { Session } from "./session"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { SessionID } from "./schema"
|
||||
import { SessionStatus } from "./status"
|
||||
|
||||
@@ -25,7 +25,7 @@ import { or } from "drizzle-orm"
|
||||
import type { SQL } from "drizzle-orm"
|
||||
import { PartTable, SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import type { InstanceContext } from "../project/instance-context"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
@@ -48,10 +48,6 @@ const runtime = makeRuntime(Database.Service, Database.defaultLayer)
|
||||
const parentTitlePrefix = "New session - "
|
||||
const childTitlePrefix = "Child session - "
|
||||
|
||||
function createDefaultTitle(isChild = false) {
|
||||
return (isChild ? childTitlePrefix : parentTitlePrefix) + new Date().toISOString()
|
||||
}
|
||||
|
||||
export function isDefaultTitle(title: string) {
|
||||
return new RegExp(
|
||||
`^(${parentTitlePrefix}|${childTitlePrefix})\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$`,
|
||||
@@ -581,7 +577,7 @@ export const layer: Layer.Layer<
|
||||
path: input.path,
|
||||
workspaceID: input.workspaceID,
|
||||
parentID: input.parentID,
|
||||
title: input.title ?? createDefaultTitle(!!input.parentID),
|
||||
title: input.title ?? (input.parentID ? childTitlePrefix : parentTitlePrefix) + new Date().toISOString(),
|
||||
agent: input.agent,
|
||||
model: input.model,
|
||||
metadata: input.metadata,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { SessionLegacy } from "@opencode-ai/core/session/legacy"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import * as Session from "./session"
|
||||
import { Session } from "./session"
|
||||
import { SessionID, MessageID } from "./schema"
|
||||
import { Config } from "@/config/config"
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ import type { TaskPromptOps } from "@/tool/task"
|
||||
import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai"
|
||||
import { Effect } from "effect"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import * as Session from "./session"
|
||||
import { Session } from "./session"
|
||||
import { SessionProcessor } from "./processor"
|
||||
import { PartID } from "./schema"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Log } from "@opencode-ai/core/util/log"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { createXai } from "@ai-sdk/xai"
|
||||
|
||||
describe("@ai-sdk/xai", () => {
|
||||
test("sends inline PDF attachments through the Responses API", async () => {
|
||||
let input: unknown
|
||||
const handle = async (_url: Parameters<typeof fetch>[0], options?: Parameters<typeof fetch>[1]) => {
|
||||
input = JSON.parse(String(options?.body)).input
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
object: "response",
|
||||
output: [],
|
||||
status: "completed",
|
||||
usage: { input_tokens: 0, output_tokens: 0 },
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
)
|
||||
}
|
||||
const xai = createXai({
|
||||
apiKey: "test",
|
||||
fetch: Object.assign(handle, { preconnect: fetch.preconnect.bind(fetch) }),
|
||||
})
|
||||
|
||||
await xai.responses("grok-4").doGenerate({
|
||||
prompt: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "file",
|
||||
mediaType: "application/pdf",
|
||||
filename: "sample.pdf",
|
||||
data: new Uint8Array([0x25, 0x50, 0x44, 0x46]),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(input).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "input_file",
|
||||
filename: "sample.pdf",
|
||||
file_data: "data:application/pdf;base64,JVBERg==",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,99 +0,0 @@
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 135b95946139bbd1fc4b62239032931586189da0..7913520ad2f1c26b0f9621e7654f5fb570cba926 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -1077,6 +1077,20 @@ async function convertToXaiResponsesInput({
|
||||
const mediaType = block.mediaType === "image/*" ? "image/jpeg" : block.mediaType;
|
||||
const imageUrl = block.data instanceof URL ? block.data.toString() : `data:${mediaType};base64,${(0, import_provider_utils5.convertToBase64)(block.data)}`;
|
||||
contentParts.push({ type: "input_image", image_url: imageUrl });
|
||||
+ } else if (block.mediaType === "application/pdf") {
|
||||
+ if (block.data instanceof URL) {
|
||||
+ contentParts.push({ type: "input_file", file_url: block.data.toString() });
|
||||
+ } else {
|
||||
+ contentParts.push({
|
||||
+ type: "input_file",
|
||||
+ ...(typeof block.data === "string" && block.data.startsWith("file-")
|
||||
+ ? { file_id: block.data }
|
||||
+ : {
|
||||
+ filename: block.filename ?? "file",
|
||||
+ file_data: `data:application/pdf;base64,${(0, import_provider_utils5.convertToBase64)(block.data)}`,
|
||||
+ }),
|
||||
+ });
|
||||
+ }
|
||||
} else {
|
||||
throw new import_provider4.UnsupportedFunctionalityError({
|
||||
functionality: `file part media type ${block.mediaType}`
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index 61be60d452682b94dcdda39ffc47cb994eb8bfb1..d928f7f46e91057cd97fade9cc238db611345bcf 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -1080,6 +1080,20 @@ async function convertToXaiResponsesInput({
|
||||
const mediaType = block.mediaType === "image/*" ? "image/jpeg" : block.mediaType;
|
||||
const imageUrl = block.data instanceof URL ? block.data.toString() : `data:${mediaType};base64,${convertToBase642(block.data)}`;
|
||||
contentParts.push({ type: "input_image", image_url: imageUrl });
|
||||
+ } else if (block.mediaType === "application/pdf") {
|
||||
+ if (block.data instanceof URL) {
|
||||
+ contentParts.push({ type: "input_file", file_url: block.data.toString() });
|
||||
+ } else {
|
||||
+ contentParts.push({
|
||||
+ type: "input_file",
|
||||
+ ...(typeof block.data === "string" && block.data.startsWith("file-")
|
||||
+ ? { file_id: block.data }
|
||||
+ : {
|
||||
+ filename: block.filename ?? "file",
|
||||
+ file_data: `data:application/pdf;base64,${convertToBase642(block.data)}`,
|
||||
+ }),
|
||||
+ });
|
||||
+ }
|
||||
} else {
|
||||
throw new UnsupportedFunctionalityError3({
|
||||
functionality: `file part media type ${block.mediaType}`
|
||||
diff --git a/src/responses/convert-to-xai-responses-input.ts b/src/responses/convert-to-xai-responses-input.ts
|
||||
index 19958d9fd90f7ce61bca70ad2d5f7b89a59fa63b..9329a1d56210af8aa33e5dbcb2916e24847070c1 100644
|
||||
--- a/src/responses/convert-to-xai-responses-input.ts
|
||||
+++ b/src/responses/convert-to-xai-responses-input.ts
|
||||
@@ -54,6 +54,24 @@ export async function convertToXaiResponsesInput({
|
||||
: `data:${mediaType};base64,${convertToBase64(block.data)}`;
|
||||
|
||||
contentParts.push({ type: 'input_image', image_url: imageUrl });
|
||||
+ } else if (block.mediaType === 'application/pdf') {
|
||||
+ if (block.data instanceof URL) {
|
||||
+ contentParts.push({
|
||||
+ type: 'input_file',
|
||||
+ file_url: block.data.toString(),
|
||||
+ });
|
||||
+ } else {
|
||||
+ contentParts.push({
|
||||
+ type: 'input_file',
|
||||
+ ...(typeof block.data === 'string' &&
|
||||
+ block.data.startsWith('file-')
|
||||
+ ? { file_id: block.data }
|
||||
+ : {
|
||||
+ filename: block.filename ?? 'file',
|
||||
+ file_data: `data:application/pdf;base64,${convertToBase64(block.data)}`,
|
||||
+ }),
|
||||
+ });
|
||||
+ }
|
||||
} else {
|
||||
throw new UnsupportedFunctionalityError({
|
||||
functionality: `file part media type ${block.mediaType}`,
|
||||
diff --git a/src/responses/xai-responses-api.ts b/src/responses/xai-responses-api.ts
|
||||
index df24c42d29fe7fc1dd7649cc2c4712a68c3a536b..00195468a83d43c1bfb50854ea040b55ea352433 100644
|
||||
--- a/src/responses/xai-responses-api.ts
|
||||
+++ b/src/responses/xai-responses-api.ts
|
||||
@@ -26,7 +26,14 @@ export type XaiResponsesSystemMessage = {
|
||||
|
||||
export type XaiResponsesUserMessageContentPart =
|
||||
| { type: 'input_text'; text: string }
|
||||
- | { type: 'input_image'; image_url: string };
|
||||
+ | { type: 'input_image'; image_url: string }
|
||||
+ | {
|
||||
+ type: 'input_file';
|
||||
+ file_url?: string;
|
||||
+ file_id?: string;
|
||||
+ file_data?: string;
|
||||
+ filename?: string;
|
||||
+ };
|
||||
|
||||
export type XaiResponsesUserMessage = {
|
||||
role: 'user';
|
||||
@@ -0,0 +1,75 @@
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -1110,6 +1110,14 @@ async function convertToXaiResponsesInput({
|
||||
type: "input_file",
|
||||
file_url: block.data.toString()
|
||||
});
|
||||
+ } else if (block.mediaType === "application/pdf") {
|
||||
+ contentParts.push({
|
||||
+ type: "input_file",
|
||||
+ ...(typeof block.data === "string" && block.data.startsWith("file-") ? { file_id: block.data } : {
|
||||
+ filename: block.filename ?? "file",
|
||||
+ file_data: `data:application/pdf;base64,${(0, import_provider_utils5.convertToBase64)(block.data)}`
|
||||
+ })
|
||||
+ });
|
||||
} else {
|
||||
throw new import_provider4.UnsupportedFunctionalityError({
|
||||
functionality: `file part media type ${block.mediaType} as inline data (xAI Responses requires a URL or a Files API reference for non-image files)`
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -1113,6 +1113,14 @@ async function convertToXaiResponsesInput({
|
||||
type: "input_file",
|
||||
file_url: block.data.toString()
|
||||
});
|
||||
+ } else if (block.mediaType === "application/pdf") {
|
||||
+ contentParts.push({
|
||||
+ type: "input_file",
|
||||
+ ...(typeof block.data === "string" && block.data.startsWith("file-") ? { file_id: block.data } : {
|
||||
+ filename: block.filename ?? "file",
|
||||
+ file_data: `data:application/pdf;base64,${convertToBase642(block.data)}`
|
||||
+ })
|
||||
+ });
|
||||
} else {
|
||||
throw new UnsupportedFunctionalityError3({
|
||||
functionality: `file part media type ${block.mediaType} as inline data (xAI Responses requires a URL or a Files API reference for non-image files)`
|
||||
diff --git a/src/responses/convert-to-xai-responses-input.ts b/src/responses/convert-to-xai-responses-input.ts
|
||||
--- a/src/responses/convert-to-xai-responses-input.ts
|
||||
+++ b/src/responses/convert-to-xai-responses-input.ts
|
||||
@@ -64,6 +64,17 @@ export async function convertToXaiResponsesInput({
|
||||
type: 'input_file',
|
||||
file_url: block.data.toString(),
|
||||
});
|
||||
+ } else if (block.mediaType === 'application/pdf') {
|
||||
+ contentParts.push({
|
||||
+ type: 'input_file',
|
||||
+ ...(typeof block.data === 'string' &&
|
||||
+ block.data.startsWith('file-')
|
||||
+ ? { file_id: block.data }
|
||||
+ : {
|
||||
+ filename: block.filename ?? 'file',
|
||||
+ file_data: `data:application/pdf;base64,${convertToBase64(block.data)}`,
|
||||
+ }),
|
||||
+ });
|
||||
} else {
|
||||
throw new UnsupportedFunctionalityError({
|
||||
functionality: `file part media type ${block.mediaType} as inline data (xAI Responses requires a URL or a Files API reference for non-image files)`,
|
||||
diff --git a/src/responses/xai-responses-api.ts b/src/responses/xai-responses-api.ts
|
||||
--- a/src/responses/xai-responses-api.ts
|
||||
+++ b/src/responses/xai-responses-api.ts
|
||||
@@ -27,6 +27,12 @@ export type XaiResponsesSystemMessage = {
|
||||
export type XaiResponsesUserMessageContentPart =
|
||||
| { type: 'input_text'; text: string }
|
||||
| { type: 'input_image'; image_url: string }
|
||||
- | { type: 'input_file'; file_url: string };
|
||||
-
|
||||
+ | {
|
||||
+ type: 'input_file';
|
||||
+ file_url?: string;
|
||||
+ file_id?: string;
|
||||
+ file_data?: string;
|
||||
+ filename?: string;
|
||||
+ };
|
||||
+
|
||||
export type XaiResponsesUserMessage = {
|
||||
Reference in New Issue
Block a user