Skip to content

feat(providers): add Alibaba Token Plan Responses compatibility - #3255

Open
MoonOld wants to merge 15 commits into
apache:mainfrom
MoonOld:feat/alibaba-responses-3162
Open

feat(providers): add Alibaba Token Plan Responses compatibility#3255
MoonOld wants to merge 15 commits into
apache:mainfrom
MoonOld:feat/alibaba-responses-3162

Conversation

@MoonOld

@MoonOld MoonOld commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an explicit Alibaba Token Plan Responses execution policy for qwen3.8-max while keeping older Token Plan models on Chat Completions.

The provider catalog and account-declared ModelInfo.apiProtocol remain shared Core facts. The new wire selection, summary replay state, and request compatibility profile are Runtime-owned execution policy:

  • routes only Alibaba Token Plan qwen3.8-max to @ai-sdk/open-responses;
  • applies final request policy (store: false, unsupported forced tool_choice rejection) after caller customization;
  • persists a bounded, versioned providerOptions.makaResponses projection with source profile, item id, and summary-part lengths, without duplicating reasoning text;
  • reconstructs Alibaba id + summary reasoning items after RuntimeEvent serialization/restart;
  • verifies streamed reasoning against the provider final summary before durable state is attached;
  • preserves multiple/empty reasoning items and isolates invalid later item ids or late same-id deltas from finalized state;
  • preserves provider failure codes through metadata-less stream flush trailers without widening retry eligibility;
  • keeps DeepSeek on its existing plaintext-content replay path;
  • keeps provider-native Alibaba tools out of scope for feat(runtime): map Alibaba Token Plan native Harness tools into Maka #3163.

Relative to current main, Core has no diff. Feature code changes only packages/runtime; the only Runtime Host diff removes three stale legacy surface fields from current-main test fixtures so the merged tree compiles.

Fixes #3162

Verification

  • root npm run build:test passed on the current-main merge;
  • focused ModelAdapter / Open Responses failure and replay tests passed, including late-delta restart recovery and unsupported durable-version degradation;
  • provider conformance matrix and Responses contracts passed, including executable two-request Alibaba CN/global tool continuations;
  • matrix override cells and executable bindings are checked as a duplicate-free bijection;
  • exact-head high-signal Runtime/Runtime Host suites passed 453/453 after merging current main; the preceding full Runtime run passed 2,992 with 13 skipped and only the pre-existing Node 25 PTY child fixture hitting its fixed 10-second timeout;
  • Biome and git diff --check passed;
  • CI planner: full=false, asf_source=false; Runtime plus its real reverse dependents are selected.

Exact-head GitHub Actions still require maintainer authorization for the external fork.

Provider and live evidence

  • The branch model factory reached Alibaba Token Plan China /compatible-mode/v1/responses with qwen3.8-max; the same credential was rejected by Coding Plan Chat with HTTP 401 (request id 3f605e8f-be3a-992a-a443-f57c2a1b7387).
  • A real Maka TUI run on the feature path completed streamed reasoning, a Maka-owned Read continuation, and the final answer: feat(providers): add Alibaba Token Plan Responses compatibility #3255 (comment)
  • Alibaba official Responses documentation defines a reasoning item with required id and required summary, whose entries are summary_text, and publishes response.output_item.done examples carrying that shape: https://help.aliyun.com/en/model-studio/qwen-api-via-openai-responses
  • The pinned @ai-sdk/open-responses@2.0.28 maps that completed item to reasoning-end provider metadata; an exact raw-SSE fixture now asserts response.reasoning_text.delta text equals response.output_item.done.item.summary before Runtime attaches durable replay state.

Review focus

  • provider-runtime-policy.ts is the effective execution-policy seam; the shared provider registry remains unchanged, and Alibaba uses one named compatibility profile rather than hypothetical composable modules.
  • request customization and provider compatibility have explicit ordering: compatibility is final authority before dispatch.
  • metadata-less reasoning-end is deferred until terminal settlement: an existing provider error wins, while a successful stream without final summary still fails closed.
  • raw response.failed codes are preserved and classified; existing retry policy remains unchanged, so rate limits without Retry-After are not automatically replayed.
  • explicit account apiProtocol: openai-chat overrides the default qwen3.8-max Responses route.

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

OpenAI Codex assisted with protocol research, implementation, automated tests, review, and PR drafting. The human contributor reviewed the scope and remains responsible for the contribution.

Checklist

  • Tests cover the changed behavior and fail without it
  • Lint, format, typecheck, build, and affected suites pass locally
  • Real user-facing reasoning and Maka-owned tool continuation evidence is attached

Does this PR entail a change in behavior?

  • Yes — described under Summary
  • No

@MoonOld

