Skip to content

Commit a913dcf

Browse files
committed
Tighten reasoning model types and simplify adapter test stubs
1 parent 865c69e commit a913dcf

4 files changed

Lines changed: 122 additions & 64 deletions

File tree

packages/typescript/ai-openai/src/models/text.ts

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,39 @@ export interface TextProviderFeatureMap {
3636
streaming: OpenAIStreamingOptions
3737
}
3838

39-
type NonReasoningTextProviderFeatureMap = Omit<
40-
TextProviderFeatureMap,
39+
type TextProviderFeature = keyof TextProviderFeatureMap
40+
type TextProviderNonReasoningFeature = Exclude<
41+
TextProviderFeature,
4142
'reasoning' | 'reasoningConcise'
4243
>
44+
type TextProviderNonBaseFeature = Exclude<TextProviderNonReasoningFeature, 'base'>
45+
type NonReasoningTextProviderFeatures = readonly [
46+
'base',
47+
...ReadonlyArray<TextProviderNonBaseFeature>,
48+
]
49+
type ReasoningTextProviderFeatures = readonly [
50+
'base',
51+
'reasoning',
52+
...ReadonlyArray<TextProviderNonBaseFeature>,
53+
]
54+
type ReasoningConciseTextProviderFeatures = readonly [
55+
'base',
56+
'reasoningConcise',
57+
...ReadonlyArray<TextProviderNonBaseFeature>,
58+
]
4359

