Merge remote-tracking branch 'origin/v2' into search-integration

# Conflicts:
#	packages/client/test/promise.test.ts
#	packages/core/schema.json
#	packages/core/src/database/migration.gen.ts
#	packages/core/src/tool/websearch.ts
#	packages/sdk-next/src/index.ts
#	packages/sdk/js/src/v2/gen/types.gen.ts
This commit is contained in:
Shoubhit Dash 2026-07-07 17:38:46 +05:30
commit 7b8d8b8861
666 changed files with 46671 additions and 20220 deletions

View file

@ -0,0 +1,43 @@
import { expect, test } from "bun:test"
import { Agent } from "@opencode-ai/schema/agent"
import { Model } from "@opencode-ai/schema/model"
import { Search } from "@opencode-ai/schema/search"
import { Session } from "@opencode-ai/schema/session"
const SDK = await import("../src/index")
test("re-exports canonical contracts directly from Schema", () => {
expect(SDK.Agent).toBe(Agent)
expect(SDK.Model).toBe(Model)
expect(SDK.Search).toBe(Search)
expect(SDK.Session).toBe(Session)
expect(Object.keys(SDK).sort()).toEqual([
"AbsolutePath",
"Agent",
"ClientError",
"Command",
"Credential",
"FileSystem",
"Integration",
"Location",
"Model",
"OpenCode",
"Permission",
"PermissionSaved",
"Project",
"ProjectCopy",
"Prompt",
"PromptInput",
"Provider",
"Pty",
"Question",
"Reference",
"RelativePath",
"Search",
"Session",
"SessionInput",
"SessionMessage",
"Skill",
"Tool",
])
})

View file

