fix(sdk): preserve V2Event name for SSE streams (#34171)
This commit is contained in:
1
bun.lock
1
bun.lock
@@ -1051,6 +1051,7 @@
|
|||||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
||||||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.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",
|
"@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",
|
"@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",
|
"@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",
|
"@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch",
|
||||||
|
|||||||
@@ -153,6 +153,7 @@
|
|||||||
"@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch",
|
"@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",
|
"@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",
|
"@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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ type OpenApiSchema = {
|
|||||||
readonly enum?: readonly unknown[]
|
readonly enum?: readonly unknown[]
|
||||||
readonly properties?: Record<string, OpenApiSchema>
|
readonly properties?: Record<string, OpenApiSchema>
|
||||||
readonly required?: readonly string[]
|
readonly required?: readonly string[]
|
||||||
|
readonly contentSchema?: OpenApiSchema
|
||||||
|
readonly contentMediaType?: string
|
||||||
}
|
}
|
||||||
type OpenApiResponse = {
|
type OpenApiResponse = {
|
||||||
readonly description?: string
|
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", () => {
|
test("preserves /api auth responses", () => {
|
||||||
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
|
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
|
||||||
|
|
||||||
|
|||||||
@@ -2766,7 +2766,7 @@ export type SessionHistory = {
|
|||||||
hasMore: boolean
|
hasMore: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionDurableEvent1 = string
|
export type SessionDurableEventStream = string
|
||||||
|
|
||||||
export type SessionMessagesResponse = {
|
export type SessionMessagesResponse = {
|
||||||
data: Array<SessionMessage>
|
data: Array<SessionMessage>
|
||||||
@@ -2847,7 +2847,7 @@ export type QuestionRejected2 = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type V2Event1 =
|
export type V2Event =
|
||||||
| ModelsDevRefreshed
|
| ModelsDevRefreshed
|
||||||
| IntegrationUpdated
|
| IntegrationUpdated
|
||||||
| IntegrationConnectionUpdated
|
| IntegrationConnectionUpdated
|
||||||
@@ -2937,7 +2937,7 @@ export type V2Event1 =
|
|||||||
| ServerConnected
|
| ServerConnected
|
||||||
| GlobalDisposed
|
| GlobalDisposed
|
||||||
|
|
||||||
export type V2Event = string
|
export type V2EventStream = string
|
||||||
|
|
||||||
export type ForbiddenError = {
|
export type ForbiddenError = {
|
||||||
_tag: "ForbiddenError"
|
_tag: "ForbiddenError"
|
||||||
@@ -11901,7 +11901,7 @@ export type V2SessionEventsResponses = {
|
|||||||
200: {
|
200: {
|
||||||
id: string
|
id: string
|
||||||
event: string
|
event: string
|
||||||
data: SessionDurableEvent1
|
data: SessionDurableEventStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11260,7 +11260,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/components/schemas/SessionDurableEvent1"
|
"$ref": "#/components/schemas/SessionDurableEventStream"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["id", "event", "data"],
|
"required": ["id", "event", "data"],
|
||||||
@@ -23732,7 +23732,7 @@
|
|||||||
"required": ["data", "hasMore"],
|
"required": ["data", "hasMore"],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"SessionDurableEvent1": {
|
"SessionDurableEventStream": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"contentSchema": {
|
"contentSchema": {
|
||||||
"$ref": "#/components/schemas/SessionDurableEvent"
|
"$ref": "#/components/schemas/SessionDurableEvent"
|
||||||
@@ -23975,7 +23975,7 @@
|
|||||||
"required": ["id", "type", "data"],
|
"required": ["id", "type", "data"],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"V2Event1": {
|
"V2Event": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Models-devRefreshed"
|
"$ref": "#/components/schemas/Models-devRefreshed"
|
||||||
@@ -24243,10 +24243,10 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"V2Event": {
|
"V2EventStream": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"contentSchema": {
|
"contentSchema": {
|
||||||
"$ref": "#/components/schemas/V2Event1"
|
"$ref": "#/components/schemas/V2Event"
|
||||||
},
|
},
|
||||||
"contentMediaType": "application/json"
|
"contentMediaType": "application/json"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type {
|
|||||||
SessionMessageAssistantTool,
|
SessionMessageAssistantTool,
|
||||||
SessionV2Info,
|
SessionV2Info,
|
||||||
SkillV2Info,
|
SkillV2Info,
|
||||||
V2Event1,
|
V2Event,
|
||||||
} from "@opencode-ai/sdk/v2"
|
} from "@opencode-ai/sdk/v2"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { createSimpleContext } from "./helper"
|
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) {
|
switch (event.type) {
|
||||||
case "catalog.updated":
|
case "catalog.updated":
|
||||||
void Promise.all([
|
void Promise.all([
|
||||||
@@ -408,7 +408,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||||||
...event,
|
...event,
|
||||||
data: event.properties,
|
data: event.properties,
|
||||||
location: { directory: metadata.directory, workspaceID: metadata.workspace },
|
location: { directory: metadata.directory, workspaceID: metadata.workspace },
|
||||||
} as V2Event1)
|
} as V2Event)
|
||||||
})
|
})
|
||||||
onCleanup(unsub)
|
onCleanup(unsub)
|
||||||
})
|
})
|
||||||
|
|||||||
57
patches/effect@4.0.0-beta.83.patch
Normal file
57
patches/effect@4.0.0-beta.83.patch
Normal file
@@ -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.
|
||||||
|
*
|
||||||
Reference in New Issue
Block a user