Gregor Martynus
90d · built 2026-09-08
Performance
What Gregor Martynus shipped in the selected window, measured in ETV, and how it compares with the 90 days before it.
Effective capacity
+6.4engineers
delivers like 7.4 (7.4x pre-AI)
Output (ETV)
22.3ETV
+52.8% vs 14.6 prior
Features share
17.0%
−13.0 pp vs prior window
Fixes share
29.7%
+23.7 pp vs prior window
Work mix
17% Features3.9% Maintenance41.3% Tests8.1% Docs29.7% Fixes
131 commits over 90 days, ending 2026-09-08.
Daily performance
Daily ETV, stacked by Features, Maintenance, Tests, Docs and Fixes.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 10 by ETV in the last 90 days.
- 3.5ETVfeat(video): externalize polling control and webhook support for generateVideo (#12515) - Adds optional `doStart`/`doStatus` methods to the experimental `VideoModelV4` spec, enabling a two-phase async model where the SDK core orchestrates polling or webhook-based completion instead of each provider implementing its own polling loop. - Adds `poll` and `webhook` parameters to `experimental_generateVideo` for user-controlled polling intervals/timeouts and webhook-based completion. - Adds optional `handleWebhookOption` to `VideoModelV4` so a provider can signal native webhook support. The SDK only invokes the user's webhook factory when the model implements it; otherwise it falls back to polling. - Replaces `doGenerate` with `doStart`/`doStatus` in the first-party models implementing `VideoModelV4` (FAL, Alibaba, KlingAI, Replicate, xAI, Google, and Google Vertex). The gateway video model remains a `doGenerate` passthrough. - Makes `doGenerate` optional on `VideoModelV4`. Third-party providers implementing only `doGenerate` continue to work unchanged, and the SDK falls back to it when no async flow is selected. - Constrains operation handles to `JSONValue`, making the provider-owned task reference serializable and suitable for future persistence and gateway support. Towards https://github.com/vercel/ai/issues/12381. ## Architecture ``` User calls: generateVideo({ model, prompt, poll?, webhook? }) ↓ Core function: Chooses flow based on model capabilities and user options ↓ ┌───────────┴───────────────┐ │ │ [Legacy / fallback] [Start/Status flow] │ (poll or webhook provided, │ or doGenerate missing) │ │ model.doGenerate(options) model.doStart(options) ↓ ↓ Provider owns polling returns { operation } ↓ ┌───────────┴────────────┐ │ │ [native webhook] [polling] │ │ await notification SDK core loop │ │ model.doStatus(op) model.doStatus(op) └───────────┬────────────┘ ↓ return result ``` The SDK core owns the polling lifecycle. Providers only implement single-shot `doStart` (submit) and `doStatus` (check once). The polling interval and timeout are controlled by the user. ## Review decisions - **`poll` and `webhook` are intentionally composable.** When a model supports native webhooks, `poll.timeoutMs` limits the webhook wait. When it does not, the same polling configuration controls the automatic fallback. This behavior is documented and covered by tests. ([discussion](https://github.com/vercel/ai/pull/12515#discussion_r2821429102)) - **The first iteration uses fixed polling intervals and timeouts.** Exponential backoff and `onAttempt` were removed to keep the API small; aligning polling with shared retry/backoff primitives can be revisited separately. ([discussion](https://github.com/vercel/ai/pull/12515#discussion_r2821435402)) - **Operation handles are JSON-serializable.** `operation` is typed as `JSONValue` instead of `unknown`, while remaining opaque to core. ([discussion](https://github.com/vercel/ai/pull/12515#discussion_r2843221880)) - **Shared cross-modality operation types are deferred.** The current types remain video-specific until another modality implements the same lifecycle, avoiding a premature provider-spec abstraction. ([discussion](https://github.com/vercel/ai/pull/12515#discussion_r2843151286)) - **Replicate's server-side `wait` remains provider-specific.** It affects how long Replicate holds the submission request open, while top-level `poll` controls the SDK's provider-neutral status lifecycle. It can be added through Replicate provider options independently if needed. ([discussion](https://github.com/vercel/ai/pull/12515#discussion_r2843203139)) ## Verification - [x] Tests added and updated - [x] Documentation added and updated - [x] Patch changeset added - [x] TypeScript, lint/format, and code consistency checks pass locally ## Future work - Expose async operations as a first-class API: start an operation, persist its handle, and resume or query it later. ([discussion](https://github.com/vercel/ai/pull/12515#discussion_r2843215786)) - Generalize operation types after another model modality implements the same lifecycle. - Add AI Gateway transport for the JSON-serializable start/status operation flow. - Explore a multi-notification webhook receiver if providers need intermediate status events rather than a single completion notification. ## Related issue https://github.com/vercel/ai/issues/12381 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: josh <144584931+dancer@users.noreply.github.com> Co-authored-by: vercel-ai-sdk[bot] <225926702+vercel-ai-sdk[bot]@users.noreply.github.com> Co-authored-by: mat lenhard <mclenhard@gmail.com> Co-authored-by: Rohan Taneja <47066511+R-Taneja@users.noreply.github.com> Co-authored-by: Felix Arntz <felix.arntz@vercel.com> Co-authored-by: Walter Korman <shaper@vercel.com> Co-authored-by: Nico Albanese <49612682+nicoalbanese@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-ai · 79e133cc · 2026-08-03
- 1.0ETVfix(openai): expand Responses API parallel tool call wrappers (#19098) ## Background Azure OpenAI gpt-5.4 can intermittently return its internal parallel dispatch wrapper as a Responses API function call named parallel. Generic tool validation treats that wrapper as an unavailable user tool, so its valid nested calls are discarded. ## Changes - recognize the wrapper only when parallel is not a declared client tool - require the reported tool_uses shape and verify every nested recipient against declared function tools - expand valid nested calls into ordinary SDK tool calls for generate and stream paths - preserve existing behavior for declared parallel tools, malformed payloads, and unavailable nested tools - add recorded-response fixtures plus focused helper coverage ## Testing - pnpm --filter @ai-sdk/openai test - pnpm check - pnpm type-check:full ## End-to-end validation A live Azure deployment is unavailable, so validation uses non-streaming and streaming fixtures derived from the trusted production payload reported in the issue. Fixes #19067github.com-vercel-ai · 6be0f51d · 2026-08-20
- 0.8ETVfix(providers): only send credentials to same-origin response-supplied URLs (#15989) ## Background Reported via the Anthropic CVD as **VULN-11567 (ANT-2026-FX163E39)**. Several provider clients follow a URL taken from the provider's API response — a polling/status URL or a final media URL (`polling_url`, `urls.get`, `result_url`, `result.sample`, `video.uri`) — and reuse the authenticated headers, or append `?key=<API_KEY>`, on that request. Because the host of the response-supplied URL is never validated, the long-lived API key is sent to whatever host the response names (a CDN in the benign case, or an attacker-chosen host if the provider is compromised or the response is tampered with), allowing credential exfiltration. A safe no-headers pattern already exists in `xai-image-model.ts`. ## Summary Adds an `isSameOrigin(url, baseUrl)` helper to `@ai-sdk/provider-utils` and gates every affected fetch so the provider credential is attached **only when the followed URL is same-origin with the provider's configured API origin**; a foreign origin gets the request without credentials. This single rule covers both cases correctly: - **Polling/status URLs** (need auth, normally same-origin) keep working. - **Media downloads** on a CDN (foreign origin) are fetched without the key. Google's video download legitimately needs the key, and its URI is same-origin, so it still works. ### Sites gated | Package | Site | Response field | | --- | --- | --- | | `@ai-sdk/black-forest-labs` | poll + image download | `polling_url`, `result.sample` | | `@ai-sdk/fireworks` | image download (workflows_async) | `result.sample` | | `@ai-sdk/replicate` | video poll | `urls.get` | | `@ai-sdk/gladia` | transcription poll | `result_url` | | `@ai-sdk/fal` | video status poll | `response_url` | | `@ai-sdk/google` | video download (`?key=`) | `video.uri` | (fal's transcription poll and Google's operation poll build their URL from a hardcoded/`baseURL`-anchored host, so they are not response-host-supplied and are left unchanged. `fal-image` and `replicate-image` already omit credentials on download.) ### Tests - Unit tests for `isSameOrigin` (same origin, foreign host, scheme/port mismatch, fail-closed on invalid input). - A foreign-origin regression test per provider asserting the credential header (or `?key=`) is **not** sent when the response names a different host. All suites pass in node and edge. ## Manual Verification <!-- TODO --> ## Checklist - [x] All commits are signed (PRs with unsigned commits cannot be merged) - [x] Tests have been added / updated (for bug fixes / features) - [ ] Documentation has been added / updated (for bug fixes / features) - [x] A _patch_ changeset for relevant packages has been added (for bug fixes / features - run `pnpm changeset` in the project root) - [x] I have reviewed this pull request (self-review) ## Future Work - Consider routing all response-supplied follow-up fetches through a small shared wrapper (origin-gated `getFromApi`) so new providers can't reintroduce this pattern, and so the rule is enforced in one place rather than per call site. ## Related Issues Linear: VULN-11567 (tracked under VULN-11626). --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>github.com-vercel-ai · aeda3739 · 2026-06-11
- 0.7ETVfix(gateway): preserve structured AI Gateway failure details in nested API call errors (#18760) ## Summary - serialize structured AI Gateway failed-response payloads before constructing nested API call errors - cover all Gateway request paths on main, including realtime client-secret and async video handlers - add a regression assertion that the nested APICallError preserves Gateway failure details ## Testing - pnpm --filter @ai-sdk/gateway test - pnpm --filter @ai-sdk/gateway type-check - pnpm check - pnpm type-check:full ## Related Issues Related to #15872. Forward-port of #18737.github.com-vercel-ai · ea75787a · 2026-08-12
- 0.6ETVfix(provider-utils,ai): harden download SSRF guard against hostname and redirect bypasses (#15980) ## Background The download URL validation guard (`validateDownloadUrl`) and the file download helpers (`downloadBlob`, `download`) had several bypasses when handling untrusted URLs. These were reported via the Anthropic coordinated vulnerability disclosure (Linear VULN-11554, VULN-11552, VULN-11560; tracked under VULN-11626). ## Summary Hardens the guard against three classes of bypass: - **Trailing-dot hostnames** — a fully-qualified name with a trailing dot (e.g. `localhost.`, `myhost.local.`) resolves identically to the bare name but skipped the localhost/`.local` blocklist. The hostname is now normalized (trailing dots stripped) before the checks. - **IPv6 with embedded IPv4** — the previous string-prefix logic missed addresses that carry an IPv4 target in their last 32 bits: IPv4-compatible (`::a.b.c.d`), IPv4-translated (`::ffff:0:a.b.c.d`), and NAT64 (`64:ff9b::/96` and the `64:ff9b:1::/48` local-use prefix). The address is now fully expanded into its 8 groups, the embedded IPv4 is decoded and run through the existing private-range checks, and unparseable addresses fail closed. - **Redirects validated too late** — redirects were only checked *after* `fetch` had already followed them, so the request to a redirect target had already been issued before the guard ran. **On the server**, both download helpers now follow redirects manually (`redirect: 'manual'`), re-validating each hop (resolved against the current URL) **before** requesting it, capped at 10 hops. ### Browser behavior The manual redirect handling is gated on a new `isBrowserRuntime()` helper. In a browser, `fetch(url, { redirect: 'manual' })` returns an unreadable opaque-redirect response, so per-hop validation is impossible and would break every legitimate redirected download. SSRF is also a server-side threat — browser fetch is constrained by CORS and cannot reach a server's internal network or cloud-metadata endpoints. So in the browser we fall back to `redirect: 'follow'` and let the platform follow redirects natively. The initial URL is still validated in both environments. ### Files - `packages/provider-utils/src/validate-download-url.ts` — trailing-dot normalization + IPv6 expansion/embedded-IPv4 detection - `packages/provider-utils/src/is-browser-runtime.ts` — new runtime helper (exported) - `packages/provider-utils/src/download-blob.ts`, `packages/ai/src/util/download/download.ts` — manual redirect following with per-hop validation on the server; native follow in the browser Regression tests added for all three bypasses, the browser fallback, and the runtime helper. A regression test asserts the unsafe redirect target is **never requested** on the server. ## Manual Verification <!-- TODO --> ## Checklist - [ ] All commits are signed (PRs with unsigned commits cannot be merged) - [x] Tests have been added / updated (for bug fixes / features) - [ ] Documentation has been added / updated (for bug fixes / features) - [x] A _patch_ changeset for relevant packages has been added (for bug fixes / features - run `pnpm changeset` in the project root) - [x] I have reviewed this pull request (self-review) ## Future Work - **DNS-resolution bypass (VULN-11509)** is intentionally not addressed here. A correct fix requires resolving the hostname and pinning the connection to the validated IP (to defeat DNS rebinding), which is Node-only and either changes the public `validateDownloadUrl` signature to async or adds an undici dispatcher dependency. Recommended as a separate, ADR-backed follow-up. ## Related Issues Linear: VULN-11554, VULN-11552, VULN-11560 (tracked under VULN-11626). VULN-11509 deferred (see Future Work). --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: dnukumamras <mukund.sarma@vercel.com>github.com-vercel-ai · 375fdd7e · 2026-06-11
- 0.6ETVfeat(openai): report Chat Completions stream mismatch (#16423) ## Background Issue #16408 reports that after migrating to AI SDK v7, `streamText` with tools can execute tool calls but the final assistant text does not appear when using `createOpenAI({ baseURL })` against a custom OpenAI-compatible endpoint. The reproduced mismatch is that `provider('gpt-4o-mini')` uses the OpenAI Responses API in v7, while the custom endpoint returns Chat Completions SSE chunks. Those chunks were previously parsed by the Responses stream parser, producing a generic validation error instead of telling the user that the wrong OpenAI API surface was selected. Using `provider.chat('gpt-4o-mini')` against the same mocked Chat Completions chunks preserves the tool call, executes the tool, and streams the final UI text. I also checked the related #12056 Azure-style chunks (`choices: []`, empty assistant content prelude, and trailing content filter chunk); the OpenAI Chat parser already streams the text delta for that shape. Credit to @itisvincent for #16408, @mac-110 for the raw Azure-compatible stream fixture in #12056, and @PaulyBearCoding for the related no-argument tool-call investigation in #10283. #10283 is a separate empty-tool-arguments issue and is not fixed here. ## Summary - Detect Chat Completions stream chunks that reach the OpenAI Responses stream parser and return a targeted `APICallError` explaining the API mismatch. - Document that `openai('model-id')` / `createOpenAI()('model-id')` uses the Responses API, including when `baseURL` is customized. - Document that Chat Completions-only custom endpoints should use `openai.chat('model-id')` or the OpenAI-compatible provider. - Add provider tests for custom `baseURL` routing, Azure-compatible Chat Completions stream chunks, and the new mismatch error. - Add a patch changeset for `@ai-sdk/openai`. ## Manual Verification Reproduced the reported setup locally with `streamText`, `createOpenAI({ baseURL, fetch })`, a tool call step, and a follow-up Chat Completions SSE text step. With `provider('gpt-4o-mini')`, the mocked Chat Completions stream is routed through the Responses parser and now reports a helpful mismatch error. Switching only the model to `provider.chat('gpt-4o-mini')` executes the tool and yields `text: "Created test."`, `finishReason: "stop"`, and a UI stream containing the final `text-delta`. Also confirmed that the Azure/content-filter stream fixture based on #12056 is realistic and is handled by the OpenAI Chat parser. The Responses parser mismatch detection covers Azure-style `choices: []` chunks. Automated focused test run: ```sh pnpm --filter @ai-sdk/openai exec vitest --config vitest.node.config.js --run src/openai-provider.test.ts src/chat/openai-chat-language-model.test.ts src/responses/openai-responses-language-model.test.ts -t "baseURL configuration|should stream text after Azure content filter chunks|should return helpful error when Chat Completions stream is received" pnpm --filter @ai-sdk/openai type-check ``` ## Checklist - [x] All commits are signed (PRs with unsigned commits cannot be merged) - [x] Tests have been added / updated (for bug fixes / features) - [x] Documentation has been added / updated (for bug fixes / features) - [x] A _patch_ changeset for relevant packages has been added (for bug fixes / features - run `pnpm changeset` in the project root) - [x] I have reviewed this pull request (self-review) ## Future Work The no-argument tool call behavior from #10283 should remain a separate fix path. ## Related Issues Closes #16408. Related to #12056 and #10283.github.com-vercel-ai · 1ead90c7 · 2026-06-29
- 0.6ETVfeat(provider/openai): support GPT-5.6 reasoning and prompt caching controls (#17028) ## Summary - add GPT-5.6 `max` reasoning effort support for Chat Completions - add Responses API `reasoningMode` and `reasoningContext` options, including the effective reasoning context in provider metadata - support GPT-5.6 implicit and explicit prompt cache options, 30-minute TTL, and explicit cache breakpoints for supported message content - report cache-write tokens for Chat Completions and Responses without double-counting uncached input tokens - document GPT-5.6 reasoning, prompt caching, cache-write usage, and original image detail behavior - add `generateText` and `streamText` examples for pro mode, persisted reasoning context, and explicit prompt caching Follow-up to #17021. ## Test plan - `pnpm test:node` in `packages/openai` — 19 files, 772 tests - `pnpm test:edge` in `packages/openai` — 19 files, 772 tests - `pnpm type-check` in `packages/openai` - `pnpm type-check:full` - `pnpm check` - `node tools/validate-properties-tables.mjs` Live GPT-5.6 examples were not run locally because no `OPENAI_API_KEY` was configured.github.com-vercel-ai · b2b1bb98 · 2026-07-09
- 0.6ETVfix(google): resolve local JSON Schema references (#19141) ## Background Google schema conversion silently dropped local `$ref` and `$defs`, leaving referenced tool properties without their declared types. #19006 attempted to preserve them using Vertex AI's `ref` / `defs` representation, but the Gemini Developer API rejects those fields because the converter is shared by both backends. ## Summary - Inline direct local references to root-level `$defs` and legacy `definitions` before converting to Google's OpenAPI-compatible schema. - Preserve sibling schema metadata such as descriptions. - Reject missing, non-local, nested, and recursive references with `UnsupportedFunctionalityError` instead of emitting malformed requests. - Cover the shared request paths for Gemini Developer API, Vertex AI, structured output, and Realtime. ## Contributor Credit - @danielo515 reported the issue and supplied the reproduction in #19002; the implementation commit includes their co-authorship. - @Oxygen56 contributed the initial fix in #19006. - The compatibility review on #19006 identified the shared-converter and request-level coverage requirements addressed here. ## End-to-End Verification Verified the complete request-construction paths for Gemini Developer API, Vertex AI, structured-output response schemas, and Realtime session configuration. Each path removes the root definitions and replaces local references with the corresponding inline schema before serialization. ## Checklist - [x] All commits are signed (PRs with unsigned commits cannot be merged) - [x] Tests have been added / updated (for bug fixes / features) - [ ] Documentation has been added / updated (for bug fixes / features) - [x] A _patch_ changeset for relevant packages has been added (for bug fixes / features - run `pnpm changeset` in the project root) - [x] I have reviewed this pull request (self-review) ## Related Issues Closes #19002. Supersedes #19006. Co-authored-by: Daniel Rodríguez Rivero <2270425+danielo515@users.noreply.github.com>github.com-vercel-ai · 6c5a1ed9 · 2026-08-20
- 0.6ETVfeat(zai): add Z.AI provider (#19630) ## Summary - add the first-party `@ai-sdk/zai` provider for Z.AI GLM chat completions - support streaming, reasoning, tool calls, multimodal URL inputs, Z.AI-specific options, error handling, and workflow serialization - add Node and Edge tests, examples, provider documentation, workspace integration, and a major changeset Closes #17254 ## Test plan - `pnpm --filter @ai-sdk/zai build` - `pnpm --filter @ai-sdk/zai test` - `pnpm --filter @ai-sdk/zai type-check` - `pnpm type-check:full` - `pnpm check` - `npm pack --dry-run` with a temporary npm cache Live API examples were not run because `ZAI_API_KEY` is not configured in this environment.github.com-vercel-ai · b5806424 · 2026-08-26
- 0.5ETVfix(provider-utils): cancel response body on download rejection to prevent socket leak (#15968) ## Background When a download is rejected early, the `fetch` response body was left unconsumed and uncancelled. Under WHATWG Fetch / undici, an undisturbed body keeps the underlying TCP socket open instead of returning it to the connection pool. This happened on three paths: - `readResponseWithSizeLimit()` threw immediately when the `Content-Length` header exceeded `maxBytes`, before any reader was acquired. - The `!response.ok` early-throw paths in `download()` and `downloadBlob()`. - The redirect-validation path in `download()` and `downloadBlob()`, where a redirect resolving to a blocked URL (SSRF / open-redirect) threw before the body was consumed. An attacker-controlled origin can advertise a large `Content-Length` (or return an error status, or open-redirect to a blocked URL) **without sending a body**, accumulating open sockets on the victim until file-descriptor exhaustion (`EMFILE`) / connection-pool starvation causes a remote denial of service. This is reachable by any application that downloads user-supplied URLs (e.g. multimodal image/file inputs), even with SSRF mitigations in place. ## Summary - Added a `cancelResponseBody(response)` helper (`packages/provider-utils/src/cancel-response-body.ts`) that calls `response.body?.cancel()` and swallows cancel errors so the original rejection is preserved. Exported from `@ai-sdk/provider-utils`. Kept in its own module since it is a connection-cleanup concern reused independently of size-limiting. - Cancel the body before throwing on every early-rejection path: - Content-Length over limit in `readResponseWithSizeLimit()` - `!response.ok` in `download()` and `downloadBlob()` - blocked redirect target in `download()` and `downloadBlob()` - Added regression tests asserting the body is cancelled on the Content-Length, non-ok, and open-redirect paths, plus dedicated unit tests for `cancelResponseBody` itself. ## Manual Verification The original PoC requires a malicious HTTP server that advertises a 10 GiB `Content-Length` and never sends a body, plus OS-level `netstat` to observe leaked `ESTABLISHED` sockets — so it does not map to a runnable `examples/` script. Verified instead via unit tests that assert `ReadableStream.cancel()` is invoked on each early-rejection path (the cancel call is what releases the socket back to the pool): - `pnpm --filter @ai-sdk/provider-utils test:node` — all passing (613) - `pnpm --filter ai test:node` (download suite) — all passing (3090) - `pnpm check` — 0 warnings, 0 errors ## Checklist - [x] All commits are signed (PRs with unsigned commits cannot be merged) - [x] Tests have been added / updated (for bug fixes / features) - [ ] Documentation has been added / updated (for bug fixes / features) - [x] A _patch_ changeset for relevant packages has been added (for bug fixes / features - run `pnpm changeset` in the project root) - [x] I have reviewed this pull request (self-review) ## Related Issues VULN-10892 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>github.com-vercel-ai · b4507d50 · 2026-06-11