|
| 1 | +/** |
| 2 | + * Regression test for #1156: dedicated session event data/payload types are |
| 3 | + * importable from the package entry point (`@github/copilot-sdk` / |
| 4 | + * `src/index.js`). |
| 5 | + * |
| 6 | + * Before this fix, only the aggregate `SessionEvent` discriminated union was |
| 7 | + * re-exported. The constituent `*Event` wrapper interfaces and their `*Data` |
| 8 | + * payload types lived in `generated/session-events.ts` and could only be |
| 9 | + * reached via a deep import (`@github/copilot-sdk/dist/generated/...`). |
| 10 | + * |
| 11 | + * Most of this file exercises the *type* surface — if these type-only imports |
| 12 | + * compile, the public API exposes the types. The runtime assertions below only |
| 13 | + * validate representative object shapes for those annotations; they do not |
| 14 | + * prove that type-only exports exist at runtime. |
| 15 | + */ |
| 16 | + |
| 17 | +import { describe, expect, it } from "vitest"; |
| 18 | +import type { |
| 19 | + // The aggregate union; must still resolve via the package root. |
| 20 | + SessionEvent, |
| 21 | + |
| 22 | + // *Data payload types from the v0.3.0 generated session-event schema. |
| 23 | + AssistantMessageData, |
| 24 | + AssistantMessageDeltaData, |
| 25 | + AssistantReasoningData, |
| 26 | + AssistantTurnStartData, |
| 27 | + ErrorData, |
| 28 | + IdleData, |
| 29 | + ResumeData, |
| 30 | + StartData, |
| 31 | + ToolExecutionCompleteData, |
| 32 | + ToolExecutionPartialData, |
| 33 | + ToolExecutionProgressData, |
| 34 | + ToolExecutionStartData, |
| 35 | + UserMessageData, |
| 36 | + |
| 37 | + // *Event wrapper interfaces. |
| 38 | + AssistantMessageEvent, |
| 39 | + ErrorEvent, |
| 40 | + IdleEvent, |
| 41 | + ResumeEvent, |
| 42 | + StartEvent, |
| 43 | + ToolExecutionCompleteEvent, |
| 44 | + ToolExecutionStartEvent, |
| 45 | + UserMessageEvent, |
| 46 | + |
| 47 | + // A sample of supporting auxiliary aliases/unions referenced by the |
| 48 | + // *Data shapes — these must also be reachable so that consumers can |
| 49 | + // narrow or annotate intermediate values. |
| 50 | + UserMessageAgentMode, |
| 51 | + UserMessageAttachment, |
| 52 | + WorkingDirectoryContextHostType, |
| 53 | +} from "../src/index.js"; |
| 54 | + |
| 55 | +/** |
| 56 | + * Type-only helper: forces the compiler to resolve the supplied type |
| 57 | + * parameter. If the type is not exported from `../src/index.js`, the file |
| 58 | + * fails to type-check and the test never runs. There is no runtime body — |
| 59 | + * the helper exists purely to make "is this type importable?" assertions |
| 60 | + * compile-time checked. |
| 61 | + */ |
| 62 | +function assertImportable<_T>(): void { |
| 63 | + /* no-op; compile-time check only */ |
| 64 | +} |
| 65 | + |
| 66 | +/** |
| 67 | + * Compile-time mutual-assignability check: passes only when `A` and `B` |
| 68 | + * are structurally equivalent. Used below to pin the package-root |
| 69 | + * `AssistantMessageEvent` (which is explicitly re-exported from |
| 70 | + * `./session.js` and therefore shadows the generated `AssistantMessageEvent` |
| 71 | + * arriving via `export type *`) to the corresponding arm of the generated |
| 72 | + * `SessionEvent` union. If a future schema regen ever caused these two |
| 73 | + * shapes to drift, this assertion would fail to type-check and `npm run |
| 74 | + * typecheck` would surface it before the public API silently changed. |
| 75 | + */ |
| 76 | +type _AssertEqual<A, B> = |
| 77 | + (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false; |
| 78 | +type _AssistantMessageEventStaysAlignedWithSessionEventUnion = _AssertEqual< |
| 79 | + AssistantMessageEvent, |
| 80 | + Extract<SessionEvent, { type: "assistant.message" }> |
| 81 | +>; |
| 82 | +const _assistantMessageEventAlignmentCheck: _AssistantMessageEventStaysAlignedWithSessionEventUnion = true; |
| 83 | + |
| 84 | +describe("Session event type exports (#1156)", () => { |
| 85 | + it("exposes the headline ToolExecutionStartData type with a usable shape", () => { |
| 86 | + // This is the specific type called out in issue #1156. The annotation |
| 87 | + // is the compile-time API-surface check; these assertions only validate |
| 88 | + // the representative runtime object shape a consumer would use. |
| 89 | + const data: ToolExecutionStartData = { |
| 90 | + toolCallId: "call-1", |
| 91 | + toolName: "shell", |
| 92 | + arguments: { command: "ls" }, |
| 93 | + mcpServerName: "filesystem", |
| 94 | + mcpToolName: "list_dir", |
| 95 | + turnId: "turn-1", |
| 96 | + }; |
| 97 | + |
| 98 | + expect(data.toolName).toBe("shell"); |
| 99 | + expect(data.toolCallId).toBe("call-1"); |
| 100 | + expect(data.arguments?.command).toBe("ls"); |
| 101 | + expect(data.mcpServerName).toBe("filesystem"); |
| 102 | + expect(data.mcpToolName).toBe("list_dir"); |
| 103 | + expect(data.turnId).toBe("turn-1"); |
| 104 | + }); |
| 105 | + |
| 106 | + it("wraps ToolExecutionStartData inside the exported ToolExecutionStartEvent", () => { |
| 107 | + const event: ToolExecutionStartEvent = { |
| 108 | + id: "evt-1", |
| 109 | + parentId: null, |
| 110 | + timestamp: "2026-01-01T00:00:00.000Z", |
| 111 | + type: "tool.execution_start", |
| 112 | + data: { |
| 113 | + toolCallId: "call-1", |
| 114 | + toolName: "shell", |
| 115 | + }, |
| 116 | + }; |
| 117 | + |
| 118 | + expect(event.type).toBe("tool.execution_start"); |
| 119 | + expect(event.data.toolName).toBe("shell"); |
| 120 | + expect(event.parentId).toBeNull(); |
| 121 | + }); |
| 122 | + |
| 123 | + it("narrows the aggregate SessionEvent union to a dedicated *Data type", () => { |
| 124 | + const evt: SessionEvent = { |
| 125 | + id: "evt-2", |
| 126 | + parentId: null, |
| 127 | + timestamp: "2026-01-01T00:00:01.000Z", |
| 128 | + type: "tool.execution_start", |
| 129 | + data: { |
| 130 | + toolCallId: "call-2", |
| 131 | + toolName: "shell", |
| 132 | + }, |
| 133 | + }; |
| 134 | + |
| 135 | + if (evt.type !== "tool.execution_start") { |
| 136 | + throw new Error("expected tool.execution_start narrowing"); |
| 137 | + } |
| 138 | + |
| 139 | + // After narrowing, `evt.data` must satisfy `ToolExecutionStartData`. |
| 140 | + // Annotating the local with the dedicated *Data type proves the |
| 141 | + // re-export is wired up correctly. |
| 142 | + const data: ToolExecutionStartData = evt.data; |
| 143 | + expect(data.toolCallId).toBe("call-2"); |
| 144 | + expect(data.toolName).toBe("shell"); |
| 145 | + }); |
| 146 | + |
| 147 | + it("re-exports the full set of *Data and *Event types named in v0.3.0", () => { |
| 148 | + // Compile-time checks: if any of these fail to resolve, the file |
| 149 | + // will not type-check and the test will not be executed. |
| 150 | + assertImportable<AssistantMessageData>(); |
| 151 | + assertImportable<AssistantMessageDeltaData>(); |
| 152 | + assertImportable<AssistantReasoningData>(); |
| 153 | + assertImportable<AssistantTurnStartData>(); |
| 154 | + assertImportable<ErrorData>(); |
| 155 | + assertImportable<IdleData>(); |
| 156 | + assertImportable<ResumeData>(); |
| 157 | + assertImportable<StartData>(); |
| 158 | + assertImportable<ToolExecutionCompleteData>(); |
| 159 | + assertImportable<ToolExecutionPartialData>(); |
| 160 | + assertImportable<ToolExecutionProgressData>(); |
| 161 | + assertImportable<ToolExecutionStartData>(); |
| 162 | + assertImportable<UserMessageData>(); |
| 163 | + |
| 164 | + assertImportable<AssistantMessageEvent>(); |
| 165 | + assertImportable<ErrorEvent>(); |
| 166 | + assertImportable<IdleEvent>(); |
| 167 | + assertImportable<ResumeEvent>(); |
| 168 | + assertImportable<StartEvent>(); |
| 169 | + assertImportable<ToolExecutionCompleteEvent>(); |
| 170 | + assertImportable<ToolExecutionStartEvent>(); |
| 171 | + assertImportable<UserMessageEvent>(); |
| 172 | + |
| 173 | + // Supporting auxiliary types referenced by the *Data shapes — these |
| 174 | + // must round-trip through the package root too, otherwise consumers |
| 175 | + // annotating intermediate values would still need a deep import. |
| 176 | + assertImportable<UserMessageAgentMode>(); |
| 177 | + assertImportable<UserMessageAttachment>(); |
| 178 | + assertImportable<WorkingDirectoryContextHostType>(); |
| 179 | + |
| 180 | + expect(true).toBe(true); |
| 181 | + }); |
| 182 | +}); |
0 commit comments