diff --git a/bun.lock b/bun.lock index 907c04c31d..697bc8ceff 100644 --- a/bun.lock +++ b/bun.lock @@ -690,6 +690,7 @@ "version": "1.17.14", "dependencies": { "@ai-sdk/provider": "3.0.8", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", "zod": "catalog:", @@ -752,6 +753,7 @@ "dependencies": { "@opencode-ai/client": "workspace:*", "@opencode-ai/core": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/server": "workspace:*", "effect": "catalog:", }, diff --git a/packages/client/test/contract-identity.test.ts b/packages/client/test/contract-identity.test.ts index 64a2e958ce..d54a48e938 100644 --- a/packages/client/test/contract-identity.test.ts +++ b/packages/client/test/contract-identity.test.ts @@ -21,6 +21,14 @@ import { Api } from "@opencode-ai/server/api" import { compile, emitPromise } from "@opencode-ai/httpapi-codegen" import { ClientApi, endpointNames, groupNames, omitEndpoints } from "../src/contract" +const Client = await import("../src/effect") + +test("effect entrypoint exposes canonical Schema contracts", () => { + expect(Client.Agent).toBe(Agent) + expect(Client.Model).toBe(Model) + expect(Client.Session).toBe(Session) +}) + test("Core and Server reuse the authoritative Schema and Protocol values", () => { expect(AgentV2.ID).toBe(Agent.ID) expect(CoreLocation.Ref).toBe(Location.Ref) diff --git a/packages/core/src/session/store.ts b/packages/core/src/session/store.ts index 273444d25c..f416e3eb5a 100644 --- a/packages/core/src/session/store.ts +++ b/packages/core/src/session/store.ts @@ -7,20 +7,20 @@ import { makeGlobalNode } from "../effect/app-node" import { SessionHistory } from "./history" import { MessageDecodeError } from "./error" import { SessionMessage } from "./message" -import { SessionSchema } from "./schema" +import { Session } from "@opencode-ai/schema/session" import { SessionMessageTable, SessionTable } from "./sql" import { fromRow } from "./info" export interface Interface { - readonly get: (sessionID: SessionSchema.ID) => Effect.Effect - readonly context: (sessionID: SessionSchema.ID) => Effect.Effect + readonly get: (sessionID: Session.ID) => Effect.Effect + readonly context: (sessionID: Session.ID) => Effect.Effect readonly runnerContext: ( - sessionID: SessionSchema.ID, + sessionID: Session.ID, baselineSeq: number, ) => Effect.Effect readonly message: ( messageID: SessionMessage.ID, - ) => Effect.Effect<{ readonly sessionID: SessionSchema.ID; readonly message: SessionMessage.Message } | undefined> + ) => Effect.Effect<{ readonly sessionID: Session.ID; readonly message: SessionMessage.Message } | undefined> } export class Service extends Context.Service()("@opencode/v2/SessionStore") {} @@ -51,7 +51,7 @@ const layer = Layer.effect( .pipe(Effect.orDie) return row ? { - sessionID: SessionSchema.ID.make(row.session_id), + sessionID: Session.ID.make(row.session_id), message: yield* decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe(Effect.orDie), } : undefined diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 3069aa104f..8ed8ddb3eb 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -5,6 +5,7 @@ "type": "module", "license": "MIT", "scripts": { + "test": "bun test --timeout 5000", "typecheck": "tsgo --noEmit", "build": "tsc" }, @@ -22,6 +23,7 @@ ], "dependencies": { "@ai-sdk/provider": "3.0.8", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", "zod": "catalog:" diff --git a/packages/plugin/src/v2/effect/agent.ts b/packages/plugin/src/v2/effect/agent.ts index 9ded7b831d..d3d43cbb52 100644 --- a/packages/plugin/src/v2/effect/agent.ts +++ b/packages/plugin/src/v2/effect/agent.ts @@ -1,11 +1,12 @@ -import type { AgentV2Info } from "@opencode-ai/sdk/v2/types" +import type { Agent } from "@opencode-ai/schema/agent" +import type { Types } from "effect" import type { Hooks } from "./registration.js" export interface AgentDraft { - list(): readonly AgentV2Info[] - get(id: string): AgentV2Info | undefined + list(): readonly Types.DeepMutable[] + get(id: string): Types.DeepMutable | undefined default(id: string | undefined): void - update(id: string, update: (agent: AgentV2Info) => void): void + update(id: string, update: (agent: Types.DeepMutable) => void): void remove(id: string): void } diff --git a/packages/plugin/src/v2/effect/aisdk.ts b/packages/plugin/src/v2/effect/aisdk.ts index ddc292e384..2934edc27d 100644 --- a/packages/plugin/src/v2/effect/aisdk.ts +++ b/packages/plugin/src/v2/effect/aisdk.ts @@ -1,16 +1,16 @@ import type { LanguageModelV3 } from "@ai-sdk/provider" -import type { ModelV2Info } from "@opencode-ai/sdk/v2/types" +import type { Model } from "@opencode-ai/schema/model" import type { Hooks } from "./registration.js" export type AISDKHooks = Hooks<{ sdk: { - readonly model: ModelV2Info + readonly model: Model.Info readonly package: string readonly options: Record sdk?: any } language: { - readonly model: ModelV2Info + readonly model: Model.Info readonly sdk: any readonly options: Record language?: LanguageModelV3 diff --git a/packages/plugin/src/v2/effect/index.ts b/packages/plugin/src/v2/effect/index.ts index f13614a54d..ff400347e2 100644 --- a/packages/plugin/src/v2/effect/index.ts +++ b/packages/plugin/src/v2/effect/index.ts @@ -1,3 +1,13 @@ export type { PluginContext } from "./context.js" export { define } from "./plugin.js" export type { Plugin } from "./plugin.js" + +export { Agent } from "@opencode-ai/schema/agent" +export { Command } from "@opencode-ai/schema/command" +export { Connection } from "@opencode-ai/schema/connection" +export { Credential } from "@opencode-ai/schema/credential" +export { Integration } from "@opencode-ai/schema/integration" +export { Model } from "@opencode-ai/schema/model" +export { Provider } from "@opencode-ai/schema/provider" +export { Reference } from "@opencode-ai/schema/reference" +export { Skill } from "@opencode-ai/schema/skill" diff --git a/packages/plugin/src/v2/promise/aisdk.ts b/packages/plugin/src/v2/promise/aisdk.ts index ddc292e384..2934edc27d 100644 --- a/packages/plugin/src/v2/promise/aisdk.ts +++ b/packages/plugin/src/v2/promise/aisdk.ts @@ -1,16 +1,16 @@ import type { LanguageModelV3 } from "@ai-sdk/provider" -import type { ModelV2Info } from "@opencode-ai/sdk/v2/types" +import type { Model } from "@opencode-ai/schema/model" import type { Hooks } from "./registration.js" export type AISDKHooks = Hooks<{ sdk: { - readonly model: ModelV2Info + readonly model: Model.Info readonly package: string readonly options: Record sdk?: any } language: { - readonly model: ModelV2Info + readonly model: Model.Info readonly sdk: any readonly options: Record language?: LanguageModelV3 diff --git a/packages/plugin/src/v2/promise/index.ts b/packages/plugin/src/v2/promise/index.ts index 8287955657..d3f94ef549 100644 --- a/packages/plugin/src/v2/promise/index.ts +++ b/packages/plugin/src/v2/promise/index.ts @@ -10,3 +10,13 @@ export type { CommandDraft, CommandHooks } from "./command.js" export type { IntegrationDraft, IntegrationHooks, IntegrationMethodRegistration } from "./integration.js" export type { ReferenceDraft, ReferenceHooks } from "./reference.js" export type { SkillDraft, SkillHooks } from "./skill.js" + +export { Agent } from "@opencode-ai/schema/agent" +export { Command } from "@opencode-ai/schema/command" +export { Connection } from "@opencode-ai/schema/connection" +export { Credential } from "@opencode-ai/schema/credential" +export { Integration } from "@opencode-ai/schema/integration" +export { Model } from "@opencode-ai/schema/model" +export { Provider } from "@opencode-ai/schema/provider" +export { Reference } from "@opencode-ai/schema/reference" +export { Skill } from "@opencode-ai/schema/skill" diff --git a/packages/plugin/test/contract-identity.test.ts b/packages/plugin/test/contract-identity.test.ts new file mode 100644 index 0000000000..1144a49344 --- /dev/null +++ b/packages/plugin/test/contract-identity.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from "bun:test" +import { Agent } from "@opencode-ai/schema/agent" +import { Command } from "@opencode-ai/schema/command" +import { Connection } from "@opencode-ai/schema/connection" +import { Credential } from "@opencode-ai/schema/credential" +import { Integration } from "@opencode-ai/schema/integration" +import { Model } from "@opencode-ai/schema/model" +import { Provider } from "@opencode-ai/schema/provider" +import { Reference } from "@opencode-ai/schema/reference" +import { Skill } from "@opencode-ai/schema/skill" + +const Plugin = await import("../src/v2/effect/index") +const PromisePlugin = await import("../src/v2/promise/index") + +test.each([ + ["effect", Plugin], + ["promise", PromisePlugin], +])("%s entrypoint exposes its canonical Schema contracts", (_, entrypoint) => { + expect(entrypoint.Agent).toBe(Agent) + expect(entrypoint.Command).toBe(Command) + expect(entrypoint.Connection).toBe(Connection) + expect(entrypoint.Credential).toBe(Credential) + expect(entrypoint.Integration).toBe(Integration) + expect(entrypoint.Model).toBe(Model) + expect(entrypoint.Provider).toBe(Provider) + expect(entrypoint.Reference).toBe(Reference) + expect(entrypoint.Skill).toBe(Skill) + expect(Object.keys(entrypoint).sort()).toEqual([ + "Agent", + "Command", + "Connection", + "Credential", + "Integration", + "Model", + "Provider", + "Reference", + "Skill", + "define", + ]) +}) diff --git a/packages/plugin/tsconfig.json b/packages/plugin/tsconfig.json index f8e9370d86..f25b5898b9 100644 --- a/packages/plugin/tsconfig.json +++ b/packages/plugin/tsconfig.json @@ -4,9 +4,9 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "module": "nodenext", + "module": "esnext", "declaration": true, - "moduleResolution": "nodenext", + "moduleResolution": "bundler", "lib": ["es2022", "dom", "dom.iterable"] }, "include": ["src"] diff --git a/packages/schema/AGENTS.md b/packages/schema/AGENTS.md index 1d5747d539..400b4b312b 100644 --- a/packages/schema/AGENTS.md +++ b/packages/schema/AGENTS.md @@ -34,6 +34,13 @@ - Use flat top-level exports plus the package's existing namespace projection pattern, for example `export * as SessionMessage from "./session-message"`. - Keep standalone ID modules only when they prevent real cycles or heavy dependency edges. Inline one-off IDs into their owning contract module when no cycle exists. +## Public Re-exports + +- Public leaf packages re-export the canonical domain namespaces used by their APIs so consumers install and version one package. Keep the list curated by public API reachability rather than mirroring the Schema root. +- Re-export directly from `@opencode-ai/schema/` and preserve the exact canonical values and types. Do not wrap schemas, duplicate definitions, or route exports through another facade package. +- Internal packages import contracts directly from their canonical Schema owner. Import a Core domain facade only when runtime behavior from that facade is required. +- Resolve name collisions by retaining the domain namespace and qualifying its members, such as `Session.Info` and `Agent.Info`. Do not introduce aliases such as `SessionSchema`, `AgentSchema`, or `ModelSchema`. + ## Naming - Exported schema values and namespace objects use `PascalCase`. diff --git a/packages/sdk-next/package.json b/packages/sdk-next/package.json index 55a891a10f..dab2f9b17e 100644 --- a/packages/sdk-next/package.json +++ b/packages/sdk-next/package.json @@ -14,6 +14,7 @@ "dependencies": { "@opencode-ai/client": "workspace:*", "@opencode-ai/core": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/server": "workspace:*", "effect": "catalog:" }, diff --git a/packages/sdk-next/src/index.ts b/packages/sdk-next/src/index.ts index fc23219fc4..dbb07ec1c9 100644 --- a/packages/sdk-next/src/index.ts +++ b/packages/sdk-next/src/index.ts @@ -2,16 +2,25 @@ export * as OpenCode from "./opencode" export * as Tool from "./tool" export { ClientError } from "@opencode-ai/client/effect" -export { - AbsolutePath, - Agent, - Location, - Model, - Prompt, - Provider, - RelativePath, - Session, - SessionInput, - SessionMessage, -} from "@opencode-ai/client/effect" export type { OpenCodeEvent } from "@opencode-ai/client/effect" +export { Agent } from "@opencode-ai/schema/agent" +export { Command } from "@opencode-ai/schema/command" +export { Credential } from "@opencode-ai/schema/credential" +export { FileSystem } from "@opencode-ai/schema/filesystem" +export { Integration } from "@opencode-ai/schema/integration" +export { Location } from "@opencode-ai/schema/location" +export { Model } from "@opencode-ai/schema/model" +export { Permission } from "@opencode-ai/schema/permission" +export { PermissionSaved } from "@opencode-ai/schema/permission-saved" +export { Project } from "@opencode-ai/schema/project" +export { ProjectCopy } from "@opencode-ai/schema/project-copy" +export { Prompt } from "@opencode-ai/schema/prompt" +export { Provider } from "@opencode-ai/schema/provider" +export { Pty } from "@opencode-ai/schema/pty" +export { Question } from "@opencode-ai/schema/question" +export { Reference } from "@opencode-ai/schema/reference" +export { AbsolutePath, RelativePath } from "@opencode-ai/schema/schema" +export { Session } from "@opencode-ai/schema/session" +export { SessionInput } from "@opencode-ai/schema/session-input" +export { SessionMessage } from "@opencode-ai/schema/session-message" +export { Skill } from "@opencode-ai/schema/skill" diff --git a/packages/sdk-next/test/contract-identity.test.ts b/packages/sdk-next/test/contract-identity.test.ts new file mode 100644 index 0000000000..96b10d6918 --- /dev/null +++ b/packages/sdk-next/test/contract-identity.test.ts @@ -0,0 +1,39 @@ +import { expect, test } from "bun:test" +import { Agent } from "@opencode-ai/schema/agent" +import { Model } from "@opencode-ai/schema/model" +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.Session).toBe(Session) + expect(Object.keys(SDK).sort()).toEqual([ + "AbsolutePath", + "Agent", + "ClientError", + "Command", + "Credential", + "FileSystem", + "Integration", + "Location", + "Model", + "OpenCode", + "Permission", + "PermissionSaved", + "Project", + "ProjectCopy", + "Prompt", + "Provider", + "Pty", + "Question", + "Reference", + "RelativePath", + "Session", + "SessionInput", + "SessionMessage", + "Skill", + "Tool", + ]) +})