Просмотр исходного кода

fix(sdk): preserve V2Event name for SSE streams (#34171)

Dax 1 месяц назад
Родитель
Сommit
6446b8ae3b

+ 1 - 0
bun.lock

@@ -1051,6 +1051,7 @@
     "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
     "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
     "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
+    "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
     "@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
     "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
     "@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch",

+ 2 - 1
package.json

@@ -153,6 +153,7 @@
     "@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch",
     "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
     "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
-    "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch"
+    "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch",
+    "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch"
   }
 }

+ 17 - 0
packages/opencode/test/server/httpapi-public-openapi.test.ts

@@ -10,6 +10,8 @@ type OpenApiSchema = {
   readonly enum?: readonly unknown[]
   readonly properties?: Record<string, OpenApiSchema>
   readonly required?: readonly string[]
+  readonly contentSchema?: OpenApiSchema
+  readonly contentMediaType?: string
 }
 type OpenApiResponse = {
   readonly description?: string
@@ -99,6 +101,21 @@ describe("PublicApi OpenAPI v2 errors", () => {
     })
   })
 
+  test("names the v2 event union without the SSE string wrapper collision", () => {
+    const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
+
+    expect(spec.components.schemas.V2Event1).toBeUndefined()
+    expect(spec.components.schemas.V2Event?.anyOf?.length).toBeGreaterThan(0)
+    expect(spec.components.schemas.V2EventStream).toMatchObject({
+      type: "string",
+      contentMediaType: "application/json",
+      contentSchema: { $ref: "#/components/schemas/V2Event" },
+    })
+    expect(spec.paths["/api/event"]?.get?.responses?.["200"]?.content?.["text/event-stream"]?.schema).toEqual({
+      $ref: "#/components/schemas/V2Event",
+    })
+  })
+
   test("preserves /api auth responses", () => {
     const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
 

+ 4 - 4
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -2766,7 +2766,7 @@ export type SessionHistory = {
   hasMore: boolean
 }
 
-export type SessionDurableEvent1 = string
+export type SessionDurableEventStream = string
 
 export type SessionMessagesResponse = {
   data: Array<SessionMessage>
@@ -2847,7 +2847,7 @@ export type QuestionRejected2 = {
   }
 }
 
-export type V2Event1 =
+export type V2Event =
   | ModelsDevRefreshed
   | IntegrationUpdated
   | IntegrationConnectionUpdated
@@ -2937,7 +2937,7 @@ export type V2Event1 =
   | ServerConnected
   | GlobalDisposed
 
-export type V2Event = string
+export type V2EventStream = string
 
 export type ForbiddenError = {
   _tag: "ForbiddenError"
@@ -11901,7 +11901,7 @@ export type V2SessionEventsResponses = {
   200: {
     id: string
     event: string
-    data: SessionDurableEvent1
+    data: SessionDurableEventStream
   }
 }
 

+ 5 - 5
packages/sdk/openapi.json

@@ -11260,7 +11260,7 @@
                       "type": "string"
                     },
                     "data": {
-                      "$ref": "#/components/schemas/SessionDurableEvent1"
+                      "$ref": "#/components/schemas/SessionDurableEventStream"
                     }
                   },
                   "required": ["id", "event", "data"],
@@ -23732,7 +23732,7 @@
         "required": ["data", "hasMore"],
         "additionalProperties": false
       },
