Compare commits

...

1 Commits

Author SHA1 Message Date
Kit Langton ad295d58fa feat(v2): expose canonical schema contracts 2026-07-06 15:22:47 -04:00
15 changed files with 159 additions and 30 deletions
+2
View File
@@ -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:",
},
@@ -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)
+6 -6
View File
@@ -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<SessionSchema.Info | undefined>
readonly context: (sessionID: SessionSchema.ID) => Effect.Effect<SessionMessage.Message[], MessageDecodeError>
readonly get: (sessionID: Session.ID) => Effect.Effect<Session.Info | undefined>
readonly context: (sessionID: Session.ID) => Effect.Effect<SessionMessage.Message[], MessageDecodeError>
readonly runnerContext: (
sessionID: SessionSchema.ID,
sessionID: Session.ID,
baselineSeq: number,
) => Effect.Effect<SessionMessage.Message[], MessageDecodeError>
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<Service, Interface>()("@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
+2
View File
@@ -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:"
+5 -4
View File
@@ -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<Agent.Info>[]
get(id: string): Types.DeepMutable<Agent.Info> | undefined
default(id: string | undefined): void
update(id: string, update: (agent: AgentV2Info) => void): void
update(id: string, update: (agent: Types.DeepMutable<Agent.Info>) => void): void
remove(id: string): void
}
+3 -3
View File
@@ -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<string, any>
sdk?: any
}
language: {
readonly model: ModelV2Info
readonly model: Model.Info
readonly sdk: any
readonly options: Record<string, any>
language?: LanguageModelV3
+10
View File
@@ -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"
+3 -3
View File
@@ -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<string, any>
sdk?: any
}
language: {
readonly model: ModelV2Info
readonly model: Model.Info
readonly sdk: any
readonly options: Record<string, any>
language?: LanguageModelV3
+10
View File
@@ -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"
@@ -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",
])
})
+2 -2
View File
@@ -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"]
+7
View File
@@ -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/<domain>` 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`.
+1
View File
@@ -14,6 +14,7 @@
"dependencies": {
"@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/schema": "workspace:*",
"@opencode-ai/server": "workspace:*",
"effect": "catalog:"
},
+21 -12
View File
@@ -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"
@@ -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",
])
})