|
| 1 | +/** |
| 2 | + * Type-level drift guard for the structural `ai` mirrors in `./index.ts`. |
| 3 | + * |
| 4 | + * This file is NOT shipped (excluded from the tshy build via `tshy.exclude` in |
| 5 | + * `package.json`) and is NOT a runtime test (its name does not match vitest's |
| 6 | + * `*.test.ts` glob). It exists purely so `pnpm typecheck` (which compiles every |
| 7 | + * `.ts` file under `src`) fails if our structural mirrors ever stop being |
| 8 | + * assignable to the real `ai` types — e.g. after an `ai` version bump. |
| 9 | + * |
| 10 | + * It is the ONLY file in this package that imports `ai`. `ai` is a devDep, so |
| 11 | + * the import resolves during typecheck but never reaches the published surface. |
| 12 | + * |
| 13 | + * Each `const _x: A = {} as B` line asserts `B` is assignable to `A`. Two lines |
| 14 | + * (both directions) assert bidirectional assignability. |
| 15 | + */ |
| 16 | +import type * as ai from "ai"; |
| 17 | +import type * as t from "./index.js"; |
| 18 | + |
| 19 | +// --------------------------------------------------------------------------- |
| 20 | +// Faithful wire types — bidirectionally assignable with `ai`. |
| 21 | +// --------------------------------------------------------------------------- |
| 22 | + |
| 23 | +// UIMessage (default generics) |
| 24 | +const _uiMessageAiToOurs: t.UIMessage = {} as ai.UIMessage; |
| 25 | +const _uiMessageOursToAi: ai.UIMessage = {} as t.UIMessage; |
| 26 | + |
| 27 | +// UIMessage with a metadata generic |
| 28 | +const _uiMessageMetaAiToOurs: t.UIMessage<{ foo: string }> = {} as ai.UIMessage<{ foo: string }>; |
| 29 | +const _uiMessageMetaOursToAi: ai.UIMessage<{ foo: string }> = {} as t.UIMessage<{ foo: string }>; |
| 30 | + |
| 31 | +// UIMessageChunk |
| 32 | +const _chunkAiToOurs: t.UIMessageChunk = {} as ai.UIMessageChunk; |
| 33 | +const _chunkOursToAi: ai.UIMessageChunk = {} as t.UIMessageChunk; |
| 34 | + |
| 35 | +// ModelMessage |
| 36 | +const _modelMessageAiToOurs: t.ModelMessage = {} as ai.ModelMessage; |
| 37 | +const _modelMessageOursToAi: ai.ModelMessage = {} as t.ModelMessage; |
| 38 | + |
| 39 | +// FinishReason |
| 40 | +const _finishReasonAiToOurs: t.FinishReason = "stop" as ai.FinishReason; |
| 41 | +const _finishReasonOursToAi: ai.FinishReason = "stop" as t.FinishReason; |
| 42 | + |
| 43 | +// LanguageModelUsage |
| 44 | +const _usageAiToOurs: t.LanguageModelUsage = {} as ai.LanguageModelUsage; |
| 45 | +const _usageOursToAi: ai.LanguageModelUsage = {} as t.LanguageModelUsage; |
| 46 | + |
| 47 | +// ChatRequestOptions |
| 48 | +const _requestOptionsAiToOurs: t.ChatRequestOptions = {} as ai.ChatRequestOptions; |
| 49 | +const _requestOptionsOursToAi: ai.ChatRequestOptions = {} as t.ChatRequestOptions; |
| 50 | + |
| 51 | +// ChatTransport — our implementation must be usable wherever `ai`'s is, and vice versa |
| 52 | +const _transportAiToOurs: t.ChatTransport<t.UIMessage> = {} as ai.ChatTransport<ai.UIMessage>; |
| 53 | +const _transportOursToAi: ai.ChatTransport<ai.UIMessage> = {} as t.ChatTransport<t.UIMessage>; |
| 54 | + |
| 55 | +// --------------------------------------------------------------------------- |
| 56 | +// Structural-by-design types — one-directional guarantees we rely on. |
| 57 | +// --------------------------------------------------------------------------- |
| 58 | + |
| 59 | +// ToolCallOptions: `ai`'s tool-execution options must be assignable to ours, so |
| 60 | +// an `execute` fn typed with our options is usable where `ai` expects its own. |
| 61 | +const _toolCallOptionsAiToOurs: t.ToolCallOptions = {} as ai.ToolCallOptions; |
| 62 | + |
| 63 | +// Schema accept-position (core `types/tools.ts`): a real `ai` Schema flows into |
| 64 | +// our looser `AISchemaLike` (we only read `_type` / `validate`). |
| 65 | +const _schemaAiToOurs: t.AISchemaLike<{ a: number }> = {} as ai.Schema<{ a: number }>; |
| 66 | + |
| 67 | +// ToolSet constraint: any concrete `ai` tool set satisfies `<T extends ToolSet>`. |
| 68 | +const _toolSetAiToOurs: t.ToolSet = {} as ai.ToolSet; |
| 69 | +const _aiToolSetSatisfiesConstraint = <T extends t.ToolSet>(_t: T): void => undefined; |
| 70 | +_aiToolSetSatisfiesConstraint({} as { search: ai.Tool<{ q: string }, { hits: number }> }); |
| 71 | + |
| 72 | +// Tool-typed UIMessage (via InferUITools): `ai` -> ours holds; the tool `input` |
| 73 | +// / `output` payloads degrade to `unknown` on our side. The reverse direction |
| 74 | +// for *typed* tools is intentionally NOT guaranteed — see the "Fidelity" note |
| 75 | +// in `./index.ts`. Default + metadata-typed UIMessages (asserted above) stay |
| 76 | +// fully faithful. |
| 77 | +type AiToolSet = { search: ai.Tool<{ q: string }, { hits: number }> }; |
| 78 | +const _toolTypedAiToOurs: t.UIMessage<unknown, t.UIDataTypes, t.InferUITools<AiToolSet>> = |
| 79 | + {} as ai.UIMessage<unknown, ai.UIDataTypes, ai.InferUITools<AiToolSet>>; |
| 80 | + |
| 81 | +export {}; |
0 commit comments