-      "SessionDurableEvent1": {
+      "SessionDurableEventStream": {
         "type": "string",
         "contentSchema": {
           "$ref": "#/components/schemas/SessionDurableEvent"
@@ -23975,7 +23975,7 @@
         "required": ["id", "type", "data"],
         "additionalProperties": false
       },
-      "V2Event1": {
+      "V2Event": {
         "anyOf": [
           {
             "$ref": "#/components/schemas/Models-devRefreshed"
@@ -24243,10 +24243,10 @@
           }
         ]
       },
-      "V2Event": {
+      "V2EventStream": {
         "type": "string",
         "contentSchema": {
-          "$ref": "#/components/schemas/V2Event1"
+          "$ref": "#/components/schemas/V2Event"
         },
         "contentMediaType": "application/json"
       },

+ 3 - 3
packages/tui/src/context/data.tsx

@@ -16,7 +16,7 @@ import type {
   SessionMessageAssistantTool,
   SessionV2Info,
   SkillV2Info,
-  V2Event1,
+  V2Event,
 } from "@opencode-ai/sdk/v2"
 import { createStore, produce } from "solid-js/store"
 import { createSimpleContext } from "./helper"
@@ -121,7 +121,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
       },
     }
 
-    function handleEvent(event: V2Event1) {
+    function handleEvent(event: V2Event) {
       switch (event.type) {
         case "catalog.updated":
           void Promise.all([
@@ -408,7 +408,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           ...event,
           data: event.properties,
           location: { directory: metadata.directory, workspaceID: metadata.workspace },
-        } as V2Event1)
+        } as V2Event)
       })
       onCleanup(unsub)
     })

+ 57 - 0
patches/effect@4.0.0-beta.83.patch

@@ -0,0 +1,57 @@
+diff --git a/dist/unstable/httpapi/HttpApiSchema.js b/dist/unstable/httpapi/HttpApiSchema.js
+index ecb6603d1ee4e89e92174b3a1100e8c9c720b3f3..08b832b6b3e9e4d10df156eca36b16f571f67f6e 100644
+--- a/dist/unstable/httpapi/HttpApiSchema.js
++++ b/dist/unstable/httpapi/HttpApiSchema.js
+@@ -151,7 +151,7 @@ export const StreamSse = options => {
+   const events = options.events ?? (options.data === undefined ? undefined : Schema.Struct({
+     id: Schema.UndefinedOr(Schema.String),
+     event: Schema.String,
+-    data: Schema.fromJsonString(options.data)
++    data: sseDataJsonSchema(options.data)
+   }));
+   if (events === undefined) {
+     throw new Error("StreamSse requires either an events schema or a data schema");
+@@ -166,6 +166,14 @@ export const StreamSse = options => {
+     error: options.error ?? Schema.Never
+   });
+ };
++const sseDataJsonSchema = data => {
++  const identifier = SchemaAST.resolveIdentifier(data.ast);
++  return identifier === undefined ? Schema.fromJsonString(data) : Schema.fromJsonString(data).annotate({
++    // The SSE transport field is a JSON string. Give that wrapper its own
++    // OpenAPI identifier so it does not claim the decoded data schema's name.
++    identifier: `${identifier}Stream`
++  });
++};
+ /**
+  * Creates a streaming `Uint8Array` success response schema.
+  *
+diff --git a/src/unstable/httpapi/HttpApiSchema.ts b/src/unstable/httpapi/HttpApiSchema.ts
+index f2920365cf0328146523ce2e2dba04d6a8809a4a..5482d635cdf01a72063ff683d244fab891910211 100644
+--- a/src/unstable/httpapi/HttpApiSchema.ts
++++ b/src/unstable/httpapi/HttpApiSchema.ts
+@@ -433,7 +433,7 @@ export const StreamSse: {
+   const events = options.events ?? (options.data === undefined ? undefined : Schema.Struct({
+     id: Schema.UndefinedOr(Schema.String),
+     event: Schema.String,
+-    data: Schema.fromJsonString(options.data)
++    data: sseDataJsonSchema(options.data)
+   }))
+   if (events === undefined) {
+     throw new Error("StreamSse requires either an events schema or a data schema")
+@@ -449,6 +449,15 @@ export const StreamSse: {
+   })
+ }
+ 
++const sseDataJsonSchema = (data: Schema.Constraint) => {
++  const identifier = SchemaAST.resolveIdentifier(data.ast)
++  return identifier === undefined ? Schema.fromJsonString(data) : Schema.fromJsonString(data).annotate({
++    // The SSE transport field is a JSON string. Give that wrapper its own
++    // OpenAPI identifier so it does not claim the decoded data schema's name.
++    identifier: `${identifier}Stream`
++  })
++}
++
+ /**
+  * Creates a streaming `Uint8Array` success response schema.
+  *