From 53d61347ff7498540d1fe60614ef4eb16596c4e1 Mon Sep 17 00:00:00 2001 From: victor0602 Date: Wed, 5 Aug 2026 19:54:22 +0800 Subject: [PATCH] refactor: share SDK client context --- src/sdk/client.ts | 23 ++++++++++--- src/sdk/index.ts | 20 +++++------ test/sdk/client.test.ts | 75 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 test/sdk/client.test.ts diff --git a/src/sdk/client.ts b/src/sdk/client.ts index 93e0d3ed..6d69f59e 100644 --- a/src/sdk/client.ts +++ b/src/sdk/client.ts @@ -1,13 +1,14 @@ import { loadConfig } from "../config/loader"; -import { Config } from "../config/schema"; -import { request as requestClient, requestJson as requestJsonClient, RequestOpts } from "../client/http"; +import type { Config } from "../config/schema"; +import { request as requestClient, requestJson as requestJsonClient } from "../client/http"; +import type { RequestOpts } from "../client/http"; import { parseSSE } from "../client/stream"; import { SDKError } from "../errors/base"; import { ExitCode } from "../errors/codes"; -import { MiniMaxSDKOptions } from "./types"; +import type { MiniMaxSDKOptions } from "./types"; -export class Client { - protected config: Config; +export class ClientContext { + readonly config: Config; constructor(options: MiniMaxSDKOptions) { const { apiKey, region, baseUrl } = options; @@ -25,6 +26,18 @@ export class Client { async: false, }); } +} + +export class Client { + protected readonly context: ClientContext; + protected readonly config: Config; + + constructor(optionsOrContext: MiniMaxSDKOptions | ClientContext) { + this.context = optionsOrContext instanceof ClientContext + ? optionsOrContext + : new ClientContext(optionsOrContext); + this.config = this.context.config; + } protected request(opts: RequestOpts) { return requestClient(this.config, opts); diff --git a/src/sdk/index.ts b/src/sdk/index.ts index fc46f3b5..239e4bd8 100644 --- a/src/sdk/index.ts +++ b/src/sdk/index.ts @@ -8,7 +8,7 @@ import { VisionSDK } from "./vision"; import { QuotaSDK } from "./quota"; import { FileSDK } from "./file"; import { Client } from "./client"; -import { MiniMaxSDKOptions } from "./types"; +import type { MiniMaxSDKOptions } from "./types"; export class MiniMaxSDK extends Client { readonly text: TextSDK; @@ -23,14 +23,14 @@ export class MiniMaxSDK extends Client { constructor(options: MiniMaxSDKOptions) { super(options); - this.text = new TextSDK(options); - this.speech = new SpeechSDK(options); - this.image = new ImageSDK(options); - this.video = new VideoSDK(options); - this.music = new MusicSDK(options); - this.search = new SearchSDK(options); - this.vision = new VisionSDK(options); - this.quota = new QuotaSDK(options); - this.file = new FileSDK(options); + this.text = new TextSDK(this.context); + this.speech = new SpeechSDK(this.context); + this.image = new ImageSDK(this.context); + this.video = new VideoSDK(this.context); + this.music = new MusicSDK(this.context); + this.search = new SearchSDK(this.context); + this.vision = new VisionSDK(this.context); + this.quota = new QuotaSDK(this.context); + this.file = new FileSDK(this.context); } } diff --git a/test/sdk/client.test.ts b/test/sdk/client.test.ts new file mode 100644 index 00000000..786bd575 --- /dev/null +++ b/test/sdk/client.test.ts @@ -0,0 +1,75 @@ +import { afterEach, describe, expect, it } from 'bun:test'; +import type { Config } from '../../src/config/schema'; +import { MiniMaxSDK } from '../../src/sdk'; +import { TextSDK } from '../../src/sdk/text'; +import { createMockServer, jsonResponse, type MockServer } from '../helpers/mock-server'; + +interface ClientState { + context: object; + config: Config; +} + +function clientState(client: object): ClientState { + return client as ClientState; +} + +describe('SDK client context', () => { + let server: MockServer; + + afterEach(() => { + server?.close(); + }); + + it('shares one resolved context and config across all child SDKs', () => { + const sdk = new MiniMaxSDK({ + apiKey: 'test-key', + baseUrl: 'https://example.com', + }); + const rootState = clientState(sdk); + const childClients = [ + sdk.text, + sdk.speech, + sdk.image, + sdk.video, + sdk.music, + sdk.search, + sdk.vision, + sdk.quota, + sdk.file, + ]; + + for (const child of childClients) { + expect(clientState(child).context).toBe(rootState.context); + expect(clientState(child).config).toBe(rootState.config); + } + + expect(rootState.config.apiKey).toBe('test-key'); + expect(rootState.config.baseUrl).toBe('https://example.com'); + }); + + it('preserves direct child SDK construction and request behavior', async () => { + server = createMockServer({ + routes: { + '/anthropic/v1/messages': () => jsonResponse({ + id: 'msg-direct', + type: 'message', + role: 'assistant', + content: [{ type: 'text', text: 'Hello!' }], + model: 'MiniMax-M3', + stop_reason: 'end_turn', + usage: { input_tokens: 1, output_tokens: 1 }, + }), + }, + }); + + const sdk = new TextSDK({ + apiKey: 'test-key', + baseUrl: server.url, + }); + const result = await sdk.chat({ + messages: [{ role: 'user', content: 'Hello' }], + }); + + expect(result.id).toBe('msg-direct'); + }); +});