Skip to content

feat[bc|notask]: migrate AI SDK provider to v7 and fix dynamic tools#3370

Open
danielAsaboro wants to merge 10 commits into
tetherto:mainfrom
danielAsaboro:feature-ai-sdk-v7
Open

feat[bc|notask]: migrate AI SDK provider to v7 and fix dynamic tools#3370
danielAsaboro wants to merge 10 commits into
tetherto:mainfrom
danielAsaboro:feature-ai-sdk-v7

Conversation

@danielAsaboro

@danielAsaboro danielAsaboro commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This migrates @qvac/ai-sdk-provider from AI SDK 6/provider v3 to AI SDK 7/provider v4 and fixes the llama.cpp dynamic-tools regression where toolless requests failed with tools_compact requires non-empty tools.

It also preserves uploaded MIME types in the CLI ephemeral-file store and exposes the same AI SDK 7 provider capabilities in external and managed modes.

API usage

The callable provider is shorthand for selecting a language model. The explicit language-model methods remain available, and other model types use their capability-specific methods:

import { qvac } from '@qvac/ai-sdk-provider'

const language = qvac('qwen3-600m')
const explicitLanguage = qvac.chatModel('qwen3-600m')

const embedding = qvac.textEmbeddingModel('embed-gemma')
const image = qvac.imageModel('flux-schnell')
const transcription = qvac.transcriptionModel('whisper')
const speech = qvac.speechModel('tts')
const files = qvac.files()

These methods select model aliases served by QVAC; they do not load every capability through the callable language-model shorthand.

Managed mode exposes the same surface after starting the configured local models:

import { createQvac } from '@qvac/ai-sdk-provider'

const qvac = await createQvac({
  mode: 'managed',
  models: ['QWEN3_600M_INST_Q4', 'EMBEDDINGGEMMA_300M_Q4_0']
})

const language = qvac('QWEN3_600M_INST_Q4')
const embedding = qvac.embeddingModel('EMBEDDINGGEMMA_300M_Q4_0')

Changes

  • Migrates @qvac/ai-sdk-provider to AI SDK 7, @ai-sdk/openai-compatible 3, provider v4 contracts, Node 22+, and ESM-only operation.
  • Keeps qvac(modelId) as the language-model shorthand while retaining languageModel() and chatModel() and adding native files, transcription, and speech adapters.
  • Preserves the complete language, embedding, image, files, transcription, and speech surface in external and managed modes.
  • Preserves multipart MIME types in the CLI ephemeral-file store and returns them from /v1/files/:id/content.
  • Removes tools_compact from model-load configuration and computes it for each request. It is true only for a dynamic-tools request with a non-empty tool set and explicitly false otherwise.
  • Applies the same per-prompt rule to batch completions so mixed batches evaluate tools independently.
  • Runs the AI SDK v7 codemod as a migration audit, then manually reviews and formats the provider changes.

Behavior / compatibility

  • Breaking: @qvac/ai-sdk-provider now requires Node.js 22 or newer, AI SDK 7, and provider v4 packages.
  • Breaking: the package is ESM-only; CommonJS require() is not supported.
  • No AI SDK 6 compatibility layer or deprecated duplicate provider API was added.
  • qvac('alias'), qvac.languageModel('alias'), and qvac.chatModel('alias') select the same language-model capability. Existing explicit chatModel() calls do not need to change for model selection.
  • Embedding and image selection remain explicit through embeddingModel() / textEmbeddingModel() and imageModel(); transcription and speech use transcriptionModel() and speechModel().
  • Dynamic tool compaction is request-scoped. Static, disabled, and toolless requests explicitly send tools_compact: false.

Package requirement change:

BEFORE:

{
  "engines": { "node": ">=20.0.0" },
  "peerDependencies": {
    "ai": "^6.0",
    "@ai-sdk/openai-compatible": "^2.0"
  }
}

AFTER:

{
  "engines": { "node": ">=22.0.0" },
  "peerDependencies": {
    "ai": "^7.0",
    "@ai-sdk/openai-compatible": "^3.0"
  }
}

Testing

  • Provider: foreground npm install; format; lint with zero warnings; typecheck; build; all unit tests (79 passed, 2 skipped); packed-package install/import smoke with AI SDK 7.
  • Provider behavior: language, embedding, and image methods; callable and explicit language-model selectors; file upload/reference round trip; header propagation; speech and transcription request/response handling; managed-mode surface.
  • CLI: format; lint; typecheck; build; all 417 unit tests; targeted /v1/files HTTP suite (7 passed), covering exact bytes, MIME header, private cache control, and missing files.
  • SDK: format; lint; typecheck; build; full unit suite; full Bare suite (71 passed); contract check.
  • Regression: a model loaded with toolsMode: "dynamic" completes a normal toolless request with tools_compact: false; unit coverage also verifies dynamic, static, disabled, and toolless requests plus independently evaluated batch prompts.

