@neurocode-ai/protocol 1.18.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ import { Model } from "@neurocode-ai/schema/model"
2
+ import { Location } from "@neurocode-ai/schema/location"
3
+ import { Schema } from "effect"
4
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
5
+ import { ServiceUnavailableError } from "../errors"
6
+ import { LocationQuery, locationQueryOpenApi } from "./location"
7
+
8
+ export const ModelGroup = HttpApiGroup.make("server.model")
9
+ .add(
10
+ HttpApiEndpoint.get("model.list", "/api/model", {
11
+ query: LocationQuery,
12
+ success: Location.response(Schema.Array(Model.Info)),
13
+ error: ServiceUnavailableError,
14
+ })
15
+ .annotateMerge(locationQueryOpenApi)
16
+ .annotateMerge(
17
+ OpenApi.annotations({
18
+ identifier: "v2.model.list",
19
+ summary: "List models",
20
+ description: "Retrieve available models ordered by release date.",
21
+ }),
22
+ ),
23
+ )
24
+ .annotateMerge(
25
+ OpenApi.annotations({
26
+ title: "models",
27
+ description: "Experimental model routes.",
28
+ }),
29
+ )
@@ -0,0 +1,137 @@
1
+ import { Agent } from "@neurocode-ai/schema/agent"
2
+ import { Location } from "@neurocode-ai/schema/location"
3
+ import { Permission } from "@neurocode-ai/schema/permission"
4
+ import { PermissionSaved } from "@neurocode-ai/schema/permission-saved"
5
+ import { Project } from "@neurocode-ai/schema/project"
6
+ import { Session } from "@neurocode-ai/schema/session"
7
+ import { Context, Schema } from "effect"
8
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
9
+ import { PermissionNotFoundError, SessionNotFoundError } from "../errors"
10
+ import { LocationQuery, locationQueryOpenApi } from "./location"
11
+
12
+ export const makePermissionGroup = <
13
+ LocationId extends HttpApiMiddleware.AnyId,
14
+ LocationService,
15
+ SessionLocationId extends HttpApiMiddleware.AnyId,
16
+ SessionLocationService,
17
+ >(
18
+ locationMiddleware: Context.Key<LocationId, LocationService>,
19
+ sessionLocationMiddleware: Context.Key<SessionLocationId, SessionLocationService>,
20
+ ) =>
21
+ HttpApiGroup.make("server.permission")
22
+ .add(
23
+ HttpApiEndpoint.get("permission.request.list", "/api/permission/request", {
24
+ query: LocationQuery,
25
+ success: Location.response(Schema.Array(Permission.Request)),
26
+ })
27
+ .annotateMerge(locationQueryOpenApi)
28
+ .annotateMerge(
29
+ OpenApi.annotations({
30
+ identifier: "v2.permission.request.list",
31
+ summary: "List pending permission requests",
32
+ description: "Retrieve pending permission requests for a location.",
33
+ }),
34
+ ),
35
+ )
36
+ .add(
37
+ HttpApiEndpoint.get("permission.saved.list", "/api/permission/saved", {
38
+ query: Schema.Struct({ projectID: Project.ID.pipe(Schema.optional) }),
39
+ success: Schema.Struct({ data: Schema.Array(PermissionSaved.Info) }),
40
+ }).annotateMerge(
41
+ OpenApi.annotations({
42
+ identifier: "v2.permission.saved.list",
43
+ summary: "List saved permissions",
44
+ description: "Retrieve saved permissions, optionally filtered by project.",
45
+ }),
46
+ ),
47
+ )
48
+ .add(
49
+ HttpApiEndpoint.delete("permission.saved.remove", "/api/permission/saved/:id", {
50
+ params: { id: PermissionSaved.ID },
51
+ success: HttpApiSchema.NoContent,
52
+ }).annotateMerge(
53
+ OpenApi.annotations({
54
+ identifier: "v2.permission.saved.remove",
55
+ summary: "Remove saved permission",
56
+ description: "Remove a saved permission by ID.",
57
+ }),
58
+ ),
59
+ )
60
+ // Effect applies group middleware only to endpoints already added; session endpoints use session placement below.
61
+ .middleware(locationMiddleware)
62
+ .add(
63
+ HttpApiEndpoint.post("session.permission.create", "/api/session/:sessionID/permission", {
64
+ params: { sessionID: Session.ID },
65
+ payload: Schema.Struct({
66
+ id: Permission.ID.pipe(Schema.optional),
67
+ action: Permission.Request.fields.action,
68
+ resources: Permission.Request.fields.resources,
69
+ save: Permission.Request.fields.save,
70
+ metadata: Permission.Request.fields.metadata,
71
+ source: Permission.Request.fields.source,
72
+ agent: Agent.ID.pipe(Schema.optional),
73
+ }),
74
+ success: Schema.Struct({
75
+ data: Schema.Struct({ id: Permission.ID, effect: Permission.Effect }),
76
+ }),
77
+ error: SessionNotFoundError,
78
+ })
79
+ .middleware(sessionLocationMiddleware)
80
+ .annotateMerge(
81
+ OpenApi.annotations({
82
+ identifier: "v2.session.permission.create",
83
+ summary: "Create permission request",
84
+ description: "Evaluate and, when approval is required, create a permission request for a session.",
85
+ }),
86
+ ),
87
+ )
88
+ .add(
89
+ HttpApiEndpoint.get("session.permission.list", "/api/session/:sessionID/permission", {
90
+ params: { sessionID: Session.ID },
91
+ success: Schema.Struct({ data: Schema.Array(Permission.Request) }),
92
+ error: SessionNotFoundError,
93
+ })
94
+ .middleware(sessionLocationMiddleware)
95
+ .annotateMerge(
96
+ OpenApi.annotations({
97
+ identifier: "v2.session.permission.list",
98
+ summary: "List session permission requests",
99
+ description: "Retrieve pending permission requests owned by a session.",
100
+ }),
101
+ ),
102
+ )
103
+ .add(
104
+ HttpApiEndpoint.get("session.permission.get", "/api/session/:sessionID/permission/:requestID", {
105
+ params: { sessionID: Session.ID, requestID: Permission.ID },
106
+ success: Schema.Struct({ data: Permission.Request }),
107
+ error: [SessionNotFoundError, PermissionNotFoundError],
108
+ })
109
+ .middleware(sessionLocationMiddleware)
110
+ .annotateMerge(
111
+ OpenApi.annotations({
112
+ identifier: "v2.session.permission.get",
113
+ summary: "Get permission request",
114
+ description: "Retrieve a pending permission request owned by a session.",
115
+ }),
116
+ ),
117
+ )
118
+ .add(
119
+ HttpApiEndpoint.post("session.permission.reply", "/api/session/:sessionID/permission/:requestID/reply", {
120
+ params: { sessionID: Session.ID, requestID: Permission.ID },
121
+ payload: Schema.Struct({
122
+ reply: Permission.Reply,
123
+ message: Schema.String.pipe(Schema.optional),
124
+ }),
125
+ success: HttpApiSchema.NoContent,
126
+ error: [SessionNotFoundError, PermissionNotFoundError],
127
+ })
128
+ .middleware(sessionLocationMiddleware)
129
+ .annotateMerge(
130
+ OpenApi.annotations({
131
+ identifier: "v2.session.permission.reply",
132
+ summary: "Reply to pending permission request",
133
+ description: "Respond to a pending permission request owned by a session.",
134
+ }),
135
+ ),
136
+ )
137
+ .annotateMerge(OpenApi.annotations({ title: "permissions", description: "Experimental permission routes." }))
@@ -0,0 +1,56 @@
1
+ import { ProjectCopy } from "@neurocode-ai/schema/project-copy"
2
+ import { Project } from "@neurocode-ai/schema/project"
3
+ import { Schema, Struct } from "effect"
4
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
5
+ import { LocationQuery, locationQueryOpenApi } from "./location"
6
+
7
+ const root = "/experimental/project/:projectID/copy"
8
+
9
+ export class ProjectCopyError extends Schema.ErrorClass<ProjectCopyError>("ProjectCopyError")(
10
+ {
11
+ name: Schema.Literal("ProjectCopyError"),
12
+ data: Schema.Struct({
13
+ message: Schema.String,
14
+ forceRequired: Schema.optional(Schema.Boolean),
15
+ }),
16
+ },
17
+ { httpApiStatus: 400 },
18
+ ) {}
19
+
20
+ const CreatePayload = Schema.Struct(Struct.omit(ProjectCopy.CreateInput.fields, ["projectID", "sourceDirectory"]))
21
+ const RemovePayload = Schema.Struct(Struct.omit(ProjectCopy.RemoveInput.fields, ["projectID"]))
22
+
23
+ export const ProjectCopyGroup = HttpApiGroup.make("server.projectCopy")
24
+ .add(
25
+ HttpApiEndpoint.post("projectCopy.create", root, {
26
+ params: { projectID: Project.ID },
27
+ query: LocationQuery,
28
+ payload: CreatePayload,
29
+ success: ProjectCopy.Copy,
30
+ error: ProjectCopyError,
31
+ })
32
+ .annotateMerge(locationQueryOpenApi)
33
+ .annotateMerge(OpenApi.annotations({ identifier: "v2.projectCopy.create" })),
34
+ )
35
+ .add(
36
+ HttpApiEndpoint.delete("projectCopy.remove", root, {
37
+ params: { projectID: Project.ID },
38
+ query: LocationQuery,
39
+ payload: RemovePayload,
40
+ success: HttpApiSchema.NoContent,
41
+ error: ProjectCopyError,
42
+ })
43
+ .annotateMerge(locationQueryOpenApi)
44
+ .annotateMerge(OpenApi.annotations({ identifier: "v2.projectCopy.remove" })),
45
+ )
46
+ .add(
47
+ HttpApiEndpoint.post("projectCopy.refresh", `${root}/refresh`, {
48
+ params: { projectID: Project.ID },
49
+ query: LocationQuery,
50
+ success: HttpApiSchema.NoContent,
51
+ error: ProjectCopyError,
52
+ })
53
+ .annotateMerge(locationQueryOpenApi)
54
+ .annotateMerge(OpenApi.annotations({ identifier: "v2.projectCopy.refresh" })),
55
+ )
56
+ .annotateMerge(OpenApi.annotations({ title: "projectCopy", description: "Project copy management routes." }))
@@ -0,0 +1,45 @@
1
+ import { Provider } from "@neurocode-ai/schema/provider"
2
+ import { Location } from "@neurocode-ai/schema/location"
3
+ import { Schema } from "effect"
4
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
5
+ import { ProviderNotFoundError, ServiceUnavailableError } from "../errors"
6
+ import { LocationQuery, locationQueryOpenApi } from "./location"
7
+
8
+ export const ProviderGroup = HttpApiGroup.make("server.provider")
9
+ .add(
10
+ HttpApiEndpoint.get("provider.list", "/api/provider", {
11
+ query: LocationQuery,
12
+ success: Location.response(Schema.Array(Provider.Info)),
13
+ error: ServiceUnavailableError,
14
+ })
15
+ .annotateMerge(locationQueryOpenApi)
16
+ .annotateMerge(
17
+ OpenApi.annotations({
18
+ identifier: "v2.provider.list",
19
+ summary: "List providers",
20
+ description: "Retrieve active AI providers so clients can show provider availability and configuration.",
21
+ }),
22
+ ),
23
+ )
24
+ .add(
25
+ HttpApiEndpoint.get("provider.get", "/api/provider/:providerID", {
26
+ params: { providerID: Provider.ID },
27
+ query: LocationQuery,
28
+ success: Location.response(Provider.Info),
29
+ error: [ProviderNotFoundError, ServiceUnavailableError],
30
+ })
31
+ .annotateMerge(locationQueryOpenApi)
32
+ .annotateMerge(
33
+ OpenApi.annotations({
34
+ identifier: "v2.provider.get",
35
+ summary: "Get provider",
36
+ description: "Retrieve a single AI provider so clients can inspect its availability and endpoint settings.",
37
+ }),
38
+ ),
39
+ )
40
+ .annotateMerge(
41
+ OpenApi.annotations({
42
+ title: "providers",
43
+ description: "Experimental provider routes.",
44
+ }),
45
+ )
@@ -0,0 +1,143 @@
1
+ import { Pty } from "@neurocode-ai/schema/pty"
2
+ import { PtyTicket } from "@neurocode-ai/schema/pty-ticket"
3
+ import { Location } from "@neurocode-ai/schema/location"
4
+ import { Schema } from "effect"
5
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
6
+ import { ForbiddenError, PtyNotFoundError } from "../errors"
7
+ import { LocationQuery, locationQueryOpenApi } from "./location"
8
+
9
+ export const PTY_CONNECT_TICKET_QUERY = "ticket"
10
+ export const PTY_CONNECT_TOKEN_HEADER = "x-neurocode-ticket"
11
+ export const PTY_CONNECT_TOKEN_HEADER_VALUE = "1"
12
+
13
+ const PTY_CONNECT_PATH = /^\/api\/pty\/[^/]+\/connect$/
14
+
15
+ // Authorization middleware skips credential checks when this matches; the PTY connect handler
16
+ // is then responsible for consuming and validating the ticket.
17
+ export function hasPtyConnectTicketURL(url: URL) {
18
+ return PTY_CONNECT_PATH.test(url.pathname) && !!url.searchParams.get(PTY_CONNECT_TICKET_QUERY)
19
+ }
20
+
21
+ export const PtyGroup = HttpApiGroup.make("server.pty")
22
+ .add(
23
+ HttpApiEndpoint.get("pty.list", "/api/pty", {
24
+ query: LocationQuery,
25
+ success: Location.response(Schema.Array(Pty.Info)),
26
+ })
27
+ .annotateMerge(locationQueryOpenApi)
28
+ .annotateMerge(
29
+ OpenApi.annotations({
30
+ identifier: "v2.pty.list",
31
+ summary: "List PTY sessions",
32
+ description: "List PTY sessions for a location, including exited sessions retained until removal.",
33
+ }),
34
+ ),
35
+ )
36
+ .add(
37
+ HttpApiEndpoint.post("pty.create", "/api/pty", {
38
+ query: LocationQuery,
39
+ payload: Pty.CreateInput,
40
+ success: Location.response(Pty.Info),
41
+ })
42
+ .annotateMerge(locationQueryOpenApi)
43
+ .annotateMerge(
44
+ OpenApi.annotations({
45
+ identifier: "v2.pty.create",
46
+ summary: "Create PTY session",
47
+ description: "Create a pseudo-terminal session for a location.",
48
+ }),
49
+ ),
50
+ )
51
+ .add(
52
+ HttpApiEndpoint.get("pty.get", "/api/pty/:ptyID", {
53
+ params: { ptyID: Pty.ID },
54
+ query: LocationQuery,
55
+ success: Location.response(Pty.Info),
56
+ error: PtyNotFoundError,
57
+ })
58
+ .annotateMerge(locationQueryOpenApi)
59
+ .annotateMerge(
60
+ OpenApi.annotations({
61
+ identifier: "v2.pty.get",
62
+ summary: "Get PTY session",
63
+ description: "Get one PTY session, including its exit code once exited.",
64
+ }),
65
+ ),
66
+ )
67
+ .add(
68
+ HttpApiEndpoint.put("pty.update", "/api/pty/:ptyID", {
69
+ params: { ptyID: Pty.ID },
70
+ query: LocationQuery,
71
+ payload: Pty.UpdateInput,
72
+ success: Location.response(Pty.Info),
73
+ error: PtyNotFoundError,
74
+ })
75
+ .annotateMerge(locationQueryOpenApi)
76
+ .annotateMerge(
77
+ OpenApi.annotations({
78
+ identifier: "v2.pty.update",
79
+ summary: "Update PTY session",
80
+ description: "Update the title or viewport size of one PTY session.",
81
+ }),
82
+ ),
83
+ )
84
+ .add(
85
+ HttpApiEndpoint.delete("pty.remove", "/api/pty/:ptyID", {
86
+ params: { ptyID: Pty.ID },
87
+ query: LocationQuery,
88
+ success: HttpApiSchema.NoContent,
89
+ error: PtyNotFoundError,
90
+ })
91
+ .annotateMerge(locationQueryOpenApi)
92
+ .annotateMerge(
93
+ OpenApi.annotations({
94
+ identifier: "v2.pty.remove",
95
+ summary: "Remove PTY session",
96
+ description: "Terminate and remove one PTY session.",
97
+ }),
98
+ ),
99
+ )
100
+ .add(
101
+ HttpApiEndpoint.post("pty.connectToken", "/api/pty/:ptyID/connect-token", {
102
+ params: { ptyID: Pty.ID },
103
+ query: LocationQuery,
104
+ success: Location.response(PtyTicket.ConnectToken),
105
+ error: [ForbiddenError, PtyNotFoundError],
106
+ })
107
+ .annotateMerge(locationQueryOpenApi)
108
+ .annotateMerge(
109
+ OpenApi.annotations({
110
+ identifier: "v2.pty.connectToken",
111
+ summary: "Create PTY WebSocket token",
112
+ description: "Create a short-lived single-use ticket for opening a PTY WebSocket connection.",
113
+ }),
114
+ ),
115
+ )
116
+ .add(
117
+ // Query fields are decoded in the raw handler after the existence check so a missing
118
+ // session responds with an empty 404 before any upgrade work.
119
+ HttpApiEndpoint.get("pty.connect", "/api/pty/:ptyID/connect", {
120
+ params: { ptyID: Pty.ID },
121
+ success: Schema.Boolean,
122
+ error: [ForbiddenError, PtyNotFoundError],
123
+ }).annotateMerge(
124
+ OpenApi.annotations({
125
+ identifier: "v2.pty.connect",
126
+ summary: "Connect to PTY session",
127
+ description: "Establish a WebSocket connection streaming PTY output and accepting terminal input.",
128
+ transform: (operation) => ({
129
+ ...operation,
130
+ "x-websocket": true,
131
+ parameters: [
132
+ ...(operation.parameters ?? []),
133
+ ...["location[directory]", "location[workspace]", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({
134
+ in: "query",
135
+ name,
136
+ schema: { type: "string" },
137
+ })),
138
+ ],
139
+ }),
140
+ }),
141
+ ),
142
+ )
143
+ .annotateMerge(OpenApi.annotations({ title: "pty", description: "Experimental location-scoped PTY routes." }))
@@ -0,0 +1,84 @@
1
+ import { Question } from "@neurocode-ai/schema/question"
2
+ import { Location } from "@neurocode-ai/schema/location"
3
+ import { Session } from "@neurocode-ai/schema/session"
4
+ import { Context, Schema } from "effect"
5
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
6
+ import { QuestionNotFoundError, SessionNotFoundError } from "../errors"
7
+ import { LocationQuery, locationQueryOpenApi } from "./location"
8
+
9
+ export const makeQuestionGroup = <
10
+ LocationId extends HttpApiMiddleware.AnyId,
11
+ LocationService,
12
+ SessionLocationId extends HttpApiMiddleware.AnyId,
13
+ SessionLocationService,
14
+ >(
15
+ locationMiddleware: Context.Key<LocationId, LocationService>,
16
+ sessionLocationMiddleware: Context.Key<SessionLocationId, SessionLocationService>,
17
+ ) =>
18
+ HttpApiGroup.make("server.question")
19
+ .add(
20
+ HttpApiEndpoint.get("question.request.list", "/api/question/request", {
21
+ query: LocationQuery,
22
+ success: Location.response(Schema.Array(Question.Request)),
23
+ })
24
+ .annotateMerge(locationQueryOpenApi)
25
+ .annotateMerge(
26
+ OpenApi.annotations({
27
+ identifier: "v2.question.request.list",
28
+ summary: "List pending question requests",
29
+ description: "Retrieve pending question requests for a location.",
30
+ }),
31
+ ),
32
+ )
33
+ .annotateMerge(OpenApi.annotations({ title: "questions", description: "Experimental question routes." }))
34
+ // Effect applies group middleware only to endpoints already added; session endpoints use session placement below.
35
+ .middleware(locationMiddleware)
36
+ .add(
37
+ HttpApiEndpoint.get("session.question.list", "/api/session/:sessionID/question", {
38
+ params: { sessionID: Session.ID },
39
+ success: Schema.Struct({ data: Schema.Array(Question.Request) }),
40
+ error: SessionNotFoundError,
41
+ })
42
+ .middleware(sessionLocationMiddleware)
43
+ .annotateMerge(
44
+ OpenApi.annotations({
45
+ identifier: "v2.session.question.list",
46
+ summary: "List session question requests",
47
+ description: "Retrieve pending question requests owned by a session.",
48
+ }),
49
+ ),
50
+ )
51
+ .add(
52
+ HttpApiEndpoint.post("session.question.reply", "/api/session/:sessionID/question/:requestID/reply", {
53
+ params: { sessionID: Session.ID, requestID: Question.ID },
54
+ payload: Question.Reply,
55
+ success: HttpApiSchema.NoContent,
56
+ error: [SessionNotFoundError, QuestionNotFoundError],
57
+ })
58
+ .middleware(sessionLocationMiddleware)
59
+ .annotateMerge(
60
+ OpenApi.annotations({
61
+ identifier: "v2.session.question.reply",
62
+ summary: "Reply to pending question request",
63
+ description: "Answer a pending question request owned by a session.",
64
+ }),
65
+ ),
66
+ )
67
+ .add(
68
+ HttpApiEndpoint.post("session.question.reject", "/api/session/:sessionID/question/:requestID/reject", {
69
+ params: { sessionID: Session.ID, requestID: Question.ID },
70
+ success: HttpApiSchema.NoContent,
71
+ error: [SessionNotFoundError, QuestionNotFoundError],
72
+ })
73
+ .middleware(sessionLocationMiddleware)
74
+ .annotateMerge(
75
+ OpenApi.annotations({
76
+ identifier: "v2.session.question.reject",
77
+ summary: "Reject pending question request",
78
+ description: "Reject a pending question request owned by a session.",
79
+ }),
80
+ ),
81
+ )
82
+ .annotateMerge(
83
+ OpenApi.annotations({ title: "session questions", description: "Experimental session question routes." }),
84
+ )
@@ -0,0 +1,27 @@
1
+ import { Location } from "@neurocode-ai/schema/location"
2
+ import { Reference } from "@neurocode-ai/schema/reference"
3
+ import { Schema } from "effect"
4
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
5
+ import { LocationQuery, locationQueryOpenApi } from "./location"
6
+
7
+ export const ReferenceGroup = HttpApiGroup.make("server.reference")
8
+ .add(
9
+ HttpApiEndpoint.get("reference.list", "/api/reference", {
10
+ query: LocationQuery,
11
+ success: Location.response(Schema.Array(Reference.Info)),
12
+ })
13
+ .annotateMerge(locationQueryOpenApi)
14
+ .annotateMerge(
15
+ OpenApi.annotations({
16
+ identifier: "v2.reference.list",
17
+ summary: "List references",
18
+ description: "List references available in the requested location.",
19
+ }),
20
+ ),
21
+ )
22
+ .annotateMerge(
23
+ OpenApi.annotations({
24
+ title: "reference",
25
+ description: "Location-scoped project references.",
26
+ }),
27
+ )