diff --git a/packages/client/script/build.ts b/packages/client/script/build.ts index 6a83d52b99..70f1dcc587 100644 --- a/packages/client/script/build.ts +++ b/packages/client/script/build.ts @@ -3,15 +3,14 @@ import { compile, emitEffectImported, emitEffectShape, emitPromise, write } from import { ClientApi, effectOmitEndpoints, - endpointNames, groupNames, promiseOmitEndpoints, } from "@opencode-ai/protocol/client" import { Effect } from "effect" import { fileURLToPath } from "url" -const promiseContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints }) -const effectContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: effectOmitEndpoints }) +const promiseContract = compile(ClientApi, { groupNames, omitEndpoints: promiseOmitEndpoints }) +const effectContract = compile(ClientApi, { groupNames, omitEndpoints: effectOmitEndpoints }) await Effect.runPromise( Effect.all( diff --git a/packages/client/src/contract.ts b/packages/client/src/contract.ts index 319a9f72e2..8fd9994f5c 100644 --- a/packages/client/src/contract.ts +++ b/packages/client/src/contract.ts @@ -1,7 +1,6 @@ export { ClientApi, effectOmitEndpoints, - endpointNames, groupNames, promiseOmitEndpoints, } from "@opencode-ai/protocol/client" diff --git a/packages/client/src/effect/api/api.ts b/packages/client/src/effect/api/api.ts index d34ff3f07e..0c7b7a3af3 100644 --- a/packages/client/src/effect/api/api.ts +++ b/packages/client/src/effect/api/api.ts @@ -291,9 +291,11 @@ export interface SessionApi { readonly shell: SessionShellOperation readonly compact: SessionCompactOperation readonly wait: SessionWaitOperation - readonly revertStage: SessionRevertStageOperation - readonly revertClear: SessionRevertClearOperation - readonly revertCommit: SessionRevertCommitOperation + readonly revert: { + readonly stage: SessionRevertStageOperation + readonly clear: SessionRevertClearOperation + readonly commit: SessionRevertCommitOperation + } readonly context: SessionContextOperation readonly instructions: { readonly entry: { @@ -438,11 +440,15 @@ export type IntegrationAttemptCancelOperation = ( export interface IntegrationApi { readonly list: IntegrationListOperation readonly get: IntegrationGetOperation - readonly connectKey: IntegrationConnectKeyOperation - readonly connectOauth: IntegrationConnectOauthOperation - readonly attemptStatus: IntegrationAttemptStatusOperation - readonly attemptComplete: IntegrationAttemptCompleteOperation - readonly attemptCancel: IntegrationAttemptCancelOperation + readonly connect: { + readonly key: IntegrationConnectKeyOperation + readonly oauth: IntegrationConnectOauthOperation + } + readonly attempt: { + readonly status: IntegrationAttemptStatusOperation + readonly complete: IntegrationAttemptCompleteOperation + readonly cancel: IntegrationAttemptCancelOperation + } } type Endpoint10_0Request = Parameters[0] @@ -453,11 +459,13 @@ export type ServerMcpListOperation = (input?: Endpoint10_0Input) => E type Endpoint10_1Request = Parameters[0] export type Endpoint10_1Input = { readonly location?: Endpoint10_1Request["query"]["location"] } export type Endpoint10_1Output = EffectValue> -export type ServerMcpCatalogOperation = (input?: Endpoint10_1Input) => Effect.Effect +export type ServerMcpResourceCatalogOperation = ( + input?: Endpoint10_1Input, +) => Effect.Effect export interface ServerMcpApi { readonly list: ServerMcpListOperation - readonly catalog: ServerMcpCatalogOperation + readonly resource: { readonly catalog: ServerMcpResourceCatalogOperation } } type Endpoint11_0Request = Parameters[0] @@ -507,7 +515,7 @@ export interface ProjectApi { type Endpoint13_0Request = Parameters[0] export type Endpoint13_0Input = { readonly location?: Endpoint13_0Request["query"]["location"] } export type Endpoint13_0Output = EffectValue> -export type FormListRequestsOperation = (input?: Endpoint13_0Input) => Effect.Effect +export type FormRequestListOperation = (input?: Endpoint13_0Input) => Effect.Effect type Endpoint13_1Request = Parameters[0] export type Endpoint13_1Input = { readonly sessionID: Endpoint13_1Request["params"]["sessionID"] } @@ -561,7 +569,7 @@ export type Endpoint13_6Output = EffectValue = (input: Endpoint13_6Input) => Effect.Effect export interface FormApi { - readonly listRequests: FormListRequestsOperation + readonly request: { readonly list: FormRequestListOperation } readonly list: FormListOperation readonly create: FormCreateOperation readonly get: FormGetOperation @@ -573,7 +581,7 @@ export interface FormApi { type Endpoint14_0Request = Parameters[0] export type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] } export type Endpoint14_0Output = EffectValue> -export type PermissionListRequestsOperation = ( +export type PermissionRequestListOperation = ( input?: Endpoint14_0Input, ) => Effect.Effect @@ -582,14 +590,14 @@ export type Endpoint14_1Input = { readonly projectID?: Endpoint14_1Request["quer export type Endpoint14_1Output = EffectValue< ReturnType >["data"] -export type PermissionListSavedOperation = ( +export type PermissionSavedListOperation = ( input?: Endpoint14_1Input, ) => Effect.Effect type Endpoint14_2Request = Parameters[0] export type Endpoint14_2Input = { readonly id: Endpoint14_2Request["params"]["id"] } export type Endpoint14_2Output = EffectValue> -export type PermissionRemoveSavedOperation = ( +export type PermissionSavedRemoveOperation = ( input: Endpoint14_2Input, ) => Effect.Effect @@ -637,9 +645,8 @@ export type Endpoint14_6Output = EffectValue = (input: Endpoint14_6Input) => Effect.Effect export interface PermissionApi { - readonly listRequests: PermissionListRequestsOperation - readonly listSaved: PermissionListSavedOperation - readonly removeSaved: PermissionRemoveSavedOperation + readonly request: { readonly list: PermissionRequestListOperation } + readonly saved: { readonly list: PermissionSavedListOperation; readonly remove: PermissionSavedRemoveOperation } readonly create: PermissionCreateOperation readonly list: PermissionListOperation readonly get: PermissionGetOperation @@ -808,7 +815,7 @@ export interface ShellApi { type Endpoint21_0Request = Parameters[0] export type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] } export type Endpoint21_0Output = EffectValue> -export type QuestionListRequestsOperation = ( +export type QuestionRequestListOperation = ( input?: Endpoint21_0Input, ) => Effect.Effect @@ -835,7 +842,7 @@ export type Endpoint21_3Output = EffectValue = (input: Endpoint21_3Input) => Effect.Effect export interface QuestionApi { - readonly listRequests: QuestionListRequestsOperation + readonly request: { readonly list: QuestionRequestListOperation } readonly list: QuestionListOperation readonly reply: QuestionReplyOperation readonly reject: QuestionRejectOperation @@ -905,16 +912,15 @@ export interface VcsApi { } export type Endpoint25_0Output = EffectValue> -export type DebugLocationOperation = () => Effect.Effect +export type DebugLocationListOperation = () => Effect.Effect type Endpoint25_1Request = Parameters[0] export type Endpoint25_1Input = { readonly location?: Endpoint25_1Request["query"]["location"] } export type Endpoint25_1Output = EffectValue> -export type DebugEvictLocationOperation = (input?: Endpoint25_1Input) => Effect.Effect +export type DebugLocationEvictOperation = (input?: Endpoint25_1Input) => Effect.Effect export interface DebugApi { - readonly location: DebugLocationOperation - readonly evictLocation: DebugEvictLocationOperation + readonly location: { readonly list: DebugLocationListOperation; readonly evict: DebugLocationEvictOperation } } export interface AppApi { diff --git a/packages/client/src/effect/generated/client.ts b/packages/client/src/effect/generated/client.ts index 008cfb294d..5175567f45 100644 --- a/packages/client/src/effect/generated/client.ts +++ b/packages/client/src/effect/generated/client.ts @@ -381,9 +381,7 @@ const adaptGroup4 = (raw: RawClient["server.session"]) => ({ shell: Endpoint4_14(raw), compact: Endpoint4_15(raw), wait: Endpoint4_16(raw), - revertStage: Endpoint4_17(raw), - revertClear: Endpoint4_18(raw), - revertCommit: Endpoint4_19(raw), + revert: { stage: Endpoint4_17(raw), clear: Endpoint4_18(raw), commit: Endpoint4_19(raw) }, context: Endpoint4_20(raw), instructions: { entry: { list: Endpoint4_21(raw), put: Endpoint4_22(raw), remove: Endpoint4_23(raw) } }, log: Endpoint4_24(raw), @@ -536,11 +534,8 @@ const Endpoint9_6 = (raw: RawClient["server.integration"]) => (input: Endpoint9_ const adaptGroup9 = (raw: RawClient["server.integration"]) => ({ list: Endpoint9_0(raw), get: Endpoint9_1(raw), - connectKey: Endpoint9_2(raw), - connectOauth: Endpoint9_3(raw), - attemptStatus: Endpoint9_4(raw), - attemptComplete: Endpoint9_5(raw), - attemptCancel: Endpoint9_6(raw), + connect: { key: Endpoint9_2(raw), oauth: Endpoint9_3(raw) }, + attempt: { status: Endpoint9_4(raw), complete: Endpoint9_5(raw), cancel: Endpoint9_6(raw) }, }) type Endpoint10_0Request = Parameters[0] @@ -553,7 +548,10 @@ type Endpoint10_1Input = { readonly location?: Endpoint10_1Request["query"]["loc const Endpoint10_1 = (raw: RawClient["server.mcp"]) => (input?: Endpoint10_1Input) => raw["mcp.resource.catalog"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup10 = (raw: RawClient["server.mcp"]) => ({ list: Endpoint10_0(raw), catalog: Endpoint10_1(raw) }) +const adaptGroup10 = (raw: RawClient["server.mcp"]) => ({ + list: Endpoint10_0(raw), + resource: { catalog: Endpoint10_1(raw) }, +}) type Endpoint11_0Request = Parameters[0] type Endpoint11_0Input = { @@ -690,7 +688,7 @@ const Endpoint13_6 = (raw: RawClient["server.form"]) => (input: Endpoint13_6Inpu ) const adaptGroup13 = (raw: RawClient["server.form"]) => ({ - listRequests: Endpoint13_0(raw), + request: { list: Endpoint13_0(raw) }, list: Endpoint13_1(raw), create: Endpoint13_2(raw), get: Endpoint13_3(raw), @@ -778,9 +776,8 @@ const Endpoint14_6 = (raw: RawClient["server.permission"]) => (input: Endpoint14 }).pipe(Effect.mapError(mapClientError)) const adaptGroup14 = (raw: RawClient["server.permission"]) => ({ - listRequests: Endpoint14_0(raw), - listSaved: Endpoint14_1(raw), - removeSaved: Endpoint14_2(raw), + request: { list: Endpoint14_0(raw) }, + saved: { list: Endpoint14_1(raw), remove: Endpoint14_2(raw) }, create: Endpoint14_3(raw), list: Endpoint14_4(raw), get: Endpoint14_5(raw), @@ -1013,7 +1010,7 @@ const Endpoint21_3 = (raw: RawClient["server.question"]) => (input: Endpoint21_3 ) const adaptGroup21 = (raw: RawClient["server.question"]) => ({ - listRequests: Endpoint21_0(raw), + request: { list: Endpoint21_0(raw) }, list: Endpoint21_1(raw), reply: Endpoint21_2(raw), reject: Endpoint21_3(raw), @@ -1099,8 +1096,7 @@ const Endpoint25_1 = (raw: RawClient["server.debug"]) => (input?: Endpoint25_1In raw["debug.location.evict"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) const adaptGroup25 = (raw: RawClient["server.debug"]) => ({ - location: Endpoint25_0(raw), - evictLocation: Endpoint25_1(raw), + location: { list: Endpoint25_0(raw), evict: Endpoint25_1(raw) }, }) const adaptClient = (raw: RawClient) => ({ diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index 79ecf0e630..cc1b6b1854 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -89,8 +89,8 @@ import type { IntegrationAttemptCancelOutput, ServerMcpListInput, ServerMcpListOutput, - ServerMcpCatalogInput, - ServerMcpCatalogOutput, + ServerMcpResourceCatalogInput, + ServerMcpResourceCatalogOutput, CredentialUpdateInput, CredentialUpdateOutput, CredentialRemoveInput, @@ -100,8 +100,8 @@ import type { ProjectCurrentOutput, ProjectDirectoriesInput, ProjectDirectoriesOutput, - FormListRequestsInput, - FormListRequestsOutput, + FormRequestListInput, + FormRequestListOutput, FormListInput, FormListOutput, FormCreateInput, @@ -114,12 +114,12 @@ import type { FormReplyOutput, FormCancelInput, FormCancelOutput, - PermissionListRequestsInput, - PermissionListRequestsOutput, - PermissionListSavedInput, - PermissionListSavedOutput, - PermissionRemoveSavedInput, - PermissionRemoveSavedOutput, + PermissionRequestListInput, + PermissionRequestListOutput, + PermissionSavedListInput, + PermissionSavedListOutput, + PermissionSavedRemoveInput, + PermissionSavedRemoveOutput, PermissionCreateInput, PermissionCreateOutput, PermissionListInput, @@ -161,8 +161,8 @@ import type { ShellOutputOutput, ShellRemoveInput, ShellRemoveOutput, - QuestionListRequestsInput, - QuestionListRequestsOutput, + QuestionRequestListInput, + QuestionRequestListOutput, QuestionListInput, QuestionListOutput, QuestionReplyInput, @@ -181,9 +181,9 @@ import type { VcsStatusOutput, VcsDiffInput, VcsDiffOutput, - DebugLocationOutput, - DebugEvictLocationInput, - DebugEvictLocationOutput, + DebugLocationListOutput, + DebugLocationEvictInput, + DebugLocationEvictOutput, } from "./types" import { ClientError } from "./client-error" @@ -601,40 +601,42 @@ export function make(options: ClientOptions) { }, requestOptions, ), - revertStage: (input: SessionRevertStageInput, requestOptions?: RequestOptions) => - request<{ readonly data: SessionRevertStageOutput }>( - { - method: "POST", - path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`, - body: { messageID: input["messageID"], files: input["files"] }, - successStatus: 200, - declaredStatuses: [404, 409, 500, 400, 401], - empty: false, - }, - requestOptions, - ).then((value) => value.data), - revertClear: (input: SessionRevertClearInput, requestOptions?: RequestOptions) => - request( - { - method: "POST", - path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`, - successStatus: 204, - declaredStatuses: [404, 409, 500, 400, 401], - empty: true, - }, - requestOptions, - ), - revertCommit: (input: SessionRevertCommitInput, requestOptions?: RequestOptions) => - request( - { - method: "POST", - path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`, - successStatus: 204, - declaredStatuses: [404, 409, 400, 401], - empty: true, - }, - requestOptions, - ), + revert: { + stage: (input: SessionRevertStageInput, requestOptions?: RequestOptions) => + request<{ readonly data: SessionRevertStageOutput }>( + { + method: "POST", + path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`, + body: { messageID: input["messageID"], files: input["files"] }, + successStatus: 200, + declaredStatuses: [404, 409, 500, 400, 401], + empty: false, + }, + requestOptions, + ).then((value) => value.data), + clear: (input: SessionRevertClearInput, requestOptions?: RequestOptions) => + request( + { + method: "POST", + path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`, + successStatus: 204, + declaredStatuses: [404, 409, 500, 400, 401], + empty: true, + }, + requestOptions, + ), + commit: (input: SessionRevertCommitInput, requestOptions?: RequestOptions) => + request( + { + method: "POST", + path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`, + successStatus: 204, + declaredStatuses: [404, 409, 400, 401], + empty: true, + }, + requestOptions, + ), + }, context: (input: SessionContextInput, requestOptions?: RequestOptions) => request<{ readonly data: SessionContextOutput }>( { @@ -836,69 +838,73 @@ export function make(options: ClientOptions) { }, requestOptions, ), - connectKey: (input: IntegrationConnectKeyInput, requestOptions?: RequestOptions) => - request( - { - method: "POST", - path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`, - query: { location: input["location"] }, - body: { key: input["key"], label: input["label"] }, - successStatus: 204, - declaredStatuses: [400, 401], - empty: true, - }, - requestOptions, - ), - connectOauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) => - request( - { - method: "POST", - path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`, - query: { location: input["location"] }, - body: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] }, - successStatus: 200, - declaredStatuses: [400, 401], - empty: false, - }, - requestOptions, - ), - attemptStatus: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) => - request( - { - method: "GET", - path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, - query: { location: input["location"] }, - successStatus: 200, - declaredStatuses: [401, 400], - empty: false, - }, - requestOptions, - ), - attemptComplete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) => - request( - { - method: "POST", - path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`, - query: { location: input["location"] }, - body: { code: input["code"] }, - successStatus: 204, - declaredStatuses: [400, 401], - empty: true, - }, - requestOptions, - ), - attemptCancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) => - request( - { - method: "DELETE", - path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, - query: { location: input["location"] }, - successStatus: 204, - declaredStatuses: [401, 400], - empty: true, - }, - requestOptions, - ), + connect: { + key: (input: IntegrationConnectKeyInput, requestOptions?: RequestOptions) => + request( + { + method: "POST", + path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`, + query: { location: input["location"] }, + body: { key: input["key"], label: input["label"] }, + successStatus: 204, + declaredStatuses: [400, 401], + empty: true, + }, + requestOptions, + ), + oauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) => + request( + { + method: "POST", + path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`, + query: { location: input["location"] }, + body: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] }, + successStatus: 200, + declaredStatuses: [400, 401], + empty: false, + }, + requestOptions, + ), + }, + attempt: { + status: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, + query: { location: input["location"] }, + successStatus: 200, + declaredStatuses: [401, 400], + empty: false, + }, + requestOptions, + ), + complete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) => + request( + { + method: "POST", + path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`, + query: { location: input["location"] }, + body: { code: input["code"] }, + successStatus: 204, + declaredStatuses: [400, 401], + empty: true, + }, + requestOptions, + ), + cancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) => + request( + { + method: "DELETE", + path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, + query: { location: input["location"] }, + successStatus: 204, + declaredStatuses: [401, 400], + empty: true, + }, + requestOptions, + ), + }, }, "server.mcp": { list: (input?: ServerMcpListInput, requestOptions?: RequestOptions) => @@ -913,18 +919,20 @@ export function make(options: ClientOptions) { }, requestOptions, ), - catalog: (input?: ServerMcpCatalogInput, requestOptions?: RequestOptions) => - request( - { - method: "GET", - path: `/api/mcp/resource`, - query: { location: input?.["location"] }, - successStatus: 200, - declaredStatuses: [401, 400], - empty: false, - }, - requestOptions, - ), + resource: { + catalog: (input?: ServerMcpResourceCatalogInput, requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/mcp/resource`, + query: { location: input?.["location"] }, + successStatus: 200, + declaredStatuses: [401, 400], + empty: false, + }, + requestOptions, + ), + }, }, credential: { update: (input: CredentialUpdateInput, requestOptions?: RequestOptions) => @@ -985,18 +993,20 @@ export function make(options: ClientOptions) { ), }, form: { - listRequests: (input?: FormListRequestsInput, requestOptions?: RequestOptions) => - request( - { - method: "GET", - path: `/api/form/request`, - query: { location: input?.["location"] }, - successStatus: 200, - declaredStatuses: [401, 400], - empty: false, - }, - requestOptions, - ), + request: { + list: (input?: FormRequestListInput, requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/form/request`, + query: { location: input?.["location"] }, + successStatus: 200, + declaredStatuses: [401, 400], + empty: false, + }, + requestOptions, + ), + }, list: (input: FormListInput, requestOptions?: RequestOptions) => request<{ readonly data: FormListOutput }>( { @@ -1074,41 +1084,45 @@ export function make(options: ClientOptions) { ), }, permission: { - listRequests: (input?: PermissionListRequestsInput, requestOptions?: RequestOptions) => - request( - { - method: "GET", - path: `/api/permission/request`, - query: { location: input?.["location"] }, - successStatus: 200, - declaredStatuses: [401, 400], - empty: false, - }, - requestOptions, - ), - listSaved: (input?: PermissionListSavedInput, requestOptions?: RequestOptions) => - request<{ readonly data: PermissionListSavedOutput }>( - { - method: "GET", - path: `/api/permission/saved`, - query: { projectID: input?.["projectID"] }, - successStatus: 200, - declaredStatuses: [401, 400], - empty: false, - }, - requestOptions, - ).then((value) => value.data), - removeSaved: (input: PermissionRemoveSavedInput, requestOptions?: RequestOptions) => - request( - { - method: "DELETE", - path: `/api/permission/saved/${encodeURIComponent(input.id)}`, - successStatus: 204, - declaredStatuses: [401, 400], - empty: true, - }, - requestOptions, - ), + request: { + list: (input?: PermissionRequestListInput, requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/permission/request`, + query: { location: input?.["location"] }, + successStatus: 200, + declaredStatuses: [401, 400], + empty: false, + }, + requestOptions, + ), + }, + saved: { + list: (input?: PermissionSavedListInput, requestOptions?: RequestOptions) => + request<{ readonly data: PermissionSavedListOutput }>( + { + method: "GET", + path: `/api/permission/saved`, + query: { projectID: input?.["projectID"] }, + successStatus: 200, + declaredStatuses: [401, 400], + empty: false, + }, + requestOptions, + ).then((value) => value.data), + remove: (input: PermissionSavedRemoveInput, requestOptions?: RequestOptions) => + request( + { + method: "DELETE", + path: `/api/permission/saved/${encodeURIComponent(input.id)}`, + successStatus: 204, + declaredStatuses: [401, 400], + empty: true, + }, + requestOptions, + ), + }, create: (input: PermissionCreateInput, requestOptions?: RequestOptions) => request<{ readonly data: PermissionCreateOutput }>( { @@ -1390,18 +1404,20 @@ export function make(options: ClientOptions) { ), }, question: { - listRequests: (input?: QuestionListRequestsInput, requestOptions?: RequestOptions) => - request( - { - method: "GET", - path: `/api/question/request`, - query: { location: input?.["location"] }, - successStatus: 200, - declaredStatuses: [401, 400], - empty: false, - }, - requestOptions, - ), + request: { + list: (input?: QuestionRequestListInput, requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/question/request`, + query: { location: input?.["location"] }, + successStatus: 200, + declaredStatuses: [401, 400], + empty: false, + }, + requestOptions, + ), + }, list: (input: QuestionListInput, requestOptions?: RequestOptions) => request<{ readonly data: QuestionListOutput }>( { @@ -1518,29 +1534,31 @@ export function make(options: ClientOptions) { ), }, debug: { - location: (requestOptions?: RequestOptions) => - request( - { - method: "GET", - path: `/api/debug/location`, - successStatus: 200, - declaredStatuses: [401, 400], - empty: false, - }, - requestOptions, - ), - evictLocation: (input?: DebugEvictLocationInput, requestOptions?: RequestOptions) => - request( - { - method: "DELETE", - path: `/api/debug/location`, - query: { location: input?.["location"] }, - successStatus: 204, - declaredStatuses: [401, 400], - empty: true, - }, - requestOptions, - ), + location: { + list: (requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/debug/location`, + successStatus: 200, + declaredStatuses: [401, 400], + empty: false, + }, + requestOptions, + ), + evict: (input?: DebugLocationEvictInput, requestOptions?: RequestOptions) => + request( + { + method: "DELETE", + path: `/api/debug/location`, + query: { location: input?.["location"] }, + successStatus: 204, + declaredStatuses: [401, 400], + empty: true, + }, + requestOptions, + ), + }, }, } } diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 07964f950e..79eae71375 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -2508,13 +2508,13 @@ export type ServerMcpListOutput = { }> } -export type ServerMcpCatalogInput = { +export type ServerMcpResourceCatalogInput = { readonly location?: { readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined }["location"] } -export type ServerMcpCatalogOutput = { +export type ServerMcpResourceCatalogOutput = { readonly location: { readonly directory: string readonly workspaceID?: string @@ -2585,13 +2585,13 @@ export type ProjectDirectoriesInput = { export type ProjectDirectoriesOutput = ReadonlyArray<{ readonly directory: string; readonly strategy?: string }> -export type FormListRequestsInput = { +export type FormRequestListInput = { readonly location?: { readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined }["location"] } -export type FormListRequestsOutput = { +export type FormRequestListOutput = { readonly location: { readonly directory: string readonly workspaceID?: string @@ -3659,13 +3659,13 @@ export type FormCancelInput = { export type FormCancelOutput = void -export type PermissionListRequestsInput = { +export type PermissionRequestListInput = { readonly location?: { readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined }["location"] } -export type PermissionListRequestsOutput = { +export type PermissionRequestListOutput = { readonly location: { readonly directory: string readonly workspaceID?: string @@ -3682,9 +3682,9 @@ export type PermissionListRequestsOutput = { }> } -export type PermissionListSavedInput = { readonly projectID?: { readonly projectID?: string | undefined }["projectID"] } +export type PermissionSavedListInput = { readonly projectID?: { readonly projectID?: string | undefined }["projectID"] } -export type PermissionListSavedOutput = { +export type PermissionSavedListOutput = { readonly data: ReadonlyArray<{ readonly id: string readonly projectID: string @@ -3693,9 +3693,9 @@ export type PermissionListSavedOutput = { }> }["data"] -export type PermissionRemoveSavedInput = { readonly id: { readonly id: string }["id"] } +export type PermissionSavedRemoveInput = { readonly id: { readonly id: string }["id"] } -export type PermissionRemoveSavedOutput = void +export type PermissionSavedRemoveOutput = void export type PermissionCreateInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] @@ -6049,13 +6049,13 @@ export type ShellRemoveInput = { export type ShellRemoveOutput = void -export type QuestionListRequestsInput = { +export type QuestionRequestListInput = { readonly location?: { readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined }["location"] } -export type QuestionListRequestsOutput = { +export type QuestionRequestListOutput = { readonly location: { readonly directory: string readonly workspaceID?: string @@ -6221,12 +6221,12 @@ export type VcsDiffOutput = { }> } -export type DebugLocationOutput = ReadonlyArray<{ readonly directory: string; readonly workspaceID?: string }> +export type DebugLocationListOutput = ReadonlyArray<{ readonly directory: string; readonly workspaceID?: string }> -export type DebugEvictLocationInput = { +export type DebugLocationEvictInput = { readonly location?: { readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined }["location"] } -export type DebugEvictLocationOutput = void +export type DebugLocationEvictOutput = void diff --git a/packages/client/test/contract-identity.test.ts b/packages/client/test/contract-identity.test.ts index a2afc9a002..8e14ad1ee8 100644 --- a/packages/client/test/contract-identity.test.ts +++ b/packages/client/test/contract-identity.test.ts @@ -19,7 +19,7 @@ import { SessionMessage } from "@opencode-ai/schema/session-message" import { Workspace } from "@opencode-ai/schema/workspace" import { Api } from "@opencode-ai/server/api" import { compile, emitPromise } from "@opencode-ai/httpapi-codegen" -import { ClientApi, endpointNames, groupNames, promiseOmitEndpoints } from "../src/contract" +import { ClientApi, groupNames, promiseOmitEndpoints } from "../src/contract" const Client = await import("../src/effect") @@ -49,8 +49,8 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () => }) test("client and Server contracts generate identically", () => { - const server = compile(Api, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints }) - const client = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints }) + const server = compile(Api, { groupNames, omitEndpoints: promiseOmitEndpoints }) + const client = compile(ClientApi, { groupNames, omitEndpoints: promiseOmitEndpoints }) expect(emitPromise(client)).toEqual(emitPromise(server)) }) diff --git a/packages/client/test/promise.test.ts b/packages/client/test/promise.test.ts index 9b26efe773..e67d737942 100644 --- a/packages/client/test/promise.test.ts +++ b/packages/client/test/promise.test.ts @@ -32,17 +32,12 @@ test("exposes every standard HTTP API group", () => { "vcs", "debug", ]) - expect(Object.keys(client.debug)).toEqual(["location", "evictLocation"]) + expect(Object.keys(client.debug)).toEqual(["location"]) + expect(Object.keys(client.debug.location)).toEqual(["list", "evict"]) expect(Object.keys(client.message)).toEqual(["list"]) - expect(Object.keys(client.integration)).toEqual([ - "list", - "get", - "connectKey", - "connectOauth", - "attemptStatus", - "attemptComplete", - "attemptCancel", - ]) + expect(Object.keys(client.integration)).toEqual(["list", "get", "connect", "attempt"]) + expect(Object.keys(client.integration.connect)).toEqual(["key", "oauth"]) + expect(Object.keys(client.integration.attempt)).toEqual(["status", "complete", "cancel"]) expect(Object.keys(client.file)).toEqual(["read", "list", "find"]) expect(Object.keys(client.vcs)).toEqual(["status", "diff"]) expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"]) @@ -66,7 +61,7 @@ test("MCP resource catalog uses the public HTTP contract", async () => { }, }) - const result = await client["server.mcp"].catalog({ location: { directory: "/tmp/project" } }) + const result = await client["server.mcp"].resource.catalog({ location: { directory: "/tmp/project" } }) expect(result.data.resources[0]?.uri).toBe("docs://readme") expect(request?.method).toBe("GET") diff --git a/packages/codemode/test/fixtures/opencode-v2-openapi.json b/packages/codemode/test/fixtures/opencode-v2-openapi.json index b20a25b25e..a37a3c5bd7 100644 --- a/packages/codemode/test/fixtures/opencode-v2-openapi.json +++ b/packages/codemode/test/fixtures/opencode-v2-openapi.json @@ -9,7 +9,7 @@ "/api/health": { "get": { "tags": [ - "server.health" + "health" ], "operationId": "v2.health.get", "parameters": [], @@ -27,10 +27,23 @@ "enum": [ true ] + }, + "version": { + "type": "string" + }, + "pid": { + "type": "integer", + "allOf": [ + { + "exclusiveMinimum": 0 + } + ] } }, "required": [ - "healthy" + "healthy", + "version", + "pid" ], "additionalProperties": false } @@ -65,7 +78,7 @@ "/api/location": { "get": { "tags": [ - "server.location" + "location" ], "operationId": "v2.location.get", "parameters": [ @@ -150,7 +163,7 @@ "/api/agent": { "get": { "tags": [ - "server.agent" + "agent" ], "operationId": "v2.agent.list", "parameters": [ @@ -251,7 +264,7 @@ "/api/plugin": { "get": { "tags": [ - "plugins" + "plugin" ], "operationId": "v2.plugin.list", "parameters": [ @@ -352,7 +365,7 @@ "/api/session": { "get": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.list", "parameters": [ @@ -568,7 +581,7 @@ }, "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.create", "parameters": [], @@ -679,7 +692,7 @@ "/api/session/active": { "get": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.active", "parameters": [], @@ -699,14 +712,10 @@ "$ref": "#/components/schemas/SessionActive" } } - }, - "watermarks": { - "$ref": "#/components/schemas/SessionWatermarks" } }, "required": [ - "data", - "watermarks" + "data" ], "additionalProperties": false } @@ -734,14 +743,14 @@ } } }, - "description": "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive. Watermarks are the durable log positions read alongside the activity snapshot; activity itself is process state, so the pairing is advisory rather than transactional.", + "description": "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.", "summary": "List active sessions" } }, "/api/session/{sessionID}": { "get": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.get", "parameters": [ @@ -820,12 +829,78 @@ }, "description": "Retrieve a session by ID.", "summary": "Get session" + }, + "delete": { + "tags": [ + "session" + ], + "operationId": "v2.session.remove", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundError" + }, + { + "$ref": "#/components/schemas/SessionNotFoundError" + } + ] + } + } + } + } + }, + "description": "Delete a session and its child sessions.", + "summary": "Delete session" } }, "/api/session/{sessionID}/fork": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.fork", "parameters": [ @@ -940,7 +1015,7 @@ "/api/session/{sessionID}/agent": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.switchAgent", "parameters": [ @@ -1027,7 +1102,7 @@ "/api/session/{sessionID}/model": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.switchModel", "parameters": [ @@ -1114,7 +1189,7 @@ "/api/session/{sessionID}/rename": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.rename", "parameters": [ @@ -1198,10 +1273,123 @@ } } }, + "/api/session/{sessionID}/move": { + "post": { + "tags": [ + "session" + ], + "operationId": "v2.session.move", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/InvalidRequestError1" + }, + { + "$ref": "#/components/schemas/InvalidRequestError" + } + ] + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundError" + }, + { + "$ref": "#/components/schemas/SessionNotFoundError" + } + ] + } + } + } + } + }, + "description": "Move a session to another project directory, optionally transferring local changes.", + "summary": "Move session", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "destination": { + "type": "object", + "properties": { + "directory": { + "type": "string" + } + }, + "required": [ + "directory" + ], + "additionalProperties": false + }, + "moveChanges": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "destination" + ], + "additionalProperties": false + } + } + }, + "required": true + } + } + }, "/api/session/{sessionID}/prompt": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.prompt", "parameters": [ @@ -1245,7 +1433,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InvalidRequestError" + "anyOf": [ + { + "$ref": "#/components/schemas/InvalidRequestError1" + }, + { + "$ref": "#/components/schemas/InvalidRequestError" + } + ] } } } @@ -1353,7 +1548,7 @@ "/api/session/{sessionID}/command": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.command", "parameters": [ @@ -1397,7 +1592,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InvalidRequestError" + "anyOf": [ + { + "$ref": "#/components/schemas/InvalidRequestError1" + }, + { + "$ref": "#/components/schemas/InvalidRequestError" + } + ] } } } @@ -1560,7 +1762,7 @@ "/api/session/{sessionID}/skill": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.skill", "parameters": [ @@ -1675,7 +1877,7 @@ "/api/session/{sessionID}/synthetic": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.synthetic", "parameters": [ @@ -1759,6 +1961,16 @@ }, "metadata": { "type": "object" + }, + "resume": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] } }, "required": [ @@ -1772,12 +1984,12 @@ } } }, - "/api/session/{sessionID}/compact": { + "/api/session/{sessionID}/shell": { "post": { "tags": [ - "sessions" + "session" ], - "operationId": "v2.session.compact", + "operationId": "v2.session.shell", "parameters": [ { "name": "sessionID", @@ -1818,6 +2030,124 @@ } } }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundError" + }, + { + "$ref": "#/components/schemas/SessionNotFoundError" + } + ] + } + } + } + } + }, + "description": "Execute one shell command in the session's working directory. Emits a shell.started event before execution and a shell.ended event with the merged output after.", + "summary": "Run shell command", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "anyOf": [ + { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + { + "type": "null" + } + ] + }, + "command": { + "type": "string" + } + }, + "required": [ + "command" + ], + "additionalProperties": false + } + } + }, + "required": true + } + } + }, + "/api/session/{sessionID}/compact": { + "post": { + "tags": [ + "session" + ], + "operationId": "v2.session.compact", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "required": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SessionInput.Compaction" + } + }, + "required": [ + "data" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -1836,44 +2166,52 @@ } }, "409": { - "description": "SessionBusyError", + "description": "ConflictError", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SessionBusyError" - } - } - } - }, - "500": { - "description": "UnknownError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnknownError" - } - } - } - }, - "503": { - "description": "ServiceUnavailableError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ServiceUnavailableError" + "$ref": "#/components/schemas/ConflictError" } } } } }, - "description": "Compact a session conversation.", - "summary": "Compact session" + "description": "Queue a durable session compaction request.", + "summary": "Compact session", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "anyOf": [ + { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + } + }, + "required": true + } } }, "/api/session/{sessionID}/wait": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.wait", "parameters": [ @@ -1951,7 +2289,7 @@ "/api/session/{sessionID}/revert/stage": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.revert.stage", "parameters": [ @@ -2092,7 +2430,7 @@ "/api/session/{sessionID}/revert/clear": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.revert.clear", "parameters": [ @@ -2179,7 +2517,7 @@ "/api/session/{sessionID}/revert/commit": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.revert.commit", "parameters": [ @@ -2256,7 +2594,7 @@ "/api/session/{sessionID}/context": { "get": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.context", "parameters": [ @@ -2353,7 +2691,7 @@ "/api/session/{sessionID}/instructions/entries": { "get": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.instructions.entry.list", "parameters": [ @@ -2440,7 +2778,7 @@ "/api/session/{sessionID}/instructions/entries/{key}": { "put": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.instructions.entry.put", "parameters": [ @@ -2531,7 +2869,7 @@ }, "delete": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.instructions.entry.remove", "parameters": [ @@ -2604,10 +2942,10 @@ "summary": "Remove instruction entry" } }, - "/api/session/{sessionID}/log": { + "/api/experimental/session/{sessionID}/log": { "get": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.log", "parameters": [ @@ -2809,14 +3147,14 @@ } } }, - "description": "Durable, ordered, gap-free read of public session events after an exclusive aggregate sequence. Emits a synced marker once replay reaches the captured watermark, then completes; with follow=true it continues with live events instead. The only event API that promises reliability: attach after a snapshot watermark to compose fetch and stream without a race window.", + "description": "Experimental durable session event log. Reads events after an exclusive aggregate sequence and continues with live events when follow=true.", "summary": "Read the session log" } }, "/api/session/{sessionID}/interrupt": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.interrupt", "parameters": [ @@ -2884,7 +3222,7 @@ "/api/session/{sessionID}/background": { "post": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.background", "parameters": [ @@ -2952,7 +3290,7 @@ "/api/session/{sessionID}/message/{messageID}": { "get": { "tags": [ - "sessions" + "session" ], "operationId": "v2.session.message", "parameters": [ @@ -3052,9 +3390,9 @@ "/api/session/{sessionID}/message": { "get": { "tags": [ - "messages" + "session" ], - "operationId": "v2.session.messages", + "operationId": "v2.message.list", "parameters": [ { "name": "sessionID", @@ -3196,7 +3534,7 @@ "/api/model": { "get": { "tags": [ - "models" + "model" ], "operationId": "v2.model.list", "parameters": [ @@ -3307,7 +3645,7 @@ "/api/model/default": { "get": { "tags": [ - "models" + "model" ], "operationId": "v2.model.default", "parameters": [ @@ -3553,7 +3891,7 @@ "/api/provider": { "get": { "tags": [ - "providers" + "provider" ], "operationId": "v2.provider.list", "parameters": [ @@ -3664,7 +4002,7 @@ "/api/provider/{providerID}": { "get": { "tags": [ - "providers" + "provider" ], "operationId": "v2.provider.get", "parameters": [ @@ -3790,7 +4128,7 @@ "/api/integration": { "get": { "tags": [ - "integrations" + "integration" ], "operationId": "v2.integration.list", "parameters": [ @@ -3891,7 +4229,7 @@ "/api/integration/{integrationID}": { "get": { "tags": [ - "integrations" + "integration" ], "operationId": "v2.integration.get", "parameters": [ @@ -4004,7 +4342,7 @@ "/api/integration/{integrationID}/connect/key": { "post": { "tags": [ - "integrations" + "integration" ], "operationId": "v2.integration.connect.key", "parameters": [ @@ -4126,7 +4464,7 @@ "/api/integration/{integrationID}/connect/oauth": { "post": { "tags": [ - "integrations" + "integration" ], "operationId": "v2.integration.connect.oauth", "parameters": [ @@ -4275,7 +4613,7 @@ "/api/integration/attempt/{attemptID}": { "get": { "tags": [ - "integrations" + "integration" ], "operationId": "v2.integration.attempt.status", "parameters": [ @@ -4379,7 +4717,7 @@ }, "delete": { "tags": [ - "integrations" + "integration" ], "operationId": "v2.integration.attempt.cancel", "parameters": [ @@ -4465,7 +4803,7 @@ "/api/integration/attempt/{attemptID}/complete": { "post": { "tags": [ - "integrations" + "integration" ], "operationId": "v2.integration.attempt.complete", "parameters": [ @@ -4679,10 +5017,108 @@ "summary": "List MCP servers" } }, + "/api/mcp/resource": { + "get": { + "tags": [ + "mcp" + ], + "operationId": "v2.mcp.resource.catalog", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/Location.Info" + }, + "data": { + "$ref": "#/components/schemas/Mcp.ResourceCatalog" + } + }, + "required": [ + "location", + "data" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "Retrieve resources and resource templates from connected MCP servers.", + "summary": "List MCP resources" + } + }, "/api/credential/{credentialID}": { "patch": { "tags": [ - "server.credential" + "credential" ], "operationId": "v2.credential.update", "parameters": [ @@ -4785,7 +5221,7 @@ }, "delete": { "tags": [ - "server.credential" + "credential" ], "operationId": "v2.credential.remove", "parameters": [ @@ -4868,10 +5304,57 @@ "summary": "Remove credential" } }, + "/api/project": { + "get": { + "tags": [ + "project" + ], + "operationId": "v2.project.list", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Project" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "List known projects.", + "summary": "List projects" + } + }, "/api/project/current": { "get": { "tags": [ - "projects" + "project" ], "operationId": "v2.project.current", "parameters": [ @@ -4956,7 +5439,7 @@ "/api/project/{projectID}/directories": { "get": { "tags": [ - "projects" + "project" ], "operationId": "v2.project.directories", "parameters": [ @@ -5049,7 +5532,7 @@ "/api/form/request": { "get": { "tags": [ - "forms" + "form" ], "operationId": "v2.form.request.list", "parameters": [ @@ -5157,7 +5640,7 @@ "/api/session/{sessionID}/form": { "get": { "tags": [ - "forms" + "form" ], "operationId": "v2.session.form.list", "parameters": [ @@ -5244,7 +5727,7 @@ }, "post": { "tags": [ - "forms" + "form" ], "operationId": "v2.session.form.create", "parameters": [ @@ -5357,7 +5840,7 @@ "/api/session/{sessionID}/form/{formID}": { "get": { "tags": [ - "forms" + "form" ], "operationId": "v2.session.form.get", "parameters": [ @@ -5459,7 +5942,7 @@ "/api/session/{sessionID}/form/{formID}/state": { "get": { "tags": [ - "forms" + "form" ], "operationId": "v2.session.form.state", "parameters": [ @@ -5554,7 +6037,7 @@ "/api/session/{sessionID}/form/{formID}/reply": { "post": { "tags": [ - "forms" + "form" ], "operationId": "v2.session.form.reply", "parameters": [ @@ -5660,7 +6143,7 @@ "/api/session/{sessionID}/form/{formID}/cancel": { "post": { "tags": [ - "forms" + "form" ], "operationId": "v2.session.form.cancel", "parameters": [ @@ -5749,7 +6232,7 @@ "/api/permission/request": { "get": { "tags": [ - "permissions" + "permission" ], "operationId": "v2.permission.request.list", "parameters": [ @@ -5850,7 +6333,7 @@ "/api/permission/saved": { "get": { "tags": [ - "permissions" + "permission" ], "operationId": "v2.permission.saved.list", "parameters": [ @@ -5922,7 +6405,7 @@ "/api/permission/saved/{id}": { "delete": { "tags": [ - "permissions" + "permission" ], "operationId": "v2.permission.saved.remove", "parameters": [ @@ -5968,7 +6451,7 @@ "/api/session/{sessionID}/permission": { "post": { "tags": [ - "permissions" + "permission" ], "operationId": "v2.session.permission.create", "parameters": [ @@ -6131,7 +6614,7 @@ }, "get": { "tags": [ - "permissions" + "permission" ], "operationId": "v2.session.permission.list", "parameters": [ @@ -6218,7 +6701,7 @@ "/api/session/{sessionID}/permission/{requestID}": { "get": { "tags": [ - "permissions" + "permission" ], "operationId": "v2.session.permission.get", "parameters": [ @@ -6318,7 +6801,7 @@ "/api/session/{sessionID}/permission/{requestID}/reply": { "post": { "tags": [ - "permissions" + "permission" ], "operationId": "v2.session.permission.reply", "parameters": [ @@ -6769,7 +7252,7 @@ "/api/command": { "get": { "tags": [ - "commands" + "command" ], "operationId": "v2.command.list", "parameters": [ @@ -6870,7 +7353,7 @@ "/api/skill": { "get": { "tags": [ - "skills" + "skill" ], "operationId": "v2.skill.list", "parameters": [ @@ -6971,7 +7454,7 @@ "/api/event": { "get": { "tags": [ - "events" + "event" ], "operationId": "v2.event.subscribe", "parameters": [], @@ -7108,154 +7591,10 @@ } } }, - "description": "Subscribe to native event payloads for the server. Volatile by contract: a slow consumer overflows and fails the stream, and events during disconnection are missed. Consumers that need reliability should combine the changes feed with durable session log reads.", + "description": "Subscribe to native event payloads for the server. Volatile by contract: a slow consumer overflows and fails the stream, and events during disconnection are missed.", "summary": "Subscribe to events" } }, - "/api/event/changes": { - "get": { - "tags": [ - "events" - ], - "operationId": "v2.event.changes", - "parameters": [], - "security": [], - "responses": { - "200": { - "description": "Success", - "content": { - "text/event-stream": { - "schema": { - "type": "object", - "properties": { - "id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "event": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/EventLog.ChangeStream" - } - }, - "required": [ - "id", - "event", - "data" - ], - "additionalProperties": false - }, - "x-effect-stream": { - "encoding": "sse", - "causeSchema": { - "type": "array", - "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "_tag": { - "type": "string", - "enum": [ - "Fail" - ] - }, - "error": { - "not": {} - } - }, - "required": [ - "_tag", - "error" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "_tag": { - "type": "string", - "enum": [ - "Die" - ] - }, - "defect": {} - }, - "required": [ - "_tag", - "defect" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "_tag": { - "type": "string", - "enum": [ - "Interrupt" - ] - }, - "fiberId": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "_tag", - "fiberId" - ], - "additionalProperties": false - } - ] - } - }, - "errorSchema": { - "not": {} - }, - "failureEvent": "effect/httpapi/stream/failure" - } - } - } - }, - "400": { - "description": "InvalidRequestError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvalidRequestError" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnauthorizedError" - } - } - } - } - }, - "description": "Payload-free hint channel: after an event commits, a subscriber eventually receives a hint for that aggregate with seq at or beyond the event, or a sweep-required marker. Hints coalesce to the latest seq per aggregate under backpressure and the stream never fails from overflow. No consumer may derive correctness from receiving a hint; correctness always comes from durable log reads plus the consumer's own checkpoint. A sweep-required marker is emitted first on every (re)subscribe and whenever hint retention is exceeded: treat every aggregate as potentially dirty and recover via bounded sweep plus log reads.", - "summary": "Subscribe to change hints" - } - }, "/api/pty": { "get": { "tags": [ @@ -7873,7 +8212,7 @@ "tags": [ "pty" ], - "operationId": "v2.pty.connectToken", + "operationId": "v2.pty.connect.token", "parameters": [ { "name": "ptyID", @@ -8005,7 +8344,6 @@ "pty" ], "operationId": "v2.pty.connect", - "x-websocket": true, "parameters": [ { "name": "ptyID", @@ -8103,7 +8441,8 @@ } }, "description": "Establish a WebSocket connection streaming PTY output and accepting terminal input.", - "summary": "Connect to PTY session" + "summary": "Connect to PTY session", + "x-websocket": true } }, "/api/shell": { @@ -8169,7 +8508,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/Shell1" + "$ref": "#/components/schemas/Shell" } } }, @@ -8266,7 +8605,7 @@ "$ref": "#/components/schemas/Location.Info" }, "data": { - "$ref": "#/components/schemas/Shell1" + "$ref": "#/components/schemas/Shell" } }, "required": [ @@ -8326,7 +8665,8 @@ } }, "required": [ - "command" + "command", + "timeout" ], "additionalProperties": false } @@ -8410,7 +8750,7 @@ "$ref": "#/components/schemas/Location.Info" }, "data": { - "$ref": "#/components/schemas/Shell1" + "$ref": "#/components/schemas/Shell" } }, "required": [ @@ -8556,6 +8896,151 @@ "summary": "Remove shell command" } }, + "/api/shell/{id}/timeout": { + "patch": { + "tags": [ + "shell" + ], + "operationId": "v2.shell.timeout", + "parameters": [ + { + "name": "id", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^sh_" + } + ] + }, + "required": true + }, + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/Location.Info" + }, + "data": { + "$ref": "#/components/schemas/Shell" + } + }, + "required": [ + "location", + "data" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "ShellNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShellNotFoundError" + } + } + } + } + }, + "description": "Replace a running shell command's timeout from now, or clear it with zero.", + "summary": "Update shell timeout", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "timeout": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + } + }, + "required": [ + "timeout" + ], + "additionalProperties": false + } + } + }, + "required": true + } + } + }, "/api/shell/{id}/output": { "get": { "tags": [ @@ -8737,7 +9222,7 @@ "/api/question/request": { "get": { "tags": [ - "session questions" + "question" ], "operationId": "v2.question.request.list", "parameters": [ @@ -8838,7 +9323,7 @@ "/api/session/{sessionID}/question": { "get": { "tags": [ - "session questions" + "question" ], "operationId": "v2.session.question.list", "parameters": [ @@ -8925,7 +9410,7 @@ "/api/session/{sessionID}/question/{requestID}/reply": { "post": { "tags": [ - "session questions" + "question" ], "operationId": "v2.session.question.reply", "parameters": [ @@ -9019,7 +9504,7 @@ "/api/session/{sessionID}/question/{requestID}/reject": { "post": { "tags": [ - "session questions" + "question" ], "operationId": "v2.session.question.reject", "parameters": [ @@ -9752,6 +10237,129 @@ "description": "Diff the working copy against HEAD (mode git) or the default-branch merge base (mode branch) for the requested location.", "summary": "VCS diff" } + }, + "/api/debug/location": { + "get": { + "tags": [ + "debug" + ], + "operationId": "v2.debug.location.list", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Location.Ref" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "List locations currently loaded by the server.", + "summary": "List loaded locations" + }, + "delete": { + "tags": [ + "debug" + ], + "operationId": "v2.debug.location.evict", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "Dispose the requested location's cached services so its next use boots them fresh.", + "summary": "Evict a loaded location" + } } }, "components": { @@ -10133,6 +10741,31 @@ } ] }, + "fork": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "messageID": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + }, "projectID": { "type": "string" }, @@ -10225,20 +10858,6 @@ ], "additionalProperties": false }, - "SessionWatermarks": { - "type": "object", - "patternProperties": { - "^ses": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - } - }, - "description": "Durable log seq each session's snapshot was computed at. Attach a live log read after the watermark to compose fetch and stream gap-free; apply a snapshot only where its watermark is at or beyond already-applied events. Sessions without durable events are absent." - }, "SessionsResponse": { "type": "object", "properties": { @@ -10248,9 +10867,6 @@ "$ref": "#/components/schemas/SessionV2.Info" } }, - "watermarks": { - "$ref": "#/components/schemas/SessionWatermarks" - }, "cursor": { "type": "object", "properties": { @@ -10280,7 +10896,6 @@ }, "required": [ "data", - "watermarks", "cursor" ], "additionalProperties": false @@ -10408,7 +11023,7 @@ ], "additionalProperties": false }, - "Prompt.Source": { + "Prompt.Mention": { "type": "object", "properties": { "start": { @@ -10440,8 +11055,8 @@ "description": { "type": "string" }, - "source": { - "$ref": "#/components/schemas/Prompt.Source" + "mention": { + "$ref": "#/components/schemas/Prompt.Mention" } }, "required": [ @@ -10455,8 +11070,8 @@ "name": { "type": "string" }, - "source": { - "$ref": "#/components/schemas/Prompt.Source" + "mention": { + "$ref": "#/components/schemas/Prompt.Mention" } }, "required": [ @@ -10488,28 +11103,78 @@ ], "additionalProperties": false }, + "Prompt.Base64": { + "type": "string", + "allOf": [ + { + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" + } + ] + }, + "Prompt.FileSource": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "uri" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "type", + "uri" + ], + "additionalProperties": false + } + ] + }, "Prompt.FileAttachment": { "type": "object", "properties": { - "uri": { - "type": "string" + "data": { + "$ref": "#/components/schemas/Prompt.Base64" }, "mime": { "type": "string" }, + "source": { + "$ref": "#/components/schemas/Prompt.FileSource" + }, "name": { "type": "string" }, "description": { "type": "string" }, - "source": { - "$ref": "#/components/schemas/Prompt.Source" + "mention": { + "$ref": "#/components/schemas/Prompt.Mention" } }, "required": [ - "uri", - "mime" + "data", + "mime", + "source" ], "additionalProperties": false }, @@ -10694,26 +11359,57 @@ ], "additionalProperties": false }, - "SessionBusyError": { + "SessionInput.Compaction": { "type": "object", "properties": { - "_tag": { + "type": { "type": "string", "enum": [ - "SessionBusyError" + "compaction" + ] + }, + "admittedSeq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } ] }, "sessionID": { - "type": "string" + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] }, - "message": { - "type": "string" + "timeCreated": { + "type": "number" + }, + "handledSeq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] } }, "required": [ - "_tag", + "type", + "admittedSeq", + "id", "sessionID", - "message" + "timeCreated" ], "additionalProperties": false }, @@ -10746,6 +11442,29 @@ ], "additionalProperties": false }, + "SessionBusyError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": [ + "SessionBusyError" + ] + }, + "sessionID": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "_tag", + "sessionID", + "message" + ], + "additionalProperties": false + }, "UnknownError": { "type": "object", "properties": { @@ -10775,7 +11494,7 @@ ], "additionalProperties": false }, - "Session.Message.AgentSwitched": { + "Session.Message.AgentSelected": { "type": "object", "properties": { "id": { @@ -10819,7 +11538,7 @@ ], "additionalProperties": false }, - "Session.Message.ModelSwitched": { + "Session.Message.ModelSelected": { "type": "object", "properties": { "id": { @@ -10853,6 +11572,9 @@ }, "model": { "$ref": "#/components/schemas/Model.Ref" + }, + "previous": { + "$ref": "#/components/schemas/Model.Ref" } }, "required": [ @@ -11067,6 +11789,182 @@ ], "additionalProperties": false }, + "Shell": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^sh_" + } + ] + }, + "status": { + "type": "string", + "enum": [ + "running", + "exited", + "timeout", + "killed" + ] + }, + "command": { + "type": "string" + }, + "cwd": { + "type": "string" + }, + "shell": { + "type": "string" + }, + "file": { + "type": "string" + }, + "pid": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "exit": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN" + ] + }, + { + "type": "string", + "enum": [ + "Infinity" + ] + }, + { + "type": "string", + "enum": [ + "-Infinity" + ] + } + ] + }, + { + "type": "string", + "enum": [ + "Infinity", + "-Infinity", + "NaN" + ] + } + ] + }, + "metadata": { + "type": "object" + }, + "time": { + "type": "object", + "properties": { + "started": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN" + ] + }, + { + "type": "string", + "enum": [ + "Infinity" + ] + }, + { + "type": "string", + "enum": [ + "-Infinity" + ] + } + ] + }, + { + "type": "string", + "enum": [ + "Infinity", + "-Infinity", + "NaN" + ] + } + ] + }, + "completed": { + "anyOf": [ + { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN" + ] + }, + { + "type": "string", + "enum": [ + "Infinity" + ] + }, + { + "type": "string", + "enum": [ + "-Infinity" + ] + } + ] + }, + { + "type": "string", + "enum": [ + "Infinity", + "-Infinity", + "NaN" + ] + } + ] + } + }, + "required": [ + "started" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "status", + "command", + "cwd", + "shell", + "file", + "metadata", + "time" + ], + "additionalProperties": false + }, "Session.Message.Shell": { "type": "object", "properties": { @@ -11102,23 +12000,49 @@ "shell" ] }, - "callID": { - "type": "string" - }, - "command": { - "type": "string" + "shell": { + "$ref": "#/components/schemas/Shell" }, "output": { - "type": "string" + "type": "object", + "properties": { + "output": { + "type": "string" + }, + "cursor": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "size": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "output", + "cursor", + "size", + "truncated" + ], + "additionalProperties": false } }, "required": [ "id", "time", "type", - "callID", - "command", - "output" + "shell" ], "additionalProperties": false }, @@ -11131,25 +12055,18 @@ "text" ] }, - "id": { - "type": "string" - }, "text": { "type": "string" } }, "required": [ "type", - "id", "text" ], "additionalProperties": false }, - "LLM.ProviderMetadata": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState": { + "type": "object" }, "Session.Message.Assistant.Reasoning": { "type": "object", @@ -11160,14 +12077,11 @@ "reasoning" ] }, - "id": { - "type": "string" - }, "text": { "type": "string" }, - "providerMetadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata" + "state": { + "$ref": "#/components/schemas/Session.Message.ProviderState" }, "time": { "type": "object", @@ -11187,7 +12101,6 @@ }, "required": [ "type", - "id", "text" ], "additionalProperties": false @@ -11339,14 +12252,11 @@ ], "additionalProperties": false }, - "Session.Error.Unknown": { + "Session.StructuredError": { "type": "object", "properties": { "type": { - "type": "string", - "enum": [ - "unknown" - ] + "type": "string" }, "message": { "type": "string" @@ -11380,7 +12290,7 @@ "type": "object" }, "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "$ref": "#/components/schemas/Session.StructuredError" }, "result": {} }, @@ -11408,23 +12318,14 @@ "name": { "type": "string" }, - "provider": { - "type": "object", - "properties": { - "executed": { - "type": "boolean" - }, - "metadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata" - }, - "resultMetadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata" - } - }, - "required": [ - "executed" - ], - "additionalProperties": false + "executed": { + "type": "boolean" + }, + "providerState": { + "$ref": "#/components/schemas/Session.Message.ProviderState" + }, + "providerResultState": { + "$ref": "#/components/schemas/Session.Message.ProviderState" }, "state": { "anyOf": [ @@ -11473,6 +12374,31 @@ ], "additionalProperties": false }, + "Session.Message.Assistant.Retry": { + "type": "object", + "properties": { + "attempt": { + "type": "integer", + "allOf": [ + { + "exclusiveMinimum": 0 + } + ] + }, + "at": { + "type": "number" + }, + "error": { + "$ref": "#/components/schemas/Session.StructuredError" + } + }, + "required": [ + "attempt", + "at", + "error" + ], + "additionalProperties": false + }, "Session.Message.Assistant": { "type": "object", "properties": { @@ -11549,7 +12475,15 @@ "additionalProperties": false }, "finish": { - "type": "string" + "type": "string", + "enum": [ + "stop", + "length", + "tool-calls", + "content-filter", + "error", + "unknown" + ] }, "cost": { "type": "number" @@ -11592,7 +12526,10 @@ "additionalProperties": false }, "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "$ref": "#/components/schemas/Session.StructuredError" + }, + "retry": { + "$ref": "#/components/schemas/Session.Message.Assistant.Retry" } }, "required": [ @@ -11614,6 +12551,15 @@ "compaction" ] }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "completed", + "failed" + ] + }, "reason": { "type": "string", "enum": [ @@ -11653,6 +12599,7 @@ }, "required": [ "type", + "status", "reason", "summary", "recent", @@ -11664,10 +12611,10 @@ "Session.Message": { "anyOf": [ { - "$ref": "#/components/schemas/Session.Message.AgentSwitched" + "$ref": "#/components/schemas/Session.Message.AgentSelected" }, { - "$ref": "#/components/schemas/Session.Message.ModelSwitched" + "$ref": "#/components/schemas/Session.Message.ModelSelected" }, { "$ref": "#/components/schemas/Session.Message.User" @@ -11697,7 +12644,7 @@ "allOf": [ { "pattern": "^[a-z0-9][a-z0-9._-]*$", - "description": "Context entry key (lowercase alphanumerics plus . _ -)" + "description": "Instruction entry key (lowercase alphanumerics plus . _ -)" } ] }, @@ -11715,7 +12662,7 @@ ], "additionalProperties": false }, - "session.next.agent.switched": { + "session.agent.selected": { "type": "object", "properties": { "id": { @@ -11726,13 +12673,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.agent.switched" + "session.agent.selected" ] }, "durable": { @@ -11771,9 +12721,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -11782,22 +12729,12 @@ } ] }, - "messageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, "agent": { "type": "string" } }, "required": [ - "timestamp", "sessionID", - "messageID", "agent" ], "additionalProperties": false @@ -11805,12 +12742,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.model.switched": { + "session.model.selected": { "type": "object", "properties": { "id": { @@ -11821,13 +12760,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.model.switched" + "session.model.selected" ] }, "durable": { @@ -11866,9 +12808,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -11877,22 +12816,12 @@ } ] }, - "messageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, "model": { "$ref": "#/components/schemas/Model.Ref" } }, "required": [ - "timestamp", "sessionID", - "messageID", "model" ], "additionalProperties": false @@ -11900,12 +12829,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.moved": { + "session.moved": { "type": "object", "properties": { "id": { @@ -11916,13 +12847,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.moved" + "session.moved" ] }, "durable": { @@ -11961,9 +12895,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -11975,12 +12906,11 @@ "location": { "$ref": "#/components/schemas/Location.Ref" }, - "subdirectory": { + "subpath": { "type": "string" } }, "required": [ - "timestamp", "sessionID", "location" ], @@ -11989,12 +12919,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.renamed": { + "session.renamed": { "type": "object", "properties": { "id": { @@ -12005,13 +12937,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.renamed" + "session.renamed" ] }, "durable": { @@ -12050,9 +12985,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -12066,7 +12998,6 @@ } }, "required": [ - "timestamp", "sessionID", "title" ], @@ -12075,12 +13006,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.forked": { + "session.deleted": { "type": "object", "properties": { "id": { @@ -12091,13 +13024,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.forked" + "session.deleted" ] }, "durable": { @@ -12136,9 +13072,89 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.forked": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.forked" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { "sessionID": { "type": "string", "allOf": [ @@ -12155,7 +13171,7 @@ } ] }, - "messageID": { + "from": { "type": "string", "allOf": [ { @@ -12165,7 +13181,6 @@ } }, "required": [ - "timestamp", "sessionID", "parentID" ], @@ -12174,12 +13189,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.prompted": { + "session.prompt.promoted": { "type": "object", "properties": { "id": { @@ -12190,13 +13207,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.prompted" + "session.prompt.promoted" ] }, "durable": { @@ -12235,9 +13255,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -12246,7 +13263,99 @@ } ] }, - "messageID": { + "inputID": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + } + }, + "required": [ + "sessionID", + "inputID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.prompt.admitted": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.prompt.admitted" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "inputID": { "type": "string", "allOf": [ { @@ -12266,9 +13375,8 @@ } }, "required": [ - "timestamp", "sessionID", - "messageID", + "inputID", "prompt", "delivery" ], @@ -12277,12 +13385,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.prompt.admitted": { + "session.execution.started": { "type": "object", "properties": { "id": { @@ -12293,13 +13403,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.prompt.admitted" + "session.execution.started" ] }, "durable": { @@ -12338,9 +13451,172 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.execution.succeeded": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.execution.succeeded" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.execution.failed": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.execution.failed" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { "sessionID": { "type": "string", "allOf": [ @@ -12349,43 +13625,119 @@ } ] }, - "messageID": { - "type": "string", + "error": { + "$ref": "#/components/schemas/Session.StructuredError" + } + }, + "required": [ + "sessionID", + "error" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.execution.interrupted": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.execution.interrupted" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", "allOf": [ { - "pattern": "^msg_" + "minimum": 0 } ] }, - "prompt": { - "$ref": "#/components/schemas/Prompt" + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] }, - "delivery": { + "reason": { "type": "string", "enum": [ - "steer", - "queue" + "user", + "shutdown", + "superseded" ] } }, "required": [ - "timestamp", "sessionID", - "messageID", - "prompt", - "delivery" + "reason" ], "additionalProperties": false } }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.instructions.updated": { + "session.instructions.updated": { "type": "object", "properties": { "id": { @@ -12396,13 +13748,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.instructions.updated" + "session.instructions.updated" ] }, "durable": { @@ -12441,9 +13796,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -12452,22 +13804,12 @@ } ] }, - "messageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, "text": { "type": "string" } }, "required": [ - "timestamp", "sessionID", - "messageID", "text" ], "additionalProperties": false @@ -12475,12 +13817,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.synthetic": { + "session.synthetic": { "type": "object", "properties": { "id": { @@ -12491,13 +13835,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.synthetic" + "session.synthetic" ] }, "durable": { @@ -12536,9 +13883,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -12547,14 +13891,6 @@ } ] }, - "messageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, "text": { "type": "string" }, @@ -12566,9 +13902,7 @@ } }, "required": [ - "timestamp", "sessionID", - "messageID", "text" ], "additionalProperties": false @@ -12576,12 +13910,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.skill.activated": { + "session.skill.activated": { "type": "object", "properties": { "id": { @@ -12592,13 +13928,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.skill.activated" + "session.skill.activated" ] }, "durable": { @@ -12637,9 +13976,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -12648,14 +13984,6 @@ } ] }, - "messageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, "name": { "type": "string" }, @@ -12664,9 +13992,7 @@ } }, "required": [ - "timestamp", "sessionID", - "messageID", "name", "text" ], @@ -12675,111 +14001,154 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.shell.started": { + "Shell1": { "type": "object", "properties": { "id": { "type": "string", "allOf": [ { - "pattern": "^evt_" + "pattern": "^sh_" + } + ] + }, + "status": { + "type": "string", + "enum": [ + "running", + "exited", + "timeout", + "killed" + ] + }, + "command": { + "type": "string" + }, + "cwd": { + "type": "string" + }, + "shell": { + "type": "string" + }, + "file": { + "type": "string" + }, + "pid": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "exit": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN" + ] + }, + { + "type": "string", + "enum": [ + "Infinity" + ] + }, + { + "type": "string", + "enum": [ + "-Infinity" + ] } ] }, "metadata": { "type": "object" }, - "type": { - "type": "string", - "enum": [ - "session.next.shell.started" - ] - }, - "durable": { + "time": { "type": "object", "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ + "started": { + "anyOf": [ { - "minimum": 0 + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN" + ] + }, + { + "type": "string", + "enum": [ + "Infinity" + ] + }, + { + "type": "string", + "enum": [ + "-Infinity" + ] } ] }, - "version": { - "type": "integer", - "allOf": [ + "completed": { + "anyOf": [ { - "minimum": 1 + "type": "number" + }, + { + "type": "string", + "enum": [ + "NaN" + ] + }, + { + "type": "string", + "enum": [ + "Infinity" + ] + }, + { + "type": "string", + "enum": [ + "-Infinity" + ] } ] } }, "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, - "location": { - "$ref": "#/components/schemas/Location.Ref" - }, - "data": { - "type": "object", - "properties": { - "timestamp": { - "type": "number" - }, - "sessionID": { - "type": "string", - "allOf": [ - { - "pattern": "^ses" - } - ] - }, - "messageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, - "callID": { - "type": "string" - }, - "command": { - "type": "string" - } - }, - "required": [ - "timestamp", - "sessionID", - "messageID", - "callID", - "command" + "started" ], "additionalProperties": false } }, "required": [ "id", - "type", - "data" + "status", + "command", + "cwd", + "shell", + "file", + "metadata", + "time" ], "additionalProperties": false }, - "session.next.shell.ended": { + "session.shell.started": { "type": "object", "properties": { "id": { @@ -12790,13 +14159,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.shell.ended" + "session.shell.started" ] }, "durable": { @@ -12835,9 +14207,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -12846,17 +14215,134 @@ } ] }, - "callID": { - "type": "string" - }, - "output": { - "type": "string" + "shell": { + "$ref": "#/components/schemas/Shell1" } }, "required": [ - "timestamp", "sessionID", - "callID", + "shell" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.shell.ended": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.shell.ended" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "shell": { + "$ref": "#/components/schemas/Shell1" + }, + "output": { + "type": "object", + "properties": { + "output": { + "type": "string" + }, + "cursor": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "size": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "output", + "cursor", + "size", + "truncated" + ], + "additionalProperties": false + } + }, + "required": [ + "sessionID", + "shell", "output" ], "additionalProperties": false @@ -12864,12 +14350,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.step.started": { + "session.step.started": { "type": "object", "properties": { "id": { @@ -12880,13 +14368,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.step.started" + "session.step.started" ] }, "durable": { @@ -12925,9 +14416,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -12955,7 +14443,6 @@ } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "agent", @@ -12966,12 +14453,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.step.ended": { + "session.step.ended": { "type": "object", "properties": { "id": { @@ -12982,13 +14471,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.step.ended" + "session.step.ended" ] }, "durable": { @@ -13027,9 +14519,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -13047,7 +14536,15 @@ ] }, "finish": { - "type": "string" + "type": "string", + "enum": [ + "stop", + "length", + "tool-calls", + "content-filter", + "error", + "unknown" + ] }, "cost": { "type": "number" @@ -13100,7 +14597,6 @@ } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "finish", @@ -13112,12 +14608,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.step.failed": { + "session.step.failed": { "type": "object", "properties": { "id": { @@ -13128,13 +14626,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.step.failed" + "session.step.failed" ] }, "durable": { @@ -13173,9 +14674,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -13193,11 +14691,50 @@ ] }, "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "$ref": "#/components/schemas/Session.StructuredError" + }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": [ + "read", + "write" + ], + "additionalProperties": false + } + }, + "required": [ + "input", + "output", + "reasoning", + "cache" + ], + "additionalProperties": false } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "error" @@ -13207,12 +14744,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.text.started": { + "session.text.started": { "type": "object", "properties": { "id": { @@ -13223,13 +14762,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.text.started" + "session.text.started" ] }, "durable": { @@ -13268,9 +14810,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -13287,27 +14826,33 @@ } ] }, - "textID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", - "textID" + "ordinal" ], "additionalProperties": false } }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.text.ended": { + "session.text.ended": { "type": "object", "properties": { "id": { @@ -13318,13 +14863,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.text.ended" + "session.text.ended" ] }, "durable": { @@ -13363,9 +14911,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -13382,18 +14927,22 @@ } ] }, - "textID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "text": { "type": "string" } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", - "textID", + "ordinal", "text" ], "additionalProperties": false @@ -13401,12 +14950,17 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.tool.input.started": { + "Session.Message.ProviderState3": { + "type": "object" + }, + "session.reasoning.started": { "type": "object", "properties": { "id": { @@ -13417,13 +14971,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.tool.input.started" + "session.reasoning.started" ] }, "durable": { @@ -13462,9 +15019,221 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] }, + "assistantMessageID": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + }, + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "state": { + "$ref": "#/components/schemas/Session.Message.ProviderState3" + } + }, + "required": [ + "sessionID", + "assistantMessageID", + "ordinal" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "Session.Message.ProviderState4": { + "type": "object" + }, + "session.reasoning.ended": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.reasoning.ended" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "assistantMessageID": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + }, + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "text": { + "type": "string" + }, + "state": { + "$ref": "#/components/schemas/Session.Message.ProviderState4" + } + }, + "required": [ + "sessionID", + "assistantMessageID", + "ordinal", + "text" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.tool.input.started": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.tool.input.started" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { "sessionID": { "type": "string", "allOf": [ @@ -13489,7 +15258,6 @@ } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "callID", @@ -13500,12 +15268,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.tool.input.ended": { + "session.tool.input.ended": { "type": "object", "properties": { "id": { @@ -13516,13 +15286,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.tool.input.ended" + "session.tool.input.ended" ] }, "durable": { @@ -13561,9 +15334,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -13588,7 +15358,6 @@ } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "callID", @@ -13599,18 +15368,17 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "LLM.ProviderMetadata3": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState5": { + "type": "object" }, - "session.next.tool.called": { + "session.tool.called": { "type": "object", "properties": { "id": { @@ -13621,13 +15389,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.tool.called" + "session.tool.called" ] }, "durable": { @@ -13666,9 +15437,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -13688,48 +15456,36 @@ "callID": { "type": "string" }, - "tool": { - "type": "string" - }, "input": { "type": "object" }, - "provider": { - "type": "object", - "properties": { - "executed": { - "type": "boolean" - }, - "metadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata3" - } - }, - "required": [ - "executed" - ], - "additionalProperties": false + "executed": { + "type": "boolean" + }, + "state": { + "$ref": "#/components/schemas/Session.Message.ProviderState5" } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "callID", - "tool", "input", - "provider" + "executed" ], "additionalProperties": false } }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.tool.progress": { + "session.tool.progress": { "type": "object", "properties": { "id": { @@ -13740,13 +15496,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.tool.progress" + "session.tool.progress" ] }, "durable": { @@ -13785,9 +15544,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -13818,7 +15574,6 @@ } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "callID", @@ -13830,18 +15585,17 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "LLM.ProviderMetadata4": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState6": { + "type": "object" }, - "session.next.tool.success": { + "session.tool.success": { "type": "object", "properties": { "id": { @@ -13852,13 +15606,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.tool.success" + "session.tool.success" ] }, "durable": { @@ -13897,9 +15654,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -13935,48 +15689,37 @@ } }, "result": {}, - "provider": { - "type": "object", - "properties": { - "executed": { - "type": "boolean" - }, - "metadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata4" - } - }, - "required": [ - "executed" - ], - "additionalProperties": false + "executed": { + "type": "boolean" + }, + "resultState": { + "$ref": "#/components/schemas/Session.Message.ProviderState6" } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "callID", "structured", "content", - "provider" + "executed" ], "additionalProperties": false } }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "LLM.ProviderMetadata5": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState7": { + "type": "object" }, - "session.next.tool.failed": { + "session.tool.failed": { "type": "object", "properties": { "id": { @@ -13987,13 +15730,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.tool.failed" + "session.tool.failed" ] }, "durable": { @@ -14032,9 +15778,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -14055,50 +15798,36 @@ "type": "string" }, "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "$ref": "#/components/schemas/Session.StructuredError" }, "result": {}, - "provider": { - "type": "object", - "properties": { - "executed": { - "type": "boolean" - }, - "metadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata5" - } - }, - "required": [ - "executed" - ], - "additionalProperties": false + "executed": { + "type": "boolean" + }, + "resultState": { + "$ref": "#/components/schemas/Session.Message.ProviderState7" } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "callID", "error", - "provider" + "executed" ], "additionalProperties": false } }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "LLM.ProviderMetadata6": { - "type": "object", - "additionalProperties": { - "type": "object" - } - }, - "session.next.reasoning.started": { + "session.retry.scheduled": { "type": "object", "properties": { "id": { @@ -14109,253 +15838,16 @@ } ] }, - "metadata": { - "type": "object" - }, - "type": { - "type": "string", - "enum": [ - "session.next.reasoning.started" - ] - }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, - "location": { - "$ref": "#/components/schemas/Location.Ref" - }, - "data": { - "type": "object", - "properties": { - "timestamp": { - "type": "number" - }, - "sessionID": { - "type": "string", - "allOf": [ - { - "pattern": "^ses" - } - ] - }, - "assistantMessageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, - "reasoningID": { - "type": "string" - }, - "providerMetadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata6" - } - }, - "required": [ - "timestamp", - "sessionID", - "assistantMessageID", - "reasoningID" - ], - "additionalProperties": false - } - }, - "required": [ - "id", - "type", - "data" - ], - "additionalProperties": false - }, - "LLM.ProviderMetadata7": { - "type": "object", - "additionalProperties": { - "type": "object" - } - }, - "session.next.reasoning.ended": { - "type": "object", - "properties": { - "id": { - "type": "string", - "allOf": [ - { - "pattern": "^evt_" - } - ] - }, - "metadata": { - "type": "object" - }, - "type": { - "type": "string", - "enum": [ - "session.next.reasoning.ended" - ] - }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, - "location": { - "$ref": "#/components/schemas/Location.Ref" - }, - "data": { - "type": "object", - "properties": { - "timestamp": { - "type": "number" - }, - "sessionID": { - "type": "string", - "allOf": [ - { - "pattern": "^ses" - } - ] - }, - "assistantMessageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, - "reasoningID": { - "type": "string" - }, - "text": { - "type": "string" - }, - "providerMetadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata7" - } - }, - "required": [ - "timestamp", - "sessionID", - "assistantMessageID", - "reasoningID", - "text" - ], - "additionalProperties": false - } - }, - "required": [ - "id", - "type", - "data" - ], - "additionalProperties": false - }, - "session.next.retry_error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { + "created": { "type": "number" }, - "isRetryable": { - "type": "boolean" - }, - "responseHeaders": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "responseBody": { - "type": "string" - }, - "metadata": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "required": [ - "message", - "isRetryable" - ], - "additionalProperties": false - }, - "session.next.retried": { - "type": "object", - "properties": { - "id": { - "type": "string", - "allOf": [ - { - "pattern": "^evt_" - } - ] - }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.retried" + "session.retry.scheduled" ] }, "durable": { @@ -14394,9 +15886,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -14405,17 +15894,39 @@ } ] }, + "assistantMessageID": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + }, "attempt": { - "type": "number" + "type": "integer", + "allOf": [ + { + "exclusiveMinimum": 0 + } + ] + }, + "at": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "error": { - "$ref": "#/components/schemas/session.next.retry_error" + "$ref": "#/components/schemas/Session.StructuredError" } }, "required": [ - "timestamp", "sessionID", + "assistantMessageID", "attempt", + "at", "error" ], "additionalProperties": false @@ -14423,12 +15934,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.compaction.started": { + "session.compaction.admitted": { "type": "object", "properties": { "id": { @@ -14439,13 +15952,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.compaction.started" + "session.compaction.admitted" ] }, "durable": { @@ -14484,9 +16000,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -14495,13 +16008,97 @@ } ] }, - "messageID": { + "inputID": { "type": "string", "allOf": [ { "pattern": "^msg_" } ] + } + }, + "required": [ + "sessionID", + "inputID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.compaction.started": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.compaction.started" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] }, "reason": { "type": "string", @@ -14512,9 +16109,7 @@ } }, "required": [ - "timestamp", "sessionID", - "messageID", "reason" ], "additionalProperties": false @@ -14522,12 +16117,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.compaction.ended": { + "session.compaction.ended": { "type": "object", "properties": { "id": { @@ -14538,13 +16135,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.compaction.ended" + "session.compaction.ended" ] }, "durable": { @@ -14583,9 +16183,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -14594,14 +16191,6 @@ } ] }, - "messageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, "reason": { "type": "string", "enum": [ @@ -14617,9 +16206,7 @@ } }, "required": [ - "timestamp", "sessionID", - "messageID", "reason", "text", "recent" @@ -14629,12 +16216,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.revert.staged": { + "session.compaction.failed": { "type": "object", "properties": { "id": { @@ -14645,13 +16234,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.revert.staged" + "session.compaction.failed" ] }, "durable": { @@ -14690,9 +16282,89 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.revert.staged": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.revert.staged" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { "sessionID": { "type": "string", "allOf": [ @@ -14706,7 +16378,6 @@ } }, "required": [ - "timestamp", "sessionID", "revert" ], @@ -14715,12 +16386,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.revert.cleared": { + "session.revert.cleared": { "type": "object", "properties": { "id": { @@ -14731,13 +16404,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.revert.cleared" + "session.revert.cleared" ] }, "durable": { @@ -14776,9 +16452,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -14789,7 +16462,6 @@ } }, "required": [ - "timestamp", "sessionID" ], "additionalProperties": false @@ -14797,12 +16469,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.revert.committed": { + "session.revert.committed": { "type": "object", "properties": { "id": { @@ -14813,13 +16487,16 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.revert.committed" + "session.revert.committed" ] }, "durable": { @@ -14858,9 +16535,6 @@ "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -14869,7 +16543,7 @@ } ] }, - "messageID": { + "to": { "type": "string", "allOf": [ { @@ -14879,16 +16553,17 @@ } }, "required": [ - "timestamp", "sessionID", - "messageID" + "to" ], "additionalProperties": false } }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false @@ -14896,97 +16571,118 @@ "SessionDurableEvent": { "oneOf": [ { - "$ref": "#/components/schemas/session.next.agent.switched" + "$ref": "#/components/schemas/session.agent.selected" }, { - "$ref": "#/components/schemas/session.next.model.switched" + "$ref": "#/components/schemas/session.model.selected" }, { - "$ref": "#/components/schemas/session.next.moved" + "$ref": "#/components/schemas/session.moved" }, { - "$ref": "#/components/schemas/session.next.renamed" + "$ref": "#/components/schemas/session.renamed" }, { - "$ref": "#/components/schemas/session.next.forked" + "$ref": "#/components/schemas/session.deleted" }, { - "$ref": "#/components/schemas/session.next.prompted" + "$ref": "#/components/schemas/session.forked" }, { - "$ref": "#/components/schemas/session.next.prompt.admitted" + "$ref": "#/components/schemas/session.prompt.promoted" }, { - "$ref": "#/components/schemas/session.next.instructions.updated" + "$ref": "#/components/schemas/session.prompt.admitted" }, { - "$ref": "#/components/schemas/session.next.synthetic" + "$ref": "#/components/schemas/session.execution.started" }, { - "$ref": "#/components/schemas/session.next.skill.activated" + "$ref": "#/components/schemas/session.execution.succeeded" }, { - "$ref": "#/components/schemas/session.next.shell.started" + "$ref": "#/components/schemas/session.execution.failed" }, { - "$ref": "#/components/schemas/session.next.shell.ended" + "$ref": "#/components/schemas/session.execution.interrupted" }, { - "$ref": "#/components/schemas/session.next.step.started" + "$ref": "#/components/schemas/session.instructions.updated" }, { - "$ref": "#/components/schemas/session.next.step.ended" + "$ref": "#/components/schemas/session.synthetic" }, { - "$ref": "#/components/schemas/session.next.step.failed" + "$ref": "#/components/schemas/session.skill.activated" }, { - "$ref": "#/components/schemas/session.next.text.started" + "$ref": "#/components/schemas/session.shell.started" }, { - "$ref": "#/components/schemas/session.next.text.ended" + "$ref": "#/components/schemas/session.shell.ended" }, { - "$ref": "#/components/schemas/session.next.tool.input.started" + "$ref": "#/components/schemas/session.step.started" }, { - "$ref": "#/components/schemas/session.next.tool.input.ended" + "$ref": "#/components/schemas/session.step.ended" }, { - "$ref": "#/components/schemas/session.next.tool.called" + "$ref": "#/components/schemas/session.step.failed" }, { - "$ref": "#/components/schemas/session.next.tool.progress" + "$ref": "#/components/schemas/session.text.started" }, { - "$ref": "#/components/schemas/session.next.tool.success" + "$ref": "#/components/schemas/session.text.ended" }, { - "$ref": "#/components/schemas/session.next.tool.failed" + "$ref": "#/components/schemas/session.reasoning.started" }, { - "$ref": "#/components/schemas/session.next.reasoning.started" + "$ref": "#/components/schemas/session.reasoning.ended" }, { - "$ref": "#/components/schemas/session.next.reasoning.ended" + "$ref": "#/components/schemas/session.tool.input.started" }, { - "$ref": "#/components/schemas/session.next.retried" + "$ref": "#/components/schemas/session.tool.input.ended" }, { - "$ref": "#/components/schemas/session.next.compaction.started" + "$ref": "#/components/schemas/session.tool.called" }, { - "$ref": "#/components/schemas/session.next.compaction.ended" + "$ref": "#/components/schemas/session.tool.progress" }, { - "$ref": "#/components/schemas/session.next.revert.staged" + "$ref": "#/components/schemas/session.tool.success" }, { - "$ref": "#/components/schemas/session.next.revert.cleared" + "$ref": "#/components/schemas/session.tool.failed" }, { - "$ref": "#/components/schemas/session.next.revert.committed" + "$ref": "#/components/schemas/session.retry.scheduled" + }, + { + "$ref": "#/components/schemas/session.compaction.admitted" + }, + { + "$ref": "#/components/schemas/session.compaction.started" + }, + { + "$ref": "#/components/schemas/session.compaction.ended" + }, + { + "$ref": "#/components/schemas/session.compaction.failed" + }, + { + "$ref": "#/components/schemas/session.revert.staged" + }, + { + "$ref": "#/components/schemas/session.revert.cleared" + }, + { + "$ref": "#/components/schemas/session.revert.committed" } ] }, @@ -15044,14 +16740,6 @@ "$ref": "#/components/schemas/Session.Message" } }, - "watermark": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, "cursor": { "type": "object", "properties": { @@ -15085,65 +16773,6 @@ ], "additionalProperties": false }, - "Model.Api": { - "anyOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "aisdk" - ] - }, - "package": { - "type": "string" - }, - "url": { - "type": "string" - }, - "settings": { - "type": "object" - } - }, - "required": [ - "id", - "type", - "package" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "native" - ] - }, - "url": { - "type": "string" - }, - "settings": { - "type": "object" - } - }, - "required": [ - "id", - "type", - "settings" - ], - "additionalProperties": false - } - ] - }, "Model.Capabilities": { "type": "object", "properties": { @@ -15170,6 +16799,30 @@ ], "additionalProperties": false }, + "Model.Variant": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, "Model.Cost": { "type": "object", "properties": { @@ -15228,6 +16881,9 @@ "id": { "type": "string" }, + "modelID": { + "type": "string" + }, "providerID": { "type": "string" }, @@ -15237,66 +16893,28 @@ "name": { "type": "string" }, - "api": { - "$ref": "#/components/schemas/Model.Api" + "package": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" }, "capabilities": { "$ref": "#/components/schemas/Model.Capabilities" }, - "request": { - "type": "object", - "properties": { - "settings": { - "$ref": "#/components/schemas/Provider.Settings" - }, - "headers": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "body": { - "type": "object" - }, - "variant": { - "type": "string" - } - }, - "required": [ - "settings", - "headers", - "body" - ], - "additionalProperties": false - }, "variants": { "type": "array", "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "settings": { - "$ref": "#/components/schemas/Provider.Settings" - }, - "headers": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "body": { - "type": "object" - } - }, - "required": [ - "id", - "settings", - "headers", - "body" - ], - "additionalProperties": false + "$ref": "#/components/schemas/Model.Variant" } }, "time": { @@ -15351,11 +16969,10 @@ }, "required": [ "id", + "modelID", "providerID", "name", - "api", "capabilities", - "request", "variants", "time", "cost", @@ -15386,63 +17003,6 @@ ], "additionalProperties": false }, - "Provider.AISDK": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "aisdk" - ] - }, - "package": { - "type": "string" - }, - "url": { - "type": "string" - }, - "settings": { - "type": "object" - } - }, - "required": [ - "type", - "package" - ], - "additionalProperties": false - }, - "Provider.Native": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "native" - ] - }, - "url": { - "type": "string" - }, - "settings": { - "type": "object" - } - }, - "required": [ - "type", - "settings" - ], - "additionalProperties": false - }, - "Provider.Api": { - "anyOf": [ - { - "$ref": "#/components/schemas/Provider.AISDK" - }, - { - "$ref": "#/components/schemas/Provider.Native" - } - ] - }, "ProviderV2.Info": { "type": "object", "properties": { @@ -15458,18 +17018,26 @@ "disabled": { "type": "boolean" }, - "api": { - "$ref": "#/components/schemas/Provider.Api" + "package": { + "type": "string" }, - "request": { - "$ref": "#/components/schemas/Provider.Request" + "settings": { + "type": "object" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" } }, "required": [ "id", "name", - "api", - "request" + "package" ], "additionalProperties": false }, @@ -16305,13 +17873,13 @@ ], "additionalProperties": false }, - "Mcp.Status.Disconnected": { + "Mcp.Status.Pending": { "type": "object", "properties": { "status": { "type": "string", "enum": [ - "disconnected" + "pending" ] } }, @@ -16400,7 +17968,7 @@ "$ref": "#/components/schemas/Mcp.Status.Connected" }, { - "$ref": "#/components/schemas/Mcp.Status.Disconnected" + "$ref": "#/components/schemas/Mcp.Status.Pending" }, { "$ref": "#/components/schemas/Mcp.Status.Disabled" @@ -16426,6 +17994,185 @@ ], "additionalProperties": false }, + "Mcp.Resource": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "name": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mimeType": { + "type": "string" + } + }, + "required": [ + "server", + "name", + "uri" + ], + "additionalProperties": false + }, + "Mcp.ResourceTemplate": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "name": { + "type": "string" + }, + "uriTemplate": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mimeType": { + "type": "string" + } + }, + "required": [ + "server", + "name", + "uriTemplate" + ], + "additionalProperties": false + }, + "Mcp.ResourceCatalog": { + "type": "object", + "properties": { + "resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Mcp.Resource" + } + }, + "templates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Mcp.ResourceTemplate" + } + } + }, + "required": [ + "resources", + "templates" + ], + "additionalProperties": false + }, + "Project.Vcs": { + "type": "string", + "enum": [ + "git", + "hg" + ] + }, + "Project.Icon": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "override": { + "type": "string" + }, + "color": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Project.Commands": { + "type": "object", + "properties": { + "start": { + "type": "string", + "description": "Startup script to run when creating a new workspace (worktree)" + } + }, + "additionalProperties": false + }, + "Project.Time": { + "type": "object", + "properties": { + "created": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "updated": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "initialized": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + } + }, + "required": [ + "created", + "updated" + ], + "additionalProperties": false + }, + "Project": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "worktree": { + "type": "string" + }, + "vcs": { + "$ref": "#/components/schemas/Project.Vcs" + }, + "name": { + "type": "string" + }, + "icon": { + "$ref": "#/components/schemas/Project.Icon" + }, + "commands": { + "$ref": "#/components/schemas/Project.Commands" + }, + "time": { + "$ref": "#/components/schemas/Project.Time" + }, + "sandboxes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "worktree", + "time", + "sandboxes" + ], + "additionalProperties": false + }, "Project.Current": { "type": "object", "properties": { @@ -17606,6 +19353,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -17615,36 +19365,6 @@ "models-dev.refreshed" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -17661,6 +19381,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -17677,6 +19398,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -17686,36 +19410,6 @@ "integration.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -17732,6 +19426,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -17748,6 +19443,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -17757,36 +19455,6 @@ "integration.connection.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -17805,6 +19473,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -17821,6 +19490,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -17830,36 +19502,6 @@ "catalog.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -17876,6 +19518,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -17892,6 +19535,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -17901,36 +19547,6 @@ "agent.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -17947,6 +19563,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -18258,6 +19875,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -18324,7 +19944,9 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false @@ -18340,6 +19962,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -18406,12 +20031,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.deleted": { + "session.deleted1": { "type": "object", "properties": { "id": { @@ -18422,6 +20049,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -18488,7 +20118,9 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false @@ -19277,6 +20909,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -19343,7 +20978,9 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false @@ -19359,6 +20996,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -19430,7 +21070,9 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false @@ -20824,6 +22466,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -20894,7 +22539,9 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false @@ -20910,6 +22557,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -20990,12 +22640,14 @@ }, "required": [ "id", + "created", "type", + "durable", "data" ], "additionalProperties": false }, - "session.next.execution.settled": { + "session.usage.updated": { "type": "object", "properties": { "id": { @@ -21006,54 +22658,24 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.execution.settled" + "session.usage.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -21062,34 +22684,64 @@ } ] }, - "outcome": { - "type": "string", - "enum": [ - "success", - "failure", - "interrupted" - ] + "cost": { + "type": "number" }, - "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": [ + "read", + "write" + ], + "additionalProperties": false + } + }, + "required": [ + "input", + "output", + "reasoning", + "cache" + ], + "additionalProperties": false } }, "required": [ - "timestamp", "sessionID", - "outcome" + "cost", + "tokens" ], "additionalProperties": false } }, "required": [ "id", + "created", "type", "data" ], "additionalProperties": false }, - "session.next.text.delta": { + "session.text.delta": { "type": "object", "properties": { "id": { @@ -21100,54 +22752,24 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.text.delta" + "session.text.delta" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -21164,18 +22786,22 @@ } ] }, - "textID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "delta": { "type": "string" } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", - "textID", + "ordinal", "delta" ], "additionalProperties": false @@ -21183,12 +22809,13 @@ }, "required": [ "id", + "created", "type", "data" ], "additionalProperties": false }, - "session.next.reasoning.delta": { + "session.reasoning.delta": { "type": "object", "properties": { "id": { @@ -21199,54 +22826,24 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.reasoning.delta" + "session.reasoning.delta" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -21263,18 +22860,22 @@ } ] }, - "reasoningID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "delta": { "type": "string" } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", - "reasoningID", + "ordinal", "delta" ], "additionalProperties": false @@ -21282,12 +22883,13 @@ }, "required": [ "id", + "created", "type", "data" ], "additionalProperties": false }, - "session.next.tool.input.delta": { + "session.tool.input.delta": { "type": "object", "properties": { "id": { @@ -21298,54 +22900,24 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.tool.input.delta" + "session.tool.input.delta" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -21370,7 +22942,6 @@ } }, "required": [ - "timestamp", "sessionID", "assistantMessageID", "callID", @@ -21381,12 +22952,13 @@ }, "required": [ "id", + "created", "type", "data" ], "additionalProperties": false }, - "session.next.compaction.delta": { + "session.compaction.delta": { "type": "object", "properties": { "id": { @@ -21397,54 +22969,24 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "session.next.compaction.delta" + "session.compaction.delta" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, "data": { "type": "object", "properties": { - "timestamp": { - "type": "number" - }, "sessionID": { "type": "string", "allOf": [ @@ -21453,22 +22995,12 @@ } ] }, - "messageID": { - "type": "string", - "allOf": [ - { - "pattern": "^msg_" - } - ] - }, "text": { "type": "string" } }, "required": [ - "timestamp", "sessionID", - "messageID", "text" ], "additionalProperties": false @@ -21476,12 +23008,13 @@ }, "required": [ "id", + "created", "type", "data" ], "additionalProperties": false }, - "file.edited": { + "filesystem.changed": { "type": "object", "properties": { "id": { @@ -21492,45 +23025,18 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, "type": { "type": "string", "enum": [ - "file.edited" + "filesystem.changed" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -21539,16 +23045,26 @@ "properties": { "file": { "type": "string" + }, + "event": { + "type": "string", + "enum": [ + "add", + "change", + "unlink" + ] } }, "required": [ - "file" + "file", + "event" ], "additionalProperties": false } }, "required": [ "id", + "created", "type", "data" ], @@ -21565,6 +23081,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -21574,36 +23093,6 @@ "reference.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -21620,6 +23109,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -21636,6 +23126,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -21645,36 +23138,6 @@ "permission.v2.asked" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -21730,6 +23193,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -21746,6 +23210,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -21755,36 +23222,6 @@ "permission.v2.replied" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -21821,6 +23258,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -21837,6 +23275,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -21846,36 +23287,6 @@ "plugin.added" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -21894,6 +23305,52 @@ }, "required": [ "id", + "created", + "type", + "data" + ], + "additionalProperties": false + }, + "plugin.updated": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "plugin.updated" + ] + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + } + }, + "required": [ + "id", + "created", "type", "data" ], @@ -21910,6 +23367,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -21919,36 +23379,6 @@ "project.directories.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -21967,6 +23397,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -21983,6 +23414,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -21992,36 +23426,6 @@ "command.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22038,6 +23442,52 @@ }, "required": [ "id", + "created", + "type", + "data" + ], + "additionalProperties": false + }, + "config.updated": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "config.updated" + ] + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + } + }, + "required": [ + "id", + "created", "type", "data" ], @@ -22054,6 +23504,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -22063,36 +23516,6 @@ "skill.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22109,88 +23532,7 @@ }, "required": [ "id", - "type", - "data" - ], - "additionalProperties": false - }, - "file.watcher.updated": { - "type": "object", - "properties": { - "id": { - "type": "string", - "allOf": [ - { - "pattern": "^evt_" - } - ] - }, - "metadata": { - "type": "object" - }, - "type": { - "type": "string", - "enum": [ - "file.watcher.updated" - ] - }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, - "location": { - "$ref": "#/components/schemas/Location.Ref" - }, - "data": { - "type": "object", - "properties": { - "file": { - "type": "string" - }, - "event": { - "type": "string", - "enum": [ - "add", - "change", - "unlink" - ] - } - }, - "required": [ - "file", - "event" - ], - "additionalProperties": false - } - }, - "required": [ - "id", + "created", "type", "data" ], @@ -22268,6 +23610,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -22277,36 +23622,6 @@ "pty.created" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22325,6 +23640,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -22341,6 +23657,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -22350,36 +23669,6 @@ "pty.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22398,6 +23687,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -22414,6 +23704,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -22423,36 +23716,6 @@ "pty.exited" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22485,6 +23748,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -22501,6 +23765,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -22510,36 +23777,6 @@ "pty.deleted" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22563,151 +23800,12 @@ }, "required": [ "id", + "created", "type", "data" ], "additionalProperties": false }, - "Shell": { - "type": "object", - "properties": { - "id": { - "type": "string", - "allOf": [ - { - "pattern": "^sh_" - } - ] - }, - "status": { - "type": "string", - "enum": [ - "running", - "exited", - "timeout", - "killed" - ] - }, - "command": { - "type": "string" - }, - "cwd": { - "type": "string" - }, - "shell": { - "type": "string" - }, - "file": { - "type": "string" - }, - "pid": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "exit": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - }, - "metadata": { - "type": "object" - }, - "time": { - "type": "object", - "properties": { - "started": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - }, - "completed": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - } - }, - "required": [ - "started" - ], - "additionalProperties": false - } - }, - "required": [ - "id", - "status", - "command", - "cwd", - "shell", - "file", - "metadata", - "time" - ], - "additionalProperties": false - }, "shell.created": { "type": "object", "properties": { @@ -22719,6 +23817,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -22728,36 +23829,6 @@ "shell.created" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22765,7 +23836,7 @@ "type": "object", "properties": { "info": { - "$ref": "#/components/schemas/Shell" + "$ref": "#/components/schemas/Shell1" } }, "required": [ @@ -22776,6 +23847,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -22792,6 +23864,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -22801,36 +23876,6 @@ "shell.exited" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22889,6 +23934,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -22905,6 +23951,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -22914,36 +23963,6 @@ "shell.deleted" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -22967,6 +23986,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -23049,6 +24069,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -23058,36 +24081,6 @@ "question.v2.asked" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -23131,6 +24124,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -23153,6 +24147,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -23162,36 +24159,6 @@ "question.v2.replied" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -23231,6 +24198,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -23247,6 +24215,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -23256,36 +24227,6 @@ "question.v2.rejected" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -23318,6 +24259,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -23886,6 +24828,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -23895,36 +24840,6 @@ "form.created" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -23950,6 +24865,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24013,6 +24929,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24022,36 +24941,6 @@ "form.replied" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24083,6 +24972,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24099,6 +24989,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24108,36 +25001,6 @@ "form.cancelled" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24165,6 +25028,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24204,6 +25068,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24213,36 +25080,6 @@ "todo.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24273,6 +25110,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24391,6 +25229,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24400,36 +25241,6 @@ "session.status" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24457,6 +25268,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24473,6 +25285,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24482,36 +25297,6 @@ "session.idle" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24535,6 +25320,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24551,6 +25337,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24560,36 +25349,6 @@ "tui.prompt.append" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24608,6 +25367,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24624,6 +25384,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24633,36 +25396,6 @@ "tui.command.execute" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24707,6 +25440,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24723,6 +25457,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24732,36 +25469,6 @@ "tui.toast.show" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24808,6 +25515,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24824,6 +25532,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24833,36 +25544,6 @@ "tui.session.select" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24887,6 +25568,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24903,6 +25585,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24912,36 +25597,6 @@ "installation.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -24960,6 +25615,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -24976,6 +25632,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -24985,36 +25644,6 @@ "installation.update-available" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25033,6 +25662,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -25049,6 +25679,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -25058,36 +25691,6 @@ "vcs.branch.updated" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25103,6 +25706,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -25119,6 +25723,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -25128,36 +25735,6 @@ "mcp.status.changed" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25176,6 +25753,54 @@ }, "required": [ "id", + "created", + "type", + "data" + ], + "additionalProperties": false + }, + "mcp.resources.changed": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "mcp.resources.changed" + ] + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "server": { + "type": "string" + } + }, + "required": [ + "server" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", "type", "data" ], @@ -25192,6 +25817,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -25201,36 +25829,6 @@ "permission.asked" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25308,6 +25906,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -25324,6 +25923,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -25333,36 +25935,6 @@ "permission.replied" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25404,6 +25976,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -25507,6 +26080,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -25516,36 +26092,6 @@ "question.asked" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25596,6 +26142,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -25618,6 +26165,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -25627,36 +26177,6 @@ "question.replied" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25696,6 +26216,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -25712,6 +26233,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -25721,36 +26245,6 @@ "question.rejected" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25783,6 +26277,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -25799,6 +26294,9 @@ } ] }, + "created": { + "type": "number" + }, "metadata": { "type": "object" }, @@ -25808,36 +26306,6 @@ "session.error" ] }, - "durable": { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, "location": { "$ref": "#/components/schemas/Location.Ref" }, @@ -25900,6 +26368,7 @@ }, "required": [ "id", + "created", "type", "data" ], @@ -25926,43 +26395,6 @@ } ] }, - "durable": { - "anyOf": [ - { - "type": "object", - "properties": { - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "version": { - "type": "integer", - "allOf": [ - { - "minimum": 1 - } - ] - } - }, - "required": [ - "aggregateID", - "seq", - "version" - ], - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, "location": { "anyOf": [ { @@ -26021,7 +26453,7 @@ "$ref": "#/components/schemas/session.updated" }, { - "$ref": "#/components/schemas/session.deleted" + "$ref": "#/components/schemas/session.deleted1" }, { "$ref": "#/components/schemas/message.updated" @@ -26036,115 +26468,136 @@ "$ref": "#/components/schemas/message.part.removed" }, { - "$ref": "#/components/schemas/session.next.agent.switched" + "$ref": "#/components/schemas/session.agent.selected" }, { - "$ref": "#/components/schemas/session.next.model.switched" + "$ref": "#/components/schemas/session.model.selected" }, { - "$ref": "#/components/schemas/session.next.moved" + "$ref": "#/components/schemas/session.moved" }, { - "$ref": "#/components/schemas/session.next.renamed" + "$ref": "#/components/schemas/session.renamed" }, { - "$ref": "#/components/schemas/session.next.forked" + "$ref": "#/components/schemas/session.usage.updated" }, { - "$ref": "#/components/schemas/session.next.prompted" + "$ref": "#/components/schemas/session.deleted" }, { - "$ref": "#/components/schemas/session.next.prompt.admitted" + "$ref": "#/components/schemas/session.forked" }, { - "$ref": "#/components/schemas/session.next.execution.settled" + "$ref": "#/components/schemas/session.prompt.promoted" }, { - "$ref": "#/components/schemas/session.next.instructions.updated" + "$ref": "#/components/schemas/session.prompt.admitted" }, { - "$ref": "#/components/schemas/session.next.synthetic" + "$ref": "#/components/schemas/session.execution.started" }, { - "$ref": "#/components/schemas/session.next.skill.activated" + "$ref": "#/components/schemas/session.execution.succeeded" }, { - "$ref": "#/components/schemas/session.next.shell.started" + "$ref": "#/components/schemas/session.execution.failed" }, { - "$ref": "#/components/schemas/session.next.shell.ended" + "$ref": "#/components/schemas/session.execution.interrupted" }, { - "$ref": "#/components/schemas/session.next.step.started" + "$ref": "#/components/schemas/session.instructions.updated" }, { - "$ref": "#/components/schemas/session.next.step.ended" + "$ref": "#/components/schemas/session.synthetic" }, { - "$ref": "#/components/schemas/session.next.step.failed" + "$ref": "#/components/schemas/session.skill.activated" }, { - "$ref": "#/components/schemas/session.next.text.started" + "$ref": "#/components/schemas/session.shell.started" }, { - "$ref": "#/components/schemas/session.next.text.delta" + "$ref": "#/components/schemas/session.shell.ended" }, { - "$ref": "#/components/schemas/session.next.text.ended" + "$ref": "#/components/schemas/session.step.started" }, { - "$ref": "#/components/schemas/session.next.reasoning.started" + "$ref": "#/components/schemas/session.step.ended" }, { - "$ref": "#/components/schemas/session.next.reasoning.delta" + "$ref": "#/components/schemas/session.step.failed" }, { - "$ref": "#/components/schemas/session.next.reasoning.ended" + "$ref": "#/components/schemas/session.text.started" }, { - "$ref": "#/components/schemas/session.next.tool.input.started" + "$ref": "#/components/schemas/session.text.delta" }, { - "$ref": "#/components/schemas/session.next.tool.input.delta" + "$ref": "#/components/schemas/session.text.ended" }, { - "$ref": "#/components/schemas/session.next.tool.input.ended" + "$ref": "#/components/schemas/session.reasoning.started" }, { - "$ref": "#/components/schemas/session.next.tool.called" + "$ref": "#/components/schemas/session.reasoning.delta" }, { - "$ref": "#/components/schemas/session.next.tool.progress" + "$ref": "#/components/schemas/session.reasoning.ended" }, { - "$ref": "#/components/schemas/session.next.tool.success" + "$ref": "#/components/schemas/session.tool.input.started" }, { - "$ref": "#/components/schemas/session.next.tool.failed" + "$ref": "#/components/schemas/session.tool.input.delta" }, { - "$ref": "#/components/schemas/session.next.retried" + "$ref": "#/components/schemas/session.tool.input.ended" }, { - "$ref": "#/components/schemas/session.next.compaction.started" + "$ref": "#/components/schemas/session.tool.called" }, { - "$ref": "#/components/schemas/session.next.compaction.delta" + "$ref": "#/components/schemas/session.tool.progress" }, { - "$ref": "#/components/schemas/session.next.compaction.ended" + "$ref": "#/components/schemas/session.tool.success" }, { - "$ref": "#/components/schemas/session.next.revert.staged" + "$ref": "#/components/schemas/session.tool.failed" }, { - "$ref": "#/components/schemas/session.next.revert.cleared" + "$ref": "#/components/schemas/session.retry.scheduled" }, { - "$ref": "#/components/schemas/session.next.revert.committed" + "$ref": "#/components/schemas/session.compaction.admitted" }, { - "$ref": "#/components/schemas/file.edited" + "$ref": "#/components/schemas/session.compaction.started" + }, + { + "$ref": "#/components/schemas/session.compaction.delta" + }, + { + "$ref": "#/components/schemas/session.compaction.ended" + }, + { + "$ref": "#/components/schemas/session.compaction.failed" + }, + { + "$ref": "#/components/schemas/session.revert.staged" + }, + { + "$ref": "#/components/schemas/session.revert.cleared" + }, + { + "$ref": "#/components/schemas/session.revert.committed" + }, + { + "$ref": "#/components/schemas/filesystem.changed" }, { "$ref": "#/components/schemas/reference.updated" @@ -26158,6 +26611,9 @@ { "$ref": "#/components/schemas/plugin.added" }, + { + "$ref": "#/components/schemas/plugin.updated" + }, { "$ref": "#/components/schemas/project.directories.updated" }, @@ -26165,10 +26621,10 @@ "$ref": "#/components/schemas/command.updated" }, { - "$ref": "#/components/schemas/skill.updated" + "$ref": "#/components/schemas/config.updated" }, { - "$ref": "#/components/schemas/file.watcher.updated" + "$ref": "#/components/schemas/skill.updated" }, { "$ref": "#/components/schemas/pty.created" @@ -26242,6 +26698,9 @@ { "$ref": "#/components/schemas/mcp.status.changed" }, + { + "$ref": "#/components/schemas/mcp.resources.changed" + }, { "$ref": "#/components/schemas/permission.asked" }, @@ -26272,68 +26731,6 @@ }, "contentMediaType": "application/json" }, - "EventLog.Hint": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "log.hint" - ] - }, - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - } - }, - "required": [ - "type", - "aggregateID", - "seq" - ], - "additionalProperties": false, - "description": "Payload-free change hint: the aggregate's durable log advanced to at least seq. Hints coalesce under backpressure (latest per aggregate) and are never a delivery guarantee." - }, - "EventLog.SweepRequired": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "log.sweep_required" - ] - } - }, - "required": [ - "type" - ], - "additionalProperties": false, - "description": "Hints may have been lost; treat every aggregate as potentially dirty and recover via bounded sweep plus durable log reads. Emitted first on every (re)subscribe." - }, - "EventLog.Change": { - "anyOf": [ - { - "$ref": "#/components/schemas/EventLog.Hint" - }, - { - "$ref": "#/components/schemas/EventLog.SweepRequired" - } - ] - }, - "EventLog.ChangeStream": { - "type": "string", - "contentSchema": { - "$ref": "#/components/schemas/EventLog.Change" - }, - "contentMediaType": "application/json" - }, "PtyNotFoundError": { "type": "object", "properties": { @@ -26397,182 +26794,6 @@ ], "additionalProperties": false }, - "Shell1": { - "type": "object", - "properties": { - "id": { - "type": "string", - "allOf": [ - { - "pattern": "^sh_" - } - ] - }, - "status": { - "type": "string", - "enum": [ - "running", - "exited", - "timeout", - "killed" - ] - }, - "command": { - "type": "string" - }, - "cwd": { - "type": "string" - }, - "shell": { - "type": "string" - }, - "file": { - "type": "string" - }, - "pid": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, - "exit": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - }, - { - "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] - } - ] - }, - "metadata": { - "type": "object" - }, - "time": { - "type": "object", - "properties": { - "started": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - }, - { - "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] - } - ] - }, - "completed": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - }, - { - "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] - } - ] - } - }, - "required": [ - "started" - ], - "additionalProperties": false - } - }, - "required": [ - "id", - "status", - "command", - "cwd", - "shell", - "file", - "metadata", - "time" - ], - "additionalProperties": false - }, "ShellNotFoundError": { "type": "object", "properties": { @@ -26863,28 +27084,28 @@ "security": [], "tags": [ { - "name": "server.health" + "name": "health" }, { - "name": "server.location" + "name": "location" }, { - "name": "server.agent" + "name": "agent" }, { - "name": "plugins", + "name": "plugin", "description": "Experimental plugin routes." }, { - "name": "sessions", + "name": "session", "description": "Experimental session routes." }, { - "name": "messages", + "name": "session", "description": "Experimental message routes." }, { - "name": "models", + "name": "model", "description": "Experimental model routes." }, { @@ -26892,30 +27113,30 @@ "description": "Experimental one-shot generation routes." }, { - "name": "providers", + "name": "provider", "description": "Experimental provider routes." }, { - "name": "integrations", + "name": "integration", "description": "Integration discovery and authentication routes." }, { "name": "mcp", - "description": "MCP server status routes." + "description": "MCP server and resource routes." }, { - "name": "server.credential" + "name": "credential" }, { - "name": "projects", + "name": "project", "description": "Location-scoped project routes." }, { - "name": "forms", + "name": "form", "description": "Session form routes." }, { - "name": "permissions", + "name": "permission", "description": "Experimental permission routes." }, { @@ -26923,15 +27144,15 @@ "description": "Experimental location-scoped filesystem routes." }, { - "name": "commands", + "name": "command", "description": "Experimental command routes." }, { - "name": "skills", + "name": "skill", "description": "Experimental skill routes." }, { - "name": "events", + "name": "event", "description": "Experimental event stream routes." }, { @@ -26943,7 +27164,7 @@ "description": "Experimental location-scoped shell command routes." }, { - "name": "session questions", + "name": "question", "description": "Experimental session question routes." }, { @@ -26957,6 +27178,9 @@ { "name": "vcs", "description": "Location-scoped version control routes." + }, + { + "name": "debug" } ] } diff --git a/packages/codemode/test/openapi.test.ts b/packages/codemode/test/openapi.test.ts index db799e5894..608019da46 100644 --- a/packages/codemode/test/openapi.test.ts +++ b/packages/codemode/test/openapi.test.ts @@ -177,13 +177,13 @@ describe("OpenAPI.fromSpec", () => { const spec = await opencodeSpec() const result = OpenAPI.fromSpec({ spec, baseUrl }) - expect(result.skipped).toHaveLength(5) + expect(result.skipped).toHaveLength(4) expect(result.skipped).toContainEqual({ method: "GET", path: "/api/pty/{ptyID}/connect", reason: "WebSocket operations are not supported", }) - expect(result.skipped.filter((item) => item.reason === "SSE operations are not supported")).toHaveLength(3) + expect(result.skipped.filter((item) => item.reason === "SSE operations are not supported")).toHaveLength(2) expect(result.skipped).toContainEqual({ method: "GET", path: "/api/fs/read/*", @@ -210,11 +210,11 @@ describe("OpenAPI.fromSpec", () => { if (!Tool.isDefinition(instructionPut)) throw new Error("v2.session.instructions.entry.put was not generated") expect(inputTypeScript(instructionPut)).toBe("{ sessionID: string; key: string; value: unknown }") expect(toolAt(result.tools, "v2_session_instructions_entry_put_2")).toBeUndefined() - expect(toolAt(result.tools, "v2.pty.connect")).toBeUndefined() + expect(Tool.isDefinition(toolAt(result.tools, "v2.pty.connect"))).toBe(false) expect(toolAt(result.tools, "v2.session.log")).toBeUndefined() expect(toolAt(result.tools, "v2.event.subscribe")).toBeUndefined() expect(toolAt(result.tools, "v2.fs.read")).toBeUndefined() - expect(toolAt(result.tools, "v2.pty.connectToken")).not.toBeUndefined() + expect(toolAt(result.tools, "v2.pty.connect.token")).not.toBeUndefined() }) test("preserves operation path sanitization and collision handling", () => { diff --git a/packages/core/src/plugin.ts b/packages/core/src/plugin.ts index 907920c10e..4e0858fd16 100644 --- a/packages/core/src/plugin.ts +++ b/packages/core/src/plugin.ts @@ -1,6 +1,6 @@ export * as PluginV2 from "./plugin" -import type { Plugin } from "@opencode-ai/plugin/v2/effect" +import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin" import { Event, ID, type Info } from "@opencode-ai/schema/plugin" import { makeLocationNode } from "./effect/app-node" import { Context, Effect, Exit, Layer, Scope, Semaphore } from "effect" @@ -18,6 +18,7 @@ import { SkillV2 } from "./skill" import { State } from "./state" import { ToolRegistry } from "./tool/registry" import { ToolHooks } from "./tool/hooks" +import { PluginHooks } from "./plugin/hooks" export interface Interface { readonly activate: (plugins: readonly { readonly plugin: Plugin; readonly version?: string }[]) => Effect.Effect @@ -57,12 +58,13 @@ const layer = Layer.effect( generation.length === definitions.length && generation.every( (plugin, index) => plugin.id === definitions[index]?.id && plugin.version === definitions[index]?.version, - ) + ) && + definitions.every((definition) => active.has(definition.id)) ) { return } generation = undefined - const exit = yield* State.batch( + yield* State.batch( Effect.gen(function* () { const scopes = Array.from(active.values()).toReversed() active.clear() @@ -81,13 +83,17 @@ const layer = Layer.effect( Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(child, exit) : Effect.void)), Effect.exit, ) - if (Exit.isFailure(loaded)) return loaded + if (Exit.isFailure(loaded)) { + yield* Effect.logWarning("failed to load plugin", { + "plugin.id": definition.id, + cause: loaded.cause, + }) + continue + } active.set(definition.id, child) } - return Exit.void }), ) - if (Exit.isFailure(exit)) return yield* exit generation = definitions.map((definition) => ({ id: definition.id, ...(definition.version === undefined ? {} : { version: definition.version }), @@ -131,6 +137,7 @@ export const node = makeLocationNode({ SkillV2.node, ToolRegistry.toolsNode, ToolHooks.node, + PluginHooks.node, PluginRuntime.node, ], }) diff --git a/packages/core/src/plugin/hooks.ts b/packages/core/src/plugin/hooks.ts new file mode 100644 index 0000000000..4ac437d392 --- /dev/null +++ b/packages/core/src/plugin/hooks.ts @@ -0,0 +1,67 @@ +export * as PluginHooks from "./hooks" + +import type { AISDKHooks } from "@opencode-ai/plugin/v2/effect/aisdk" +import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session" +import type { ToolHooks } from "@opencode-ai/plugin/v2/effect/tool" +import { Context, Effect, Layer, Scope } from "effect" +import { makeLocationNode } from "../effect/app-node" +import { State } from "../state" + +export interface Domains { + readonly aisdk: AISDKHooks + readonly session: SessionHooks + readonly tool: ToolHooks +} + +type Callback = (event: Event) => Effect.Effect + +export interface Interface { + readonly register: ( + domain: Domain, + name: Name, + callback: Callback, + ) => Effect.Effect + readonly trigger: ( + domain: Domain, + name: Name, + event: Domains[Domain][Name], + ) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/v2/PluginHooks") {} + +const layer = Layer.effect( + Service, + Effect.gen(function* () { + const callbacks = new Map() + const key = (domain: keyof Domains, name: PropertyKey) => `${domain}.${String(name)}` + + const register: Interface["register"] = Effect.fn("PluginHooks.register")(function* (domain, name, callback) { + const scope = yield* Scope.Scope + const id = key(domain, name) + let active = true + callbacks.set(id, [...(callbacks.get(id) ?? []), callback]) + const dispose = Effect.sync(() => { + if (!active) return + active = false + const next = (callbacks.get(id) ?? []).filter((item) => item !== callback) + if (next.length === 0) callbacks.delete(id) + else callbacks.set(id, next) + }) + yield* Scope.addFinalizer(scope, dispose) + return { dispose } + }) + + const trigger: Interface["trigger"] = Effect.fn("PluginHooks.trigger")(function* (domain, name, event) { + for (const callback of callbacks.get(key(domain, name)) ?? []) { + const result: Effect.Effect = Reflect.apply(callback, undefined, [event]) + yield* result + } + return event + }) + + return Service.of({ register, trigger }) + }), +) + +export const node = makeLocationNode({ service: Service, layer, deps: [] }) diff --git a/packages/core/src/plugin/host.ts b/packages/core/src/plugin/host.ts index 01ea0da0d3..a109dd02e7 100644 --- a/packages/core/src/plugin/host.ts +++ b/packages/core/src/plugin/host.ts @@ -1,6 +1,6 @@ export * as PluginHost from "./host" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Plugin } from "@opencode-ai/plugin/v2/effect" import { EventManifest } from "@opencode-ai/schema/event-manifest" import { Effect, Schema, Stream } from "effect" import { AgentV2 } from "../agent" @@ -22,6 +22,7 @@ import { Tool } from "../tool/tool" import { Tools } from "../tool/tools" import { ToolHooks } from "../tool/hooks" import { WorkspaceV2 } from "../workspace" +import { PluginHooks } from "./hooks" const mutable = (value: T) => value as DeepMutable export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) { @@ -36,6 +37,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int const skill = yield* SkillV2.Service const tools = yield* Tools.Service const toolHooks = yield* ToolHooks.Service + const hooks = yield* PluginHooks.Service const runtime = yield* PluginRuntime.Service const locationInfo = () => new Location.Info({ @@ -43,7 +45,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int workspaceID: location.workspaceID, project: location.project, }) - const locationRef = (input?: Parameters[0]) => + const locationRef = (input?: Parameters[0]) => input?.location === undefined ? undefined : Location.Ref.make({ @@ -79,32 +81,32 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int }), }, aisdk: { - sdk: (callback) => - aisdk.hook.sdk((event) => { + hook: (name, callback) => { + if (name === "sdk") { + return aisdk.hook.sdk((event) => { + const output = { + model: mutable(event.model), + package: event.package, + options: event.options, + sdk: event.sdk, + } + return Reflect.apply(callback, undefined, [output]).pipe( + Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))), + ) + }) + } + return aisdk.hook.language((event) => { const output = { model: mutable(event.model), - package: event.package, options: event.options, sdk: event.sdk, - } - const result = callback(output) - return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe( - Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))), - ) - }), - language: (callback) => - aisdk.hook.language((event) => { - const output = { - model: mutable(event.model), - sdk: event.sdk, - options: event.options, language: event.language, } - const result = callback(output) - return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe( + return Reflect.apply(callback, undefined, [output]).pipe( Effect.tap(() => Effect.sync(() => (event.language = output.language))), ) - }), + }) + }, }, catalog: { provider: { @@ -164,25 +166,29 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int integration: { list: () => response(integration.list()), get: (input) => response(integration.get(Integration.ID.make(input.integrationID))), - connectKey: (input) => - integration.connection.key({ - integrationID: Integration.ID.make(input.integrationID), - key: input.key, - label: input.label, - }), - connectOauth: (input) => - response( - integration.connection.oauth({ + connect: { + key: (input) => + integration.connection.key({ integrationID: Integration.ID.make(input.integrationID), - methodID: Integration.MethodID.make(input.methodID), - inputs: input.inputs, + key: input.key, label: input.label, }), - ), - attemptStatus: (input) => response(integration.attempt.status(Integration.AttemptID.make(input.attemptID))), - attemptComplete: (input) => - integration.attempt.complete({ attemptID: Integration.AttemptID.make(input.attemptID), code: input.code }), - attemptCancel: (input) => integration.attempt.cancel(Integration.AttemptID.make(input.attemptID)), + oauth: (input) => + response( + integration.connection.oauth({ + integrationID: Integration.ID.make(input.integrationID), + methodID: Integration.MethodID.make(input.methodID), + inputs: input.inputs, + label: input.label, + }), + ), + }, + attempt: { + status: (input) => response(integration.attempt.status(Integration.AttemptID.make(input.attemptID))), + complete: (input) => + integration.attempt.complete({ attemptID: Integration.AttemptID.make(input.attemptID), code: input.code }), + cancel: (input) => integration.attempt.cancel(Integration.AttemptID.make(input.attemptID)), + }, reload: integration.reload, connection: { active: (id) => integration.connection.active(Integration.ID.make(id)), @@ -317,11 +323,12 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int registrations, (registration) => tools.register({ [registration.name]: registration.tool }, registration.options), { discard: true }, - ) + ).pipe(Effect.orDie) + return { dispose: Effect.void } }), - execute: { - before: (callback) => - toolHooks.hook.before((event) => { + hook: (name, callback) => { + if (name === "execute.before") { + return toolHooks.hook.before((event) => { const output = { tool: event.tool, sessionID: event.sessionID, @@ -330,38 +337,37 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int toolCallID: event.toolCallID, input: event.input, } - const result = callback(output) - return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe( + return Reflect.apply(callback, undefined, [output]).pipe( Effect.tap(() => Effect.sync(() => (event.input = output.input))), ) - }), - after: (callback) => - toolHooks.hook.after((event) => { - const output = { - tool: event.tool, - sessionID: event.sessionID, - agent: event.agent, - assistantMessageID: event.assistantMessageID, - toolCallID: event.toolCallID, - input: event.input, - result: event.result, - output: event.output, - outputPaths: event.outputPaths, - } - const result = callback(output) - return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe( - Effect.tap(() => - Effect.sync(() => { - event.result = output.result - event.output = output.output - event.outputPaths = output.outputPaths - }), - ), - ) - }), + }) + } + return toolHooks.hook.after((event) => { + const output = { + tool: event.tool, + sessionID: event.sessionID, + agent: event.agent, + assistantMessageID: event.assistantMessageID, + toolCallID: event.toolCallID, + input: event.input, + result: event.result, + output: event.output, + outputPaths: event.outputPaths, + } + return Reflect.apply(callback, undefined, [output]).pipe( + Effect.tap(() => + Effect.sync(() => { + event.result = output.result + event.output = output.output + event.outputPaths = output.outputPaths + }), + ), + ) + }) }, }, session: { + hook: (name, callback) => hooks.register("session", name, callback), create: (input) => runtime.session.create({ id: input?.id, @@ -375,5 +381,5 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int command: runtime.session.command, interrupt: (input) => runtime.session.interrupt(input.sessionID), }, - } satisfies PluginContext + } satisfies Plugin.Context }) diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index 2789cc3c2e..40715378e1 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -1,6 +1,6 @@ export * as PluginInternal from "./internal" -import type { Plugin } from "@opencode-ai/plugin/v2/effect" +import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin" import { Context, Effect, Scope } from "effect" import { HttpClient } from "effect/unstable/http" import { AgentV2 } from "../agent" diff --git a/packages/core/src/plugin/promise.ts b/packages/core/src/plugin/promise.ts index bcd3db1323..8eafe52734 100644 --- a/packages/core/src/plugin/promise.ts +++ b/packages/core/src/plugin/promise.ts @@ -1,11 +1,12 @@ export * as PluginPromise from "./promise" -import { define } from "@opencode-ai/plugin/v2/effect" -import type { Plugin, PluginContext } from "@opencode-ai/plugin/v2/promise" +import { Plugin } from "@opencode-ai/plugin/v2/effect" import { Effect, Scope, Stream } from "effect" type HostRegistration = { readonly dispose: Effect.Effect } type Registration = { readonly dispose: () => Promise } +type PromisePlugin = import("@opencode-ai/plugin/v2/plugin").Plugin +type PromisePluginContext = import("@opencode-ai/plugin/v2/plugin").Context /** * Adapts a Promise plugin into an Effect plugin so the existing Effect-only @@ -16,8 +17,8 @@ type Registration = { readonly dispose: () => Promise } * preserves boot-time batching, so Promise-plugin transforms still coalesce * into one reload per domain. */ -export function fromPromise(plugin: Plugin) { - return define({ +export function fromPromise(plugin: PromisePlugin) { + return Plugin.define({ id: plugin.id, effect: (host) => Effect.gen(function* () { @@ -43,7 +44,7 @@ export function fromPromise(plugin: Plugin) { }), ) - const context2: PluginContext = { + const context2: PromisePluginContext = { options: host.options, agent: { list: (input) => run(host.agent.list(input)), @@ -51,10 +52,8 @@ export function fromPromise(plugin: Plugin) { reload: () => run(host.agent.reload()), }, aisdk: { - sdk: (callback) => - register(host.aisdk.sdk((event) => Effect.promise(() => Promise.resolve(callback(event))))), - language: (callback) => - register(host.aisdk.language((event) => Effect.promise(() => Promise.resolve(callback(event))))), + hook: (name, callback) => + register(host.aisdk.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))), }, catalog: { provider: { @@ -79,11 +78,15 @@ export function fromPromise(plugin: Plugin) { integration: { list: (input) => run(host.integration.list(input)), get: (input) => run(host.integration.get(input)), - connectKey: (input) => run(host.integration.connectKey(input)), - connectOauth: (input) => run(host.integration.connectOauth(input)), - attemptStatus: (input) => run(host.integration.attemptStatus(input)), - attemptComplete: (input) => run(host.integration.attemptComplete(input)), - attemptCancel: (input) => run(host.integration.attemptCancel(input)), + connect: { + key: (input) => run(host.integration.connect.key(input)), + oauth: (input) => run(host.integration.connect.oauth(input)), + }, + attempt: { + status: (input) => run(host.integration.attempt.status(input)), + complete: (input) => run(host.integration.attempt.complete(input)), + cancel: (input) => run(host.integration.attempt.cancel(input)), + }, transform: transform(host.integration), reload: () => run(host.integration.reload()), connection: { @@ -104,12 +107,19 @@ export function fromPromise(plugin: Plugin) { transform: transform(host.skill), reload: () => run(host.skill.reload()), }, + tool: { + transform: transform(host.tool), + hook: (name, callback) => + register(host.tool.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))), + }, session: { create: (input) => run(host.session.create(input)), get: (input) => run(host.session.get(input)), prompt: (input) => run(host.session.prompt(input)), command: (input) => run(host.session.command(input)), interrupt: (input) => run(host.session.interrupt(input)), + hook: (name, callback) => + register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))), }, } diff --git a/packages/core/src/plugin/provider/alibaba.ts b/packages/core/src/plugin/provider/alibaba.ts index 607e565d4a..ea37b00453 100644 --- a/packages/core/src/plugin/provider/alibaba.ts +++ b/packages/core/src/plugin/provider/alibaba.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const AlibabaPlugin = define({ id: "opencode.provider.alibaba", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/alibaba") return const mod = yield* Effect.promise(() => import("@ai-sdk/alibaba")) diff --git a/packages/core/src/plugin/provider/amazon-bedrock.ts b/packages/core/src/plugin/provider/amazon-bedrock.ts index 5bbe953f80..ecc137bbde 100644 --- a/packages/core/src/plugin/provider/amazon-bedrock.ts +++ b/packages/core/src/plugin/provider/amazon-bedrock.ts @@ -75,7 +75,8 @@ export const AmazonBedrockPlugin = define({ }) } }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (!["@ai-sdk/amazon-bedrock", "@ai-sdk/amazon-bedrock/mantle"].includes(evt.package)) return const options = { ...evt.options } @@ -108,7 +109,8 @@ export const AmazonBedrockPlugin = define({ evt.sdk = mod.createAmazonBedrock(options) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return if ( diff --git a/packages/core/src/plugin/provider/anthropic.ts b/packages/core/src/plugin/provider/anthropic.ts index 4c5938e51f..bbf0d56dfb 100644 --- a/packages/core/src/plugin/provider/anthropic.ts +++ b/packages/core/src/plugin/provider/anthropic.ts @@ -17,7 +17,8 @@ export const AnthropicPlugin = define({ }) } }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/anthropic") return const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic")) diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts index 65a40c023b..f50bbdb1ba 100644 --- a/packages/core/src/plugin/provider/azure.ts +++ b/packages/core/src/plugin/provider/azure.ts @@ -26,7 +26,8 @@ export const AzurePlugin = define({ }) } }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/azure") return if (evt.model.providerID === ProviderV2.ID.azure) { @@ -44,7 +45,8 @@ export const AzurePlugin = define({ evt.sdk = mod.createAzure(evt.options) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.azure) return evt.language = selectLanguage( @@ -75,7 +77,8 @@ export const AzureCognitiveServicesPlugin = define({ }) } }) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return evt.language = selectLanguage( diff --git a/packages/core/src/plugin/provider/cerebras.ts b/packages/core/src/plugin/provider/cerebras.ts index 42fea46550..13614d6be0 100644 --- a/packages/core/src/plugin/provider/cerebras.ts +++ b/packages/core/src/plugin/provider/cerebras.ts @@ -14,7 +14,8 @@ export const CerebrasPlugin = define({ }) } }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/cerebras") return const mod = yield* Effect.promise(() => import("@ai-sdk/cerebras")) diff --git a/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts b/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts index afab0c36df..0602549614 100644 --- a/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts +++ b/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts @@ -6,7 +6,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const CloudflareAIGatewayPlugin = define({ id: "opencode.provider.cloudflare-ai-gateway", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "ai-gateway-provider") return if (evt.options.baseURL) return diff --git a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts index 9b4558b5d1..c3aa50fbb4 100644 --- a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts +++ b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts @@ -19,7 +19,8 @@ export const CloudflareWorkersAIPlugin = define({ if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) } }) }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.model.providerID !== providerID) return if (evt.package !== "@ai-sdk/openai-compatible") return @@ -35,7 +36,8 @@ export const CloudflareWorkersAIPlugin = define({ ) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== providerID) return evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id) diff --git a/packages/core/src/plugin/provider/cohere.ts b/packages/core/src/plugin/provider/cohere.ts index 8b0831604a..9284defcb1 100644 --- a/packages/core/src/plugin/provider/cohere.ts +++ b/packages/core/src/plugin/provider/cohere.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const CoherePlugin = define({ id: "opencode.provider.cohere", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/cohere") return const mod = yield* Effect.promise(() => import("@ai-sdk/cohere")) diff --git a/packages/core/src/plugin/provider/deepinfra.ts b/packages/core/src/plugin/provider/deepinfra.ts index e8316012ad..f4ddf97859 100644 --- a/packages/core/src/plugin/provider/deepinfra.ts +++ b/packages/core/src/plugin/provider/deepinfra.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const DeepInfraPlugin = define({ id: "opencode.provider.deepinfra", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/deepinfra") return const mod = yield* Effect.promise(() => import("@ai-sdk/deepinfra")) diff --git a/packages/core/src/plugin/provider/dynamic.ts b/packages/core/src/plugin/provider/dynamic.ts index 2e51674ba0..ae20d0d020 100644 --- a/packages/core/src/plugin/provider/dynamic.ts +++ b/packages/core/src/plugin/provider/dynamic.ts @@ -7,7 +7,8 @@ export const DynamicProviderPlugin = define({ id: "opencode.provider.dynamic", effect: Effect.fn(function* (ctx) { const npm = yield* Npm.Service - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.sdk) return diff --git a/packages/core/src/plugin/provider/gateway.ts b/packages/core/src/plugin/provider/gateway.ts index 07249391a0..b67c0ef179 100644 --- a/packages/core/src/plugin/provider/gateway.ts +++ b/packages/core/src/plugin/provider/gateway.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const GatewayPlugin = define({ id: "opencode.provider.gateway", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/gateway") return const mod = yield* Effect.promise(() => import("@ai-sdk/gateway")) diff --git a/packages/core/src/plugin/provider/github-copilot.ts b/packages/core/src/plugin/provider/github-copilot.ts index ab9364260c..139e6efd2d 100644 --- a/packages/core/src/plugin/provider/github-copilot.ts +++ b/packages/core/src/plugin/provider/github-copilot.ts @@ -23,14 +23,16 @@ export const GithubCopilotPlugin = define({ model.enabled = false }) }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/github-copilot") return const mod = yield* Effect.promise(() => import("../../github-copilot/copilot-provider")) evt.sdk = mod.createOpenaiCompatible(evt.options) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) { diff --git a/packages/core/src/plugin/provider/gitlab.ts b/packages/core/src/plugin/provider/gitlab.ts index 1a8a4a1231..a413c85d7d 100644 --- a/packages/core/src/plugin/provider/gitlab.ts +++ b/packages/core/src/plugin/provider/gitlab.ts @@ -7,7 +7,8 @@ import { ProviderV2 } from "../../provider" export const GitLabPlugin = define({ id: "opencode.provider.gitlab", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "gitlab-ai-provider") return const mod = yield* Effect.promise(() => import("gitlab-ai-provider")) @@ -31,7 +32,8 @@ export const GitLabPlugin = define({ }) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.gitlab) return const featureFlags = diff --git a/packages/core/src/plugin/provider/google-vertex.ts b/packages/core/src/plugin/provider/google-vertex.ts index 2c307a8725..ffc29f4bf3 100644 --- a/packages/core/src/plugin/provider/google-vertex.ts +++ b/packages/core/src/plugin/provider/google-vertex.ts @@ -85,7 +85,8 @@ export const GoogleVertexPlugin = define({ }) } }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.model.providerID === ProviderV2.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) { evt.options.fetch = authFetch(evt.options.fetch) @@ -104,7 +105,8 @@ export const GoogleVertexPlugin = define({ }) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.googleVertex) return evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim()) @@ -135,7 +137,8 @@ export const GoogleVertexAnthropicPlugin = define({ }) } }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/google-vertex/anthropic") return const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic")) @@ -161,7 +164,8 @@ export const GoogleVertexAnthropicPlugin = define({ }) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim()) diff --git a/packages/core/src/plugin/provider/google.ts b/packages/core/src/plugin/provider/google.ts index 3d2013a523..c62d962eb5 100644 --- a/packages/core/src/plugin/provider/google.ts +++ b/packages/core/src/plugin/provider/google.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const GooglePlugin = define({ id: "opencode.provider.google", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/google") return const mod = yield* Effect.promise(() => import("@ai-sdk/google")) diff --git a/packages/core/src/plugin/provider/groq.ts b/packages/core/src/plugin/provider/groq.ts index d84ece2151..51b8e4c42f 100644 --- a/packages/core/src/plugin/provider/groq.ts +++ b/packages/core/src/plugin/provider/groq.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const GroqPlugin = define({ id: "opencode.provider.groq", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/groq") return const mod = yield* Effect.promise(() => import("@ai-sdk/groq")) diff --git a/packages/core/src/plugin/provider/mistral.ts b/packages/core/src/plugin/provider/mistral.ts index 92bc09abec..f5de664432 100644 --- a/packages/core/src/plugin/provider/mistral.ts +++ b/packages/core/src/plugin/provider/mistral.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const MistralPlugin = define({ id: "opencode.provider.mistral", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/mistral") return const mod = yield* Effect.promise(() => import("@ai-sdk/mistral")) diff --git a/packages/core/src/plugin/provider/openai-compatible.ts b/packages/core/src/plugin/provider/openai-compatible.ts index 3854bdcde2..76646b4e45 100644 --- a/packages/core/src/plugin/provider/openai-compatible.ts +++ b/packages/core/src/plugin/provider/openai-compatible.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const OpenAICompatiblePlugin = define({ id: "opencode.provider.openai-compatible", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.sdk) return if (!evt.package.includes("@ai-sdk/openai-compatible")) return diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index 72fe8ed646..5e4a1b08ef 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -210,14 +210,16 @@ export const OpenAIPlugin = define({ Effect.forkScoped({ startImmediately: true }), ) yield* refresh().pipe(Effect.forkScoped) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/openai") return const mod = yield* Effect.promise(() => import("@ai-sdk/openai")) evt.sdk = mod.createOpenAI(evt.options) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.openai) return evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id) diff --git a/packages/core/src/plugin/provider/openrouter.ts b/packages/core/src/plugin/provider/openrouter.ts index c27cc30207..f00386a94a 100644 --- a/packages/core/src/plugin/provider/openrouter.ts +++ b/packages/core/src/plugin/provider/openrouter.ts @@ -23,7 +23,8 @@ export const OpenRouterPlugin = define({ } } }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@openrouter/ai-sdk-provider") return const mod = yield* Effect.promise(() => import("@openrouter/ai-sdk-provider")) diff --git a/packages/core/src/plugin/provider/perplexity.ts b/packages/core/src/plugin/provider/perplexity.ts index 9eb5b1e246..36cafdb2d1 100644 --- a/packages/core/src/plugin/provider/perplexity.ts +++ b/packages/core/src/plugin/provider/perplexity.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const PerplexityPlugin = define({ id: "opencode.provider.perplexity", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/perplexity") return const mod = yield* Effect.promise(() => import("@ai-sdk/perplexity")) diff --git a/packages/core/src/plugin/provider/sap-ai-core.ts b/packages/core/src/plugin/provider/sap-ai-core.ts index 4a8c26e82a..559493787d 100644 --- a/packages/core/src/plugin/provider/sap-ai-core.ts +++ b/packages/core/src/plugin/provider/sap-ai-core.ts @@ -8,7 +8,8 @@ export const SapAICorePlugin = define({ id: "opencode.provider.sap-ai-core", effect: Effect.fn(function* (ctx) { const npm = yield* Npm.Service - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return const serviceKey = @@ -37,7 +38,8 @@ export const SapAICorePlugin = define({ ) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return evt.language = evt.sdk(evt.model.modelID ?? evt.model.id) diff --git a/packages/core/src/plugin/provider/snowflake-cortex.ts b/packages/core/src/plugin/provider/snowflake-cortex.ts index 2e6cc9f9b4..45c5f095ee 100644 --- a/packages/core/src/plugin/provider/snowflake-cortex.ts +++ b/packages/core/src/plugin/provider/snowflake-cortex.ts @@ -67,7 +67,8 @@ export function cortexFetch(upstream: FetchLike = fetch) { export const SnowflakeCortexPlugin = define({ id: "opencode.provider.snowflake-cortex", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("snowflake-cortex")) return const token = diff --git a/packages/core/src/plugin/provider/togetherai.ts b/packages/core/src/plugin/provider/togetherai.ts index d9454cfd24..ea43db9e4a 100644 --- a/packages/core/src/plugin/provider/togetherai.ts +++ b/packages/core/src/plugin/provider/togetherai.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const TogetherAIPlugin = define({ id: "opencode.provider.togetherai", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/togetherai") return const mod = yield* Effect.promise(() => import("@ai-sdk/togetherai")) diff --git a/packages/core/src/plugin/provider/venice.ts b/packages/core/src/plugin/provider/venice.ts index c9d5b163ae..9930ce831d 100644 --- a/packages/core/src/plugin/provider/venice.ts +++ b/packages/core/src/plugin/provider/venice.ts @@ -4,7 +4,8 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin" export const VenicePlugin = define({ id: "opencode.provider.venice", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "venice-ai-sdk-provider") return const mod = yield* Effect.promise(() => import("venice-ai-sdk-provider")) diff --git a/packages/core/src/plugin/provider/vercel.ts b/packages/core/src/plugin/provider/vercel.ts index 70a27c8f67..fc392fe5e1 100644 --- a/packages/core/src/plugin/provider/vercel.ts +++ b/packages/core/src/plugin/provider/vercel.ts @@ -14,7 +14,8 @@ export const VercelPlugin = define({ }) } }) - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/vercel") return const mod = yield* Effect.promise(() => import("@ai-sdk/vercel")) diff --git a/packages/core/src/plugin/provider/xai.ts b/packages/core/src/plugin/provider/xai.ts index fb1d33e86e..724a3d2567 100644 --- a/packages/core/src/plugin/provider/xai.ts +++ b/packages/core/src/plugin/provider/xai.ts @@ -5,14 +5,16 @@ import { ProviderV2 } from "../../provider" export const XAIPlugin = define({ id: "opencode.provider.xai", effect: Effect.fn(function* (ctx) { - yield* ctx.aisdk.sdk( + yield* ctx.aisdk.hook( + "sdk", Effect.fn(function* (evt) { if (evt.package !== "@ai-sdk/xai") return const mod = yield* Effect.promise(() => import("@ai-sdk/xai")) evt.sdk = mod.createXai(evt.options) }), ) - yield* ctx.aisdk.language( + yield* ctx.aisdk.hook( + "language", Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("xai")) return evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id) diff --git a/packages/core/src/plugin/sdk.ts b/packages/core/src/plugin/sdk.ts index da14b4a61f..3dd42a8037 100644 --- a/packages/core/src/plugin/sdk.ts +++ b/packages/core/src/plugin/sdk.ts @@ -1,6 +1,6 @@ export * as SdkPlugins from "./sdk" -import type { Plugin } from "@opencode-ai/plugin/v2/effect" +import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin" import { Context, Effect, Layer } from "effect" import { makeGlobalNode } from "../effect/app-node" import { EventV2 } from "../event" diff --git a/packages/core/src/plugin/supervisor.ts b/packages/core/src/plugin/supervisor.ts index 4c4e71be17..6bd4f6bfac 100644 --- a/packages/core/src/plugin/supervisor.ts +++ b/packages/core/src/plugin/supervisor.ts @@ -1,6 +1,6 @@ export * as PluginSupervisor from "./supervisor" -import type { Plugin } from "@opencode-ai/plugin/v2/effect" +import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin" import { Event } from "@opencode-ai/schema/config" import { Context, Effect, Fiber, Layer, Option, Schema, Semaphore, Stream } from "effect" import path from "path" diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 2d5220310c..23cbfa049d 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -50,6 +50,8 @@ import { llmClient } from "../../effect/app-node-platform" import { StepFailedError, UserInterruptedError } from "../error" import { toSessionError } from "../to-session-error" import { SessionRunnerRetry } from "./retry" +import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session" +import { PluginHooks } from "../../plugin/hooks" type StepTokens = { readonly input: number @@ -132,6 +134,7 @@ const layer = Layer.effect( const llm = yield* LLMClient.Service const agents = yield* AgentV2.Service const tools = yield* ToolRegistry.Service + const hooks = yield* PluginHooks.Service const models = yield* SessionRunnerModel.Service const store = yield* SessionStore.Service const location = yield* Location.Service @@ -252,10 +255,34 @@ const layer = Layer.effect( tools: toolMaterialization?.definitions ?? [], toolChoice: isLastStep ? "none" : undefined, }) + const availableTools = new Map(request.tools.map((tool) => [tool.name, tool])) + const requestEvent: SessionHooks["request"] = { + sessionID: session.id, + agent: agent.id, + model: resolved.ref, + system: [...request.system], + messages: [...request.messages], + tools: Object.fromEntries( + request.tools.map((tool) => [tool.name, { description: tool.description, input: { ...tool.inputSchema } }]), + ), + } + // Plugins may reshape the draft, but cannot advertise tools excluded earlier + // by permissions or registration state. + yield* hooks.trigger("session", "request", requestEvent) + const hookedRequest = LLM.updateRequest(request, { + system: requestEvent.system, + messages: requestEvent.messages, + tools: Object.entries(requestEvent.tools).flatMap(([name, tool]) => { + const registered = availableTools.get(name) + if (!registered) return [] + return [{ ...registered, description: tool.description, inputSchema: tool.input }] + }), + }) + const advertisedTools = new Set(hookedRequest.tools.map((tool) => tool.name)) // Automatic compaction completed; rebuild the request from compacted history. if ( !(yield* SessionInput.pendingCompaction(db, session.id)) && - (yield* compaction.compactIfNeeded({ sessionID: session.id, messages: context, request })) + (yield* compaction.compactIfNeeded({ sessionID: session.id, messages: context, request: hookedRequest })) ) return { _tag: "RestartAfterCompaction", step: currentStep } as const const startSnapshot = yield* snapshots.capture() @@ -276,7 +303,7 @@ const layer = Layer.effect( const publish = (event: LLMEvent, outputPaths: ReadonlyArray = [], error?: SessionError.Error) => serialized(publisher.publish(event, outputPaths, error)) let overflowFailure: ProviderErrorEvent | undefined - const providerStream = llm.stream(request).pipe( + const providerStream = llm.stream(hookedRequest).pipe( Stream.runForEach((event) => Effect.gen(function* () { if (overflowFailure || publisher.hasProviderError()) return @@ -288,11 +315,11 @@ const layer = Layer.effect( } yield* publish(event) if (event.type !== "tool-call" || event.providerExecuted) return - if (!toolMaterialization) { + if (!toolMaterialization || !advertisedTools.has(event.name)) { yield* serialized( publisher.failUnsettledTools({ type: "tool.execution", - message: "Tools are disabled after the maximum agent steps", + message: `Tool is not available for this request: ${event.name}`, }), ) return @@ -625,6 +652,7 @@ export const node = makeLocationNode({ llmClient, AgentV2.node, ToolRegistry.node, + PluginHooks.node, SessionRunnerModel.node, SessionStore.node, Location.node, diff --git a/packages/core/src/tool/apply-patch.ts b/packages/core/src/tool/apply-patch.ts index e359787f2c..a1ae7dc808 100644 --- a/packages/core/src/tool/apply-patch.ts +++ b/packages/core/src/tool/apply-patch.ts @@ -1,6 +1,6 @@ export * as ApplyPatchTool from "./apply-patch" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { ToolFailure } from "@opencode-ai/llm" import { FileDiff } from "@opencode-ai/schema/file-diff" import { createTwoFilesPatch, diffLines } from "diff" @@ -194,6 +194,19 @@ export const Plugin = { ), ) .pipe(Effect.orDie) + + yield* ctx.session.hook("request", (event) => + Effect.sync(() => { + const usePatch = + event.model.providerID.toLowerCase() === "openai" || event.model.id.toLowerCase().includes("gpt") + if (usePatch) { + delete event.tools.edit + delete event.tools.write + return + } + delete event.tools.patch + }), + ) }), } diff --git a/packages/core/src/tool/edit.ts b/packages/core/src/tool/edit.ts index 1128243b70..927cd3098d 100644 --- a/packages/core/src/tool/edit.ts +++ b/packages/core/src/tool/edit.ts @@ -6,7 +6,7 @@ */ export * as EditTool from "./edit" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { ToolFailure } from "@opencode-ai/llm" import { FileDiff } from "@opencode-ai/schema/file-diff" import { createTwoFilesPatch, diffLines } from "diff" diff --git a/packages/core/src/tool/glob.ts b/packages/core/src/tool/glob.ts index 4c0938c3d2..b047101c39 100644 --- a/packages/core/src/tool/glob.ts +++ b/packages/core/src/tool/glob.ts @@ -1,7 +1,7 @@ export * as GlobTool from "./glob" import { ToolFailure } from "@opencode-ai/llm" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { Effect, Schema } from "effect" import path from "path" import { FileSystem } from "../filesystem" diff --git a/packages/core/src/tool/grep.ts b/packages/core/src/tool/grep.ts index 64f2a3a9d6..937963f4ca 100644 --- a/packages/core/src/tool/grep.ts +++ b/packages/core/src/tool/grep.ts @@ -1,6 +1,6 @@ export * as GrepTool from "./grep" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { ToolFailure } from "@opencode-ai/llm" import { Effect, Schema } from "effect" import path from "path" diff --git a/packages/core/src/tool/question.ts b/packages/core/src/tool/question.ts index a2ac9828c6..d5217f8505 100644 --- a/packages/core/src/tool/question.ts +++ b/packages/core/src/tool/question.ts @@ -1,6 +1,6 @@ export * as QuestionTool from "./question" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { ToolFailure } from "@opencode-ai/llm" import { Effect, Schema } from "effect" import { Form } from "../form" diff --git a/packages/core/src/tool/read.ts b/packages/core/src/tool/read.ts index f6a7c474b5..001a29ba55 100644 --- a/packages/core/src/tool/read.ts +++ b/packages/core/src/tool/read.ts @@ -1,6 +1,6 @@ export * as ReadTool from "./read" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { dirname } from "path" import { ToolFailure } from "@opencode-ai/llm" import { Effect, Schema } from "effect" diff --git a/packages/core/src/tool/registry.ts b/packages/core/src/tool/registry.ts index ae5cd3d352..cef6f3c071 100644 --- a/packages/core/src/tool/registry.ts +++ b/packages/core/src/tool/registry.ts @@ -191,12 +191,8 @@ const registryLayer = Layer.effect( const registration = entries.at(-1)?.registration if (registration) registrations.set(name, registration) } - // OpenAI/GPT models use patch; every other model uses edit and write. - const usePatch = input.model.provider.toLowerCase() === "openai" || input.model.id.toLowerCase().includes("gpt") for (const [name, registration] of registrations) { - const wrongEditTool = name === "patch" ? !usePatch : (name === "edit" || name === "write") && usePatch if ( - wrongEditTool || (registration.deferred && !Flag.CODEMODE_ENABLED) || whollyDisabled(permission(registration.tool, name), input.permissions ?? []) ) diff --git a/packages/core/src/tool/shell.ts b/packages/core/src/tool/shell.ts index ca6b9f1969..d2b8bb4744 100644 --- a/packages/core/src/tool/shell.ts +++ b/packages/core/src/tool/shell.ts @@ -2,7 +2,7 @@ export * as ShellTool from "./shell" import path from "path" import { ToolFailure } from "@opencode-ai/llm" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { Effect, Schema, Scope } from "effect" import { FSUtil } from "../fs-util" import { LocationMutation } from "../location-mutation" diff --git a/packages/core/src/tool/skill.ts b/packages/core/src/tool/skill.ts index 95c68cb257..8e9133b029 100644 --- a/packages/core/src/tool/skill.ts +++ b/packages/core/src/tool/skill.ts @@ -1,6 +1,6 @@ export * as SkillTool from "./skill" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import path from "path" import { ToolFailure } from "@opencode-ai/llm" import { Effect, Schema } from "effect" diff --git a/packages/core/src/tool/subagent.ts b/packages/core/src/tool/subagent.ts index 0f781c5e6c..e6c994c216 100644 --- a/packages/core/src/tool/subagent.ts +++ b/packages/core/src/tool/subagent.ts @@ -1,10 +1,11 @@ export * as SubagentTool from "./subagent" import { ToolFailure } from "@opencode-ai/llm" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { Effect, Schema, Scope } from "effect" import { AgentV2 } from "../agent" import { PluginRuntime } from "../plugin/runtime" +import { PermissionV2 } from "../permission" import { SessionSchema } from "../session/schema" import { Tool } from "./tool" @@ -42,6 +43,7 @@ export const Plugin = { effect: Effect.fn("SubagentTool.Plugin")(function* (ctx: PluginContext) { const runtime = yield* PluginRuntime.Service const agents = yield* AgentV2.Service + const permission = yield* PermissionV2.Service const scope = yield* Scope.Scope // Concatenate the child's final completed assistant text. Distinguishes "completed with no @@ -114,6 +116,20 @@ export const Plugin = { if (agent === undefined) return yield* new ToolFailure({ message: `Unknown agent: ${input.agent}` }) if (agent.mode === "primary") return yield* new ToolFailure({ message: `Agent ${input.agent} cannot run as a subagent` }) + yield* permission + .assert({ + action: name, + resources: [agent.id], + save: [agent.id], + sessionID: context.sessionID, + agent: context.agent, + source: { + type: "tool", + messageID: context.assistantMessageID, + callID: context.toolCallID, + }, + }) + .pipe(Effect.mapError((error) => new ToolFailure({ message: `Subagent denied: ${agent.id}`, error }))) // Model selection is policy/config/session state, not an LLM-facing tool argument. const model = agent.model ?? parent.model @@ -176,5 +192,32 @@ export const Plugin = { ), ) .pipe(Effect.orDie) + + yield* ctx.session.hook("request", (event) => + Effect.gen(function* () { + const tool = event.tools[name] + if (!tool) return + const selected = yield* agents.resolve(event.agent) + if (!selected) return + const available = (yield* agents.list()) + .filter( + (agent) => + agent.mode !== "primary" && + !agent.hidden && + PermissionV2.evaluate(name, agent.id, selected.permissions).effect !== "deny", + ) + .toSorted((a, b) => a.id.localeCompare(b.id)) + if (available.length === 0) return + tool.description = [ + tool.description, + "", + "Available subagents:", + ...available.map( + (agent) => + `- ${agent.id}: ${agent.description ?? "This subagent should only be called when explicitly requested."}`, + ), + ].join("\n") + }), + ) }), } diff --git a/packages/core/src/tool/todowrite.ts b/packages/core/src/tool/todowrite.ts index 189ee846b9..96c0f10f1a 100644 --- a/packages/core/src/tool/todowrite.ts +++ b/packages/core/src/tool/todowrite.ts @@ -1,6 +1,6 @@ export * as TodoWriteTool from "./todowrite" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { ToolFailure } from "@opencode-ai/llm" import { Effect, Schema } from "effect" import { PermissionV2 } from "../permission" diff --git a/packages/core/src/tool/webfetch.ts b/packages/core/src/tool/webfetch.ts index d2efc61c73..d761645939 100644 --- a/packages/core/src/tool/webfetch.ts +++ b/packages/core/src/tool/webfetch.ts @@ -1,6 +1,6 @@ export * as WebFetchTool from "./webfetch" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { ToolFailure } from "@opencode-ai/llm" import { Duration, Effect, Schema } from "effect" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" diff --git a/packages/core/src/tool/websearch.ts b/packages/core/src/tool/websearch.ts index 4064dc0f85..9c91d84f92 100644 --- a/packages/core/src/tool/websearch.ts +++ b/packages/core/src/tool/websearch.ts @@ -1,6 +1,6 @@ export * as WebSearchTool from "./websearch" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { ToolFailure } from "@opencode-ai/llm" import { Context, Duration, Effect, Layer, Schema } from "effect" import { HttpClient, HttpClientRequest } from "effect/unstable/http" diff --git a/packages/core/src/tool/write.ts b/packages/core/src/tool/write.ts index c50a3a1e6d..a88063f4e2 100644 --- a/packages/core/src/tool/write.ts +++ b/packages/core/src/tool/write.ts @@ -6,7 +6,7 @@ */ export * as WriteTool from "./write" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { ToolFailure } from "@opencode-ai/llm" import { Effect, Schema } from "effect" import { FileMutation } from "../file-mutation" diff --git a/packages/core/test/config/fixtures/plugin/directory-plugin.ts b/packages/core/test/config/fixtures/plugin/directory-plugin.ts index e26e12bdac..f5e15c2c00 100644 --- a/packages/core/test/config/fixtures/plugin/directory-plugin.ts +++ b/packages/core/test/config/fixtures/plugin/directory-plugin.ts @@ -1,6 +1,6 @@ -import { define } from "@opencode-ai/plugin/v2/promise" +import { Plugin } from "@opencode-ai/plugin/v2" -export default define({ +export default Plugin.define({ id: "directory-plugin", setup: async (ctx) => { await ctx.agent.transform((agents) => { diff --git a/packages/core/test/config/fixtures/plugins/folder-plugin/index.ts b/packages/core/test/config/fixtures/plugins/folder-plugin/index.ts index afa7f5c37e..365d5566d2 100644 --- a/packages/core/test/config/fixtures/plugins/folder-plugin/index.ts +++ b/packages/core/test/config/fixtures/plugins/folder-plugin/index.ts @@ -1,6 +1,6 @@ -import { define } from "@opencode-ai/plugin/v2/promise" +import { Plugin } from "@opencode-ai/plugin/v2" -export default define({ +export default Plugin.define({ id: "folder-plugin", setup: async (ctx) => { await ctx.agent.transform((agents) => { diff --git a/packages/core/test/config/plugin.test.ts b/packages/core/test/config/plugin.test.ts index 34a69ffbc5..dc1fbc72e7 100644 --- a/packages/core/test/config/plugin.test.ts +++ b/packages/core/test/config/plugin.test.ts @@ -2,7 +2,7 @@ import fs from "fs/promises" import path from "path" import { pathToFileURL } from "url" import { describe, expect } from "bun:test" -import { define } from "@opencode-ai/plugin/v2/effect" +import { Plugin as EffectPlugin } from "@opencode-ai/plugin/v2/effect" import { Config as ConfigSchema } from "@opencode-ai/schema/config" import { Plugin } from "@opencode-ai/schema/plugin" import { AgentV2 } from "@opencode-ai/core/agent" @@ -178,7 +178,7 @@ describe("PluginSupervisor config", () => { it.live("loads user plugins before internal post plugins", () => Effect.gen(function* () { const sdk = yield* SdkPlugins.Service - yield* sdk.register(define({ id: "sdk-order", effect: () => Effect.void })) + yield* sdk.register(EffectPlugin.define({ id: "sdk-order", effect: () => Effect.void })) yield* withLocation( { plugins: [ @@ -275,7 +275,7 @@ function mutablePlugin(description: string) { return ` import { define } from ${JSON.stringify(plugin)} -export default define({ +export default EffectPlugin.define({ id: "mutable-plugin", setup: async (ctx) => { await ctx.agent.transform((agents) => { diff --git a/packages/core/test/lib/tool.ts b/packages/core/test/lib/tool.ts index 5677e288ef..3692e5476e 100644 --- a/packages/core/test/lib/tool.ts +++ b/packages/core/test/lib/tool.ts @@ -4,7 +4,7 @@ import { SessionMessage } from "@opencode-ai/core/session/message" import { ToolRegistry } from "@opencode-ai/core/tool/registry" import { Tool } from "@opencode-ai/core/tool/tool" import { Tools } from "@opencode-ai/core/tool/tools" -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { Effect, type Scope } from "effect" export const toolIdentity = { @@ -66,12 +66,10 @@ export const registerToolPlugin = (plugin: { registrations, (registration) => tools.register({ [registration.name]: registration.tool }, registration.options), { discard: true }, - ) + ).pipe(Effect.orDie) + return { dispose: Effect.void } }), - execute: { - before: () => Effect.die("registerToolPlugin does not support tool hooks"), - after: () => Effect.die("registerToolPlugin does not support tool hooks"), - }, + hook: () => Effect.die("registerToolPlugin does not support tool hooks"), }, } yield* plugin.effect(context as PluginContext) diff --git a/packages/core/test/location-layer.test.ts b/packages/core/test/location-layer.test.ts index f2b8d004a1..6b7ba5654c 100644 --- a/packages/core/test/location-layer.test.ts +++ b/packages/core/test/location-layer.test.ts @@ -4,7 +4,7 @@ import { describe, expect } from "bun:test" import { Config } from "@opencode-ai/schema/config" import { Plugin } from "@opencode-ai/schema/plugin" import { Context, DateTime, Effect, Equal, Hash, RcMap, Schema, Stream } from "effect" -import { define } from "@opencode-ai/plugin/v2/effect" +import { Plugin as EffectPlugin } from "@opencode-ai/plugin/v2/effect" import { AgentV2 } from "@opencode-ai/core/agent" import { Catalog } from "@opencode-ai/core/catalog" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" @@ -44,7 +44,7 @@ describe("LocationServiceMap", () => { const sdk = yield* SdkPlugins.Service const locations = yield* LocationServiceMap.Service const id = AgentV2.ID.make("persistent-sdk-agent") - const plugin = define({ + const plugin = EffectPlugin.define({ id: "persistent-sdk-plugin", effect: (ctx) => ctx.agent.transform((agents) => agents.update(id, () => {})), }) @@ -432,7 +432,7 @@ describe("LocationServiceMap", () => { Effect.flatMap((dir) => Effect.gen(function* () { const plugins = yield* PluginV2.Service - const reviewer = define({ + const reviewer = EffectPlugin.define({ id: "reviewer", effect: (ctx) => ctx.agent diff --git a/packages/core/test/plugin-hooks.test.ts b/packages/core/test/plugin-hooks.test.ts new file mode 100644 index 0000000000..8927df9a05 --- /dev/null +++ b/packages/core/test/plugin-hooks.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "bun:test" +import { Message, SystemPart } from "@opencode-ai/llm" +import { Agent } from "@opencode-ai/schema/agent" +import { Model } from "@opencode-ai/schema/model" +import { Provider } from "@opencode-ai/schema/provider" +import { Session } from "@opencode-ai/schema/session" +import { Effect, Layer } from "effect" +import { PluginHooks } from "../src/plugin/hooks" + +describe("PluginHooks", () => { + it("registers scoped domain hooks and triggers them sequentially", async () => { + const seen: string[] = [] + const program = Effect.gen(function* () { + const hooks = yield* PluginHooks.Service + yield* hooks.register("session", "request", (event) => + Effect.sync(() => { + seen.push("first") + event.system.push(SystemPart.make("second")) + }), + ) + yield* hooks.register("session", "request", (event) => + Effect.sync(() => { + seen.push(event.system[1]?.text ?? "missing") + event.messages = [Message.user("changed")] + }), + ) + const event = { + sessionID: Session.ID.make("ses_hooks"), + agent: Agent.ID.make("build"), + model: Model.Ref.make({ providerID: Provider.ID.make("test"), id: Model.ID.make("model") }), + system: [SystemPart.make("first")], + messages: [Message.user("original")], + tools: {}, + } + + expect(yield* hooks.trigger("session", "request", event)).toBe(event) + expect(seen).toEqual(["first", "second"]) + expect(event.messages).toEqual([Message.user("changed")]) + }) + + await Effect.runPromise( + Effect.scoped(program).pipe( + Effect.provide(PluginHooks.node.implementation as Layer.Layer), + ), + ) + }) +}) diff --git a/packages/core/test/plugin.test.ts b/packages/core/test/plugin.test.ts index 5d87102b7d..79fcc5d0f6 100644 --- a/packages/core/test/plugin.test.ts +++ b/packages/core/test/plugin.test.ts @@ -1,6 +1,6 @@ import { describe, expect } from "bun:test" import { Context, Effect, Exit, Fiber, Schema, Stream } from "effect" -import { define } from "@opencode-ai/plugin/v2/effect" +import { Plugin as EffectPlugin } from "@opencode-ai/plugin/v2/effect" import { Config as ConfigSchema } from "@opencode-ai/schema/config" import { Plugin } from "@opencode-ai/schema/plugin" import { AgentV2 } from "@opencode-ai/core/agent" @@ -49,7 +49,7 @@ describe("PluginV2", () => { .pipe(Stream.take(2), Stream.runCollect, Effect.forkScoped({ startImmediately: true })) const managed = () => - define({ + EffectPlugin.define({ id: "managed", effect: (ctx) => ctx.agent @@ -97,25 +97,40 @@ describe("PluginV2", () => { }), ) - it.effect("retries the same generation after materialization fails", () => + it.effect("skips failed plugins and loads the rest", () => Effect.gen(function* () { const plugins = yield* PluginV2.Service + const agents = yield* AgentV2.Service let fail = true - const plugin = define({ - id: "retry", + const good = EffectPlugin.define({ + id: "good", effect: (ctx) => ctx.agent - .transform(() => { - if (fail) throw new Error("materialization failed") - }) + .transform((agents) => + agents.update("configured", (agent) => { + agent.description = "loaded" + }), + ) .pipe(Effect.asVoid), }) + const bad = EffectPlugin.define({ + id: "bad", + effect: () => { + if (fail) return Effect.die(new Error("materialization failed")) + return Effect.void + }, + }) + + yield* plugins.activate([{ plugin: good }, { plugin: bad }]) + expect(yield* plugins.list()).toEqual([{ id: Plugin.ID.make("good") }]) + expect((yield* agents.get(AgentV2.ID.make("configured")))?.description).toBe("loaded") - expect(Exit.isFailure(yield* plugins.activate([{ plugin }]).pipe(Effect.exit))).toBe(true) fail = false - yield* plugins.activate([{ plugin }]) - - expect(yield* plugins.list()).toEqual([{ id: Plugin.ID.make("retry") }]) + yield* plugins.activate([{ plugin: good }, { plugin: bad }]) + expect(yield* plugins.list()).toEqual([ + { id: Plugin.ID.make("good") }, + { id: Plugin.ID.make("bad") }, + ]) }), ) @@ -142,7 +157,7 @@ describe("PluginV2", () => { Effect.gen(function* () { const plugins = yield* PluginV2.Service let visible = true - const plugin = define({ + const plugin = EffectPlugin.define({ id: "isolated", effect: () => Effect.serviceOption(Secret).pipe( @@ -161,7 +176,7 @@ describe("PluginV2", () => { Effect.gen(function* () { const plugins = yield* PluginV2.Service const registry = yield* ToolRegistry.Service - const plugin = define({ + const plugin = EffectPlugin.define({ id: "tool-plugin", effect: (ctx) => ctx.tool @@ -202,7 +217,7 @@ describe("PluginV2", () => { output: Schema.Struct({ ok: Schema.Boolean }), execute: () => Effect.succeed({ ok: true }), }) - const plugin = define({ + const plugin = EffectPlugin.define({ id: "grouped-tools", effect: (ctx) => ctx.tool @@ -234,7 +249,7 @@ describe("PluginV2", () => { after?: { input: unknown; result: unknown; output: unknown } } = {} - const plugin = define({ + const plugin = EffectPlugin.define({ id: "tool-hooks", effect: (ctx) => Effect.gen(function* () { @@ -252,19 +267,23 @@ describe("PluginV2", () => { ) .pipe(Effect.orDie) - yield* ctx.tool.execute - .before((event) => { - seen.before = event.input - event.input = { text: "before-mutated" } - }) + yield* ctx.tool + .hook("execute.before", (event) => + Effect.sync(() => { + seen.before = event.input + event.input = { text: "before-mutated" } + }), + ) .pipe(Effect.asVoid) - yield* ctx.tool.execute - .after((event) => { - seen.after = { input: event.input, result: event.result, output: event.output } - event.result = { type: "text", value: "after-mutated" } - event.output = { structured: { rewritten: true }, content: [] } - }) + yield* ctx.tool + .hook("execute.after", (event) => + Effect.sync(() => { + seen.after = { input: event.input, result: event.result, output: event.output } + event.result = { type: "text", value: "after-mutated" } + event.output = { structured: { rewritten: true }, content: [] } + }), + ) .pipe(Effect.asVoid) }), }) diff --git a/packages/core/test/plugin/fixture.ts b/packages/core/test/plugin/fixture.ts index ea6e65bb64..ab5c64ae3e 100644 --- a/packages/core/test/plugin/fixture.ts +++ b/packages/core/test/plugin/fixture.ts @@ -13,6 +13,7 @@ import { Integration } from "@opencode-ai/core/integration" import { Location } from "@opencode-ai/core/location" import { Npm } from "@opencode-ai/core/npm" import { PluginV2 } from "@opencode-ai/core/plugin" +import { PluginHooks } from "@opencode-ai/core/plugin/hooks" import { PluginRuntime } from "@opencode-ai/core/plugin/runtime" import { Reference } from "@opencode-ai/core/reference" import { SkillV2 } from "@opencode-ai/core/skill" diff --git a/packages/core/test/plugin/fixtures/config-effect-plugin.ts b/packages/core/test/plugin/fixtures/config-effect-plugin.ts index a5f12a113d..e607b658e3 100644 --- a/packages/core/test/plugin/fixtures/config-effect-plugin.ts +++ b/packages/core/test/plugin/fixtures/config-effect-plugin.ts @@ -1,7 +1,7 @@ -import { define } from "@opencode-ai/plugin/v2/effect" +import { Plugin } from "@opencode-ai/plugin/v2/effect" import { Effect } from "effect" -export default define({ +export default Plugin.define({ id: "config-effect-plugin", effect: (ctx) => ctx.agent diff --git a/packages/core/test/plugin/fixtures/config-promise-plugin.ts b/packages/core/test/plugin/fixtures/config-promise-plugin.ts index ed53e4b947..0fef8fbc65 100644 --- a/packages/core/test/plugin/fixtures/config-promise-plugin.ts +++ b/packages/core/test/plugin/fixtures/config-promise-plugin.ts @@ -1,6 +1,6 @@ -import { define } from "@opencode-ai/plugin/v2/promise" +import { Plugin } from "@opencode-ai/plugin/v2" -export default define({ +export default Plugin.define({ id: "config-promise-plugin", setup: async (ctx) => { await ctx.agent.transform((agents) => { diff --git a/packages/core/test/plugin/fixtures/failing-plugin.ts b/packages/core/test/plugin/fixtures/failing-plugin.ts index 4daac0acd0..dac49b410d 100644 --- a/packages/core/test/plugin/fixtures/failing-plugin.ts +++ b/packages/core/test/plugin/fixtures/failing-plugin.ts @@ -1,7 +1,7 @@ -import { define } from "@opencode-ai/plugin/v2/effect" +import { Plugin } from "@opencode-ai/plugin/v2/effect" import { Effect } from "effect" -export default define({ +export default Plugin.define({ id: "failing-plugin", effect: () => Effect.die("plugin failed"), }) diff --git a/packages/core/test/plugin/fixtures/variant-source-plugin.ts b/packages/core/test/plugin/fixtures/variant-source-plugin.ts index f799c96888..5dae699f6e 100644 --- a/packages/core/test/plugin/fixtures/variant-source-plugin.ts +++ b/packages/core/test/plugin/fixtures/variant-source-plugin.ts @@ -1,8 +1,8 @@ -import { define } from "@opencode-ai/plugin/v2/effect" +import { Plugin } from "@opencode-ai/plugin/v2/effect" import { ProviderV2 } from "@opencode-ai/core/provider" import { Effect } from "effect" -export default define({ +export default Plugin.define({ id: "variant-source", effect: (ctx) => ctx.catalog diff --git a/packages/core/test/plugin/host.ts b/packages/core/test/plugin/host.ts index 2aa8789548..c5dc96c384 100644 --- a/packages/core/test/plugin/host.ts +++ b/packages/core/test/plugin/host.ts @@ -1,4 +1,4 @@ -import type { PluginContext } from "@opencode-ai/plugin/v2/effect" +import type { Context as PluginContext } from "@opencode-ai/plugin/v2/effect/plugin" import { AgentV2 } from "@opencode-ai/core/agent" import { Catalog } from "@opencode-ai/core/catalog" import { Credential } from "@opencode-ai/core/credential" @@ -19,8 +19,7 @@ export function host(overrides: Overrides = {}): PluginContext { reload: () => Effect.die("unused agent.reload"), }, aisdk: overrides.aisdk ?? { - sdk: () => Effect.die("unused aisdk.sdk"), - language: () => Effect.die("unused aisdk.language"), + hook: () => Effect.die("unused aisdk.hook"), }, catalog: overrides.catalog ?? { provider: { @@ -45,11 +44,15 @@ export function host(overrides: Overrides = {}): PluginContext { integration: overrides.integration ?? { list: () => Effect.die("unused integration.list"), get: () => Effect.die("unused integration.get"), - connectKey: () => Effect.die("unused integration.connectKey"), - connectOauth: () => Effect.die("unused integration.connectOauth"), - attemptStatus: () => Effect.die("unused integration.attemptStatus"), - attemptComplete: () => Effect.die("unused integration.attemptComplete"), - attemptCancel: () => Effect.die("unused integration.attemptCancel"), + connect: { + key: () => Effect.die("unused integration.connect.key"), + oauth: () => Effect.die("unused integration.connect.oauth"), + }, + attempt: { + status: () => Effect.die("unused integration.attempt.status"), + complete: () => Effect.die("unused integration.attempt.complete"), + cancel: () => Effect.die("unused integration.attempt.cancel"), + }, transform: () => Effect.die("unused integration.transform"), reload: () => Effect.die("unused integration.reload"), connection: { @@ -72,10 +75,7 @@ export function host(overrides: Overrides = {}): PluginContext { }, tool: overrides.tool ?? { transform: () => Effect.die("unused tool.transform"), - execute: { - before: () => Effect.die("unused tool.execute.before"), - after: () => Effect.die("unused tool.execute.after"), - }, + hook: () => Effect.die("unused tool.hook"), }, session: overrides.session ?? { create: () => Effect.die("unused session.create"), @@ -83,6 +83,7 @@ export function host(overrides: Overrides = {}): PluginContext { prompt: () => Effect.die("unused session.prompt"), command: () => Effect.die("unused session.command"), interrupt: () => Effect.die("unused session.interrupt"), + hook: () => Effect.die("unused session.hook"), }, } } @@ -188,11 +189,15 @@ export function integrationHost(integration: Integration.Interface): PluginConte return { list: () => Effect.die("unused integration.list"), get: () => Effect.die("unused integration.get"), - connectKey: () => Effect.die("unused integration.connectKey"), - connectOauth: () => Effect.die("unused integration.connectOauth"), - attemptStatus: () => Effect.die("unused integration.attemptStatus"), - attemptComplete: () => Effect.die("unused integration.attemptComplete"), - attemptCancel: () => Effect.die("unused integration.attemptCancel"), + connect: { + key: () => Effect.die("unused integration.connect.key"), + oauth: () => Effect.die("unused integration.connect.oauth"), + }, + attempt: { + status: () => Effect.die("unused integration.attempt.status"), + complete: () => Effect.die("unused integration.attempt.complete"), + cancel: () => Effect.die("unused integration.attempt.cancel"), + }, reload: integration.reload, connection: { active: (id) => integration.connection.active(Integration.ID.make(id)), diff --git a/packages/core/test/plugin/promise.test.ts b/packages/core/test/plugin/promise.test.ts index fbe4029c3a..05a20693b5 100644 --- a/packages/core/test/plugin/promise.test.ts +++ b/packages/core/test/plugin/promise.test.ts @@ -4,7 +4,7 @@ import { AgentV2 } from "@opencode-ai/core/agent" import { PluginV2 } from "@opencode-ai/core/plugin" import { PluginHost } from "@opencode-ai/core/plugin/host" import { PluginPromise } from "@opencode-ai/core/plugin/promise" -import { define } from "@opencode-ai/plugin/v2/promise" +import { Plugin } from "@opencode-ai/plugin/v2" import { testEffect } from "../lib/effect" import { PluginTestLayer } from "./fixture" @@ -16,7 +16,7 @@ describe("fromPromise", () => { const plugin = yield* PluginV2.Service const host = yield* PluginHost.make(plugin) const seen: string[] = [] - const promisePlugin = define({ + const promisePlugin = Plugin.define({ id: "promise-client-reads", setup: async (ctx) => { const results = await Promise.all([ @@ -46,7 +46,7 @@ describe("fromPromise", () => { const plugin = yield* PluginV2.Service const host = yield* PluginHost.make(plugin) - const promisePlugin = define({ + const promisePlugin = Plugin.define({ id: "promise-example", setup: async (ctx) => { expect(ctx.options.mode).toBe("strict") @@ -75,7 +75,7 @@ describe("fromPromise", () => { const plugin = yield* PluginV2.Service const host = yield* PluginHost.make(plugin) - const promisePlugin = define({ + const promisePlugin = Plugin.define({ id: "promise-dispose", setup: async (ctx) => { const registration = await ctx.agent.transform((draft) => { diff --git a/packages/core/test/session-runner-tool-registry.test.ts b/packages/core/test/session-runner-tool-registry.test.ts index 01e417ee2f..2944f07972 100644 --- a/packages/core/test/session-runner-tool-registry.test.ts +++ b/packages/core/test/session-runner-tool-registry.test.ts @@ -82,7 +82,7 @@ describe("ToolRegistry", () => { }), ) - it.effect("selects one edit tool family for each model", () => + it.effect("materializes all permission-eligible edit tools before request policy", () => Effect.gen(function* () { const service = yield* ToolRegistry.Service yield* service.register({ @@ -96,10 +96,13 @@ describe("ToolRegistry", () => { .materialize({ model }) .pipe(Effect.map((materialized) => materialized.definitions.map((tool) => tool.name))) - expect(yield* names({ id: "gpt-5", provider: "openai" })).toEqual(["read", "patch"]) - expect(yield* names({ id: "gpt-4o", provider: "opencode" })).toEqual(["read", "patch"]) - expect(yield* names({ id: "computer-use-preview", provider: "openai" })).toEqual(["read", "patch"]) - expect(yield* names({ id: "claude-sonnet-4", provider: "anthropic" })).toEqual(["read", "edit", "write"]) + expect(yield* names({ id: "gpt-5", provider: "openai" })).toEqual(["read", "edit", "write", "patch"]) + expect(yield* names({ id: "claude-sonnet-4", provider: "anthropic" })).toEqual([ + "read", + "edit", + "write", + "patch", + ]) }), ) diff --git a/packages/docs/openapi.json b/packages/docs/openapi.json index bcad31bd32..a37a3c5bd7 100644 --- a/packages/docs/openapi.json +++ b/packages/docs/openapi.json @@ -27,10 +27,23 @@ "enum": [ true ] + }, + "version": { + "type": "string" + }, + "pid": { + "type": "integer", + "allOf": [ + { + "exclusiveMinimum": 0 + } + ] } }, "required": [ - "healthy" + "healthy", + "version", + "pid" ], "additionalProperties": false } @@ -699,14 +712,10 @@ "$ref": "#/components/schemas/SessionActive" } } - }, - "watermarks": { - "$ref": "#/components/schemas/SessionWatermarks" } }, "required": [ - "data", - "watermarks" + "data" ], "additionalProperties": false } @@ -734,7 +743,7 @@ } } }, - "description": "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive. Watermarks are the durable log positions read alongside the activity snapshot; activity itself is process state, so the pairing is advisory rather than transactional.", + "description": "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.", "summary": "List active sessions" } }, @@ -820,6 +829,72 @@ }, "description": "Retrieve a session by ID.", "summary": "Get session" + }, + "delete": { + "tags": [ + "session" + ], + "operationId": "v2.session.remove", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundError" + }, + { + "$ref": "#/components/schemas/SessionNotFoundError" + } + ] + } + } + } + } + }, + "description": "Delete a session and its child sessions.", + "summary": "Delete session" } }, "/api/session/{sessionID}/fork": { @@ -1198,6 +1273,119 @@ } } }, + "/api/session/{sessionID}/move": { + "post": { + "tags": [ + "session" + ], + "operationId": "v2.session.move", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/InvalidRequestError1" + }, + { + "$ref": "#/components/schemas/InvalidRequestError" + } + ] + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundError" + }, + { + "$ref": "#/components/schemas/SessionNotFoundError" + } + ] + } + } + } + } + }, + "description": "Move a session to another project directory, optionally transferring local changes.", + "summary": "Move session", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "destination": { + "type": "object", + "properties": { + "directory": { + "type": "string" + } + }, + "required": [ + "directory" + ], + "additionalProperties": false + }, + "moveChanges": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "destination" + ], + "additionalProperties": false + } + } + }, + "required": true + } + } + }, "/api/session/{sessionID}/prompt": { "post": { "tags": [ @@ -1245,7 +1433,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InvalidRequestError" + "anyOf": [ + { + "$ref": "#/components/schemas/InvalidRequestError1" + }, + { + "$ref": "#/components/schemas/InvalidRequestError" + } + ] } } } @@ -1397,7 +1592,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InvalidRequestError" + "anyOf": [ + { + "$ref": "#/components/schemas/InvalidRequestError1" + }, + { + "$ref": "#/components/schemas/InvalidRequestError" + } + ] } } } @@ -1759,6 +1961,16 @@ }, "metadata": { "type": "object" + }, + "resume": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] } }, "required": [ @@ -1897,8 +2109,24 @@ ], "security": [], "responses": { - "204": { - "description": "" + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SessionInput.Compaction" + } + }, + "required": [ + "data" + ], + "additionalProperties": false + } + } + } }, "400": { "description": "InvalidRequestError", @@ -1938,38 +2166,46 @@ } }, "409": { - "description": "SessionBusyError", + "description": "ConflictError", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SessionBusyError" - } - } - } - }, - "500": { - "description": "UnknownError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnknownError" - } - } - } - }, - "503": { - "description": "ServiceUnavailableError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ServiceUnavailableError" + "$ref": "#/components/schemas/ConflictError" } } } } }, - "description": "Compact a session conversation.", - "summary": "Compact session" + "description": "Queue a durable session compaction request.", + "summary": "Compact session", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "anyOf": [ + { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + } + }, + "required": true + } } }, "/api/session/{sessionID}/wait": { @@ -2452,12 +2688,12 @@ "summary": "Get session context" } }, - "/api/session/{sessionID}/context-entry": { + "/api/session/{sessionID}/instructions/entries": { "get": { "tags": [ "session" ], - "operationId": "v2.session.context.entry.list", + "operationId": "v2.session.instructions.entry.list", "parameters": [ { "name": "sessionID", @@ -2485,7 +2721,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/SessionContextEntry.Info" + "$ref": "#/components/schemas/InstructionEntry.Info" } } }, @@ -2535,16 +2771,16 @@ } } }, - "description": "List API-managed context entries attached to the session's system context.", - "summary": "List context entries" + "description": "List API-managed instruction entries attached to the session.", + "summary": "List instruction entries" } }, - "/api/session/{sessionID}/context-entry/{key}": { + "/api/session/{sessionID}/instructions/entries/{key}": { "put": { "tags": [ "session" ], - "operationId": "v2.session.context.entry.put", + "operationId": "v2.session.instructions.entry.put", "parameters": [ { "name": "sessionID", @@ -2563,7 +2799,7 @@ "name": "key", "in": "path", "schema": { - "$ref": "#/components/schemas/SessionContextEntry.Key" + "$ref": "#/components/schemas/InstructionEntry.Key" }, "required": true } @@ -2611,8 +2847,8 @@ } } }, - "description": "Attach or replace one durable context entry. The value is rendered into the session's system context; changes announce as updates at the next turn boundary.", - "summary": "Put context entry", + "description": "Attach or replace one durable instruction entry. Changes announce as updates at the next step boundary.", + "summary": "Put instruction entry", "requestBody": { "content": { "application/json": { @@ -2635,7 +2871,7 @@ "tags": [ "session" ], - "operationId": "v2.session.context.entry.remove", + "operationId": "v2.session.instructions.entry.remove", "parameters": [ { "name": "sessionID", @@ -2654,7 +2890,7 @@ "name": "key", "in": "path", "schema": { - "$ref": "#/components/schemas/SessionContextEntry.Key" + "$ref": "#/components/schemas/InstructionEntry.Key" }, "required": true } @@ -2702,11 +2938,11 @@ } } }, - "description": "Remove one context entry; the removal is announced to the model at the next turn boundary.", - "summary": "Remove context entry" + "description": "Remove one instruction entry; the removal is announced to the model at the next step boundary.", + "summary": "Remove instruction entry" } }, - "/api/session/{sessionID}/log": { + "/api/experimental/session/{sessionID}/log": { "get": { "tags": [ "session" @@ -2911,7 +3147,7 @@ } } }, - "description": "Durable, ordered, gap-free read of public session events after an exclusive aggregate sequence. Emits a synced marker once replay reaches the captured watermark, then completes; with follow=true it continues with live events instead. The only event API that promises reliability: attach after a snapshot watermark to compose fetch and stream without a race window.", + "description": "Experimental durable session event log. Reads events after an exclusive aggregate sequence and continues with live events when follow=true.", "summary": "Read the session log" } }, @@ -3156,7 +3392,7 @@ "tags": [ "session" ], - "operationId": "v2.session.messages", + "operationId": "v2.message.list", "parameters": [ { "name": "sessionID", @@ -4781,6 +5017,104 @@ "summary": "List MCP servers" } }, + "/api/mcp/resource": { + "get": { + "tags": [ + "mcp" + ], + "operationId": "v2.mcp.resource.catalog", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/Location.Info" + }, + "data": { + "$ref": "#/components/schemas/Mcp.ResourceCatalog" + } + }, + "required": [ + "location", + "data" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "Retrieve resources and resource templates from connected MCP servers.", + "summary": "List MCP resources" + } + }, "/api/credential/{credentialID}": { "patch": { "tags": [ @@ -7257,154 +7591,10 @@ } } }, - "description": "Subscribe to native event payloads for the server. Volatile by contract: a slow consumer overflows and fails the stream, and events during disconnection are missed. Consumers that need reliability should combine the changes feed with durable session log reads.", + "description": "Subscribe to native event payloads for the server. Volatile by contract: a slow consumer overflows and fails the stream, and events during disconnection are missed.", "summary": "Subscribe to events" } }, - "/api/event/changes": { - "get": { - "tags": [ - "event" - ], - "operationId": "v2.event.changes", - "parameters": [], - "security": [], - "responses": { - "200": { - "description": "Success", - "content": { - "text/event-stream": { - "schema": { - "type": "object", - "properties": { - "id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "event": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/EventLog.ChangeStream" - } - }, - "required": [ - "id", - "event", - "data" - ], - "additionalProperties": false - }, - "x-effect-stream": { - "encoding": "sse", - "causeSchema": { - "type": "array", - "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "_tag": { - "type": "string", - "enum": [ - "Fail" - ] - }, - "error": { - "not": {} - } - }, - "required": [ - "_tag", - "error" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "_tag": { - "type": "string", - "enum": [ - "Die" - ] - }, - "defect": {} - }, - "required": [ - "_tag", - "defect" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "_tag": { - "type": "string", - "enum": [ - "Interrupt" - ] - }, - "fiberId": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "_tag", - "fiberId" - ], - "additionalProperties": false - } - ] - } - }, - "errorSchema": { - "not": {} - }, - "failureEvent": "effect/httpapi/stream/failure" - } - } - } - }, - "400": { - "description": "InvalidRequestError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvalidRequestError" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnauthorizedError" - } - } - } - } - }, - "description": "Payload-free hint channel: after an event commits, a subscriber eventually receives a hint for that aggregate with seq at or beyond the event, or a sweep-required marker. Hints coalesce to the latest seq per aggregate under backpressure and the stream never fails from overflow. No consumer may derive correctness from receiving a hint; correctness always comes from durable log reads plus the consumer's own checkpoint. A sweep-required marker is emitted first on every (re)subscribe and whenever hint retention is exceeded: treat every aggregate as potentially dirty and recover via bounded sweep plus log reads.", - "summary": "Subscribe to change hints" - } - }, "/api/pty": { "get": { "tags": [ @@ -8022,7 +8212,7 @@ "tags": [ "pty" ], - "operationId": "v2.pty.connectToken", + "operationId": "v2.pty.connect.token", "parameters": [ { "name": "ptyID", @@ -8475,7 +8665,8 @@ } }, "required": [ - "command" + "command", + "timeout" ], "additionalProperties": false } @@ -8705,6 +8896,151 @@ "summary": "Remove shell command" } }, + "/api/shell/{id}/timeout": { + "patch": { + "tags": [ + "shell" + ], + "operationId": "v2.shell.timeout", + "parameters": [ + { + "name": "id", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^sh_" + } + ] + }, + "required": true + }, + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "location": { + "$ref": "#/components/schemas/Location.Info" + }, + "data": { + "$ref": "#/components/schemas/Shell" + } + }, + "required": [ + "location", + "data" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "ShellNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShellNotFoundError" + } + } + } + } + }, + "description": "Replace a running shell command's timeout from now, or clear it with zero.", + "summary": "Update shell timeout", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "timeout": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + } + }, + "required": [ + "timeout" + ], + "additionalProperties": false + } + } + }, + "required": true + } + } + }, "/api/shell/{id}/output": { "get": { "tags": [ @@ -9907,7 +10243,7 @@ "tags": [ "debug" ], - "operationId": "v2.debug.location", + "operationId": "v2.debug.location.list", "parameters": [], "security": [], "responses": { @@ -9947,6 +10283,82 @@ }, "description": "List locations currently loaded by the server.", "summary": "List loaded locations" + }, + "delete": { + "tags": [ + "debug" + ], + "operationId": "v2.debug.location.evict", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "anyOf": [ + { + "type": "object", + "properties": { + "directory": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "workspace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "Dispose the requested location's cached services so its next use boots them fresh.", + "summary": "Evict a loaded location" } } }, @@ -10329,6 +10741,31 @@ } ] }, + "fork": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "messageID": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + }, "projectID": { "type": "string" }, @@ -10421,20 +10858,6 @@ ], "additionalProperties": false }, - "SessionWatermarks": { - "type": "object", - "patternProperties": { - "^ses": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - } - }, - "description": "Durable log seq each session's snapshot was computed at. Attach a live log read after the watermark to compose fetch and stream gap-free; apply a snapshot only where its watermark is at or beyond already-applied events. Sessions without durable events are absent." - }, "SessionsResponse": { "type": "object", "properties": { @@ -10444,9 +10867,6 @@ "$ref": "#/components/schemas/SessionV2.Info" } }, - "watermarks": { - "$ref": "#/components/schemas/SessionWatermarks" - }, "cursor": { "type": "object", "properties": { @@ -10476,7 +10896,6 @@ }, "required": [ "data", - "watermarks", "cursor" ], "additionalProperties": false @@ -10604,7 +11023,7 @@ ], "additionalProperties": false }, - "Prompt.Source": { + "Prompt.Mention": { "type": "object", "properties": { "start": { @@ -10636,8 +11055,8 @@ "description": { "type": "string" }, - "source": { - "$ref": "#/components/schemas/Prompt.Source" + "mention": { + "$ref": "#/components/schemas/Prompt.Mention" } }, "required": [ @@ -10651,8 +11070,8 @@ "name": { "type": "string" }, - "source": { - "$ref": "#/components/schemas/Prompt.Source" + "mention": { + "$ref": "#/components/schemas/Prompt.Mention" } }, "required": [ @@ -10684,28 +11103,78 @@ ], "additionalProperties": false }, + "Prompt.Base64": { + "type": "string", + "allOf": [ + { + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" + } + ] + }, + "Prompt.FileSource": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "uri" + ] + }, + "uri": { + "type": "string" + } + }, + "required": [ + "type", + "uri" + ], + "additionalProperties": false + } + ] + }, "Prompt.FileAttachment": { "type": "object", "properties": { - "uri": { - "type": "string" + "data": { + "$ref": "#/components/schemas/Prompt.Base64" }, "mime": { "type": "string" }, + "source": { + "$ref": "#/components/schemas/Prompt.FileSource" + }, "name": { "type": "string" }, "description": { "type": "string" }, - "source": { - "$ref": "#/components/schemas/Prompt.Source" + "mention": { + "$ref": "#/components/schemas/Prompt.Mention" } }, "required": [ - "uri", - "mime" + "data", + "mime", + "source" ], "additionalProperties": false }, @@ -10890,26 +11359,57 @@ ], "additionalProperties": false }, - "SessionBusyError": { + "SessionInput.Compaction": { "type": "object", "properties": { - "_tag": { + "type": { "type": "string", "enum": [ - "SessionBusyError" + "compaction" + ] + }, + "admittedSeq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } ] }, "sessionID": { - "type": "string" + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] }, - "message": { - "type": "string" + "timeCreated": { + "type": "number" + }, + "handledSeq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] } }, "required": [ - "_tag", + "type", + "admittedSeq", + "id", "sessionID", - "message" + "timeCreated" ], "additionalProperties": false }, @@ -10942,6 +11442,29 @@ ], "additionalProperties": false }, + "SessionBusyError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": [ + "SessionBusyError" + ] + }, + "sessionID": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "_tag", + "sessionID", + "message" + ], + "additionalProperties": false + }, "UnknownError": { "type": "object", "properties": { @@ -11532,25 +12055,18 @@ "text" ] }, - "id": { - "type": "string" - }, "text": { "type": "string" } }, "required": [ "type", - "id", "text" ], "additionalProperties": false }, - "LLM.ProviderMetadata": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState": { + "type": "object" }, "Session.Message.Assistant.Reasoning": { "type": "object", @@ -11561,14 +12077,11 @@ "reasoning" ] }, - "id": { - "type": "string" - }, "text": { "type": "string" }, - "providerMetadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata" + "state": { + "$ref": "#/components/schemas/Session.Message.ProviderState" }, "time": { "type": "object", @@ -11588,7 +12101,6 @@ }, "required": [ "type", - "id", "text" ], "additionalProperties": false @@ -11740,14 +12252,11 @@ ], "additionalProperties": false }, - "Session.Error.Unknown": { + "Session.StructuredError": { "type": "object", "properties": { "type": { - "type": "string", - "enum": [ - "unknown" - ] + "type": "string" }, "message": { "type": "string" @@ -11781,7 +12290,7 @@ "type": "object" }, "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "$ref": "#/components/schemas/Session.StructuredError" }, "result": {} }, @@ -11809,23 +12318,14 @@ "name": { "type": "string" }, - "provider": { - "type": "object", - "properties": { - "executed": { - "type": "boolean" - }, - "metadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata" - }, - "resultMetadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata" - } - }, - "required": [ - "executed" - ], - "additionalProperties": false + "executed": { + "type": "boolean" + }, + "providerState": { + "$ref": "#/components/schemas/Session.Message.ProviderState" + }, + "providerResultState": { + "$ref": "#/components/schemas/Session.Message.ProviderState" }, "state": { "anyOf": [ @@ -11874,6 +12374,31 @@ ], "additionalProperties": false }, + "Session.Message.Assistant.Retry": { + "type": "object", + "properties": { + "attempt": { + "type": "integer", + "allOf": [ + { + "exclusiveMinimum": 0 + } + ] + }, + "at": { + "type": "number" + }, + "error": { + "$ref": "#/components/schemas/Session.StructuredError" + } + }, + "required": [ + "attempt", + "at", + "error" + ], + "additionalProperties": false + }, "Session.Message.Assistant": { "type": "object", "properties": { @@ -11950,7 +12475,15 @@ "additionalProperties": false }, "finish": { - "type": "string" + "type": "string", + "enum": [ + "stop", + "length", + "tool-calls", + "content-filter", + "error", + "unknown" + ] }, "cost": { "type": "number" @@ -11993,7 +12526,10 @@ "additionalProperties": false }, "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "$ref": "#/components/schemas/Session.StructuredError" + }, + "retry": { + "$ref": "#/components/schemas/Session.Message.Assistant.Retry" } }, "required": [ @@ -12015,6 +12551,15 @@ "compaction" ] }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "completed", + "failed" + ] + }, "reason": { "type": "string", "enum": [ @@ -12054,6 +12599,7 @@ }, "required": [ "type", + "status", "reason", "summary", "recent", @@ -12093,20 +12639,20 @@ } ] }, - "SessionContextEntry.Key": { + "InstructionEntry.Key": { "type": "string", "allOf": [ { "pattern": "^[a-z0-9][a-z0-9._-]*$", - "description": "Context entry key (lowercase alphanumerics plus . _ -)" + "description": "Instruction entry key (lowercase alphanumerics plus . _ -)" } ] }, - "SessionContextEntry.Info": { + "InstructionEntry.Info": { "type": "object", "properties": { "key": { - "$ref": "#/components/schemas/SessionContextEntry.Key" + "$ref": "#/components/schemas/InstructionEntry.Key" }, "value": {} }, @@ -12467,6 +13013,89 @@ ], "additionalProperties": false }, + "session.deleted": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.deleted" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, "session.forked": { "type": "object", "properties": { @@ -12763,7 +13392,7 @@ ], "additionalProperties": false }, - "session.context.updated": { + "session.execution.started": { "type": "object", "properties": { "id": { @@ -12783,7 +13412,352 @@ "type": { "type": "string", "enum": [ - "session.context.updated" + "session.execution.started" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.execution.succeeded": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.execution.succeeded" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.execution.failed": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.execution.failed" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "error": { + "$ref": "#/components/schemas/Session.StructuredError" + } + }, + "required": [ + "sessionID", + "error" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.execution.interrupted": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.execution.interrupted" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "reason": { + "type": "string", + "enum": [ + "user", + "shutdown", + "superseded" + ] + } + }, + "required": [ + "sessionID", + "reason" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, + "session.instructions.updated": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.instructions.updated" ] }, "durable": { @@ -13562,7 +14536,15 @@ ] }, "finish": { - "type": "string" + "type": "string", + "enum": [ + "stop", + "length", + "tool-calls", + "content-filter", + "error", + "unknown" + ] }, "cost": { "type": "number" @@ -13709,7 +14691,47 @@ ] }, "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "$ref": "#/components/schemas/Session.StructuredError" + }, + "cost": { + "type": "number" + }, + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": [ + "read", + "write" + ], + "additionalProperties": false + } + }, + "required": [ + "input", + "output", + "reasoning", + "cache" + ], + "additionalProperties": false } }, "required": [ @@ -13804,14 +14826,19 @@ } ] }, - "textID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] } }, "required": [ "sessionID", "assistantMessageID", - "textID" + "ordinal" ], "additionalProperties": false } @@ -13900,8 +14927,13 @@ } ] }, - "textID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "text": { "type": "string" @@ -13910,7 +14942,7 @@ "required": [ "sessionID", "assistantMessageID", - "textID", + "ordinal", "text" ], "additionalProperties": false @@ -13925,11 +14957,8 @@ ], "additionalProperties": false }, - "LLM.ProviderMetadata3": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState3": { + "type": "object" }, "session.reasoning.started": { "type": "object", @@ -14006,17 +15035,22 @@ } ] }, - "reasoningID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, - "providerMetadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata3" + "state": { + "$ref": "#/components/schemas/Session.Message.ProviderState3" } }, "required": [ "sessionID", "assistantMessageID", - "reasoningID" + "ordinal" ], "additionalProperties": false } @@ -14030,11 +15064,8 @@ ], "additionalProperties": false }, - "LLM.ProviderMetadata4": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState4": { + "type": "object" }, "session.reasoning.ended": { "type": "object", @@ -14111,20 +15142,25 @@ } ] }, - "reasoningID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "text": { "type": "string" }, - "providerMetadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata4" + "state": { + "$ref": "#/components/schemas/Session.Message.ProviderState4" } }, "required": [ "sessionID", "assistantMessageID", - "reasoningID", + "ordinal", "text" ], "additionalProperties": false @@ -14339,11 +15375,8 @@ ], "additionalProperties": false }, - "LLM.ProviderMetadata5": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState5": { + "type": "object" }, "session.tool.called": { "type": "object", @@ -14423,35 +15456,22 @@ "callID": { "type": "string" }, - "tool": { - "type": "string" - }, "input": { "type": "object" }, - "provider": { - "type": "object", - "properties": { - "executed": { - "type": "boolean" - }, - "metadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata5" - } - }, - "required": [ - "executed" - ], - "additionalProperties": false + "executed": { + "type": "boolean" + }, + "state": { + "$ref": "#/components/schemas/Session.Message.ProviderState5" } }, "required": [ "sessionID", "assistantMessageID", "callID", - "tool", "input", - "provider" + "executed" ], "additionalProperties": false } @@ -14572,11 +15592,8 @@ ], "additionalProperties": false }, - "LLM.ProviderMetadata6": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState6": { + "type": "object" }, "session.tool.success": { "type": "object", @@ -14672,20 +15689,11 @@ } }, "result": {}, - "provider": { - "type": "object", - "properties": { - "executed": { - "type": "boolean" - }, - "metadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata6" - } - }, - "required": [ - "executed" - ], - "additionalProperties": false + "executed": { + "type": "boolean" + }, + "resultState": { + "$ref": "#/components/schemas/Session.Message.ProviderState6" } }, "required": [ @@ -14694,7 +15702,7 @@ "callID", "structured", "content", - "provider" + "executed" ], "additionalProperties": false } @@ -14708,11 +15716,8 @@ ], "additionalProperties": false }, - "LLM.ProviderMetadata7": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "Session.Message.ProviderState7": { + "type": "object" }, "session.tool.failed": { "type": "object", @@ -14793,23 +15798,14 @@ "type": "string" }, "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "$ref": "#/components/schemas/Session.StructuredError" }, "result": {}, - "provider": { - "type": "object", - "properties": { - "executed": { - "type": "boolean" - }, - "metadata": { - "$ref": "#/components/schemas/LLM.ProviderMetadata7" - } - }, - "required": [ - "executed" - ], - "additionalProperties": false + "executed": { + "type": "boolean" + }, + "resultState": { + "$ref": "#/components/schemas/Session.Message.ProviderState7" } }, "required": [ @@ -14817,7 +15813,7 @@ "assistantMessageID", "callID", "error", - "provider" + "executed" ], "additionalProperties": false } @@ -14831,41 +15827,7 @@ ], "additionalProperties": false }, - "session.retry.error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - }, - "isRetryable": { - "type": "boolean" - }, - "responseHeaders": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "responseBody": { - "type": "string" - }, - "metadata": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "required": [ - "message", - "isRetryable" - ], - "additionalProperties": false - }, - "session.retried": { + "session.retry.scheduled": { "type": "object", "properties": { "id": { @@ -14885,7 +15847,7 @@ "type": { "type": "string", "enum": [ - "session.retried" + "session.retry.scheduled" ] }, "durable": { @@ -14932,16 +15894,39 @@ } ] }, + "assistantMessageID": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + }, "attempt": { - "type": "number" + "type": "integer", + "allOf": [ + { + "exclusiveMinimum": 0 + } + ] + }, + "at": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "error": { - "$ref": "#/components/schemas/session.retry.error" + "$ref": "#/components/schemas/Session.StructuredError" } }, "required": [ "sessionID", + "assistantMessageID", "attempt", + "at", "error" ], "additionalProperties": false @@ -14956,6 +15941,98 @@ ], "additionalProperties": false }, + "session.compaction.admitted": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.compaction.admitted" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "inputID": { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + } + }, + "required": [ + "sessionID", + "inputID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, "session.compaction.started": { "type": "object", "properties": { @@ -15146,6 +16223,89 @@ ], "additionalProperties": false }, + "session.compaction.failed": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "session.compaction.failed" + ] + }, + "durable": { + "type": "object", + "properties": { + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] + }, + "version": { + "type": "integer", + "allOf": [ + { + "minimum": 1 + } + ] + } + }, + "required": [ + "aggregateID", + "seq", + "version" + ], + "additionalProperties": false + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + } + }, + "required": [ + "sessionID" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "durable", + "data" + ], + "additionalProperties": false + }, "session.revert.staged": { "type": "object", "properties": { @@ -15383,7 +16543,7 @@ } ] }, - "messageID": { + "to": { "type": "string", "allOf": [ { @@ -15394,7 +16554,7 @@ }, "required": [ "sessionID", - "messageID" + "to" ], "additionalProperties": false } @@ -15422,6 +16582,9 @@ { "$ref": "#/components/schemas/session.renamed" }, + { + "$ref": "#/components/schemas/session.deleted" + }, { "$ref": "#/components/schemas/session.forked" }, @@ -15432,7 +16595,19 @@ "$ref": "#/components/schemas/session.prompt.admitted" }, { - "$ref": "#/components/schemas/session.context.updated" + "$ref": "#/components/schemas/session.execution.started" + }, + { + "$ref": "#/components/schemas/session.execution.succeeded" + }, + { + "$ref": "#/components/schemas/session.execution.failed" + }, + { + "$ref": "#/components/schemas/session.execution.interrupted" + }, + { + "$ref": "#/components/schemas/session.instructions.updated" }, { "$ref": "#/components/schemas/session.synthetic" @@ -15486,7 +16661,10 @@ "$ref": "#/components/schemas/session.tool.failed" }, { - "$ref": "#/components/schemas/session.retried" + "$ref": "#/components/schemas/session.retry.scheduled" + }, + { + "$ref": "#/components/schemas/session.compaction.admitted" }, { "$ref": "#/components/schemas/session.compaction.started" @@ -15494,6 +16672,9 @@ { "$ref": "#/components/schemas/session.compaction.ended" }, + { + "$ref": "#/components/schemas/session.compaction.failed" + }, { "$ref": "#/components/schemas/session.revert.staged" }, @@ -15559,14 +16740,6 @@ "$ref": "#/components/schemas/Session.Message" } }, - "watermark": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - }, "cursor": { "type": "object", "properties": { @@ -15600,65 +16773,6 @@ ], "additionalProperties": false }, - "Model.Api": { - "anyOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "aisdk" - ] - }, - "package": { - "type": "string" - }, - "url": { - "type": "string" - }, - "settings": { - "type": "object" - } - }, - "required": [ - "id", - "type", - "package" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "native" - ] - }, - "url": { - "type": "string" - }, - "settings": { - "type": "object" - } - }, - "required": [ - "id", - "type", - "settings" - ], - "additionalProperties": false - } - ] - }, "Model.Capabilities": { "type": "object", "properties": { @@ -15685,6 +16799,30 @@ ], "additionalProperties": false }, + "Model.Variant": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, "Model.Cost": { "type": "object", "properties": { @@ -15743,6 +16881,9 @@ "id": { "type": "string" }, + "modelID": { + "type": "string" + }, "providerID": { "type": "string" }, @@ -15752,66 +16893,28 @@ "name": { "type": "string" }, - "api": { - "$ref": "#/components/schemas/Model.Api" + "package": { + "type": "string" + }, + "settings": { + "type": "object" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" }, "capabilities": { "$ref": "#/components/schemas/Model.Capabilities" }, - "request": { - "type": "object", - "properties": { - "settings": { - "$ref": "#/components/schemas/Provider.Settings" - }, - "headers": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "body": { - "type": "object" - }, - "variant": { - "type": "string" - } - }, - "required": [ - "settings", - "headers", - "body" - ], - "additionalProperties": false - }, "variants": { "type": "array", "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "settings": { - "$ref": "#/components/schemas/Provider.Settings" - }, - "headers": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "body": { - "type": "object" - } - }, - "required": [ - "id", - "settings", - "headers", - "body" - ], - "additionalProperties": false + "$ref": "#/components/schemas/Model.Variant" } }, "time": { @@ -15866,11 +16969,10 @@ }, "required": [ "id", + "modelID", "providerID", "name", - "api", "capabilities", - "request", "variants", "time", "cost", @@ -15901,63 +17003,6 @@ ], "additionalProperties": false }, - "Provider.AISDK": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "aisdk" - ] - }, - "package": { - "type": "string" - }, - "url": { - "type": "string" - }, - "settings": { - "type": "object" - } - }, - "required": [ - "type", - "package" - ], - "additionalProperties": false - }, - "Provider.Native": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "native" - ] - }, - "url": { - "type": "string" - }, - "settings": { - "type": "object" - } - }, - "required": [ - "type", - "settings" - ], - "additionalProperties": false - }, - "Provider.Api": { - "anyOf": [ - { - "$ref": "#/components/schemas/Provider.AISDK" - }, - { - "$ref": "#/components/schemas/Provider.Native" - } - ] - }, "ProviderV2.Info": { "type": "object", "properties": { @@ -15973,18 +17018,26 @@ "disabled": { "type": "boolean" }, - "api": { - "$ref": "#/components/schemas/Provider.Api" + "package": { + "type": "string" }, - "request": { - "$ref": "#/components/schemas/Provider.Request" + "settings": { + "type": "object" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "body": { + "type": "object" } }, "required": [ "id", "name", - "api", - "request" + "package" ], "additionalProperties": false }, @@ -16941,6 +17994,80 @@ ], "additionalProperties": false }, + "Mcp.Resource": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "name": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mimeType": { + "type": "string" + } + }, + "required": [ + "server", + "name", + "uri" + ], + "additionalProperties": false + }, + "Mcp.ResourceTemplate": { + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "name": { + "type": "string" + }, + "uriTemplate": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mimeType": { + "type": "string" + } + }, + "required": [ + "server", + "name", + "uriTemplate" + ], + "additionalProperties": false + }, + "Mcp.ResourceCatalog": { + "type": "object", + "properties": { + "resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Mcp.Resource" + } + }, + "templates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Mcp.ResourceTemplate" + } + } + }, + "required": [ + "resources", + "templates" + ], + "additionalProperties": false + }, "Project.Vcs": { "type": "string", "enum": [ @@ -18911,7 +20038,7 @@ ], "additionalProperties": false }, - "session.deleted": { + "session.deleted1": { "type": "object", "properties": { "id": { @@ -21520,7 +22647,7 @@ ], "additionalProperties": false }, - "session.execution.settled": { + "session.usage.updated": { "type": "object", "properties": { "id": { @@ -21540,7 +22667,7 @@ "type": { "type": "string", "enum": [ - "session.execution.settled" + "session.usage.updated" ] }, "location": { @@ -21557,21 +22684,51 @@ } ] }, - "outcome": { - "type": "string", - "enum": [ - "success", - "failure", - "interrupted" - ] + "cost": { + "type": "number" }, - "error": { - "$ref": "#/components/schemas/Session.Error.Unknown" + "tokens": { + "type": "object", + "properties": { + "input": { + "type": "number" + }, + "output": { + "type": "number" + }, + "reasoning": { + "type": "number" + }, + "cache": { + "type": "object", + "properties": { + "read": { + "type": "number" + }, + "write": { + "type": "number" + } + }, + "required": [ + "read", + "write" + ], + "additionalProperties": false + } + }, + "required": [ + "input", + "output", + "reasoning", + "cache" + ], + "additionalProperties": false } }, "required": [ "sessionID", - "outcome" + "cost", + "tokens" ], "additionalProperties": false } @@ -21629,8 +22786,13 @@ } ] }, - "textID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "delta": { "type": "string" @@ -21639,7 +22801,7 @@ "required": [ "sessionID", "assistantMessageID", - "textID", + "ordinal", "delta" ], "additionalProperties": false @@ -21698,8 +22860,13 @@ } ] }, - "reasoningID": { - "type": "string" + "ordinal": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } + ] }, "delta": { "type": "string" @@ -21708,7 +22875,7 @@ "required": [ "sessionID", "assistantMessageID", - "reasoningID", + "ordinal", "delta" ], "additionalProperties": false @@ -24592,6 +25759,53 @@ ], "additionalProperties": false }, + "mcp.resources.changed": { + "type": "object", + "properties": { + "id": { + "type": "string", + "allOf": [ + { + "pattern": "^evt_" + } + ] + }, + "created": { + "type": "number" + }, + "metadata": { + "type": "object" + }, + "type": { + "type": "string", + "enum": [ + "mcp.resources.changed" + ] + }, + "location": { + "$ref": "#/components/schemas/Location.Ref" + }, + "data": { + "type": "object", + "properties": { + "server": { + "type": "string" + } + }, + "required": [ + "server" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "created", + "type", + "data" + ], + "additionalProperties": false + }, "permission.asked": { "type": "object", "properties": { @@ -25239,7 +26453,7 @@ "$ref": "#/components/schemas/session.updated" }, { - "$ref": "#/components/schemas/session.deleted" + "$ref": "#/components/schemas/session.deleted1" }, { "$ref": "#/components/schemas/message.updated" @@ -25265,6 +26479,12 @@ { "$ref": "#/components/schemas/session.renamed" }, + { + "$ref": "#/components/schemas/session.usage.updated" + }, + { + "$ref": "#/components/schemas/session.deleted" + }, { "$ref": "#/components/schemas/session.forked" }, @@ -25275,10 +26495,19 @@ "$ref": "#/components/schemas/session.prompt.admitted" }, { - "$ref": "#/components/schemas/session.execution.settled" + "$ref": "#/components/schemas/session.execution.started" }, { - "$ref": "#/components/schemas/session.context.updated" + "$ref": "#/components/schemas/session.execution.succeeded" + }, + { + "$ref": "#/components/schemas/session.execution.failed" + }, + { + "$ref": "#/components/schemas/session.execution.interrupted" + }, + { + "$ref": "#/components/schemas/session.instructions.updated" }, { "$ref": "#/components/schemas/session.synthetic" @@ -25341,7 +26570,10 @@ "$ref": "#/components/schemas/session.tool.failed" }, { - "$ref": "#/components/schemas/session.retried" + "$ref": "#/components/schemas/session.retry.scheduled" + }, + { + "$ref": "#/components/schemas/session.compaction.admitted" }, { "$ref": "#/components/schemas/session.compaction.started" @@ -25352,6 +26584,9 @@ { "$ref": "#/components/schemas/session.compaction.ended" }, + { + "$ref": "#/components/schemas/session.compaction.failed" + }, { "$ref": "#/components/schemas/session.revert.staged" }, @@ -25463,6 +26698,9 @@ { "$ref": "#/components/schemas/mcp.status.changed" }, + { + "$ref": "#/components/schemas/mcp.resources.changed" + }, { "$ref": "#/components/schemas/permission.asked" }, @@ -25493,68 +26731,6 @@ }, "contentMediaType": "application/json" }, - "EventLog.Hint": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "log.hint" - ] - }, - "aggregateID": { - "type": "string" - }, - "seq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] - } - }, - "required": [ - "type", - "aggregateID", - "seq" - ], - "additionalProperties": false, - "description": "Payload-free change hint: the aggregate's durable log advanced to at least seq. Hints coalesce under backpressure (latest per aggregate) and are never a delivery guarantee." - }, - "EventLog.SweepRequired": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "log.sweep_required" - ] - } - }, - "required": [ - "type" - ], - "additionalProperties": false, - "description": "Hints may have been lost; treat every aggregate as potentially dirty and recover via bounded sweep plus durable log reads. Emitted first on every (re)subscribe." - }, - "EventLog.Change": { - "anyOf": [ - { - "$ref": "#/components/schemas/EventLog.Hint" - }, - { - "$ref": "#/components/schemas/EventLog.SweepRequired" - } - ] - }, - "EventLog.ChangeStream": { - "type": "string", - "contentSchema": { - "$ref": "#/components/schemas/EventLog.Change" - }, - "contentMediaType": "application/json" - }, "PtyNotFoundError": { "type": "object", "properties": { @@ -25946,7 +27122,7 @@ }, { "name": "mcp", - "description": "MCP server status routes." + "description": "MCP server and resource routes." }, { "name": "credential" diff --git a/packages/httpapi-codegen/src/index.ts b/packages/httpapi-codegen/src/index.ts index f6ad6b02d7..30822e57bb 100644 --- a/packages/httpapi-codegen/src/index.ts +++ b/packages/httpapi-codegen/src/index.ts @@ -1,7 +1,7 @@ import { isAbsolute, join } from "node:path" -import { Effect, FileSystem, PlatformError, Schema, SchemaAST, SchemaRepresentation } from "effect" +import { Context, Effect, FileSystem, PlatformError, Schema, SchemaAST, SchemaRepresentation } from "effect" import { HttpMethod, type HttpRouter } from "effect/unstable/http" -import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" +import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" import { format } from "prettier" export type InputField = { @@ -35,8 +35,6 @@ export type Contract = { readonly groups: ReadonlyArray } -export type EndpointName = string | readonly [string, ...Array] - export class GenerationError extends Schema.TaggedErrorClass()("GenerationError", { reason: Schema.String, }) { @@ -89,7 +87,6 @@ export function compile( api: HttpApi.HttpApi, options?: { readonly groupNames?: Readonly> - readonly endpointNames?: Readonly> readonly omitEndpoints?: ReadonlySet }, ): Contract { @@ -151,8 +148,11 @@ export function compile( for (const [path, schema] of schemaPaths) assertPortable(schema, path, portable) } - const clientPath = normalizeClientPath( - options?.endpointNames?.[endpoint.name] ?? clientEndpointName(endpoint.name), + const clientPath = clientEndpointPath( + group.identifier, + Context.getOrElse(endpoint.annotations, OpenApi.Identifier, () => + group.topLevel ? endpoint.name : `${group.identifier}.${endpoint.name}`, + ), ) endpoints.push({ group: groupName, @@ -699,21 +699,17 @@ function clientOperationKey(group: Group, endpoint: Endpoint) { return [group.identifier, ...endpoint.clientPath].join(".") } -function clientEndpointName(name: string) { - return name.slice(name.lastIndexOf(".") + 1) -} - -function normalizeClientPath(path: EndpointName) { - const result = typeof path === "string" ? [path] : [...path] +function clientEndpointPath(group: string, name: string) { + const parts = name.split(".") + if (parts[0] === "v2") parts.shift() + const index = parts.lastIndexOf(group.slice(group.lastIndexOf(".") + 1)) + const result = index < 0 ? parts : parts.slice(index + 1) if (result.length === 0 || result.some((part) => part.length === 0)) { throw new GenerationError({ reason: "Client endpoint path must contain non-empty names" }) } if (result.some((part) => part === "__proto__")) { throw new GenerationError({ reason: "Client endpoint path cannot contain __proto__" }) } - if (typeof path !== "string" && result.some((part) => part.includes("."))) { - throw new GenerationError({ reason: "Nested client endpoint path segments cannot contain dots" }) - } return result as [string, ...Array] } diff --git a/packages/httpapi-codegen/test/generate.test.ts b/packages/httpapi-codegen/test/generate.test.ts index f6a55e73a0..f949f70d46 100644 --- a/packages/httpapi-codegen/test/generate.test.ts +++ b/packages/httpapi-codegen/test/generate.test.ts @@ -3,7 +3,7 @@ import { mkdtemp, rm } from "node:fs/promises" import { tmpdir } from "node:os" import { join } from "node:path" import { Effect, FileSystem, Schema, SchemaAST, SchemaGetter } from "effect" -import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema } from "effect/unstable/httpapi" +import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" import { format } from "prettier" import { compile as compileContract, @@ -139,48 +139,50 @@ describe("HttpApiCodegen.generate", () => { expect(contract.groups[0]?.endpoints[0]?.operation).toMatchObject({ group: "sessions", name: "get" }) }) - test("supports explicit public endpoint names", () => { + test("derives nested paths from OpenAPI operation IDs", () => { const source = HttpApi.make("test").add( - HttpApiGroup.make("server.permission") - .add(HttpApiEndpoint.get("permission.request.list", "/request", { success: Schema.String })) - .add(HttpApiEndpoint.get("session.permission.list", "/session", { success: Schema.String })), + HttpApiGroup.make("server.session").add( + HttpApiEndpoint.get("internal.stage", "/session/revert/stage", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "v2.session.revert.stage" }), + ), + ), ) - const contract = compileContract(source, { - endpointNames: { "permission.request.list": "listRequests" }, - }) + const contract = compileContract(source, { groupNames: { "server.session": "session" } }) - expect(contract.groups[0]?.endpoints.map((endpoint) => endpoint.operation.name)).toEqual(["listRequests", "list"]) + expect(contract.groups[0]?.endpoints[0]?.clientPath).toEqual(["revert", "stage"]) + expect(OpenApi.fromApi(source).paths["/session/revert/stage"]?.get?.operationId).toBe("v2.session.revert.stage") }) - test("supports explicit nested endpoint paths while string aliases remain flat", () => { + test("uses nested OpenAPI operation IDs across emitters", () => { const source = HttpApi.make("test").add( HttpApiGroup.make("server.session") - .add(HttpApiEndpoint.get("session.instructions.list", "/session/instructions", { success: Schema.String })) - .add(HttpApiEndpoint.put("session.instructions.put", "/session/instructions", { success: Schema.String })) - .add(HttpApiEndpoint.delete("session.instructions.remove", "/session/instructions", { success: Schema.String })) - .add(HttpApiEndpoint.get("session.messages", "/session/message", { success: Schema.String })), + .add( + HttpApiEndpoint.get("list", "/session/instructions", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "v2.session.instructions.list" }), + ), + ) + .add( + HttpApiEndpoint.put("put", "/session/instructions", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "v2.session.instructions.put" }), + ), + ) + .add( + HttpApiEndpoint.delete("remove", "/session/instructions", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "v2.session.instructions.remove" }), + ), + ), ) - const contract = compileContract(source, { - groupNames: { "server.session": "session" }, - endpointNames: { - "session.instructions.list": ["instructions", "list"], - "session.instructions.put": ["instructions", "put"], - "session.instructions.remove": ["instructions", "remove"], - "session.messages": "instructions.flat", - }, - }) + const contract = compileContract(source, { groupNames: { "server.session": "session" } }) expect(contract.groups[0]?.endpoints.map((endpoint) => endpoint.clientPath)).toEqual([ ["instructions", "list"], ["instructions", "put"], ["instructions", "remove"], - ["instructions.flat"], ]) expect(contract.groups[0]?.endpoints.map((endpoint) => endpoint.operation.name)).toEqual([ "instructions.list", "instructions.put", "instructions.remove", - "instructions.flat", ]) const promise = emitPromise(contract, { @@ -196,7 +198,6 @@ describe("HttpApiCodegen.generate", () => { expect(promiseClient).toContain('"session": { "instructions": { "list": (requestOptions?: RequestOptions)') expect(promiseClient).toContain('"put": (requestOptions?: RequestOptions)') expect(promiseClient).toContain('"remove": (requestOptions?: RequestOptions)') - expect(promiseClient).toContain('"instructions.flat": (requestOptions?: RequestOptions)') expect(promiseTypes).toContain('import type { InstructionListWire } from "./instruction-list-wire"') expect(promiseTypes).toContain("export type SessionInstructionsListOutput = InstructionListWire") expect(promiseTypes).toContain("export type SessionInstructionsPutOutput = string") @@ -204,12 +205,12 @@ describe("HttpApiCodegen.generate", () => { const effect = emitEffect(contract) expect(effect.files.find((file) => file.path === "session.ts")?.content).toContain( - '"instructions": { "list": Endpoint0(raw), "put": Endpoint1(raw), "remove": Endpoint2(raw) }, "instructions.flat": Endpoint3(raw)', + '"instructions": { "list": Endpoint0(raw), "put": Endpoint1(raw), "remove": Endpoint2(raw) }', ) const imported = emitEffectImported(contract, { module: "@example/api", api: "Api" }) expect(imported.files.find((file) => file.path === "client.ts")?.content).toContain( - '"instructions": { "list": Endpoint0_0(raw), "put": Endpoint0_1(raw), "remove": Endpoint0_2(raw) }, "instructions.flat": Endpoint0_3(raw)', + '"instructions": { "list": Endpoint0_0(raw), "put": Endpoint0_1(raw), "remove": Endpoint0_2(raw) }', ) const shape = emitEffectShape(contract, { module: "@example/api", api: "Api" }) @@ -217,25 +218,28 @@ describe("HttpApiCodegen.generate", () => { expect(apiShape).toContain('readonly "instructions": { readonly "list": SessionInstructionsListOperation') expect(apiShape).toContain('readonly "put": SessionInstructionsPutOperation') expect(apiShape).toContain('readonly "remove": SessionInstructionsRemoveOperation') - expect(apiShape).toContain('readonly "instructions.flat": SessionInstructionsFlatOperation') }) - test("executes nested Promise endpoint aliases", async () => { + test("executes nested Promise operation IDs", async () => { const source = HttpApi.make("test").add( HttpApiGroup.make("session") - .add(HttpApiEndpoint.get("instructions.list", "/session/instructions", { success: Schema.String })) - .add(HttpApiEndpoint.put("instructions.put", "/session/instructions", { success: Schema.String })) - .add(HttpApiEndpoint.delete("instructions.remove", "/session/instructions", { success: Schema.String })), - ) - const output = emitPromise( - compileContract(source, { - endpointNames: { - "instructions.list": ["instructions", "list"], - "instructions.put": ["instructions", "put"], - "instructions.remove": ["instructions", "remove"], - }, - }), + .add( + HttpApiEndpoint.get("list", "/session/instructions", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "session.instructions.list" }), + ), + ) + .add( + HttpApiEndpoint.put("put", "/session/instructions", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "session.instructions.put" }), + ), + ) + .add( + HttpApiEndpoint.delete("remove", "/session/instructions", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "session.instructions.remove" }), + ), + ), ) + const output = emitPromise(compileContract(source)) const directory = await mkdtemp(join(tmpdir(), "opencode-httpapi-codegen-")) const methods: Array = [] @@ -262,59 +266,67 @@ describe("HttpApiCodegen.generate", () => { test("rejects duplicate and leaf-namespace endpoint paths", () => { const source = HttpApi.make("test").add( HttpApiGroup.make("session") - .add(HttpApiEndpoint.get("first", "/first", { success: Schema.String })) - .add(HttpApiEndpoint.get("second", "/second", { success: Schema.String })), + .add( + HttpApiEndpoint.get("first", "/first", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "session.instructions.list" }), + ), + ) + .add( + HttpApiEndpoint.get("second", "/second", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "session.instructions.list" }), + ), + ), ) - expect(() => - compileContract(source, { endpointNames: { first: ["instructions", "list"], second: ["instructions", "list"] } }), - ).toThrow("Client endpoint name collision: session.instructions.list") - expect(() => - compileContract(source, { endpointNames: { first: "instructions", second: ["instructions", "list"] } }), - ).toThrow("Client endpoint name collision: session.instructions.list") + expect(() => compileContract(source)).toThrow("Client endpoint name collision: session.instructions.list") }) test("rejects nested root collisions across top-level groups", () => { const source = HttpApi.make("test") .add( HttpApiGroup.make("first", { topLevel: true }).add( - HttpApiEndpoint.get("first.list", "/first", { success: Schema.String }), + HttpApiEndpoint.get("first.list", "/first", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "instructions.list" }), + ), ), ) .add( HttpApiGroup.make("second", { topLevel: true }).add( - HttpApiEndpoint.get("second.put", "/second", { success: Schema.String }), + HttpApiEndpoint.get("second.put", "/second", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "instructions.put" }), + ), ), ) - expect(() => - compileContract(source, { - endpointNames: { "first.list": ["instructions", "list"], "second.put": ["instructions", "put"] }, - }), - ).toThrow("Client name collision: instructions") + expect(() => compileContract(source)).toThrow("Client name collision: instructions") }) test("rejects nested paths that collide after type-name normalization", () => { const source = HttpApi.make("test").add( HttpApiGroup.make("session") - .add(HttpApiEndpoint.get("first", "/first", { success: Schema.String })) - .add(HttpApiEndpoint.get("second", "/second", { success: Schema.String })), + .add( + HttpApiEndpoint.get("first", "/first", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "session.foo.bar" }), + ), + ) + .add( + HttpApiEndpoint.get("second", "/second", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "session.foo-bar" }), + ), + ), ) - expect(() => compileContract(source, { endpointNames: { first: ["foo", "bar"], second: "foo-bar" } })).toThrow( - "Client endpoint type collision: SessionFooBar", - ) + expect(() => compileContract(source)).toThrow("Client endpoint type collision: SessionFooBar") }) test("rejects ambiguous and prototype-mutating nested path segments", () => { - const source = api(HttpApiEndpoint.get("get", "/session", { success: Schema.String })) + const source = api( + HttpApiEndpoint.get("get", "/session", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "session.__proto__.get" }), + ), + ) - expect(() => compileContract(source, { endpointNames: { get: ["a.b", "get"] } })).toThrow( - "Nested client endpoint path segments cannot contain dots", - ) - expect(() => compileContract(source, { endpointNames: { get: ["__proto__", "get"] } })).toThrow( - "Client endpoint path cannot contain __proto__", - ) + expect(() => compileContract(source)).toThrow("Client endpoint path cannot contain __proto__") }) test("rejects normalized group, operation-key, and group prototype collisions", () => { @@ -324,20 +336,38 @@ describe("HttpApiCodegen.generate", () => { expect(() => compileContract(normalized)).toThrow("Client group type collision: FooBar") const endpointType = HttpApi.make("test") - .add(HttpApiGroup.make("foo").add(HttpApiEndpoint.get("first", "/first", { success: Schema.String }))) - .add(HttpApiGroup.make("fooBar").add(HttpApiEndpoint.get("second", "/second", { success: Schema.String }))) - expect(() => - compileContract(endpointType, { - endpointNames: { first: ["bar", "baz"], second: ["baz"] }, - }), - ).toThrow("Client endpoint type collision: FooBarBaz") + .add( + HttpApiGroup.make("foo").add( + HttpApiEndpoint.get("first", "/first", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "foo.bar.baz" }), + ), + ), + ) + .add( + HttpApiGroup.make("fooBar").add( + HttpApiEndpoint.get("second", "/second", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "fooBar.baz" }), + ), + ), + ) + expect(() => compileContract(endpointType)).toThrow("Client endpoint type collision: FooBarBaz") const operationKey = HttpApi.make("test") - .add(HttpApiGroup.make("a.b").add(HttpApiEndpoint.get("get", "/first", { success: Schema.String }))) - .add(HttpApiGroup.make("a").add(HttpApiEndpoint.get("b.c", "/second", { success: Schema.String }))) - expect(() => compileContract(operationKey, { endpointNames: { get: "c", "b.c": ["b", "c"] } })).toThrow( - "Client operation key collision: a.b.c", - ) + .add( + HttpApiGroup.make("a.b").add( + HttpApiEndpoint.get("get", "/first", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "a.b.c" }), + ), + ), + ) + .add( + HttpApiGroup.make("a").add( + HttpApiEndpoint.get("b.c", "/second", { success: Schema.String }).annotateMerge( + OpenApi.annotations({ identifier: "a.b.c" }), + ), + ), + ) + expect(() => compileContract(operationKey)).toThrow("Client operation key collision: a.b.c") const prototype = HttpApi.make("test").add( HttpApiGroup.make("session").add(HttpApiEndpoint.get("get", "/session", { success: Schema.String })), diff --git a/packages/plugin/package.json b/packages/plugin/package.json index d93db3e5b0..e590557044 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -14,10 +14,9 @@ "./tool": "./src/tool.ts", "./tui": "./src/tui.ts", "./v2/effect": "./src/v2/effect/index.ts", - "./v2/effect/integration": "./src/v2/effect/integration.ts", - "./v2/effect/plugin": "./src/v2/effect/plugin.ts", - "./v2/effect/tool": "./src/v2/effect/tool.ts", - "./v2/promise": "./src/v2/promise/index.ts" + "./v2/effect/*": "./src/v2/effect/*.ts", + "./v2": "./src/v2/promise/index.ts", + "./v2/*": "./src/v2/promise/*.ts" }, "files": [ "dist" diff --git a/packages/plugin/src/v2/effect/PLAN.md b/packages/plugin/src/v2/effect/PLAN.md index 71fa07bd7b..284d1ffe09 100644 --- a/packages/plugin/src/v2/effect/PLAN.md +++ b/packages/plugin/src/v2/effect/PLAN.md @@ -176,7 +176,7 @@ Both use the same low-level scoped registration registry, but consumers invoke t ```ts ctx.tool.transform(...) // replayed to build effective tool registry state -ctx.tool.hook(...) // invoked at a live tool operation boundary +ctx.tool.hook(...) // invoked at a live tool operation boundary ``` The shared low-level machinery owns registration order, scope cleanup, disposal, and snapshots. Each domain owns when its transforms or runtime hooks execute. diff --git a/packages/plugin/src/v2/effect/README.md b/packages/plugin/src/v2/effect/README.md index 3da1d7b566..f2526ec860 100644 --- a/packages/plugin/src/v2/effect/README.md +++ b/packages/plugin/src/v2/effect/README.md @@ -5,15 +5,13 @@ The Effect plugin API grants plugins two in-process capabilities: - `hook` installs behavior at an OpenCode extension point. - `reload` reruns every transform hook for a stateful domain. -The public server client will be exposed separately. It is intentionally not part of `PluginContext` yet. - ## Defining A Plugin ```ts -import { define } from "@opencode-ai/plugin/v2/effect" +import { Plugin } from "@opencode-ai/plugin/v2/effect" import { Effect } from "effect" -export const Plugin = define({ +export default Plugin.define({ id: "example", effect: Effect.fn(function* (ctx) { yield* ctx.catalog.transform((catalog) => { @@ -25,7 +23,7 @@ export const Plugin = define({ }) ``` -Plugin setup registers hooks imperatively. It does not return a hook object. +Plugin setup registers hooks imperatively through each domain's `hook` method. Configuration supplied for the plugin is available as `ctx.options`. @@ -64,7 +62,8 @@ Runtime hooks intercept live operations rather than rebuilding domain state: ```ts yield * - ctx.aisdk.sdk( + ctx.aisdk.hook( + "sdk", Effect.fn(function* (event) { if (event.package !== "@ai-sdk/xai") return const mod = yield* Effect.promise(() => import("@ai-sdk/xai")) @@ -73,7 +72,7 @@ yield * ) yield * - ctx.aisdk.language((event) => { + ctx.aisdk.hook("language", (event) => { if (event.model.providerID !== "xai") return event.language = event.sdk.responses(event.model.api.id) }) @@ -81,6 +80,16 @@ yield * Hooks run sequentially in registration order. Later hooks observe mutations made by earlier hooks. +Session request context is mutable immediately before provider dispatch: + +```ts +yield * + ctx.session.hook("request", (event) => { + event.tools.read.description = "Read a file using narrow line ranges." + delete event.tools.write + }) +``` + ## Reloading A Domain When data captured by a transform changes, reload the affected domain: diff --git a/packages/plugin/src/v2/effect/agent.ts b/packages/plugin/src/v2/effect/agent.ts index aadacf99d5..80932c5e9e 100644 --- a/packages/plugin/src/v2/effect/agent.ts +++ b/packages/plugin/src/v2/effect/agent.ts @@ -1,7 +1,7 @@ import type { AgentApi } from "@opencode-ai/client/effect/api" import type { AgentV2Info } from "@opencode-ai/sdk/v2/types" import type { Effect } from "effect" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export interface AgentDraft { list(): readonly AgentV2Info[] @@ -11,7 +11,7 @@ export interface AgentDraft { remove(id: string): void } -export interface AgentHooks extends AgentApi { - readonly transform: TransformHook +export interface AgentDomain extends AgentApi { + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/v2/effect/aisdk.ts b/packages/plugin/src/v2/effect/aisdk.ts index 2934edc27d..9539e7b987 100644 --- a/packages/plugin/src/v2/effect/aisdk.ts +++ b/packages/plugin/src/v2/effect/aisdk.ts @@ -2,7 +2,7 @@ import type { LanguageModelV3 } from "@ai-sdk/provider" import type { Model } from "@opencode-ai/schema/model" import type { Hooks } from "./registration.js" -export type AISDKHooks = Hooks<{ +export interface AISDKHooks { sdk: { readonly model: Model.Info readonly package: string @@ -15,4 +15,8 @@ export type AISDKHooks = Hooks<{ readonly options: Record language?: LanguageModelV3 } -}> +} + +export interface AISDKDomain { + readonly hook: Hooks +} diff --git a/packages/plugin/src/v2/effect/catalog.ts b/packages/plugin/src/v2/effect/catalog.ts index 4cc5e55227..0792d5f39c 100644 --- a/packages/plugin/src/v2/effect/catalog.ts +++ b/packages/plugin/src/v2/effect/catalog.ts @@ -1,7 +1,7 @@ import type { ModelV2Info, ProviderV2Info } from "@opencode-ai/sdk/v2/types" import type { CatalogApi } from "@opencode-ai/client/effect/api" import type { Effect } from "effect" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export interface CatalogProviderRecord { readonly provider: ProviderV2Info @@ -26,7 +26,7 @@ export interface CatalogDraft { } } -export interface CatalogHooks extends CatalogApi { - readonly transform: TransformHook +export interface CatalogDomain extends CatalogApi { + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/v2/effect/command.ts b/packages/plugin/src/v2/effect/command.ts index 2faaa91fa9..bd9ebfa919 100644 --- a/packages/plugin/src/v2/effect/command.ts +++ b/packages/plugin/src/v2/effect/command.ts @@ -1,7 +1,7 @@ import type { CommandV2Info } from "@opencode-ai/sdk/v2/types" import type { CommandApi } from "@opencode-ai/client/effect/api" import type { Effect } from "effect" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export interface CommandDraft { list(): readonly CommandV2Info[] @@ -10,7 +10,7 @@ export interface CommandDraft { remove(name: string): void } -export interface CommandHooks extends CommandApi { - readonly transform: TransformHook +export interface CommandDomain extends CommandApi { + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/v2/effect/context.ts b/packages/plugin/src/v2/effect/context.ts deleted file mode 100644 index 219dc16581..0000000000 --- a/packages/plugin/src/v2/effect/context.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { PluginOptions } from "../options.js" -import type { AgentHooks } from "./agent.js" -import type { AISDKHooks } from "./aisdk.js" -import type { CatalogHooks } from "./catalog.js" -import type { CommandHooks } from "./command.js" -import type { EventHooks } from "./event.js" -import type { IntegrationHooks } from "./integration.js" -import type { PluginDomain } from "./plugin.js" -import type { ReferenceHooks } from "./reference.js" -import type { SkillHooks } from "./skill.js" -import type { ToolDomain } from "./tool.js" -import type { SessionHooks } from "./runtime.js" - -export interface PluginContext { - readonly options: PluginOptions - readonly agent: AgentHooks - readonly aisdk: AISDKHooks - readonly catalog: CatalogHooks - readonly command: CommandHooks - readonly event: EventHooks - readonly integration: IntegrationHooks - readonly plugin: PluginDomain - readonly reference: ReferenceHooks - readonly skill: SkillHooks - readonly tool: ToolDomain - readonly session: SessionHooks -} diff --git a/packages/plugin/src/v2/effect/event.ts b/packages/plugin/src/v2/effect/event.ts index 49ad375d67..283d4109f0 100644 --- a/packages/plugin/src/v2/effect/event.ts +++ b/packages/plugin/src/v2/effect/event.ts @@ -1,3 +1,3 @@ import type { EventApi } from "@opencode-ai/client/effect/api" -export interface EventHooks extends Pick, "subscribe"> {} +export interface EventDomain extends Pick, "subscribe"> {} diff --git a/packages/plugin/src/v2/effect/index.ts b/packages/plugin/src/v2/effect/index.ts index 1332ce2752..e00e302b7a 100644 --- a/packages/plugin/src/v2/effect/index.ts +++ b/packages/plugin/src/v2/effect/index.ts @@ -1,17 +1,4 @@ -export type { PluginContext } from "./context.js" -export { define } from "./plugin.js" -export type { Plugin, PluginDomain } from "./plugin.js" -export type { AgentDraft, AgentHooks } from "./agent.js" -export type { AISDKHooks } from "./aisdk.js" -export type { CatalogDraft, CatalogHooks, CatalogProviderRecord } from "./catalog.js" -export type { CommandDraft, CommandHooks } from "./command.js" -export type { EventHooks } from "./event.js" -export type { IntegrationDraft, IntegrationHooks, IntegrationMethodRegistration } from "./integration.js" -export type { ReferenceDraft, ReferenceHooks } from "./reference.js" -export type { SkillDraft, SkillHooks } from "./skill.js" -export * as Tool from "./tool.js" -export type { ToolDomain, ToolDraft, ToolExecuteBeforeEvent, ToolExecuteAfterEvent } from "./tool.js" -export type { SessionHooks } from "./runtime.js" +export * as Plugin from "./plugin.js" export { Agent } from "@opencode-ai/schema/agent" export { Command } from "@opencode-ai/schema/command" diff --git a/packages/plugin/src/v2/effect/integration.ts b/packages/plugin/src/v2/effect/integration.ts index 1433786634..7af3542b6c 100644 --- a/packages/plugin/src/v2/effect/integration.ts +++ b/packages/plugin/src/v2/effect/integration.ts @@ -11,7 +11,7 @@ import type { } from "@opencode-ai/sdk/v2/types" import type { IntegrationApi } from "@opencode-ai/client/effect/api" import type { Effect, Scope } from "effect" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export type IntegrationOAuthAuthorization = { readonly url: string @@ -56,8 +56,8 @@ export interface IntegrationDraft { } } -export interface IntegrationHooks extends IntegrationApi { - readonly transform: TransformHook +export interface IntegrationDomain extends IntegrationApi { + readonly transform: Transform readonly reload: () => Effect.Effect readonly connection: { readonly active: (integrationID: string) => Effect.Effect diff --git a/packages/plugin/src/v2/effect/plugin.ts b/packages/plugin/src/v2/effect/plugin.ts index 66f57caf60..3b0b6eb918 100644 --- a/packages/plugin/src/v2/effect/plugin.ts +++ b/packages/plugin/src/v2/effect/plugin.ts @@ -1,14 +1,37 @@ import type { PluginApi } from "@opencode-ai/client/effect/api" import type { Effect, Scope } from "effect" -import type { PluginContext } from "./context.js" +import type { PluginOptions } from "../options.js" +import type { AgentDomain } from "./agent.js" +import type { AISDKDomain } from "./aisdk.js" +import type { CatalogDomain } from "./catalog.js" +import type { CommandDomain } from "./command.js" +import type { EventDomain } from "./event.js" +import type { IntegrationDomain } from "./integration.js" +import type { ReferenceDomain } from "./reference.js" +import type { SessionDomain } from "./session.js" +import type { SkillDomain } from "./skill.js" +import type { ToolDomain } from "./tool.js" + +export interface Context { + readonly options: PluginOptions + readonly agent: AgentDomain + readonly aisdk: AISDKDomain + readonly catalog: CatalogDomain + readonly command: CommandDomain + readonly event: EventDomain + readonly integration: IntegrationDomain + readonly plugin: PluginApi + readonly reference: ReferenceDomain + readonly session: SessionDomain + readonly skill: SkillDomain + readonly tool: ToolDomain +} export interface Plugin { readonly id: string - readonly effect: (context: PluginContext) => Effect.Effect + readonly effect: (context: Context) => Effect.Effect } export function define(plugin: Plugin) { return plugin } - -export interface PluginDomain extends PluginApi {} diff --git a/packages/plugin/src/v2/effect/reference.ts b/packages/plugin/src/v2/effect/reference.ts index 1216d3c0b8..085ae0de5e 100644 --- a/packages/plugin/src/v2/effect/reference.ts +++ b/packages/plugin/src/v2/effect/reference.ts @@ -1,7 +1,7 @@ import type { ReferenceGitSource, ReferenceLocalSource } from "@opencode-ai/sdk/v2/types" import type { ReferenceApi } from "@opencode-ai/client/effect/api" import type { Effect } from "effect" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export interface ReferenceDraft { add(name: string, source: ReferenceLocalSource | ReferenceGitSource): void @@ -9,7 +9,7 @@ export interface ReferenceDraft { list(): readonly (readonly [string, ReferenceLocalSource | ReferenceGitSource])[] } -export interface ReferenceHooks extends ReferenceApi { - readonly transform: TransformHook +export interface ReferenceDomain extends ReferenceApi { + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/v2/effect/registration.ts b/packages/plugin/src/v2/effect/registration.ts index 1ddb9e3489..916b034a73 100644 --- a/packages/plugin/src/v2/effect/registration.ts +++ b/packages/plugin/src/v2/effect/registration.ts @@ -4,10 +4,9 @@ export interface Registration { readonly dispose: Effect.Effect } -export type Hooks = { - readonly [Name in keyof Spec]: ( - callback: (input: Spec[Name]) => Effect.Effect | void, - ) => Effect.Effect -} +export type Hooks = ( + name: Name, + callback: (input: Spec[Name]) => Effect.Effect, +) => Effect.Effect -export type TransformHook = (callback: (input: Input) => void) => Effect.Effect +export type Transform = (callback: (input: Input) => void) => Effect.Effect diff --git a/packages/plugin/src/v2/effect/runtime.ts b/packages/plugin/src/v2/effect/runtime.ts deleted file mode 100644 index 0358330210..0000000000 --- a/packages/plugin/src/v2/effect/runtime.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { SessionApi } from "@opencode-ai/client/effect/api" - -export interface SessionHooks - extends Pick, "create" | "get" | "prompt" | "command" | "interrupt"> {} diff --git a/packages/plugin/src/v2/effect/session.ts b/packages/plugin/src/v2/effect/session.ts new file mode 100644 index 0000000000..00ee8b9125 --- /dev/null +++ b/packages/plugin/src/v2/effect/session.ts @@ -0,0 +1,25 @@ +import type { SessionApi } from "@opencode-ai/client/effect/api" +import type { Message, SystemPart } from "@opencode-ai/llm" +import type { Agent } from "@opencode-ai/schema/agent" +import type { Model } from "@opencode-ai/schema/model" +import type { Session } from "@opencode-ai/schema/session" +import type { JsonSchema } from "effect" +import type { Hooks } from "./registration.js" + +export interface SessionRequestBeforeEvent { + readonly sessionID: Session.ID + readonly agent: Agent.ID + readonly model: Model.Ref + system: Array + messages: Array + tools: Record +} + +export interface SessionHooks { + readonly request: SessionRequestBeforeEvent +} + +export interface SessionDomain + extends Pick, "create" | "get" | "prompt" | "command" | "interrupt"> { + readonly hook: Hooks +} diff --git a/packages/plugin/src/v2/effect/skill.ts b/packages/plugin/src/v2/effect/skill.ts index b01b81bc3b..4437e846e3 100644 --- a/packages/plugin/src/v2/effect/skill.ts +++ b/packages/plugin/src/v2/effect/skill.ts @@ -1,14 +1,14 @@ import type { SkillV2Source } from "@opencode-ai/sdk/v2/types" import type { SkillApi } from "@opencode-ai/client/effect/api" import type { Effect } from "effect" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export interface SkillDraft { source(source: SkillV2Source): void list(): readonly SkillV2Source[] } -export interface SkillHooks extends SkillApi { - readonly transform: TransformHook +export interface SkillDomain extends SkillApi { + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/v2/effect/tool.ts b/packages/plugin/src/v2/effect/tool.ts index bb5931af16..db22b18b4f 100644 --- a/packages/plugin/src/v2/effect/tool.ts +++ b/packages/plugin/src/v2/effect/tool.ts @@ -5,7 +5,7 @@ import { Agent } from "@opencode-ai/schema/agent" import { Session } from "@opencode-ai/schema/session" import { SessionMessage } from "@opencode-ai/schema/session-message" import { Effect, JsonSchema, Schema, type Scope } from "effect" -import type { Hooks } from "./registration.js" +import type { Hooks, Transform } from "./registration.js" export interface Context { readonly sessionID: Session.ID @@ -253,7 +253,12 @@ export interface ToolDraft { add(name: string, tool: AnyTool, options?: RegisterOptions): void } -export interface ToolDomain { - readonly transform: (callback: (draft: ToolDraft) => void) => Effect.Effect - readonly execute: Hooks<{ before: ToolExecuteBeforeEvent; after: ToolExecuteAfterEvent }> +export interface ToolHooks { + readonly "execute.before": ToolExecuteBeforeEvent + readonly "execute.after": ToolExecuteAfterEvent +} + +export interface ToolDomain { + readonly transform: Transform + readonly hook: Hooks } diff --git a/packages/plugin/src/v2/promise/README.md b/packages/plugin/src/v2/promise/README.md index e91b93fbdf..3e96cefc27 100644 --- a/packages/plugin/src/v2/promise/README.md +++ b/packages/plugin/src/v2/promise/README.md @@ -1,6 +1,6 @@ # OpenCode V2 Promise Plugin API -The Promise plugin API is the async/await equivalent of `@opencode-ai/plugin/v2/effect`. It grants plugins the same two in-process capabilities: +The Promise plugin API at `@opencode-ai/plugin/v2` is the async/await equivalent of `@opencode-ai/plugin/v2/effect`. It grants plugins the same two in-process capabilities: - `hook` installs behavior at an OpenCode extension point. - `reload` reruns every transform hook for a stateful domain. @@ -10,9 +10,9 @@ The only difference from the Effect API is the async boundary: hook callbacks, h ## Defining A Plugin ```ts -import { define } from "@opencode-ai/plugin/v2/promise" +import { Plugin } from "@opencode-ai/plugin/v2" -export const Plugin = define({ +export default Plugin.define({ id: "example", setup: async (ctx) => { await ctx.catalog.transform((catalog) => { @@ -24,7 +24,7 @@ export const Plugin = define({ }) ``` -Plugin setup registers hooks imperatively. It does not return a hook object. +Plugin setup registers hooks imperatively through each domain's `hook` method. Configuration supplied for the plugin is available as `ctx.options`. @@ -64,18 +64,43 @@ ctx.skill.transform Runtime hooks intercept live operations: ```ts -await ctx.aisdk.sdk(async (event) => { +await ctx.aisdk.hook("sdk", async (event) => { if (event.package !== "@ai-sdk/xai") return const mod = await import("@ai-sdk/xai") event.sdk = mod.createXai(event.options) }) -await ctx.aisdk.language((event) => { +await ctx.aisdk.hook("language", (event) => { if (event.model.providerID !== "xai") return event.language = event.sdk.responses(event.model.api.id) }) ``` +Session request context is mutable immediately before provider dispatch: + +```ts +await ctx.session.hook("request", (event) => { + event.tools.read.description = "Read a file using narrow line ranges." + delete event.tools.write +}) +``` + +Promise tools use the same schemas and registration model as Effect tools, with async executors: + +```ts +import { Schema } from "effect" +import { Tool } from "@opencode-ai/plugin/v2/tool" + +const echo = Tool.make({ + description: "Echo text", + input: Schema.Struct({ text: Schema.String }), + output: Schema.Struct({ text: Schema.String }), + execute: async ({ text }) => ({ text }), +}) + +await ctx.tool.transform((tools) => tools.add("echo", echo)) +``` + ## Reloading A Domain When data captured by a transform changes, reload the affected domain: diff --git a/packages/plugin/src/v2/promise/agent.ts b/packages/plugin/src/v2/promise/agent.ts index d9313838cb..bfffdebb4e 100644 --- a/packages/plugin/src/v2/promise/agent.ts +++ b/packages/plugin/src/v2/promise/agent.ts @@ -1,10 +1,10 @@ import type { AgentApi } from "@opencode-ai/client/promise/api" import type { AgentDraft } from "../effect/agent.js" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export type { AgentDraft } -export interface AgentHooks extends AgentApi { - readonly transform: TransformHook +export interface AgentDomain extends AgentApi { + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/v2/promise/aisdk.ts b/packages/plugin/src/v2/promise/aisdk.ts index 2934edc27d..9539e7b987 100644 --- a/packages/plugin/src/v2/promise/aisdk.ts +++ b/packages/plugin/src/v2/promise/aisdk.ts @@ -2,7 +2,7 @@ import type { LanguageModelV3 } from "@ai-sdk/provider" import type { Model } from "@opencode-ai/schema/model" import type { Hooks } from "./registration.js" -export type AISDKHooks = Hooks<{ +export interface AISDKHooks { sdk: { readonly model: Model.Info readonly package: string @@ -15,4 +15,8 @@ export type AISDKHooks = Hooks<{ readonly options: Record language?: LanguageModelV3 } -}> +} + +export interface AISDKDomain { + readonly hook: Hooks +} diff --git a/packages/plugin/src/v2/promise/catalog.ts b/packages/plugin/src/v2/promise/catalog.ts index f6b0f649f8..e0a51f5e52 100644 --- a/packages/plugin/src/v2/promise/catalog.ts +++ b/packages/plugin/src/v2/promise/catalog.ts @@ -1,10 +1,10 @@ import type { CatalogApi } from "@opencode-ai/client/promise/api" import type { CatalogDraft, CatalogProviderRecord } from "../effect/catalog.js" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export type { CatalogDraft, CatalogProviderRecord } -export interface CatalogHooks extends CatalogApi { - readonly transform: TransformHook +export interface CatalogDomain extends CatalogApi { + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/v2/promise/command.ts b/packages/plugin/src/v2/promise/command.ts index d042259c47..2c675e2856 100644 --- a/packages/plugin/src/v2/promise/command.ts +++ b/packages/plugin/src/v2/promise/command.ts @@ -1,10 +1,10 @@ import type { CommandApi } from "@opencode-ai/client/promise/api" import type { CommandDraft } from "../effect/command.js" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export type { CommandDraft } -export interface CommandHooks extends CommandApi { - readonly transform: TransformHook +export interface CommandDomain extends CommandApi { + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/v2/promise/context.ts b/packages/plugin/src/v2/promise/context.ts deleted file mode 100644 index 5e67e44961..0000000000 --- a/packages/plugin/src/v2/promise/context.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { PluginOptions } from "../options.js" -import type { AgentHooks } from "./agent.js" -import type { AISDKHooks } from "./aisdk.js" -import type { CatalogHooks } from "./catalog.js" -import type { CommandHooks } from "./command.js" -import type { EventHooks } from "./event.js" -import type { IntegrationHooks } from "./integration.js" -import type { PluginDomain } from "./plugin.js" -import type { ReferenceHooks } from "./reference.js" -import type { SessionHooks } from "./runtime.js" -import type { SkillHooks } from "./skill.js" - -export interface PluginContext { - readonly options: PluginOptions - readonly agent: AgentHooks - readonly aisdk: AISDKHooks - readonly catalog: CatalogHooks - readonly command: CommandHooks - readonly event: EventHooks - readonly integration: IntegrationHooks - readonly plugin: PluginDomain - readonly reference: ReferenceHooks - readonly session: SessionHooks - readonly skill: SkillHooks -} diff --git a/packages/plugin/src/v2/promise/event.ts b/packages/plugin/src/v2/promise/event.ts index 5330f70c7c..344f5d6f30 100644 --- a/packages/plugin/src/v2/promise/event.ts +++ b/packages/plugin/src/v2/promise/event.ts @@ -1,3 +1,3 @@ import type { EventApi } from "@opencode-ai/client/promise/api" -export interface EventHooks extends Pick {} +export interface EventDomain extends Pick {} diff --git a/packages/plugin/src/v2/promise/index.ts b/packages/plugin/src/v2/promise/index.ts index 4f5d5754ea..ae8e6d18a1 100644 --- a/packages/plugin/src/v2/promise/index.ts +++ b/packages/plugin/src/v2/promise/index.ts @@ -1,16 +1,5 @@ -export type { PluginContext } from "./context.js" export type { PluginOptions } from "../options.js" -export { define } from "./plugin.js" -export type { Plugin, PluginDomain } from "./plugin.js" -export type { AgentDraft, AgentHooks } from "./agent.js" -export type { AISDKHooks } from "./aisdk.js" -export type { CatalogDraft, CatalogHooks, CatalogProviderRecord } from "./catalog.js" -export type { CommandDraft, CommandHooks } from "./command.js" -export type { EventHooks } from "./event.js" -export type { IntegrationDraft, IntegrationHooks, IntegrationMethodRegistration } from "./integration.js" -export type { ReferenceDraft, ReferenceHooks } from "./reference.js" -export type { SessionHooks } from "./runtime.js" -export type { SkillDraft, SkillHooks } from "./skill.js" +export * as Plugin from "./plugin.js" export { Agent } from "@opencode-ai/schema/agent" export { Command } from "@opencode-ai/schema/command" diff --git a/packages/plugin/src/v2/promise/integration.ts b/packages/plugin/src/v2/promise/integration.ts index bd133e889e..76e5a99eb5 100644 --- a/packages/plugin/src/v2/promise/integration.ts +++ b/packages/plugin/src/v2/promise/integration.ts @@ -1,12 +1,12 @@ import type { IntegrationApi } from "@opencode-ai/client/promise/api" import type { IntegrationDraft, IntegrationMethodRegistration } from "../effect/integration.js" import type { CredentialValue } from "@opencode-ai/sdk/v2/types" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export type { IntegrationDraft, IntegrationMethodRegistration } -export interface IntegrationHooks extends IntegrationApi { - readonly transform: TransformHook +export interface IntegrationDomain extends IntegrationApi { + readonly transform: Transform readonly reload: () => Promise readonly connection: { readonly active: (integrationID: string) => Promise diff --git a/packages/plugin/src/v2/promise/plugin.ts b/packages/plugin/src/v2/promise/plugin.ts index eb91b9df53..37bce3cc03 100644 --- a/packages/plugin/src/v2/promise/plugin.ts +++ b/packages/plugin/src/v2/promise/plugin.ts @@ -1,13 +1,36 @@ import type { PluginApi } from "@opencode-ai/client/promise/api" -import type { PluginContext } from "./context.js" +import type { PluginOptions } from "../options.js" +import type { AgentDomain } from "./agent.js" +import type { AISDKDomain } from "./aisdk.js" +import type { CatalogDomain } from "./catalog.js" +import type { CommandDomain } from "./command.js" +import type { EventDomain } from "./event.js" +import type { IntegrationDomain } from "./integration.js" +import type { ReferenceDomain } from "./reference.js" +import type { SessionDomain } from "./session.js" +import type { SkillDomain } from "./skill.js" +import type { ToolDomain } from "./tool.js" + +export interface Context { + readonly options: PluginOptions + readonly agent: AgentDomain + readonly aisdk: AISDKDomain + readonly catalog: CatalogDomain + readonly command: CommandDomain + readonly event: EventDomain + readonly integration: IntegrationDomain + readonly plugin: PluginApi + readonly reference: ReferenceDomain + readonly session: SessionDomain + readonly skill: SkillDomain + readonly tool: ToolDomain +} export interface Plugin { readonly id: string - readonly setup: (context: PluginContext) => Promise | void + readonly setup: (context: Context) => Promise | void } export function define(plugin: Plugin) { return plugin } - -export interface PluginDomain extends PluginApi {} diff --git a/packages/plugin/src/v2/promise/reference.ts b/packages/plugin/src/v2/promise/reference.ts index 66bd5b4874..05542b2baf 100644 --- a/packages/plugin/src/v2/promise/reference.ts +++ b/packages/plugin/src/v2/promise/reference.ts @@ -1,10 +1,10 @@ import type { ReferenceApi } from "@opencode-ai/client/promise/api" import type { ReferenceDraft } from "../effect/reference.js" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export type { ReferenceDraft } -export interface ReferenceHooks extends ReferenceApi { - readonly transform: TransformHook +export interface ReferenceDomain extends ReferenceApi { + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/v2/promise/registration.ts b/packages/plugin/src/v2/promise/registration.ts index 76be0982b8..0537c1b98e 100644 --- a/packages/plugin/src/v2/promise/registration.ts +++ b/packages/plugin/src/v2/promise/registration.ts @@ -2,8 +2,9 @@ export interface Registration { readonly dispose: () => Promise } -export type Hooks = { - readonly [Name in keyof Spec]: (callback: (input: Spec[Name]) => Promise | void) => Promise -} +export type Hooks = ( + name: Name, + callback: (input: Spec[Name]) => Promise | void, +) => Promise -export type TransformHook = (callback: (input: Input) => void) => Promise +export type Transform = (callback: (input: Input) => void) => Promise diff --git a/packages/plugin/src/v2/promise/runtime.ts b/packages/plugin/src/v2/promise/runtime.ts deleted file mode 100644 index b89b6e8abf..0000000000 --- a/packages/plugin/src/v2/promise/runtime.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { SessionApi } from "@opencode-ai/client/promise/api" - -export interface SessionHooks extends Pick {} diff --git a/packages/plugin/src/v2/promise/session.ts b/packages/plugin/src/v2/promise/session.ts new file mode 100644 index 0000000000..41bf598f46 --- /dev/null +++ b/packages/plugin/src/v2/promise/session.ts @@ -0,0 +1,24 @@ +import type { SessionApi } from "@opencode-ai/client/promise/api" +import type { Message, SystemPart } from "@opencode-ai/llm" +import type { Agent } from "@opencode-ai/schema/agent" +import type { Model } from "@opencode-ai/schema/model" +import type { Session } from "@opencode-ai/schema/session" +import type { JsonSchema } from "effect" +import type { Hooks } from "./registration.js" + +export interface SessionRequestBeforeEvent { + readonly sessionID: Session.ID + readonly agent: Agent.ID + readonly model: Model.Ref + system: Array + messages: Array + tools: Record +} + +export interface SessionHooks { + readonly request: SessionRequestBeforeEvent +} + +export interface SessionDomain extends Pick { + readonly hook: Hooks +} diff --git a/packages/plugin/src/v2/promise/skill.ts b/packages/plugin/src/v2/promise/skill.ts index a1e62fc544..cbc6459a33 100644 --- a/packages/plugin/src/v2/promise/skill.ts +++ b/packages/plugin/src/v2/promise/skill.ts @@ -1,10 +1,10 @@ import type { SkillApi } from "@opencode-ai/client/promise/api" import type { SkillDraft } from "../effect/skill.js" -import type { TransformHook } from "./registration.js" +import type { Transform } from "./registration.js" export type { SkillDraft } -export interface SkillHooks extends SkillApi { - readonly transform: TransformHook +export interface SkillDomain extends SkillApi { + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/v2/promise/tool.ts b/packages/plugin/src/v2/promise/tool.ts new file mode 100644 index 0000000000..da4ee1b200 --- /dev/null +++ b/packages/plugin/src/v2/promise/tool.ts @@ -0,0 +1,110 @@ +export * as Tool from "./tool.js" + +import { Tool } from "../effect/tool.js" +import type { ToolOutput, ToolResultValue } from "@opencode-ai/llm" +import type { Agent } from "@opencode-ai/schema/agent" +import type { Session } from "@opencode-ai/schema/session" +import type { SessionMessage } from "@opencode-ai/schema/session-message" +import { Effect, type JsonSchema, type Schema } from "effect" +import type { Hooks, Transform } from "./registration.js" + +export type Context = Tool.Context +export type SchemaType = Tool.SchemaType +export type Definition, Output extends SchemaType> = Tool.Definition +export type AnyTool = Tool.AnyTool +export const Failure = Tool.Failure +export type Failure = Tool.Failure +export const RegistrationError = Tool.RegistrationError +export type RegistrationError = Tool.RegistrationError +export type Content = Tool.Content +export type DynamicOutput = Tool.DynamicOutput + +type Config< + Input extends SchemaType, + Output extends SchemaType, + Structured extends SchemaType = Output, +> = { + readonly description: string + readonly input: Input + readonly output: Output + readonly structured?: Structured + readonly toStructuredOutput?: (input: { + readonly input: Schema.Schema.Type + readonly output: Output["Encoded"] + }) => Schema.Schema.Type + readonly execute: ( + input: Schema.Schema.Type, + context: Context, + ) => Promise> + readonly toModelOutput?: (input: { + readonly input: Schema.Schema.Type + readonly output: Output["Encoded"] + }) => ReadonlyArray +} + +type DynamicConfig = { + readonly description: string + readonly jsonSchema: JsonSchema.JsonSchema + readonly outputSchema?: JsonSchema.JsonSchema + readonly execute: (input: unknown, context: Context) => Promise +} + +export function make< + Input extends SchemaType, + Output extends SchemaType, + Structured extends SchemaType = Output, +>(config: Config): Definition +export function make(config: DynamicConfig): AnyTool +export function make(config: Config | DynamicConfig): AnyTool { + if ("jsonSchema" in config) + return Tool.make({ + ...config, + execute: (input, context) => Effect.promise(() => config.execute(input, context)), + }) + return Tool.make({ + ...config, + execute: (input, context) => Effect.promise(() => config.execute(input, context)), + }) +} + +export const withPermission = Tool.withPermission + +export interface ToolExecuteBeforeEvent { + readonly tool: string + readonly sessionID: Session.ID + readonly agent: Agent.ID + readonly assistantMessageID: SessionMessage.ID + readonly toolCallID: string + input: unknown +} + +export interface ToolExecuteAfterEvent { + readonly tool: string + readonly sessionID: Session.ID + readonly agent: Agent.ID + readonly assistantMessageID: SessionMessage.ID + readonly toolCallID: string + readonly input: unknown + result: ToolResultValue + output?: ToolOutput + outputPaths?: ReadonlyArray +} + +export interface RegisterOptions { + readonly group?: string + readonly deferred?: boolean +} + +export interface ToolDraft { + add(name: string, tool: AnyTool, options?: RegisterOptions): void +} + +export interface ToolHooks { + readonly "execute.before": ToolExecuteBeforeEvent + readonly "execute.after": ToolExecuteAfterEvent +} + +export interface ToolDomain { + readonly transform: Transform + readonly hook: Hooks +} diff --git a/packages/protocol/src/client.ts b/packages/protocol/src/client.ts index 652beb6595..03f9a34015 100644 --- a/packages/protocol/src/client.ts +++ b/packages/protocol/src/client.ts @@ -60,26 +60,5 @@ export const groupNames = { "server.vcs": "vcs", } as const -export const endpointNames = { - "debug.location.evict": "evictLocation", - "session.messages": "list", - "integration.connect.key": "connectKey", - "integration.connect.oauth": "connectOauth", - "integration.attempt.status": "attemptStatus", - "integration.attempt.complete": "attemptComplete", - "integration.attempt.cancel": "attemptCancel", - "session.instructions.entry.list": ["instructions", "entry", "list"], - "session.instructions.entry.put": ["instructions", "entry", "put"], - "session.instructions.entry.remove": ["instructions", "entry", "remove"], - "session.revert.stage": "revertStage", - "session.revert.clear": "revertClear", - "session.revert.commit": "revertCommit", - "permission.request.list": "listRequests", - "permission.saved.list": "listSaved", - "permission.saved.remove": "removeSaved", - "form.request.list": "listRequests", - "question.request.list": "listRequests", -} as const - export const promiseOmitEndpoints = new Set(["pty.connect", "pty.connectToken"]) export const effectOmitEndpoints = new Set(["fs.read", "pty.connect", "pty.connectToken"]) diff --git a/packages/protocol/src/groups/debug.ts b/packages/protocol/src/groups/debug.ts index a405044105..fd7cd9def5 100644 --- a/packages/protocol/src/groups/debug.ts +++ b/packages/protocol/src/groups/debug.ts @@ -9,7 +9,7 @@ export const DebugGroup = HttpApiGroup.make("server.debug") success: Schema.Array(Location.Ref), }).annotateMerge( OpenApi.annotations({ - identifier: "v2.debug.location", + identifier: "v2.debug.location.list", summary: "List loaded locations", description: "List locations currently loaded by the server.", }), diff --git a/packages/protocol/src/groups/message.ts b/packages/protocol/src/groups/message.ts index b8c387576f..cab0d8bb0f 100644 --- a/packages/protocol/src/groups/message.ts +++ b/packages/protocol/src/groups/message.ts @@ -36,7 +36,7 @@ export const MessageGroup = HttpApiGroup.make("server.message") error: [InvalidCursorError, SessionNotFoundError, UnknownError], }).annotateMerge( OpenApi.annotations({ - identifier: "v2.session.messages", + identifier: "v2.message.list", summary: "Get session messages", description: "Retrieve projected messages for a session. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline.", diff --git a/packages/protocol/src/groups/pty.ts b/packages/protocol/src/groups/pty.ts index 309868189f..a8fd1a3bc9 100644 --- a/packages/protocol/src/groups/pty.ts +++ b/packages/protocol/src/groups/pty.ts @@ -107,7 +107,7 @@ export const PtyGroup = HttpApiGroup.make("server.pty") .annotateMerge(locationQueryOpenApi) .annotateMerge( OpenApi.annotations({ - identifier: "v2.pty.connectToken", + identifier: "v2.pty.connect.token", summary: "Create PTY WebSocket token", description: "Create a short-lived single-use ticket for opening a PTY WebSocket connection.", }), diff --git a/packages/sdk-next/test/embedded.test.ts b/packages/sdk-next/test/embedded.test.ts index 0b3b639087..9dfe7e2a14 100644 --- a/packages/sdk-next/test/embedded.test.ts +++ b/packages/sdk-next/test/embedded.test.ts @@ -139,7 +139,7 @@ it.live( yield* opencode.plugin.list({ location: ref }) yield* Deferred.await(booted).pipe(Effect.timeout("5 seconds")) - yield* opencode.debug.evictLocation({ location: ref }) + yield* opencode.debug.location.evict({ location: ref }) yield* opencode.plugin.list({ location: ref }) yield* Deferred.await(recommitted).pipe(Effect.timeout("5 seconds")) diff --git a/packages/sdk/js/script/build.ts b/packages/sdk/js/script/build.ts index 4101e0751f..20a4fcd803 100755 --- a/packages/sdk/js/script/build.ts +++ b/packages/sdk/js/script/build.ts @@ -134,7 +134,7 @@ if (sessionListTypesPatched === logTypesPatched) { throw new Error("Session list numeric query patch did not apply") } const sessionMessagesTypesPatched = sessionListTypesPatched.replace( - /(export type V2SessionMessagesData = \{[\s\S]*?query\?: \{[\s\S]*?limit\?: )string( \| null)/, + /(export type V2MessageListData = \{[\s\S]*?query\?: \{[\s\S]*?limit\?: )string( \| null)/, "$1number$2", ) if (sessionMessagesTypesPatched === sessionListTypesPatched) { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 239783dc87..a88ad8e008 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -276,10 +276,10 @@ import type { V2CredentialRemoveResponses, V2CredentialUpdateErrors, V2CredentialUpdateResponses, - V2DebugLocationErrors, V2DebugLocationEvictErrors, V2DebugLocationEvictResponses, - V2DebugLocationResponses, + V2DebugLocationListErrors, + V2DebugLocationListResponses, V2EventSubscribeErrors, V2EventSubscribeResponses, V2FormRequestListErrors, @@ -312,6 +312,10 @@ import type { V2LocationGetResponses, V2McpListErrors, V2McpListResponses, + V2McpResourceCatalogErrors, + V2McpResourceCatalogResponses, + V2MessageListErrors, + V2MessageListResponses, V2ModelDefaultErrors, V2ModelDefaultResponses, V2ModelListErrors, @@ -400,8 +404,8 @@ import type { V2SessionLogResponses, V2SessionMessageErrors, V2SessionMessageResponses, - V2SessionMessagesErrors, - V2SessionMessagesResponses, + V2SessionMoveErrors, + V2SessionMoveResponses, V2SessionPermissionCreateErrors, V2SessionPermissionCreateResponses, V2SessionPermissionGetErrors, @@ -5181,6 +5185,86 @@ export class Plugin extends HeyApiClient { } } +export class Switch extends HeyApiClient { + /** + * Switch session agent + * + * Switch the agent used by subsequent provider turns. + */ + public agent( + parameters: { + sessionID: string + agent?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "body", key: "agent" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + V2SessionSwitchAgentResponses, + V2SessionSwitchAgentErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/agent", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Switch session model + * + * Switch the model used by subsequent provider turns. + */ + public model( + parameters: { + sessionID: string + model?: ModelRef + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "body", key: "model" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + V2SessionSwitchModelResponses, + V2SessionSwitchModelErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/model", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } +} + export class Revert extends HeyApiClient { /** * Stage session revert @@ -5984,84 +6068,6 @@ export class Session3 extends HeyApiClient { }) } - /** - * Switch session agent - * - * Switch the agent used by subsequent provider turns. - */ - public switchAgent( - parameters: { - sessionID: string - agent?: string - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "agent" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post< - V2SessionSwitchAgentResponses, - V2SessionSwitchAgentErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/agent", - ...options, - ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, - }) - } - - /** - * Switch session model - * - * Switch the model used by subsequent provider turns. - */ - public switchModel( - parameters: { - sessionID: string - model?: ModelRef - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "model" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post< - V2SessionSwitchModelResponses, - V2SessionSwitchModelErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/model", - ...options, - ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, - }) - } - /** * Rename session * @@ -6097,6 +6103,45 @@ export class Session3 extends HeyApiClient { }) } + /** + * Move session + * + * Move a session to another project directory, optionally transferring local changes. + */ + public move( + parameters: { + sessionID: string + destination?: { + directory: string + } + moveChanges?: boolean | null + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "body", key: "destination" }, + { in: "body", key: "moveChanges" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/move", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + /** * Send message * @@ -6241,6 +6286,7 @@ export class Session3 extends HeyApiClient { metadata?: { [key: string]: unknown } + resume?: boolean | null }, options?: Options, ) { @@ -6253,6 +6299,7 @@ export class Session3 extends HeyApiClient { { in: "body", key: "text" }, { in: "body", key: "description" }, { in: "body", key: "metadata" }, + { in: "body", key: "resume" }, ], }, ], @@ -6481,38 +6528,9 @@ export class Session3 extends HeyApiClient { }) } - /** - * Get session messages - * - * Retrieve projected messages for a session. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline. - */ - public messages( - parameters: { - sessionID: string - limit?: number | null - order?: "asc" | "desc" | null - cursor?: string | null - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "limit" }, - { in: "query", key: "order" }, - { in: "query", key: "cursor" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/api/session/{sessionID}/message", - ...options, - ...params, - }) + private _switch?: Switch + get switch(): Switch { + return (this._switch ??= new Switch({ client: this.client })) } private _revert?: Revert @@ -6541,6 +6559,42 @@ export class Session3 extends HeyApiClient { } } +export class Message extends HeyApiClient { + /** + * Get session messages + * + * Retrieve projected messages for a session. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline. + */ + public list( + parameters: { + sessionID: string + limit?: number | null + order?: "asc" | "desc" | null + cursor?: string | null + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "limit" }, + { in: "query", key: "order" }, + { in: "query", key: "cursor" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/api/session/{sessionID}/message", + ...options, + ...params, + }) + } +} + export class Model extends HeyApiClient { /** * List models @@ -6971,6 +7025,34 @@ export class Integration extends HeyApiClient { } } +export class Resource2 extends HeyApiClient { + /** + * List MCP resources + * + * Retrieve resources and resource templates from connected MCP servers. + */ + public catalog( + parameters?: { + location?: { + directory?: string | null + workspace?: string | null + } | null + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get< + V2McpResourceCatalogResponses, + V2McpResourceCatalogErrors, + ThrowOnError + >({ + url: "/api/mcp/resource", + ...options, + ...params, + }) + } +} + export class Mcp2 extends HeyApiClient { /** * List MCP servers @@ -6993,6 +7075,11 @@ export class Mcp2 extends HeyApiClient { ...params, }) } + + private _resource?: Resource2 + get resource(): Resource2 { + return (this._resource ??= new Resource2({ client: this.client })) + } } export class Credential extends HeyApiClient { @@ -7420,6 +7507,41 @@ export class Event2 extends HeyApiClient { } } +export class Connect2 extends HeyApiClient { + /** + * Create PTY WebSocket token + * + * Create a short-lived single-use ticket for opening a PTY WebSocket connection. + */ + public token( + parameters: { + ptyID: string + location?: { + directory?: string | null + workspace?: string | null + } | null + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/api/pty/{ptyID}/connect-token", + ...options, + ...params, + }) + } +} + export class Pty2 extends HeyApiClient { /** * List PTY sessions @@ -7602,39 +7724,6 @@ export class Pty2 extends HeyApiClient { }) } - /** - * Create PTY WebSocket token - * - * Create a short-lived single-use ticket for opening a PTY WebSocket connection. - */ - public connectToken( - parameters: { - ptyID: string - location?: { - directory?: string | null - workspace?: string | null - } | null - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "location" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/api/pty/{ptyID}/connect-token", - ...options, - ...params, - }) - } - /** * Connect to PTY session * @@ -7670,6 +7759,11 @@ export class Pty2 extends HeyApiClient { ...params, }) } + + private _connect?: Connect2 + get connect2(): Connect2 { + return (this._connect ??= new Connect2({ client: this.client })) + } } export class Shell extends HeyApiClient { @@ -8145,23 +8239,23 @@ export class Location2 extends HeyApiClient { ...params, }) } -} -export class Debug extends HeyApiClient { /** * List loaded locations * * List locations currently loaded by the server. */ - public location(options?: Options) { - return (options?.client ?? this.client).get({ + public list(options?: Options) { + return (options?.client ?? this.client).get({ url: "/api/debug/location", ...options, }) } +} +export class Debug extends HeyApiClient { private _location?: Location2 - get location2(): Location2 { + get location(): Location2 { return (this._location ??= new Location2({ client: this.client })) } } @@ -8192,6 +8286,11 @@ export class V2 extends HeyApiClient { return (this._session ??= new Session3({ client: this.client })) } + private _message?: Message + get message(): Message { + return (this._message ??= new Message({ client: this.client })) + } + private _model?: Model get model(): Model { return (this._model ??= new Model({ client: this.client })) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 68ad7c2a0e..59afb28c06 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -96,6 +96,7 @@ export type Event = | EventTuiToastShow2 | EventTuiSessionSelect2 | EventMcpToolsChanged + | EventMcpResourcesChanged | EventMcpStatusChanged | EventCommandExecuted | EventFileEdited @@ -1631,6 +1632,13 @@ export type GlobalEvent = { server: string } } + | { + id: string + type: "mcp.resources.changed" + properties: { + server: string + } + } | { id: string type: "mcp.status.changed" @@ -3019,6 +3027,14 @@ export type ProviderNotFoundError = { message: string } +export type McpResource2 = { + server: string + name: string + uri: string + description?: string + mimeType?: string +} + export type FormNotFoundError = { _tag: "FormNotFoundError" id: string @@ -3182,6 +3198,7 @@ export type V2Event = | TuiToastShow | TuiSessionSelect | McpToolsChanged + | McpResourcesChanged | McpStatusChanged | CommandExecuted | FileEdited @@ -5751,6 +5768,19 @@ export type McpServer = { integrationID?: string } +export type McpResourceTemplate = { + server: string + name: string + uriTemplate: string + description?: string + mimeType?: string +} + +export type McpResourceCatalog = { + resources: Array + templates: Array +} + export type ProjectCurrent = { id: string directory: string @@ -6680,6 +6710,19 @@ export type McpToolsChanged = { } } +export type McpResourcesChanged = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "mcp.resources.changed" + location?: LocationRef + data: { + server: string + } +} + export type McpStatusChanged = { id: string created: number @@ -7860,6 +7903,14 @@ export type EventMcpToolsChanged = { } } +export type EventMcpResourcesChanged = { + id: string + type: "mcp.resources.changed" + properties: { + server: string + } +} + export type EventMcpStatusChanged = { id: string type: "mcp.status.changed" @@ -8698,6 +8749,7 @@ export type V2EventV2 = | InstallationUpdateAvailableV2 | VcsBranchUpdatedV2 | McpStatusChangedV2 + | McpResourcesChangedV2 | PermissionAskedV2 | PermissionRepliedV2 | QuestionAskedV2 @@ -9797,6 +9849,19 @@ export type EventLogSyncedV2 = { seq?: number } +export type McpResourceV2 = { + server: string + name: string + uri: string + description?: string + mimeType?: string +} + +export type McpResourceCatalogV2 = { + resources: Array + templates: Array +} + export type ProjectTimeV2 = { created: number updated: number @@ -10749,6 +10814,19 @@ export type McpStatusChangedV2 = { } } +export type McpResourcesChangedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "mcp.resources.changed" + location?: LocationRefV2 + data: { + server: string + } +} + export type PermissionAskedV2 = { id: string created: number @@ -15505,6 +15583,46 @@ export type V2SessionRenameResponses = { export type V2SessionRenameResponse = V2SessionRenameResponses[keyof V2SessionRenameResponses] +export type V2SessionMoveData = { + body: { + destination: { + directory: string + } + moveChanges?: boolean | null + } + path: { + sessionID: string + } + query?: never + url: "/api/session/{sessionID}/move" +} + +export type V2SessionMoveErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError1 | InvalidRequestErrorV2 + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * SessionNotFoundError + */ + 404: SessionNotFoundError +} + +export type V2SessionMoveError = V2SessionMoveErrors[keyof V2SessionMoveErrors] + +export type V2SessionMoveResponses = { + /** + * + */ + 204: void +} + +export type V2SessionMoveResponse = V2SessionMoveResponses[keyof V2SessionMoveResponses] + export type V2SessionPromptData = { body: { id?: string | null @@ -15652,6 +15770,7 @@ export type V2SessionSyntheticData = { metadata?: { [key: string]: unknown } + resume?: boolean | null } path: { sessionID: string @@ -16244,7 +16363,7 @@ export type V2SessionMessageResponses = { export type V2SessionMessageResponse = V2SessionMessageResponses[keyof V2SessionMessageResponses] -export type V2SessionMessagesData = { +export type V2MessageListData = { body?: never path: { sessionID: string @@ -16263,7 +16382,7 @@ export type V2SessionMessagesData = { url: "/api/session/{sessionID}/message" } -export type V2SessionMessagesErrors = { +export type V2MessageListErrors = { /** * InvalidCursorError | InvalidRequestError */ @@ -16282,16 +16401,16 @@ export type V2SessionMessagesErrors = { 500: UnknownErrorV2 } -export type V2SessionMessagesError = V2SessionMessagesErrors[keyof V2SessionMessagesErrors] +export type V2MessageListError = V2MessageListErrors[keyof V2MessageListErrors] -export type V2SessionMessagesResponses = { +export type V2MessageListResponses = { /** * SessionMessagesResponse */ 200: SessionMessagesResponseV2 } -export type V2SessionMessagesResponse = V2SessionMessagesResponses[keyof V2SessionMessagesResponses] +export type V2MessageListResponse = V2MessageListResponses[keyof V2MessageListResponses] export type V2ModelListData = { body?: never @@ -16819,6 +16938,43 @@ export type V2McpListResponses = { export type V2McpListResponse = V2McpListResponses[keyof V2McpListResponses] +export type V2McpResourceCatalogData = { + body?: never + path?: never + query?: { + location?: { + directory?: string | null + workspace?: string | null + } | null + } + url: "/api/mcp/resource" +} + +export type V2McpResourceCatalogErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestErrorV2 + /** + * UnauthorizedError + */ + 401: UnauthorizedError +} + +export type V2McpResourceCatalogError = V2McpResourceCatalogErrors[keyof V2McpResourceCatalogErrors] + +export type V2McpResourceCatalogResponses = { + /** + * Success + */ + 200: { + location: LocationInfoV2 + data: McpResourceCatalogV2 + } +} + +export type V2McpResourceCatalogResponse = V2McpResourceCatalogResponses[keyof V2McpResourceCatalogResponses] + export type V2CredentialRemoveData = { body?: never path: { @@ -18717,14 +18873,14 @@ export type V2DebugLocationEvictResponses = { export type V2DebugLocationEvictResponse = V2DebugLocationEvictResponses[keyof V2DebugLocationEvictResponses] -export type V2DebugLocationData = { +export type V2DebugLocationListData = { body?: never path?: never query?: never url: "/api/debug/location" } -export type V2DebugLocationErrors = { +export type V2DebugLocationListErrors = { /** * InvalidRequestError */ @@ -18735,16 +18891,16 @@ export type V2DebugLocationErrors = { 401: UnauthorizedError } -export type V2DebugLocationError = V2DebugLocationErrors[keyof V2DebugLocationErrors] +export type V2DebugLocationListError = V2DebugLocationListErrors[keyof V2DebugLocationListErrors] -export type V2DebugLocationResponses = { +export type V2DebugLocationListResponses = { /** * Success */ 200: Array } -export type V2DebugLocationResponse = V2DebugLocationResponses[keyof V2DebugLocationResponses] +export type V2DebugLocationListResponse = V2DebugLocationListResponses[keyof V2DebugLocationListResponses] export type PtyConnectData = { body?: never diff --git a/packages/tui/src/component/dialog-integration.tsx b/packages/tui/src/component/dialog-integration.tsx index 52aba6d0e3..1b962142fc 100644 --- a/packages/tui/src/component/dialog-integration.tsx +++ b/packages/tui/src/component/dialog-integration.tsx @@ -180,7 +180,7 @@ function KeyMethod(props: { onConfirm={(key) => { if (!key) return void sdk.api.integration - .connectKey({ + .connect.key({ integrationID: props.integration.id, location: location(data), key, @@ -219,7 +219,7 @@ function OAuthStarting(props: { onMount(() => { void sdk.api.integration - .connectOauth({ + .connect.oauth({ integrationID: props.integration.id, location: location(data), methodID: props.method.id, @@ -288,7 +288,7 @@ function OAuthAuto(props: { const poll = () => { void sdk.api.integration - .attemptStatus({ attemptID: props.attempt.attemptID, location: location(data) }) + .attempt.status({ attemptID: props.attempt.attemptID, location: location(data) }) .then((result) => { const status = result.data if (status.status === "pending") { @@ -314,7 +314,7 @@ function OAuthAuto(props: { onCleanup(() => { if (timer) clearTimeout(timer) if (settled) return - void sdk.api.integration.attemptCancel({ attemptID: props.attempt.attemptID, location: location(data) }) + void sdk.api.integration.attempt.cancel({ attemptID: props.attempt.attemptID, location: location(data) }) }) return ( @@ -344,7 +344,7 @@ function OAuthCode(props: { onCleanup(() => { if (settled) return - void sdk.api.integration.attemptCancel({ attemptID: props.attempt.attemptID, location: location(data) }) + void sdk.api.integration.attempt.cancel({ attemptID: props.attempt.attemptID, location: location(data) }) }) return ( @@ -354,7 +354,7 @@ function OAuthCode(props: { onConfirm={(code) => { if (!code) return void sdk.api.integration - .attemptComplete({ attemptID: props.attempt.attemptID, location: location(data), code }) + .attempt.complete({ attemptID: props.attempt.attemptID, location: location(data), code }) .then(() => { settled = true return connected(props.integration, data, dialog, toast, props.onConnected) diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 1e5a9132c9..14b34f93c2 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -1127,7 +1127,7 @@ export function Prompt(props: PromptProps) { }) } if (session?.revert) { - const error = await sdk.api.session.revertCommit({ sessionID }).then( + const error = await sdk.api.session.revert.commit({ sessionID }).then( () => undefined, (error) => error, ) diff --git a/packages/tui/src/context/data.tsx b/packages/tui/src/context/data.tsx index 7b15781ffc..22f505682d 100644 --- a/packages/tui/src/context/data.tsx +++ b/packages/tui/src/context/data.tsx @@ -919,7 +919,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ return store.project.permission[projectID] }, async refresh(projectID: string) { - setStore("project", "permission", projectID, mutable(await sdk.api.permission.listSaved({ projectID }))) + setStore("project", "permission", projectID, mutable(await sdk.api.permission.saved.list({ projectID }))) }, }, }, @@ -1064,7 +1064,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ ) for (const session of response.data) registerSession(session.id) }), - sdk.api.permission.listRequests({ location: locationQuery(defaultLocation()) }).then((response) => { + sdk.api.permission.request.list({ location: locationQuery(defaultLocation()) }).then((response) => { const permissions = mutable(response.data).reduce>( (result, request) => ({ ...result, @@ -1074,7 +1074,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ ) setStore("session", "permission", reconcile(permissions)) }), - sdk.api.form.listRequests({ location: locationQuery(defaultLocation()) }).then((response) => { + sdk.api.form.request.list({ location: locationQuery(defaultLocation()) }).then((response) => { const forms = mutable(response.data).reduce>( (result, form) => ({ ...result, diff --git a/packages/tui/src/routes/session/dialog-message.tsx b/packages/tui/src/routes/session/dialog-message.tsx index f31b14868a..db64c7b5da 100644 --- a/packages/tui/src/routes/session/dialog-message.tsx +++ b/packages/tui/src/routes/session/dialog-message.tsx @@ -24,7 +24,7 @@ export function DialogMessage(props: { messageID: string; sessionID: string }) { description: "undo messages and file changes", onSelect: async (dialog) => { await sdk.api.session - .revertStage({ sessionID: props.sessionID, messageID: props.messageID }) + .revert.stage({ sessionID: props.sessionID, messageID: props.messageID }) .catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 })) dialog.clear() }, diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index f76838de46..3743abed05 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -437,7 +437,7 @@ export function Session() { dialog.clear() return } - const error = await sdk.api.session.revertStage({ sessionID: route.sessionID, messageID: target }).then( + const error = await sdk.api.session.revert.stage({ sessionID: route.sessionID, messageID: target }).then( () => undefined, (error) => error, ) @@ -454,7 +454,7 @@ export function Session() { slash: { name: "redo" }, run: () => { void (async () => { - const error = await sdk.api.session.revertClear({ sessionID: route.sessionID }).then( + const error = await sdk.api.session.revert.clear({ sessionID: route.sessionID }).then( () => undefined, (error) => error, ) @@ -1383,7 +1383,7 @@ function RevertMessage(props: { onMouseUp={() => { if (renderer.getSelection()?.getSelectedText()) return void (async () => { - const error = await sdk.api.session.revertClear({ sessionID: route.sessionID }).then( + const error = await sdk.api.session.revert.clear({ sessionID: route.sessionID }).then( () => undefined, (error) => error, )