MoonOld commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han Could you review the compatibility architecture when convenient? Current main owns parsing/serialization through @ai-sdk/open-responses; Maka adds typed request policies and bounded durable summary identity for Alibaba while keeping DeepSeek content-only. The automated contract and negative-path coverage is in place. A protected Token Plan rerun with retained evidence and the requested real TUI screenshot are still pending.

@MoonOld

MoonOld commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Review follow-up complete on final head 41a1047. Four independently identified boundaries are now covered: header-only request customization cannot bypass store:false/tool-choice policy; legacy and foreign-profile reasoning degrades without blocking; multiple and empty reasoning items retain identity and summary-part boundaries; and streamed reasoning must equal the provider's final summary before durable metadata is attached. A mismatch flushes visible partial text without makaResponses and a later Turn remains usable. DeepSeek is explicitly unchanged from #2972: content-only replay, no new item ID or durable state. Three independent subagent reviews now report no remaining P0-P2. Local evidence: Core 557 passed, focused Runtime 263 passed, provider matrix/thinking 168 passed, root build passed; full Runtime recorded 2,962 passed / 12 skipped with one unrelated PTY 10-second timeout reproduced in isolation. The PR and #3162 descriptions have been updated to match the final behavior.

@MoonOld

MoonOld commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han The final review follow-ups are now on head 41a1047. DeepSeek has been restored exactly to main's content-only behavior, while Alibaba's request policy, profile-isolated durable replay, multi/empty item handling, and failure-safe summary verification now have focused regression coverage. Could you review this final architecture when convenient? GitHub still requires maintainer authorization before the external-fork Actions can start.

@Astro-Han

Copy link
Copy Markdown
Contributor

Reviewing! Thanks for asking~

@Astro-Han
Astro-Han marked this pull request as ready for review August 19, 2026 15:17

@Astro-Han Astro-Han 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.

Thanks for the final architecture update. I re-reviewed this exact head and found one P2 in the reasoning-item failure path, added inline below.

The provider routing, final store: false override, profile isolation, durable summary validation, and AI provenance otherwise look sound. Approval also needs one author-provided screenshot from the actual user-facing surface: please attach a real terminal or Desktop capture showing qwen3.8-max reasoning followed by a Maka-owned tool continuation. Text-only live verification is not enough for this UI/UX gate.

The remaining exact-head CI gates are still pending, so this is not merge-ready yet.

AI-assisted review disclosure: OpenAI Codex performed the exact-head architecture, failure-recovery, provenance, UI-evidence, and CI analysis; I verified the reproduction, severity, smallest fix, and live GitHub state before posting.

中文说明

当前有一个 P2:第二个 reasoning item 的非法 ID 会让它的无 ID delta 追加到已完成的第一个 item,最终把持久化 summary 状态写坏,导致下一轮在发请求前失败。另请作者提供一张真实 terminal 或 Desktop 截图,展示 qwen3.8-max reasoning 后继续执行 Maka-owned tool;文字说明不能替代 UI/UX 截图。CI 也仍需全绿。