@github-actions github-actions Bot added the community-contribution PR or issue from an external contributor label Jul 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Review Status

Current Status: ❌ PENDING
Approvals so far: none

Pending reviews: Needs 1 Management or Team Lead, and 1 more from Management, Team Lead, or Member.

@danielAsaboro
danielAsaboro marked this pull request as ready for review July 21, 2026 11:03
@danielAsaboro
danielAsaboro requested review from a team as code owners July 21, 2026 11:03

@simon-iribarren simon-iribarren left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking public change, so the title should use [bc] rather than [api|bc]. Please also document the Node 22+, AI SDK 7/provider-v4, and ESM-only requirements in the README. Full CI is currently skipped because the PR lacks the verified label.

): T & { tools_compact: boolean } {
return {
...(request.generationParams ?? ({} as T)),
tools_compact: requestUsesCompactTools(request.tools, options)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: tools_compact does not currently cross the native addon boundary. parseGenerationParams() in AddonJs.hpp never reads this field, while LlamaModel only configures the tools-compaction controller at model load. Since this PR also removes the load-time setting, tool-bearing dynamic requests will no longer enable compaction. Please add native per-request support and an integration test through the real addon boundary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d58e8851. tools_compact now crosses the JS/native boundary and is applied per request, with isolated continuous-batch controllers. Added real-addon coverage for enablement, explicit disable/reset, and mixed batches.


async function throwResponseError(operation: string, response: Response): Promise<never> {
const detail = (await response.text()).slice(0, 1_000)
throw new Error(`QVAC ${operation} failed (${response.status}): ${detail}`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new HTTP adapter paths throw plain Error objects for non-2xx responses. AI SDK retry behavior and consumer error inspection rely on structured provider errors such as APICallError, including status, response body/headers, and retryability. Please use the provider-utils error helpers or equivalent structured QVAC errors across the audio, files, and file-reference adapters.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d58e8851. Audio, upload, and file-reference failures now use provider-utils response handlers and surface structured APICallError metadata, including status, body, headers, request values, and retryability.

@danielAsaboro danielAsaboro changed the title feat[api|bc|notask]: migrate AI SDK provider to v7 and fix dynamic tools feat[bc|notask]: migrate AI SDK provider to v7 and fix dynamic tools Jul 22, 2026
Copilot AI review requested due to automatic review settings July 22, 2026 16:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades @qvac/ai-sdk-provider to AI SDK 7 / provider v4, expands the provider surface to include native files + speech + transcription, and fixes a llama.cpp dynamic-tools regression by making tools_compact a per-request decision (explicitly false for toolless prompts) across single and batch completions. It also preserves uploaded MIME types in the CLI ephemeral-file store.

Changes:

  • Migrate the AI SDK provider package to AI SDK 7/provider v4 (Node 22+, ESM-only) and compose native QVAC files/transcription/speech capabilities over the OpenAI-compatible transport.
  • Make tools_compact request-scoped in the SDK and addon (single + continuous batch), including per-request enable/disable and safe handling for toolless prompts.
  • Preserve multipart MIME types through the CLI ephemeral file store and validate via HTTP e2e tests.

Reviewed changes

Copilot reviewed 36 out of 37 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/sdk/test/unit/tools-compact.test.ts Adds unit coverage for request-scoped tools_compact computation and mixed-batch behavior.
packages/sdk/test/unit/llm-plugin-transform.test.ts Asserts tools_compact is not set at model-load transform time.
packages/sdk/test/bare/tools-compact.test.ts Bare-runtime regression test ensuring toolless dynamic requests succeed with tools_compact: false.
packages/sdk/server/bare/plugins/llamacpp-completion/transform.ts Removes model-load-time mapping from tools mode to tools_compact.
packages/sdk/server/bare/plugins/llamacpp-completion/ops/tools-compact.ts Introduces shared helpers for computing/merging request-scoped tools_compact.
packages/sdk/server/bare/plugins/llamacpp-completion/ops/completion-stream.ts Applies request-scoped tools_compact in single completion requests and preserves other per-request params.
packages/sdk/server/bare/plugins/llamacpp-completion/ops/batch-completion-stream.ts Applies request-scoped tools_compact per batch slot and always forwards generation params.
packages/sdk/schemas/tools.ts Updates docs to reflect request-scoped tools_compact behavior for dynamic tools.
packages/llm-llamacpp/test/unit/test_tools_compact_controller.cpp Extends controller unit tests for enable/disable and unsupported behavior.
packages/llm-llamacpp/test/integration/tools-compact.test.js Adds integration coverage for per-request override and mixed-slot continuous batch isolation.
packages/llm-llamacpp/test/integration/generation-params.test.js Adds validation that tools_compact overrides must be boolean at the JS boundary.
packages/llm-llamacpp/index.js Enforces runtime type checking for generationParams.tools_compact.
packages/llm-llamacpp/index.d.ts Exposes tools_compact?: boolean as a per-request generation param override.
packages/llm-llamacpp/addon/src/model-interface/ToolsCompactController.hpp Enables controller to start disabled and supports per-request toggling.
packages/llm-llamacpp/addon/src/model-interface/ToolsCompactController.cpp Implements setEnabled() and ensures disabling clears boundaries and validation is gated by enabled state.
packages/llm-llamacpp/addon/src/model-interface/TextLlmContext.hpp Adds virtual hook to toggle tools compaction per request.
packages/llm-llamacpp/addon/src/model-interface/TextLlmContext.cpp Rebuilds chat templates when the effective tools-compaction state changes.
packages/llm-llamacpp/addon/src/model-interface/MtmdLlmContext.hpp Adds per-request tools-compaction toggle for multimodal contexts.
packages/llm-llamacpp/addon/src/model-interface/MtmdLlmContext.cpp Mirrors template rebuild logic for multimodal contexts.
packages/llm-llamacpp/addon/src/model-interface/LlmContext.hpp Adds tools_compact override plumbing + a dedicated setter separate from sampler overrides.
packages/llm-llamacpp/addon/src/model-interface/LlamaModel.hpp Tracks a load-time default and supports request overrides.
packages/llm-llamacpp/addon/src/model-interface/LlamaModel.cpp Applies per-request tools_compact, logs unsupported overrides, and wires defaults into schedulers.
packages/llm-llamacpp/addon/src/model-interface/ContinuousBatchScheduler.hpp Carries a tools-compaction default through the scheduler.
packages/llm-llamacpp/addon/src/model-interface/ContinuousBatchScheduler.cpp Instantiates per-seq tools controllers with request-specific enabled state and warnings on unsupported models.
packages/llm-llamacpp/addon/src/addon/AddonJs.hpp Parses tools_compact as an optional boolean generation override from JS.
packages/cli/test/e2e/http/files-validation.test.ts Verifies /v1/files/:id/content returns the original uploaded Content-Type.
packages/cli/src/serve/routes/files.ts Stores upload MIME type in the ephemeral file record.
packages/ai-sdk-provider/test/provider.test.ts Expands provider tests for v4 surface, local audio endpoints, files upload/reference resolution, and APICallError shape.
packages/ai-sdk-provider/test/managed-provider.test.ts Verifies managed-mode provider exposes the same expanded surface.
packages/ai-sdk-provider/src/types.ts Updates provider types to include files/transcription/speech v4 contracts.
packages/ai-sdk-provider/src/provider.ts Composes custom provider capabilities and wraps language models for local file reference resolution.
packages/ai-sdk-provider/src/managed/index.ts Reuses createExternalQvac for managed-mode provider construction.
packages/ai-sdk-provider/src/files.ts Implements AI SDK v4 Files adapter backed by QVAC serve’s ephemeral store.
packages/ai-sdk-provider/src/file-reference-model.ts Resolves { qvac: <id> } file references via local /files/:id/content before model calls.
packages/ai-sdk-provider/src/audio-models.ts Adds native transcription + speech model adapters targeting QVAC local audio endpoints.
packages/ai-sdk-provider/README.md Updates docs for Node 22+, AI SDK 7/provider v4, and new files/audio capabilities.
packages/ai-sdk-provider/package.json Bumps engine/peer deps and adds provider/provider-utils runtime deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 17 to 20
@@ -13,15 +18,58 @@ import type {
// pointed at a `qvac serve openai` endpoint the caller runs themselves. This is
// the v1 behaviour, kept byte-for-byte identical.
export function createExternalQvac(options: QvacExternalOptions = {}): QvacProvider {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3a5fe82b. The comment now describes external mode as composing the OpenAI-compatible language-model surface with QVAC native files, transcription, speech, and file-reference resolution.

Copilot AI review requested due to automatic review settings July 22, 2026 16:23
@danielAsaboro

Copy link
Copy Markdown
Contributor Author

Addressed all review feedback in d58e8851 and 3a5fe82b.

  • Added native per-request tools_compact support across the JS boundary, serialized requests, and isolated continuous-batch slots. Load-time configuration remains the default for direct addon consumers.
  • Added validation and real-addon coverage for request overrides, explicit disable/reset, and mixed enabled/disabled batches.
  • Replaced plain HTTP errors in audio, uploads, and file-reference handling with structured APICallError responses via provider-utils.
  • Documented Node 22+, AI SDK 7, provider-v4, and ESM-only requirements.
  • Updated the external-provider comment to describe its native files/audio capabilities and file-reference resolution accurately.
  • Updated the PR title to use [bc].

Verification passed locally:

  • Provider format, lint, typecheck, build, unit tests, and packed ESM smoke
  • SDK format, lint, typecheck, build, unit, and Bare tests
  • Native addon build and type checks
  • C++ tools-compaction tests: 41/41
  • Generation-parameter integration: 3/3
  • Real-addon tools-compaction integration: 21/21

Could a maintainer please apply verified, run-cpp-addon-tests, and run-desktop-addon-tests so the full native CI suite runs?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 37 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

packages/ai-sdk-provider/src/provider.ts:20

  • The comment says external mode is kept “byte-for-byte identical” to the v1 behavior, but this function now composes additional native capabilities (files/transcription/speech) and wraps language models to resolve local file references. This documentation is now misleading for readers trying to understand what differs from the older wrapper.
// External mode synchronously composes the OpenAI-compatible language-model
// surface with QVAC's native files, transcription, and speech capabilities.
// Language models also resolve QVAC file references through the caller-managed
// `qvac serve openai` endpoint.

Comment on lines +21 to +25
const headers: Record<string, string> = { ...DEFAULT_HEADERS, ...options.headers }
if (options.apiKey ?? DEFAULT_API_KEY) {
headers['Authorization'] = `Bearer ${options.apiKey ?? DEFAULT_API_KEY}`
}
const baseURL = options.baseURL ?? DEFAULT_BASE_URL
Comment on lines +58 to +63
const prepare = async (
call: LanguageModelV4CallOptions
): Promise<LanguageModelV4CallOptions> => ({
...call,
prompt: await resolvePrompt(call.prompt, options, call.abortSignal)
})
Copilot AI review requested due to automatic review settings July 22, 2026 16:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 37 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

packages/ai-sdk-provider/src/provider.ts:25

  • createExternalQvac can end up sending two Authorization headers if the caller passes headers: { authorization: ... } (lowercase) and this code also adds Authorization (capitalized). Because header names are case-insensitive, this can lead to an ambiguous/combined Authorization value and break authentication. Normalize by deleting any pre-existing authorization header (any casing) and then setting exactly one authorization header based on the resolved apiKey.
  const headers: Record<string, string> = { ...DEFAULT_HEADERS, ...options.headers }
  if (options.apiKey ?? DEFAULT_API_KEY) {
    headers['Authorization'] = `Bearer ${options.apiKey ?? DEFAULT_API_KEY}`
  }

Copilot AI review requested due to automatic review settings July 22, 2026 16:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 38 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 22, 2026 17:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 22, 2026 18:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@opaninakuffo

opaninakuffo commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

hey @danielAsaboro, thanks for the AI SDK v7 work!

one thing, the request-scoped tools_compact / dynamic-tools fix overlaps with work we're landing to remove SDK dynamic tools mode and the addon tools_compact path (#3380, #3373). that surface is being deprecated, so we'd rather not deepen it here.

could you kindly carve those bits out and keep this PR focused on the AI SDK 7 migration?

also curious, were you relying on dynamic tools or did you just hit the bug along the way?

happy to re-review once it's split

Copilot AI review requested due to automatic review settings July 23, 2026 14:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@danielAsaboro

Copy link
Copy Markdown
Contributor Author

Hi, @opaninakuffo

Thanks for the context ...that makes sense.

I was relying on dynamic tools in Leash rather than just encountering the code incidentally.

We serve Qwen3 with tools: true / toolsMode:"dynamic" and expose a focused, per-turn subset of a larger tool registry.

The regression surfaced on real tool-less paths - including raw completions, mesh-forward/mobile borrowing, and reasoning-only subagents. This is often where a model loaded in dynamic mode received no tools and failed
with tools_compact requires non-empty tools.

I implemented the request-scoped fix before I was aware that #3373 and #3380 were removing the SDK and addon surfaces entirely. Given that direction, I agree it doesn’t make sense to deepen the feature here.

I’ll carve all tools_compact / dynamic-tools changes and their tests out of #3370, update the title and description, and keep the PR focused on the AI SDK 7/provider-v4 migration. I’ll rerun the relevant provider and CLI checks and request another review afterward.

On the Leash side, I’ll retain the existing 0.13.x workaround while needed and migrate to static tool placement when the removal ships. Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution PR or issue from an external contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants