@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.
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@neurocode-ai/protocol",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "exports": {
7
+ "./*": "./src/*.ts"
8
+ },
9
+ "scripts": {
10
+ "typecheck": "tsgo --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@neurocode-ai/schema": "workspace:*",
14
+ "effect": "catalog:"
15
+ },
16
+ "devDependencies": {
17
+ "@tsconfig/bun": "catalog:",
18
+ "@types/bun": "catalog:",
19
+ "@typescript/native-preview": "catalog:"
20
+ },
21
+ "version": "1.18.9"
22
+ }
package/src/api.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { Context } from "effect"
2
+ import { HttpApi, HttpApiGroup, HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi"
3
+ import { SchemaErrorMiddleware } from "./middleware/schema-error"
4
+ import { MessageGroup } from "./groups/message"
5
+ import { ModelGroup } from "./groups/model"
6
+ import { ProviderGroup } from "./groups/provider"
7
+ import { makeSessionGroup } from "./groups/session"
8
+ import { makePermissionGroup } from "./groups/permission"
9
+ import { FileSystemGroup } from "./groups/fs"
10
+ import { CommandGroup } from "./groups/command"
11
+ import { SkillGroup } from "./groups/skill"
12
+ import { EventGroup, makeEventGroup } from "./groups/event"
13
+ import type { Definition } from "@neurocode-ai/schema/event"
14
+ import { AgentGroup } from "./groups/agent"
15
+ import { HealthGroup } from "./groups/health"
16
+ import { PtyGroup } from "./groups/pty"
17
+ import { makeQuestionGroup } from "./groups/question"
18
+ import { ReferenceGroup } from "./groups/reference"
19
+ import { Authorization } from "./middleware/authorization"
20
+ import { LocationGroup } from "./groups/location"
21
+ import { IntegrationGroup } from "./groups/integration"
22
+ import { CredentialGroup } from "./groups/credential"
23
+ import { ProjectCopyGroup } from "./groups/project-copy"
24
+
25
+ // Protocol owns middleware placement, while Server injects concrete keys so Core service identities stay downstream.
26
+ const makeApiFromGroup = <
27
+ const Group extends HttpApiGroup.Any,
28
+ LocationId extends HttpApiMiddleware.AnyId,
29
+ LocationService,
30
+ SessionLocationId extends HttpApiMiddleware.AnyId,
31
+ SessionLocationService,
32
+ >(
33
+ eventGroup: Group,
34
+ locationMiddleware: Context.Key<LocationId, LocationService>,
35
+ sessionLocationMiddleware: Context.Key<SessionLocationId, SessionLocationService>,
36
+ ) =>
37
+ HttpApi.make("server")
38
+ .add(HealthGroup)
39
+ .add(LocationGroup.middleware(locationMiddleware))
40
+ .add(AgentGroup.middleware(locationMiddleware))
41
+ .add(makeSessionGroup(sessionLocationMiddleware))
42
+ .add(MessageGroup.middleware(sessionLocationMiddleware))
43
+ .add(ModelGroup.middleware(locationMiddleware))
44
+ .add(ProviderGroup.middleware(locationMiddleware))
45
+ .add(IntegrationGroup.middleware(locationMiddleware))
46
+ .add(CredentialGroup.middleware(locationMiddleware))
47
+ .add(makePermissionGroup(locationMiddleware, sessionLocationMiddleware))
48
+ .add(FileSystemGroup.middleware(locationMiddleware))
49
+ .add(CommandGroup.middleware(locationMiddleware))
50
+ .add(SkillGroup.middleware(locationMiddleware))
51
+ .add(eventGroup)
52
+ .add(PtyGroup.middleware(locationMiddleware))
53
+ .add(makeQuestionGroup(locationMiddleware, sessionLocationMiddleware))
54
+ .add(ReferenceGroup.middleware(locationMiddleware))
55
+ .add(ProjectCopyGroup.middleware(locationMiddleware))
56
+ .annotateMerge(
57
+ OpenApi.annotations({
58
+ title: "neurocode HttpApi",
59
+ version: "0.0.1",
60
+ description: "Experimental HttpApi surface for selected instance routes.",
61
+ }),
62
+ )
63
+ .middleware(Authorization)
64
+ .middleware(SchemaErrorMiddleware)
65
+
66
+ export const makeApi = <
67
+ LocationId extends HttpApiMiddleware.AnyId,
68
+ LocationService,
69
+ SessionLocationId extends HttpApiMiddleware.AnyId,
70
+ SessionLocationService,
71
+ >(options: {
72
+ readonly definitions: ReadonlyArray<Definition>
73
+ readonly locationMiddleware: Context.Key<LocationId, LocationService>
74
+ readonly sessionLocationMiddleware: Context.Key<SessionLocationId, SessionLocationService>
75
+ }) =>
76
+ makeApiFromGroup(makeEventGroup(options.definitions), options.locationMiddleware, options.sessionLocationMiddleware)
77
+
78
+ export const makeDefaultApi = <
79
+ LocationId extends HttpApiMiddleware.AnyId,
80
+ LocationService,
81
+ SessionLocationId extends HttpApiMiddleware.AnyId,
82
+ SessionLocationService,
83
+ >(options: {
84
+ readonly locationMiddleware: Context.Key<LocationId, LocationService>
85
+ readonly sessionLocationMiddleware: Context.Key<SessionLocationId, SessionLocationService>
86
+ }) => makeApiFromGroup(EventGroup, options.locationMiddleware, options.sessionLocationMiddleware)
package/src/errors.ts ADDED
@@ -0,0 +1,111 @@
1
+ import { Schema } from "effect"
2
+
3
+ export class InvalidRequestError extends Schema.TaggedErrorClass<InvalidRequestError>()(
4
+ "InvalidRequestError",
5
+ {
6
+ message: Schema.String,
7
+ kind: Schema.optional(Schema.String),
8
+ field: Schema.optional(Schema.String),
9
+ },
10
+ { httpApiStatus: 400 },
11
+ ) {}
12
+
13
+ export class UnauthorizedError extends Schema.TaggedErrorClass<UnauthorizedError>()(
14
+ "UnauthorizedError",
15
+ { message: Schema.String },
16
+ { httpApiStatus: 401 },
17
+ ) {}
18
+
19
+ export class ConflictError extends Schema.TaggedErrorClass<ConflictError>()(
20
+ "ConflictError",
21
+ {
22
+ message: Schema.String,
23
+ resource: Schema.optional(Schema.String),
24
+ },
25
+ { httpApiStatus: 409 },
26
+ ) {}
27
+
28
+ export class ServiceUnavailableError extends Schema.TaggedErrorClass<ServiceUnavailableError>()(
29
+ "ServiceUnavailableError",
30
+ {
31
+ message: Schema.String,
32
+ service: Schema.optional(Schema.String),
33
+ },
34
+ { httpApiStatus: 503 },
35
+ ) {}
36
+
37
+ export class UnknownError extends Schema.TaggedErrorClass<UnknownError>()(
38
+ "UnknownError",
39
+ {
40
+ message: Schema.String,
41
+ ref: Schema.optional(Schema.String),
42
+ },
43
+ { httpApiStatus: 500 },
44
+ ) {}
45
+
46
+ export class ProviderNotFoundError extends Schema.TaggedErrorClass<ProviderNotFoundError>()(
47
+ "ProviderNotFoundError",
48
+ {
49
+ providerID: Schema.String,
50
+ message: Schema.String,
51
+ },
52
+ { httpApiStatus: 404 },
53
+ ) {}
54
+
55
+ export class SessionNotFoundError extends Schema.TaggedErrorClass<SessionNotFoundError>()(
56
+ "SessionNotFoundError",
57
+ {
58
+ sessionID: Schema.String,
59
+ message: Schema.String,
60
+ },
61
+ { httpApiStatus: 404 },
62
+ ) {}
63
+
64
+ export class MessageNotFoundError extends Schema.TaggedErrorClass<MessageNotFoundError>()(
65
+ "MessageNotFoundError",
66
+ {
67
+ sessionID: Schema.String,
68
+ messageID: Schema.String,
69
+ message: Schema.String,
70
+ },
71
+ { httpApiStatus: 404 },
72
+ ) {}
73
+
74
+ export class InvalidCursorError extends Schema.TaggedErrorClass<InvalidCursorError>()(
75
+ "InvalidCursorError",
76
+ { message: Schema.String },
77
+ { httpApiStatus: 400 },
78
+ ) {}
79
+
80
+ export class PermissionNotFoundError extends Schema.TaggedErrorClass<PermissionNotFoundError>()(
81
+ "PermissionNotFoundError",
82
+ {
83
+ requestID: Schema.String,
84
+ message: Schema.String,
85
+ },
86
+ { httpApiStatus: 404 },
87
+ ) {}
88
+
89
+ export class QuestionNotFoundError extends Schema.TaggedErrorClass<QuestionNotFoundError>()(
90
+ "QuestionNotFoundError",
91
+ {
92
+ requestID: Schema.String,
93
+ message: Schema.String,
94
+ },
95
+ { httpApiStatus: 404 },
96
+ ) {}
97
+
98
+ export class ForbiddenError extends Schema.TaggedErrorClass<ForbiddenError>()(
99
+ "ForbiddenError",
100
+ { message: Schema.String },
101
+ { httpApiStatus: 403 },
102
+ ) {}
103
+
104
+ export class PtyNotFoundError extends Schema.TaggedErrorClass<PtyNotFoundError>()(
105
+ "PtyNotFoundError",
106
+ {
107
+ ptyID: Schema.String,
108
+ message: Schema.String,
109
+ },
110
+ { httpApiStatus: 404 },
111
+ ) {}
@@ -0,0 +1,20 @@
1
+ import { Agent } from "@neurocode-ai/schema/agent"
2
+ import { Location } from "@neurocode-ai/schema/location"
3
+ import { Schema } from "effect"
4
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
5
+ import { LocationQuery, locationQueryOpenApi } from "./location"
6
+
7
+ export const AgentGroup = HttpApiGroup.make("server.agent").add(
8
+ HttpApiEndpoint.get("agent.list", "/api/agent", {
9
+ query: LocationQuery,
10
+ success: Location.response(Schema.Array(Agent.Info)),
11
+ })
12
+ .annotateMerge(locationQueryOpenApi)
13
+ .annotateMerge(
14
+ OpenApi.annotations({
15
+ identifier: "v2.agent.list",
16
+ summary: "List agents",
17
+ description: "Retrieve currently registered agents.",
18
+ }),
19
+ ),
20
+ )
@@ -0,0 +1,27 @@
1
+ import { Command } from "@neurocode-ai/schema/command"
2
+ import { Location } from "@neurocode-ai/schema/location"
3
+ import { Schema } from "effect"
4
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
5
+ import { LocationQuery, locationQueryOpenApi } from "./location"
6
+
7
+ export const CommandGroup = HttpApiGroup.make("server.command")
8
+ .add(
9
+ HttpApiEndpoint.get("command.list", "/api/command", {
10
+ query: LocationQuery,
11
+ success: Location.response(Schema.Array(Command.Info)),
12
+ })
13
+ .annotateMerge(locationQueryOpenApi)
14
+ .annotateMerge(
15
+ OpenApi.annotations({
16
+ identifier: "v2.command.list",
17
+ summary: "List commands",
18
+ description: "Retrieve currently registered commands.",
19
+ }),
20
+ ),
21
+ )
22
+ .annotateMerge(
23
+ OpenApi.annotations({
24
+ title: "commands",
25
+ description: "Experimental command routes.",
26
+ }),
27
+ )
@@ -0,0 +1,37 @@
1
+ import { Credential } from "@neurocode-ai/schema/credential"
2
+ import { Schema } from "effect"
3
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
4
+ import { LocationQuery, locationQueryOpenApi } from "./location"
5
+
6
+ export const CredentialGroup = HttpApiGroup.make("server.credential")
7
+ .add(
8
+ HttpApiEndpoint.patch("credential.update", "/api/credential/:credentialID", {
9
+ params: { credentialID: Credential.ID },
10
+ query: LocationQuery,
11
+ payload: Schema.Struct({ label: Schema.String }),
12
+ success: HttpApiSchema.NoContent,
13
+ })
14
+ .annotateMerge(locationQueryOpenApi)
15
+ .annotateMerge(
16
+ OpenApi.annotations({
17
+ identifier: "v2.credential.update",
18
+ summary: "Update credential",
19
+ description: "Update a stored credential label.",
20
+ }),
21
+ ),
22
+ )
23
+ .add(
24
+ HttpApiEndpoint.delete("credential.remove", "/api/credential/:credentialID", {
25
+ params: { credentialID: Credential.ID },
26
+ query: LocationQuery,
27
+ success: HttpApiSchema.NoContent,
28
+ })
29
+ .annotateMerge(locationQueryOpenApi)
30
+ .annotateMerge(
31
+ OpenApi.annotations({
32
+ identifier: "v2.credential.remove",
33
+ summary: "Remove credential",
34
+ description: "Remove a stored integration credential.",
35
+ }),
36
+ ),
37
+ )
@@ -0,0 +1,56 @@
1
+ import { Event } from "@neurocode-ai/schema/event"
2
+ import { EventManifest } from "@neurocode-ai/schema/event-manifest"
3
+ import { Location } from "@neurocode-ai/schema/location"
4
+ import type { Definition } from "@neurocode-ai/schema/event"
5
+ import { Schema } from "effect"
6
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
7
+
8
+ const fields = {
9
+ id: Event.ID,
10
+ metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
11
+ durable: Schema.optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Int, version: Schema.Int })),
12
+ location: Schema.optional(Location.Ref),
13
+ }
14
+
15
+ const schema = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) =>
16
+ Schema.Union([
17
+ ...definitions,
18
+ ...(definitions.some((definition) => definition.type === "server.connected")
19
+ ? []
20
+ : [
21
+ Schema.Struct({
22
+ ...fields,
23
+ type: Schema.Literal("server.connected"),
24
+ data: Schema.Struct({}),
25
+ }).annotate({ identifier: "V2Event.server.connected" }),
26
+ ]),
27
+ ]).annotate({ identifier: "V2Event" })
28
+
29
+ const make = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) => {
30
+ const EventSchema = schema(definitions)
31
+ return {
32
+ schema: EventSchema,
33
+ group: HttpApiGroup.make("server.event")
34
+ .add(
35
+ HttpApiEndpoint.get("event.subscribe", "/api/event", {
36
+ success: HttpApiSchema.StreamSse({ data: EventSchema }),
37
+ }).annotateMerge(
38
+ OpenApi.annotations({
39
+ identifier: "v2.event.subscribe",
40
+ summary: "Subscribe to events",
41
+ description: "Subscribe to native event payloads for the server.",
42
+ }),
43
+ ),
44
+ )
45
+ .annotateMerge(OpenApi.annotations({ title: "events", description: "Experimental event stream route." })),
46
+ }
47
+ }
48
+
49
+ export const makeEventGroup = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) =>
50
+ make(definitions).group
51
+
52
+ const event = make(EventManifest.ServerDefinitions)
53
+ export const EventGroup = event.group
54
+ export const OpenCodeEvent = event.schema
55
+ export type OpenCodeEvent = typeof OpenCodeEvent.Type
56
+ export type OpenCodeEventEncoded = typeof OpenCodeEvent.Encoded
@@ -0,0 +1,68 @@
1
+ import { FileSystem } from "@neurocode-ai/schema/filesystem"
2
+ import { Location } from "@neurocode-ai/schema/location"
3
+ import { PositiveInt, RelativePath } from "@neurocode-ai/schema/schema"
4
+ import { Schema } from "effect"
5
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
6
+ import { LocationQuery, locationQueryOpenApi } from "./location"
7
+
8
+ const ListQuery = Schema.Struct({
9
+ ...LocationQuery.fields,
10
+ path: RelativePath.pipe(Schema.optional),
11
+ })
12
+
13
+ const FindQuery = Schema.Struct({
14
+ ...LocationQuery.fields,
15
+ query: FileSystem.FindInput.fields.query,
16
+ type: FileSystem.FindInput.fields.type,
17
+ limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional),
18
+ })
19
+
20
+ export const FileSystemGroup = HttpApiGroup.make("server.fs")
21
+ .add(
22
+ HttpApiEndpoint.get("fs.read", "/api/fs/read/*", {
23
+ query: LocationQuery,
24
+ success: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()),
25
+ })
26
+ .annotateMerge(locationQueryOpenApi)
27
+ .annotateMerge(
28
+ OpenApi.annotations({
29
+ identifier: "v2.fs.read",
30
+ summary: "Read file",
31
+ description: "Serve one file relative to the requested location.",
32
+ }),
33
+ ),
34
+ )
35
+ .add(
36
+ HttpApiEndpoint.get("fs.list", "/api/fs/list", {
37
+ query: ListQuery,
38
+ success: Location.response(Schema.Array(FileSystem.Entry)),
39
+ })
40
+ .annotateMerge(locationQueryOpenApi)
41
+ .annotateMerge(
42
+ OpenApi.annotations({
43
+ identifier: "v2.fs.list",
44
+ summary: "List directory",
45
+ description: "List direct children of one directory relative to the requested location.",
46
+ }),
47
+ ),
48
+ )
49
+ .add(
50
+ HttpApiEndpoint.get("fs.find", "/api/fs/find", {
51
+ query: FindQuery,
52
+ success: Location.response(Schema.Array(FileSystem.Entry)),
53
+ })
54
+ .annotateMerge(locationQueryOpenApi)
55
+ .annotateMerge(
56
+ OpenApi.annotations({
57
+ identifier: "v2.fs.find",
58
+ summary: "Find files",
59
+ description: "Find recursively ranked filesystem entries relative to the requested location.",
60
+ }),
61
+ ),
62
+ )
63
+ .annotateMerge(
64
+ OpenApi.annotations({
65
+ title: "filesystem",
66
+ description: "Experimental location-scoped filesystem routes.",
67
+ }),
68
+ )
@@ -0,0 +1,14 @@
1
+ import { Schema } from "effect"
2
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
3
+
4
+ export const HealthGroup = HttpApiGroup.make("server.health").add(
5
+ HttpApiEndpoint.get("health.get", "/api/health", {
6
+ success: Schema.Struct({ healthy: Schema.Literal(true) }),
7
+ }).annotateMerge(
8
+ OpenApi.annotations({
9
+ identifier: "v2.health.get",
10
+ summary: "Check server health",
11
+ description: "Check whether the API server is ready to accept requests.",
12
+ }),
13
+ ),
14
+ )
@@ -0,0 +1,130 @@
1
+ import { Integration } from "@neurocode-ai/schema/integration"
2
+ import { Location } from "@neurocode-ai/schema/location"
3
+ import { Schema } from "effect"
4
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
5
+ import { InvalidRequestError } from "../errors"
6
+ import { LocationQuery, locationQueryOpenApi } from "./location"
7
+
8
+ const Inputs = Schema.Record(Schema.String, Schema.String)
9
+
10
+ export const IntegrationGroup = HttpApiGroup.make("server.integration")
11
+ .add(
12
+ HttpApiEndpoint.get("integration.list", "/api/integration", {
13
+ query: LocationQuery,
14
+ success: Location.response(Schema.Array(Integration.Info)),
15
+ })
16
+ .annotateMerge(locationQueryOpenApi)
17
+ .annotateMerge(
18
+ OpenApi.annotations({
19
+ identifier: "v2.integration.list",
20
+ summary: "List integrations",
21
+ description: "Retrieve available integrations and their authentication methods.",
22
+ }),
23
+ ),
24
+ )
25
+ .add(
26
+ HttpApiEndpoint.get("integration.get", "/api/integration/:integrationID", {
27
+ params: { integrationID: Integration.ID },
28
+ query: LocationQuery,
29
+ success: Location.response(Schema.UndefinedOr(Integration.Info)),
30
+ })
31
+ .annotateMerge(locationQueryOpenApi)
32
+ .annotateMerge(
33
+ OpenApi.annotations({
34
+ identifier: "v2.integration.get",
35
+ summary: "Get integration",
36
+ description: "Retrieve one integration and its authentication methods.",
37
+ }),
38
+ ),
39
+ )
40
+ .add(
41
+ HttpApiEndpoint.post("integration.connect.key", "/api/integration/:integrationID/connect/key", {
42
+ params: { integrationID: Integration.ID },
43
+ query: LocationQuery,
44
+ payload: Schema.Struct({
45
+ key: Schema.String,
46
+ label: Schema.optional(Schema.String),
47
+ }),
48
+ success: HttpApiSchema.NoContent,
49
+ error: InvalidRequestError,
50
+ })
51
+ .annotateMerge(locationQueryOpenApi)
52
+ .annotateMerge(
53
+ OpenApi.annotations({
54
+ identifier: "v2.integration.connect.key",
55
+ summary: "Connect with key",
56
+ description: "Run a key authentication method and store the resulting credential.",
57
+ }),
58
+ ),
59
+ )
60
+ .add(
61
+ HttpApiEndpoint.post("integration.connect.oauth", "/api/integration/:integrationID/connect/oauth", {
62
+ params: { integrationID: Integration.ID },
63
+ query: LocationQuery,
64
+ payload: Schema.Struct({
65
+ methodID: Integration.MethodID,
66
+ inputs: Inputs,
67
+ label: Schema.optional(Schema.String),
68
+ }),
69
+ success: Location.response(Integration.Attempt),
70
+ error: InvalidRequestError,
71
+ })
72
+ .annotateMerge(locationQueryOpenApi)
73
+ .annotateMerge(
74
+ OpenApi.annotations({
75
+ identifier: "v2.integration.connect.oauth",
76
+ summary: "Begin OAuth connection",
77
+ description: "Start an OAuth attempt and return the authorization details.",
78
+ }),
79
+ ),
80
+ )
81
+ .add(
82
+ HttpApiEndpoint.get("integration.attempt.status", "/api/integration/attempt/:attemptID", {
83
+ params: { attemptID: Integration.AttemptID },
84
+ query: LocationQuery,
85
+ success: Location.response(Integration.AttemptStatus),
86
+ })
87
+ .annotateMerge(locationQueryOpenApi)
88
+ .annotateMerge(
89
+ OpenApi.annotations({
90
+ identifier: "v2.integration.attempt.status",
91
+ summary: "Get OAuth attempt status",
92
+ description: "Poll the current status of an OAuth attempt.",
93
+ }),
94
+ ),
95
+ )
96
+ .add(
97
+ HttpApiEndpoint.post("integration.attempt.complete", "/api/integration/attempt/:attemptID/complete", {
98
+ params: { attemptID: Integration.AttemptID },
99
+ query: LocationQuery,
100
+ payload: Schema.Struct({ code: Schema.optional(Schema.String) }),
101
+ success: HttpApiSchema.NoContent,
102
+ error: InvalidRequestError,
103
+ })
104
+ .annotateMerge(locationQueryOpenApi)
105
+ .annotateMerge(
106
+ OpenApi.annotations({
107
+ identifier: "v2.integration.attempt.complete",
108
+ summary: "Complete OAuth connection",
109
+ description: "Complete a code-based OAuth attempt and store the resulting credential.",
110
+ }),
111
+ ),
112
+ )
113
+ .add(
114
+ HttpApiEndpoint.delete("integration.attempt.cancel", "/api/integration/attempt/:attemptID", {
115
+ params: { attemptID: Integration.AttemptID },
116
+ query: LocationQuery,
117
+ success: HttpApiSchema.NoContent,
118
+ })
119
+ .annotateMerge(locationQueryOpenApi)
120
+ .annotateMerge(
121
+ OpenApi.annotations({
122
+ identifier: "v2.integration.attempt.cancel",
123
+ summary: "Cancel OAuth connection",
124
+ description: "Cancel an OAuth attempt and release its resources.",
125
+ }),
126
+ ),
127
+ )
128
+ .annotateMerge(
129
+ OpenApi.annotations({ title: "integrations", description: "Integration discovery and authentication routes." }),
130
+ )
@@ -0,0 +1,42 @@
1
+ import { Location } from "@neurocode-ai/schema/location"
2
+ import { Schema } from "effect"
3
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
4
+
5
+ export const LocationQuery = Schema.Struct({
6
+ location: Schema.optional(
7
+ Schema.Struct({
8
+ directory: Schema.optional(Schema.String),
9
+ workspace: Schema.optional(Schema.String),
10
+ }),
11
+ ),
12
+ }).annotate({ identifier: "LocationQuery" })
13
+
14
+ export const locationQueryOpenApi = OpenApi.annotations({
15
+ transform: (operation) => {
16
+ const parameters = operation.parameters
17
+ if (!Array.isArray(parameters)) return operation
18
+ return {
19
+ ...operation,
20
+ parameters: parameters.map((parameter) =>
21
+ parameter?.name === "location" && parameter?.in === "query"
22
+ ? { ...parameter, style: "deepObject", explode: true }
23
+ : parameter,
24
+ ),
25
+ }
26
+ },
27
+ })
28
+
29
+ export const LocationGroup = HttpApiGroup.make("server.location").add(
30
+ HttpApiEndpoint.get("location.get", "/api/location", {
31
+ query: LocationQuery,
32
+ success: Location.Info,
33
+ })
34
+ .annotateMerge(locationQueryOpenApi)
35
+ .annotateMerge(
36
+ OpenApi.annotations({
37
+ identifier: "v2.location.get",
38
+ summary: "Get location",
39
+ description: "Resolve the requested location or the server default location.",
40
+ }),
41
+ ),
42
+ )
@@ -0,0 +1,51 @@
1
+ import { Session } from "@neurocode-ai/schema/session"
2
+ import { SessionMessage } from "@neurocode-ai/schema/session-message"
3
+ import { Schema } from "effect"
4
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
5
+ import { InvalidCursorError, SessionNotFoundError, UnknownError } from "../errors"
6
+
7
+ export const SessionMessagesQuery = Schema.Struct({
8
+ limit: Schema.optional(
9
+ Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(200)),
10
+ ).annotate({
11
+ description: "Maximum number of messages to return. When omitted, the endpoint returns its default page size.",
12
+ }),
13
+ order: Schema.optional(Schema.Union([Schema.Literal("asc"), Schema.Literal("desc")])).annotate({
14
+ description: "Message order for the first page. Use desc for newest first or asc for oldest first.",
15
+ }),
16
+ cursor: Schema.optional(
17
+ Schema.String.annotate({
18
+ description:
19
+ "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response. Do not combine with order.",
20
+ }),
21
+ ),
22
+ }).annotate({ identifier: "SessionMessagesQuery" })
23
+
24
+ export const MessageGroup = HttpApiGroup.make("server.message")
25
+ .add(
26
+ HttpApiEndpoint.get("session.messages", "/api/session/:sessionID/message", {
27
+ params: { sessionID: Session.ID },
28
+ query: SessionMessagesQuery,
29
+ success: Schema.Struct({
30
+ data: Schema.Array(SessionMessage.Message),
31
+ cursor: Schema.Struct({
32
+ previous: Schema.String.pipe(Schema.optional),
33
+ next: Schema.String.pipe(Schema.optional),
34
+ }),
35
+ }).annotate({ identifier: "SessionMessagesResponse" }),
36
+ error: [InvalidCursorError, SessionNotFoundError, UnknownError],
37
+ }).annotateMerge(
38
+ OpenApi.annotations({
39
+ identifier: "v2.session.messages",
40
+ summary: "Get session messages",
41
+ description:
42
+ "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.",
43
+ }),
44
+ ),
45
+ )
46
+ .annotateMerge(
47
+ OpenApi.annotations({
48
+ title: "messages",
49
+ description: "Experimental message routes.",
50
+ }),
51
+ )