refactor(schema): extract shared public schemas (#33571)

This commit is contained in:
Kit Langton 2026-06-24 04:31:06 +02:00 committed by GitHub
parent 60aba622ab
commit 516cfe4e09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 2004 additions and 1583 deletions

View file

@ -277,6 +277,7 @@
"@opencode-ai/effect-sqlite-node": "workspace:*",
"@opencode-ai/llm": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/schema": "workspace:*",
"@openrouter/ai-sdk-provider": "2.9.0",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/context-async-hooks": "2.6.1",
@ -480,6 +481,7 @@
"name": "@opencode-ai/llm",
"version": "1.17.9",
"dependencies": {
"@opencode-ai/schema": "workspace:*",
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
"aws4fetch": "1.0.20",
@ -651,6 +653,17 @@
"@opentui/solid",
],
},
"packages/schema": {
"name": "@opencode-ai/schema",
"dependencies": {
"effect": "catalog:",
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
},
},
"packages/script": {
"name": "@opencode-ai/script",
"dependencies": {
@ -1804,6 +1817,8 @@
"@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"],
"@opencode-ai/schema": ["@opencode-ai/schema@workspace:packages/schema"],
"@opencode-ai/script": ["@opencode-ai/script@workspace:packages/script"],
"@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk/js"],

View file

@ -91,6 +91,7 @@
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
"@opencode-ai/effect-sqlite-node": "workspace:*",
"@opencode-ai/llm": "workspace:*",
"@opencode-ai/schema": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/context-async-hooks": "2.6.1",

View file

