diff --git a/CHANGELOG.md b/CHANGELOG.md index 650b551a..a821d451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -571,6 +571,11 @@ npm release are grouped under the in-development version that introduced them. ### Fixed +- **`axiosAdapter(axios)` — the adapter's own documented snippet — now typechecks against real + axios.** ([#708](https://github.com/rejifald/StitchAPI/issues/708)) `AxiosLikeConfig.responseType` + was `string`, which axios types as its narrower `ResponseType` union, so passing `axios` (or + `axios.create()`) failed with `TS2345` under `exactOptionalPropertyTypes`. The type-level test + missed it by casting a client through `AxiosLike`; it now asserts against real `axios` types. - **A truncated LLM completion is no longer a silent success.** ([#699](https://github.com/rejifald/StitchAPI/issues/699)) `finishReason` was lifted by both provider mappings and read by nothing, so a completion cut short at the token cap resolved `ok: true` with `findings: []`. It now sets a normalised `truncated` on diff --git a/packages/core/package.json b/packages/core/package.json index c2cb9948..3976d5ec 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -328,6 +328,7 @@ "@types/node": "^22.10.0", "@vitest/coverage-v8": "^4.1.10", "@vitest/eslint-plugin": "^1.6.24", + "axios": "^1.19.0", "esbuild": "^0.28.1", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", diff --git a/packages/core/src/axios-adapter.ts b/packages/core/src/axios-adapter.ts index 193888a4..070d7b0e 100644 --- a/packages/core/src/axios-adapter.ts +++ b/packages/core/src/axios-adapter.ts @@ -34,7 +34,11 @@ export interface AxiosLikeConfig { method: string; headers?: Record; data?: unknown; - responseType?: string; + // Narrower than `string` on purpose: axios types this as its own `ResponseType` union, and a + // plain `string` makes `AxiosLikeConfig` unassignable to `AxiosRequestConfig` — which made + // `axiosAdapter(axios)` itself fail to typecheck against real axios (#708). The adapter only + // ever sends 'arraybuffer' (below), so narrowing to a subset of axios's union costs nothing. + responseType?: 'arraybuffer' | 'json' | 'text'; signal?: AbortSignal; validateStatus?: ((status: number) => boolean) | null; onUploadProgress?: (e: AxiosLikeProgressEvent) => void; diff --git a/packages/core/test-d/adapter-capabilities.test-d.ts b/packages/core/test-d/adapter-capabilities.test-d.ts index 8a093022..684e0be3 100644 --- a/packages/core/test-d/adapter-capabilities.test-d.ts +++ b/packages/core/test-d/adapter-capabilities.test-d.ts @@ -12,6 +12,7 @@ import type { AxiosLike, } from '../src'; +import axios from 'axios'; import { expectAssignable, expectError, expectType } from 'tsd'; // A plain transport function — no `capabilities` — still satisfies Adapter (backward compatible). @@ -33,9 +34,20 @@ expectType(fetchAdapter().capabilities); // Every built-in resolves to Adapter. expectAssignable(fetchAdapter()); expectAssignable(xhrAdapter()); + +// The axios seam is structural, so any hand-rolled client that satisfies it is accepted... const client = null as unknown as AxiosLike; expectAssignable(axiosAdapter(client)); +// ...but the assertion that matters is against REAL axios, because that is what the documented +// snippet passes. The `as unknown as AxiosLike` cast above cannot make it: it ASSERTS the client is +// an AxiosLike rather than testing whether axios actually is one, so #708 — `AxiosLikeConfig` being +// unassignable to axios's own `AxiosRequestConfig`, via a too-wide `responseType?: string` — passed +// this file while `axiosAdapter(axios)` failed to compile for every user. Pin all three call forms. +expectAssignable(axiosAdapter(axios)); +expectAssignable(axiosAdapter(axios.create())); +expectAssignable(axiosAdapter(axios, { timeout: 5 })); + // `supports` is a list of the known capability tags; `name` is optional. expectAssignable({ supports: [] }); expectAssignable({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a1f693f..cd012f75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -378,6 +378,9 @@ importers: '@vitest/eslint-plugin': specifier: ^1.6.24 version: 1.6.24(@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)(vitest@4.1.10) + axios: + specifier: ^1.19.0 + version: 1.19.0 esbuild: specifier: ^0.28.1 version: 0.28.1 @@ -4464,6 +4467,10 @@ packages: resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} engines: {node: '>=14.0'} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -4632,6 +4639,9 @@ packages: resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} engines: {node: '>=4'} + axios@1.19.0: + resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -5923,6 +5933,15 @@ packages: flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + fontfaceobserver@2.3.0: resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} @@ -6344,6 +6363,10 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -7903,6 +7926,10 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -12715,6 +12742,12 @@ snapshots: adm-zip@0.6.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + agent-base@7.1.4: {} ai@6.0.208(zod@4.4.3): @@ -12899,6 +12932,16 @@ snapshots: axe-core@4.12.1: {} + axios@1.19.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + axobject-query@4.1.0: {} babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): @@ -14483,6 +14526,8 @@ snapshots: flow-enums-runtime@0.0.6: {} + follow-redirects@1.16.0: {} + fontfaceobserver@2.3.0: {} for-each@0.3.5: @@ -14967,6 +15012,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -16861,6 +16913,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@2.1.0: {} + punycode@2.3.1: {} pure-rand@6.1.0: {}