feat(providers): add Qwen Token Plan (global + China)
Providers 14-15 (17th & 18th registry variants), Alibaba DashScope
compatible-mode. Global: token-plan.ap-southeast-1.maas.aliyuncs.com,
QWEN_TOKEN_PLAN_API_KEY, models.dev alibaba-token-plan. China:
token-plan.cn-beijing.maas.aliyuncs.com, QWEN_TOKEN_PLAN_CN_API_KEY,
alibaba-token-plan-cn. Both Bearer + OpenAI listing + ChatCompletions +
Passthrough (stream_options.include_usage is documented-supported).
/models is auth-gated (401 without a key) so it doubles as the validator;
metadata from models.dev enrichment. The token plan is a multi-vendor
catalog (deepseek/kimi/minimax/glm/qwen); restrict_to_enriched keeps the 15
tool-calling chat models and drops the 4 qwen-image/wan image generators.
Review downgraded two flagged concerns: the enable_thinking non-streaming
400 cannot occur (Kigi never issues non-streaming ChatCompletions in
production — all inference streams), and Qwen thinking is NOT invisible
(reasoning_content is parsed regardless of the Passthrough dialect). No
defects. Residual (logged): restrict does an exact id-match of live /models
ids vs the models.dev keys; a mismatch fails safe (0 models) — verify with a
real key. Snapshot already bundles both providers (regenerated in ebf1105).
Tests: global e2e proves enrichment-supplied context (wire carries none),
non-vacuous tool_call restriction (qwen-image dropped), bare-id round-trip,
Passthrough; both variants' validation tests hit /models (401 reject) and
assert the correct per-variant console host. Registry at 18; picker 19 rows.
This commit is contained in:
@@ -620,7 +620,9 @@ mod tests {
|
||||
"cerebras",
|
||||
"nvidia",
|
||||
"vercel-ai-gateway",
|
||||
"xai"
|
||||
"xai",
|
||||
"qwen-token-plan",
|
||||
"qwen-token-plan-cn"
|
||||
]
|
||||
);
|
||||
assert_eq!(default_id(&built), Some(XAI_API_KEY_METHOD_ID));
|
||||
@@ -659,7 +661,9 @@ mod tests {
|
||||
"cerebras",
|
||||
"nvidia",
|
||||
"vercel-ai-gateway",
|
||||
"xai"
|
||||
"xai",
|
||||
"qwen-token-plan",
|
||||
"qwen-token-plan-cn"
|
||||
]
|
||||
);
|
||||
assert_eq!(default_id(&built), Some(CACHED_TOKEN_AUTH_METHOD_ID));
|
||||
@@ -691,7 +695,9 @@ mod tests {
|
||||
"cerebras",
|
||||
"nvidia",
|
||||
"vercel-ai-gateway",
|
||||
"xai"
|
||||
"xai",
|
||||
"qwen-token-plan",
|
||||
"qwen-token-plan-cn"
|
||||
]
|
||||
);
|
||||
assert_eq!(default_id(&built), Some(CACHED_TOKEN_AUTH_METHOD_ID));
|
||||
@@ -726,7 +732,9 @@ mod tests {
|
||||
"cerebras",
|
||||
"nvidia",
|
||||
"vercel-ai-gateway",
|
||||
"xai"
|
||||
"xai",
|
||||
"qwen-token-plan",
|
||||
"qwen-token-plan-cn"
|
||||
]
|
||||
);
|
||||
assert_eq!(default_id(&built), None);
|
||||
@@ -1006,4 +1014,56 @@ mod tests {
|
||||
.await
|
||||
.expect("200 from /models must validate the key");
|
||||
}
|
||||
|
||||
/// Qwen Token Plan (global): DashScope /models is auth-gated (401 for a bad
|
||||
/// key), so it validates the key; the error names the Model Studio console.
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn qwen_token_plan_validates_against_models_and_rejects_bad_key() {
|
||||
use wiremock::matchers::{method, path};
|
||||
let server = wiremock::MockServer::start().await;
|
||||
wiremock::Mock::given(method("GET"))
|
||||
.and(path("/models"))
|
||||
.respond_with(wiremock::ResponseTemplate::new(401))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let _base = EnvGuard::set(kigi_models::QWEN_TOKEN_PLAN_BASE_URL_ENV, &server.uri());
|
||||
let err =
|
||||
authenticate_platform_api_key(kigi_models::PlatformId::QwenTokenPlan, Some("qtp-bad"))
|
||||
.await
|
||||
.expect_err("a 401 from /models must reject the key");
|
||||
assert_eq!(
|
||||
err.message,
|
||||
"Invalid API key for qwen-token-plan \u{2014} check your key on \
|
||||
modelstudio.console.alibabacloud.com"
|
||||
);
|
||||
}
|
||||
|
||||
/// Qwen Token Plan (China): distinct base URL + console host; same
|
||||
/// auth-gated /models validation.
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn qwen_token_plan_cn_validates_against_models_and_rejects_bad_key() {
|
||||
use wiremock::matchers::{method, path};
|
||||
let server = wiremock::MockServer::start().await;
|
||||
wiremock::Mock::given(method("GET"))
|
||||
.and(path("/models"))
|
||||
.respond_with(wiremock::ResponseTemplate::new(401))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let _base = EnvGuard::set(kigi_models::QWEN_TOKEN_PLAN_CN_BASE_URL_ENV, &server.uri());
|
||||
let err = authenticate_platform_api_key(
|
||||
kigi_models::PlatformId::QwenTokenPlanCn,
|
||||
Some("qtp-cn-bad"),
|
||||
)
|
||||
.await
|
||||
.expect_err("a 401 from /models must reject the key");
|
||||
assert_eq!(
|
||||
err.message,
|
||||
"Invalid API key for qwen-token-plan-cn \u{2014} check your key on \
|
||||
bailian.console.aliyun.com"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user