Comment thread packages/runtime/src/ai-sdk-backend.ts Outdated
}
part.text += event.text;
} else if (stepResponsesThinkingParts.length > 0) {
stepResponsesThinkingParts.at(-1)!.text += event.text;

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.

[P2] Keep an invalid next reasoning item from corrupting the finalized item

If item A has already finalized with makaResponses.summaryPartLengths and item B then supplies an ID rejected by safePlaintextResponsesReasoningItemId (for example, over 512 characters or containing C0), B's deltas arrive without an item ID and this branch appends them to A. B's reasoning-end fails, but the partial flush can persist A's old lengths with A+B text; the next turn then fails in reconstructSummaryParts() before provider dispatch. Please start a new undurable part when an idless delta follows a part with finalized provider metadata (or clear that durable metadata before the partial-error flush), and add a valid-A + invalid-B + next-turn recovery regression.

中文说明

已完成的 A 带有持久化长度元数据;非法 ID 的 B 会走无 ID 分支并把文本追加到 A。随后错误路径会保存“A 的旧长度 + A/B 合并文本”,下一轮重建时长度不一致,整个会话无法继续。最小修复是在已 finalized part 后遇到无 ID delta 时新建不带 durable metadata 的 part,并补恢复回归。

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 34bed03. When an idless delta follows a finalized plaintext-summary item, Runtime now starts a separate undurable part instead of appending to the finalized item. The exact regression covers valid A → unsafe-ID B → provider error/partial flush → next-Turn recovery: A retains its original text and makaResponses boundary state, B persists without provider metadata, and the recovery prompt replays only A. Focused Runtime/Responses coverage is 264/264 green; Biome and diff checks pass.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Addressed Astro-Han’s P2 inline finding on head 34bed03. The finalized item can no longer be corrupted by deltas from a following unsafe-ID item, and the new restart/recovery regression proves the failed partial Turn does not brick the next request. The remaining reviewer request is the author-provided real Terminal/Desktop screenshot; code-side focused verification is green.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Correction to this correction: the credential owner has clarified that the supplied key is a Token Plan key; the brief Coding Plan classification was incorrect. The earlier temporary run still retained no raw HTTP artifact or request ID, so it remains withdrawn as merge evidence until the reproducible Token Plan probe and requested TUI screenshot complete.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Protected credential/routing probe completed on 2026-08-20 through the branch model factory. Coding Plan Chat returned HTTP 401 (invalid_api_key, request ID 3f605e8f-be3a-992a-a443-f57c2a1b7387), while Token Plan China /compatible-mode/v1/responses with qwen3.8-max completed successfully. This confirms the credential is Token Plan and the branch reaches the intended Responses endpoint. The probe used a 32-token output cap and produced no final text, so I am not treating it as reasoning/tool-loop evidence; the requested real TUI continuation screenshot remains pending.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Real Maka TUI verification on head 34bed03 using Alibaba Token Plan China with qwen3.8-max: streamed reasoning, a Maka-owned Read tool continuation, and the final answer completed successfully.
qwen3 8max

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han The P2 reasoning-item isolation finding is fixed on head 34bed03f1202, and the requested real Maka TUI screenshot is now attached above. Could you do a final re-review when convenient? The external-fork CI is still awaiting maintainer authorization.

jackwener
jackwener previously approved these changes Aug 20, 2026

@Astro-Han Astro-Han 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.

Reviewed at 34bed03f. This does not work as written, and the reason is a single unverified assumption about the wire contract.

provider-registry.ts declares reasoningReplay: 'plaintext-summary' for the Alibaba Token Plan provider, which commits the adapter to receiving reasoning as a summary-channel event. The vendored @ai-sdk/open-responses@2.0.28 in this tree only handles response.reasoning_text.delta — the content channel — and emits nothing on the summary channel at all. Because ai-sdk-backend.ts:2394 checks the carrier with strict equality and throws on mismatch, every turn that produces reasoning ends in an error rather than a degraded rendering. That is a P0: the feature's primary path is the path that fails. The fix is either to declare the carrier the vendored SDK actually produces, or to verify against a live endpoint that the summary channel is emitted and pin the SDK version that does so.

Architecturally, the PR builds three things that already exist one seam over: a second provider-options key resolver alongside openAiCompatibleProviderName, a second body-rewriting fetch alongside request-customization-fetch.ts, and a STATE_VERSION that does not match the one sessions were written with. The first two are duplication that will drift; the third silently strands existing sessions. None of them is hard to fold back into the existing seam, and doing so now is much cheaper than after this ships.

Reviewed with Claude Opus as an analysis assistant. The carrier finding is confirmed by reading both this head and the vendored SDK source; it is not reproduced against a live Alibaba endpoint, and that verification is exactly what I am asking for. Reproduction status is stated per finding.

Comment thread packages/core/src/provider-registry.ts Outdated
name: 'provider',
responses: {
adapter: 'open-responses',
reasoningReplay: 'plaintext-summary',

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.

[P0] Verify the reasoning carrier before declaring it, or declare the one the SDK actually emits. reasoningReplay: 'plaintext-summary' commits the adapter to the Responses summary channel, but the vendored @ai-sdk/open-responses@2.0.28 in this tree handles only response.reasoning_text.delta — the content channel — and never emits a summary event. ai-sdk-backend.ts:2394 compares the carrier with strict equality and throws on mismatch, so every Alibaba Token Plan turn that produces reasoning terminates in an error instead of rendering. Confirmed by reading this head and the vendored SDK source; not reproduced against a live endpoint, which is the verification this line needs. Either set the carrier to what the pinned SDK produces, or capture a live transcript showing the summary channel and pin the SDK version that parses it. Regression test: a recorded Alibaba reasoning stream replayed through the adapter, asserting a rendered turn rather than a throw.

safePlaintextResponsesReasoningItemId(streamItemIdValue);
const summaryParts = plaintextSummaryParts(provider);
if (!itemId || !summaryParts) {
throw new Error('Plaintext Responses reasoning item is missing final summary metadata');

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.

[P1] Do not throw on a reasoning-end that carries no providerMetadata. The SDK's flush() path emits reasoning-end with no metadata attached whenever the stream terminates early — including when the provider itself failed mid-turn — so this hard throw fires during the cleanup of an already-failing request and replaces the provider's real error with a generic plaintext-reasoning message. The user sees a misleading cause and the actual failure is lost. Confirmed by reading code at this head and the SDK's flush path; not executed. Treat missing metadata on reasoning-end as a no-op and let the underlying error surface. Regression test: abort a reasoning stream mid-flight, assert the surfaced error is the transport error and not this message.

Comment thread packages/runtime/src/model-runtime.ts Outdated
};
}

function responsesProviderOptionsKey(

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.

[P2] Reuse openAiCompatibleProviderName instead of adding a second provider-options key resolver. responsesProviderOptionsKey reimplements the same provider-to-key mapping that openAiCompatibleProviderName already owns, so a provider added to one will silently take the wrong options path in the other — and the failure is a silently ignored provider option, not an error. Confirmed by reading code at this head. Derive the key from the existing resolver. Regression test: add a provider to the existing mapping only, assert the Responses path picks up the same key.

return body;
}

async function parseJsonBody(request: Request): Promise<Record<string, unknown>> {

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.

[P2] Route this body rewrite through request-customization-fetch.ts rather than a second fetch wrapper. parseJsonBody plus the surrounding wrapper reimplements request-body interception that the existing customization fetch already performs, which means two layers can now rewrite the same request with no defined ordering between them, and a future change to one leaves the other stale. Confirmed by reading code at this head. Express this compatibility shim as a customization within the existing seam. Regression test: a request that both layers would touch, assert a single well-defined resulting body.

) {
return undefined;
}
throw new Error('Malformed durable plaintext Responses reasoning state');

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.

[P2] Reconcile STATE_VERSION with the version existing sessions were written at, or migrate them. The durable plaintext-reasoning state is read back under a version that does not match what previously-persisted sessions carry, so this throw makes those sessions permanently unresumable — the user's only recovery is to abandon the conversation, and nothing in the error says so. Confirmed by reading code at this head; not executed. Either keep the version stable and tolerate the older shape, or add an explicit migration that upgrades stored state on read. Regression test: persist a session at the pre-PR version, resume it at this head, assert it resumes.

case 'force-store-false':
body = { ...body, store: false };
break;
case 'reject-forced-tool-choice': {

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.

[P3] Remove the reject-forced-tool-choice branch or wire it up. Nothing in this PR ever produces that compatibility mode, so the case is unreachable as merged; leaving it in place suggests a behaviour the build does not have. Confirmed by reading code at this head. Delete it, and reintroduce it with its call site when it is actually needed.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han Thanks for the exact-head review. I rechecked 34bed03f, the pinned @ai-sdk/open-responses@2.0.28 package source, and every commit in this PR.

The P0 premise does not match the pinned adapter's two-stage stream mapping:

  1. response.reasoning_text.delta becomes reasoning-delta; this is the visible streamed text.
  2. response.output_item.done for a reasoning item becomes reasoning-end. Its createReasoningProviderMetadata maps the final item.summary into providerMetadata[providerOptionsName].reasoningSummary.

In this PR, plaintext-summary names the durable replay carrier, not the raw delta event name. ModelAdapter accumulates the content deltas, reads the final summary from the reasoning-end metadata, and AiSdkBackend verifies strict equality before attaching makaResponses. Missing, empty, or mismatched final summary fails the turn by design.

The real Maka TUI run on this exact head completed streamed reasoning, a Maka-owned Read continuation, and the final answer. That path cannot complete if the final summary metadata is absent or differs from the streamed text. Both package.json and the lockfile pin the adapter exactly to 2.0.28.

On the three architecture points:

  • responsesProviderOptionsKey repeats the small raw-provider-identity rule used by openAiCompatibleProviderName. I agree that helper can be centralized, but the current values are identical and contract-tested; this is a maintainability cleanup, not a failing path.
  • The two fetch layers have different authority. request-customization-fetch applies caller-owned additive headers/body fields and rejects collisions. The compatibility layer is deliberately final: it must override store to false and reject unsupported forced tool_choice, including after caller customization. Their JSON plumbing could share a utility later, but folding their authority semantics together would weaken the invariant this PR tests.
  • makaResponses does not exist on main. Its STATE_VERSION has been 1 since the first commit in this PR and never changed across the five commits, so there is no earlier released or PR-head version for this decoder to strand. Legacy/foreign state without this key remains on the tested fail-open skip path.

Given the green exact-head CI, approval, pinned adapter, and live strict-summary evidence, I do not see a code change required for these findings. If you have a concrete 2.0.28 stream shape that bypasses response.output_item.done.item.summary, please share it and I will test that exact case.

Review assistance disclosure: OpenAI Codex rechecked the exact head, pinned npm tarball, and PR commit history; I reviewed and approved this response.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Architecture follow-up before the next head update: the new Alibaba Token Plan wire selection, plaintext-summary replay contract, and request compatibility modules (store:false / forced tool-choice policy) are Runtime execution policy, not shared @maka/core domain facts. I am moving the #3255 additions out of Core and into @maka/runtime. The existing ModelInfo.apiProtocol field remains in Core because it is an account-declared cross-process model fact; only the default/effective wire resolution moves. This keeps the provider catalog shared while preventing API-adapter mechanics from widening the Core dependency boundary. The next pushed head will include the migration and updated Runtime-owned contract tests.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up completed locally for the next head update: (1) the conformance matrix now resolves reasoning replay through the effective Runtime adapter and binds both alibaba-token-plan-cn and alibaba-token-plan to an executable qwen3.8-max Responses tool-continuation test, while retaining generated Chat coverage for the other Token Plan models; (2) the branch is merged with current main (a6f33c952), so the real CI planner now reports full=false and selects Runtime plus its actual reverse dependents only. Verification on the merged tree: root build:test passed after applying repository dependency patches; Runtime test:dist passed 2,990 / skipped 13 / failed 0; focused provider matrix and Responses contracts passed 155/155. Core remains absent from the PR diff.

@Astro-Han Astro-Han 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.

COMMENT. My earlier P1 and P3 are properly fixed; the P0 has moved rather than been answered, and the way it moved introduces a new architecture problem that I think is the most important thing in this round.

Fixed, and well:

  • P1, the throw on a metadata-less reasoning-end. isUnfinalizedPlaintextSummaryReasoningEnd (model-adapter.ts:869-879) now detects exactly the SDK's flush() trailer and :364-372 defers the decision to the terminal outcome, so an existing provider failure wins while a clean stream still fails closed rather than silently losing replay state. That is a more precise fix than the one I asked for, and the comment explains why.
  • P3, the unreachable reject-forced-tool-choice branch. It is now reachable — ALIBABA_TOKEN_PLAN_RESPONSES.compatibility lists it.

Still open from the previous head, and unchanged by this one: the second fetch wrapper in open-responses-compatibility.ts (parseJsonBody plus a wrapping fetch) rather than routing the body rewrite through request-customization-fetch.ts, and the duplicate provider-to-key resolver in model-runtime.ts alongside openAiCompatibleProviderName. Both are P2 and both are now more clearly worth doing, because the new provider-runtime-policy.ts makes three separate places where this provider's special-casing lives. On the STATE_VERSION P2 I will hold rather than restate it — the durable state moved into a new responses-reasoning-state.ts at version 1 and I could not re-derive at this head whether any previously written session reads back under a different version. Please say either way in the PR body.

Two P1s inline. To be explicit about what would move me: for the second one, a captured qwen3.8-max Responses body showing the reasoning on the reasoningSummary channel would close it outright and is probably ten minutes of work. For the first one, putting the contract back in the registry — verified — closes it too, so the two are really one decision.

AI disclosure: this review was assisted by Claude (Opus) for code search and cross-checking. Everything above I re-derived myself against the source at b625a699c.

| Exclude<ProviderRuntimeAdapter, { kind: 'openai-compatible' }>
| OpenAiCompatibleRuntimeAdapter;

const ALIBABA_TOKEN_PLAN_RESPONSES = {

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.

[P1] This is a second declaration authority for provider capabilities, shadowing the registry. The previous head declared reasoningReplay: 'plaintext-summary', the two compatibility modules, and the qwen3.8-max Responses routing in packages/coreprovider-registry.ts and model-metadata.ts, the seam the whole codebase reads. This head deletes all three from core and re-declares them here: ProviderResponsesCompatibilityModule verbatim, plaintext-summary re-added to a runtime-local RuntimeProviderResponsesContract, and openAiAdapterApiProtocol's Alibaba branch reborn as defaultOpenAiApiProtocol wrapping the core function. resolveRuntimeProviderAdapter then overrides whatever the registry declared for these two provider types at model-construction time (model-runtime.ts:140). Two consequences. Anything reading PROVIDER_DEFAULTS[...].runtimeAdapter.responses — settings, capability display, any future consumer — now sees a contract the runtime does not actually use, with nothing at the boundary saying so. And the core test that pinned the routing rule (model-metadata.test.ts's "routes only Qwen3.8 Max through Alibaba Token Plan Responses") was deleted rather than moved, so the rule lost its coverage at the level where the rule lives. AGENTS.md asks for the closest existing seam rather than a parallel path, and the registry is that seam. If the concern was keeping an unverified carrier out of the published contract, the answer is to verify it, not to relocate it. Confirmed by reading the source at this head. Regression test needed: whatever replaces the deleted model-metadata.test.ts case, asserting the protocol routing at the level that owns it.


const ALIBABA_TOKEN_PLAN_RESPONSES = {
adapter: 'open-responses',
reasoningReplay: 'plaintext-summary',

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.

[P1] The substance of my earlier P0 is still open — this declares the Responses summary channel and nothing in the PR establishes that the vendored @ai-sdk/open-responses emits it for this provider. Moving the declaration out of packages/core did not answer the question; it only changed which file makes the claim. Everything downstream is built on it: plaintextSummaryParts (model-adapter.ts:881-893) accepts only reasoningSummary entries shaped { type: 'summary_text', text } and returns undefined for anything else, and model-adapter.ts:837 then throws Plaintext Responses reasoning item is missing final summary metadata for every finalized item. So if the SDK actually delivers this provider's reasoning on the content channel rather than the summary channel, the failure mode is not degraded replay — it is a thrown error on the normal path. The tests I can see all construct the summary shape themselves, so they confirm the handler, not the premise. A captured response body from a real qwen3.8-max Responses call showing the reasoningSummary array would settle it; short of that, plaintext-content is the safe declaration. Confirmed by reading the source at this head. Regression test needed: a wire-level fixture recorded from the provider, not a hand-built chunk.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Final review/fix loop update on pushed head fc20601c0: response.failed now preserves its raw provider code through settlement and usage telemetry, so rate_limit_exceeded is classified as rate_limit instead of generic provider_unavailable. Existing policy remains unchanged: without Retry-After, the failure is classified correctly but is not automatically retried. The provider conformance matrix now enforces a duplicate-free bijection between override cells and executable bindings; DeepSeek/xAI/custom-Responses gaps use real two-request HTTP continuation tests. Current main 84fbe05db is merged; planner is full=false, asf_source=false, and Core diff is zero. Local evidence: root build:test passed; focused matrix/Responses tests passed; Runtime full suite passed 2,992 with 13 skipped, with only the known local Node 25 PTY 10-second child timeout failing. Independent production re-review reports no remaining P0-P2. Exact-head Actions still await external-fork maintainer authorization and renewed approval.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han I updated the PR body with direct provider evidence for the two remaining architecture/carrier questions. Alibaba official Responses documentation defines reasoning items with required id and required summary entries of type summary_text, and publishes response.output_item.done examples carrying exactly that shape: https://help.aliyun.com/en/model-studio/qwen-api-via-openai-responses. The real Maka TUI run on this feature path also completed the strict streamed-text == final-summary gate before a Maka-owned Read continuation, so this is no longer based only on a synthetic fixture. Core remains the shared provider/catalog/account-fact authority; the Alibaba @ai-sdk choice, replay carrier, and body policy are intentionally Runtime execution policy, with one effective resolver and zero Core diff. The latest pushed head also preserves raw response.failed codes (rate_limit_exceeded now classifies as rate_limit) and closes the matrix override/binding gap. Could you re-review fc20601c0 when convenient? Exact-head Actions still await maintainer authorization.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Occam/razor cleanup pushed on b0d4362fe: compared with the prior head, the PR is 200 net lines smaller. The two nested fetch wrappers now share one JSON/Request customization seam with an explicit finalizer; provider identity has one Runtime authority; Alibaba uses one named compatibility profile instead of a hypothetical module list; and the unreleased durable state no longer carries unused content-carrier branches or a single-value carrier field. Root build:test and exact-head high-signal suites passed (199/199), with Biome and diff checks green. Current main advanced only through Windows CI workflow repairs (#3317); it does not touch Runtime and the merge tree is clean, so I did not add another merge commit or retrigger Actions solely for that unrelated change.

@Astro-Han Astro-Han 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.

Re-reviewed at b0d4362fe. I started this round on fc20601c0, the head moved while I was verifying, and I re-derived every finding against the new head rather than carrying anything forward. I dispatched four scoped sub-reviews — security, correctness and resource bounds, integration, simplification and test quality — and used them as leads only; everything published below I confirmed myself by reading this head.

Four things are fixed, two of them the way I would have asked for.

The second fetch wrapper is gone. open-responses-compatibility.ts is now a fourteen-line body finalizer plugged into the existing request-customization-fetch.ts seam instead of a parallel wrapper with its own request reconstruction — that is exactly the right shape, and it removes the behavioural divergence I was about to file (the old copy hard-threw on GET/HEAD/null-body/non-JSON requests where the established wrapper passes them through). The carrier dimension is also gone: plaintext-summary no longer carries a carrier discriminant, which deletes a union arm no code path could produce. The reasoning-end-without-metadata throw is replaced by a deferral, and the yield in the finally does deliver the deferred error because the generator body completes normally there. reject-forced-tool-choice is now genuinely declared.

I have to correct my own P1 about the declaration split. Two of the three things I offered as evidence do not hold, and I would rather retract them than have you refute them one at a time:

  • I said the deleted core routing test was not moved. It was, and it is stronger than what it replaced: __tests__/responses-wire-contract.test.ts asserts both Token Plan providers route qwen3.8-max to Responses, that qwen3.7-max stays on chat, and that an account-declared apiProtocol override wins. The rule is covered at the layer it now lives in.
  • I said settings and capability display would see a contract the runtime does not use. There is no such consumer. Outside provider-registry.ts itself and packages/runtime/, nothing in the repo reads runtimeAdapter — not desktop, not the CLI. The divergence is latent, not observable today.

What survives is one thing, and I still think it matters: one fact has two declaration sites, and the core side states the opposite of the truth rather than a stale version of it. packages/core/src/provider-registry.ts gives both Alibaba Token Plan providers a runtimeAdapter with no responses at all, and ProviderResponsesContract has no plaintext-summary member, so core's declared position is "this provider does not support Responses" while the runtime routes it there. Relatedly, the docstring at packages/core/src/model-metadata.ts:94-98 now asserts two things that are false at this head — it calls itself the single declared source of the protocol split, and names the runtime model factory and the conformance matrix as its consumers, both of which have moved to defaultOpenAiApiProtocol. Whichever way the layering decision goes, that paragraph has to change.

The architectural question I would like a maintainer to settle, because it outlives this PR: are we adopting "core declares catalog facts, runtime declares execution policy, and runtime policy may add capabilities core says are absent" as a layering? Today it is neither documented nor annotated at runtimeAdapter. A smaller instance of the same question: model-protocol.ts adds reasoningItemId and reasoningSummaryText to the generic thinking event, filled only by the plaintext-summary path. Extending our own protocol is legitimate, but it sets the precedent that every new provider carrier adds fields to the shared event, and that is worth accepting or declining explicitly rather than by default.

For the record on the Runtime Host boundary: this PR does not touch packages/runtime-host at all, and settleModelStepOutcome decides a model step's terminal state, a kind already produced on main. The P2 below is intent bundling, not an authority violation.

One open thread I re-confirmed rather than re-filed, with a sharper mechanism than I first gave: the STATE_VERSION trap is worse than described. The profile is parsed before the version check, so a same-profile version mismatch decodes as malformed with a matching profile, misses the graceful return undefined in materializeRuntimeReplayPlan, and falls through to the throw. A future version bump therefore bricks every session written at version 1. It should degrade the way a profile mismatch already does — that is a two-line change and it is the difference between a migration and a data loss.

Carrying a P1 → COMMENT, not approval.

AI disclosure: reviewed with Claude Code, including four scoped sub-reviews used as leads. Every finding published here I re-derived at b0d4362fe myself — I read the pinned @ai-sdk/open-responses@2.0.28 stream mapping, the decode and replay paths, and the finish-classification diff, and I ran the repo-wide enumeration that retracts my two earlier evidence claims. Sub-review findings I could not confirm at this head are not included.

(candidate.providerOptions?.openai as { itemId?: unknown } | undefined)
?.itemId === itemId,
);
let part = stepResponsesThinkingPartsByItemId.get(itemId);

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.

[P1] Guard this id-keyed branch against a delta that arrives after its item was finalized, the way the id-less branch below already is. When a reasoning-delta carries an itemId this map already resolves to a part whose providerOptions hold a finalized makaResponses state, the branch appends event.text to part.text and leaves summaryPartLengths untouched; deltas carry no reasoningSummaryText, so the mismatch check immediately below never runs. flushStep then persists text of length |T|+|X| against lengths summing to |T|, and on the next turn reconstructSummaryParts in responses-reasoning-state.ts throws Durable plaintext Responses reasoning summary boundaries do not match text from inside materializeRuntimeReplayPlan; nothing between there and provider dispatch catches it and the persisted RuntimeEvent is immutable, so every later turn in that session fails before reaching the provider. The id-less branch a few lines down handles exactly this hazard correctly — it decodes the last part and starts a fresh one when the state is valid — so the fix is to apply that same decode here and start a new part, or drop the durable metadata, instead of reusing the finalized one. Evidence: confirmed by reading code at this head; reachability depends on the provider emitting a stray or duplicated response.reasoning_text.delta after response.output_item.done, which the pinned SDK forwards unconditionally (@ai-sdk/open-responses@2.0.28, dist/index.js:803-808 tracks no finalization), so nothing in the stack defends it. Regression test: reasoning-start A → delta → reasoning-end A(metadata) → delta A → finish, assert the persisted part is either split or stripped of makaResponses, then assert the following turn replays instead of throwing.

if (finishReason === 'content-filter' || finishReason === 'error') {
const terminalFailure =
finishReason === 'error'
? providerFinishFailure(rawFinishReason)

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.

[P2] Split this finish-reason reclassification out of the provider-compatibility PR, or at minimum cover it. On main a step with finishReason === 'error' is unconditionally terminal-failure carrying a non-retryable provider_unavailable; here it runs through providerFinishFailurenormalizeProviderFailure, and the outcome kind becomes terminalFailure.retryable ? 'retryable-failure' : 'terminal-failure'. That is a retry-behaviour change for every ai-sdk provider in the tree, not only Alibaba: any provider whose raw finish reason normalizes to a retryable code is now retried where it previously failed terminally. The PR body says the retry policy is unchanged — true of the policy, false of the classification fed into it. Under the one-revertable-intent rule that is the problem: reverting the Alibaba work would also roll back a cross-provider failure reclassification that has nothing to do with it. The content-filter arm is unaffected, since modelStepFailure always sets retryable: false; only the error arm moves. Evidence: confirmed by reading code at this head, not executed. Regression test: a non-Alibaba adapter finishing with finishReason: 'error' and a raw reason that maps to a retryable code, asserting both the outcome kind and the retry decision — the new retryable-failure arm has no test today, and the one test on this path asserts terminal-failure.


const ALIBABA_TOKEN_PLAN_RESPONSES = {
adapter: 'open-responses',
reasoningReplay: 'plaintext-summary',

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.

[P3] Record which wire event this declaration actually depends on, and cover it with a raw-SSE fixture. This corrects my own earlier P1 on this line: I claimed nothing established that the summary channel carries reasoning here, and having now read the pinned SDK myself I no longer think that premise is in doubt — @ai-sdk/open-responses@2.0.28 builds reasoning-end's reasoningSummary from item.summary unconditionally (dist/index.js:883-895), so any provider populating summary on response.output_item.done satisfies plaintextSummaryParts. What survives is narrower and worth stating in a comment: the streamed text and the durable lengths arrive on two different channels. Deltas reach you through response.reasoning_text.delta (dist/index.js:803) while the lengths come from item.summary, and the SDK has zero handling for response.reasoning_summary_text.delta — 0 occurrences in dist/index.js, typed but unhandled in its source. So this contract holds only because Alibaba emits summary content on the content-channel delta event. If it ever moves to the standard summary delta, the streamed text is empty while the final summary is not, and the mismatch check in ai-sdk-backend.ts throws on every reasoning turn — a hard turn failure, not degraded replay. Evidence: confirmed by reading the pinned SDK at this head; the provider-side behaviour is inference from its published Responses shape, not executed here. Regression test: a wire-level fixture driving the real SDK from raw SSE frames rather than hand-built post-SDK chunks, asserting the streamed text equals the concatenated summary.

runtime?: ResolvedModelRuntime,
): ModelStreamEvent[] {
switch (chunk.type) {
case 'reasoning-start': {

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.

[P3] Delete the now-dead second case 'reasoning-start' further down in this switch. That label is still grouped with start-step/tool-result/tool-error and their providerExecuted/toolCallId guard, but this new case precedes it, so the old arm is unreachable. Behaviour is unchanged today only because both returned [] for this chunk type — which is precisely why it will not be noticed later: TypeScript does not diagnose duplicate case labels, and biome.jsonc runs preset: none with noDuplicateCase off, so neither gate reports it. Evidence: confirmed by reading code at this head. No regression test needed; this is a deletion.

@MoonOld

MoonOld commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han @jackwener Exact-head follow-up is pushed at d84bfc901, merged with current main fd9ad4baa (merge, not rebase).

The latest review findings are addressed:

  • late same-ID reasoning deltas now start an undurable part after a finalized item; the regression persists the split and proves the following turn replays instead of throwing;
  • raw finishReason: error classification keeps its diagnostic code but is forced non-retryable, with a provider-agnostic 503 settlement regression, so this PR no longer widens retry behavior;
  • an exact raw-SSE fixture drives pinned @ai-sdk/open-responses@2.0.28 and asserts response.reasoning_text.delta text equals response.output_item.done.item.summary;
  • the duplicate reasoning-start switch label is deleted;
  • well-formed future durable-state versions now decode as unsupported and degrade out of replay, while malformed current-version state remains fail-closed.

The earlier invalid-ID isolation, metadata-less trailer settlement, shared request-customization seam, single Runtime provider-name authority, forced-tool-choice declaration, and real TUI evidence remain intact. Core still has zero diff. Runtime now explicitly documents that its provider policy is layered over Core's catalog-level adapter default and that account ModelInfo.apiProtocol wins. @jackwener, the remaining question is the maintainer-level layering decision: whether this Runtime execution overlay is the intended boundary; I do not see a remaining functional divergence.

Current-main integration also exposed three stale legacy surface fields in owned-candidate.test.ts that no longer typecheck on main; this head removes only those fixture fields, without changing Runtime Host production behavior.

Verification on the pushed tree: root npm run build:test passed; exact high-signal Runtime/Runtime Host set passed 453/453; Biome and diff checks passed; CI planner reports full=false, Core is not selected. Exact-head Actions is currently action_required and needs maintainer authorization: https://github.com/apache/maka/actions/runs/32374114698

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(providers): add Alibaba Token Plan Responses wire with durable Qwen reasoning replay

3 participants