@ -1,6 +1,8 @@
import fs from "fs/promises"
import path from "path"
import { expect } from "bun:test"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Deferred, Effect, Latch, Layer, Option, Schema, Stream } from "effect"
import { Deferred, Effect, Latch, Layer, Option, Ref, Schema, Stream } from "effect"
import { testEffect } from "../../core/test/lib/effect"
import { tmpdir } from "../../core/test/fixture/tmpdir"
import type { OpenCodeEvent } from "../src"
@ -26,6 +28,118 @@ const sessionID = (fixture: Fixture) => fixture.sdk.Session.ID.create()
const location = (fixture: Fixture) =>
fixture.sdk.Location.Ref.make({ directory: fixture.sdk.AbsolutePath.make(fixture.directory) })
it.live(
"reloads every booted Location after SDK plugin registration",
() =>
withEmbedded("opencode-embedded-plugin-reload-", (fixture) =>
Effect.gen(function* () {
const opencode = yield* fixture.sdk.OpenCode.create()
const booted = yield* Deferred.make<void>()
const activated = yield* Deferred.make<boolean>()
const bootCount = yield* Ref.make(0)
const activationCount = yield* Ref.make(0)
const secondDirectory = path.join(fixture.directory, "second")
yield* Effect.promise(() => fs.mkdir(secondDirectory))
const refs = [
location(fixture),
fixture.sdk.Location.Ref.make({ directory: fixture.sdk.AbsolutePath.make(secondDirectory) }),
]
const bootstrapID = `bootstrap-sdk-${crypto.randomUUID()}`
const id = `late-sdk-${crypto.randomUUID()}`
yield* opencode.plugin({
id: bootstrapID,
effect: (ctx) =>
Effect.gen(function* () {
yield* ctx.tool
.transform((draft) =>
draft.add(
"bootstrap_sdk_tool",
fixture.sdk.Tool.make({
description: "Marks the initial Location plugin generation",
input: Schema.Struct({}),
output: Schema.Void,
execute: () => Effect.void,
}),
),
)
.pipe(Effect.orDie)
if (yield* Ref.updateAndGet(bootCount, (count) => count + 1).pipe(Effect.map((count) => count === 2))) {
yield* Deferred.succeed(booted, undefined)
}
}),
})
yield* Effect.all(
refs.map((ref) => opencode.plugin.list({ location: ref })),
{ discard: true },
)
yield* Deferred.await(booted).pipe(Effect.timeout("4 seconds"))
yield* opencode.plugin({
id,
effect: (ctx) =>
Effect.gen(function* () {
yield* ctx.tool
.transform((draft) =>
draft.add(
"late_sdk_tool",
fixture.sdk.Tool.make({
description: "Tool registered after Location boot",
input: Schema.Struct({}),
output: Schema.Void,
execute: () => Effect.void,
}),
),
)
.pipe(Effect.orDie)
if (
yield* Ref.updateAndGet(activationCount, (count) => count + 1).pipe(Effect.map((count) => count === 2))
) {
yield* Deferred.succeed(activated, true)
}
}),
})
expect(yield* Deferred.await(activated).pipe(Effect.timeout("10 seconds"))).toBe(true)
}),
),
25_000,
)
it.live(
"keeps SDK plugin registration isolated between embedded hosts",
() =>
withEmbedded("opencode-embedded-plugin-isolation-", (fixture) =>
Effect.gen(function* () {
const first = yield* fixture.sdk.OpenCode.create()
const second = yield* fixture.sdk.OpenCode.create()
const firstReady = yield* Deferred.make<void>()
const secondReady = yield* Deferred.make<void>()
const activated = yield* Deferred.make<void>()
const ref = location(fixture)
const id = `isolated-sdk-${crypto.randomUUID()}`
yield* first.plugin({
id: `first-ready-${crypto.randomUUID()}`,
effect: () => Deferred.succeed(firstReady, undefined),
})
yield* second.plugin({
id: `second-ready-${crypto.randomUUID()}`,
effect: () => Deferred.succeed(secondReady, undefined),
})
yield* Effect.all([first.plugin.list({ location: ref }), second.plugin.list({ location: ref })], {
discard: true,
})
yield* Effect.all([Deferred.await(firstReady), Deferred.await(secondReady)], { discard: true })
yield* first.plugin({ id, effect: () => Deferred.succeed(activated, undefined) })
yield* Deferred.await(activated).pipe(Effect.timeout("5 seconds"))
expect((yield* second.plugin.list({ location: ref })).data.map((plugin) => String(plugin.id))).not.toContain(id)
}),
),
15_000,
)
it.live(
"embedded client uses the real router and handlers",
() =>
@ -42,14 +156,17 @@ it.live(
id: `embedded-tools-${crypto.randomUUID()}`,
effect: (ctx) =>
ctx.tool
.register({
embedded_tool: fixture.sdk.Tool.make({
description: "Embedded test tool",
input: Schema.Struct({}),
output: Schema.Struct({ ok: Schema.Boolean }),
execute: () => Effect.succeed({ ok: true }),
}),
})
.transform((draft) =>
draft.add(
"embedded_tool",
fixture.sdk.Tool.make({
description: "Embedded test tool",
input: Schema.Struct({}),
output: Schema.Struct({ ok: Schema.Boolean }),
execute: () => Effect.succeed({ ok: true }),
}),
),
)
.pipe(Effect.orDie),
})
@ -64,18 +181,18 @@ it.live(
const active = yield* opencode.sessions.active()
const admitted = yield* opencode.sessions.prompt({
sessionID: id,
prompt: fixture.sdk.Prompt.make({ text: "Do not run" }),
prompt: fixture.sdk.PromptInput.Prompt.make({ text: "Do not run" }),
resume: false,
})
const context = yield* opencode.sessions.context({ sessionID: id })
yield* opencode.sessions.putContextEntry({ sessionID: id, key: "deploy-target", value: "production" })
yield* opencode.sessions.putContextEntry({ sessionID: id, key: "flags", value: { beta: true } })
const contextEntries = yield* opencode.sessions.listContextEntries({ sessionID: id })
yield* opencode.sessions.removeContextEntry({ sessionID: id, key: "flags" })
const remainingContextEntries = yield* opencode.sessions.listContextEntries({ sessionID: id })
yield* opencode.sessions.instructions.entry.put({ sessionID: id, key: "deploy-target", value: "production" })
yield* opencode.sessions.instructions.entry.put({ sessionID: id, key: "flags", value: { beta: true } })
const contextEntries = yield* opencode.sessions.instructions.entry.list({ sessionID: id })
yield* opencode.sessions.instructions.entry.remove({ sessionID: id, key: "flags" })
const remainingContextEntries = yield* opencode.sessions.instructions.entry.list({ sessionID: id })
const wake = yield* opencode.sessions.prompt({
sessionID: id,
prompt: fixture.sdk.Prompt.make({ text: "Promote this input" }),
prompt: fixture.sdk.PromptInput.Prompt.make({ text: "Promote this input" }),
})
const prompted = yield* opencode.sessions.log({ sessionID: id, follow: true }).pipe(
Stream.filter((event) => event.type === "session.prompt.promoted" && event.data.inputID === wake.id),
@ -102,7 +219,7 @@ it.live(
opencode.sessions.log({ sessionID: missingSessionID }).pipe(Stream.runHead, Effect.flip),
opencode.sessions.interrupt({ sessionID: missingSessionID }).pipe(Effect.flip),
opencode.sessions.message({ sessionID: missingSessionID, messageID: modelMessage.id }).pipe(Effect.flip),
opencode.sessions.listContextEntries({ sessionID: missingSessionID }).pipe(Effect.flip),
opencode.sessions.instructions.entry.list({ sessionID: missingSessionID }).pipe(Effect.flip),
],
{ concurrency: "unbounded" },
)
@ -117,7 +234,7 @@ it.live(
expect(selected.model?.id).toBe(model.id)
expect(selected.model?.providerID).toBe(model.providerID)
expect(page.data.some((session) => session.id === id)).toBe(true)
expect(active).toEqual({ data: {}, watermarks: {} })
expect(active).toEqual({})
expect(admitted.sessionID).toBe(id)
expect(prompted.type).toBe("session.prompt.promoted")
expect(wakeContext).toContainEqual(expect.objectContaining({ id: wake.id, type: "user" }))
@ -199,7 +316,7 @@ it.live(
yield* opencode.sessions.create({ id, location: location(fixture) })
yield* opencode.sessions.prompt({
sessionID: id,
prompt: fixture.sdk.Prompt.make({ text: "Observe this input" }),
prompt: fixture.sdk.PromptInput.Prompt.make({ text: "Observe this input" }),
})
const event = yield* Deferred.await(prompted).pipe(Effect.timeout("4 seconds"))