summaryrefslogtreecommitdiffhomepage
path: root/packages/server/src
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-04-15 04:45:25 +0530
committerDax Raad <[email protected]>2026-04-14 20:55:39 -0400
commitfba752a5016a93ad7ea54890cf444de02a89a0f8 (patch)
treef56a7f7705574fb418c7773e915dceafdcc51a1f /packages/server/src
parent87b2a9d749ac39f47ea2d9d6806e32f224fe8ba9 (diff)
downloadopencode-fba752a5016a93ad7ea54890cf444de02a89a0f8.tar.gz
opencode-fba752a5016a93ad7ea54890cf444de02a89a0f8.zip
feat(server): extract question httpapi contract
Diffstat (limited to 'packages/server/src')
-rw-r--r--packages/server/src/definition/api.ts16
-rw-r--r--packages/server/src/definition/index.ts1
-rw-r--r--packages/server/src/definition/question.ts94
-rw-r--r--packages/server/src/index.ts1
-rw-r--r--packages/server/src/openapi.ts13
-rw-r--r--packages/server/src/types.ts17
6 files changed, 113 insertions, 29 deletions
diff --git a/packages/server/src/definition/api.ts b/packages/server/src/definition/api.ts
index 6eda4090e..e2f70196d 100644
--- a/packages/server/src/definition/api.ts
+++ b/packages/server/src/definition/api.ts
@@ -1,6 +1,12 @@
-import type { ServerApi } from "../types.js"
+import { HttpApi, OpenApi } from "effect/unstable/httpapi"
+import { questionApi } from "./question.js"
-export const api: ServerApi = {
- name: "opencode",
- groups: [],
-}
+export const api = HttpApi.make("opencode")
+ .addHttpApi(questionApi)
+ .annotateMerge(
+ OpenApi.annotations({
+ title: "opencode experimental HttpApi",
+ version: "0.0.1",
+ description: "Experimental HttpApi surface for selected instance routes.",
+ }),
+ )
diff --git a/packages/server/src/definition/index.ts b/packages/server/src/definition/index.ts
index 39cab2446..e9a52dc93 100644
--- a/packages/server/src/definition/index.ts
+++ b/packages/server/src/definition/index.ts
@@ -1 +1,2 @@
export { api } from "./api.js"
+export { questionApi, QuestionReply, QuestionRequest } from "./question.js"
diff --git a/packages/server/src/definition/question.ts b/packages/server/src/definition/question.ts
new file mode 100644
index 000000000..0d161e013
--- /dev/null
+++ b/packages/server/src/definition/question.ts
@@ -0,0 +1,94 @@
+import { Schema } from "effect"
+import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
+
+const root = "/experimental/httpapi/question"
+
+// Temporary transport-local schemas until canonical question schemas move into packages/core.
+export const QuestionID = Schema.String.annotate({ identifier: "QuestionID" })
+export const SessionID = Schema.String.annotate({ identifier: "SessionID" })
+export const MessageID = Schema.String.annotate({ identifier: "MessageID" })
+
+export class QuestionOption extends Schema.Class<QuestionOption>("QuestionOption")({
+ label: Schema.String.annotate({
+ description: "Display text (1-5 words, concise)",
+ }),
+ description: Schema.String.annotate({
+ description: "Explanation of choice",
+ }),
+}) {}
+
+const base = {
+ question: Schema.String.annotate({
+ description: "Complete question",
+ }),
+ header: Schema.String.annotate({
+ description: "Very short label (max 30 chars)",
+ }),
+ options: Schema.Array(QuestionOption).annotate({
+ description: "Available choices",
+ }),
+ multiple: Schema.optional(Schema.Boolean).annotate({
+ description: "Allow selecting multiple choices",
+ }),
+}
+
+export class QuestionInfo extends Schema.Class<QuestionInfo>("QuestionInfo")({
+ ...base,
+ custom: Schema.optional(Schema.Boolean).annotate({
+ description: "Allow typing a custom answer (default: true)",
+ }),
+}) {}
+
+export class QuestionTool extends Schema.Class<QuestionTool>("QuestionTool")({
+ messageID: MessageID,
+ callID: Schema.String,
+}) {}
+
+export class QuestionRequest extends Schema.Class<QuestionRequest>("QuestionRequest")({
+ id: QuestionID,
+ sessionID: SessionID,
+ questions: Schema.Array(QuestionInfo).annotate({
+ description: "Questions to ask",
+ }),
+ tool: Schema.optional(QuestionTool),
+}) {}
+
+export const QuestionAnswer = Schema.Array(Schema.String).annotate({ identifier: "QuestionAnswer" })
+
+export class QuestionReply extends Schema.Class<QuestionReply>("QuestionReply")({
+ answers: Schema.Array(QuestionAnswer).annotate({
+ description: "User answers in order of questions (each answer is an array of selected labels)",
+ }),
+}) {}
+
+export const questionApi = HttpApi.make("question").add(
+ HttpApiGroup.make("question")
+ .add(
+ HttpApiEndpoint.get("list", root, {
+ success: Schema.Array(QuestionRequest),
+ }).annotateMerge(
+ OpenApi.annotations({
+ identifier: "question.list",
+ summary: "List pending questions",
+ description: "Get all pending question requests across all sessions.",
+ }),
+ ),
+ HttpApiEndpoint.post("reply", `${root}/:requestID/reply`, {
+ params: { requestID: QuestionID },
+ payload: QuestionReply,
+ success: Schema.Boolean,
+ }).annotateMerge(
+ OpenApi.annotations({
+ identifier: "question.reply",
+ summary: "Reply to question request",
+ description: "Provide answers to a question request from the AI assistant.",
+ }),
+ ),
+ )
+ .annotateMerge(
+ OpenApi.annotations({
+ title: "question",
+ description: "Experimental HttpApi question routes.",
+ }),
+ ),
+)
diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index 2fbe31a0d..d5bdb6c8d 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -1,3 +1,4 @@
export { openapi } from "./openapi.js"
export { api } from "./definition/api.js"
+export { questionApi, QuestionReply, QuestionRequest } from "./definition/question.js"
export type { OpenApiSpec, ServerApi } from "./types.js"
diff --git a/packages/server/src/openapi.ts b/packages/server/src/openapi.ts
index c4ac95300..dda870d2b 100644
--- a/packages/server/src/openapi.ts
+++ b/packages/server/src/openapi.ts
@@ -1,14 +1,5 @@
+import { OpenApi } from "effect/unstable/httpapi"
import { api } from "./definition/api.js"
import type { OpenApiSpec } from "./types.js"
-export function openapi(): OpenApiSpec {
- return {
- openapi: "3.1.1",
- info: {
- title: api.name,
- version: "0.0.0",
- description: "Contract-first server package scaffold.",
- },
- paths: {},
- }
-}
+export const openapi = (): OpenApiSpec => OpenApi.fromApi(api)
diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts
index 8d337be42..9e89fe74c 100644
--- a/packages/server/src/types.ts
+++ b/packages/server/src/types.ts
@@ -1,14 +1,5 @@
-export interface ServerApi {
- readonly name: string
- readonly groups: readonly string[]
-}
+import type { HttpApi, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
-export interface OpenApiSpec {
- readonly openapi: string
- readonly info: {
- readonly title: string
- readonly version: string
- readonly description: string
- }
- readonly paths: Record<string, never>
-}
+export type ServerApi = HttpApi.HttpApi<string, HttpApiGroup.Any>
+
+export type OpenApiSpec = OpenApi.OpenAPISpec