@ -1,46 +1,17 @@
export * as AgentV2 from "./agent"
import { Array, Context, Effect, Layer, Schema, Scope, Types } from "effect"
import { ModelV2 } from "./model"
import { PermissionSchema } from "./permission/schema"
import { ProviderV2 } from "./provider"
import { PositiveInt } from "./schema"
import { Array, Context, Effect, Layer, Types } from "effect"
import { Agent } from "@opencode-ai/schema/agent"
import { State } from "./state"
export const ID = Schema.String.pipe(Schema.brand("AgentV2.ID"))
export const ID = Agent.ID
export type ID = typeof ID.Type
export const defaultID = ID.make("build")
export const Color = Schema.Union([
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
])
export const Color = Agent.Color
export class Info extends Schema.Class<Info>("AgentV2.Info")({
id: ID,
model: ModelV2.Ref.pipe(Schema.optional),
request: ProviderV2.Request,
system: Schema.String.pipe(Schema.optional),
description: Schema.String.pipe(Schema.optional),
mode: Schema.Literals(["subagent", "primary", "all"]),
hidden: Schema.Boolean,
color: Color.pipe(Schema.optional),
steps: PositiveInt.pipe(Schema.optional),
permissions: PermissionSchema.Ruleset,
}) {
static empty(id: ID) {
return new Info({
id,
request: {
headers: {},
body: {},
},
mode: "all",
hidden: false,
permissions: [],
})
}
}
export const Info = Agent.Info
export type Info = Agent.Info
export interface Selection {
readonly id: ID

View file

@ -89,7 +89,7 @@ export const layer = Layer.effect(
...ModelRequest.merge({ ...provider.request, generation: {}, options: {} }, model.request),
variant: model.request.variant,
}
return new ModelV2.Info({
return ModelV2.Info.make({
...model,
api,
request,

View file

@ -1,17 +1,11 @@
export * as CommandV2 from "./command"
import { Context, Effect, Layer, Schema, Types } from "effect"
import { ModelV2 } from "./model"
import { Context, Effect, Layer, Types } from "effect"
import { Command } from "@opencode-ai/schema/command"
import { State } from "./state"
export class Info extends Schema.Class<Info>("CommandV2.Info")({
name: Schema.String,
template: Schema.String,
description: Schema.String.pipe(Schema.optional),
agent: Schema.String.pipe(Schema.optional),
model: ModelV2.Ref.pipe(Schema.optional),
subtask: Schema.Boolean.pipe(Schema.optional),
}) {}
export const Info = Command.Info
export type Info = Command.Info
export type Data = {
commands: Map<string, Types.DeepMutable<Info>>
@ -40,7 +34,7 @@ export const layer = Layer.effect(
list: () => Array.from(draft.commands.values()) as Info[],
get: (name) => draft.commands.get(name),
update: (name, update) => {
const current = draft.commands.get(name) ?? (new Info({ name, template: "" }) as Types.DeepMutable<Info>)
const current = draft.commands.get(name) ?? ({ name, template: "" } as Types.DeepMutable<Info>)
if (!draft.commands.has(name)) draft.commands.set(name, current)
update(current)
current.name = name

View file

@ -28,7 +28,7 @@ export const Plugin = define({
entries.set(
name,
local(entry)
? new Reference.LocalSource({
? Reference.LocalSource.make({
type: "local",
path: AbsolutePath.make(
localPath(directory, global.home, typeof entry === "string" ? entry : entry.path),
@ -36,7 +36,7 @@ export const Plugin = define({
description: typeof entry === "string" ? undefined : entry.description,
hidden: typeof entry === "string" ? undefined : entry.hidden,
})
: new Reference.GitSource({
: Reference.GitSource.make({
type: "git",
repository: typeof entry === "string" ? entry : entry.repository,
branch: typeof entry === "string" ? undefined : entry.branch,

View file

@ -22,20 +22,23 @@ export const Plugin = define({
const items = entries.flatMap((entry) => (entry.type === "document" ? (entry.info.skills ?? []) : []))
for (const directory of directories) {
draft.source(
new SkillV2.DirectorySource({ type: "directory", path: AbsolutePath.make(path.join(directory, "skill")) }),
SkillV2.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skill")) }),
)
draft.source(
new SkillV2.DirectorySource({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) }),
SkillV2.DirectorySource.make({
type: "directory",
path: AbsolutePath.make(path.join(directory, "skills")),
}),
)
}
for (const item of items) {
if (URL.canParse(item) && /^(https?:)$/.test(new URL(item).protocol)) {
draft.source(new SkillV2.UrlSource({ type: "url", url: item }))
draft.source(SkillV2.UrlSource.make({ type: "url", url: item }))
continue
}
const expanded = item.startsWith("~/") ? path.join(global.home, item.slice(2)) : item
draft.source(
new SkillV2.DirectorySource({
SkillV2.DirectorySource.make({
type: "directory",
path: AbsolutePath.make(path.isAbsolute(expanded) ? expanded : path.join(location.directory, expanded)),
}),

View file

@ -2,37 +2,22 @@ export * as Credential from "./credential"
import { asc, eq } from "drizzle-orm"
import { Context, Effect, Layer, Schema } from "effect"
import { Credential } from "@opencode-ai/schema/credential"
import { Database } from "./database/database"
import { IntegrationSchema } from "./integration/schema"
import { NonNegativeInt, withStatics } from "./schema"
import { Identifier } from "./util/identifier"
import { CredentialTable } from "./credential/sql"
export const ID = Schema.String.pipe(
Schema.brand("Credential.ID"),
withStatics((schema) => ({ create: () => schema.make("cred_" + Identifier.ascending()) })),
)
export type ID = typeof ID.Type
export const ID = Credential.ID
export type ID = Credential.ID
export class OAuth extends Schema.Class<OAuth>("Credential.OAuth")({
type: Schema.Literal("oauth"),
methodID: IntegrationSchema.MethodID,
refresh: Schema.String,
access: Schema.String,
expires: NonNegativeInt,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
}) {}
export const OAuth = Credential.OAuth
export type OAuth = Credential.OAuth
export class Key extends Schema.Class<Key>("Credential.Key")({
type: Schema.Literal("key"),
key: Schema.String,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
}) {}
export const Key = Credential.Key
export type Key = Credential.Key
export const Value = Schema.Union([OAuth, Key])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "Credential.Value" })
export type Value = Schema.Schema.Type<typeof Value>
export const Value = Credential.Value
export type Value = Credential.Value
export class Info extends Schema.Class<Info>("Credential.Info")({
id: ID,

View file

@ -5,7 +5,7 @@ import { and, asc, eq, gt } from "drizzle-orm"
import { Database } from "./database/database"
import { EventSequenceTable, EventTable } from "./event/sql"
import { Location } from "./location"
import { externalID, type ExternalID, withStatics } from "./schema"
import { withStatics } from "./schema"
import { Identifier } from "./util/identifier"
import { LayerNode } from "./effect/layer-node"
import { isDeepStrictEqual } from "node:util"
@ -14,7 +14,6 @@ export const ID = Schema.String.check(Schema.isStartsWith("evt_")).pipe(
Schema.brand("Event.ID"),
withStatics((schema) => ({
create: () => schema.make("evt_" + Identifier.ascending()),
fromExternal: (input: ExternalID) => schema.make(externalID("evt", input)),
})),
)
export type ID = typeof ID.Type

View file

@ -108,7 +108,7 @@ const baseLayer = Layer.effect(
const absolute = path.join(target.absolute, item.name)
const relative = path.relative(target.directory, absolute)
return [
new Entry({
Entry.make({
path: RelativePath.make(relative + (item.type === "directory" ? path.sep : "")),
type: item.type,
}),

View file

@ -1,22 +1,10 @@
import { Schema } from "effect"
import { NonNegativeInt, PositiveInt, RelativePath } from "../schema"
import { FileSystem } from "@opencode-ai/schema/filesystem"
export class Entry extends Schema.Class<Entry>("FileSystem.Entry")({
path: RelativePath,
type: Schema.Literals(["file", "directory"]),
}) {}
export const Entry = FileSystem.Entry
export type Entry = FileSystem.Entry
export const Submatch = Schema.Struct({
text: Schema.String,
start: NonNegativeInt,
end: NonNegativeInt,
})
export type Submatch = typeof Submatch.Type
export const Submatch = FileSystem.Submatch
export type Submatch = FileSystem.Submatch
export class Match extends Schema.Class<Match>("FileSystem.Match")({
entry: Entry,
line: PositiveInt,
offset: NonNegativeInt,
text: Schema.String,
submatches: Schema.Array(Submatch),
}) {}
export const Match = FileSystem.Match
export type Match = FileSystem.Match

View file

@ -59,12 +59,11 @@ export const ripgrepLayer = Layer.effect(
})
.pipe(
Effect.map((result) =>
result.map(
(entry) =>
new FileSystem.Entry({
...entry,
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
}),
result.map((entry) =>
FileSystem.Entry.make({
...entry,
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
}),
),
),
Effect.orDie,
@ -85,15 +84,14 @@ export const ripgrepLayer = Layer.effect(
})
.pipe(
Effect.map((result) =>
result.map(
(match) =>
new FileSystem.Match({
...match,
entry: new FileSystem.Entry({
...match.entry,
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, match.entry.path))),
}),
result.map((match) =>
FileSystem.Match.make({
...match,
entry: FileSystem.Entry.make({
...match.entry,
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, match.entry.path))),
}),
}),
),
),
Effect.orDie,
@ -110,7 +108,7 @@ export const ripgrepLayer = Layer.effect(
return fuzzysort.go(input.query, items, { limit: input.limit ?? 50 }).map((item) => {
const relative = item.target
const type = relative.endsWith(path.sep) ? ("directory" as const) : ("file" as const)
return new FileSystem.Entry({
return FileSystem.Entry.make({
path: RelativePath.make(relative),
type,
})
@ -145,12 +143,11 @@ export const fffLayer = Layer.effect(
pageSize: input.limit,
})
if (!found.ok) throw found.error
return found.value.items.map(
(item) =>
new FileSystem.Entry({
path: RelativePath.make(item.relativePath.replaceAll("\\", "/")),
type: "file",
}),
return found.value.items.map((item) =>
FileSystem.Entry.make({
path: RelativePath.make(item.relativePath.replaceAll("\\", "/")),
type: "file",
}),
)
}),
grep: (input) =>
@ -165,8 +162,8 @@ export const fffLayer = Layer.effect(
if (!found.ok) throw found.error
return found.value.items.map((match) => {
const bytes = Buffer.from(match.lineContent)
return new FileSystem.Match({
entry: new FileSystem.Entry({
return FileSystem.Match.make({
entry: FileSystem.Entry.make({
path: RelativePath.make(match.relativePath.replaceAll("\\", "/")),
type: "file",
}),
@ -215,7 +212,7 @@ export const fffLayer = Layer.effect(
.sort((a, b) => b.score - a.score || a.path.length - b.path.length)
.map((item) => {
const relative = item.path.replaceAll("\\", "/").replace(/\/$/, "")
return new FileSystem.Entry({
return FileSystem.Entry.make({
path: RelativePath.make(relative + (item.type === "directory" ? path.sep : "")),
type: item.type,
})

View file

@ -1,4 +1,4 @@
import { randomBytes } from "crypto"
import { create as createIdentifier } from "@opencode-ai/schema/identifier"
const prefixes = {
job: "job",
@ -13,12 +13,6 @@ const prefixes = {
workspace: "wrk",
} as const
const LENGTH = 26
// State for monotonic ID generation
let lastTimestamp = 0
let counter = 0
export function ascending(prefix: keyof typeof prefixes, given?: string) {
return generateID(prefix, "ascending", given)
}
@ -38,35 +32,8 @@ function generateID(prefix: keyof typeof prefixes, direction: "descending" | "as
return given
}
function randomBase62(length: number): string {
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
let result = ""
const bytes = randomBytes(length)
for (let i = 0; i < length; i++) {
result += chars[bytes[i] % 62]
}
return result
}
export function create(prefix: string, direction: "descending" | "ascending", timestamp?: number): string {
const currentTimestamp = timestamp ?? Date.now()
if (currentTimestamp !== lastTimestamp) {
lastTimestamp = currentTimestamp
counter = 0
}
counter++
let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter)
now = direction === "descending" ? ~now : now
const timeBytes = Buffer.alloc(6)
for (let i = 0; i < 6; i++) {
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff))
}
return prefix + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12)
return prefix + "_" + createIdentifier(direction === "descending", timestamp)
}
/** Extract timestamp from an ascending ID. Does not work with descending IDs. */

View file

@ -14,6 +14,7 @@ import {
SynchronizedRef,
Types,
} from "effect"
import { Integration } from "@opencode-ai/schema/integration"
import { Credential } from "./credential"
import { IntegrationSchema } from "./integration/schema"
import { withStatics } from "./schema"
@ -34,66 +35,29 @@ export const AttemptID = Schema.String.pipe(
)
export type AttemptID = typeof AttemptID.Type
export const When = Schema.Struct({
key: Schema.String,
op: Schema.Literals(["eq", "neq"]),
value: Schema.String,
}).annotate({ identifier: "Integration.When" })
export type When = typeof When.Type
export const When = Integration.When
export type When = Integration.When
export const TextPrompt = Schema.Struct({
type: Schema.Literal("text"),
key: Schema.String,
message: Schema.String,
placeholder: Schema.optional(Schema.String),
when: Schema.optional(When),
}).annotate({ identifier: "Integration.TextPrompt" })
export type TextPrompt = typeof TextPrompt.Type
export const TextPrompt = Integration.TextPrompt
export type TextPrompt = Integration.TextPrompt
export const SelectPrompt = Schema.Struct({
type: Schema.Literal("select"),
key: Schema.String,
message: Schema.String,
options: Schema.mutable(
Schema.Array(
Schema.Struct({
label: Schema.String,
value: Schema.String,
hint: Schema.optional(Schema.String),
}),
),
),
when: Schema.optional(When),
}).annotate({ identifier: "Integration.SelectPrompt" })
export type SelectPrompt = typeof SelectPrompt.Type
export const SelectPrompt = Integration.SelectPrompt
export type SelectPrompt = Integration.SelectPrompt
export const Prompt = Schema.Union([TextPrompt, SelectPrompt]).pipe(Schema.toTaggedUnion("type"))
export type Prompt = typeof Prompt.Type
export const Prompt = Integration.Prompt
export type Prompt = Integration.Prompt
export const OAuthMethod = Schema.Struct({
id: MethodID,
type: Schema.Literal("oauth"),
label: Schema.String,
prompts: Schema.optional(Schema.mutable(Schema.Array(Prompt))),
}).annotate({ identifier: "Integration.OAuthMethod" })
export type OAuthMethod = typeof OAuthMethod.Type
export const OAuthMethod = Integration.OAuthMethod
export type OAuthMethod = Integration.OAuthMethod
export const KeyMethod = Schema.Struct({
type: Schema.Literal("key"),
label: Schema.optional(Schema.String),
}).annotate({ identifier: "Integration.KeyMethod" })
export type KeyMethod = typeof KeyMethod.Type
export const KeyMethod = Integration.KeyMethod
export type KeyMethod = Integration.KeyMethod
export const EnvMethod = Schema.Struct({
type: Schema.Literal("env"),
names: Schema.mutable(Schema.Array(Schema.String)),
}).annotate({ identifier: "Integration.EnvMethod" })
export type EnvMethod = typeof EnvMethod.Type
export const EnvMethod = Integration.EnvMethod
export type EnvMethod = Integration.EnvMethod
export const Method = Schema.Union([OAuthMethod, KeyMethod, EnvMethod])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "Integration.Method" })
export type Method = typeof Method.Type
export const Method = Integration.Method
export type Method = Integration.Method
export class Info extends Schema.Class<Info>("Integration.Info")({
id: ID,
@ -102,8 +66,8 @@ export class Info extends Schema.Class<Info>("Integration.Info")({
connections: Schema.mutable(Schema.Array(IntegrationConnection.Info)),
}) {}
export const Inputs = Schema.Record(Schema.String, Schema.String).annotate({ identifier: "Integration.Inputs" })
export type Inputs = typeof Inputs.Type
export const Inputs = Integration.Inputs
export type Inputs = Integration.Inputs
export type OAuthAuthorization = {
readonly url: string
@ -188,11 +152,8 @@ export const Event = {
}),
}
export const Ref = Schema.Struct({
id: ID,
name: Schema.String,
}).annotate({ identifier: "Integration.Ref" })
export type Ref = typeof Ref.Type
export const Ref = Integration.Ref
export type Ref = Integration.Ref
type Entry = {
ref: Types.DeepMutable<Ref>
@ -464,7 +425,7 @@ export const locationLayer = Layer.effect(
resolve: Effect.fn("Integration.connection.resolve")(function* (connection) {
if (connection.type === "env") {
const key = process.env[connection.name]
return key ? new Credential.Key({ type: "key", key }) : undefined
return key ? Credential.Key.make({ type: "key", key }) : undefined
}
const credential = yield* credentials.get(connection.id)
if (!credential) return undefined
@ -489,7 +450,7 @@ export const locationLayer = Layer.effect(
yield* credentials.create({
integrationID: input.integrationID,
label: input.label,
value: new Credential.Key({ type: "key", key: input.key }),
value: Credential.Key.make({ type: "key", key: input.key }),
})
yield* events.publish(Event.ConnectionUpdated, { integrationID: input.integrationID })
yield* events.publish(Event.Updated, {})

View file

@ -1,22 +1,12 @@
export * as IntegrationConnection from "./connection"
import { Schema } from "effect"
import { Credential } from "../credential"
import { Connection } from "@opencode-ai/schema/connection"
export const CredentialInfo = Schema.Struct({
type: Schema.Literal("credential"),
id: Credential.ID,
label: Schema.String,
}).annotate({ identifier: "Connection.CredentialInfo" })
export type CredentialInfo = typeof CredentialInfo.Type
export const CredentialInfo = Connection.CredentialInfo
export type CredentialInfo = Connection.CredentialInfo
export const EnvInfo = Schema.Struct({
type: Schema.Literal("env"),
name: Schema.String,
}).annotate({ identifier: "Connection.EnvInfo" })
export type EnvInfo = typeof EnvInfo.Type
export const EnvInfo = Connection.EnvInfo
export type EnvInfo = Connection.EnvInfo
export const Info = Schema.Union([CredentialInfo, EnvInfo])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "Connection.Info" })
export type Info = typeof Info.Type
export const Info = Connection.Info
export type Info = Connection.Info

View file

@ -1,9 +1,9 @@
export * as IntegrationSchema from "./schema"
import { Schema } from "effect"
import { Integration } from "@opencode-ai/schema/integration"
export const ID = Schema.String.pipe(Schema.brand("Integration.ID"))
export type ID = typeof ID.Type
export const ID = Integration.ID
export type ID = Integration.ID
export const MethodID = Schema.String.pipe(Schema.brand("Integration.MethodID"))
export type MethodID = typeof MethodID.Type
export const MethodID = Integration.MethodID
export type MethodID = Integration.MethodID

View file

@ -1,14 +1,12 @@
import { Context, Effect, Layer, Schema } from "effect"
import { Ref } from "@opencode-ai/schema/location"
import { Project } from "./project"
import { AbsolutePath, optionalOmitUndefined } from "./schema"
import { WorkspaceV2 } from "./workspace"
export * as Location from "./location"
export class Ref extends Schema.Class<Ref>("Location.Ref")({
directory: AbsolutePath,
workspaceID: Schema.optional(WorkspaceV2.ID).pipe(Schema.withConstructorDefault(Effect.succeed(undefined))),
}) {}
export { Ref }
export class Info extends Schema.Class<Info>("Location.Info")({
directory: AbsolutePath,

View file

@ -1,34 +1,12 @@
export * as ModelRequest from "./model-request"
import { Effect, Schema } from "effect"
import { ModelRequest } from "@opencode-ai/schema/model-request"
export const Generation = Schema.Struct({
maxTokens: Schema.Number.pipe(Schema.optional),
temperature: Schema.Number.pipe(Schema.optional),
topP: Schema.Number.pipe(Schema.optional),
topK: Schema.Number.pipe(Schema.optional),
frequencyPenalty: Schema.Number.pipe(Schema.optional),
presencePenalty: Schema.Number.pipe(Schema.optional),
seed: Schema.Number.pipe(Schema.optional),
stop: Schema.String.pipe(Schema.Array, Schema.mutable, Schema.optional),
})
export type Generation = typeof Generation.Type
export const Generation = ModelRequest.Generation
export type Generation = ModelRequest.Generation
export const Request = Schema.Struct({
headers: Schema.Record(Schema.String, Schema.String),
body: Schema.Record(Schema.String, Schema.Any),
generation: Generation.pipe(
Schema.optionalKey,
Schema.withConstructorDefault(Effect.succeed({})),
Schema.withDecodingDefaultKey(Effect.succeed({})),
),
options: Schema.Record(Schema.String, Schema.Any).pipe(
Schema.optionalKey,
Schema.withConstructorDefault(Effect.succeed({})),
Schema.withDecodingDefaultKey(Effect.succeed({})),
),
})
export type Request = typeof Request.Type
export const Request = ModelRequest.Request
export type Request = ModelRequest.Request
interface MutableRequest {
headers: Record<string, string>

View file

@ -1,119 +1,30 @@
import { Schema, Types } from "effect"
import { Types } from "effect"
import { Model } from "@opencode-ai/schema/model"
import { ProviderV2 } from "./provider"
import { ModelRequest } from "./model-request"
export const ID = Schema.String.pipe(Schema.brand("ModelV2.ID"))
export const ID = Model.ID
export type ID = typeof ID.Type
export const VariantID = Schema.String.pipe(Schema.brand("VariantID"))
export const VariantID = Model.VariantID
export type VariantID = typeof VariantID.Type
// Grouping of models, eg claude opus, claude sonnet
export const Family = Schema.String.pipe(Schema.brand("Family"))
export type Family = typeof Family.Type
export const Family = Model.Family
export type Family = Model.Family
export const Capabilities = Schema.Struct({
tools: Schema.Boolean,
// mime patterns, image, audio, video/*, text/*
input: Schema.String.pipe(Schema.Array, Schema.mutable),
output: Schema.String.pipe(Schema.Array, Schema.mutable),
})
export type Capabilities = typeof Capabilities.Type
export const Capabilities = Model.Capabilities
export type Capabilities = Model.Capabilities
export const Cost = Schema.Struct({
tier: Schema.Struct({
type: Schema.Literal("context"),
size: Schema.Int,
}).pipe(Schema.optional),
input: Schema.Finite,
output: Schema.Finite,
cache: Schema.Struct({
read: Schema.Finite,
write: Schema.Finite,
}),
})
export const Cost = Model.Cost
export const Ref = Schema.Struct({
id: ID,
providerID: ProviderV2.ID,
variant: VariantID.pipe(Schema.optional),
})
export const Ref = Model.Ref
export type Ref = typeof Ref.Type
export const Api = Schema.Union([
Schema.Struct({
id: ID,
...ProviderV2.AISDK.fields,
}),
Schema.Struct({
id: ID,
...ProviderV2.Native.fields,
}),
]).pipe(Schema.toTaggedUnion("type"))
export type Api = typeof Api.Type
export const Api = Model.Api
export type Api = Model.Api
export class Info extends Schema.Class<Info>("ModelV2.Info")({
id: ID,
providerID: ProviderV2.ID,
family: Family.pipe(Schema.optional),
name: Schema.String,
api: Api,
capabilities: Capabilities,
request: Schema.Struct({
...ModelRequest.Request.fields,
variant: Schema.String.pipe(Schema.optional),
}),
variants: Schema.Struct({
id: VariantID,
...ModelRequest.Request.fields,
}).pipe(Schema.Array, Schema.mutable),
time: Schema.Struct({
released: Schema.Finite,
}),
cost: Cost.pipe(Schema.Array, Schema.mutable),
status: Schema.Literals(["alpha", "beta", "deprecated", "active"]),
enabled: Schema.Boolean,
limit: Schema.Struct({
context: Schema.Int,
input: Schema.Int.pipe(Schema.optional),
output: Schema.Int,
}),
}) {
static empty(providerID: ProviderV2.ID, modelID: ID): Info {
return new Info({
id: modelID,
providerID,
name: modelID,
api: {
id: modelID,
type: "native",
settings: {},
},
capabilities: {
tools: false,
input: [],
output: [],
},
request: {
headers: {},
body: {},
generation: {},
options: {},
},
variants: [],
time: {
released: 0,
},
cost: [],
status: "active",
enabled: true,
limit: {
context: 0,
output: 0,
},
})
}
}
export const Info = Model.Info
export type Info = Model.Info
export type MutableInfo = Omit<Types.DeepMutable<Info>, "api"> & {
api: ProviderV2.MutableApi<Api>

View file

@ -1,16 +1,12 @@
export * as PermissionSchema from "./schema"
import { Schema } from "effect"
import { Permission } from "@opencode-ai/schema/permission"
export const Effect = Schema.Literals(["allow", "deny", "ask"]).annotate({ identifier: "PermissionV2.Effect" })
export type Effect = typeof Effect.Type
export const Effect = Permission.Effect
export type Effect = Permission.Effect
export const Rule = Schema.Struct({
action: Schema.String,
resource: Schema.String,
effect: Effect,
}).annotate({ identifier: "PermissionV2.Rule" })
export type Rule = typeof Rule.Type
export const Rule = Permission.Rule
export type Rule = Permission.Rule
export const Ruleset = Schema.mutable(Schema.Array(Rule)).annotate({ identifier: "PermissionV2.Ruleset" })
export type Ruleset = typeof Ruleset.Type
export const Ruleset = Permission.Ruleset
export type Ruleset = Permission.Ruleset

View file

@ -128,12 +128,11 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
return {
...authorization,
callback: authorization.callback.pipe(
Effect.map(
(credential) =>
new Credential.OAuth({
...credential,
methodID: Integration.MethodID.make(credential.methodID),
}),
Effect.map((credential) =>
Credential.OAuth.make({
...credential,
methodID: Integration.MethodID.make(credential.methodID),
}),
),
),
}
@ -142,12 +141,11 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
...authorization,
callback: (code: string) =>
authorization.callback(code).pipe(
Effect.map(
(credential) =>
new Credential.OAuth({
...credential,
methodID: Integration.MethodID.make(credential.methodID),
}),
Effect.map((credential) =>
Credential.OAuth.make({
...credential,
methodID: Integration.MethodID.make(credential.methodID),
}),
),
),
}
@ -157,12 +155,11 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
? {
refresh: (value: Credential.OAuth) =>
refresh(value).pipe(
Effect.map(
(next) =>
new Credential.OAuth({
...next,
methodID: Integration.MethodID.make(next.methodID),
}),
Effect.map((next) =>
Credential.OAuth.make({
...next,
methodID: Integration.MethodID.make(next.methodID),
}),
),
),
}

View file

@ -213,7 +213,7 @@ function refresh(methodID: Integration.MethodID, value: Pick<Credential.OAuth, "
}).pipe(
Effect.map((tokens) => {
const next = credential(methodID, tokens)
return new Credential.OAuth({ ...next, metadata: next.metadata ?? value.metadata })
return Credential.OAuth.make({ ...next, metadata: next.metadata ?? value.metadata })
}),
)
}
@ -231,7 +231,7 @@ function request<A>(url: string, init: RequestInit) {
function credential(methodID: Integration.MethodID, tokens: TokenResponse) {
const accountID = extractAccountID(tokens)
return new Credential.OAuth({
return Credential.OAuth.make({
type: "oauth",
methodID,
refresh: tokens.refresh_token,

View file

@ -274,7 +274,7 @@ function credential(http: HttpClient.HttpClient, server: string, token: typeof T
{ concurrency: 2 },
)
const org = orgs.toSorted((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id))[0]
return new Credential.OAuth({
return Credential.OAuth.make({
type: "oauth" as const,
methodID,
access: token.access_token,

View file

@ -15,9 +15,9 @@ export const Plugin = define({
effect: Effect.fn(function* (ctx) {
yield* ctx.skill.transform((draft) => {
draft.source(
new SkillV2.EmbeddedSource({
SkillV2.EmbeddedSource.make({
type: "embedded",
skill: new SkillV2.Info({
skill: SkillV2.Info.make({
name: "customize-opencode",
description:
"Use ONLY when the user is editing or creating opencode's own configuration: opencode.json, opencode.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself.",

View file

@ -1,14 +1,10 @@
export * as ProjectSchema from "./schema"
import { Schema } from "effect"
import { AbsolutePath, withStatics } from "../schema"
import { Project } from "@opencode-ai/schema/project"
import { AbsolutePath } from "../schema"
export const ID = Schema.String.pipe(
Schema.brand("Project.ID"),
withStatics((schema) => ({
global: schema.make("global"),
})),
)
export const ID = Project.ID
export type ID = typeof ID.Type
export const Vcs = Schema.Union([

View file

@ -1,75 +1,25 @@
export * as ProviderV2 from "./provider"
import { withStatics } from "./schema"
import { IntegrationSchema } from "./integration/schema"
import { Schema, Types } from "effect"
import { Types } from "effect"
import { Provider } from "@opencode-ai/schema/provider"
export const ID = Schema.String.pipe(
Schema.brand("ProviderV2.ID"),
withStatics((schema) => ({
// Well-known providers
opencode: schema.make("opencode"),
anthropic: schema.make("anthropic"),
openai: schema.make("openai"),
google: schema.make("google"),
googleVertex: schema.make("google-vertex"),
githubCopilot: schema.make("github-copilot"),
amazonBedrock: schema.make("amazon-bedrock"),
azure: schema.make("azure"),
openrouter: schema.make("openrouter"),
mistral: schema.make("mistral"),
gitlab: schema.make("gitlab"),
})),
)
export const ID = Provider.ID
export type ID = typeof ID.Type
export const AISDK = Schema.Struct({
type: Schema.Literal("aisdk"),
package: Schema.String,
url: Schema.String.pipe(Schema.optional),
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
})
export const AISDK = Provider.AISDK
export const Native = Schema.Struct({
type: Schema.Literal("native"),
url: Schema.String.pipe(Schema.optional),
settings: Schema.Record(Schema.String, Schema.Unknown),
})
export const Native = Provider.Native
export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type"))
export type Api = typeof Api.Type
export const Api = Provider.Api
export type Api = Provider.Api
export type MutableApi<T extends Api = Api> = T extends Api
? Omit<Types.DeepMutable<T>, "settings"> & (undefined extends T["settings"] ? { settings?: any } : { settings: any })
: never
export const Request = Schema.Struct({
headers: Schema.Record(Schema.String, Schema.String),
body: Schema.Record(Schema.String, Schema.Any),
})
export type Request = typeof Request.Type
export const Request = Provider.Request
export type Request = Provider.Request
export class Info extends Schema.Class<Info>("ProviderV2.Info")({
id: ID,
integrationID: IntegrationSchema.ID.pipe(Schema.optional),
name: Schema.String,
disabled: Schema.Boolean.pipe(Schema.optional),
api: Api,
request: Request,
}) {
static empty(providerID: ID): Info {
return new Info({
id: providerID,
name: providerID,
api: {
type: "native",
settings: {},
},
request: {
headers: {},
body: {},
},
})
}
}
export const Info = Provider.Info
export type Info = Provider.Info
export type MutableInfo = Omit<Types.DeepMutable<Info>, "api"> & { api: MutableApi }

View file

@ -1,6 +1,7 @@
export * as Reference from "./reference"
import { Context, Effect, Layer, Schema, Scope, Types } from "effect"
import { Reference } from "@opencode-ai/schema/reference"
import { Global } from "./global"
import { EventV2 } from "./event"
import { Repository } from "./repository"
@ -8,23 +9,14 @@ import { RepositoryCache } from "./repository-cache"
import { AbsolutePath } from "./schema"
import { State } from "./state"
export class LocalSource extends Schema.Class<LocalSource>("Reference.LocalSource")({
type: Schema.Literal("local"),
path: AbsolutePath,
description: Schema.String.pipe(Schema.optional),
hidden: Schema.Boolean.pipe(Schema.optional),
}) {}
export const LocalSource = Reference.LocalSource
export type LocalSource = Reference.LocalSource
export class GitSource extends Schema.Class<GitSource>("Reference.GitSource")({
type: Schema.Literal("git"),
repository: Schema.String,
branch: Schema.String.pipe(Schema.optional),
description: Schema.String.pipe(Schema.optional),
hidden: Schema.Boolean.pipe(Schema.optional),
}) {}
export const GitSource = Reference.GitSource
export type GitSource = Reference.GitSource
export const Source = Schema.Union([LocalSource, GitSource]).pipe(Schema.toTaggedUnion("type"))
export type Source = typeof Source.Type
export const Source = Reference.Source
export type Source = Reference.Source
export const Event = {
Updated: EventV2.define({ type: "reference.updated", schema: {} }),

View file

@ -176,12 +176,11 @@ export const layer = Layer.effect(
),
}).pipe(
Effect.map((result) =>
result.items.map(
(relative) =>
new Entry({
path: RelativePath.make(relative),
type: "file",
}),
result.items.map((relative) =>
Entry.make({
path: RelativePath.make(relative),
type: "file",
}),
),
),
Effect.catchTag("Ripgrep.InvalidPatternError", (cause) => Effect.fail(failure(cause.message, cause))),
@ -206,7 +205,7 @@ export const layer = Layer.effect(
.replace(/^[\\/]+/u, "")
.replaceAll("\\", "/")
return Effect.succeed(
new Entry({
Entry.make({
path: RelativePath.make(relative),
type: "file",
}),
@ -259,8 +258,8 @@ export const layer = Layer.effect(
.replace(/^(?:\.[\\/])+/u, "")
.replace(/^[\\/]+/u, "")
.replaceAll("\\", "/")
return new Match({
entry: new Entry({
return Match.make({
entry: Entry.make({
path: RelativePath.make(relative),
type: "file",
}),

View file

@ -1,48 +1,24 @@
import { Option, Schema, SchemaGetter } from "effect"
import { Hash } from "./util/hash"
import { Schema } from "effect"
import {
AbsolutePath,
DateTimeUtcFromMillis,
NonNegativeInt,
optionalOmitUndefined,
PositiveInt,
RelativePath,
withStatics,
} from "@opencode-ai/schema/schema"
export type ExternalID = {
readonly namespace: string
readonly key: string
export {
AbsolutePath,
DateTimeUtcFromMillis,
NonNegativeInt,
optionalOmitUndefined,
PositiveInt,
RelativePath,
withStatics,
}
export const externalID = (prefix: string, input: ExternalID) =>
`${prefix}_${Hash.sha256(JSON.stringify([input.namespace, input.key]))}`
/**
* Integer greater than zero.
*/
export const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0))
/**
* Integer greater than or equal to zero.
*/
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
/**
* Relative file path (e.g., `src/components/Button.tsx`).
*/
export const RelativePath = Schema.String.pipe(Schema.brand("RelativePath"))
export type RelativePath = Schema.Schema.Type<typeof RelativePath>
/**
* Absolute file path (e.g., `/home/user/projects/myapp/src/main.ts`).
*/
export const AbsolutePath = Schema.String.pipe(Schema.brand("AbsolutePath"))
export type AbsolutePath = Schema.Schema.Type<typeof AbsolutePath>
/**
* Optional public JSON field that can hold explicit `undefined` on the type
* side but encodes it as an omitted key, matching legacy `JSON.stringify`.
*/
export const optionalOmitUndefined = <S extends Schema.Top>(schema: S) =>
Schema.optionalKey(schema).pipe(
Schema.decodeTo(Schema.optional(schema), {
decode: SchemaGetter.passthrough({ strict: false }),
encode: SchemaGetter.transformOptional(Option.filter((value) => value !== undefined)),
}),
)
/**
* Strip `readonly` from a nested type. Stand-in for `effect`'s `Types.DeepMutable`
* until `effect:core/x228my` ("Types.DeepMutable widens unknown to `{}`") lands.
@ -71,22 +47,6 @@ export type DeepMutable<T> = T extends string | number | boolean | bigint | symb
? { -readonly [K in keyof T]: DeepMutable<T[K]> }
: T
/**
* Attach static methods to a schema object. Designed to be used with `.pipe()`:
*
* @example
* export const Foo = fooSchema.pipe(
* withStatics((schema) => ({
* zero: schema.make(0),
* from: Schema.decodeUnknownOption(schema),
* }))
* )
*/
export const withStatics =
<S extends object, M extends Record<string, unknown>>(methods: (schema: S) => M) =>
(schema: S): S & M =>
Object.assign(schema, methods(schema))
/**
* Nominal wrapper for scalar types. The class itself is a valid schema
* pass it directly to `Schema.decode`, `Schema.decodeEffect`, etc.

View file

@ -2,6 +2,7 @@ export * as SessionV2 from "./session"
export * from "./session/schema"
import { DateTime, Effect, Layer, Schema, Context, Stream } from "effect"
import { ListAnchor } from "@opencode-ai/schema/session"
import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
import { ProjectV2 } from "./project"
import { WorkspaceV2 } from "./workspace"
@ -38,12 +39,7 @@ import { SessionInput } from "./session/input"
// - by subpath
// - by workspace (home is special)
export const ListAnchor = Schema.Struct({
id: SessionSchema.ID,
time: Schema.Finite,
direction: Schema.Literals(["previous", "next"]),
})
export type ListAnchor = typeof ListAnchor.Type
export { ListAnchor }
const ListInputBase = {
workspaceID: WorkspaceV2.ID.pipe(Schema.optional),

View file

@ -1,14 +1,14 @@
import { Schema } from "effect"
import { ProviderMetadata, ToolContent } from "@opencode-ai/llm"
import { ProviderMetadata, ToolContent } from "@opencode-ai/schema/llm"
import { Delivery } from "@opencode-ai/schema/session-delivery"
import { EventV2 } from "../event"
import { ModelV2 } from "../model"
import { NonNegativeInt } from "../schema"
import { V2Schema } from "../v2-schema"
import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "../schema"
import { FileAttachment, Prompt } from "./prompt"
import { SessionSchema } from "./schema"
import { Location } from "../location"
import { RelativePath } from "../schema"
import { SessionMessageID } from "./message-id"
import { SessionMessage } from "./message"
export { FileAttachment }
@ -22,14 +22,14 @@ export const Source = Schema.Struct({
export type Source = typeof Source.Type
const Base = {
timestamp: V2Schema.DateTimeUtcFromMillis,
timestamp: DateTimeUtcFromMillis,
sessionID: SessionSchema.ID,
}
const PromptFields = {
...Base,
messageID: SessionMessageID.ID,
prompt: Prompt,
delivery: Schema.Literals(["steer", "queue"]),
delivery: Delivery,
}
const options = {
@ -45,13 +45,8 @@ const stepSettlementOptions = {
},
} as const
export const UnknownError = Schema.Struct({
type: Schema.Literal("unknown"),
message: Schema.String,
}).annotate({
identifier: "Session.Error.Unknown",
})
export type UnknownError = typeof UnknownError.Type
export const UnknownError = SessionMessage.UnknownError
export type UnknownError = SessionMessage.UnknownError
export const AgentSwitched = EventV2.define({
type: "session.next.agent.switched",

View file

@ -2,10 +2,9 @@ export * as SessionInput from "./input"
import { and, asc, eq, isNull, lte } from "drizzle-orm"
import { DateTime, Effect, Schema } from "effect"
import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
import type { Database } from "../database/database"
import type { EventV2 } from "../event"
import { NonNegativeInt } from "../schema"
import { V2Schema } from "../v2-schema"
import { SessionEvent } from "./event"
import { SessionMessage } from "./message"
import { Prompt } from "./prompt"
@ -14,24 +13,13 @@ import { SessionInputTable, SessionMessageTable } from "./sql"
type DatabaseService = Database.Interface["db"]
export const Delivery = Schema.Literals(["steer", "queue"])
export type Delivery = typeof Delivery.Type
export class Admitted extends Schema.Class<Admitted>("SessionInput.Admitted")({
admittedSeq: NonNegativeInt,
id: SessionMessage.ID,
sessionID: SessionSchema.ID,
prompt: Prompt,
delivery: Delivery,
timeCreated: V2Schema.DateTimeUtcFromMillis,
promotedSeq: NonNegativeInt.pipe(Schema.optional),
}) {}
export { Admitted, Delivery }
const decodePrompt = Schema.decodeUnknownSync(Prompt)
const encodePrompt = Schema.encodeSync(Prompt)
const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted =>
new Admitted({
Admitted.make({
admittedSeq: row.admitted_seq,
id: SessionMessage.ID.make(row.id),
sessionID: SessionSchema.ID.make(row.session_id),
@ -76,7 +64,7 @@ export const admit = Effect.fn("SessionInput.admit")(function* (
event.durable === undefined
? Effect.die("Prompt admission event is missing aggregate sequence")
: Effect.succeed(
new Admitted({
Admitted.make({
admittedSeq: event.durable.seq,
id: input.id,
sessionID: input.sessionID,

View file

@ -1,13 +1,2 @@
export * as SessionMessageID from "./message-id"
import { Schema } from "effect"
import { withStatics } from "../schema"
import { Identifier } from "../util/identifier"
export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
Schema.brand("Session.Message.ID"),
withStatics((schema) => ({
create: () => schema.make("msg_" + Identifier.ascending()),
})),
)
export type ID = typeof ID.Type
export { ID } from "@opencode-ai/schema/session-message-id"

View file

@ -102,7 +102,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
yield* SessionEvent.All.match(event, {
"session.next.agent.switched": (event) => {
return adapter.appendMessage(
new SessionMessage.AgentSwitched({
SessionMessage.AgentSwitched.make({
id: event.data.messageID,
type: "agent-switched",
metadata: event.metadata,
@ -113,7 +113,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
},
"session.next.model.switched": (event) => {
return adapter.appendMessage(
new SessionMessage.ModelSwitched({
SessionMessage.ModelSwitched.make({
id: event.data.messageID,
type: "model-switched",
metadata: event.metadata,
@ -125,7 +125,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
"session.next.moved": () => Effect.void,
"session.next.prompted": (event) => {
return adapter.appendMessage(
new SessionMessage.User({
SessionMessage.User.make({
id: event.data.messageID,
type: "user",
metadata: event.metadata,
@ -139,7 +139,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
"session.next.prompt.admitted": () => Effect.void,
"session.next.context.updated": (event) =>
adapter.appendMessage(
new SessionMessage.System({
SessionMessage.System.make({
id: event.data.messageID,
type: "system",
text: event.data.text,
@ -148,7 +148,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
),
"session.next.synthetic": (event) => {
return adapter.appendMessage(
new SessionMessage.Synthetic({
SessionMessage.Synthetic.make({
sessionID: event.data.sessionID,
text: event.data.text,
id: event.data.messageID,
@ -159,7 +159,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
},
"session.next.shell.started": (event) => {
return adapter.appendMessage(
new SessionMessage.Shell({
SessionMessage.Shell.make({
id: event.data.messageID,
type: "shell",
metadata: event.metadata,
@ -194,7 +194,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
)
}
yield* adapter.appendMessage(
new SessionMessage.Assistant({
SessionMessage.Assistant.make({
id: event.data.assistantMessageID,
type: "assistant",
agent: event.data.agent,
@ -225,7 +225,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
"session.next.text.started": (event) => {
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
draft.content.push(
castDraft(new SessionMessage.AssistantText({ type: "text", id: event.data.textID, text: "" })),
castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })),
)
})
},
@ -245,12 +245,12 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
draft.content.push(
castDraft(
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: event.data.callID,
name: event.data.name,
time: { created: event.data.timestamp },
state: new SessionMessage.ToolStatePending({ status: "pending", input: "" }),
state: SessionMessage.ToolStatePending.make({ status: "pending", input: "" }),
}),
),
)
@ -270,7 +270,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
match.provider = event.data.provider
match.time.ran = event.data.timestamp
match.state = castDraft(
new SessionMessage.ToolStateRunning({
SessionMessage.ToolStateRunning.make({
status: "running",
input: event.data.input,
structured: {},
@ -300,7 +300,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
}
match.time.completed = event.data.timestamp
match.state = castDraft(
new SessionMessage.ToolStateCompleted({
SessionMessage.ToolStateCompleted.make({
status: "completed",
input: match.state.input,
structured: event.data.structured,
@ -323,7 +323,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
}
match.time.completed = event.data.timestamp
match.state = castDraft(
new SessionMessage.ToolStateError({
SessionMessage.ToolStateError.make({
status: "error",
error: event.data.error,
input: typeof match.state.input === "string" ? {} : match.state.input,
@ -339,7 +339,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
draft.content.push(
castDraft(
new SessionMessage.AssistantReasoning({
SessionMessage.AssistantReasoning.make({
type: "reasoning",
id: event.data.reasoningID,
text: "",
@ -369,7 +369,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
"session.next.compaction.delta": () => Effect.void,
"session.next.compaction.ended": (event) => {
return adapter.appendMessage(
new SessionMessage.Compaction({
SessionMessage.Compaction.make({
id: event.data.messageID,
type: "compaction",
metadata: event.metadata,

View file

@ -1,193 +1,2 @@
export * as SessionMessage from "./message"
import { Schema } from "effect"
import { ProviderMetadata, ToolContent } from "@opencode-ai/llm"
import { ModelV2 } from "../model"
import { V2Schema } from "../v2-schema"
import { SessionEvent } from "./event"
import { Prompt } from "./prompt"
import { SessionMessageID } from "./message-id"
export const ID = SessionMessageID.ID
export type ID = typeof ID.Type
const Base = {
id: ID,
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
}),
}
export class AgentSwitched extends Schema.Class<AgentSwitched>("Session.Message.AgentSwitched")({
...Base,
type: Schema.Literal("agent-switched"),
agent: SessionEvent.AgentSwitched.data.fields.agent,
}) {}
export class ModelSwitched extends Schema.Class<ModelSwitched>("Session.Message.ModelSwitched")({
...Base,
type: Schema.Literal("model-switched"),
model: ModelV2.Ref,
}) {}
export class User extends Schema.Class<User>("Session.Message.User")({
...Base,
text: Prompt.fields.text,
files: Prompt.fields.files,
agents: Prompt.fields.agents,
type: Schema.Literal("user"),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
}),
}) {}
export class Synthetic extends Schema.Class<Synthetic>("Session.Message.Synthetic")({
...Base,
sessionID: SessionEvent.Synthetic.data.fields.sessionID,
text: SessionEvent.Synthetic.data.fields.text,
type: Schema.Literal("synthetic"),
}) {}
export class System extends Schema.Class<System>("Session.Message.System")({
...Base,
type: Schema.Literal("system"),
text: SessionEvent.ContextUpdated.data.fields.text,
}) {}
export class Shell extends Schema.Class<Shell>("Session.Message.Shell")({
...Base,
type: Schema.Literal("shell"),
callID: SessionEvent.Shell.Started.data.fields.callID,
command: SessionEvent.Shell.Started.data.fields.command,
output: Schema.String,
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
}),
}) {}
export class ToolStatePending extends Schema.Class<ToolStatePending>("Session.Message.ToolState.Pending")({
status: Schema.Literal("pending"),
input: Schema.String,
}) {}
export class ToolStateRunning extends Schema.Class<ToolStateRunning>("Session.Message.ToolState.Running")({
status: Schema.Literal("running"),
input: Schema.Record(Schema.String, Schema.Unknown),
structured: Schema.Record(Schema.String, Schema.Any),
content: ToolContent.pipe(Schema.Array),
}) {}
export class ToolStateCompleted extends Schema.Class<ToolStateCompleted>("Session.Message.ToolState.Completed")({
status: Schema.Literal("completed"),
input: Schema.Record(Schema.String, Schema.Unknown),
attachments: SessionEvent.FileAttachment.pipe(Schema.Array, Schema.optional),
content: ToolContent.pipe(Schema.Array),
outputPaths: SessionEvent.Tool.Success.data.fields.outputPaths,
structured: Schema.Record(Schema.String, Schema.Any),
result: SessionEvent.Tool.Success.data.fields.result,
}) {}
export class ToolStateError extends Schema.Class<ToolStateError>("Session.Message.ToolState.Error")({
status: Schema.Literal("error"),
input: Schema.Record(Schema.String, Schema.Unknown),
content: ToolContent.pipe(Schema.Array),
structured: Schema.Record(Schema.String, Schema.Any),
error: SessionEvent.UnknownError,
result: SessionEvent.Tool.Failed.data.fields.result,
}) {}
export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).pipe(
Schema.toTaggedUnion("status"),
)
export type ToolState = Schema.Schema.Type<typeof ToolState>
export class AssistantTool extends Schema.Class<AssistantTool>("Session.Message.Assistant.Tool")({
type: Schema.Literal("tool"),
id: Schema.String,
name: Schema.String,
provider: Schema.Struct({
executed: Schema.Boolean,
metadata: ProviderMetadata.pipe(Schema.optional),
resultMetadata: ProviderMetadata.pipe(Schema.optional),
}).pipe(Schema.optional),
state: ToolState,
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
ran: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
pruned: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
}),
}) {}
export class AssistantText extends Schema.Class<AssistantText>("Session.Message.Assistant.Text")({
type: Schema.Literal("text"),
id: Schema.String,
text: Schema.String,
}) {}
export class AssistantReasoning extends Schema.Class<AssistantReasoning>("Session.Message.Assistant.Reasoning")({
type: Schema.Literal("reasoning"),
id: Schema.String,
text: Schema.String,
providerMetadata: ProviderMetadata.pipe(Schema.optional),
}) {}
export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, AssistantTool]).pipe(
Schema.toTaggedUnion("type"),
)
export type AssistantContent = Schema.Schema.Type<typeof AssistantContent>
export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistant")({
...Base,
type: Schema.Literal("assistant"),
agent: Schema.String,
model: SessionEvent.Step.Started.data.fields.model,
content: AssistantContent.pipe(Schema.Array),
snapshot: Schema.Struct({
start: Schema.String.pipe(Schema.optional),
end: Schema.String.pipe(Schema.optional),
}).pipe(Schema.optional),
finish: Schema.String.pipe(Schema.optional),
cost: Schema.Finite.pipe(Schema.optional),
tokens: Schema.Struct({
input: Schema.Finite,
output: Schema.Finite,
reasoning: Schema.Finite,
cache: Schema.Struct({
read: Schema.Finite,
write: Schema.Finite,
}),
}).pipe(Schema.optional),
error: SessionEvent.Step.Failed.data.fields.error.pipe(Schema.optional),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
}),
}) {}
export class Compaction extends Schema.Class<Compaction>("Session.Message.Compaction")({
type: Schema.Literal("compaction"),
reason: SessionEvent.Compaction.Started.data.fields.reason,
summary: Schema.String,
recent: Schema.String,
...Base,
}) {}
export const Message = Schema.Union([
AgentSwitched,
ModelSwitched,
User,
Synthetic,
System,
Shell,
Assistant,
Compaction,
])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "Session.Message" })
export type Message = Schema.Schema.Type<typeof Message>
export type Type = Message["type"]
export * from "@opencode-ai/schema/session-message"

View file

@ -1,46 +1 @@
import * as Schema from "effect/Schema"
export class Source extends Schema.Class<Source>("Prompt.Source")({
start: Schema.Finite,
end: Schema.Finite,
text: Schema.String,
}) {}
export class FileAttachment extends Schema.Class<FileAttachment>("Prompt.FileAttachment")({
uri: Schema.String,
mime: Schema.String,
name: Schema.String.pipe(Schema.optional),
description: Schema.String.pipe(Schema.optional),
source: Source.pipe(Schema.optional),
}) {
static create(input: FileAttachment) {
return new FileAttachment({
uri: input.uri,
mime: input.mime,
name: input.name,
description: input.description,
source: input.source,
})
}
}
export class AgentAttachment extends Schema.Class<AgentAttachment>("Prompt.AgentAttachment")({
name: Schema.String,
source: Source.pipe(Schema.optional),
}) {}
export class Prompt extends Schema.Class<Prompt>("Prompt")({
text: Schema.String,
files: Schema.Array(FileAttachment).pipe(Schema.optional),
agents: Schema.Array(AgentAttachment).pipe(Schema.optional),
}) {
static readonly equivalence = Schema.toEquivalence(Prompt)
static fromUserMessage(input: Pick<Prompt, "text" | "files" | "agents">) {
return new Prompt({
text: input.text,
...(input.files === undefined ? {} : { files: input.files }),
...(input.agents === undefined ? {} : { agents: input.agents }),
})
}
}
export { AgentAttachment, FileAttachment, Prompt, Source } from "@opencode-ai/schema/prompt"

View file

@ -1,49 +1,9 @@
export * as SessionSchema from "./schema"
import { Schema } from "effect"
import { Location } from "../location"
import { ModelV2 } from "../model"
import { ProjectV2 } from "../project"
import { externalID, type ExternalID, RelativePath, optionalOmitUndefined, withStatics } from "../schema"
import { Identifier } from "../util/identifier"
import { V2Schema } from "../v2-schema"
import { AgentV2 } from "../agent"
import { Session } from "@opencode-ai/schema/session"
export const ID = Schema.String.check(Schema.isStartsWith("ses")).pipe(
Schema.brand("SessionID"),
withStatics((schema) => {
const create = () => schema.make("ses_" + Identifier.descending())
return {
create,
descending: (id?: string) => (id === undefined ? create() : schema.make(id)),
fromExternal: (input: ExternalID) => schema.make(externalID("ses", input)),
}
}),
)
export const ID = Session.ID
export type ID = typeof ID.Type
export class Info extends Schema.Class<Info>("SessionV2.Info")({
id: ID,
parentID: ID.pipe(optionalOmitUndefined),
projectID: ProjectV2.ID,
agent: AgentV2.ID.pipe(Schema.optional),
model: ModelV2.Ref.pipe(Schema.optional),
cost: Schema.Finite,
tokens: Schema.Struct({
input: Schema.Finite,
output: Schema.Finite,
reasoning: Schema.Finite,
cache: Schema.Struct({
read: Schema.Finite,
write: Schema.Finite,
}),
}),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
updated: V2Schema.DateTimeUtcFromMillis,
archived: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
}),
title: Schema.String,
location: Location.Ref,
subpath: RelativePath.pipe(Schema.optional),
}) {}
export const Info = Session.Info
export type Info = Session.Info

View file

@ -2,57 +2,29 @@ export * as SkillV2 from "./skill"
import path from "path"
import { Context, Effect, Layer, Schema, Types } from "effect"
import { Skill } from "@opencode-ai/schema/skill"
import { AgentV2 } from "./agent"
import { ConfigMarkdown } from "./config/markdown"
import { FSUtil } from "./fs-util"
import { PermissionV2 } from "./permission"
import { AbsolutePath, withStatics } from "./schema"
import { AbsolutePath } from "./schema"
import { SkillDiscovery } from "./skill/discovery"
import { State } from "./state"
export class DirectorySource extends Schema.Class<DirectorySource>("SkillV2.DirectorySource")({
type: Schema.Literal("directory"),
path: AbsolutePath,
}) {}
export const DirectorySource = Skill.DirectorySource
export type DirectorySource = Skill.DirectorySource
export class UrlSource extends Schema.Class<UrlSource>("SkillV2.UrlSource")({
type: Schema.Literal("url"),
url: Schema.String,
}) {}
export const UrlSource = Skill.UrlSource
export type UrlSource = Skill.UrlSource
export class EmbeddedSource extends Schema.Class<EmbeddedSource>("SkillV2.EmbeddedSource")({
type: Schema.Literal("embedded"),
skill: Schema.suspend(() => Info),
}) {}
export const EmbeddedSource = Skill.EmbeddedSource
export type EmbeddedSource = Skill.EmbeddedSource
export const Source = Schema.Union([DirectorySource, UrlSource, EmbeddedSource]).pipe(
Schema.toTaggedUnion("type"),
Schema.annotate({ identifier: "SkillV2.Source" }),
withStatics(() => ({
equals: (a: DirectorySource | UrlSource | EmbeddedSource, b: DirectorySource | UrlSource | EmbeddedSource) => {
if (a.type !== b.type) return false
if (a.type === "directory" && b.type === "directory") return a.path === b.path
if (a.type === "url" && b.type === "url") return a.url === b.url
if (a.type === "embedded" && b.type === "embedded") return a.skill.name === b.skill.name
return false
},
key: (source: DirectorySource | UrlSource | EmbeddedSource) =>
source.type === "directory"
? `directory:${source.path}`
: source.type === "url"
? `url:${source.url}`
: `embedded:${source.skill.name}`,
})),
)
export const Source = Skill.Source
export type Source = typeof Source.Type
export class Info extends Schema.Class<Info>("SkillV2.Info")({
name: Schema.String,
description: Schema.String.pipe(Schema.optional),
slash: Schema.Boolean.pipe(Schema.optional),
location: AbsolutePath,
content: Schema.String,
}) {}
export const Info = Skill.Info
export type Info = Skill.Info
export const available = (skills: ReadonlyArray<Info>, agent: AgentV2.Info) =>
skills.filter((skill) => PermissionV2.evaluate("skill", skill.name, agent.permissions).effect !== "deny")
@ -119,15 +91,13 @@ export const layer = Layer.effect(
? path.basename(filepath, ".md")
: undefined
if (!name) continue
skills.push(
new Info({
name,
description: frontmatter.description,
slash: frontmatter.slash,
location: AbsolutePath.make(filepath),
content: markdown.content,
}),
)
skills.push({
name,
description: frontmatter.description,
slash: frontmatter.slash,
location: AbsolutePath.make(filepath),
content: markdown.content,
})
}
}
return skills

View file

@ -79,12 +79,11 @@ export const layer = Layer.effectDiscard(
})
.pipe(
Effect.map((result) =>
result.map(
(entry) =>
new FileSystem.Entry({
...entry,
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
}),
result.map((entry) =>
FileSystem.Entry.make({
...entry,
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
}),
),
),
)

View file

@ -102,23 +102,22 @@ export const layer = Layer.effectDiscard(
})
.pipe(
Effect.map((result) =>
result.map(
(match) =>
new FileSystem.Match({
...match,
entry: new FileSystem.Entry({
...match.entry,
path: RelativePath.make(
path.relative(
location.directory,
path.resolve(
info?.type === "Directory" ? target : path.dirname(target),
match.entry.path,
),
result.map((match) =>
FileSystem.Match.make({
...match,
entry: FileSystem.Entry.make({
...match.entry,
path: RelativePath.make(
path.relative(
location.directory,
path.resolve(
info?.type === "Directory" ? target : path.dirname(target),
match.entry.path,
),
),
}),
),
}),
}),
),
),
)

View file

@ -335,7 +335,7 @@ export const list = Effect.fn("ReadTool.list")(function* (fs: FSUtil.Interface,
const info = yield* fs.stat(target).pipe(Effect.catch(() => Effect.void))
const type = info?.type === "Directory" ? "directory" : info?.type === "File" ? "file" : undefined
if (!type) return
return new FileSystem.Entry({
return FileSystem.Entry.make({
path: RelativePath.make(item.name + (type === "directory" ? path.sep : "")),
type,
})

View file

@ -1,48 +1 @@
import { randomBytes } from "crypto"
export namespace Identifier {
const LENGTH = 26
// State for monotonic ID generation
let lastTimestamp = 0
let counter = 0
export function ascending() {
return create(false)
}
export function descending() {
return create(true)
}
function randomBase62(length: number): string {
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
let result = ""
const bytes = randomBytes(length)
for (let i = 0; i < length; i++) {
result += chars[bytes[i] % 62]
}
return result
}
export function create(descending: boolean, timestamp?: number): string {
const currentTimestamp = timestamp ?? Date.now()
if (currentTimestamp !== lastTimestamp) {
lastTimestamp = currentTimestamp
counter = 0
}
counter++
let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter)
now = descending ? ~now : now
const timeBytes = Buffer.alloc(6)
for (let i = 0; i < 6; i++) {
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff))
}
return timeBytes.toString("hex") + randomBase62(LENGTH - 12)
}
}
export * as Identifier from "@opencode-ai/schema/identifier"

View file

@ -1,10 +1,3 @@
import { DateTime, Schema, SchemaGetter } from "effect"
export const DateTimeUtcFromMillis = Schema.Finite.pipe(
Schema.decodeTo(Schema.DateTimeUtc, {
decode: SchemaGetter.transform((value) => DateTime.makeUnsafe(value)),
encode: SchemaGetter.transform((value) => DateTime.toEpochMillis(value)),
}),
)
export * as V2Schema from "./v2-schema"
export { DateTimeUtcFromMillis } from "@opencode-ai/schema/schema"

View file

@ -1,18 +1,6 @@
export * as WorkspaceV2 from "./workspace"
import { Schema } from "effect"
import { withStatics } from "./schema"
import { Identifier } from "./util/identifier"
import { Workspace } from "@opencode-ai/schema/workspace"
export const ID = Schema.String.check(Schema.isStartsWith("wrk")).pipe(
Schema.brand("WorkspaceV2.ID"),
withStatics((schema) => ({
ascending: (id?: string) => {
if (!id) return schema.make("wrk_" + Identifier.ascending())
if (!id.startsWith("wrk")) throw new Error(`ID ${id} does not start with wrk`)
return schema.make(id)
},
create: () => schema.make("wrk_" + Identifier.ascending()),
})),
)
export const ID = Workspace.ID
export type ID = typeof ID.Type

View file

@ -61,7 +61,7 @@ describe("CatalogV2", () => {
yield* credentials.create({
integrationID,
label: "First",
value: new Credential.Key({ type: "key", key: "first", metadata: { tenant: "one" } }),
value: Credential.Key.make({ type: "key", key: "first", metadata: { tenant: "one" } }),
})
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toEqual([ProviderV2.ID.make("test")])
@ -69,7 +69,7 @@ describe("CatalogV2", () => {
yield* credentials.create({
integrationID,
label: "Second",
value: new Credential.Key({ type: "key", key: "second", metadata: { tenant: "two" } }),
value: Credential.Key.make({ type: "key", key: "second", metadata: { tenant: "two" } }),
})
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toEqual([ProviderV2.ID.make("test")])
expect(required(yield* catalog.provider.get(ProviderV2.ID.make("test"))).request.body).toEqual({})
@ -98,7 +98,7 @@ describe("CatalogV2", () => {
yield* (yield* Credential.Service).create({
integrationID,
value: new Credential.Key({ type: "key", key: "secret" }),
value: Credential.Key.make({ type: "key", key: "secret" }),
})
expect((yield* catalog.provider.available()).map((provider) => provider.id)).toEqual([providerID])

View file

@ -27,7 +27,7 @@ describe("CommandV2", () => {
})
expect(yield* command.get("review")).toEqual(
new CommandV2.Info({
CommandV2.Info.make({
name: "review",
template: "Second",
description: "Review code",
@ -39,7 +39,7 @@ describe("CommandV2", () => {
}),
)
expect(yield* command.list()).toEqual([
new CommandV2.Info({
CommandV2.Info.make({
name: "review",
template: "Second",
description: "Review code",

View file

@ -59,7 +59,7 @@ Review files`,
)
expect(yield* command.list()).toEqual([
new CommandV2.Info({
CommandV2.Info.make({
name: "review",
template: "Review files",
description: "File review",
@ -71,8 +71,8 @@ Review files`,
},
subtask: true,
}),
new CommandV2.Info({ name: "empty", template: "" }),
new CommandV2.Info({ name: "nested/docs", template: "Write docs" }),
CommandV2.Info.make({ name: "empty", template: "" }),
CommandV2.Info.make({ name: "nested/docs", template: "Write docs" }),
])
}),
),

View file

@ -59,21 +59,21 @@ describe("ConfigSkillPlugin.Plugin", () => {
)
expect(sources).toEqual([
new SkillV2.DirectorySource({
SkillV2.DirectorySource.make({
type: "directory",
path: AbsolutePath.make(path.join("/repo/.opencode", "skill")),
}),
new SkillV2.DirectorySource({
SkillV2.DirectorySource.make({
type: "directory",
path: AbsolutePath.make(path.join("/repo/.opencode", "skills")),
}),
new SkillV2.DirectorySource({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) }),
new SkillV2.DirectorySource({
SkillV2.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) }),
SkillV2.DirectorySource.make({
type: "directory",
path: AbsolutePath.make(path.join("/home/test", "shared-skills")),
}),
new SkillV2.DirectorySource({ type: "directory", path: AbsolutePath.make("/opt/skills") }),
new SkillV2.UrlSource({ type: "url", url: "https://example.test/skills/" }),
SkillV2.DirectorySource.make({ type: "directory", path: AbsolutePath.make("/opt/skills") }),
SkillV2.UrlSource.make({ type: "url", url: "https://example.test/skills/" }),
])
}),
)

View file

@ -14,7 +14,7 @@ describe("Credential", () => {
const created = yield* credentials.create({
integrationID,
label: "Work",
value: new Credential.Key({ type: "key", key: "secret" }),
value: Credential.Key.make({ type: "key", key: "secret" }),
})
expect(yield* credentials.list(integrationID)).toEqual([created])
@ -24,7 +24,7 @@ describe("Credential", () => {
const replacement = yield* credentials.create({
integrationID,
label: "Replacement",
value: new Credential.Key({ type: "key", key: "replacement" }),
value: Credential.Key.make({ type: "key", key: "replacement" }),
})
expect(yield* credentials.list(integrationID)).toEqual([replacement])

View file

@ -4,9 +4,8 @@ import { EventV2 } from "@opencode-ai/core/event"
import { Database } from "@opencode-ai/core/database/database"
import { EventSequenceTable, EventTable } from "@opencode-ai/core/event/sql"
import { Location } from "@opencode-ai/core/location"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { AbsolutePath, DateTimeUtcFromMillis } from "@opencode-ai/core/schema"
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { V2Schema } from "@opencode-ai/core/v2-schema"
import { eq } from "drizzle-orm"
import { location } from "./fixture/location"
import { testEffect } from "./lib/effect"
@ -79,24 +78,11 @@ const SyncTimestamp = EventV2.define({
},
schema: {
id: Schema.String,
timestamp: V2Schema.DateTimeUtcFromMillis,
timestamp: DateTimeUtcFromMillis,
},
})
describe("EventV2", () => {
it.effect("derives stable namespaced external IDs", () =>
Effect.sync(() => {
const input = { namespace: "opencord.agent-input", key: "input-1" }
expect(EventV2.ID.fromExternal(input)).toBe(EventV2.ID.fromExternal(input))
expect(EventV2.ID.fromExternal(input)).toMatch(/^evt_[a-f0-9]{64}$/)
expect(EventV2.ID.fromExternal({ ...input, namespace: "another-app" })).not.toBe(EventV2.ID.fromExternal(input))
expect(EventV2.ID.fromExternal({ namespace: "a:b", key: "c" })).not.toBe(
EventV2.ID.fromExternal({ namespace: "a", key: "b:c" }),
)
}),
)
it.effect("publishes events with the current location", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service

View file

@ -125,7 +125,7 @@ describe("Integration", () => {
expect.objectContaining({
integrationID,
label: "Work",
value: new Credential.Key({ type: "key", key: "secret" }),
value: Credential.Key.make({ type: "key", key: "secret" }),
}),
])
expect((yield* Fiber.join(updated)).length).toBe(1)
@ -149,7 +149,7 @@ describe("Integration", () => {
instructions: "Paste the code",
callback: (code: string) =>
Effect.succeed(
new Credential.OAuth({
Credential.OAuth.make({
type: "oauth",
methodID,
access: "access",
@ -175,7 +175,7 @@ describe("Integration", () => {
expect.objectContaining({
integrationID,
label: "Personal",
value: new Credential.OAuth({
value: Credential.OAuth.make({
type: "oauth",
methodID,
access: "access",
@ -238,7 +238,7 @@ describe("Integration", () => {
url: "https://example.com/authorize",
instructions: "Sign in",
callback: Effect.succeed(
new Credential.OAuth({ type: "oauth", methodID, access: "access", refresh: "refresh", expires: 1 }),
Credential.OAuth.make({ type: "oauth", methodID, access: "access", refresh: "refresh", expires: 1 }),
),
}),
}),
@ -315,12 +315,12 @@ describe("Integration", () => {
const work = yield* credentials.create({
integrationID,
label: "Work",
value: new Credential.Key({ type: "key", key: "a" }),
value: Credential.Key.make({ type: "key", key: "a" }),
})
const personal = yield* credentials.create({
integrationID,
label: "Personal",
value: new Credential.Key({ type: "key", key: "b" }),
value: Credential.Key.make({ type: "key", key: "b" }),
})
// Stored credentials and detected env vars appear as connections.

View file

@ -52,14 +52,28 @@ const it = testEffect(
)
describe("LocationServiceMap", () => {
it.effect("compares equivalent location refs by value", () =>
Effect.sync(() => {
const directory = AbsolutePath.make("/project")
expect(Equal.equals(Location.Ref.make({ directory }), Location.Ref.make({ directory }))).toBe(true)
expect(Hash.hash(Location.Ref.make({ directory }))).toBe(
Hash.hash(Location.Ref.make({ directory, workspaceID: undefined })),
)
}),
it.live("reuses cached services for constructed and decoded location refs", () =>
Effect.acquireRelease(
Effect.promise(() => tmpdir()),
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
).pipe(
Effect.flatMap((dir) =>
Effect.scoped(
Effect.gen(function* () {
const locations = yield* LocationServiceMap
const directory = AbsolutePath.make(dir.path)
const constructed = Location.Ref.make({ directory })
const decoded = Schema.decodeUnknownSync(Location.Ref)({ directory })
expect(constructed).toEqual({ directory, workspaceID: undefined })
expect(decoded).toEqual(constructed)
expect(Equal.equals(constructed, decoded)).toBe(true)
expect(Hash.hash(constructed)).toBe(Hash.hash(decoded))
expect(yield* locations.contextEffect(constructed)).toBe(yield* locations.contextEffect(decoded))
}),
),
),
),
)
it.live("isolates location state while sharing location policy with catalog", () =>

View file

@ -176,12 +176,11 @@ export function integrationHost(integration: Integration.Interface): PluginConte
return {
...authorization,
callback: authorization.callback.pipe(
Effect.map(
(credential) =>
new Credential.OAuth({
...credential,
methodID: Integration.MethodID.make(credential.methodID),
}),
Effect.map((credential) =>
Credential.OAuth.make({
...credential,
methodID: Integration.MethodID.make(credential.methodID),
}),
),
),
}
@ -190,12 +189,11 @@ export function integrationHost(integration: Integration.Interface): PluginConte
...authorization,
callback: (code: string) =>
authorization.callback(code).pipe(
Effect.map(
(credential) =>
new Credential.OAuth({
...credential,
methodID: Integration.MethodID.make(credential.methodID),
}),
Effect.map((credential) =>
Credential.OAuth.make({
...credential,
methodID: Integration.MethodID.make(credential.methodID),
}),
),
),
}
@ -205,12 +203,11 @@ export function integrationHost(integration: Integration.Interface): PluginConte
? {
refresh: (value: Credential.OAuth) =>
refresh(value).pipe(
Effect.map(
(next) =>
new Credential.OAuth({
...next,
methodID: Integration.MethodID.make(next.methodID),
}),
Effect.map((next) =>
Credential.OAuth.make({
...next,
methodID: Integration.MethodID.make(next.methodID),
}),
),
),
}

View file

@ -26,7 +26,7 @@ describe("AlibabaPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("qwen")),
api: { id: ModelV2.ID.make("qwen"), type: "aisdk", package: "test-provider" },
}),
@ -43,7 +43,7 @@ describe("AlibabaPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("qwen")),
api: { id: ModelV2.ID.make("qwen"), type: "aisdk", package: "test-provider" },
}),
@ -60,7 +60,7 @@ describe("AlibabaPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-alibaba"), ModelV2.ID.make("qwen")),
api: { id: ModelV2.ID.make("qwen"), type: "aisdk", package: "test-provider" },
}),
@ -79,7 +79,7 @@ describe("AlibabaPlugin", () => {
const plugin = yield* PluginV2.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const item = new ModelV2.Info({
const item = ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("qwen-plus"), type: "aisdk", package: "test-provider" },
})

View file

@ -83,7 +83,7 @@ describe("AmazonBedrockPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const bedrock = new ProviderV2.Info({
const bedrock = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.amazonBedrock),
api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" },
request: {
@ -114,7 +114,7 @@ describe("AmazonBedrockPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -139,7 +139,7 @@ describe("AmazonBedrockPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -173,7 +173,7 @@ describe("AmazonBedrockPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: {
id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
@ -197,7 +197,7 @@ describe("AmazonBedrockPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -216,7 +216,7 @@ describe("AmazonBedrockPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -235,7 +235,7 @@ describe("AmazonBedrockPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -255,7 +255,7 @@ describe("AmazonBedrockPlugin", () => {
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -284,7 +284,7 @@ describe("AmazonBedrockPlugin", () => {
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -312,7 +312,7 @@ describe("AmazonBedrockPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")),
api: {
id: ModelV2.ID.make("openai.gpt-5.5"),
@ -343,7 +343,7 @@ describe("AmazonBedrockPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")),
api: {
id: ModelV2.ID.make("openai.gpt-5.5"),
@ -355,7 +355,7 @@ describe("AmazonBedrockPlugin", () => {
options: { baseURL: "https://bedrock-mantle.us-east-2.api.aws/openai/v1", region: "us-east-2" },
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-oss-safeguard-120b")),
api: {
id: ModelV2.ID.make("openai.gpt-oss-safeguard-120b"),
@ -376,7 +376,7 @@ describe("AmazonBedrockPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: {
id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
@ -407,7 +407,7 @@ describe("AmazonBedrockPlugin", () => {
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: {
id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
@ -442,7 +442,7 @@ describe("AmazonBedrockPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -450,7 +450,7 @@ describe("AmazonBedrockPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -458,7 +458,7 @@ describe("AmazonBedrockPlugin", () => {
options: { region: "eu-west-1" },
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("global.anthropic.claude-sonnet-4-5")),
api: {
id: ModelV2.ID.make("global.anthropic.claude-sonnet-4-5"),
@ -470,7 +470,7 @@ describe("AmazonBedrockPlugin", () => {
options: { region: "eu-west-1" },
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -478,7 +478,7 @@ describe("AmazonBedrockPlugin", () => {
options: { region: "ap-northeast-1" },
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -503,7 +503,7 @@ describe("AmazonBedrockPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -589,7 +589,7 @@ describe("AmazonBedrockPlugin", () => {
yield* addPlugin()
for (const item of cases) {
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make(item.modelID)),
api: { id: ModelV2.ID.make(item.modelID), type: "aisdk", package: "test-provider" },
}),
@ -608,7 +608,7 @@ describe("AmazonBedrockPlugin", () => {
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),

View file

@ -29,7 +29,7 @@ describe("AnthropicPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const item = new ProviderV2.Info({
const item = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.anthropic),
api: { type: "aisdk", package: "@ai-sdk/anthropic" },
request: { headers: { Existing: "1" }, body: {} },
@ -64,7 +64,7 @@ describe("AnthropicPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-anthropic"), ModelV2.ID.make("claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "@ai-sdk/anthropic" },
}),
@ -81,7 +81,7 @@ describe("AnthropicPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "@ai-sdk/anthropic" },
}),

View file

@ -87,11 +87,11 @@ describe("AzureCognitiveServicesPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const azure = new ProviderV2.Info({
const azure = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services")),
api: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
})
const openai = new ProviderV2.Info({
const openai = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.openai),
api: { type: "aisdk", package: "test-provider" },
})
@ -120,7 +120,7 @@ describe("AzureCognitiveServicesPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -138,7 +138,7 @@ describe("AzureCognitiveServicesPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -146,7 +146,7 @@ describe("AzureCognitiveServicesPlugin", () => {
options: {},
})
const ignored = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -166,7 +166,7 @@ describe("AzureCognitiveServicesPlugin", () => {
const sdk = fakeSelectorSdk(calls)
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("messages-deployment")),
api: { id: ModelV2.ID.make("messages-deployment"), type: "aisdk", package: "test-provider" },
}),
@ -174,7 +174,7 @@ describe("AzureCognitiveServicesPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("chat-deployment")),
api: { id: ModelV2.ID.make("chat-deployment"), type: "aisdk", package: "test-provider" },
}),
@ -182,7 +182,7 @@ describe("AzureCognitiveServicesPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("language-deployment")),
api: { id: ModelV2.ID.make("language-deployment"), type: "aisdk", package: "test-provider" },
}),

View file

@ -80,7 +80,7 @@ describe("AzurePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const azure = new ProviderV2.Info({
const azure = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.azure),
api: { type: "aisdk", package: "@ai-sdk/azure" },
request: { headers: {}, body: { resourceName: "from-config" } },
@ -103,7 +103,7 @@ describe("AzurePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const azure = new ProviderV2.Info({
const azure = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.azure),
api: { type: "aisdk", package: "@ai-sdk/azure" },
request: { headers: {}, body: { resourceName: "" } },
@ -124,7 +124,7 @@ describe("AzurePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const azure = new ProviderV2.Info({
const azure = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.azure),
api: { type: "aisdk", package: "@ai-sdk/azure" },
request: { headers: {}, body: { resourceName: " " } },
@ -147,7 +147,7 @@ describe("AzurePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -166,7 +166,7 @@ describe("AzurePlugin", () => {
yield* addPlugin()
const exit = yield* aisdk
.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -186,7 +186,7 @@ describe("AzurePlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -204,7 +204,7 @@ describe("AzurePlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -222,7 +222,7 @@ describe("AzurePlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
request: { headers: {}, body: { useCompletionUrls: true } },
@ -241,7 +241,7 @@ describe("AzurePlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -249,7 +249,7 @@ describe("AzurePlugin", () => {
options: {},
})
const ignored = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("deployment")),
api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" },
}),
@ -272,7 +272,7 @@ describe("AzurePlugin", () => {
}
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("messages-deployment")),
api: { id: ModelV2.ID.make("messages-deployment"), type: "aisdk", package: "test-provider" },
}),
@ -280,7 +280,7 @@ describe("AzurePlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("language-deployment")),
api: { id: ModelV2.ID.make("language-deployment"), type: "aisdk", package: "test-provider" },
}),

View file

@ -64,7 +64,7 @@ describe("CerebrasPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(
ProviderV2.ID.make("custom-cerebras"),
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
@ -90,7 +90,7 @@ describe("CerebrasPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(
ProviderV2.ID.make("custom-cerebras"),
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
@ -115,7 +115,7 @@ describe("CerebrasPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(
ProviderV2.ID.make("custom-cerebras"),
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),

View file

@ -116,7 +116,7 @@ describe("CloudflareAIGatewayPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -137,7 +137,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -181,7 +181,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -210,7 +210,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -247,7 +247,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -278,7 +278,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -300,7 +300,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -323,7 +323,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -352,7 +352,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -375,7 +375,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(
ProviderV2.ID.make("cloudflare-ai-gateway"),
ModelV2.ID.make("anthropic/claude-sonnet-4-5"),
@ -410,7 +410,7 @@ describe("CloudflareAIGatewayPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),

View file

@ -93,7 +93,7 @@ describe("CloudflareWorkersAIPlugin", () => {
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai")))
const sdk = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
api: { id: ModelV2.ID.make("@cf/model"), ...provider.api },
}),
@ -136,7 +136,7 @@ describe("CloudflareWorkersAIPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
api: {
id: ModelV2.ID.make("@cf/model"),
@ -180,7 +180,7 @@ describe("CloudflareWorkersAIPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
api: {
id: ModelV2.ID.make("@cf/model"),
@ -212,7 +212,7 @@ describe("CloudflareWorkersAIPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
api: {
id: ModelV2.ID.make("@cf/model"),
@ -241,7 +241,7 @@ describe("CloudflareWorkersAIPlugin", () => {
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("@cf/api-model"), type: "aisdk", package: "test-provider" },
}),
@ -260,7 +260,7 @@ describe("CloudflareWorkersAIPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
api: {
id: ModelV2.ID.make("@cf/model"),

View file

@ -54,7 +54,7 @@ describe("CoherePlugin", () => {
yield* addPlugin()
const ignored = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cohere"), ModelV2.ID.make("command")),
api: { id: ModelV2.ID.make("command"), type: "aisdk", package: "test-provider" },
}),
@ -64,7 +64,7 @@ describe("CoherePlugin", () => {
expect(ignored.sdk).toBeUndefined()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cohere"), ModelV2.ID.make("command")),
api: { id: ModelV2.ID.make("command"), type: "aisdk", package: "test-provider" },
}),
@ -81,7 +81,7 @@ describe("CoherePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-cohere"), ModelV2.ID.make("command-r-plus")),
api: { id: ModelV2.ID.make("command-r-plus"), type: "aisdk", package: "test-provider" },
}),
@ -106,7 +106,7 @@ describe("CoherePlugin", () => {
const sdk = fakeSelectorSdk(calls)
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cohere"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("command-r-plus"), type: "aisdk", package: "test-provider" },
}),

View file

@ -46,7 +46,7 @@ describe("DeepInfraPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" },
}),
@ -64,7 +64,7 @@ describe("DeepInfraPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-deepinfra"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" },
}),
@ -83,7 +83,7 @@ describe("DeepInfraPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" },
}),
@ -109,7 +109,7 @@ describe("DeepInfraPlugin", () => {
yield* Effect.forEach(packages, (item) =>
Effect.gen(function* () {
const ignored = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" },
}),
@ -120,7 +120,7 @@ describe("DeepInfraPlugin", () => {
}),
)
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" },
}),
@ -139,7 +139,7 @@ describe("DeepInfraPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const sdkEvent = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct")),
api: {
id: ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct"),

View file

@ -51,7 +51,7 @@ describe("DynamicProviderPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")),
api: { id: ModelV2.ID.make("test-model"), type: "aisdk", package: fixtureProvider },
}),
@ -69,7 +69,7 @@ describe("DynamicProviderPlugin", () => {
const sdk = { marker: "existing" }
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")),
api: { id: ModelV2.ID.make("test-model"), type: "aisdk", package: fixtureProvider },
}),
@ -86,7 +86,7 @@ describe("DynamicProviderPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("test-model")),
api: { id: ModelV2.ID.make("test-model"), type: "aisdk", package: fixtureProvider },
}),
@ -102,7 +102,7 @@ describe("DynamicProviderPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin(npmEntrypoint(fixtureProviderPath))
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("npm-provider"), ModelV2.ID.make("test-model")),
api: { id: ModelV2.ID.make("test-model"), type: "aisdk", package: "fixture-provider" },
}),
@ -119,7 +119,7 @@ describe("DynamicProviderPlugin", () => {
yield* addPlugin(npmEntrypoint())
const exit = yield* aisdk
.language(
new ModelV2.Info({
ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("missing-entrypoint"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("alias"), type: "aisdk", package: "fixture-provider" },
}),
@ -136,7 +136,7 @@ describe("DynamicProviderPlugin", () => {
yield* addPlugin()
const exit = yield* aisdk
.language(
new ModelV2.Info({
ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("bad-import"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("alias"), type: "aisdk", package: "file:///missing/provider-factory.js" },
}),
@ -155,7 +155,7 @@ describe("DynamicProviderPlugin", () => {
yield* addPlugin(npmEntrypoint(tmp.entrypoint))
const exit = yield* aisdk
.language(
new ModelV2.Info({
ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("missing-factory"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("alias"), type: "aisdk", package: "fixture-provider" },
}),
@ -172,7 +172,7 @@ describe("DynamicProviderPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const language = yield* aisdk.language(
new ModelV2.Info({
ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("test-model-api"), type: "aisdk", package: fixtureProvider },
}),

View file

@ -43,7 +43,7 @@ describe("GatewayPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gateway"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -63,7 +63,7 @@ describe("GatewayPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("vercel"), ModelV2.ID.make("anthropic/claude-sonnet-4")),
api: {
id: ModelV2.ID.make("anthropic/claude-sonnet-4"),
@ -89,7 +89,7 @@ describe("GatewayPlugin", () => {
for (const modelID of vercelGatewayModels) {
const ignored = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("vercel"), ModelV2.ID.make(modelID)),
api: { id: ModelV2.ID.make(modelID), type: "aisdk", package: "test-provider" },
}),
@ -99,7 +99,7 @@ describe("GatewayPlugin", () => {
expect(ignored.sdk).toBeUndefined()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("vercel"), ModelV2.ID.make(modelID)),
api: { id: ModelV2.ID.make(modelID), type: "aisdk", package: "test-provider" },
}),

View file

@ -45,7 +45,7 @@ describe("GithubCopilotPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const ignored = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -53,7 +53,7 @@ describe("GithubCopilotPlugin", () => {
options: { name: "github-copilot" },
})
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -72,7 +72,7 @@ describe("GithubCopilotPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("claude-sonnet-4")),
api: { id: ModelV2.ID.make("claude-sonnet-4"), type: "aisdk", package: "test-provider" },
}),
@ -90,7 +90,7 @@ describe("GithubCopilotPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("claude-sonnet-4"), type: "aisdk", package: "test-provider" },
}),
@ -108,7 +108,7 @@ describe("GithubCopilotPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -116,7 +116,7 @@ describe("GithubCopilotPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5.1-codex")),
api: { id: ModelV2.ID.make("gpt-5.1-codex"), type: "aisdk", package: "test-provider" },
}),
@ -124,7 +124,7 @@ describe("GithubCopilotPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-4o")),
api: { id: ModelV2.ID.make("gpt-4o"), type: "aisdk", package: "test-provider" },
}),
@ -132,7 +132,7 @@ describe("GithubCopilotPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini")),
api: { id: ModelV2.ID.make("gpt-5-mini"), type: "aisdk", package: "test-provider" },
}),
@ -140,7 +140,7 @@ describe("GithubCopilotPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini-2025-08-07")),
api: { id: ModelV2.ID.make("gpt-5-mini-2025-08-07"), type: "aisdk", package: "test-provider" },
}),
@ -164,7 +164,7 @@ describe("GithubCopilotPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("default")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -172,7 +172,7 @@ describe("GithubCopilotPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("small")),
api: { id: ModelV2.ID.make("gpt-5-mini"), type: "aisdk", package: "test-provider" },
}),
@ -180,7 +180,7 @@ describe("GithubCopilotPlugin", () => {
options: {},
})
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("sonnet")),
api: { id: ModelV2.ID.make("claude-sonnet-4"), type: "aisdk", package: "test-provider" },
}),
@ -228,7 +228,7 @@ describe("GithubCopilotPlugin", () => {
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-5")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),

View file

@ -68,7 +68,7 @@ describe("GitLabPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" },
}),
@ -105,7 +105,7 @@ describe("GitLabPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" },
}),
@ -130,7 +130,7 @@ describe("GitLabPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" },
}),
@ -171,7 +171,7 @@ describe("GitLabPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" },
}),
@ -190,7 +190,7 @@ describe("GitLabPlugin", () => {
const calls: [string, unknown][] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")),
api: { id: ModelV2.ID.make("duo-workflow-custom"), type: "aisdk", package: "test-provider" },
request: {
@ -225,7 +225,7 @@ describe("GitLabPlugin", () => {
const calls: [string, unknown][] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-exact")),
api: { id: ModelV2.ID.make("duo-workflow-exact"), type: "aisdk", package: "test-provider" },
}),
@ -252,7 +252,7 @@ describe("GitLabPlugin", () => {
const calls: [string, unknown][] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")),
api: { id: ModelV2.ID.make("duo-workflow-custom"), type: "aisdk", package: "test-provider" },
request: {
@ -280,7 +280,7 @@ describe("GitLabPlugin", () => {
const calls: [string, unknown][] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" },
request: { headers: { h: "v" }, body: {} },

View file

@ -116,7 +116,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(
ProviderV2.ID.make("google-vertex-anthropic"),
ModelV2.ID.make("claude-sonnet-4-5"),
@ -142,7 +142,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(
ProviderV2.ID.make("google-vertex-anthropic"),
ModelV2.ID.make("claude-sonnet-4-5"),
@ -165,7 +165,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -184,7 +184,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),
@ -202,7 +202,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
yield* addPlugin(GoogleVertexPlugin)
yield* addPlugin(GoogleVertexAnthropicPlugin)
const sdkResult = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
api: { id: ModelV2.ID.make(" claude-sonnet-4-5 "), type: "aisdk", package: "test-provider" },
}),
@ -210,7 +210,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
options: { name: "google-vertex", project: "project", location: "us" },
})
const languageResult = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
api: { id: ModelV2.ID.make(" claude-sonnet-4-5 "), type: "aisdk", package: "test-provider" },
}),
@ -232,7 +232,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
const calls: string[] = []
yield* addPlugin(GoogleVertexAnthropicPlugin)
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex-anthropic"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
api: { id: ModelV2.ID.make(" claude-sonnet-4-5 "), type: "aisdk", package: "test-provider" },
}),
@ -250,7 +250,7 @@ describe("GoogleVertexAnthropicPlugin", () => {
const calls: string[] = []
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "test-provider" },
}),

View file

@ -170,7 +170,7 @@ describe("GoogleVertexPlugin", () => {
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")))
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
api: {
id: ModelV2.ID.make("gemini"),
@ -295,7 +295,7 @@ describe("GoogleVertexPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
api: {
id: ModelV2.ID.make("gemini"),
@ -343,7 +343,7 @@ describe("GoogleVertexPlugin", () => {
Effect.void,
() =>
aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
api: {
id: ModelV2.ID.make("gemini"),
@ -374,7 +374,7 @@ describe("GoogleVertexPlugin", () => {
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" gemini-2.5-pro ")),
api: { id: ModelV2.ID.make(" gemini-2.5-pro "), type: "aisdk", package: "test-provider" },
}),

View file

@ -25,7 +25,7 @@ describe("GooglePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("gemini")),
api: { id: ModelV2.ID.make("gemini"), type: "aisdk", package: "@ai-sdk/google" },
}),
@ -43,7 +43,7 @@ describe("GooglePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("google"), ModelV2.ID.make("gemini")),
api: { id: ModelV2.ID.make("gemini"), type: "aisdk", package: "@ai-sdk/google" },
}),
@ -60,7 +60,7 @@ describe("GooglePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const sdkEvent = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("gemini-api"), type: "aisdk", package: "@ai-sdk/google" },
}),

View file

@ -26,7 +26,7 @@ describe("GroqPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("groq"), ModelV2.ID.make("llama")),
api: { id: ModelV2.ID.make("llama"), type: "aisdk", package: "@ai-sdk/groq" },
}),
@ -43,7 +43,7 @@ describe("GroqPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("groq"), ModelV2.ID.make("llama")),
api: { id: ModelV2.ID.make("llama"), type: "aisdk", package: "@ai-sdk/groq" },
}),
@ -60,7 +60,7 @@ describe("GroqPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("groq"), ModelV2.ID.make("llama")),
api: { id: ModelV2.ID.make("llama"), type: "aisdk", package: "@ai-sdk/groq" },
}),
@ -77,7 +77,7 @@ describe("GroqPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-groq"), ModelV2.ID.make("llama")),
api: { id: ModelV2.ID.make("llama"), type: "aisdk", package: "@ai-sdk/groq" },
}),
@ -102,7 +102,7 @@ describe("GroqPlugin", () => {
name: string
})
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("groq"), ModelV2.ID.make("alias")),
api: {
id: ModelV2.ID.make("llama-api"),

View file

@ -26,7 +26,7 @@ describe("MistralPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("mistral"), ModelV2.ID.make("mistral-large")),
api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" },
}),
@ -43,7 +43,7 @@ describe("MistralPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("mistral"), ModelV2.ID.make("mistral-large")),
api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" },
}),
@ -66,7 +66,7 @@ describe("MistralPlugin", () => {
}),
)
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("mistral"), ModelV2.ID.make("mistral-large")),
api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" },
}),
@ -90,7 +90,7 @@ describe("MistralPlugin", () => {
}),
)
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-mistral"), ModelV2.ID.make("mistral-large")),
api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" },
}),
@ -114,7 +114,7 @@ describe("MistralPlugin", () => {
}
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("mistral"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" },
}),

View file

@ -25,7 +25,7 @@ describe("OpenAICompatiblePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const defaulted = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -33,7 +33,7 @@ describe("OpenAICompatiblePlugin", () => {
options: { name: "custom" },
})
const disabled = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -51,7 +51,7 @@ describe("OpenAICompatiblePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -74,7 +74,7 @@ describe("OpenAICompatiblePlugin", () => {
}),
)
yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -94,7 +94,7 @@ describe("OpenAICompatiblePlugin", () => {
})
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),

View file

@ -65,7 +65,7 @@ describe("OpenAIPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-openai"), ModelV2.ID.make("gpt-5")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -82,7 +82,7 @@ describe("OpenAIPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -100,7 +100,7 @@ describe("OpenAIPlugin", () => {
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -119,7 +119,7 @@ describe("OpenAIPlugin", () => {
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.anthropic, ModelV2.ID.make("gpt-5")),
api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -135,7 +135,7 @@ describe("OpenAIPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const item = new ProviderV2.Info({
const item = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.openai),
api: { type: "aisdk", package: "@ai-sdk/openai" },
})
@ -157,7 +157,7 @@ describe("OpenAIPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const item = new ProviderV2.Info({
const item = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.make("custom-openai")),
api: { type: "aisdk", package: "test-provider" },
})

View file

@ -120,7 +120,7 @@ describe("OpencodePlugin", () => {
})
yield* credentials.create({
integrationID: Integration.ID.make("opencode"),
value: new Credential.Key({
value: Credential.Key.make({
type: "key",
key: "secret",
metadata: { server: server.url.origin },
@ -176,11 +176,11 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = new ProviderV2.Info({
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
api: { type: "aisdk", package: "test-provider" },
})
const model = new ModelV2.Info({
const model = ModelV2.Info.make({
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" },
cost: cost(1),
@ -202,11 +202,11 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = new ProviderV2.Info({
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
api: { type: "aisdk", package: "test-provider" },
})
const model = new ModelV2.Info({
const model = ModelV2.Info.make({
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("free")),
api: { id: ModelV2.ID.make("free"), type: "aisdk", package: "test-provider" },
cost: cost(0),
@ -228,11 +228,11 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = new ProviderV2.Info({
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
api: { type: "aisdk", package: "test-provider" },
})
const model = new ModelV2.Info({
const model = ModelV2.Info.make({
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("output-only")),
api: { id: ModelV2.ID.make("output-only"), type: "aisdk", package: "test-provider" },
cost: cost(0, 1),
@ -256,11 +256,11 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = new ProviderV2.Info({
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
api: { type: "aisdk", package: "test-provider" },
})
const model = new ModelV2.Info({
const model = ModelV2.Info.make({
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" },
cost: cost(1),
@ -289,11 +289,11 @@ describe("OpencodePlugin", () => {
})
})
yield* catalog.transform((catalog) => {
const provider = new ProviderV2.Info({
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
api: { type: "aisdk", package: "test-provider" },
})
const model = new ModelV2.Info({
const model = ModelV2.Info.make({
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" },
cost: cost(1),
@ -315,7 +315,7 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = new ProviderV2.Info({
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
api: { type: "aisdk", package: "test-provider" },
request: {
@ -323,7 +323,7 @@ describe("OpencodePlugin", () => {
body: { apiKey: "configured" },
},
})
const model = new ModelV2.Info({
const model = ModelV2.Info.make({
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" },
cost: cost(1),
@ -347,11 +347,11 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = new ProviderV2.Info({
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.openai),
api: { type: "aisdk", package: "test-provider" },
})
const model = new ModelV2.Info({
const model = ModelV2.Info.make({
...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")),
api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" },
cost: cost(1),

View file

@ -53,7 +53,7 @@ describe("OpenRouterPlugin", () => {
yield* addPlugin()
const ignored = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),
@ -63,7 +63,7 @@ describe("OpenRouterPlugin", () => {
expect(ignored.sdk).toBeUndefined()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("openai/gpt-5")),
api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" },
}),

View file

@ -39,7 +39,7 @@ describe("PerplexityPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("perplexity"), ModelV2.ID.make("sonar")),
api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" },
}),
@ -56,7 +56,7 @@ describe("PerplexityPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("perplexity"), ModelV2.ID.make("sonar")),
api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" },
}),
@ -73,7 +73,7 @@ describe("PerplexityPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("perplexity"), ModelV2.ID.make("sonar")),
api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" },
}),
@ -90,7 +90,7 @@ describe("PerplexityPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-perplexity"), ModelV2.ID.make("sonar")),
api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" },
}),
@ -108,7 +108,7 @@ describe("PerplexityPlugin", () => {
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("perplexity"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" },
}),

View file

@ -47,7 +47,7 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
}
function model(providerID: string) {
return new ModelV2.Info({
return ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make("sap-model")),
api: { id: ModelV2.ID.make("sap-model"), type: "aisdk", package: fixtureProvider },
})

View file

@ -55,7 +55,7 @@ describe("SnowflakeCortexPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-4")),
api: { id: ModelV2.ID.make("gpt-4"), type: "aisdk", package: "test-provider" },
}),
@ -73,7 +73,7 @@ describe("SnowflakeCortexPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" },
}),
@ -92,7 +92,7 @@ describe("SnowflakeCortexPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" },
}),
@ -115,7 +115,7 @@ describe("SnowflakeCortexPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" },
}),
@ -134,7 +134,7 @@ describe("SnowflakeCortexPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" },
}),
@ -157,7 +157,7 @@ describe("SnowflakeCortexPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" },
}),

View file

@ -39,7 +39,7 @@ describe("TogetherAIPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("togetherai"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -57,7 +57,7 @@ describe("TogetherAIPlugin", () => {
yield* addPlugin()
const ignored = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("togetherai"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -67,7 +67,7 @@ describe("TogetherAIPlugin", () => {
expect(ignored.sdk).toBeUndefined()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("togetherai"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -85,7 +85,7 @@ describe("TogetherAIPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-togetherai"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -105,7 +105,7 @@ describe("TogetherAIPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(
ProviderV2.ID.make("togetherai"),
ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct-Turbo"),

View file

@ -39,7 +39,7 @@ describe("VenicePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("venice"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -56,7 +56,7 @@ describe("VenicePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-venice"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -74,7 +74,7 @@ describe("VenicePlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const similar = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("venice"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -82,7 +82,7 @@ describe("VenicePlugin", () => {
options: { name: "venice" },
})
const other = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("venice"), ModelV2.ID.make("model")),
api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" },
}),
@ -101,7 +101,7 @@ describe("VenicePlugin", () => {
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("venice"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("alias"), type: "aisdk", package: "test-provider" },
}),

View file

@ -60,7 +60,7 @@ describe("VercelPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const event = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-vercel"), ModelV2.ID.make("v0-1.0-md")),
api: { id: ModelV2.ID.make("v0-1.0-md"), type: "aisdk", package: "@ai-sdk/vercel" },
}),

View file

@ -40,7 +40,7 @@ describe("XAIPlugin", () => {
yield* addPlugin()
const ignored = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" },
}),
@ -49,7 +49,7 @@ describe("XAIPlugin", () => {
})
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" },
}),
@ -69,7 +69,7 @@ describe("XAIPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("custom-xai"), ModelV2.ID.make("grok-4")),
api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" },
}),
@ -89,7 +89,7 @@ describe("XAIPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("alias")),
api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" },
}),
@ -110,7 +110,7 @@ describe("XAIPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: new ModelV2.Info({
model: ModelV2.Info.make({
...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("grok-4")),
api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" },
}),

View file

@ -26,7 +26,7 @@ describe("ReferenceGuidance", () => {
name: "docs",
path: AbsolutePath.make("/docs"),
description: "Use for product documentation",
source: new Reference.LocalSource({
source: Reference.LocalSource.make({
type: "local",
path: AbsolutePath.make("/docs"),
description: "Use for product documentation",
@ -63,7 +63,7 @@ describe("ReferenceGuidance", () => {
new Reference.Info({
name: "docs",
path: AbsolutePath.make("/docs"),
source: new Reference.LocalSource({ type: "local", path: AbsolutePath.make("/docs") }),
source: Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/docs") }),
}),
]),
}),

View file

@ -18,7 +18,7 @@ describe("Reference", () => {
const references = yield* Reference.Service
const scope = yield* Scope.make()
const path = AbsolutePath.make("/docs")
const source = new Reference.LocalSource({
const source = Reference.LocalSource.make({
type: "local",
path,
description: "Use for API documentation",
@ -44,7 +44,7 @@ describe("Reference", () => {
Effect.gen(function* () {
const references = yield* Reference.Service
const repository = Repository.parseRemote("owner/repo")
const source = new Reference.GitSource({ type: "git", repository: "owner/repo", branch: "main" })
const source = Reference.GitSource.make({ type: "git", repository: "owner/repo", branch: "main" })
yield* references.transform((editor) => editor.add("sdk", source))
expect(yield* references.list()).toEqual([
@ -67,7 +67,7 @@ describe("Reference", () => {
Effect.gen(function* () {
const references = yield* Reference.Service
const repository = Repository.parseRemote("owner/repo")
const source = new Reference.GitSource({
const source = Reference.GitSource.make({
type: "git",
repository: "owner/repo",
description: "Use for SDK implementation details",

View file

@ -55,21 +55,6 @@ const location = Location.Ref.make({ directory: AbsolutePath.make("/project") })
const id = SessionV2.ID.create()
describe("SessionV2.create", () => {
it.effect("derives stable namespaced external IDs", () =>
Effect.sync(() => {
const input = { namespace: "opencord.agent-thread", key: "thread-1" }
expect(SessionV2.ID.fromExternal(input)).toBe(SessionV2.ID.fromExternal(input))
expect(SessionV2.ID.fromExternal(input)).toMatch(/^ses_[a-f0-9]{64}$/)
expect(SessionV2.ID.fromExternal({ ...input, namespace: "another-app" })).not.toBe(
SessionV2.ID.fromExternal(input),
)
expect(SessionV2.ID.fromExternal({ namespace: "a:b", key: "c" })).not.toBe(
SessionV2.ID.fromExternal({ namespace: "a", key: "b:c" }),
)
}),
)
it.effect("creates a fresh projected session when the ID is omitted", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
@ -218,7 +203,7 @@ describe("SessionV2.create", () => {
const events = yield* EventV2.Service
const { db } = yield* Database.Service
const created = yield* session.create({ location })
yield* session.prompt({ sessionID: created.id, prompt: new Prompt({ text: "Hello" }), resume: false })
yield* session.prompt({ sessionID: created.id, prompt: Prompt.make({ text: "Hello" }), resume: false })
yield* SessionInput.promoteSteers(db, events, created.id, Number.MAX_SAFE_INTEGER)
expect(
@ -238,7 +223,7 @@ describe("SessionV2.create", () => {
const created = yield* session.create({ id: SessionV2.ID.make("ses_fresh_target_replay"), location })
const admitted = yield* session.prompt({
sessionID: created.id,
prompt: new Prompt({ text: "Replay lifecycle" }),
prompt: Prompt.make({ text: "Replay lifecycle" }),
resume: false,
})
yield* SessionInput.promoteSteers(sourceDb, sourceEvents, created.id, Number.MAX_SAFE_INTEGER)

View file

@ -36,7 +36,7 @@ const assistantRow = (
id: _,
type,
...data
} = encodeMessage(new SessionMessage.Assistant({ id, type: "assistant", agent: "build", model, content: [], time }))
} = encodeMessage(SessionMessage.Assistant.make({ id, type: "assistant", agent: "build", model, content: [], time }))
return { id, session_id: sessionID, type, seq, time_created: DateTime.toEpochMillis(time.created), data }
}
@ -69,7 +69,7 @@ describe("SessionProjector", () => {
sessionID,
messageID: SessionMessage.ID.make("msg_first"),
timestamp: created,
prompt: new Prompt({ text: "first" }),
prompt: Prompt.make({ text: "first" }),
delivery: "steer",
},
{ id: EventV2.ID.make("evt_z") },
@ -80,7 +80,7 @@ describe("SessionProjector", () => {
sessionID,
messageID: SessionMessage.ID.make("msg_second"),
timestamp: created,
prompt: new Prompt({ text: "second" }),
prompt: Prompt.make({ text: "second" }),
delivery: "steer",
},
{ id: EventV2.ID.make("evt_a") },
@ -145,7 +145,7 @@ describe("SessionProjector", () => {
const admitted = yield* SessionInput.admit(db, events, {
id,
sessionID,
prompt: new Prompt({ text: "promote me" }),
prompt: Prompt.make({ text: "promote me" }),
delivery: "steer",
})
if (!admitted) return yield* Effect.die("Prompt admission failed")
@ -154,7 +154,7 @@ describe("SessionProjector", () => {
sessionID,
timestamp: admitted.timeCreated,
messageID: id,
prompt: new Prompt({ text: "promote me" }),
prompt: Prompt.make({ text: "promote me" }),
delivery: "steer",
})
@ -334,7 +334,7 @@ describe("SessionProjector", () => {
it.effect("does not revive a stale incomplete in-memory assistant projection", () =>
Effect.gen(function* () {
const stale = new SessionMessage.Assistant({
const stale = SessionMessage.Assistant.make({
id: SessionMessage.ID.make("msg_assistant_stale"),
type: "assistant",
agent: "build",
@ -342,7 +342,7 @@ describe("SessionProjector", () => {
content: [],
time: { created },
})
const completed = new SessionMessage.Assistant({
const completed = SessionMessage.Assistant.make({
id: SessionMessage.ID.make("msg_assistant_completed"),
type: "assistant",
agent: "build",
@ -466,15 +466,15 @@ describe("SessionProjector", () => {
Schema.decodeUnknownSync(SessionMessage.Message)({ ...row.data, id: row.id, type: row.type }),
)
expect(messages).toEqual([
new SessionMessage.Assistant({
SessionMessage.Assistant.make({
id: SessionMessage.ID.make("msg_assistant_completed"),
type: "assistant",
agent: "build",
model,
content: [new SessionMessage.AssistantText({ type: "text", id: "text-stale", text: "" })],
content: [SessionMessage.AssistantText.make({ type: "text", id: "text-stale", text: "" })],
time: { created: DateTime.makeUnsafe(1), completed: DateTime.makeUnsafe(2) },
}),
new SessionMessage.Assistant({
SessionMessage.Assistant.make({
id: SessionMessage.ID.make("msg_assistant_stale"),
type: "assistant",
agent: "build",

View file

@ -147,7 +147,7 @@ describe("SessionV2.prompt", () => {
const message = yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Fix the failing tests" }),
prompt: Prompt.make({ text: "Fix the failing tests" }),
resume: false,
})
@ -171,8 +171,8 @@ describe("SessionV2.prompt", () => {
const fiber = yield* session.events({ sessionID }).pipe(Stream.take(4), Stream.runCollect, Effect.forkScoped)
yield* Effect.yieldNow
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
yield* SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER)
const streamed = Array.from(yield* Fiber.join(fiber))
@ -198,7 +198,7 @@ describe("SessionV2.prompt", () => {
const session = yield* SessionV2.Service
const message = yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Fix the failing tests" }),
prompt: Prompt.make({ text: "Fix the failing tests" }),
resume: false,
})
@ -217,7 +217,7 @@ describe("SessionV2.prompt", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
const input = { sessionID, prompt: new Prompt({ text: "Fix the failing tests" }), resume: false }
const input = { sessionID, prompt: Prompt.make({ text: "Fix the failing tests" }), resume: false }
const first = yield* session.prompt(input)
const second = yield* session.prompt(input)
@ -235,7 +235,7 @@ describe("SessionV2.prompt", () => {
const input = {
sessionID,
id: messageID,
prompt: new Prompt({ text: "Fix the failing tests" }),
prompt: Prompt.make({ text: "Fix the failing tests" }),
resume: false,
}
@ -255,7 +255,7 @@ describe("SessionV2.prompt", () => {
const input = {
sessionID,
id: messageID,
prompt: new Prompt({ text: "Recover committed prompt" }),
prompt: Prompt.make({ text: "Recover committed prompt" }),
resume: false,
}
const first = yield* session.prompt(input)
@ -276,13 +276,13 @@ describe("SessionV2.prompt", () => {
yield* session.prompt({
sessionID,
id: messageID,
prompt: new Prompt({ text: "Fix the failing tests" }),
prompt: Prompt.make({ text: "Fix the failing tests" }),
})
const failure = yield* session
.prompt({
sessionID,
id: messageID,
prompt: new Prompt({ text: "Delete the failing tests" }),
prompt: Prompt.make({ text: "Delete the failing tests" }),
resume: false,
})
.pipe(Effect.flip)
@ -301,14 +301,14 @@ describe("SessionV2.prompt", () => {
yield* session.prompt({
id: messageID,
sessionID,
prompt: new Prompt({ text: "Fix the failing tests" }),
prompt: Prompt.make({ text: "Fix the failing tests" }),
resume: false,
})
const failure = yield* session
.prompt({
id: messageID,
sessionID,
prompt: new Prompt({ text: "Fix the failing tests" }),
prompt: Prompt.make({ text: "Fix the failing tests" }),
delivery: "queue",
resume: false,
})
@ -325,7 +325,7 @@ describe("SessionV2.prompt", () => {
const input = {
sessionID,
id: messageID,
prompt: new Prompt({ text: "Fix the failing tests" }),
prompt: Prompt.make({ text: "Fix the failing tests" }),
resume: false,
}
@ -344,7 +344,7 @@ describe("SessionV2.prompt", () => {
const { db } = yield* Database.Service
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
yield* session.prompt({ id: messageID, sessionID, prompt: new Prompt({ text: "Promote once" }), resume: false })
yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "Promote once" }), resume: false })
yield* Effect.all(
[
@ -368,9 +368,9 @@ describe("SessionV2.prompt", () => {
const { db } = yield* Database.Service
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
const first = yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Before cutoff" }), resume: false })
const first = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Before cutoff" }), resume: false })
const cutoff = first.admittedSeq
const second = yield* session.prompt({ sessionID, prompt: new Prompt({ text: "After cutoff" }), resume: false })
const second = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "After cutoff" }), resume: false })
yield* SessionInput.promoteSteers(db, events, sessionID, cutoff)
@ -386,7 +386,12 @@ describe("SessionV2.prompt", () => {
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
wakeCalls.length = 0
yield* session.prompt({ id: messageID, sessionID, prompt: new Prompt({ text: "Replay pending" }), resume: false })
yield* session.prompt({
id: messageID,
sessionID,
prompt: Prompt.make({ text: "Replay pending" }),
resume: false,
})
const recorded = yield* db
.select()
.from(EventTable)
@ -422,7 +427,7 @@ describe("SessionV2.prompt", () => {
yield* setup
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
const prompt = new Prompt({ text: "Historical prompt" })
const prompt = Prompt.make({ text: "Historical prompt" })
yield* events.publish(SessionEvent.Prompted, {
sessionID,
messageID,
@ -443,7 +448,7 @@ describe("SessionV2.prompt", () => {
yield* setup
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
const prompt = new Prompt({ text: "Historical queued prompt" })
const prompt = Prompt.make({ text: "Historical queued prompt" })
yield* events.publish(SessionEvent.Prompted, {
sessionID,
messageID,
@ -478,7 +483,7 @@ describe("SessionV2.prompt", () => {
.onConflictDoNothing()
.run()
.pipe(Effect.orDie)
const prompt = new Prompt({ text: "Fix the failing tests" })
const prompt = Prompt.make({ text: "Fix the failing tests" })
yield* session.prompt({ id: messageID, sessionID, prompt, resume: false })
const failure = yield* session
@ -502,7 +507,7 @@ describe("SessionV2.prompt", () => {
})
const failure = yield* session
.prompt({ id: messageID, sessionID, prompt: new Prompt({ text: "Conflicting prompt" }), resume: false })
.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "Conflicting prompt" }), resume: false })
.pipe(Effect.flip)
expect(failure).toMatchObject({ _tag: "Session.PromptConflictError", sessionID, messageID })
@ -517,7 +522,7 @@ describe("SessionV2.prompt", () => {
executionCalls.length = 0
wakeCalls.length = 0
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Run by default" }) })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run by default" }) })
expect(executionCalls).toEqual([])
expect(wakeCalls).toEqual([sessionID])
@ -533,7 +538,7 @@ describe("SessionV2.prompt", () => {
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Run explicitly" }),
prompt: Prompt.make({ text: "Run explicitly" }),
resume: true,
})
@ -549,7 +554,7 @@ describe("SessionV2.prompt", () => {
executionCalls.length = 0
wakeCalls.length = 0
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Do not run" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Do not run" }), resume: false })
expect(executionCalls).toEqual([])
expect(wakeCalls).toEqual([])

View file

@ -16,7 +16,7 @@ const model = Model.make({ id: "model", provider: "provider", route: OpenAIChat.
describe("toLLMMessages", () => {
test("omits empty assistant turns", () => {
const assistant = (value: string, content: SessionMessage.Assistant["content"]) =>
new SessionMessage.Assistant({
SessionMessage.Assistant.make({
id: id(value),
type: "assistant",
agent: "build",
@ -27,13 +27,13 @@ describe("toLLMMessages", () => {
const messages = toLLMMessages(
[
assistant("empty", []),
assistant("empty-text", [new SessionMessage.AssistantText({ type: "text", id: "empty", text: "" })]),
assistant("empty-text", [SessionMessage.AssistantText.make({ type: "text", id: "empty", text: "" })]),
assistant("empty-reasoning", [
new SessionMessage.AssistantReasoning({ type: "reasoning", id: "empty-reasoning", text: "" }),
SessionMessage.AssistantReasoning.make({ type: "reasoning", id: "empty-reasoning", text: "" }),
]),
assistant("text", [new SessionMessage.AssistantText({ type: "text", id: "text", text: "Partial" })]),
assistant("text", [SessionMessage.AssistantText.make({ type: "text", id: "text", text: "Partial" })]),
assistant("reasoning", [
new SessionMessage.AssistantReasoning({
SessionMessage.AssistantReasoning.make({
type: "reasoning",
id: "reasoning",
text: "",
@ -48,43 +48,43 @@ describe("toLLMMessages", () => {
})
test("maps every top-level V2 Session message type", () => {
const file = new FileAttachment({ uri: "data:image/png;base64,aGVsbG8=", mime: "image/png", name: "hello.png" })
const file = FileAttachment.make({ uri: "data:image/png;base64,aGVsbG8=", mime: "image/png", name: "hello.png" })
const messages = toLLMMessages(
[
new SessionMessage.AgentSwitched({
SessionMessage.AgentSwitched.make({
id: id("agent"),
type: "agent-switched",
agent: "build",
time: { created },
}),
new SessionMessage.ModelSwitched({
SessionMessage.ModelSwitched.make({
id: id("model"),
type: "model-switched",
model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") },
time: { created },
}),
new SessionMessage.System({
SessionMessage.System.make({
id: id("system"),
type: "system",
text: "Updated context\n\nOther context",
time: { created },
}),
new SessionMessage.User({
SessionMessage.User.make({
id: id("user"),
type: "user",
text: "Inspect this image",
files: [file],
agents: [new AgentAttachment({ name: "build" })],
agents: [AgentAttachment.make({ name: "build" })],
time: { created },
}),
new SessionMessage.Synthetic({
SessionMessage.Synthetic.make({
id: id("synthetic"),
type: "synthetic",
sessionID: SessionV2.ID.make("ses_translate"),
text: "Synthetic context",
time: { created },
}),
new SessionMessage.Shell({
SessionMessage.Shell.make({
id: id("shell"),
type: "shell",
callID: "shell-1",
@ -92,7 +92,7 @@ describe("toLLMMessages", () => {
output: "/project",
time: { created, completed: created },
}),
new SessionMessage.Compaction({
SessionMessage.Compaction.make({
id: id("compaction"),
type: "compaction",
reason: "auto",
@ -142,31 +142,31 @@ Recent work
test("replays durable tool media into canonical tool messages without structured base64", () => {
const messages = toLLMMessages(
[
new SessionMessage.Assistant({
SessionMessage.Assistant.make({
id: id("assistant"),
type: "assistant",
agent: "build",
model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") },
content: [
new SessionMessage.AssistantText({ type: "text", id: "text-1", text: "Checking" }),
new SessionMessage.AssistantReasoning({
SessionMessage.AssistantText.make({ type: "text", id: "text-1", text: "Checking" }),
SessionMessage.AssistantReasoning.make({
type: "reasoning",
id: "reasoning-1",
text: "Think",
providerMetadata: { anthropic: { signature: "sig_1" } },
}),
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: "pending",
name: "read",
state: new SessionMessage.ToolStatePending({ status: "pending", input: '{"path":"README.md"}' }),
state: SessionMessage.ToolStatePending.make({ status: "pending", input: '{"path":"README.md"}' }),
time: { created },
}),
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: "running",
name: "read",
state: new SessionMessage.ToolStateRunning({
state: SessionMessage.ToolStateRunning.make({
status: "running",
input: { path: "README.md" },
content: [],
@ -174,11 +174,11 @@ Recent work
}),
time: { created },
}),
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: "completed",
name: "read",
state: new SessionMessage.ToolStateCompleted({
state: SessionMessage.ToolStateCompleted.make({
status: "completed",
input: { path: "README.md" },
content: [
@ -194,7 +194,7 @@ Recent work
}),
time: { created, completed: created },
}),
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: "hosted",
name: "web_search",
@ -203,7 +203,7 @@ Recent work
metadata: { fake: { continuation: "hosted-call" } },
resultMetadata: { fake: { continuation: "hosted-result" } },
},
state: new SessionMessage.ToolStateCompleted({
state: SessionMessage.ToolStateCompleted.make({
status: "completed",
input: { query: "Effect" },
content: [{ type: "text", text: "Found it" }],
@ -211,12 +211,12 @@ Recent work
}),
time: { created, completed: created },
}),
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: "hosted-failed",
name: "write",
provider: { executed: true, metadata: { fake: { continuation: "failed" } } },
state: new SessionMessage.ToolStateError({
state: SessionMessage.ToolStateError.make({
status: "error",
input: { path: "README.md" },
content: [],
@ -299,13 +299,13 @@ Recent work
test("restores OpenAI encrypted reasoning metadata", () => {
const messages = toLLMMessages(
[
new SessionMessage.Assistant({
SessionMessage.Assistant.make({
id: id("assistant-openai-reasoning"),
type: "assistant",
agent: "build",
model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") },
content: [
new SessionMessage.AssistantReasoning({
SessionMessage.AssistantReasoning.make({
type: "reasoning",
id: "reasoning-openai",
text: "Think",
@ -330,19 +330,19 @@ Recent work
test("drops provider-native continuation metadata after a model switch", () => {
const messages = toLLMMessages(
[
new SessionMessage.Assistant({
SessionMessage.Assistant.make({
id: id("assistant-old-model"),
type: "assistant",
agent: "build",
model: { id: ModelV2.ID.make("old-model"), providerID: ProviderV2.ID.make("provider") },
content: [
new SessionMessage.AssistantReasoning({
SessionMessage.AssistantReasoning.make({
type: "reasoning",
id: "reasoning-old-model",
text: "Visible thought",
providerMetadata: { anthropic: { signature: "sig_old" } },
}),
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: "hosted-old-model",
name: "web_search",
@ -351,7 +351,7 @@ Recent work
metadata: { openai: { itemId: "hosted-old-model" } },
resultMetadata: { openai: { itemId: "hosted-old-model" } },
},
state: new SessionMessage.ToolStateCompleted({
state: SessionMessage.ToolStateCompleted.make({
status: "completed",
input: { query: "Effect" },
content: [],
@ -360,7 +360,7 @@ Recent work
}),
time: { created, completed: created },
}),
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: "local-old-model",
name: "read",
@ -369,7 +369,7 @@ Recent work
metadata: { fake: { call: "old" } },
resultMetadata: { fake: { result: "old" } },
},
state: new SessionMessage.ToolStateCompleted({
state: SessionMessage.ToolStateCompleted.make({
status: "completed",
input: { path: "README.md" },
content: [],

View file

@ -22,7 +22,7 @@ type Api =
| { readonly type: "native"; readonly url?: string; readonly settings: Record<string, unknown> }
const model = (api: Api, variants: ModelV2.Info["variants"] = []) =>
new ModelV2.Info({
ModelV2.Info.make({
id: ModelV2.ID.make("test-model"),
providerID: ProviderV2.ID.make("test-provider"),
name: "Test model",
@ -79,7 +79,7 @@ describe("SessionRunnerModel", () => {
it.effect("uses merged API settings for OpenAI-compatible auth and request defaults", () =>
Effect.gen(function* () {
const resolved = yield* SessionRunnerModel.fromCatalogModel(
new ModelV2.Info({
ModelV2.Info.make({
...model({
type: "aisdk",
package: "@ai-sdk/openai-compatible",
@ -114,7 +114,7 @@ describe("SessionRunnerModel", () => {
options: { reasoningEffort: "high" },
},
])
const catalog = new ModelV2.Info({
const catalog = ModelV2.Info.make({
...base,
request: { ...base.request, options: { ...base.request.options, reasoningEffort: "medium" } },
})
@ -264,11 +264,11 @@ describe("SessionRunnerModel", () => {
it.effect("uses resolved credentials for bearer auth", () =>
Effect.gen(function* () {
const resolved = yield* SessionRunnerModel.fromCatalogModel(
new ModelV2.Info({
ModelV2.Info.make({
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
request: { headers: {}, body: {}, generation: {}, options: {} },
}),
new Credential.Key({ type: "key", key: "secret" }),
Credential.Key.make({ type: "key", key: "secret" }),
)
const request = LLM.request({ model: resolved, prompt: "Hello" })
const headers = yield* resolved.route.auth.apply({
@ -285,9 +285,9 @@ describe("SessionRunnerModel", () => {
it.effect("prefers stored credentials over configured auth", () =>
Effect.gen(function* () {
const credential = new Credential.Key({ type: "key", key: "stored-secret", metadata: { tenant: "work" } })
const credential = Credential.Key.make({ type: "key", key: "stored-secret", metadata: { tenant: "work" } })
const resolved = yield* SessionRunnerModel.fromCatalogModel(
new ModelV2.Info({
ModelV2.Info.make({
...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
request: { headers: {}, body: { apiKey: "configured-secret" }, generation: {}, options: {} },
}),

View file

@ -154,7 +154,7 @@ describe("SessionRunnerLLM recorded", () => {
const session = yield* SessionV2.Service
const prompt = yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Say hello in one short sentence." }),
prompt: Prompt.make({ text: "Say hello in one short sentence." }),
resume: false,
})

View file

@ -26,7 +26,6 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
import { Prompt } from "@opencode-ai/core/session/prompt"
import { SessionProjector } from "@opencode-ai/core/session/projector"
import { SessionExecution } from "@opencode-ai/core/session/execution"
import { SessionContextEpoch } from "@opencode-ai/core/session/context-epoch"
import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
import { SessionRunner } from "@opencode-ai/core/session/runner"
import * as SessionRunnerLLM from "@opencode-ai/core/session/runner/llm"
@ -351,7 +350,7 @@ const setupOverflowRecovery = Effect.gen(function* () {
response = fragmentFixture("text", "text-earlier", ["Earlier answer"]).completeEvents
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Earlier question ".repeat(700) }),
prompt: Prompt.make({ text: "Earlier question ".repeat(700) }),
resume: false,
})
yield* session.resume(sessionID)
@ -476,7 +475,7 @@ const verifyEphemeralDeltas = (kind: FragmentKind) =>
const chunks = Array.from({ length: 32 }, (_, index) => `${index},`)
const fixture = fragmentFixture(kind, fragmentID(kind, "many"), chunks)
const expectedContext = [{ type: "user", text: prompt }, fixture.expectedAssistant]
yield* session.prompt({ sessionID, prompt: new Prompt({ text: prompt }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
const events = yield* EventV2.Service
const live = yield* events.subscribe(fixture.delta).pipe(Stream.take(32), Stream.runCollect, Effect.forkScoped)
yield* Effect.yieldNow
@ -507,7 +506,7 @@ const verifyPartialFlushOnFailure = (kind: FragmentKind) =>
const prompt = `Fail after ${kind}`
const fixture = fragmentFixture(kind, fragmentID(kind, "partial"), ["Partial"])
const failure = providerUnavailable()
yield* session.prompt({ sessionID, prompt: new Prompt({ text: prompt }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
responseStream = Stream.concat(Stream.fromIterable(fixture.partialEvents), Stream.fail(failure))
expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure)
@ -529,7 +528,7 @@ const verifyPartialFlushOnInterruption = (kind: FragmentKind) =>
const prompt = `Interrupt after ${kind}`
const fixture = fragmentFixture(kind, fragmentID(kind, "interrupted"), ["Partial"])
const streamed = yield* Deferred.make<void>()
yield* session.prompt({ sessionID, prompt: new Prompt({ text: prompt }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
responseStream = Stream.concat(
Stream.fromIterable(fixture.partialEvents),
Stream.fromEffect(Deferred.succeed(streamed, undefined)).pipe(Stream.flatMap(() => Stream.never)),
@ -573,7 +572,7 @@ describe("SessionRunnerLLM", () => {
}),
}),
})
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Use application context" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Use application context" }), resume: false })
responses = [
[
LLMEvent.stepStart({ index: 0 }),
@ -621,7 +620,7 @@ describe("SessionRunnerLLM", () => {
streamStarted = undefined
response = []
const message = yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Run automatically" }) })
const message = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run automatically" }) })
expect(requests).toHaveLength(1)
expect(yield* session.messages({ sessionID })).toMatchObject([
@ -634,8 +633,8 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
requests.length = 0
responses = undefined
@ -662,7 +661,7 @@ describe("SessionRunnerLLM", () => {
const { db } = yield* Database.Service
const messageID = SessionMessage.ID.create()
systemUnavailable = true
yield* session.prompt({ id: messageID, sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
@ -680,7 +679,7 @@ describe("SessionRunnerLLM", () => {
).toBeUndefined()
systemUnavailable = false
yield* session.prompt({ id: messageID, sessionID, prompt: new Prompt({ text: "First" }) })
yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "First" }) })
expect(requests).toHaveLength(1)
expect(requests[0]?.messages.map((message) => message.role)).toEqual(["user"])
@ -693,7 +692,7 @@ describe("SessionRunnerLLM", () => {
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
const { db } = yield* Database.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
yield* session.resume(sessionID)
@ -711,7 +710,7 @@ describe("SessionRunnerLLM", () => {
.get(),
).toBeUndefined()
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
expect(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)).toBe(true)
@ -725,7 +724,7 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const { db } = yield* Database.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
response = []
yield* session.resume(sessionID)
yield* db
@ -734,7 +733,7 @@ describe("SessionRunnerLLM", () => {
.where(eq(SessionContextEpochTable.session_id, sessionID))
.run()
.pipe(Effect.orDie)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
requests.length = 0
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
@ -749,13 +748,13 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
yield* session.resume(sessionID)
systemBaseline = "Changed context"
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
yield* session.resume(sessionID)
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
@ -790,7 +789,7 @@ describe("SessionRunnerLLM", () => {
}),
)
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = fragmentFixture("text", "text-build", ["Done"]).completeEvents
@ -816,7 +815,7 @@ describe("SessionRunnerLLM", () => {
editor.default(AgentV2.ID.make("reviewer"))
})
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = fragmentFixture("text", "text-reviewer", ["Done"]).completeEvents
@ -845,7 +844,7 @@ describe("SessionRunnerLLM", () => {
.run()
.pipe(Effect.orDie)
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = fragmentFixture("text", "text-selected", ["Done"]).completeEvents
@ -862,7 +861,7 @@ describe("SessionRunnerLLM", () => {
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
skillBaselines.set(AgentV2.ID.make("build"), "Build skills")
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
@ -874,7 +873,7 @@ describe("SessionRunnerLLM", () => {
timestamp: DateTime.makeUnsafe(1),
agent: "reviewer",
})
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
yield* session.resume(sessionID)
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
@ -905,7 +904,7 @@ describe("SessionRunnerLLM", () => {
})
.pipe(Effect.asVoid)
})
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
@ -935,7 +934,7 @@ describe("SessionRunnerLLM", () => {
})
.pipe(Effect.asVoid)
})
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
@ -949,13 +948,13 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
yield* session.resume(sessionID)
systemRemoved = true
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
yield* session.resume(sessionID)
expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "user", "system"])
@ -971,13 +970,13 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
yield* session.resume(sessionID)
systemBaseline = "Changed context"
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
yield* session.resume(sessionID)
yield* events.publish(SessionEvent.ModelSwitched, {
sessionID,
@ -986,7 +985,7 @@ describe("SessionRunnerLLM", () => {
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
})
systemBaseline = "Replacement context"
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Third" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
yield* session.resume(sessionID)
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
@ -1006,7 +1005,7 @@ describe("SessionRunnerLLM", () => {
])
yield* replaySessionProjection(sessionID)
expect(yield* session.messages({ sessionID })).toHaveLength(6)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Fourth" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fourth" }), resume: false })
yield* session.resume(sessionID)
}),
)
@ -1016,7 +1015,7 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
@ -1028,11 +1027,11 @@ describe("SessionRunnerLLM", () => {
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
})
systemUnavailable = true
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
yield* session.resume(sessionID)
systemUnavailable = false
systemBaseline = "Replacement context"
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Third" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
yield* session.resume(sessionID)
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
@ -1048,7 +1047,7 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
@ -1069,7 +1068,7 @@ describe("SessionRunnerLLM", () => {
recent: "",
})
systemBaseline = "Replacement context"
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
yield* session.resume(sessionID)
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
@ -1077,7 +1076,7 @@ describe("SessionRunnerLLM", () => {
["Replacement context"],
])
yield* replaySessionProjection(sessionID)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Third" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
yield* session.resume(sessionID)
}),
)
@ -1089,7 +1088,7 @@ describe("SessionRunnerLLM", () => {
response = fragmentFixture("text", "text-first", ["Earlier answer"]).completeEvents
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Earlier question ".repeat(180) }),
prompt: Prompt.make({ text: "Earlier question ".repeat(180) }),
resume: false,
})
yield* session.resume(sessionID)
@ -1102,7 +1101,7 @@ describe("SessionRunnerLLM", () => {
]
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Recent exact request ".repeat(180) }),
prompt: Prompt.make({ text: "Recent exact request ".repeat(180) }),
resume: false,
})
yield* session.resume(sessionID)
@ -1128,7 +1127,7 @@ describe("SessionRunnerLLM", () => {
]
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Newest exact request ".repeat(180) }),
prompt: Prompt.make({ text: "Newest exact request ".repeat(180) }),
resume: false,
})
yield* session.resume(sessionID)
@ -1156,7 +1155,7 @@ describe("SessionRunnerLLM", () => {
fragmentFixture("text", "text-summary", ["## Goal\n- Recover overflow"]).completeEvents,
fragmentFixture("text", "text-final", ["Recovered"]).completeEvents,
]
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Continue" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
yield* session.resume(sessionID)
expect(requests).toHaveLength(3)
@ -1186,7 +1185,7 @@ describe("SessionRunnerLLM", () => {
fragmentFixture("text", "text-summary", ["## Goal\n- Recover once"]).completeEvents,
overflow(),
]
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Continue" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
yield* session.resume(sessionID)
expect(requests).toHaveLength(3)
@ -1214,7 +1213,7 @@ describe("SessionRunnerLLM", () => {
fragmentFixture("text", "text-summary", ["## Goal\n- Recover raw overflow"]).completeEvents,
fragmentFixture("text", "text-final", ["Recovered"]).completeEvents,
]
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Continue" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
yield* session.resume(sessionID)
expect(requests).toHaveLength(3)
@ -1232,7 +1231,7 @@ describe("SessionRunnerLLM", () => {
[LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" })],
[LLMEvent.providerError({ message: "summary unavailable" })],
]
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Continue" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
yield* session.resume(sessionID)
expect(requests).toHaveLength(2)
@ -1255,7 +1254,7 @@ describe("SessionRunnerLLM", () => {
const firstGate = yield* Deferred.make<void>()
const summaryGate = yield* Deferred.make<void>()
streamGate = firstGate
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Continue" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
while (requests.length < 1) yield* Effect.yieldNow
streamGate = summaryGate
@ -1275,13 +1274,13 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
requests.length = 0
response = []
yield* session.resume(sessionID)
systemBaseline = "Changed context"
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
yield* session.resume(sessionID)
const compactionID = SessionMessage.ID.create()
yield* events.publish(SessionEvent.Compaction.Started, {
@ -1299,7 +1298,7 @@ describe("SessionRunnerLLM", () => {
recent: "",
})
systemUnavailable = true
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Third" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
yield* session.resume(sessionID)
expect(requests.at(-1)?.system.map((part) => part.text)).toEqual(["Initial context"])
@ -1311,7 +1310,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Use tools" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Use tools" }), resume: false })
requests.length = 0
responses = undefined
@ -1409,7 +1408,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Echo this" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Echo this" }), resume: false })
requests.length = 0
authorizations.length = 0
@ -1468,7 +1467,7 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Echo this" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Echo this" }), resume: false })
requests.length = 0
responses = [
@ -1512,7 +1511,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Think first" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Think first" }), resume: false })
requests.length = 0
response = [
@ -1550,7 +1549,7 @@ describe("SessionRunnerLLM", () => {
},
])
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Continue" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
response = []
yield* session.resume(sessionID)
@ -1569,7 +1568,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Search first" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Search first" }), resume: false })
requests.length = 0
response = [
@ -1594,7 +1593,7 @@ describe("SessionRunnerLLM", () => {
yield* session.resume(sessionID)
yield* replaySessionProjection(sessionID)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Continue" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
response = []
yield* session.resume(sessionID)
@ -1624,7 +1623,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Echo five times" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Echo five times" }), resume: false })
requests.length = 0
executions.length = 0
@ -1685,7 +1684,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Echo twice" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Echo twice" }), resume: false })
requests.length = 0
executions.length = 0
@ -1773,7 +1772,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Run once" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run once" }), resume: false })
requests.length = 0
responses = undefined
@ -1812,7 +1811,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Start working" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
requests.length = 0
responses = [
@ -1832,7 +1831,7 @@ describe("SessionRunnerLLM", () => {
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
yield* Deferred.await(streamStarted)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Change direction" }) })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Change direction" }) })
yield* Deferred.succeed(streamGate, undefined)
yield* Fiber.join(first)
streamGate = undefined
@ -1855,7 +1854,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Start working" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
requests.length = 0
responses = [
@ -1883,7 +1882,7 @@ describe("SessionRunnerLLM", () => {
yield* Deferred.await(streamStarted)
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Wait until continuation ends" }),
prompt: Prompt.make({ text: "Wait until continuation ends" }),
delivery: "queue",
})
yield* Deferred.succeed(streamGate, undefined)
@ -1903,7 +1902,7 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const { db } = yield* Database.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Interrupt current work" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt current work" }), resume: false })
requests.length = 0
responses = [
@ -1921,7 +1920,7 @@ describe("SessionRunnerLLM", () => {
yield* Deferred.await(streamStarted)
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Run after interrupt" }),
prompt: Prompt.make({ text: "Run after interrupt" }),
delivery: "queue",
})
yield* session.interrupt(sessionID)
@ -1946,7 +1945,7 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const { db } = yield* Database.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Interrupt current work" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt current work" }), resume: false })
requests.length = 0
responses = [
@ -1964,7 +1963,7 @@ describe("SessionRunnerLLM", () => {
yield* Deferred.await(streamStarted)
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Steer after interrupt" }),
prompt: Prompt.make({ text: "Steer after interrupt" }),
})
yield* session.interrupt(sessionID)
expect(yield* Fiber.await(run)).toMatchObject({ _tag: "Failure" })
@ -1988,7 +1987,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Start working" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
requests.length = 0
responses = [
@ -2013,8 +2012,8 @@ describe("SessionRunnerLLM", () => {
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
yield* Deferred.await(streamStarted)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Queue first" }), delivery: "queue" })
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Queue second" }), delivery: "queue" })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Queue first" }), delivery: "queue" })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Queue second" }), delivery: "queue" })
yield* Deferred.succeed(streamGate, undefined)
yield* Fiber.join(first)
streamGate = undefined
@ -2031,10 +2030,10 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Start steering" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start steering" }), resume: false })
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Queue for later" }),
prompt: Prompt.make({ text: "Queue for later" }),
delivery: "queue",
resume: false,
})
@ -2065,7 +2064,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Start working" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
requests.length = 0
responses = [
@ -2096,13 +2095,13 @@ describe("SessionRunnerLLM", () => {
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
while (requests.length < 1) yield* Effect.yieldNow
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Queue first" }), delivery: "queue" })
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Queue second" }), delivery: "queue" })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Queue first" }), delivery: "queue" })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Queue second" }), delivery: "queue" })
streamGate = secondGate
yield* Deferred.succeed(firstGate, undefined)
while (requests.length < 2) yield* Effect.yieldNow
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Steer before next queued input" }) })
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Also steer before next queued input" }) })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Steer before next queued input" }) })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Also steer before next queued input" }) })
yield* Deferred.succeed(secondGate, undefined)
yield* Fiber.join(first)
streamGate = undefined
@ -2130,7 +2129,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Start working" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
requests.length = 0
responses = [
@ -2150,8 +2149,8 @@ describe("SessionRunnerLLM", () => {
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
yield* Deferred.await(streamStarted)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First steer" }) })
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second steer" }) })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First steer" }) })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second steer" }) })
yield* Deferred.succeed(streamGate, undefined)
yield* Fiber.join(first)
streamGate = undefined
@ -2170,7 +2169,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Start working" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
requests.length = 0
responses = undefined
@ -2181,7 +2180,7 @@ describe("SessionRunnerLLM", () => {
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
yield* Deferred.await(streamStarted)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Recover with this" }) })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Recover with this" }) })
yield* Deferred.succeed(streamGate, undefined)
expect(yield* Fiber.join(first).pipe(Effect.flip)).toBe(streamFailure)
@ -2200,7 +2199,7 @@ describe("SessionRunnerLLM", () => {
yield* setup
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Recover interrupted tool" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Recover interrupted tool" }), resume: false })
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID, Number.MAX_SAFE_INTEGER)
const assistantMessageID = SessionMessage.ID.create()
yield* events.publish(SessionEvent.Step.Started, {
@ -2262,7 +2261,7 @@ describe("SessionRunnerLLM", () => {
const events = yield* EventV2.Service
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Recover interrupted hosted tool" }),
prompt: Prompt.make({ text: "Recover interrupted hosted tool" }),
resume: false,
})
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID, Number.MAX_SAFE_INTEGER)
@ -2322,7 +2321,7 @@ describe("SessionRunnerLLM", () => {
const events = yield* EventV2.Service
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Recover interrupted tool input" }),
prompt: Prompt.make({ text: "Recover interrupted tool input" }),
resume: false,
})
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID, Number.MAX_SAFE_INTEGER)
@ -2360,7 +2359,7 @@ describe("SessionRunnerLLM", () => {
const session = yield* SessionV2.Service
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Wait in queue" }),
prompt: Prompt.make({ text: "Wait in queue" }),
delivery: "queue",
resume: false,
})
@ -2382,7 +2381,7 @@ describe("SessionRunnerLLM", () => {
const defect = new Error("fail after prompt promotion")
let fail = true
yield* events.project(SessionEvent.Prompted, () => (fail ? Effect.die(defect) : Effect.void))
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Recover promoted input" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Recover promoted input" }), resume: false })
expect(yield* session.resume(sessionID).pipe(Effect.catchDefect(Effect.succeed))).toBe(defect)
fail = false
@ -2410,7 +2409,7 @@ describe("SessionRunnerLLM", () => {
)
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Run committed promotion" }),
prompt: Prompt.make({ text: "Run committed promotion" }),
resume: false,
})
@ -2427,8 +2426,8 @@ describe("SessionRunnerLLM", () => {
yield* setup
yield* insertSession(otherSessionID)
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Run first" }), resume: false })
yield* session.prompt({ sessionID: otherSessionID, prompt: new Prompt({ text: "Run second" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run first" }), resume: false })
yield* session.prompt({ sessionID: otherSessionID, prompt: Prompt.make({ text: "Run second" }), resume: false })
requests.length = 0
responses = undefined
@ -2454,37 +2453,31 @@ describe("SessionRunnerLLM", () => {
}),
)
it.effect("bounds external session prompt cache keys", () =>
it.effect("bounds 64-character session prompt cache keys", () =>
Effect.gen(function* () {
yield* setup
const externalSessionID = SessionV2.ID.fromExternal({
namespace: "discord",
key: "thread-one",
})
const otherExternalSessionID = SessionV2.ID.fromExternal({
namespace: "discord",
key: "thread-two",
})
yield* insertSession(externalSessionID)
yield* insertSession(otherExternalSessionID)
const longSessionID = SessionV2.ID.make(`ses_${"a".repeat(64)}`)
const otherLongSessionID = SessionV2.ID.make(`ses_${"b".repeat(64)}`)
yield* insertSession(longSessionID)
yield* insertSession(otherLongSessionID)
const session = yield* SessionV2.Service
yield* session.prompt({
sessionID: externalSessionID,
prompt: new Prompt({ text: "Run external session" }),
sessionID: longSessionID,
prompt: Prompt.make({ text: "Run long session" }),
resume: false,
})
yield* session.prompt({
sessionID: otherExternalSessionID,
prompt: new Prompt({ text: "Run other external session" }),
sessionID: otherLongSessionID,
prompt: Prompt.make({ text: "Run other long session" }),
resume: false,
})
requests.length = 0
yield* session.resume(externalSessionID)
yield* session.resume(otherExternalSessionID)
yield* session.resume(longSessionID)
yield* session.resume(otherLongSessionID)
const keys = requests.map((request) => request.providerOptions?.openai?.promptCacheKey)
expect(keys).toEqual([externalSessionID.slice(4), otherExternalSessionID.slice(4)])
expect(keys).toEqual([longSessionID.slice(4), otherLongSessionID.slice(4)])
expect(keys.every((key) => typeof key === "string" && key.length === 64)).toBe(true)
expect(keys[0]).not.toBe(keys[1])
}),
@ -2494,7 +2487,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Retry after failure" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Retry after failure" }), resume: false })
requests.length = 0
responses = undefined
@ -2525,7 +2518,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Call missing" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call missing" }), resume: false })
requests.length = 0
responses = [
@ -2571,7 +2564,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Call defect" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call defect" }), resume: false })
requests.length = 0
responses = [
@ -2620,7 +2613,7 @@ describe("SessionRunnerLLM", () => {
questions.ask({ sessionID: context.sessionID, questions: [] }).pipe(Effect.as({}), Effect.orDie),
}),
})
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Ask then stop" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Ask then stop" }), resume: false })
requests.length = 0
responses = [
@ -2665,7 +2658,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Settle before failing" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Settle before failing" }), resume: false })
const failure = providerUnavailable()
toolExecutionGate = yield* Deferred.make<void>()
responseStream = Stream.concat(
@ -2699,7 +2692,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Interrupt blocked tool" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt blocked tool" }), resume: false })
executions.length = 0
toolExecutionGate = yield* Deferred.make<void>()
responseStream = Stream.concat(
@ -2749,7 +2742,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Interrupt provider" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt provider" }), resume: false })
requests.length = 0
response = []
streamGate = yield* Deferred.make<void>()
@ -2772,7 +2765,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Interrupt tool settlement" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt tool settlement" }), resume: false })
executions.length = 0
toolExecutionGate = yield* Deferred.make<void>()
response = [
@ -2815,7 +2808,7 @@ describe("SessionRunnerLLM", () => {
}),
)
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Finish at the limit" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Finish at the limit" }), resume: false })
requests.length = 0
executions.length = 0
@ -2863,7 +2856,7 @@ describe("SessionRunnerLLM", () => {
}),
)
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Start work" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start work" }), resume: false })
requests.length = 0
executions.length = 0
@ -2891,7 +2884,7 @@ describe("SessionRunnerLLM", () => {
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
yield* Deferred.await(streamStarted)
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Change direction" }) })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Change direction" }) })
yield* Deferred.succeed(streamGate, undefined)
yield* Fiber.join(run)
streamGate = undefined
@ -2909,7 +2902,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Fail durably" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail durably" }), resume: false })
requests.length = 0
responses = undefined
@ -2931,7 +2924,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Fail before step" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail before step" }), resume: false })
requests.length = 0
response = [LLMEvent.providerError({ message: "Provider unavailable" })]
@ -2950,7 +2943,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Fail after output" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail after output" }), resume: false })
requests.length = 0
response = [
@ -2979,7 +2972,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Fail raw stream durably" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail raw stream durably" }), resume: false })
const failure = providerUnavailable()
responseStream = Stream.fail(failure)
@ -2998,7 +2991,7 @@ describe("SessionRunnerLLM", () => {
const session = yield* SessionV2.Service
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Do not continue failed provider" }),
prompt: Prompt.make({ text: "Do not continue failed provider" }),
resume: false,
})
@ -3021,7 +3014,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Fail hosted tool durably" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail hosted tool durably" }), resume: false })
requests.length = 0
response = [
@ -3052,7 +3045,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Fail hosted tool at EOF" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail hosted tool at EOF" }), resume: false })
response = [
LLMEvent.stepStart({ index: 0 }),
LLMEvent.toolCall({
@ -3079,7 +3072,7 @@ describe("SessionRunnerLLM", () => {
const session = yield* SessionV2.Service
yield* session.prompt({
sessionID,
prompt: new Prompt({ text: "Fail hosted tool on raw failure" }),
prompt: Prompt.make({ text: "Fail hosted tool on raw failure" }),
resume: false,
})
const failure = providerUnavailable()
@ -3114,7 +3107,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Two blocks" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Two blocks" }), resume: false })
responses = undefined
streamGate = undefined
@ -3177,7 +3170,7 @@ describe("SessionRunnerLLM", () => {
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Call provider tool" }), resume: false })
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call provider tool" }), resume: false })
responses = undefined
streamGate = undefined

View file

@ -0,0 +1,183 @@
import { expect, test } from "bun:test"
import { Schema } from "effect"
import { AgentV2 } from "@opencode-ai/core/agent"
import { ModelV2 } from "@opencode-ai/core/model"
import { SessionV2 } from "@opencode-ai/core/session"
import { Agent } from "@opencode-ai/schema/agent"
import { Location } from "@opencode-ai/schema/location"
import { Model } from "@opencode-ai/schema/model"
import { AgentAttachment, FileAttachment, Prompt, Source } from "@opencode-ai/schema/prompt"
import { Provider } from "@opencode-ai/schema/provider"
import { Project } from "@opencode-ai/schema/project"
import { Session } from "@opencode-ai/schema/session"
import { SessionInput } from "@opencode-ai/schema/session-input"
import { SessionMessage } from "@opencode-ai/schema/session-message"
import { Workspace } from "@opencode-ai/schema/workspace"
import { Command } from "@opencode-ai/schema/command"
import { Connection } from "@opencode-ai/schema/connection"
import { Credential } from "@opencode-ai/schema/credential"
import { FileSystem } from "@opencode-ai/schema/filesystem"
import { Integration } from "@opencode-ai/schema/integration"
import { LLM } from "@opencode-ai/schema/llm"
import { ModelRequest } from "@opencode-ai/schema/model-request"
import { Permission } from "@opencode-ai/schema/permission"
import { Reference } from "@opencode-ai/schema/reference"
import { Skill } from "@opencode-ai/schema/skill"
import { AbsolutePath, DateTimeUtcFromMillis } from "@opencode-ai/schema/schema"
import { ProviderV2 } from "@opencode-ai/core/provider"
test("Core reuses the canonical shared schemas", async () => {
const [
coreCommand,
coreConnection,
coreCredential,
coreFileSystem,
coreIntegration,
coreLocation,
coreLLM,
coreModelRequest,
corePermission,
coreProject,
coreReference,
coreSessionInput,
coreSessionMessage,
corePrompt,
coreSkill,
coreV2Schema,
coreWorkspace,
] = await Promise.all([
import("@opencode-ai/core/command"),
import("@opencode-ai/core/integration/connection"),
import("@opencode-ai/core/credential"),
import("@opencode-ai/core/filesystem/schema"),
import("@opencode-ai/core/integration"),
import("@opencode-ai/core/location"),
import("@opencode-ai/llm"),
import("@opencode-ai/core/model-request"),
import("@opencode-ai/core/permission/schema"),
import("@opencode-ai/core/project/schema"),
import("@opencode-ai/core/reference"),
import("@opencode-ai/core/session/input"),
import("@opencode-ai/core/session/message"),
import("@opencode-ai/core/session/prompt"),
import("@opencode-ai/core/skill"),
import("@opencode-ai/core/v2-schema"),
import("@opencode-ai/core/workspace"),
])
const schemas = [
[AgentV2.ID, Agent.ID],
[AgentV2.Color, Agent.Color],
[AgentV2.Info, Agent.Info],
[coreCommand.Info, Command.Info],
[coreConnection.CredentialInfo, Connection.CredentialInfo],
[coreConnection.EnvInfo, Connection.EnvInfo],
[coreConnection.Info, Connection.Info],
[coreCredential.ID, Credential.ID],
[coreCredential.OAuth, Credential.OAuth],
[coreCredential.Key, Credential.Key],
[coreCredential.Value, Credential.Value],
[coreFileSystem.Entry, FileSystem.Entry],
[coreFileSystem.Submatch, FileSystem.Submatch],
[coreFileSystem.Match, FileSystem.Match],
[coreIntegration.When, Integration.When],
[coreIntegration.TextPrompt, Integration.TextPrompt],
[coreIntegration.SelectPrompt, Integration.SelectPrompt],
[coreIntegration.Prompt, Integration.Prompt],
[coreIntegration.OAuthMethod, Integration.OAuthMethod],
[coreIntegration.KeyMethod, Integration.KeyMethod],
[coreIntegration.EnvMethod, Integration.EnvMethod],
[coreIntegration.Method, Integration.Method],
[coreIntegration.Inputs, Integration.Inputs],
[coreIntegration.Ref, Integration.Ref],
[coreLocation.Ref, Location.Ref],
[coreLLM.ProviderMetadata, LLM.ProviderMetadata],
[coreLLM.ToolTextContent, LLM.ToolTextContent],
[coreLLM.ToolFileContent, LLM.ToolFileContent],
[coreLLM.ToolContent, LLM.ToolContent],
[ModelV2.ID, Model.ID],
[ModelV2.VariantID, Model.VariantID],
[ModelV2.Ref, Model.Ref],
[ModelV2.Family, Model.Family],
[ModelV2.Capabilities, Model.Capabilities],
[ModelV2.Cost, Model.Cost],
[ModelV2.Api, Model.Api],
[ModelV2.Info, Model.Info],
[ProviderV2.ID, Provider.ID],
[ProviderV2.AISDK, Provider.AISDK],
[ProviderV2.Native, Provider.Native],
[ProviderV2.Api, Provider.Api],
[ProviderV2.Request, Provider.Request],
[ProviderV2.Info, Provider.Info],
[coreModelRequest.Generation, ModelRequest.Generation],
[coreModelRequest.Request, ModelRequest.Request],
[corePermission.Effect, Permission.Effect],
[corePermission.Rule, Permission.Rule],
[corePermission.Ruleset, Permission.Ruleset],
[coreProject.ID, Project.ID],
[coreReference.LocalSource, Reference.LocalSource],
[coreReference.GitSource, Reference.GitSource],
[coreReference.Source, Reference.Source],
[SessionV2.ID, Session.ID],
[SessionV2.Info, Session.Info],
[SessionV2.ListAnchor, Session.ListAnchor],
[coreSessionInput.Delivery, SessionInput.Delivery],
[coreSessionInput.Admitted, SessionInput.Admitted],
[coreSessionMessage.ID, SessionMessage.ID],
[coreSessionMessage.UnknownError, SessionMessage.UnknownError],
[coreSessionMessage.AgentSwitched, SessionMessage.AgentSwitched],
[coreSessionMessage.ModelSwitched, SessionMessage.ModelSwitched],
[coreSessionMessage.User, SessionMessage.User],
[coreSessionMessage.Synthetic, SessionMessage.Synthetic],
[coreSessionMessage.System, SessionMessage.System],
[coreSessionMessage.Shell, SessionMessage.Shell],
[coreSessionMessage.ToolStatePending, SessionMessage.ToolStatePending],
[coreSessionMessage.ToolStateRunning, SessionMessage.ToolStateRunning],
[coreSessionMessage.ToolStateCompleted, SessionMessage.ToolStateCompleted],
[coreSessionMessage.ToolStateError, SessionMessage.ToolStateError],
[coreSessionMessage.ToolState, SessionMessage.ToolState],
[coreSessionMessage.AssistantTool, SessionMessage.AssistantTool],
[coreSessionMessage.AssistantText, SessionMessage.AssistantText],
[coreSessionMessage.AssistantReasoning, SessionMessage.AssistantReasoning],
[coreSessionMessage.AssistantContent, SessionMessage.AssistantContent],
[coreSessionMessage.Assistant, SessionMessage.Assistant],
[coreSessionMessage.Compaction, SessionMessage.Compaction],
[coreSessionMessage.Message, SessionMessage.Message],
[corePrompt.Source, Source],
[corePrompt.FileAttachment, FileAttachment],
[corePrompt.AgentAttachment, AgentAttachment],
[corePrompt.Prompt, Prompt],
[coreSkill.DirectorySource, Skill.DirectorySource],
[coreSkill.UrlSource, Skill.UrlSource],
[coreSkill.EmbeddedSource, Skill.EmbeddedSource],
[coreSkill.Source, Skill.Source],
[coreSkill.Info, Skill.Info],
[coreV2Schema.DateTimeUtcFromMillis, DateTimeUtcFromMillis],
[coreWorkspace.ID, Workspace.ID],
]
for (const [core, shared] of schemas) expect(core).toBe(shared)
expect(Agent.Info.empty(Agent.ID.make("test"))).toEqual(AgentV2.Info.empty(AgentV2.ID.make("test")))
expect(Model.Info.empty(Provider.ID.make("test"), Model.ID.make("model"))).toEqual(
ModelV2.Info.empty(ProviderV2.ID.make("test"), ModelV2.ID.make("model")),
)
expect(Provider.Info.empty(Provider.ID.make("test"))).toEqual(ProviderV2.Info.empty(ProviderV2.ID.make("test")))
expect(Skill.Source.key(Skill.DirectorySource.make({ type: "directory", path: AbsolutePath.make("/tmp") }))).toBe(
"directory:/tmp",
)
})
test("shared record schemas construct and decode plain objects", () => {
const made = Prompt.make({ text: "hello" })
const decoded = Schema.decodeUnknownSync(Prompt)({ text: "hello" })
const content = Schema.decodeUnknownSync(SessionMessage.AssistantText)({ type: "text", id: "part_1", text: "hi" })
expect(Object.getPrototypeOf(made)).toBe(Object.prototype)
expect(Object.getPrototypeOf(decoded)).toBe(Object.prototype)
expect(Object.getPrototypeOf(content)).toBe(Object.prototype)
expect(Prompt.ast.annotations?.identifier).toBe("Prompt")
expect(SessionMessage.AssistantText.ast.annotations?.identifier).toBe("Session.Message.Assistant.Text")
expect(Prompt.equivalence(Prompt.make({ text: "hello" }), decoded)).toBe(true)
expect(Prompt.fromUserMessage({ text: "hello" })).toEqual(made)
expect(Workspace.ID.ascending("")).toStartWith("wrk_")
})

View file

@ -74,7 +74,7 @@ describe("SkillV2", () => {
{ type: "directory", path: AbsolutePath.make(second) },
])
expect(yield* skill.list()).toEqual([
new SkillV2.Info({
SkillV2.Info.make({
name: "foo",
slash: true,
location: AbsolutePath.make(path.join(first, "foo.md")),

View file

@ -9,18 +9,18 @@ import { SkillGuidance } from "@opencode-ai/core/skill/guidance"
import { it } from "../lib/effect"
const build = AgentV2.ID.make("build")
const effect = new SkillV2.Info({
const effect = SkillV2.Info.make({
name: "effect",
description: "Build applications with Effect",
location: AbsolutePath.make(path.resolve("/skills/effect/SKILL.md")),
content: "Effect guidance",
})
const hidden = new SkillV2.Info({
const hidden = SkillV2.Info.make({
name: "hidden",
location: AbsolutePath.make(path.resolve("/skills/hidden/SKILL.md")),
content: "Undescribed guidance",
})
const denied = new SkillV2.Info({
const denied = SkillV2.Info.make({
name: "denied",
description: "Must not be advertised",
location: AbsolutePath.make(path.resolve("/skills/denied/SKILL.md")),
@ -32,7 +32,7 @@ const layer = (list: () => SkillV2.Info[]) =>
describe("SkillGuidance", () => {
it.effect("renders described agent skills and reconciles the complete available list", () => {
const agent = new AgentV2.Info({
const agent = AgentV2.Info.make({
...AgentV2.Info.empty(build),
permissions: [{ action: "skill", resource: "denied", effect: "deny" }],
})
@ -69,7 +69,7 @@ describe("SkillGuidance", () => {
})
it.effect("omits guidance when the selected agent denies all skills", () => {
const agent = new AgentV2.Info({
const agent = AgentV2.Info.make({
...AgentV2.Info.empty(build),
permissions: [{ action: "skill", resource: "*", effect: "deny" }],
})
@ -85,7 +85,7 @@ describe("SkillGuidance", () => {
})
it.effect("omits guidance when a resource-specific denial follows the global denial", () => {
const agent = new AgentV2.Info({
const agent = AgentV2.Info.make({
...AgentV2.Info.empty(build),
permissions: [
{ action: "skill", resource: "*", effect: "deny" },
@ -104,7 +104,7 @@ describe("SkillGuidance", () => {
})
it.effect("retains specifically allowed skills after a global denial", () => {
const agent = new AgentV2.Info({
const agent = AgentV2.Info.make({
...AgentV2.Info.empty(build),
permissions: [
{ action: "skill", resource: "*", effect: "deny" },
@ -120,7 +120,7 @@ describe("SkillGuidance", () => {
})
it.effect("omits guidance when a specifically allowed skill is denied again", () => {
const agent = new AgentV2.Info({
const agent = AgentV2.Info.make({
...AgentV2.Info.empty(build),
permissions: [
{ action: "skill", resource: "*", effect: "deny" },

View file

@ -122,7 +122,7 @@ describe("SkillTool", () => {
}),
).toEqual({ type: "error", value: "Unable to load skill effect" })
deny = false
const flat = new SkillV2.Info({
const flat = SkillV2.Info.make({
name: "public",
description: "Public guidance",
location: AbsolutePath.make(path.join(tmp.path, "public.md")),

View file

@ -45,6 +45,7 @@
"dependencies": {
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
"@opencode-ai/schema": "workspace:*",
"aws4fetch": "1.0.20",
"effect": "catalog:"
}

View file

@ -1,4 +1,7 @@
import { Schema } from "effect"
import { ProviderMetadata } from "@opencode-ai/schema/llm"
export { ProviderMetadata }
/** Stable string identifier for a protocol implementation. */
export const ProtocolID = Schema.String
@ -38,6 +41,3 @@ export type FinishReason = Schema.Schema.Type<typeof FinishReason>
export const JsonSchema = Schema.Record(Schema.String, Schema.Unknown)
export type JsonSchema = Schema.Schema.Type<typeof JsonSchema>
export const ProviderMetadata = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown))
export type ProviderMetadata = Schema.Schema.Type<typeof ProviderMetadata>

View file

@ -1,4 +1,5 @@
import { Schema } from "effect"
import { ToolContent, ToolFileContent, ToolTextContent } from "@opencode-ai/schema/llm"
import { JsonSchema, MessageRole, ProviderMetadata } from "./ids"
import { CacheHint, CachePolicy, GenerationOptions, HttpOptions, ModelSchema, ProviderOptions } from "./options"
import { isRecord } from "../utils/record"
@ -39,23 +40,7 @@ export const MediaPart = Schema.Struct({
}).annotate({ identifier: "LLM.Content.Media" })
export type MediaPart = Schema.Schema.Type<typeof MediaPart>
export const ToolTextContent = Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
}).annotate({ identifier: "Tool.TextContent" })
export type ToolTextContent = typeof ToolTextContent.Type
export const ToolFileContent = Schema.Struct({
type: Schema.Literal("file"),
uri: Schema.String,
mime: Schema.String,
name: Schema.optional(Schema.String),
}).annotate({ identifier: "Tool.FileContent" })
export type ToolFileContent = typeof ToolFileContent.Type
/** Ordered, provider-independent content shown to models and UIs after a tool succeeds. */
export const ToolContent = Schema.Union([ToolTextContent, ToolFileContent]).pipe(Schema.toTaggedUnion("type"))
export type ToolContent = Schema.Schema.Type<typeof ToolContent>
export { ToolContent, ToolFileContent, ToolTextContent }
const isToolResultValue = (value: unknown): value is ToolResultValue =>
isRecord(value) &&

View file

@ -1086,12 +1086,12 @@ export const layer = Layer.effect(
}
if (part.type === "file") {
result.files.push(
new FileAttachment({
FileAttachment.make({
uri: part.url,
mime: part.mime,
name: part.filename,
source: part.source
? new Source({
? Source.make({
start: part.source.text.start,
end: part.source.text.end,
text: part.source.text.value,
@ -1102,10 +1102,10 @@ export const layer = Layer.effect(
}
if (part.type === "agent") {
result.agents.push(
new AgentAttachment({
AgentAttachment.make({
name: part.name,
source: part.source
? new Source({
? Source.make({
start: part.source.start,
end: part.source.end,
text: part.source.value,
@ -1130,7 +1130,7 @@ export const layer = Layer.effect(
messageID: SessionMessage.ID.create(),
timestamp: DateTime.makeUnsafe(info.time.created),
delivery: "steer",
prompt: new Prompt({
prompt: Prompt.make({
text: nextPrompt.text.join("\n"),
files: nextPrompt.files,
agents: nextPrompt.agents,

Some files were not shown because too many files have changed in this diff Show more