Skip to content

Commit 64b95ad

Browse files
committed
chore(release): dashscope-sdk-official v1.25.5
1 parent 13892c5 commit 64b95ad

20 files changed

Lines changed: 592 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,21 @@
33
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
44
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
55

6-
## [Unreleased](https://github.com/dashscope/dashscope-sdk-nodejs/compare/v1.25.4...HEAD)
6+
## [Unreleased](https://github.com/dashscope/dashscope-sdk-nodejs/compare/v1.25.5...HEAD)
7+
8+
## [1.25.5](https://github.com/dashscope/dashscope-sdk-nodejs/releases/tag/v1.25.5) - 2026-07-08
9+
10+
### Added
11+
12+
- **ImageGeneration** (`src/aigc/imageGeneration/`): New module for wan2.6-image / wan2.6-t2i image generation, based on a `messages` interface. Supports synchronous (streaming / non-streaming) and asynchronous task modes. Includes `call()`, `asyncCall()`, `fetch()`, `wait()` methods, with `incremental_to_full` streaming merge and `wait_timeout` support. Exported as `ImageGeneration` and `ImageGenerationModels` from the package entry.
13+
- **Async task `wait_timeout`**: Added optional `wait_timeout` parameter (in seconds) to `ImageSynthesis`, `VideoSynthesis`, `Transcription`, and `BatchTextEmbedding` `call()` methods. When set to a value > 0, the method returns a timeout response (`code: 'WaitTaskTimeout'`, `status_code: 408`) instead of waiting indefinitely. Aligned with Python `BaseAsyncApi.wait(wait_timeout)`.
14+
- **MultiModalConversation incremental merge**: Added `incremental_to_full` streaming logic to `MultiModalConversation` — when `incremental_output` is `false` and the model supports it, the SDK transparently requests incremental output and merges deltas into a full response, matching `Generation` behavior.
15+
16+
### Fixed
17+
18+
- **Generation `User-Agent` header**: The `incremental_to_full` User-Agent flag now includes the SDK version (`dashscope-sdk-nodejs/x.y.z; incremental_to_full/N`) instead of overriding the entire User-Agent string. Previously the SDK version was lost during streaming requests.
19+
20+
Synced from [dashscope-sdk-python](https://github.com/dashscope/dashscope-sdk-python) **v1.25.24** (tag `v1.25.24`).
721

822
## [1.25.4](https://github.com/dashscope/dashscope-sdk-nodejs/releases/tag/v1.25.4) - 2026-06-09
923

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dashscope-sdk-official",
3-
"version": "1.25.4",
3+
"version": "1.25.5",
44
"description": "Official Node.js SDK for Alibaba Cloud Model Studio (DashScope) APIs",
55
"keywords": [
66
"dashscope-sdk-official",

src/aigc/generation/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import BaseApi from '../../common/baseApi';
2+
import { getDashscopeUserAgent } from '../../common/userAgent';
23
import { GenerateOptions } from '../../types';
34
import { shouldModifyIncrementalOutput } from '../../utils/paramUtils';
45
import GenerationResult from './result';
@@ -92,7 +93,7 @@ class Generation extends BaseApi {
9293
parameters.incremental_output = true;
9394
}
9495
if (stream) {
95-
streamHeaders['User-Agent'] = mergeIncremental ? 'incremental_to_full/1' : 'incremental_to_full/0';
96+
streamHeaders['User-Agent'] = `${getDashscopeUserAgent()}; incremental_to_full/${mergeIncremental ? '1' : '0'}`;
9697
}
9798

9899
return { input, parameters, streamHeaders, mergeIncremental };

src/aigc/imageGeneration/index.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import BaseApi from '../../common/baseApi';
2+
import { getDashscopeUserAgent } from '../../common/userAgent';
3+
import { waitForTask } from '../../common/asyncTask';
4+
import { ImageGenerationOptions } from '../../types';
5+
import { shouldModifyIncrementalOutput } from '../../utils/paramUtils';
6+
import GenerationResult from '../generation/result';
7+
import { parseStreamResult } from '../generation/streamUtils';
8+
9+
/** Model ids supported by ImageGeneration (aligned with Python `ImageGeneration.Models`). */
10+
export const ImageGenerationModels = {
11+
WAN2_6_IMAGE: 'wan2.6-image',
12+
WAN2_6_T2I: 'wan2.6-t2i',
13+
} as const;
14+
15+
/**
16+
* Image generation API based on a `messages` interface (wan2.6-image / wan2.6-t2i).
17+
*
18+
* Supports both synchronous (streaming / non-streaming) and asynchronous task modes.
19+
* Aligned with Python `dashscope.aigc.image_generation.ImageGeneration`.
20+
*/
21+
class ImageGeneration extends BaseApi {
22+
23+
/** Sync service path (multimodal-generation). */
24+
private static SYNC_SERVICE = 'services/aigc/multimodal-generation/generation';
25+
/** Async service path (image-generation). */
26+
private static ASYNC_SERVICE = 'services/aigc/image-generation/generation';
27+
28+
protected service = ImageGeneration.SYNC_SERVICE;
29+
30+
/** Synchronous / streaming call. */
31+
async call(options: ImageGenerationOptions) {
32+
const { model, messages, stream = false, incremental_output, n, is_async, wait_timeout, ...rest } = options;
33+
if (!model) throw new Error('model is required');
34+
if (!messages || messages.length === 0) throw new Error('messages is required');
35+
36+
const input: Record<string, unknown> = { messages };
37+
const parameters: Record<string, unknown> = { ...rest };
38+
39+
// Incremental merge logic (aligned with Python)
40+
let mergeIncremental = false;
41+
if (stream && shouldModifyIncrementalOutput(model) && incremental_output === false) {
42+
mergeIncremental = true;
43+
parameters.incremental_output = true;
44+
} else if (incremental_output !== undefined) {
45+
parameters.incremental_output = incremental_output;
46+
}
47+
48+
const data: Record<string, unknown> = { model, input };
49+
if (Object.keys(parameters).length) Object.assign(data, { parameters });
50+
51+
// Async mode: create task then wait
52+
if (is_async) {
53+
return this.asyncCallAndWait(data, wait_timeout);
54+
}
55+
56+
// Sync mode
57+
const headers: Record<string, string> = {};
58+
if (stream) {
59+
headers['Accept'] = 'text/event-stream';
60+
headers['X-Accel-Buffering'] = 'no';
61+
headers['X-DashScope-SSE'] = 'enable';
62+
headers['User-Agent'] = `${getDashscopeUserAgent()}; incremental_to_full/${mergeIncremental ? '1' : '0'}`;
63+
const result = await this.request({
64+
method: 'post',
65+
data,
66+
headers,
67+
responseType: 'stream',
68+
});
69+
const opts = mergeIncremental ? { mergeIncremental: true, n: (n as number) ?? 1 } : {};
70+
return parseStreamResult(result, opts);
71+
}
72+
73+
const result = await this.request({ method: 'post', data, headers: undefined });
74+
return new GenerationResult(result.status, result.data);
75+
}
76+
77+
/** Create an async image generation task. Returns task info with `output.task_id`. */
78+
async asyncCall(options: ImageGenerationOptions) {
79+
const { model, messages, ...rest } = options;
80+
if (!model) throw new Error('model is required');
81+
if (!messages || messages.length === 0) throw new Error('messages is required');
82+
const input: Record<string, unknown> = { messages };
83+
const parameters: Record<string, unknown> = {};
84+
for (const [k, v] of Object.entries(rest)) {
85+
if (v !== undefined && k !== 'is_async' && k !== 'wait_timeout') parameters[k] = v;
86+
}
87+
const data: Record<string, unknown> = { model, input };
88+
if (Object.keys(parameters).length) Object.assign(data, { parameters });
89+
const result = await this.request({
90+
method: 'post',
91+
service: ImageGeneration.ASYNC_SERVICE,
92+
headers: { 'X-DashScope-Async': 'enable' },
93+
data,
94+
});
95+
return result.data;
96+
}
97+
98+
/** Internal: send async request and wait for completion. */
99+
private async asyncCallAndWait(data: Record<string, unknown>, waitTimeout?: number) {
100+
const result = await this.request({
101+
method: 'post',
102+
service: ImageGeneration.ASYNC_SERVICE,
103+
headers: { 'X-DashScope-Async': 'enable' },
104+
data,
105+
});
106+
const createResult = result.data;
107+
const taskOpts = typeof waitTimeout === 'number' ? { waitTimeout } : undefined;
108+
return waitForTask(createResult, (taskId) => this.fetch(taskId), taskOpts);
109+
}
110+
111+
/** Fetch task status by task id. */
112+
async fetch(taskId: string) {
113+
const result = await this.request({
114+
service: 'tasks',
115+
api: taskId,
116+
method: 'get',
117+
headers: { 'X-DashScope-Async': 'enable' },
118+
});
119+
return result.data;
120+
}
121+
122+
/** Wait for an async task to complete, with optional `wait_timeout` (seconds). */
123+
async wait(taskId: string, waitTimeout?: number) {
124+
const taskOpts = typeof waitTimeout === 'number' ? { waitTimeout } : undefined;
125+
return waitForTask(
126+
{ output: { task_id: taskId } },
127+
(id) => this.fetch(id),
128+
taskOpts,
129+
);
130+
}
131+
}
132+
133+
export default ImageGeneration;

src/aigc/imageSynthesis/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ class ImageSynthesis extends BaseApi {
6262
}
6363

6464
async call(options: ImageSynthesisOptions) {
65-
const createResult = await this.asyncCall(options);
66-
return waitForTask(createResult, (taskId) => this.fetch(taskId));
65+
const { wait_timeout, ...callOptions } = options;
66+
const createResult = await this.asyncCall(callOptions);
67+
const taskOpts = typeof wait_timeout === 'number' ? { waitTimeout: wait_timeout } : undefined;
68+
return waitForTask(createResult, (taskId) => this.fetch(taskId), taskOpts);
6769
}
6870
}
6971

src/aigc/multimodalConversation/index.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
import BaseApi from '../../common/baseApi';
2+
import { getDashscopeUserAgent } from '../../common/userAgent';
23
import { MultiModalConversationOptions } from '../../types';
4+
import { shouldModifyIncrementalOutput } from '../../utils/paramUtils';
35
import GenerationResult from '../generation/result';
46
import { parseStreamResult } from '../generation/streamUtils';
57

68
class MultiModalConversation extends BaseApi {
79

810
protected service = 'services/aigc/multimodal-generation/generation';
911

10-
private async streamRequest(data: Record<string, unknown>) {
12+
private async streamRequest(data: Record<string, unknown>, mergeIncremental: boolean, n: number) {
13+
const headers: Record<string, string> = {
14+
'Accept': 'text/event-stream',
15+
'X-Accel-Buffering': 'no',
16+
'X-DashScope-SSE': 'enable',
17+
};
18+
headers['User-Agent'] = `${getDashscopeUserAgent()}; incremental_to_full/${mergeIncremental ? '1' : '0'}`;
1119
const result = await this.request({
1220
method: 'post',
1321
data,
14-
headers: {
15-
'Accept': 'text/event-stream',
16-
'X-Accel-Buffering': 'no',
17-
'X-DashScope-SSE': 'enable',
18-
},
22+
headers,
1923
responseType: 'stream',
2024
});
21-
return parseStreamResult(result);
25+
const opts = mergeIncremental ? { mergeIncremental: true, n } : {};
26+
return parseStreamResult(result, opts);
2227
}
2328

2429
private async syncRequest(data: Record<string, unknown>) {
@@ -30,17 +35,29 @@ class MultiModalConversation extends BaseApi {
3035
}
3136

3237
async call(options: MultiModalConversationOptions) {
33-
const { model, messages, stream = false, text, voice, language_type, ...rest } = options;
38+
const { model, messages, stream = false, text, voice, language_type, incremental_output, n, ...rest } = options;
3439
if (!model) throw new Error('Model is required!');
3540
const input: Record<string, unknown> = {};
3641
if (text) input.text = text;
3742
if (voice) input.voice = voice;
3843
if (language_type) input.language_type = language_type;
3944
if (Array.isArray(messages) && messages.length > 0) input.messages = messages;
45+
const parameters: Record<string, unknown> = { ...rest };
46+
if (n !== undefined) parameters.n = n;
47+
48+
// Check if we need to merge incremental output (aligned with Python)
49+
let mergeIncremental = false;
50+
if (stream && shouldModifyIncrementalOutput(model) && incremental_output === false) {
51+
mergeIncremental = true;
52+
parameters.incremental_output = true;
53+
} else if (incremental_output !== undefined) {
54+
parameters.incremental_output = incremental_output;
55+
}
56+
4057
const data: Record<string, unknown> = { model, input };
41-
if (Object.keys(rest).length) Object.assign(data, { parameters: rest });
58+
if (Object.keys(parameters).length) Object.assign(data, { parameters });
4259
if (stream) {
43-
return this.streamRequest(data);
60+
return this.streamRequest(data, mergeIncremental, n ?? 1);
4461
}
4562
return this.syncRequest(data);
4663
}

src/aigc/videoSynthesis/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ class VideoSynthesis extends BaseApi {
7878
}
7979

8080
async call(options: VideoSynthesisOptions) {
81-
const createResult = await this.asyncCall(options);
82-
return waitForTask(createResult, (taskId) => this.fetch(taskId));
81+
const { wait_timeout, ...callOptions } = options;
82+
const createResult = await this.asyncCall(callOptions);
83+
const taskOpts = typeof wait_timeout === 'number' ? { waitTimeout: wait_timeout } : undefined;
84+
return waitForTask(createResult, (taskId) => this.fetch(taskId), taskOpts);
8385
}
8486
}
8587

src/audio/asr/transcription/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ class Transcription extends BaseApi {
3535
}
3636

3737
async call(options: TranscriptionOptions) {
38-
const createResult = await this.asyncCall(options);
39-
return waitForTask(createResult, (taskId) => this.fetch(taskId));
38+
const { wait_timeout, ...callOptions } = options;
39+
const createResult = await this.asyncCall(callOptions);
40+
const taskOpts = typeof wait_timeout === 'number' ? { waitTimeout: wait_timeout } : undefined;
41+
return waitForTask(createResult, (taskId) => this.fetch(taskId), taskOpts);
4042
}
4143
}
4244

src/common/asyncTask.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,20 @@ export interface AsyncTaskFetchFn {
1313
(taskId: string): Promise<AsyncTaskResult>;
1414
}
1515

16+
/**
17+
* Options for `waitForTask`.
18+
*/
1619
export interface AsyncTaskOptions {
17-
/** Max wait time in ms (default 300000 = 5 minutes). */
20+
/** Max wait time in ms (default 300000 = 5 minutes). Throws on expiry. */
1821
maxWait?: number;
22+
23+
/**
24+
* Maximum seconds to wait for the task to complete.
25+
* Default is -1 (no timeout). When set to a value > 0, if the task
26+
* does not complete within this time, a timeout response object is
27+
* returned instead of waiting further (aligned with Python `wait_timeout`).
28+
*/
29+
waitTimeout?: number;
1930
}
2031

2132
const WAIT_MS_INITIAL = 1000;
@@ -35,11 +46,22 @@ export async function waitForTask(
3546
): Promise<AsyncTaskResult> {
3647
const taskId = createResult?.output?.task_id;
3748
if (typeof taskId !== 'string') return createResult;
38-
const { maxWait = DEFAULT_TIMEOUT_MS } = options;
49+
const { maxWait: rawMaxWait = DEFAULT_TIMEOUT_MS, waitTimeout = -1 } = options;
50+
// When waitTimeout is set, ensure maxWait is at least as long so it doesn't fire first
51+
const maxWait = waitTimeout > 0 ? Math.max(rawMaxWait, waitTimeout * 1000) : rawMaxWait;
3952
const start = Date.now();
4053
let waitMs = WAIT_MS_INITIAL;
4154
let step = 0;
4255
while (true) {
56+
// Python-aligned: waitTimeout in seconds, returns timeout response instead of throwing
57+
if (waitTimeout > 0 && Date.now() - start > waitTimeout * 1000) {
58+
return {
59+
request_id: taskId,
60+
status_code: 408,
61+
code: 'WaitTaskTimeout',
62+
message: `Wait task: ${taskId} timeout after ${waitTimeout} seconds.`,
63+
};
64+
}
4365
if (Date.now() - start > maxWait) throw new Error(`Task ${taskId} timed out after ${maxWait}ms`);
4466
try {
4567
const taskResult = await fetch(taskId);

src/dashscopeApi.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ImageSynthesis from './aigc/imageSynthesis';
55
import VideoSynthesis from './aigc/videoSynthesis';
66
import MultiModalConversation from './aigc/multimodalConversation';
77
import CodeGeneration from './aigc/codeGeneration';
8+
import ImageGeneration from './aigc/imageGeneration';
89
import File from './file';
910
import Models from './models';
1011
import Deployments from './deployments';
@@ -17,6 +18,7 @@ import {
1718
TranscriptionOptions, TextEmbeddingOptions, ChatCompletionOptions, ModelsListOptions,
1819
ImageSynthesisOptions, VideoSynthesisOptions, MultiModalConversationOptions, CodeGenerationOptions,
1920
BatchTextEmbeddingOptions, MultiModalEmbeddingOptions, UnderstandingOptions, TextReRankOptions,
21+
ImageGenerationOptions,
2022
DeploymentOptions, AssistantCreateOptions,
2123
ThreadCreateOptions, MessageCreateOptions, RunCreateOptions,
2224
} from './types';
@@ -60,6 +62,26 @@ class DashscopeApi {
6062
return new CodeGeneration(this.configuration).call(options);
6163
}
6264

65+
/** Create an image generation task (wan2.6-image / wan2.6-t2i, messages-based). */
66+
createImageGeneration(options: ImageGenerationOptions) {
67+
return new ImageGeneration(this.configuration).call(options);
68+
}
69+
70+
/** Create an async image generation task. Returns task info with `output.task_id`. */
71+
createImageGenerationAsync(options: ImageGenerationOptions) {
72+
return new ImageGeneration(this.configuration).asyncCall(options);
73+
}
74+
75+
/** Fetch image generation task status by task id. */
76+
fetchImageGenerationTask(taskId: string) {
77+
return new ImageGeneration(this.configuration).fetch(taskId);
78+
}
79+
80+
/** Wait for an image generation task to complete, with optional `wait_timeout` (seconds). */
81+
waitImageGenerationTask(taskId: string, waitTimeout?: number) {
82+
return new ImageGeneration(this.configuration).wait(taskId, waitTimeout);
83+
}
84+
6385
listModels(options?: ModelsListOptions) {
6486
return new Models(this.configuration).list(options || {});
6587
}

0 commit comments

Comments
 (0)