44-
type TextProviderFeature = keyof TextProviderFeatureMap
45-
46-
interface TextReasoningSpec {
60+
interface TextReasoningSpec<
61+
TSummary extends OpenAIReasoningSummaryWithConcise = OpenAIReasoningSummaryWithConcise,
62+
> {
4763
efforts: ReadonlyArray<OpenAIReasoningEffort>
48-
summaries: ReadonlyArray<OpenAIReasoningSummaryWithConcise>
64+
summaries: ReadonlyArray<TSummary>
4965
}
5066

51-
interface TextModelSpec<
52-
TFeatures extends ReadonlyArray<TextProviderFeature>,
67+
interface TextModelSpecBase<
68+
TFeatures extends
69+
| NonReasoningTextProviderFeatures
70+
| ReasoningTextProviderFeatures
71+
| ReasoningConciseTextProviderFeatures,
5372
TInput extends ReadonlyArray<OpenAIRegistryInput>,
5473
TOutput extends ReadonlyArray<OpenAIRegistryOutput>,
5574
> {
@@ -65,6 +84,54 @@ interface TextModelSpec<
6584
docs?: OpenAIRegistryDocs
6685
}
6786

87+
type NonReasoningTextModelSpec<
88+
TFeatures extends NonReasoningTextProviderFeatures,
89+
TInput extends ReadonlyArray<OpenAIRegistryInput>,
90+
TOutput extends ReadonlyArray<OpenAIRegistryOutput>,
91+
> = TextModelSpecBase<TFeatures, TInput, TOutput> & {
92+
reasoning?: never
93+
}
94+
95+
type ReasoningTextModelSpec<
96+
TFeatures extends ReasoningTextProviderFeatures,
97+
TInput extends ReadonlyArray<OpenAIRegistryInput>,
98+
TOutput extends ReadonlyArray<OpenAIRegistryOutput>,
99+
> = TextModelSpecBase<TFeatures, TInput, TOutput> & {
100+
reasoning: TextReasoningSpec<OpenAIReasoningSummary>
101+
}
102+
103+
type ReasoningConciseTextModelSpec<
104+
TFeatures extends ReasoningConciseTextProviderFeatures,
105+
TInput extends ReadonlyArray<OpenAIRegistryInput>,
106+
TOutput extends ReadonlyArray<OpenAIRegistryOutput>,
107+
> = TextModelSpecBase<TFeatures, TInput, TOutput> & {
108+
reasoning: TextReasoningSpec<OpenAIReasoningSummaryWithConcise>
109+
}
110+
111+
type TextModelSpec<
112+
TFeatures extends
113+
| NonReasoningTextProviderFeatures
114+
| ReasoningTextProviderFeatures
115+
| ReasoningConciseTextProviderFeatures,
116+
TInput extends ReadonlyArray<OpenAIRegistryInput>,
117+
TOutput extends ReadonlyArray<OpenAIRegistryOutput>,
118+
> =
119+
| NonReasoningTextModelSpec<
120+
Extract<TFeatures, NonReasoningTextProviderFeatures>,
121+
TInput,
122+
TOutput
123+
>
124+
| ReasoningTextModelSpec<
125+
Extract<TFeatures, ReasoningTextProviderFeatures>,
126+
TInput,
127+
TOutput
128+
>
129+
| ReasoningConciseTextModelSpec<
130+
Extract<TFeatures, ReasoningConciseTextProviderFeatures>,
131+
TInput,
132+
TOutput
133+
>
134+
68135
const COMMON_TOOLS = [
69136
'web_search',
70137
'file_search',
@@ -107,8 +174,8 @@ export type TextProviderOptionsForEntry<
107174
TEntry extends { features: ReadonlyArray<string> },
108175
> =
109176
UnionToIntersection<
110-
NonReasoningTextProviderFeatureMap[
111-
Extract<TEntry['features'][number], keyof NonReasoningTextProviderFeatureMap>
177+
TextProviderFeatureMap[
178+
Extract<TEntry['features'][number], TextProviderNonReasoningFeature>
112179
]
113180
> &
114181
TextReasoningOptionsForEntry<TEntry>
@@ -746,7 +813,9 @@ export const TEXT_MODELS = {
746813
} as const satisfies Record<
747814
string,
748815
TextModelSpec<
749-
ReadonlyArray<TextProviderFeature>,
816+
| NonReasoningTextProviderFeatures
817+
| ReasoningTextProviderFeatures
818+
| ReasoningConciseTextProviderFeatures,
750819
ReadonlyArray<OpenAIRegistryInput>,
751820
ReadonlyArray<OpenAIRegistryOutput>
752821
>

packages/typescript/ai-openai/tests/model-meta.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ import type {
5151
GptImage1ProviderOptions,
5252
} from '../src/image/image-provider-options'
5353
import type {
54-
OpenAIReasoningOptions,
5554
OpenAIReasoningOptionsWithConcise,
5655
OpenAIStreamingOptions,
5756
OpenAIStructuredOutputOptions,
@@ -66,6 +65,8 @@ describe('OpenAI registries', () => {
6665
const expectUnique = (ids: ReadonlyArray<string>) => {
6766
expect(new Set(ids).size).toBe(ids.length)
6867
}
68+
const hasOwn = (object: object, key: PropertyKey) =>
69+
Object.prototype.hasOwnProperty.call(object, key)
6970

7071
it('derives public arrays from the keyed registries', () => {
7172
expect(OPENAI_CHAT_MODELS).toEqual(supportedIds(TEXT_MODELS))
@@ -99,19 +100,19 @@ describe('OpenAI registries', () => {
99100
)
100101
for (const id of OPENAI_CHAT_SNAPSHOT_MODELS) {
101102
expect(OPENAI_CHAT_MODELS).toContain(id)
102-
expect(TEXT_MODELS).not.toHaveProperty(id)
103+
expect(hasOwn(TEXT_MODELS, id)).toBe(false)
103104
}
104105
for (const id of OPENAI_IMAGE_SNAPSHOT_MODELS) {
105106
expect(OPENAI_IMAGE_MODELS).toContain(id)
106-
expect(IMAGE_MODELS).not.toHaveProperty(id)
107+
expect(hasOwn(IMAGE_MODELS, id)).toBe(false)
107108
}
108109
for (const id of OPENAI_TTS_SNAPSHOT_MODELS) {
109110
expect(OPENAI_TTS_MODELS).toContain(id)
110-
expect(TTS_MODELS).not.toHaveProperty(id)
111+
expect(hasOwn(TTS_MODELS, id)).toBe(false)
111112
}
112113
for (const id of OPENAI_TRANSCRIPTION_SNAPSHOT_MODELS) {
113114
expect(OPENAI_TRANSCRIPTION_MODELS).toContain(id)
114-
expect(TRANSCRIPTION_MODELS).not.toHaveProperty(id)
115+
expect(hasOwn(TRANSCRIPTION_MODELS, id)).toBe(false)
115116
}
116117
})
117118

packages/typescript/ai-openai/tests/transcription-adapter.test.ts

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import { afterEach, describe, expect, it, vi } from 'vitest'
22
import { createOpenaiTranscription } from '../src/adapters/transcription'
33

4+
const stubAdapterClient = (
5+
adapter: ReturnType<typeof createOpenaiTranscription>,
6+
create: unknown,
7+
) => {
8+
;(adapter as unknown as {
9+
client: { audio: { transcriptions: { create: unknown } } }
10+
}).client = {
11+
audio: {
12+
transcriptions: {
13+
create,
14+
},
15+
},
16+
}
17+
}
18+
419
describe('OpenAI transcription adapter', () => {
520
afterEach(() => {
621
vi.restoreAllMocks()
@@ -24,14 +39,7 @@ describe('OpenAI transcription adapter', () => {
2439
})
2540

2641
const adapter = createOpenaiTranscription('gpt-4o-transcribe', 'test-api-key')
27-
;(adapter as unknown as { client: { audio: { transcriptions: { create: unknown } } } }).client =
28-
{
29-
audio: {
30-
transcriptions: {
31-
create,
32-
},
33-
},
34-
}
42+
stubAdapterClient(adapter, create)
3543

3644
const result = await adapter.transcribe({
3745
model: 'gpt-4o-transcribe',
@@ -64,14 +72,7 @@ describe('OpenAI transcription adapter', () => {
6472
const create = vi.fn().mockResolvedValueOnce('plain transcript')
6573

6674
const adapter = createOpenaiTranscription('whisper-1', 'test-api-key')
67-
;(adapter as unknown as { client: { audio: { transcriptions: { create: unknown } } } }).client =
68-
{
69-
audio: {
70-
transcriptions: {
71-
create,
72-
},
73-
},
74-
}
75+
stubAdapterClient(adapter, create)
7576

7677
const result = await adapter.transcribe({
7778
model: 'whisper-1',
@@ -98,14 +99,7 @@ describe('OpenAI transcription adapter', () => {
9899
const create = vi.fn().mockResolvedValueOnce({ segments: [] })
99100

100101
const adapter = createOpenaiTranscription('gpt-4o-transcribe', 'test-api-key')
101-
;(adapter as unknown as { client: { audio: { transcriptions: { create: unknown } } } }).client =
102-
{
103-
audio: {
104-
transcriptions: {
105-
create,
106-
},
107-
},
108-
}
102+
stubAdapterClient(adapter, create)
109103

110104
const result = await adapter.transcribe({
111105
model: 'gpt-4o-transcribe',
@@ -133,14 +127,7 @@ describe('OpenAI transcription adapter', () => {
133127
})
134128

135129
const adapter = createOpenaiTranscription('gpt-4o-transcribe', 'test-api-key')
136-
;(adapter as unknown as { client: { audio: { transcriptions: { create: unknown } } } }).client =
137-
{
138-
audio: {
139-
transcriptions: {
140-
create,
141-
},
142-
},
143-
}
130+
stubAdapterClient(adapter, create)
144131

145132
await adapter.transcribe({
146133
model: 'gpt-4o-transcribe',

packages/typescript/ai-openai/tests/tts-adapter.test.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
import { describe, expect, it, vi } from 'vitest'
22
import { createOpenaiSpeech } from '../src/adapters/tts'
33

4+
const stubAdapterClient = (
5+
adapter: ReturnType<typeof createOpenaiSpeech>,
6+
create: unknown,
7+
) => {
8+
;(adapter as unknown as {
9+
client: { audio: { speech: { create: unknown } } }
10+
}).client = {
11+
audio: {
12+
speech: {
13+
create,
14+
},
15+
},
16+
}
17+
}
18+
419
describe('OpenAI TTS adapter', () => {
520
it('passes supported instructions through and returns mp3 output metadata', async () => {
621
const create = vi
722
.fn()
823
.mockResolvedValueOnce(new Response(Uint8Array.from([1, 2, 3])))
924

1025
const adapter = createOpenaiSpeech('gpt-4o-mini-tts', 'test-api-key')
11-
;(adapter as unknown as { client: { audio: { speech: { create: unknown } } } }).client =
12-
{
13-
audio: {
14-
speech: {
15-
create,
16-
},
17-
},
18-
}
26+
stubAdapterClient(adapter, create)
1927

2028
const result = await adapter.generateSpeech({
2129
model: 'gpt-4o-mini-tts',
@@ -43,14 +51,7 @@ describe('OpenAI TTS adapter', () => {
4351
const create = vi.fn()
4452

4553
const adapter = createOpenaiSpeech('tts-1', 'test-api-key')
46-
;(adapter as unknown as { client: { audio: { speech: { create: unknown } } } }).client =
47-
{
48-
audio: {
49-
speech: {
50-
create,
51-
},
52-
},
53-
}
54+
stubAdapterClient(adapter, create)
5455

5556
await expect(
5657
adapter.generateSpeech({

0 commit comments

Comments
 (0)